コンテンツ情報取得の実装をapi.phpから剥がした
This commit is contained in:
		
							
								
								
									
										10
									
								
								app/MetadataResolver/Metadata.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/MetadataResolver/Metadata.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <?php | ||||
|  | ||||
| namespace App\MetadataResolver; | ||||
|  | ||||
| class Metadata | ||||
| { | ||||
|     public $title = ''; | ||||
|     public $description = ''; | ||||
|     public $image = ''; | ||||
| } | ||||
							
								
								
									
										24
									
								
								app/MetadataResolver/MetadataResolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/MetadataResolver/MetadataResolver.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| <?php | ||||
|  | ||||
| namespace App\MetadataResolver; | ||||
|  | ||||
| class MetadataResolver implements Resolver | ||||
| { | ||||
|     public $rules = [ | ||||
|         '~(((sp\.)?seiga\.nicovideo\.jp/seiga(/#!)?|nico\.ms))/im~' => 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.'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										24
									
								
								app/MetadataResolver/NicoSeigaResolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/MetadataResolver/NicoSeigaResolver.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| <?php | ||||
|  | ||||
| namespace App\MetadataResolver; | ||||
|  | ||||
| class NicoSeigaResolver implements Resolver | ||||
| { | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $client = new \GuzzleHttp\Client(); | ||||
|         $res = $client->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"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								app/MetadataResolver/NijieResolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/MetadataResolver/NijieResolver.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <?php | ||||
|  | ||||
| namespace App\MetadataResolver; | ||||
|  | ||||
| class NijieResolver implements Resolver | ||||
| { | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $client = new \GuzzleHttp\Client(); | ||||
|         $res = $client->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"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										52
									
								
								app/MetadataResolver/OGPResolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								app/MetadataResolver/OGPResolver.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| <?php | ||||
|  | ||||
| namespace App\MetadataResolver; | ||||
|  | ||||
| class OGPResolver implements Resolver | ||||
| { | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $client = new \GuzzleHttp\Client(); | ||||
|         $res = $client->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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										8
									
								
								app/MetadataResolver/Resolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/MetadataResolver/Resolver.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?php | ||||
|  | ||||
| namespace App\MetadataResolver; | ||||
|  | ||||
| interface Resolver | ||||
| { | ||||
|     public function resolve(string $url): Metadata; | ||||
| } | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| namespace App\Providers; | ||||
|  | ||||
| use App\MetadataResolver\MetadataResolver; | ||||
| use Illuminate\Support\ServiceProvider; | ||||
|  | ||||
| class AppServiceProvider extends ServiceProvider | ||||
| @@ -23,6 +24,8 @@ class AppServiceProvider extends ServiceProvider | ||||
|      */ | ||||
|     public function register() | ||||
|     { | ||||
|         // | ||||
|         $this->app->singleton(MetadataResolver::class, function ($app) { | ||||
|             return new MetadataResolver(); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| <?php | ||||
|  | ||||
| use App\MetadataResolver\MetadataResolver; | ||||
| use Illuminate\Http\Request; | ||||
|  | ||||
| /* | ||||
| @@ -17,75 +18,16 @@ Route::middleware('auth:api')->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); | ||||
|     $metadata = $resolver->resolve($url); | ||||
|     $response = response()->json($metadata); | ||||
|     if (!config('app.debug')) { | ||||
|         $response = $response->setCache(['public' => true, 'max_age' => 86400]); | ||||
|     } | ||||
|     return $response; | ||||
|     } else { | ||||
|         abort($res->getStatusCode()); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										28
									
								
								tests/Unit/MetadataResolver/OGPResolverTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								tests/Unit/MetadataResolver/OGPResolverTest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Tests\Unit\MetadataResolver; | ||||
|  | ||||
| use App\MetadataResolver\OGPResolver; | ||||
| use GuzzleHttp\Exception\ClientException; | ||||
| use Tests\TestCase; | ||||
|  | ||||
| class OGPResolverTest extends TestCase | ||||
| { | ||||
|     public function testMissingUrl() | ||||
|     { | ||||
|         $resolver = new OGPResolver(); | ||||
|  | ||||
|         $this->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); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 shibafu
					shibafu