diff --git a/app/DeactivatedUser.php b/app/DeactivatedUser.php new file mode 100644 index 0000000..7cc4b55 --- /dev/null +++ b/app/DeactivatedUser.php @@ -0,0 +1,18 @@ + 'required|string|regex:/^[a-zA-Z0-9_-]+$/u|max:15|unique:users', + 'name' => 'required|string|regex:/^[a-zA-Z0-9_-]+$/u|max:15|unique:users|unique:deactivated_users', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6|confirmed' ]; diff --git a/app/Http/Controllers/EjaculationController.php b/app/Http/Controllers/EjaculationController.php index aa770ca..c0d6277 100644 --- a/app/Http/Controllers/EjaculationController.php +++ b/app/Http/Controllers/EjaculationController.php @@ -65,6 +65,10 @@ class EjaculationController extends Controller if (!empty($inputs['tags'])) { $tags = explode(' ', $inputs['tags']); foreach ($tags as $tag) { + if ($tag === '') { + continue; + } + $tag = Tag::firstOrCreate(['name' => $tag]); $tagIds[] = $tag->id; } @@ -106,9 +110,7 @@ class EjaculationController extends Controller { $ejaculation = Ejaculation::findOrFail($id); - if (Auth::user()->cant('edit', $ejaculation)) { - abort(403); - } + $this->authorize('edit', $ejaculation); return view('ejaculation.edit')->with(compact('ejaculation')); } @@ -117,9 +119,7 @@ class EjaculationController extends Controller { $ejaculation = Ejaculation::findOrFail($id); - if (Auth::user()->cant('edit', $ejaculation)) { - abort(403); - } + $this->authorize('edit', $ejaculation); $inputs = $request->all(); @@ -155,6 +155,10 @@ class EjaculationController extends Controller if (!empty($inputs['tags'])) { $tags = explode(' ', $inputs['tags']); foreach ($tags as $tag) { + if ($tag === '') { + continue; + } + $tag = Tag::firstOrCreate(['name' => $tag]); $tagIds[] = $tag->id; } @@ -172,9 +176,7 @@ class EjaculationController extends Controller { $ejaculation = Ejaculation::findOrFail($id); - if (Auth::user()->cant('edit', $ejaculation)) { - abort(403); - } + $this->authorize('edit', $ejaculation); $user = User::findOrFail($ejaculation->user_id); $ejaculation->tags()->detach(); diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index 76c9676..78636a9 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -2,10 +2,14 @@ namespace App\Http\Controllers; +use App\DeactivatedUser; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; +use Illuminate\Validation\ValidationException; class SettingController extends Controller { @@ -67,6 +71,51 @@ class SettingController extends Controller return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。'); } + public function deactivate() + { + return view('setting.deactivate'); + } + + public function destroyUser(Request $request) + { + // パスワードチェック + $validated = $request->validate([ + 'password' => 'required|string' + ]); + + if (!Hash::check($validated['password'], Auth::user()->getAuthPassword())) { + throw ValidationException::withMessages([ + 'password' => 'パスワードが正しくありません。' + ]); + } + + // データの削除 + set_time_limit(0); + DB::transaction(function () { + $user = Auth::user(); + + // 関連レコードの削除 + // TODO: 別にDELETE文相当のクエリを一発発行するだけでもいい? + foreach ($user->ejaculations as $ejaculation) { + $ejaculation->delete(); + } + foreach ($user->likes as $like) { + $like->delete(); + } + + // 先にログアウトしないとユーザーは消せない + Auth::logout(); + + // ユーザーの削除 + $user->delete(); + + // ユーザー名履歴に追記 + DeactivatedUser::create(['name' => $user->name]); + }); + + return view('setting.deactivated'); + } + // ( ◠‿◠ )☛ここに気づいたか・・・消えてもらう ▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂うわあああああああ // public function password() // { diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php new file mode 100644 index 0000000..811153f --- /dev/null +++ b/app/Http/Controllers/TagController.php @@ -0,0 +1,37 @@ +join('ejaculation_tag', 'tags.id', '=', 'ejaculation_tag.tag_id') + ->join('ejaculations', 'ejaculations.id', '=', 'ejaculation_tag.ejaculation_id') + ->join('users', 'users.id', '=', 'ejaculations.user_id') + ->where('ejaculations.is_private', false) + ->where(function ($query) { + $query->where('users.is_protected', false); + if (Auth::check()) { + $query->orWhere('users.id', Auth::id()); + } + }) + ->groupBy('tags.name') + ->orderByDesc('checkins_count') + ->orderBy('tags.name') + ->paginate(100); + + return view('tag.index', compact('tags')); + } +} diff --git a/app/MetadataResolver/ActivityPubResolver.php b/app/MetadataResolver/ActivityPubResolver.php index 7cf3e00..c0580c8 100644 --- a/app/MetadataResolver/ActivityPubResolver.php +++ b/app/MetadataResolver/ActivityPubResolver.php @@ -37,6 +37,10 @@ class ActivityPubResolver implements Resolver, Parser $activityOrObject = json_decode($json, true); $object = $activityOrObject['object'] ?? $activityOrObject; + if ($object['type'] !== 'Note') { + throw new UnsupportedContentException('Unsupported object type: ' . $object['type']); + } + $metadata = new Metadata(); $metadata->title = isset($object['attributedTo']) ? $this->getTitleFromActor($object['attributedTo']) : ''; diff --git a/app/MetadataResolver/DLsiteResolver.php b/app/MetadataResolver/DLsiteResolver.php index 957a5d1..f9aee84 100644 --- a/app/MetadataResolver/DLsiteResolver.php +++ b/app/MetadataResolver/DLsiteResolver.php @@ -53,17 +53,19 @@ class DLsiteResolver implements Resolver public function resolve(string $url): Metadata { //アフィリエイトの場合は普通のURLに変換 - if (strpos($url, '/dlaf/=/link/') !== false) { - preg_match('~www\.dlsite\.com/(?P.+)/dlaf/=/link/work/aid/.+/id/(?P..\d+)(\.html)?~', $url, $matches); + // ID型 + if (preg_match('~/dlaf/=(/.+/.+)?/link/~', $url)) { + preg_match('~www\.dlsite\.com/(?P.+)/dlaf/=(/.+/.+)?/link/work/aid/(?P.+)/id/(?P..\d+)(\.html)?~', $url, $matches); $url = "https://www.dlsite.com/{$matches['genre']}/work/=/product_id/{$matches['titleId']}.html"; } + // URL型 if (strpos($url, '/dlaf/=/aid/') !== false) { preg_match('~www\.dlsite\.com/.+/dlaf/=/aid/.+/url/(?P.+)~', $url, $matches); - $affiliate_url = urldecode($matches['url']); - if (preg_match('~www\.dlsite\.com/.+/(work|announce)/=/product_id/..\d+(\.html)?~', $affiliate_url, $matches)) { - $url = $affiliate_url; + $affiliateUrl = urldecode($matches['url']); + if (preg_match('~www\.dlsite\.com/.+/(work|announce)/=/product_id/..\d+(\.html)?~', $affiliateUrl, $matches)) { + $url = $affiliateUrl; } else { - throw new \RuntimeException("アフィリエイト先のリンクがDLsiteのタイトルではありません: $affiliate_url"); + throw new \RuntimeException("アフィリエイト先のリンクがDLsiteのタイトルではありません: $affiliateUrl"); } } diff --git a/app/MetadataResolver/HentaiFoundryResolver.php b/app/MetadataResolver/HentaiFoundryResolver.php new file mode 100644 index 0000000..87131a5 --- /dev/null +++ b/app/MetadataResolver/HentaiFoundryResolver.php @@ -0,0 +1,56 @@ +client = $client; + $this->ogpResolver = $ogpResolver; + } + + private function nbsp2space(string $string): string + { + return str_replace("\xc2\xa0", ' ', $string); + } + + private function br2nl(string $string): string + { + return str_replace('
', PHP_EOL, $string); + } + + public function resolve(string $url): Metadata + { + $res = $this->client->get( + http_build_url($url, ['query' => 'enterAgree=1']), + ['cookies' => new CookieJar()] + ); + + $metadata = new Metadata(); + $crawler = new Crawler((string) $res->getBody()); + + $author = $crawler->filter('#picBox .boxtitle a')->text(); + $description = trim(strip_tags($this->nbsp2space($this->br2nl($crawler->filter('.picDescript')->html())))); + + $metadata->title = $crawler->filter('#picBox .boxtitle .imageTitle')->text(); + $metadata->description = 'by ' . $author . PHP_EOL . $description; + $metadata->image = 'https:' . $crawler->filter('img[src^="//picture"]')->attr('src'); + $metadata->tags = $crawler->filter('a[rel="tag"]')->extract('_text'); + + return $metadata; + } +} diff --git a/app/MetadataResolver/Kb10uyShortStoryServerResolver.php b/app/MetadataResolver/Kb10uyShortStoryServerResolver.php new file mode 100644 index 0000000..a29389e --- /dev/null +++ b/app/MetadataResolver/Kb10uyShortStoryServerResolver.php @@ -0,0 +1,36 @@ +client = $client; + } + + public function resolve(string $url): Metadata + { + $res = $this->client->get($url); + $html = (string) $res->getBody(); + $crawler = new Crawler($html); + $infoElement = $crawler->filter('div.post-info'); + + $metadata = new Metadata(); + $metadata->title = $infoElement->filter('h1')->text(); + $metadata->description = trim($infoElement->filter('p.summary')->text()); + $metadata->tags = array_values(array_diff($infoElement->filter('ul.tags > li.tag > a')->extract('_text'), self::EXCLUDED_TAGS)); + + return $metadata; + } +} diff --git a/app/MetadataResolver/MetadataResolver.php b/app/MetadataResolver/MetadataResolver.php index 9e19fe9..83351db 100644 --- a/app/MetadataResolver/MetadataResolver.php +++ b/app/MetadataResolver/MetadataResolver.php @@ -16,11 +16,11 @@ class MetadataResolver implements Resolver '~ec\.toranoana\.(jp|shop)/(tora|joshi)(_[rd]+)?/(ec|digi)/item/~' => ToranoanaResolver::class, '~iwara\.tv/(videos|images)/.*~' => IwaraResolver::class, '~www\.dlsite\.com/.*/(work|announce)/=/product_id/..\d+(\.html)?~' => DLsiteResolver::class, - '~www\.dlsite\.com/.*/dlaf/=/link/(work|announce)/aid/.+/..\d+(\.html)?~' => DLsiteResolver::class, + '~www\.dlsite\.com/.*/dlaf/=(/.+/.+)?/link/work/aid/.+(/id)?/..\d+(\.html)?~' => DLsiteResolver::class, '~www\.dlsite\.com/.*/dlaf/=/aid/.+/url/.+~' => DLsiteResolver::class, '~dlsite\.jp/...tw/..\d+~' => DLsiteResolver::class, '~www\.pixiv\.net/member_illust\.php\?illust_id=\d+~' => PixivResolver::class, - '~www\.pixiv\.net/artworks/\d+~' => PixivResolver::class, + '~www\.pixiv\.net/(en/)?artworks/\d+~' => PixivResolver::class, '~www\.pixiv\.net/user/\d+/series/\d+~' => PixivResolver::class, '~fantia\.jp/posts/\d+~' => FantiaResolver::class, '~dmm\.co\.jp/~' => FanzaResolver::class, @@ -32,6 +32,8 @@ class MetadataResolver implements Resolver '~(adult\.)?contents\.fc2\.com\/article_search\.php\?id=\d+~' => FC2ContentsResolver::class, '~store\.steampowered\.com/app/\d+~' => SteamResolver::class, '~www\.xtube\.com/video-watch/.*-\d+$~'=> XtubeResolver::class, + '~ss\.kb10uy\.org/posts/\d+$~' => Kb10uyShortStoryServerResolver::class, + '~www\.hentai-foundry\.com/pictures/user/.+/\d+/.+~'=> HentaiFoundryResolver::class, ]; public $mimeTypes = [ @@ -47,16 +49,19 @@ class MetadataResolver implements Resolver { foreach ($this->rules as $pattern => $class) { if (preg_match($pattern, $url) === 1) { - /** @var Resolver $resolver */ - $resolver = app($class); + try { + /** @var Resolver $resolver */ + $resolver = app($class); - return $resolver->resolve($url); + return $resolver->resolve($url); + } catch (UnsupportedContentException $e) { + } } } - $result = $this->resolveWithAcceptHeader($url); - if ($result !== null) { - return $result; + try { + return $this->resolveWithAcceptHeader($url); + } catch (UnsupportedContentException $e) { } if (isset($this->defaultResolver)) { @@ -69,7 +74,7 @@ class MetadataResolver implements Resolver throw new \UnexpectedValueException('URL not matched.'); } - public function resolveWithAcceptHeader(string $url): ?Metadata + public function resolveWithAcceptHeader(string $url): Metadata { try { // Rails等はAcceptに */* が入っていると、ブラウザの適当なAcceptヘッダだと判断して全部無視してしまう。 @@ -114,6 +119,6 @@ class MetadataResolver implements Resolver // 5xx は変なAcceptが原因かもしれない(?)ので無視してフォールバック } - return null; + throw new UnsupportedContentException(); } } diff --git a/app/MetadataResolver/NijieResolver.php b/app/MetadataResolver/NijieResolver.php index 8ce9315..4bbe5ca 100644 --- a/app/MetadataResolver/NijieResolver.php +++ b/app/MetadataResolver/NijieResolver.php @@ -36,12 +36,16 @@ class NijieResolver implements Resolver $metadata = $this->ogpResolver->parse($html); $crawler = new Crawler($html); - // DomCrawler内でjson内の日本語がHTMLエンティティに変換されるのでhtml_entity_decode - $json = html_entity_decode($crawler->filter('script[type="application/ld+json"]')->first()->text()); + $json = $crawler->filter('script[type="application/ld+json"]')->first()->text(); // 改行がそのまま入っていることがあるのでデコード前にエスケープが必要 $data = json_decode(preg_replace('/\r?\n/', '\n', $json), true); + // DomCrawler内でjson内の日本語がHTMLエンティティに変換されるので、全要素に対してhtml_entity_decode + array_walk_recursive($data, function (&$v) { + $v = html_entity_decode($v); + }); + $metadata->title = $data['name']; $metadata->description = '投稿者: ' . $data['author']['name'] . PHP_EOL . $data['description']; if ( diff --git a/app/MetadataResolver/PixivResolver.php b/app/MetadataResolver/PixivResolver.php index e6690ac..c7435a8 100644 --- a/app/MetadataResolver/PixivResolver.php +++ b/app/MetadataResolver/PixivResolver.php @@ -45,8 +45,8 @@ class PixivResolver implements Resolver } $page = 0; - if (preg_match('~www\.pixiv\.net/artworks/(\d+)~', $url, $matches)) { - $illustId = $matches[1]; + if (preg_match('~www\.pixiv\.net/(en/)?artworks/(?P\d+)~', $url, $matches)) { + $illustId = $matches['illustId']; } else { parse_str(parse_url($url, PHP_URL_QUERY), $params); $illustId = $params['illust_id']; diff --git a/app/MetadataResolver/UnsupportedContentException.php b/app/MetadataResolver/UnsupportedContentException.php new file mode 100644 index 0000000..6ca6ed3 --- /dev/null +++ b/app/MetadataResolver/UnsupportedContentException.php @@ -0,0 +1,12 @@ +client = $client; + $this->ogpResolver = $ogpResolver; } public function resolve(string $url): Metadata @@ -25,17 +30,14 @@ class XtubeResolver implements Resolver $res = $this->client->get($url); $html = (string) $res->getBody(); - $metadata = new Metadata(); + $metadata = $this->ogpResolver->parse($html); $crawler = new Crawler($html); - // poster URL抽出 - $playerConfig = explode("\n", trim($crawler->filter('#playerWrapper script')->last()->text())); - preg_match('~https:\\\/\\\/cdn\d+-s-hw-e5\.xtube\.com\\\/m=(?P.{8})\\\/videos\\\/\d{6}\\\/\d{2}\\\/.{5}-.{4}-\\\/original\\\/\d+\.jpg~', $playerConfig[0], $matches); - $metadata->image = str_replace('\/', '/', $matches[0]); - $metadata->title = trim($crawler->filter('.underPlayerRateForm h1')->text('')); $metadata->description = trim($crawler->filter('.fullDescription ')->text('')); - $metadata->tags = $crawler->filter('.tagsCategories a')->extract('_text'); + $metadata->image = str_replace('m=eSuQ8f', 'm=eaAaaEFb', $metadata->image); + $metadata->image = str_replace('240X180', 'original', $metadata->image); + $metadata->tags = array_map('trim', $crawler->filter('.tagsCategories a')->extract('_text')); return $metadata; } diff --git a/app/User.php b/app/User.php index 1cafaa2..c82e5d0 100644 --- a/app/User.php +++ b/app/User.php @@ -53,6 +53,11 @@ class User extends Authenticatable return Auth::check() && $this->id === Auth::user()->id; } + public function ejaculations() + { + return $this->hasMany(Ejaculation::class); + } + public function likes() { return $this->hasMany(Like::class); diff --git a/composer.json b/composer.json index 2c46aed..3b89d39 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "doctrine/dbal": "^2.9", "fideloper/proxy": "~3.3", "guzzlehttp/guzzle": "^6.3", + "jakeasmith/http_build_url": "^1.0", "laravel/framework": "5.5.*", "laravel/tinker": "~1.0", "misd/linkify": "^1.1", diff --git a/composer.lock b/composer.lock index eaf81d3..fb352b4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "28abd730d4572663d10ae815393c73cd", + "content-hash": "b6dfb80c350a7276bb2513a1aeb3d602", "packages": [ { "name": "anhskohbo/no-captcha", @@ -806,6 +806,39 @@ ], "time": "2019-07-01T23:21:34+00:00" }, + { + "name": "jakeasmith/http_build_url", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/jakeasmith/http_build_url.git", + "reference": "93c273e77cb1edead0cf8bcf8cd2003428e74e37" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jakeasmith/http_build_url/zipball/93c273e77cb1edead0cf8bcf8cd2003428e74e37", + "reference": "93c273e77cb1edead0cf8bcf8cd2003428e74e37", + "shasum": "" + }, + "type": "library", + "autoload": { + "files": [ + "src/http_build_url.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jake A. Smith", + "email": "theman@jakeasmith.com" + } + ], + "description": "Provides functionality for http_build_url() to environments without pecl_http.", + "time": "2017-05-01T15:36:40+00:00" + }, { "name": "jakub-onderka/php-console-color", "version": "v0.2", diff --git a/database/factories/EjaculationFactory.php b/database/factories/EjaculationFactory.php new file mode 100644 index 0000000..65fbb6e --- /dev/null +++ b/database/factories/EjaculationFactory.php @@ -0,0 +1,12 @@ +define(Ejaculation::class, function (Faker $faker) { + return [ + 'ejaculated_date' => $faker->date('Y-m-d H:i:s'), + 'note' => $faker->text, + ]; +}); diff --git a/database/factories/LikeFactory.php b/database/factories/LikeFactory.php new file mode 100644 index 0000000..38e82a4 --- /dev/null +++ b/database/factories/LikeFactory.php @@ -0,0 +1,10 @@ +define(App\Like::class, function (Faker $faker) { + return [ + // + ]; +}); diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php deleted file mode 100644 index 7926c79..0000000 --- a/database/factories/ModelFactory.php +++ /dev/null @@ -1,24 +0,0 @@ -define(App\User::class, function (Faker\Generator $faker) { - static $password; - - return [ - 'name' => $faker->name, - 'email' => $faker->unique()->safeEmail, - 'password' => $password ?: $password = bcrypt('secret'), - 'remember_token' => str_random(10), - ]; -}); diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..fed18ec --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,21 @@ +define(App\User::class, function (Faker\Generator $faker) { + static $password; + + return [ + 'name' => substr($faker->userName, 0, 15), + 'email' => $faker->unique()->safeEmail, + 'password' => $password ?: $password = bcrypt('secret'), + 'remember_token' => str_random(10), + 'display_name' => substr($faker->name, 0, 20), + 'is_protected' => false, + 'accept_analytics' => false, + 'private_likes' => false, + ]; +}); + +$factory->state(App\User::class, 'protected', [ + 'is_protected' => true, +]); diff --git a/database/migrations/2019_11_14_003449_create_deactivated_users_table.php b/database/migrations/2019_11_14_003449_create_deactivated_users_table.php new file mode 100644 index 0000000..3e4f475 --- /dev/null +++ b/database/migrations/2019_11_14_003449_create_deactivated_users_table.php @@ -0,0 +1,33 @@ +string('name', 15); + $table->timestamps(); + + $table->primary('name'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('deactivated_users'); + } +} diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index 629d2dc..3daf978 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -85,6 +85,9 @@ $(() => { if (xhr.status === 409) { callback(JSON.parse(xhr.responseText)); return; + } else if (xhr.status === 401) { + alert('いいねするためにはログインしてください。'); + return; } console.error(xhr); @@ -98,4 +101,4 @@ $(() => { $this.siblings(".card-link").removeClass("card-spoiler"); $this.remove(); }); -}); \ No newline at end of file +}); diff --git a/resources/assets/js/setting/deactivate.js b/resources/assets/js/setting/deactivate.js new file mode 100644 index 0000000..e99acd0 --- /dev/null +++ b/resources/assets/js/setting/deactivate.js @@ -0,0 +1,5 @@ +$('#deactivate-form').on('submit', function () { + if (!confirm('本当にアカウントを削除してもよろしいですか?')) { + return false; + } +}); diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss index 0fe2b4f..6f38126 100644 --- a/resources/assets/sass/app.scss +++ b/resources/assets/sass/app.scss @@ -13,3 +13,6 @@ $primary: #e53fb1; // Components @import "components/ejaculation"; @import "components/link-card"; + +// Tag +@import "tag/index"; diff --git a/resources/assets/sass/tag/_index.scss b/resources/assets/sass/tag/_index.scss new file mode 100644 index 0000000..eed3db6 --- /dev/null +++ b/resources/assets/sass/tag/_index.scss @@ -0,0 +1,22 @@ +.tags { + & > .btn-tag { + width: 100%; + + .tag-name { + display: inline-block; + max-width: 80%; + overflow: hidden; + line-height: 40px; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; + } + + .checkins-count { + display: inline-block; + line-height: 40px; + white-space: nowrap; + vertical-align: middle; + } + } +} diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 14943e4..00e0d46 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -54,6 +54,9 @@ いいね 設定 + @can ('admin') + 管理 + @endcan ログアウト @@ -79,6 +82,9 @@ + {{----}} @@ -137,6 +143,13 @@ オカズ +
+ +
+
+
{{--
ランキング diff --git a/resources/views/setting/base.blade.php b/resources/views/setting/base.blade.php index d3274cf..2d53e94 100644 --- a/resources/views/setting/base.blade.php +++ b/resources/views/setting/base.blade.php @@ -10,6 +10,8 @@ href="{{ route('setting') }}"> プロフィール プライバシー + アカウントの削除 {{-- パスワード--}}
@@ -19,4 +21,4 @@
-@endsection \ No newline at end of file +@endsection diff --git a/resources/views/setting/deactivate.blade.php b/resources/views/setting/deactivate.blade.php new file mode 100644 index 0000000..07df952 --- /dev/null +++ b/resources/views/setting/deactivate.blade.php @@ -0,0 +1,32 @@ +@extends('setting.base') + +@section('title', 'アカウントの削除') + +@section('tab-content') +

アカウントの削除

+
+

Tissueからあなたのアカウントに関する情報を削除します。

+
+

警告

+

削除はすぐに実行され、取り消すことはできません!

+

なりすましを防止するため、あなたのユーザー名はサーバーに記録されます。今後、同じユーザー名を使って再登録することはできません。

+
+ +
+ {{ csrf_field() }} +
+

上記の条件に同意してアカウントを削除する場合は、パスワードを入力して削除ボタンを押してください。

+ + + @if ($errors->has('password')) +
{{ $errors->first('password') }}
+ @endif +
+ + +
+@endsection + +@push('script') + +@endpush diff --git a/resources/views/setting/deactivated.blade.php b/resources/views/setting/deactivated.blade.php new file mode 100644 index 0000000..1e5fafa --- /dev/null +++ b/resources/views/setting/deactivated.blade.php @@ -0,0 +1,16 @@ +@extends('layouts.base') + +@section('title', 'アカウント削除完了') + +@section('content') +
+

アカウントを削除しました

+
+

Tissueをご利用いただき、ありがとうございました。

+

トップページへ

+
+@endsection + +@push('script') + +@endpush diff --git a/resources/views/tag/index.blade.php b/resources/views/tag/index.blade.php new file mode 100644 index 0000000..e097f4c --- /dev/null +++ b/resources/views/tag/index.blade.php @@ -0,0 +1,20 @@ +@extends('layouts.base') + +@section('title', 'タグ一覧') + +@section('content') +
+

タグ一覧

+

公開チェックインに付けられているタグを、チェックイン数の多い順で表示しています。

+
+
+ @foreach($tags as $tag) + + @endforeach +
+ {{ $tags->links(null, ['className' => 'mt-4 justify-content-center']) }} +
+
+@endsection diff --git a/routes/web.php b/routes/web.php index 6eba7e1..184bd18 100644 --- a/routes/web.php +++ b/routes/web.php @@ -36,6 +36,8 @@ Route::middleware('auth')->group(function () { Route::post('/setting/profile', 'SettingController@updateProfile')->name('setting.profile.update'); Route::get('/setting/privacy', 'SettingController@privacy')->name('setting.privacy'); Route::post('/setting/privacy', 'SettingController@updatePrivacy')->name('setting.privacy.update'); + Route::get('/setting/deactivate', 'SettingController@deactivate')->name('setting.deactivate'); + Route::post('/setting/deactivate', 'SettingController@destroyUser')->name('setting.deactivate.destroy'); // Route::get('/setting/password', 'SettingController@password')->name('setting.password'); }); @@ -46,6 +48,8 @@ Route::redirect('/search', '/search/checkin', 301); Route::get('/search/checkin', 'SearchController@index')->name('search'); Route::get('/search/related-tag', 'SearchController@relatedTag')->name('search.related-tag'); +Route::get('/tag', 'TagController@index')->name('tag'); + Route::middleware('can:admin') ->namespace('Admin') ->prefix('admin') diff --git a/tests/Feature/SettingTest.php b/tests/Feature/SettingTest.php new file mode 100644 index 0000000..c54fd84 --- /dev/null +++ b/tests/Feature/SettingTest.php @@ -0,0 +1,64 @@ +create(); + $ejaculation = factory(Ejaculation::class)->create(['user_id' => $user->id]); + + $anotherUser = factory(User::class)->create(); + $anotherEjaculation = factory(Ejaculation::class)->create(['user_id' => $anotherUser->id]); + + $like = factory(Like::class)->create([ + 'user_id' => $user->id, + 'ejaculation_id' => $anotherEjaculation->id, + ]); + $anotherLike = factory(Like::class)->create([ + 'user_id' => $anotherUser->id, + 'ejaculation_id' => $ejaculation->id, + ]); + + $token = $this->getCsrfToken($user, '/setting/deactivate'); + $response = $this->actingAs($user) + ->followingRedirects() + ->post('/setting/deactivate', [ + '_token' => $token, + 'password' => 'secret', + ]); + + $response->assertStatus(200) + ->assertViewIs('setting.deactivated'); + $this->assertGuest(); + $this->assertDatabaseMissing('users', ['id' => $user->id]); + $this->assertDatabaseMissing('ejaculations', ['id' => $ejaculation->id]); + $this->assertDatabaseMissing('likes', ['id' => $like->id]); + $this->assertDatabaseMissing('likes', ['id' => $anotherLike->id]); + $this->assertDatabaseHas('deactivated_users', ['name' => $user->name]); + } + + /** + * テスト対象を呼び出す前にGETリクエストを行い、CSRFトークンを得る + * @param Authenticatable $user 認証情報 + * @param string $uri リクエスト先 + * @return string CSRFトークン + */ + private function getCsrfToken(Authenticatable $user, string $uri): string + { + $response = $this->actingAs($user)->get($uri); + $crawler = new Crawler($response->getContent()); + + return $crawler->filter('input[name=_token]')->attr('value'); + } +} diff --git a/tests/Unit/MetadataResolver/DLsiteResolverTest.php b/tests/Unit/MetadataResolver/DLsiteResolverTest.php index ff164ba..3267039 100644 --- a/tests/Unit/MetadataResolver/DLsiteResolverTest.php +++ b/tests/Unit/MetadataResolver/DLsiteResolverTest.php @@ -227,7 +227,7 @@ class DLsiteResolverTest extends TestCase } } - public function testAffiliateLink() + public function testOldAffiliateLink() { $responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html'); @@ -243,6 +243,38 @@ class DLsiteResolverTest extends TestCase } } + public function testSnsAffiliateLink() + { + $responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html'); + + $this->createResolver(DLsiteResolver::class, $responseText); + + $metadata = $this->resolver->resolve('https://www.dlsite.com/home/dlaf/=/t/s/link/work/aid/eai04191/id/RJ221761.html'); + $this->assertEquals('ひつじ、数えてあげるっ', $metadata->title); + $this->assertEquals('サークル名: Butterfly Dream' . PHP_EOL . '眠れないあなたに彼女が羊を数えてくれる音声です。', $metadata->description); + $this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ222000/RJ221761_img_main.jpg', $metadata->image); + $this->assertEquals(['癒し', 'バイノーラル/ダミヘ', '日常/生活', 'ほのぼの', '恋人同士'], $metadata->tags); + if ($this->shouldUseMock()) { + $this->assertSame('https://www.dlsite.com/home/work/=/product_id/RJ221761.html', (string) $this->handler->getLastRequest()->getUri()); + } + } + + public function testAffiliateLink() + { + $responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html'); + + $this->createResolver(DLsiteResolver::class, $responseText); + + $metadata = $this->resolver->resolve('https://www.dlsite.com/home/dlaf/=/t/t/link/work/aid/eai04191/id/RJ221761.html'); + $this->assertEquals('ひつじ、数えてあげるっ', $metadata->title); + $this->assertEquals('サークル名: Butterfly Dream' . PHP_EOL . '眠れないあなたに彼女が羊を数えてくれる音声です。', $metadata->description); + $this->assertEquals('https://img.dlsite.jp/modpub/images2/work/doujin/RJ222000/RJ221761_img_main.jpg', $metadata->image); + $this->assertEquals(['癒し', 'バイノーラル/ダミヘ', '日常/生活', 'ほのぼの', '恋人同士'], $metadata->tags); + if ($this->shouldUseMock()) { + $this->assertSame('https://www.dlsite.com/home/work/=/product_id/RJ221761.html', (string) $this->handler->getLastRequest()->getUri()); + } + } + public function testAffiliateUrl() { $responseText = file_get_contents(__DIR__ . '/../../fixture/DLsite/testHome.html'); diff --git a/tests/Unit/MetadataResolver/HentaiFoundryResolverTest.php b/tests/Unit/MetadataResolver/HentaiFoundryResolverTest.php new file mode 100644 index 0000000..f781bb1 --- /dev/null +++ b/tests/Unit/MetadataResolver/HentaiFoundryResolverTest.php @@ -0,0 +1,36 @@ +shouldUseMock()) { + sleep(1); + } + } + + public function test() + { + $responseText = file_get_contents(__DIR__ . '/../../fixture/HentaiFoundry/illust.html'); + + $this->createResolver(HentaiFoundryResolver::class, $responseText); + + $metadata = $this->resolver->resolve('https://www.hentai-foundry.com/pictures/user/DevilHS/723498/Witchcraft'); + $this->assertSame('Witchcraft', $metadata->title); + $this->assertSame('by DevilHS' . PHP_EOL . 'gift for Liru', $metadata->description); + $this->assertEquals(['witch', 'futa'], $metadata->tags); + $this->assertSame('https://pictures.hentai-foundry.com/d/DevilHS/723498/DevilHS-723498-Witchcraft.png', $metadata->image); + if ($this->shouldUseMock()) { + $this->assertSame('https://www.hentai-foundry.com/pictures/user/DevilHS/723498/Witchcraft?enterAgree=1', (string) $this->handler->getLastRequest()->getUri()); + } + } +} diff --git a/tests/Unit/MetadataResolver/Kb10uyShortStoryServerResolverTest.php b/tests/Unit/MetadataResolver/Kb10uyShortStoryServerResolverTest.php new file mode 100644 index 0000000..7407b9c --- /dev/null +++ b/tests/Unit/MetadataResolver/Kb10uyShortStoryServerResolverTest.php @@ -0,0 +1,35 @@ +shouldUseMock()) { + sleep(1); + } + } + + public function testNormalPost() + { + $responseText = file_get_contents(__DIR__ . '/../../fixture/Kb10uyShortStoryServer/tomone.html'); + + $this->createResolver(Kb10uyShortStoryServerResolver::class, $responseText); + + $metadata = $this->resolver->resolve('https://ss.kb10uy.org/posts/14'); + $this->assertSame('朋音「は、はぁ?おむつ?」', $metadata->title); + $this->assertSame('自炊したおかずってやつです。とりあえずこのSSの中ではkb10uyの彼女は朋音ってことにしといてください。そうじゃないと出す男が決定できないので。', $metadata->description); + $this->assertSame(['妄想', 'kb10uy', '岩永朋音', 'おむつ'], $metadata->tags); + if ($this->shouldUseMock()) { + $this->assertSame('https://ss.kb10uy.org/posts/14', (string) $this->handler->getLastRequest()->getUri()); + } + } +} diff --git a/tests/Unit/MetadataResolver/NijieResolverTest.php b/tests/Unit/MetadataResolver/NijieResolverTest.php index 417a853..f0e8d32 100644 --- a/tests/Unit/MetadataResolver/NijieResolverTest.php +++ b/tests/Unit/MetadataResolver/NijieResolverTest.php @@ -129,4 +129,23 @@ class NijieResolverTest extends TestCase $this->assertSame('https://nijie.info/view.php?id=66384', (string) $this->handler->getLastRequest()->getUri()); } } + + public function testHasHtmlInAuthorProfile() + { + $responseText = file_get_contents(__DIR__ . '/../../fixture/Nijie/testHasHtmlInAuthorProfileResponse.html'); + + $this->createResolver(NijieResolver::class, $responseText); + + $metadata = $this->resolver->resolve('https://nijie.info/view.php?id=285698'); + $this->assertSame('JK文化祭コスプレ喫茶', $metadata->title); + $this->assertSame('投稿者: ままままま' . PHP_EOL . + 'https://www.pixiv.net/fanbox/creator/32045169' . PHP_EOL . + 'ピクシブのファンボックスでこっちに上げてた一次創作のノリでえっちなやつ描いてます' . PHP_EOL . + '二次創作のえっちなやつは相変わらずこっち' . PHP_EOL . '健全目なのはついったー', $metadata->description); + $this->assertSame('https://pic.nijie.net/02/nijie_picture/540086_20181028112046_0.png', $metadata->image); + $this->assertSame(['バニーガール'], $metadata->tags); + if ($this->shouldUseMock()) { + $this->assertSame('https://nijie.info/view.php?id=285698', (string) $this->handler->getLastRequest()->getUri()); + } + } } diff --git a/tests/Unit/MetadataResolver/PixivResolverTest.php b/tests/Unit/MetadataResolver/PixivResolverTest.php index df5e83a..5565b7c 100644 --- a/tests/Unit/MetadataResolver/PixivResolverTest.php +++ b/tests/Unit/MetadataResolver/PixivResolverTest.php @@ -81,4 +81,20 @@ class PixivResolverTest extends TestCase $this->assertSame('https://www.pixiv.net/ajax/illust/68188073', (string) $this->handler->getLastRequest()->getUri()); } } + + public function testArtworkUrlEn() + { + $responseText = file_get_contents(__DIR__ . '/../../fixture/Pixiv/illust.json'); + + $this->createResolver(PixivResolver::class, $responseText); + + $metadata = $this->resolver->resolve('https://www.pixiv.net/en/artworks/68188073'); + $this->assertEquals('coffee break', $metadata->title); + $this->assertEquals('投稿者: 裕' . PHP_EOL, $metadata->description); + $this->assertEquals('https://i.pixiv.cat/img-master/img/2018/04/12/00/01/28/68188073_p0_master1200.jpg', $metadata->image); + $this->assertEquals(['オリジナル', 'カフェ', '眼鏡', 'イヤホン', 'ぱっつん', '艶ぼくろ', '眼鏡っ娘', 'オリジナル5000users入り'], $metadata->tags); + if ($this->shouldUseMock()) { + $this->assertSame('https://www.pixiv.net/ajax/illust/68188073', (string) $this->handler->getLastRequest()->getUri()); + } + } } diff --git a/tests/fixture/HentaiFoundry/illust.html b/tests/fixture/HentaiFoundry/illust.html new file mode 100644 index 0000000..fc5b11c --- /dev/null +++ b/tests/fixture/HentaiFoundry/illust.html @@ -0,0 +1,350 @@ + + + + + + + + + + + + + + + + + + + +Witchcraft by DevilHS - Hentai Foundry + + + + + + + + + + + + + + +
+ + + + + +
+
+ Username   + Password   + +
+ Remember +   Register   + |   Forgot your password? +
+
+
+ + +
+

Witchcraft

+

+ + +
+ + + +
+

Witchcraft

+
Witchcraft by DevilHS
+
+
+Witchcraft by DevilHS
+
+
+

Description

+
Description
+
+
+DevilHS
gift for Liru
+
+

General Info

+
General Info
+
+
+
+
+ + + + + + + +
Ratings
NSxTg
Comments 11
Category +Original » Futanari (Dickgirls) Media Digital drawing or painting
Date Submitted Time Taken
Views 20597 Reference
Favorites... 876 Keywords ,
Vote Score 649 License Berne Convention
+
+

Comments

+
Comments (11)
+
+
+

You are not authorized to comment here. Your must be registered and logged in to comment

+

Kessandra on August 15, 2019, 8:21:56 AM

+
Kessandra on
+
+
+
KessandraLooks like some beautiful magic is going on. 
+
+

TrueInsanity on July 28, 2019, 4:34:52 AM

+
TrueInsanity on
+
+
+
TrueInsanityOh hell yes
+
+

Kaze26 on July 27, 2019, 5:23:29 AM

+
Kaze26 on
+
+
+
Kaze26Spectacular
+
+

QuarterLife on July 25, 2019, 6:39:08 PM

+
QuarterLife on
+
+
+
QuarterLifeI'm curious as to what she's casting, is it to help her last longer or is it why she has a cock and balls?
+
+Either way it's a good picture, good job, nice tight.
+
+

DevilHS on July 25, 2019, 10:59:26 PM

+
DevilHS on
+
+
+
DevilHSShe casted herself a magic dick.
+
+

QuarterLife on July 26, 2019, 12:00:08 AM

+
QuarterLife on
+
+
+
QuarterLifeAh alright, not just a regular dick, but a magic dick, looks like a good dick too. :D
+
+

XSUBREADERX on July 25, 2019, 9:59:58 PM

+
XSUBREADERX on
+
+
+
XSUBREADERXVery Nice!
+
+

AceSR on July 25, 2019, 6:41:45 PM

+
AceSR on
+
+
+
AceSRNice gift
+
+

FloridasSonShines on July 25, 2019, 2:47:00 PM

+
FloridasSonShines on
+
+
+
FloridasSonShinesAwesome!
+
+

Kreegan on July 25, 2019, 12:39:04 PM

+
Kreegan on
+
+
+
KreeganGreat :D
+
+
+
+
+
+
+ +
+
Site Copyright © 2006-2019 All Rights Reserved
Site design by Sticky
+
Art and stories Copyright their artists/writers
+Series & Characters Copyright their respective creators/studios
+

All characters depicted are 18 or older, even if otherwise specified.

+ + ipv6 ready + +
+
+
+ + +
+
+ +
+ + + + \ No newline at end of file diff --git a/tests/fixture/Kb10uyShortStoryServer/tomone.html b/tests/fixture/Kb10uyShortStoryServer/tomone.html new file mode 100644 index 0000000..37dca5a --- /dev/null +++ b/tests/fixture/Kb10uyShortStoryServer/tomone.html @@ -0,0 +1,205 @@ + + + + + + + + + + + + 朋音「は、はぁ?おむつ?」 - ShortStoryServer + + +
+ +
+
+ +
+ + +
+

+kb10uy「うん、今日はこれ穿いて学校行って」 +朋音「あの……アンタ本気で言ってるの?」 +kb10uy「自分の好きでもない子にそんなおむつ穿いてとか言わないよ」 +朋音「そういうことじゃないのよっ……」 +

+

+kb10uy の要求はいたって単純である。 +

+
    +
  • 今日一日、おむつを穿いていること。
  • +
  • 必ずおむつの中に出すこと(大小問わず)。
  • +
  • 必ず人のいる場所で出すこと。
  • +
+

+単純か? +

+

+朋音「いまアンタのカノジョになっちゃったことを心底後悔してるわ……」 +kb10uy「でもやってくれるよね?」 +朋音「うぅ……そうよ、やるわよ……正直、そ、その……やり、たい……し」 +kb10uy「朋音ならそう言ってくれると思った!じゃあよろしくね」 +朋音「何が宜しいのよ……まったく」 +

+
+

+朋音「え、ちょっと待って、ワタシこれ大きいほうもこれにするの!?」 +kb10uy「うん、言ったじゃん」 +朋音「はぁ……」 +kb10uy「嫌だったら我慢して帰ってきてからしてもいいから」 +朋音「言われなくてもそうするわよ!w」 +kb10uy「おしっこは我慢できなさそう?」 +朋音「ん、正直自信ない……」 +kb10uy「まあ大丈夫だって、きっとバレないよ」 +朋音「よくもそんなヘラヘラと……」 +

+
+

+はぁ〜……。もうユウウツだわ……。 +夏稀「トモ、大丈夫?なんか妙に落ち込んでるように見えるけど……」 +朋音「うーん……微妙」 +夏稀「何かあった?」 +朋音「あのさ……kb10uyいるじゃん」 +夏稀「あぁ……1つ上の」 +朋音「多分なつには言ってなかったと思うんだけどさ、ワタシちょっと前からあいつと付き合ってるんだわ」 +夏稀「え、ほんとに!!良かったじゃん、おめでとう〜」 +朋音「それがあんまりおめでたくなくてさ……」 +夏稀「というと」 +朋音「kb10uyって精力絶倫ってウワサじゃん」 +夏稀「そうらしいね」 +朋音「絶倫なだけならまだ良くてさ、ワタシもそういうことするんだろうなってのは覚悟してたし」 +夏稀「したんだ?」 +朋音「ん。プロポーズした日に……」 +夏稀「マジで絶倫なんだ……」 +朋音「いやこっからなのよ問題は。kb10uy、性癖もヤバヤバのヤバでさ」 +夏稀「あー、はぁ……」 +朋音「……誰にも言わない?」 +夏稀「……うん。」 +

+

+朋音「ワタシ今おむつ穿かされてるの」 +夏稀「えっっ!! +

+

+朋音「っ………///」 +夏稀「なるほど……」 +夏稀「でもトモも穿いてるってことはやっぱりムッツリだよね」 +朋音「返す言葉もないわ……」 +

+
+

+実際に中に放尿するシーンはみなさんのご想像におまかせします +

+

+僕は廊下で人とぶつかってその衝撃でジョロロロって感じのシチュエーションで抜きました +

+
+ +
+ + + + diff --git a/tests/fixture/Nijie/testHasHtmlInAuthorProfileResponse.html b/tests/fixture/Nijie/testHasHtmlInAuthorProfileResponse.html new file mode 100644 index 0000000..2cd5313 --- /dev/null +++ b/tests/fixture/Nijie/testHasHtmlInAuthorProfileResponse.html @@ -0,0 +1,114 @@ +JK文化祭コスプレ喫茶 | ままままま | ニジエ +

JK文化祭コスプレ喫茶 | ままままま

+ +
+ + + + +
+ diff --git a/tests/fixture/Xtube/video.html b/tests/fixture/Xtube/video.html index ef4cbc5..893b8b1 100644 --- a/tests/fixture/Xtube/video.html +++ b/tests/fixture/Xtube/video.html @@ -1,9 +1,23 @@ - - - + + + + + + + + + + + + + + + + + @@ -11,21 +25,21 @@ - - - - - + + + + + - + - + - + @@ -136,13 +150,16 @@
    @@ -214,7 +231,7 @@
    - + - + diff --git a/webpack.mix.js b/webpack.mix.js index 3532d8b..bf24e44 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -16,6 +16,7 @@ mix.js('resources/assets/js/app.js', 'public/js') .js('resources/assets/js/home.js', 'public/js') .js('resources/assets/js/user/stats.js', 'public/js/user') .js('resources/assets/js/setting/privacy.js', 'public/js/setting') + .js('resources/assets/js/setting/deactivate.js', 'public/js/setting') .ts('resources/assets/js/checkin.ts', 'public/js') .sass('resources/assets/sass/app.scss', 'public/css') .autoload({