diff --git a/app/Ejaculation.php b/app/Ejaculation.php index 399783a..48bdaf9 100644 --- a/app/Ejaculation.php +++ b/app/Ejaculation.php @@ -27,4 +27,9 @@ class Ejaculation extends Model { return $this->belongsToMany('App\Tag')->withTimestamps(); } + + public function textTags() + { + return implode(' ', $this->tags->map(function ($v) { return $v->name; })->all()); + } } diff --git a/app/MetadataResolver/Metadata.php b/app/MetadataResolver/Metadata.php new file mode 100644 index 0000000..ae2508b --- /dev/null +++ b/app/MetadataResolver/Metadata.php @@ -0,0 +1,10 @@ + NicoSeigaResolver::class, + '~nijie\.info/view\.php~' => NijieResolver::class, + '/.*/' => OGPResolver::class + ]; + + public function resolve(string $url): Metadata + { + foreach ($this->rules as $pattern => $class) { + if (preg_match($pattern, $url) === 1) { + $resolver = new $class; + return $resolver->resolve($url); + } + } + + throw new \UnexpectedValueException('URL not matched.'); + } +} \ No newline at end of file diff --git a/app/MetadataResolver/NicoSeigaResolver.php b/app/MetadataResolver/NicoSeigaResolver.php new file mode 100644 index 0000000..dbd81bc --- /dev/null +++ b/app/MetadataResolver/NicoSeigaResolver.php @@ -0,0 +1,24 @@ +get($url); + if ($res->getStatusCode() === 200) { + $ogpResolver = new OGPResolver(); + $metadata = $ogpResolver->parse($res->getBody()); + + // ページURLからサムネイルURLに変換 + preg_match('~http://(?:(?:sp\\.)?seiga\\.nicovideo\\.jp/seiga(?:/#!)?|nico\\.ms)/im(\\d+)~', $url, $matches); + $metadata->image = "http://lohas.nicoseiga.jp/thumb/${matches[1]}l?"; + + return $metadata; + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } +} \ No newline at end of file diff --git a/app/MetadataResolver/NijieResolver.php b/app/MetadataResolver/NijieResolver.php new file mode 100644 index 0000000..9bfef14 --- /dev/null +++ b/app/MetadataResolver/NijieResolver.php @@ -0,0 +1,37 @@ +get($url); + if ($res->getStatusCode() === 200) { + $ogpResolver = new OGPResolver(); + $metadata = $ogpResolver->parse($res->getBody()); + + $dom = new \DOMDocument(); + @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); + $xpath = new \DOMXPath($dom); + $dataNode = $xpath->query('//script[substring(@type, string-length(@type) - 3, 4) = "json"]'); + foreach ($dataNode as $node) { + // 改行がそのまま入っていることがあるのでデコード前にエスケープが必要 + $imageData = json_decode(preg_replace('/\r?\n/', '\n', $node->nodeValue), true); + if (isset($imageData['thumbnailUrl']) && !ends_with($imageData['thumbnailUrl'], '.gif') && !ends_with($imageData['thumbnailUrl'], '.mp4')) { + $metadata->image = preg_replace('~nijie\\.info/.*/nijie_picture/~', 'nijie.info/nijie_picture/', $imageData['thumbnailUrl']); + break; + } + } + + return $metadata; + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } +} \ No newline at end of file diff --git a/app/MetadataResolver/OGPResolver.php b/app/MetadataResolver/OGPResolver.php new file mode 100644 index 0000000..0475d7d --- /dev/null +++ b/app/MetadataResolver/OGPResolver.php @@ -0,0 +1,52 @@ +get($url); + if ($res->getStatusCode() === 200) { + return $this->parse($res->getBody()); + } else { + throw new \RuntimeException("{$res->getStatusCode()}: $url"); + } + } + + public function parse(string $html): Metadata + { + $dom = new \DOMDocument(); + @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $xpath = new \DOMXPath($dom); + + $metadata = new Metadata(); + + $titleNode = $xpath->query('//meta[@*="og:title"]'); + foreach ($titleNode as $node) { + if (!empty($node->getAttribute('content'))) { + $metadata->title = $node->getAttribute('content'); + break; + } + } + + $descriptionNode = $xpath->query('//meta[@*="og:description"]'); + foreach ($descriptionNode as $node) { + if (!empty($node->getAttribute('content'))) { + $metadata->description = $node->getAttribute('content'); + break; + } + } + + $imageNode = $xpath->query('//meta[@*="og:image"]'); + foreach ($imageNode as $node) { + if (!empty($node->getAttribute('content'))) { + $metadata->image = $node->getAttribute('content'); + break; + } + } + + return $metadata; + } +} \ No newline at end of file diff --git a/app/MetadataResolver/Resolver.php b/app/MetadataResolver/Resolver.php new file mode 100644 index 0000000..cc54d28 --- /dev/null +++ b/app/MetadataResolver/Resolver.php @@ -0,0 +1,8 @@ +app->singleton(MetadataResolver::class, function ($app) { + return new MetadataResolver(); + }); } } diff --git a/resources/views/ejaculation/edit.blade.php b/resources/views/ejaculation/edit.blade.php index ed94a68..293802d 100644 --- a/resources/views/ejaculation/edit.blade.php +++ b/resources/views/ejaculation/edit.blade.php @@ -37,7 +37,7 @@
- +
    diff --git a/resources/views/ejaculation/show.blade.php b/resources/views/ejaculation/show.blade.php index 6afbfd8..c9006cc 100644 --- a/resources/views/ejaculation/show.blade.php +++ b/resources/views/ejaculation/show.blade.php @@ -28,6 +28,7 @@
    {{ $ejaculatedSpan ?? '精通' }} {{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ~ ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}
    @if ($user->isMe())
    +
    diff --git a/resources/views/user/profile.blade.php b/resources/views/user/profile.blade.php index e3f3e50..8bc7c14 100644 --- a/resources/views/user/profile.blade.php +++ b/resources/views/user/profile.blade.php @@ -14,6 +14,7 @@
    {{ $ejaculation->ejaculated_span ?? '精通' }} {{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ~ ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}
    @if ($user->isMe())
    +
    diff --git a/routes/api.php b/routes/api.php index 79ecd98..083a21b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,6 @@ get('/user', function (Request $request) { return $request->user(); }); -Route::get('/checkin/card', function (Request $request) { +Route::get('/checkin/card', function (Request $request, MetadataResolver $resolver) { $request->validate([ 'url:required|url' ]); $url = $request->input('url'); - $client = new GuzzleHttp\Client(); - $res = $client->get($url); - if ($res->getStatusCode() === 200) { - $dom = new DOMDocument(); - @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); - $xpath = new DOMXPath($dom); - - $result = [ - 'title' => '', - 'description' => '', - 'image' => '' - ]; - - $titleNode = $xpath->query('//meta[@*="og:title"]'); - foreach ($titleNode as $node) { - if (!empty($node->getAttribute('content'))) { - $result['title'] = $node->getAttribute('content'); - break; - } - } - - $descriptionNode = $xpath->query('//meta[@*="og:description"]'); - foreach ($descriptionNode as $node) { - if (!empty($node->getAttribute('content'))) { - $result['description'] = $node->getAttribute('content'); - break; - } - } - - $imageNode = $xpath->query('//meta[@*="og:image"]'); - foreach ($imageNode as $node) { - if (!empty($node->getAttribute('content'))) { - $result['image'] = $node->getAttribute('content'); - break; - } - } - - // 一部サイトについては別のサムネイルの取得を試みる - if (mb_strpos($url, 'nico.ms/im') !== false || - mb_strpos($url, 'seiga.nicovideo.jp/seiga/im') !== false || - mb_strpos($url, 'sp.seiga.nicovideo.jp/seiga/#!/im') !== false) { - // ニコニコ静画用の処理 - preg_match('~http://(?:(?:sp\\.)?seiga\\.nicovideo\\.jp/seiga(?:/#!)?|nico\\.ms)/im(\\d+)~', $url, $matches); - $result['image'] = "http://lohas.nicoseiga.jp/thumb/${matches[1]}l?"; - } elseif (mb_strpos($url, 'nijie.info/view.php')) { - // ニジエ用の処理 - $dataNode = $xpath->query('//script[substring(@type, string-length(@type) - 3, 4) = "json"]'); - foreach ($dataNode as $node) { - // 改行がそのまま入っていることがあるのでデコード前にエスケープが必要 - $imageData = json_decode(preg_replace('/\r?\n/', '\n', $node->nodeValue), true); - if (isset($imageData['thumbnailUrl'])) { - $result['image'] = preg_replace('~nijie\\.info/.*/nijie_picture/~', 'nijie.info/nijie_picture/', $imageData['thumbnailUrl']); - break; - } - } - } - - $response = response()->json($result); - if (!config('app.debug')) { - $response = $response->setCache(['public' => true, 'max_age' => 86400]); - } - return $response; - } else { - abort($res->getStatusCode()); + $metadata = $resolver->resolve($url); + $response = response()->json($metadata); + if (!config('app.debug')) { + $response = $response->setCache(['public' => true, 'max_age' => 86400]); } + return $response; }); \ No newline at end of file diff --git a/tests/Unit/MetadataResolver/NijieResolverTest.php b/tests/Unit/MetadataResolver/NijieResolverTest.php new file mode 100644 index 0000000..5963418 --- /dev/null +++ b/tests/Unit/MetadataResolver/NijieResolverTest.php @@ -0,0 +1,101 @@ +resolve('https://nijie.info/view.php?id=66384'); + $this->assertEquals('チンポップくんの日常ep.1「チンポップくんと釣り」 | ニジエ運営', $metadata->title); + $this->assertEquals("メールマガジン漫画のバックナンバー第一話です!\r\n最新話はメールマガジンより配信中です。", $metadata->description); + $this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image); + $this->assertNotRegExp('~/diff/main/~', $metadata->image); + } + + public function testMultiplePicture() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $resolver->resolve('https://nijie.info/view.php?id=202707'); + $this->assertEquals('ニジエ壁紙 | ニジエ運営', $metadata->title); + $this->assertEquals("ニジエのPCとiphone用(4.7inch推奨)の壁紙です。\r\n保存してご自由にお使いくださいませ。", $metadata->description); + $this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image); + $this->assertNotRegExp('~/diff/main/~', $metadata->image); + } + + public function testAnimationGif() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $resolver->resolve('https://nijie.info/view.php?id=258078'); + $this->assertEquals('騎乗位ルーミア | しょったれ', $metadata->title); + $this->assertEquals("以前pixivに投稿したgifアニメ。\r\n気の利いたタイトルが浮かばなかった。", $metadata->description); + $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image); + } + + public function testMp4Movie() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $resolver->resolve('https://nijie.info/view.php?id=256283'); + $this->assertEquals('てすと | ニジエ運営', $metadata->title); + $this->assertEquals("H264動画てすと あとで消します\r\n\r\n今の所、H264コーデックのみ、出力時に音声なしにしないと投稿できません\r\n動画は勝手にループします", $metadata->description); + $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image); + } + + public function testStandardPictureSp() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $resolver->resolve('https://sp.nijie.info/view.php?id=66384'); + $this->assertEquals('チンポップくんの日常ep.1「チンポップくんと釣り」 | ニジエ運営', $metadata->title); + $this->assertEquals("メールマガジン漫画のバックナンバー第一話です!\r\n最新話はメールマガジンより配信中です。", $metadata->description); + $this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image); + $this->assertNotRegExp('~/diff/main/~', $metadata->image); + } + + public function testMultiplePictureSp() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $resolver->resolve('https://sp.nijie.info/view.php?id=202707'); + $this->assertEquals('ニジエ壁紙 | ニジエ運営', $metadata->title); + $this->assertEquals("ニジエのPCとiphone用(4.7inch推奨)の壁紙です。\r\n保存してご自由にお使いくださいませ。", $metadata->description); + $this->assertRegExp('/pic\d+\.nijie\.info/', $metadata->image); + $this->assertNotRegExp('~/diff/main/~', $metadata->image); + } + + public function testAnimationGifSp() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $resolver->resolve('https://sp.nijie.info/view.php?id=258078'); + $this->assertEquals('騎乗位ルーミア | しょったれ', $metadata->title); + $this->assertEquals("以前pixivに投稿したgifアニメ。\r\n気の利いたタイトルが浮かばなかった。", $metadata->description); + $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image); + } + + public function testMp4MovieSp() + { + sleep(1); + $resolver = new NijieResolver(); + + $metadata = $resolver->resolve('https://sp.nijie.info/view.php?id=256283'); + $this->assertEquals('てすと | ニジエ運営', $metadata->title); + $this->assertEquals("H264動画てすと あとで消します\r\n\r\n今の所、H264コーデックのみ、出力時に音声なしにしないと投稿できません\r\n動画は勝手にループします", $metadata->description); + $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image); + } +} diff --git a/tests/Unit/MetadataResolver/OGPResolverTest.php b/tests/Unit/MetadataResolver/OGPResolverTest.php new file mode 100644 index 0000000..a8c0ab0 --- /dev/null +++ b/tests/Unit/MetadataResolver/OGPResolverTest.php @@ -0,0 +1,28 @@ +expectException(ClientException::class); + $resolver->resolve('http://example.com/404'); + } + + public function testResolve() + { + $resolver = new OGPResolver(); + + $metadata = $resolver->resolve('http://ogp.me'); + $this->assertEquals('Open Graph protocol', $metadata->title); + $this->assertEquals('The Open Graph protocol enables any web page to become a rich object in a social graph.', $metadata->description); + $this->assertEquals('http://ogp.me/logo.png', $metadata->image); + } +}