Added ニコニコ動画 metadata support.

This commit is contained in:
Pachira 2023-01-25 13:37:38 +00:00
parent 5fe283a2a7
commit 19b46188ad
6 changed files with 154 additions and 2 deletions

View file

@ -12,6 +12,7 @@ if(UIH_DEBUG)
$ctx->registerLookup(new \Uiharu\Lookup\TwitterLookup);
$ctx->registerLookup(new \Uiharu\Lookup\YouTubeLookup);
$ctx->registerLookup(new \Uiharu\Lookup\NicoNicoLookup);
// this should always come AFTER other lookups involved http(s)
$ctx->registerLookup(new \Uiharu\Lookup\WebLookup);

View file

@ -17,6 +17,7 @@ use Uiharu\Lookup\TwitterLookupResult;
use Uiharu\Lookup\TwitterLookupTweetResult;
use Uiharu\Lookup\TwitterLookupUserResult;
use Uiharu\Lookup\YouTubeLookupResult;
use Uiharu\Lookup\NicoNicoLookupResult;
use Index\MediaType;
use Index\Data\IDbConnection;
use Index\Http\HttpFx;
@ -154,6 +155,14 @@ final class v1_0 implements \Uiharu\IApi {
}
}
if($result instanceof NicoNicoLookupResult) {
$resp->nicovideo_video_id = $result->getNicoNicoVideoId();
if(UIH_DEBUG) {
$resp->dbg_nicovideo_thumb_info = $result->getNicoNicoThumbInfo()->ownerDocument->saveXML();
}
}
if($result instanceof IHasMediaInfo) {
if($result->isMedia()) {
$resp->is_image = $result->isImage();

View file

@ -0,0 +1,63 @@
<?php
namespace Uiharu\Lookup;
use DOMDocument;
use RuntimeException;
use Uiharu\Url;
final class NicoNicoLookup implements \Uiharu\ILookup {
public function match(Url $url): bool {
if(!$url->isWeb() || ($url->getHost() !== 'www.nicovideo.jp' && $url->getHost() !== 'nicovideo.jp'))
return false;
if(str_starts_with($url->getPath(), '/watch/sm'))
return true;
return false;
}
public function lookup(Url $url): NicoNicoLookupResult {
$videoId = explode('/', trim($url->getPath(), '/'))[1] ?? '';
if(empty($videoId))
throw new RuntimeException('Nico Nico Douga video id missing.');
$thumbDoc = self::lookupThumbInfo($videoId);
$thumbResp = $thumbDoc->getElementsByTagName('nicovideo_thumb_response')[0] ?? null;
if(empty($thumbResp) || !$thumbResp->hasAttribute('status') || $thumbResp->getAttribute('status') !== 'ok')
throw new RuntimeException('Nico Nico Douga video with the given id could not be found.');
$thumbInfo = $thumbResp->getElementsByTagName('thumb')[0] ?? null;
if(empty($thumbInfo))
throw new RuntimeException('Nico Nico Douga thumb info missing from API result????');
return new NicoNicoLookupResult($url, $videoId, $thumbInfo);
}
private static function lookupThumbInfo(string $videoId): DOMDocument {
$curl = curl_init("https://ext.nicovideo.jp/api/getthumbinfo/{$videoId}");
curl_setopt_array($curl, [
CURLOPT_AUTOREFERER => false,
CURLOPT_CERTINFO => false,
CURLOPT_FAILONERROR => false,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TCP_FASTOPEN => true,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_TIMEOUT => 5,
CURLOPT_USERAGENT => 'Uiharu/' . UIH_VERSION,
]);
$resp = curl_exec($curl);
curl_close($curl);
if(empty($resp))
throw new RuntimeException('Nico Nico Douga API request failed.');
$doc = new DOMDocument;
if(!$doc->loadXML($resp))
throw new RuntimeException('Failed to parse Nico Nico Douga API response.');
return $doc;
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace Uiharu\Lookup;
use DOMElement;
use Index\MediaType;
use Uiharu\Url;
final class NicoNicoLookupResult implements \Uiharu\ILookupResult {
private DOMElement|false|null $title = null;
private DOMElement|false|null $description = null;
private DOMElement|false|null $previewImage = null;
public function __construct(
private Url $url,
private string $videoId,
private DOMElement $thumbInfo,
) {}
public function getUrl(): Url {
return $this->url;
}
public function getObjectType(): string {
return 'niconico:video';
}
public function getNicoNicoVideoId(): string {
return $this->videoId;
}
public function getNicoNicoThumbInfo(): DOMElement {
return $this->thumbInfo;
}
public function hasMediaType(): bool {
return false;
}
public function getMediaType(): MediaType {
throw new RuntimeException('Unsupported');
}
public function hasColour(): bool {
return true;
}
public function getColour(): int {
return 0x252525;
}
public function hasTitle(): bool {
if($this->title === null)
$this->title = $this->thumbInfo->getElementsByTagName('title')[0] ?? false;
return $this->title !== false;
}
public function getTitle(): string {
return $this->hasTitle() ? trim($this->title->textContent) : 'No Title';
}
public function hasSiteName(): bool {
return true;
}
public function getSiteName(): string {
return 'ニコニコ動画';
}
public function hasDescription(): bool {
if($this->description === null)
$this->description = $this->thumbInfo->getElementsByTagName('description')[0] ?? false;
return $this->description !== false && !empty($this->description->textContent);
}
public function getDescription(): string {
return $this->hasDescription() ? trim($this->description->textContent) : '';
}
public function hasPreviewImage(): bool {
if($this->previewImage === null)
$this->previewImage = $this->thumbInfo->getElementsByTagName('thumbnail_url')[0] ?? false;
return $this->previewImage !== false && !empty($this->previewImage->textContent);
}
public function getPreviewImage(): string {
return $this->hasPreviewImage() ? (trim($this->previewImage->textContent) . '.L') : '';
}
}

View file

@ -4,7 +4,6 @@ namespace Uiharu\Lookup;
use RuntimeException;
use Uiharu\Config;
use Uiharu\Url;
use Index\MediaType;
final class YouTubeLookup implements \Uiharu\ILookup {
private const SHORT_DOMAINS = [

View file

@ -12,7 +12,7 @@ define('UIH_DEBUG', is_file(UIH_ROOT . '/.debug'));
define('UIH_PUBLIC', UIH_ROOT . '/public');
define('UIH_SOURCE', UIH_ROOT . '/src');
define('UIH_LIBRARY', UIH_ROOT . '/lib');
define('UIH_VERSION', '20220716');
define('UIH_VERSION', '20230125');
require_once UIH_LIBRARY . '/index/index.php';