| @@ -25,19 +25,15 @@ class CienResolver extends MetadataResolver | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|  | ||||
|             // 画像URLから有効期限の起点を拾う | ||||
|             parse_str(parse_url($metadata->image, PHP_URL_QUERY), $params); | ||||
|             if (empty($params['px-time'])) { | ||||
|                 throw new \RuntimeException('Parameter "px-time" not found. Image=' . $metadata->image . ' Source=' . $url); | ||||
|             } | ||||
|             $metadata->expires_at = Carbon::createFromTimestamp($params['px-time'])->addHour(1); | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         // 画像URLから有効期限の起点を拾う | ||||
|         parse_str(parse_url($metadata->image, PHP_URL_QUERY), $params); | ||||
|         if (empty($params['px-time'])) { | ||||
|             throw new \RuntimeException('Parameter "px-time" not found. Image=' . $metadata->image . ' Source=' . $url); | ||||
|         } | ||||
|         $metadata->expires_at = Carbon::createFromTimestamp($params['px-time'])->addHour(1); | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -73,59 +73,55 @@ class DLsiteResolver implements Resolver | ||||
|         } | ||||
|  | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|  | ||||
|             $dom = new \DOMDocument(); | ||||
|             @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|             $xpath = new \DOMXPath($dom); | ||||
|         $dom = new \DOMDocument(); | ||||
|         @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|         $xpath = new \DOMXPath($dom); | ||||
|  | ||||
|             // OGPタイトルから[]に囲まれているmakerを取得する | ||||
|             // 複数の作者がいる場合スペース区切りになるためexplodeしている | ||||
|             // スペースを含むmakerの場合名前の一部しか取れないが動作には問題ない | ||||
|             preg_match('~ \[([^\[\]]*)\] (予告作品 )?\| DLsite(がるまに)?$~', $metadata->title, $match); | ||||
|             $makers = explode(' ', $match[1]); | ||||
|         // OGPタイトルから[]に囲まれているmakerを取得する | ||||
|         // 複数の作者がいる場合スペース区切りになるためexplodeしている | ||||
|         // スペースを含むmakerの場合名前の一部しか取れないが動作には問題ない | ||||
|         preg_match('~ \[([^\[\]]*)\] (予告作品 )?\| DLsite(がるまに)?$~', $metadata->title, $match); | ||||
|         $makers = explode(' ', $match[1]); | ||||
|  | ||||
|             //フォローボタン(.btn_follow)はテキストを含んでしまうことがあるので要素を削除しておく | ||||
|             $followButtonNode = $xpath->query('//*[@class="btn_follow"]')->item(0); | ||||
|             $followButtonNode->parentNode->removeChild($followButtonNode); | ||||
|         //フォローボタン(.btn_follow)はテキストを含んでしまうことがあるので要素を削除しておく | ||||
|         $followButtonNode = $xpath->query('//*[@class="btn_follow"]')->item(0); | ||||
|         $followButtonNode->parentNode->removeChild($followButtonNode); | ||||
|  | ||||
|             // maker, makerHeadを探す | ||||
|         // maker, makerHeadを探す | ||||
|  | ||||
|             // makers | ||||
|             // #work_makerから「makerを含むテキスト」を持つ要素を持つtdを探す | ||||
|             // 作者名単体の場合もあるし、"作者A / 作者B"のようになることもある | ||||
|             $makersNode = $xpath->query('//*[@id="work_maker"]//*[contains(text(), "' . $makers[0] . '")]/ancestor::td')->item(0); | ||||
|             // nbspをspaceに置換 | ||||
|             $makers = trim(str_replace("\xc2\xa0", ' ', $makersNode->textContent)); | ||||
|         // makers | ||||
|         // #work_makerから「makerを含むテキスト」を持つ要素を持つtdを探す | ||||
|         // 作者名単体の場合もあるし、"作者A / 作者B"のようになることもある | ||||
|         $makersNode = $xpath->query('//*[@id="work_maker"]//*[contains(text(), "' . $makers[0] . '")]/ancestor::td')->item(0); | ||||
|         // nbspをspaceに置換 | ||||
|         $makers = trim(str_replace("\xc2\xa0", ' ', $makersNode->textContent)); | ||||
|  | ||||
|             // makersHaed | ||||
|             // $makerNode(td)に対するthを探す | ||||
|             // "著者", "サークル名", "ブランド名"など | ||||
|             $makersHeadNode = $xpath->query('preceding-sibling::th', $makersNode)->item(0); | ||||
|             $makersHead = trim($makersHeadNode->textContent); | ||||
|         // makersHaed | ||||
|         // $makerNode(td)に対するthを探す | ||||
|         // "著者", "サークル名", "ブランド名"など | ||||
|         $makersHeadNode = $xpath->query('preceding-sibling::th', $makersNode)->item(0); | ||||
|         $makersHead = trim($makersHeadNode->textContent); | ||||
|  | ||||
|             // 余分な文を消す | ||||
|         // 余分な文を消す | ||||
|  | ||||
|             // OGPタイトルから作者名とサイト名を消す | ||||
|             $metadata->title = trim(preg_replace('~ \[[^\[\]]*\] (予告作品 )?\| DLsite(がるまに)?$~', '', $metadata->title)); | ||||
|         // OGPタイトルから作者名とサイト名を消す | ||||
|         $metadata->title = trim(preg_replace('~ \[[^\[\]]*\] (予告作品 )?\| DLsite(がるまに)?$~', '', $metadata->title)); | ||||
|  | ||||
|             // OGP説明文から定型文を消す | ||||
|             if (strpos($url, 'dlsite.com/eng/') || strpos($url, 'dlsite.com/ecchi-eng/')) { | ||||
|                 $metadata->description = preg_replace('~DLsite.+ is a download shop for .+With a huge selection of products, we\'re sure you\'ll find whatever tickles your fancy\. DLsite is one of the greatest indie contents download shops in Japan\.$~', '', $metadata->description); | ||||
|             } else { | ||||
|                 $metadata->description = preg_replace('~「DLsite.+」は.+のダウンロードショップ。お気に入りの作品をすぐダウンロードできてすぐ楽しめる!毎日更新しているのであなたが探している作品にきっと出会えます。国内最大級の二次元総合ダウンロードショップ「DLsite」!$~', '', $metadata->description); | ||||
|             } | ||||
|             $metadata->description = trim(strip_tags($metadata->description)); | ||||
|  | ||||
|             // 整形 | ||||
|             $metadata->description = $makersHead . ': ' . $makers . PHP_EOL . $metadata->description; | ||||
|             $metadata->image = str_replace('img_sam.jpg', 'img_main.jpg', $metadata->image); | ||||
|             $metadata->tags = $this->extractTags($res->getBody()); | ||||
|  | ||||
|             return $metadata; | ||||
|         // OGP説明文から定型文を消す | ||||
|         if (strpos($url, 'dlsite.com/eng/') || strpos($url, 'dlsite.com/ecchi-eng/')) { | ||||
|             $metadata->description = preg_replace('~DLsite.+ is a download shop for .+With a huge selection of products, we\'re sure you\'ll find whatever tickles your fancy\. DLsite is one of the greatest indie contents download shops in Japan\.$~', '', $metadata->description); | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|             $metadata->description = preg_replace('~「DLsite.+」は.+のダウンロードショップ。お気に入りの作品をすぐダウンロードできてすぐ楽しめる!毎日更新しているのであなたが探している作品にきっと出会えます。国内最大級の二次元総合ダウンロードショップ「DLsite」!$~', '', $metadata->description); | ||||
|         } | ||||
|         $metadata->description = trim(strip_tags($metadata->description)); | ||||
|  | ||||
|         // 整形 | ||||
|         $metadata->description = $makersHead . ': ' . $makers . PHP_EOL . $metadata->description; | ||||
|         $metadata->image = str_replace('img_sam.jpg', 'img_main.jpg', $metadata->image); | ||||
|         $metadata->tags = $this->extractTags($res->getBody()); | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,21 +24,17 @@ class FC2ContentsResolver implements Resolver | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|  | ||||
|             $dom = new \DOMDocument(); | ||||
|             @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|             $xpath = new \DOMXPath($dom); | ||||
|         $dom = new \DOMDocument(); | ||||
|         @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|         $xpath = new \DOMXPath($dom); | ||||
|  | ||||
|             $thumbnailNode = $xpath->query('//*[@class="main_thum_img"]/a')->item(0); | ||||
|             if ($thumbnailNode) { | ||||
|                 $metadata->image = preg_replace('~^http:~', 'https:', $thumbnailNode->getAttribute('href')); | ||||
|             } | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         $thumbnailNode = $xpath->query('//*[@class="main_thum_img"]/a')->item(0); | ||||
|         if ($thumbnailNode) { | ||||
|             $metadata->image = preg_replace('~^http:~', 'https:', $thumbnailNode->getAttribute('href')); | ||||
|         } | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,29 +28,25 @@ class FantiaResolver implements Resolver | ||||
|         $postId = $match[0]; | ||||
|  | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|  | ||||
|             $dom = new \DOMDocument(); | ||||
|             @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|             $xpath = new \DOMXPath($dom); | ||||
|         $dom = new \DOMDocument(); | ||||
|         @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|         $xpath = new \DOMXPath($dom); | ||||
|  | ||||
|             $node = $xpath->query("//meta[@property='twitter:image']")->item(0); | ||||
|             $ogpUrl = $node->getAttribute('content'); | ||||
|         $node = $xpath->query("//meta[@property='twitter:image']")->item(0); | ||||
|         $ogpUrl = $node->getAttribute('content'); | ||||
|  | ||||
|             // 投稿に画像がない場合(ogp.jpgでない場合)のみ大きい画像に変換する | ||||
|             if ($ogpUrl != 'http://fantia.jp/images/ogp.jpg') { | ||||
|                 preg_match("~https://fantia\.s3\.amazonaws\.com/uploads/post/file/{$postId}/ogp_(.*?)\.(jpg|png)~", $ogpUrl, $match); | ||||
|                 $uuid = $match[1]; | ||||
|                 $extension = $match[2]; | ||||
|         // 投稿に画像がない場合(ogp.jpgでない場合)のみ大きい画像に変換する | ||||
|         if ($ogpUrl != 'http://fantia.jp/images/ogp.jpg') { | ||||
|             preg_match("~https://fantia\.s3\.amazonaws\.com/uploads/post/file/{$postId}/ogp_(.*?)\.(jpg|png)~", $ogpUrl, $match); | ||||
|             $uuid = $match[1]; | ||||
|             $extension = $match[2]; | ||||
|  | ||||
|                 // 大きい画像に変換 | ||||
|                 $metadata->image = "https://c.fantia.jp/uploads/post/file/{$postId}/main_{$uuid}.{$extension}"; | ||||
|             } | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|             // 大きい画像に変換 | ||||
|             $metadata->image = "https://c.fantia.jp/uploads/post/file/{$postId}/main_{$uuid}.{$extension}"; | ||||
|         } | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,14 +24,10 @@ class FanzaResolver implements Resolver | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|             $metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $metadata->image); | ||||
|             $metadata->description = str_replace('<>', '', $metadata->description); | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $metadata->image); | ||||
|         $metadata->description = str_replace('<>', '', $metadata->description); | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         } | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,45 +20,41 @@ class IwaraResolver implements Resolver | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = new Metadata(); | ||||
|             $html = (string) $res->getBody(); | ||||
|             $crawler = new Crawler($html); | ||||
|         $metadata = new Metadata(); | ||||
|         $html = (string) $res->getBody(); | ||||
|         $crawler = new Crawler($html); | ||||
|  | ||||
|             $infoElements = $crawler->filter('#video-player + div, .field-name-field-video-url + div, .field-name-field-images + div'); | ||||
|             $title = $infoElements->filter('h1.title')->text(); | ||||
|             $author = $infoElements->filter('.username')->text(); | ||||
|             $description = $infoElements->filter('.field-type-text-with-summary')->text(''); | ||||
|             $tags =  $infoElements->filter('a[href^="/videos"], a[href^="/images"]')->extract('_text'); | ||||
|             // 役に立たないタグを削除する | ||||
|             $tags = array_values(array_diff($tags, ['Uncategorized', 'Other'])); | ||||
|             array_push($tags, $author); | ||||
|         $infoElements = $crawler->filter('#video-player + div, .field-name-field-video-url + div, .field-name-field-images + div'); | ||||
|         $title = $infoElements->filter('h1.title')->text(); | ||||
|         $author = $infoElements->filter('.username')->text(); | ||||
|         $description = $infoElements->filter('.field-type-text-with-summary')->text(''); | ||||
|         $tags =  $infoElements->filter('a[href^="/videos"], a[href^="/images"]')->extract('_text'); | ||||
|         // 役に立たないタグを削除する | ||||
|         $tags = array_values(array_diff($tags, ['Uncategorized', 'Other'])); | ||||
|         array_push($tags, $author); | ||||
|  | ||||
|             $metadata->title = $title; | ||||
|             $metadata->description = '投稿者: ' . $author . PHP_EOL . $description; | ||||
|             $metadata->tags = $tags; | ||||
|         $metadata->title = $title; | ||||
|         $metadata->description = '投稿者: ' . $author . PHP_EOL . $description; | ||||
|         $metadata->tags = $tags; | ||||
|  | ||||
|             // iwara video | ||||
|             if ($crawler->filter('#video-player')->count()) { | ||||
|                 $metadata->image = 'https:' . $crawler->filter('#video-player')->attr('poster'); | ||||
|             } | ||||
|  | ||||
|             // youtube | ||||
|             if ($crawler->filter('iframe[src^="//www.youtube.com"]')->count()) { | ||||
|                 if (preg_match('~youtube\.com/embed/(\S+)\?~', $crawler->filter('iframe[src^="//www.youtube.com"]')->attr('src'), $matches) === 1) { | ||||
|                     $youtubeId = $matches[1]; | ||||
|                     $metadata->image = 'https://img.youtube.com/vi/' . $youtubeId . '/maxresdefault.jpg'; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // images | ||||
|             if ($crawler->filter('.field-name-field-images')->count()) { | ||||
|                 $metadata->image = 'https:' . $crawler->filter('.field-name-field-images a')->first()->attr('href'); | ||||
|             } | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         // iwara video | ||||
|         if ($crawler->filter('#video-player')->count()) { | ||||
|             $metadata->image = 'https:' . $crawler->filter('#video-player')->attr('poster'); | ||||
|         } | ||||
|  | ||||
|         // youtube | ||||
|         if ($crawler->filter('iframe[src^="//www.youtube.com"]')->count()) { | ||||
|             if (preg_match('~youtube\.com/embed/(\S+)\?~', $crawler->filter('iframe[src^="//www.youtube.com"]')->attr('src'), $matches) === 1) { | ||||
|                 $youtubeId = $matches[1]; | ||||
|                 $metadata->image = 'https://img.youtube.com/vi/' . $youtubeId . '/maxresdefault.jpg'; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // images | ||||
|         if ($crawler->filter('.field-name-field-images')->count()) { | ||||
|             $metadata->image = 'https:' . $crawler->filter('.field-name-field-images a')->first()->attr('href'); | ||||
|         } | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,33 +24,28 @@ class KomifloResolver implements Resolver | ||||
|         $id = $matches[1]; | ||||
|  | ||||
|         $res = $this->client->get('https://api.komiflo.com/content/id/' . $id); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $json = json_decode($res->getBody()->getContents(), true); | ||||
|             $metadata = new Metadata(); | ||||
|         $json = json_decode($res->getBody()->getContents(), true); | ||||
|         $metadata = new Metadata(); | ||||
|  | ||||
|             $metadata->title = $json['content']['data']['title'] ?? ''; | ||||
|             $metadata->description = ($json['content']['attributes']['artists']['children'][0]['data']['name'] ?? '?') . | ||||
|                 ' - ' . | ||||
|                 ($json['content']['parents'][0]['data']['title'] ?? '?'); | ||||
|             $metadata->image = 'https://t.komiflo.com/564_mobile_large_3x/' . $json['content']['named_imgs']['cover']['filename']; | ||||
|         $metadata->title = $json['content']['data']['title'] ?? ''; | ||||
|         $metadata->description = ($json['content']['attributes']['artists']['children'][0]['data']['name'] ?? '?') . | ||||
|             ' - ' . ($json['content']['parents'][0]['data']['title'] ?? '?'); | ||||
|         $metadata->image = 'https://t.komiflo.com/564_mobile_large_3x/' . $json['content']['named_imgs']['cover']['filename']; | ||||
|  | ||||
|             // 作者情報 | ||||
|             if (!empty($json['content']['attributes']['artists']['children'])) { | ||||
|                 foreach ($json['content']['attributes']['artists']['children'] as $artist) { | ||||
|                     $metadata->tags[] = preg_replace('/\s/', '_', $artist['data']['name']); | ||||
|                 } | ||||
|         // 作者情報 | ||||
|         if (!empty($json['content']['attributes']['artists']['children'])) { | ||||
|             foreach ($json['content']['attributes']['artists']['children'] as $artist) { | ||||
|                 $metadata->tags[] = preg_replace('/\s/', '_', $artist['data']['name']); | ||||
|             } | ||||
|  | ||||
|             // タグ | ||||
|             if (!empty($json['content']['attributes']['tags']['children'])) { | ||||
|                 foreach ($json['content']['attributes']['tags']['children'] as $tag) { | ||||
|                     $metadata->tags[] = preg_replace('/\s/', '_', $tag['data']['name']); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         } | ||||
|  | ||||
|         // タグ | ||||
|         if (!empty($json['content']['attributes']['tags']['children'])) { | ||||
|             foreach ($json['content']['attributes']['tags']['children'] as $tag) { | ||||
|                 $metadata->tags[] = preg_replace('/\s/', '_', $tag['data']['name']); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -27,46 +27,42 @@ class MelonbooksResolver implements Resolver | ||||
|         $cookieJar = CookieJar::fromArray(['AUTH_ADULT' => '1'], 'www.melonbooks.co.jp'); | ||||
|  | ||||
|         $res = $this->client->get($url, ['cookies' => $cookieJar]); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|  | ||||
|             $dom = new \DOMDocument(); | ||||
|             @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|             $xpath = new \DOMXPath($dom); | ||||
|             $descriptionNodelist = $xpath->query('//div[@id="description"]//p'); | ||||
|             $specialDescriptionNodelist = $xpath->query('//div[@id="special_description"]//p'); | ||||
|         $dom = new \DOMDocument(); | ||||
|         @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|         $xpath = new \DOMXPath($dom); | ||||
|         $descriptionNodelist = $xpath->query('//div[@id="description"]//p'); | ||||
|         $specialDescriptionNodelist = $xpath->query('//div[@id="special_description"]//p'); | ||||
|  | ||||
|             // censoredフラグの除去 | ||||
|             if (mb_strpos($metadata->image, '&c=1') !== false) { | ||||
|                 $metadata->image = preg_replace('/&c=1/u', '', $metadata->image); | ||||
|             } | ||||
|  | ||||
|             // 抽出 | ||||
|             preg_match('~^(.+)((.+))の通販・購入はメロンブックス$~', $metadata->title, $match); | ||||
|             $title = $match[1]; | ||||
|             $maker = $match[2]; | ||||
|  | ||||
|             // 整形 | ||||
|             $description = 'サークル: ' . $maker . "\n"; | ||||
|  | ||||
|             if ($specialDescriptionNodelist->length !== 0) { | ||||
|                 $description .= trim(str_replace('<br>', "\n", $specialDescriptionNodelist->item(0)->nodeValue)) . "\n"; | ||||
|                 if ($specialDescriptionNodelist->length === 2) { | ||||
|                     $description .= "\n"; | ||||
|                     $description .= trim(str_replace('<br>', "\n", $specialDescriptionNodelist->item(1)->nodeValue)) . "\n"; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if ($descriptionNodelist->length !== 0) { | ||||
|                 $description .= trim(str_replace('<br>', "\n", $descriptionNodelist->item(0)->nodeValue)); | ||||
|             } | ||||
|  | ||||
|             $metadata->title = $title; | ||||
|             $metadata->description = trim($description); | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         // censoredフラグの除去 | ||||
|         if (mb_strpos($metadata->image, '&c=1') !== false) { | ||||
|             $metadata->image = preg_replace('/&c=1/u', '', $metadata->image); | ||||
|         } | ||||
|  | ||||
|         // 抽出 | ||||
|         preg_match('~^(.+)((.+))の通販・購入はメロンブックス$~', $metadata->title, $match); | ||||
|         $title = $match[1]; | ||||
|         $maker = $match[2]; | ||||
|  | ||||
|         // 整形 | ||||
|         $description = 'サークル: ' . $maker . "\n"; | ||||
|  | ||||
|         if ($specialDescriptionNodelist->length !== 0) { | ||||
|             $description .= trim(str_replace('<br>', "\n", $specialDescriptionNodelist->item(0)->nodeValue)) . "\n"; | ||||
|             if ($specialDescriptionNodelist->length === 2) { | ||||
|                 $description .= "\n"; | ||||
|                 $description .= trim(str_replace('<br>', "\n", $specialDescriptionNodelist->item(1)->nodeValue)) . "\n"; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($descriptionNodelist->length !== 0) { | ||||
|             $description .= trim(str_replace('<br>', "\n", $descriptionNodelist->item(0)->nodeValue)); | ||||
|         } | ||||
|  | ||||
|         $metadata->title = $title; | ||||
|         $metadata->description = trim($description); | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -27,34 +27,30 @@ class NarouResolver implements Resolver | ||||
|         $cookieJar = CookieJar::fromArray(['over18' => 'yes'], '.syosetu.com'); | ||||
|  | ||||
|         $res = $this->client->get($url, ['cookies' => $cookieJar]); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|             $metadata->description = ''; | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata->description = ''; | ||||
|  | ||||
|             $dom = new \DOMDocument(); | ||||
|             @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'ASCII,JIS,UTF-8,eucJP-win,SJIS-win')); | ||||
|             $xpath = new \DOMXPath($dom); | ||||
|         $dom = new \DOMDocument(); | ||||
|         @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'ASCII,JIS,UTF-8,eucJP-win,SJIS-win')); | ||||
|         $xpath = new \DOMXPath($dom); | ||||
|  | ||||
|             $description = []; | ||||
|         $description = []; | ||||
|  | ||||
|             // 作者名 | ||||
|             $writerNodes = $xpath->query('//*[contains(@class, "novel_writername")]'); | ||||
|             if ($writerNodes->length !== 0 && !empty($writerNodes->item(0)->textContent)) { | ||||
|                 $description[] = trim($writerNodes->item(0)->textContent); | ||||
|             } | ||||
|  | ||||
|             // あらすじ | ||||
|             $exNodes = $xpath->query('//*[@id="novel_ex"]'); | ||||
|             if ($exNodes->length !== 0 && !empty($exNodes->item(0)->textContent)) { | ||||
|                 $summary = trim($exNodes->item(0)->textContent); | ||||
|                 $description[] = mb_strimwidth($summary, 0, 101, '…'); // 100 + '…'(1) | ||||
|             } | ||||
|  | ||||
|             $metadata->description = implode(' / ', $description); | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         // 作者名 | ||||
|         $writerNodes = $xpath->query('//*[contains(@class, "novel_writername")]'); | ||||
|         if ($writerNodes->length !== 0 && !empty($writerNodes->item(0)->textContent)) { | ||||
|             $description[] = trim($writerNodes->item(0)->textContent); | ||||
|         } | ||||
|  | ||||
|         // あらすじ | ||||
|         $exNodes = $xpath->query('//*[@id="novel_ex"]'); | ||||
|         if ($exNodes->length !== 0 && !empty($exNodes->item(0)->textContent)) { | ||||
|             $summary = trim($exNodes->item(0)->textContent); | ||||
|             $description[] = mb_strimwidth($summary, 0, 101, '…'); // 100 + '…'(1) | ||||
|         } | ||||
|  | ||||
|         $metadata->description = implode(' / ', $description); | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -32,32 +32,28 @@ class NijieResolver implements Resolver | ||||
|         } | ||||
|  | ||||
|         $res =  $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $html = (string) $res->getBody(); | ||||
|             $metadata = $this->ogpResolver->parse($html); | ||||
|             $crawler = new Crawler($html); | ||||
|         $html = (string) $res->getBody(); | ||||
|         $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()); | ||||
|         // DomCrawler内でjson内の日本語がHTMLエンティティに変換されるのでhtml_entity_decode | ||||
|         $json = html_entity_decode($crawler->filter('script[type="application/ld+json"]')->first()->text()); | ||||
|  | ||||
|             // 改行がそのまま入っていることがあるのでデコード前にエスケープが必要 | ||||
|             $data = json_decode(preg_replace('/\r?\n/', '\n', $json), true); | ||||
|         // 改行がそのまま入っていることがあるのでデコード前にエスケープが必要 | ||||
|         $data = json_decode(preg_replace('/\r?\n/', '\n', $json), true); | ||||
|  | ||||
|             $metadata->title = $data['name']; | ||||
|             $metadata->description = '投稿者: ' . $data['author']['name'] . PHP_EOL . $data['description']; | ||||
|             if ( | ||||
|                 isset($data['thumbnailUrl']) && | ||||
|                 !ends_with($data['thumbnailUrl'], '.gif') && | ||||
|                 !ends_with($data['thumbnailUrl'], '.mp4') | ||||
|             ) { | ||||
|                 // サムネイルからメイン画像に | ||||
|                 $metadata->image = str_replace('__rs_l160x160/', '', $data['thumbnailUrl']); | ||||
|             } | ||||
|             $metadata->tags = $crawler->filter('#view-tag span.tag_name')->extract('_text'); | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         $metadata->title = $data['name']; | ||||
|         $metadata->description = '投稿者: ' . $data['author']['name'] . PHP_EOL . $data['description']; | ||||
|         if ( | ||||
|             isset($data['thumbnailUrl']) && | ||||
|             !ends_with($data['thumbnailUrl'], '.gif') && | ||||
|             !ends_with($data['thumbnailUrl'], '.mp4') | ||||
|         ) { | ||||
|             // サムネイルからメイン画像に | ||||
|             $metadata->image = str_replace('__rs_l160x160/', '', $data['thumbnailUrl']); | ||||
|         } | ||||
|         $metadata->tags = $crawler->filter('#view-tag span.tag_name')->extract('_text'); | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -19,11 +19,7 @@ class OGPResolver implements Resolver, Parser | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             return $this->parse($res->getBody()); | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         } | ||||
|         return $this->parse($res->getBody()); | ||||
|     } | ||||
|  | ||||
|     public function parse(string $html): Metadata | ||||
|   | ||||
| @@ -25,18 +25,14 @@ class PatreonResolver implements Resolver | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|  | ||||
|             parse_str(parse_url($metadata->image, PHP_URL_QUERY), $query); | ||||
|             if (isset($query['token-time'])) { | ||||
|                 $expires_at_unixtime = $query['token-time']; | ||||
|                 $metadata->expires_at = Carbon::createFromTimestamp($expires_at_unixtime); | ||||
|             } | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         parse_str(parse_url($metadata->image, PHP_URL_QUERY), $query); | ||||
|         if (isset($query['token-time'])) { | ||||
|             $expires_at_unixtime = $query['token-time']; | ||||
|             $metadata->expires_at = Carbon::createFromTimestamp($expires_at_unixtime); | ||||
|         } | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -38,14 +38,10 @@ class PixivResolver implements Resolver | ||||
|     { | ||||
|         if (preg_match('~www\.pixiv\.net/user/\d+/series/\d+~', $url, $matches)) { | ||||
|             $res = $this->client->get($url); | ||||
|             if ($res->getStatusCode() === 200) { | ||||
|                 $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|                 $metadata->image = $this->proxize($metadata->image); | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|             $metadata->image = $this->proxize($metadata->image); | ||||
|  | ||||
|                 return $metadata; | ||||
|             } else { | ||||
|                 throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|             } | ||||
|             return $metadata; | ||||
|         } | ||||
|  | ||||
|         parse_str(parse_url($url, PHP_URL_QUERY), $params); | ||||
| @@ -58,32 +54,28 @@ class PixivResolver implements Resolver | ||||
|         } | ||||
|  | ||||
|         $res = $this->client->get('https://www.pixiv.net/ajax/illust/' . $illustId); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $json = json_decode($res->getBody()->getContents(), true); | ||||
|             $metadata = new Metadata(); | ||||
|         $json = json_decode($res->getBody()->getContents(), true); | ||||
|         $metadata = new Metadata(); | ||||
|  | ||||
|             $metadata->title = $json['body']['illustTitle'] ?? ''; | ||||
|             $metadata->description = '投稿者: ' . $json['body']['userName'] . PHP_EOL . strip_tags(str_replace('<br />', PHP_EOL, $json['body']['illustComment'] ?? '')); | ||||
|             $metadata->image = $this->proxize($json['body']['urls']['regular'] ?? ''); | ||||
|         $metadata->title = $json['body']['illustTitle'] ?? ''; | ||||
|         $metadata->description = '投稿者: ' . $json['body']['userName'] . PHP_EOL . strip_tags(str_replace('<br />', PHP_EOL, $json['body']['illustComment'] ?? '')); | ||||
|         $metadata->image = $this->proxize($json['body']['urls']['regular'] ?? ''); | ||||
|  | ||||
|             // ページ数の指定がある場合は画像URLをそのページにする | ||||
|             if ($page != 0) { | ||||
|                 $metadata->image = str_replace('_p0', '_p'.$page, $metadata->image); | ||||
|             } | ||||
|         // ページ数の指定がある場合は画像URLをそのページにする | ||||
|         if ($page != 0) { | ||||
|             $metadata->image = str_replace('_p0', '_p' . $page, $metadata->image); | ||||
|         } | ||||
|  | ||||
|             // タグ | ||||
|             if (!empty($json['body']['tags']['tags'])) { | ||||
|                 foreach ($json['body']['tags']['tags'] as $tag) { | ||||
|                     // 一部の固定キーワードは無視 | ||||
|                     if (array_search($tag['tag'], ['R-18', 'イラスト', 'pixiv', 'ピクシブ'], true) === false) { | ||||
|                         $metadata->tags[] = preg_replace('/\s/', '_', $tag['tag']); | ||||
|                     } | ||||
|         // タグ | ||||
|         if (!empty($json['body']['tags']['tags'])) { | ||||
|             foreach ($json['body']['tags']['tags'] as $tag) { | ||||
|                 // 一部の固定キーワードは無視 | ||||
|                 if (array_search($tag['tag'], ['R-18', 'イラスト', 'pixiv', 'ピクシブ'], true) === false) { | ||||
|                     $metadata->tags[] = preg_replace('/\s/', '_', $tag['tag']); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         } | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,21 +24,17 @@ class PlurkResolver implements Resolver | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|  | ||||
|             $dom = new \DOMDocument(); | ||||
|             @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|             $xpath = new \DOMXPath($dom); | ||||
|             $imageNode = $xpath->query('//div[@class="text_holder"]/a[1]')->item(0); | ||||
|         $dom = new \DOMDocument(); | ||||
|         @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|         $xpath = new \DOMXPath($dom); | ||||
|         $imageNode = $xpath->query('//div[@class="text_holder"]/a[1]')->item(0); | ||||
|  | ||||
|             if ($imageNode) { | ||||
|                 $metadata->image = $imageNode->getAttribute('href'); | ||||
|             } | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         if ($imageNode) { | ||||
|             $metadata->image = $imageNode->getAttribute('href'); | ||||
|         } | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,21 +24,17 @@ class SteamResolver implements Resolver | ||||
|         $appid = $matches[1]; | ||||
|  | ||||
|         $res = $this->client->get('https://store.steampowered.com/api/appdetails/?l=japanese&appids=' . $appid); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $json = json_decode($res->getBody()->getContents(), true); | ||||
|             if ($json[$appid]['success'] === false) { | ||||
|                 throw new \RuntimeException("API response [$appid][success] is false: $url"); | ||||
|             } | ||||
|             $data = $json[$appid]['data']; | ||||
|             $metadata = new Metadata(); | ||||
|  | ||||
|             $metadata->title = $data['name'] ?? ''; | ||||
|             $metadata->description = strip_tags(str_replace('<br />', PHP_EOL, html_entity_decode($data['short_description'] ?? ''))); | ||||
|             $metadata->image = $data['header_image'] ?? ''; | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         $json = json_decode($res->getBody()->getContents(), true); | ||||
|         if ($json[$appid]['success'] === false) { | ||||
|             throw new \RuntimeException("API response [$appid][success] is false: $url"); | ||||
|         } | ||||
|         $data = $json[$appid]['data']; | ||||
|         $metadata = new Metadata(); | ||||
|  | ||||
|         $metadata->title = $data['name'] ?? ''; | ||||
|         $metadata->description = strip_tags(str_replace('<br />', PHP_EOL, html_entity_decode($data['short_description'] ?? ''))); | ||||
|         $metadata->image = $data['header_image'] ?? ''; | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -25,20 +25,16 @@ class ToranoanaResolver implements Resolver | ||||
|     public function resolve(string $url): Metadata | ||||
|     { | ||||
|         $res = $this->client->get($url); | ||||
|         if ($res->getStatusCode() === 200) { | ||||
|             $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|         $metadata = $this->ogpResolver->parse($res->getBody()); | ||||
|  | ||||
|             $dom = new \DOMDocument(); | ||||
|             @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|             $xpath = new \DOMXPath($dom); | ||||
|             $imgNode = $xpath->query('//*[@id="preview"]//img')->item(0); | ||||
|             if ($imgNode !== null) { | ||||
|                 $metadata->image = $imgNode->getAttribute('src'); | ||||
|             } | ||||
|  | ||||
|             return $metadata; | ||||
|         } else { | ||||
|             throw new \RuntimeException("{$res->getStatusCode()}: $url"); | ||||
|         $dom = new \DOMDocument(); | ||||
|         @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8')); | ||||
|         $xpath = new \DOMXPath($dom); | ||||
|         $imgNode = $xpath->query('//*[@id="preview"]//img')->item(0); | ||||
|         if ($imgNode !== null) { | ||||
|             $metadata->image = $imgNode->getAttribute('src'); | ||||
|         } | ||||
|  | ||||
|         return $metadata; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,21 +3,12 @@ | ||||
| namespace Tests\Unit\MetadataResolver; | ||||
|  | ||||
| use App\MetadataResolver\OGPResolver; | ||||
| use GuzzleHttp\Exception\ClientException; | ||||
| use Tests\TestCase; | ||||
|  | ||||
| class OGPResolverTest extends TestCase | ||||
| { | ||||
|     use CreateMockedResolver; | ||||
|  | ||||
|     public function testMissingUrl() | ||||
|     { | ||||
|         $this->createResolver(OGPResolver::class, '', [], 404); | ||||
|  | ||||
|         $this->expectException(\RuntimeException::class); | ||||
|         $this->resolver->resolve('http://example.com/404'); | ||||
|     } | ||||
|  | ||||
|     public function testResolve() | ||||
|     { | ||||
|         $response = <<< 'HTML' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 eai04191
					eai04191