From 7ca0acacb4754d0769937af6af55be6cdc59d909 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 15 Apr 2018 02:05:41 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=86=E3=83=B3=E3=83=84?= =?UTF-8?q?=E6=83=85=E5=A0=B1=E5=8F=96=E5=BE=97=E3=81=AE=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=E3=82=92api.php=E3=81=8B=E3=82=89=E5=89=A5=E3=81=8C=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/MetadataResolver/Metadata.php | 10 +++ app/MetadataResolver/MetadataResolver.php | 24 +++++++ app/MetadataResolver/NicoSeigaResolver.php | 24 +++++++ app/MetadataResolver/NijieResolver.php | 33 +++++++++ app/MetadataResolver/OGPResolver.php | 52 ++++++++++++++ app/MetadataResolver/Resolver.php | 8 +++ app/Providers/AppServiceProvider.php | 5 +- routes/api.php | 72 ++----------------- .../Unit/MetadataResolver/OGPResolverTest.php | 28 ++++++++ 9 files changed, 190 insertions(+), 66 deletions(-) create mode 100644 app/MetadataResolver/Metadata.php create mode 100644 app/MetadataResolver/MetadataResolver.php create mode 100644 app/MetadataResolver/NicoSeigaResolver.php create mode 100644 app/MetadataResolver/NijieResolver.php create mode 100644 app/MetadataResolver/OGPResolver.php create mode 100644 app/MetadataResolver/Resolver.php create mode 100644 tests/Unit/MetadataResolver/OGPResolverTest.php 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..f4fdba1 --- /dev/null +++ b/app/MetadataResolver/NijieResolver.php @@ -0,0 +1,33 @@ +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'])) { + $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/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/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); + } +}