✅ニコニコのURLからサムネイル画像を貼り付けるUserScriptを作る
from ニコニコ動画のURLからサムネイル画像を貼り付けるUserScriptを作る
開発着手前に考えていた仕様
メニューにニコニコのアイコンを追加する
クリックするとモーダルでURL入力を促される
https://www.nicovideo.jp/watch/sm37956838 を貼ると
[サムネイルURL https://www.nicovideo.jp/watch/sm37956838]が貼り付けられる
ニコニコ動画の試聴URLからサムネイル画像URLを取得するAPI
---
サムネのURLを取得する
方法1
https://ext.nicovideo.jp/api/getthumbinfo/sm37956838
レスポンスがXMLで帰ってくる....
方法2
https://www.nicovideo.jp/watch/sm37956838 にCLIでアクセスするとogpが設定されているのでこのURLを抽出する
こっちは想定されていそうだしURLの意味がわかりやすいのでこっちをやってみる
帰ってこない動画がある
例:https://www.nicovideo.jp/watch/sm41994908
対応済み
せっかくなのでdenoで開発をしてみる
haederのURLが入っているmetaタグは次のように取れるが、ここからURLをとるのに苦戦
nodeをconsole.log()してみるとattributesにURLがある。これを取得したい
code:zsh
attributes: NamedNodeMap {
property: "og:image",
content: "https://img.cdn.nimg.jp/s/nicovideo/thumbnails/37231246/37231246.20071609.original/r1280x720l?key=44..."
},
deno-dom-wasmで取得したNodeからURLの文字列を取得できない
このMeta nodeはElementである
code:ts
(doc.querySelectorAll('metaproperty="og:image"')0.nodeType)
// 1 = Element
https://developer.mozilla.org/ja/docs/Web/API/Element
しかしElementにあるはずのattributesを取得しようとするとNodeにattributesはないとエラーが出る
code:zsh
error: TS2339 ERROR: Property 'attributes' does not exist on type 'Node'.
console.log(doc.querySelectorAll('metaproperty="og:image"')0.attributes)
attributesはElementの要素なのでNodeをElementにしたい
問題:castしても同種のエラーが出る
code:zsh
error: TS2304 ERROR: Cannot find name 'Element'.
const node = doc.querySelectorAll('metaproperty="og:image"')0 as Element;
解決策:deno-domのElementをロードしておけばcastできる
code:これをimportする必要があった.ts
import { Element } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
/watch/smXXXからサムネイルのURLを取得することができるようになった
code:mod.ts
import {
DOMParser,
Element
} from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
// https://www.nicovideo.jp/watch/sm37231246
export async function getThumUrl(url: string): Promise<string> {
const res = await fetch(url);
const doc = new DOMParser().parseFromString(await res.text(), "text/html");
if (doc == null) {
throw new Error("document not found");
}
const node = doc.querySelectorAll('metaproperty="og:image"')0 as Element;
const thumbUrl = node.getAttribute("content");
if (thumbUrl == null) {
throw new Error("document not found");
}
console.log(thumbUrl);
return thumbUrl;
}
あとはこれをニコニコ動画のURLからサムネイル画像を貼り付けるUserScript#6270de0a774b1700005090e4に組み込めればいい
どうやって?
URLの取得はこうすればいい
1. prompt方式
tweet2image-upload#6270cc6f774b1700005d395b
2. 範囲選択の文字列から正規表現で抽出
denoのプログラムをどうUserScriptにすればいい?
/takkerをみる
https://deno.land/manual/tools/bundler を使う?
やってみたらサイズが大きすぎた
minifyしたい
/deno-ja/Deno scriptをbundle & minifyするはエラーが出る
code:zsh
error: TS2322 ERROR: Type '"esm"' is not assignable to type '"module" | "classic" | undefined'.
bundle: 'esm',
~~~~~~
at https://scrapbox.io/api/code/deno-ja/Deno_script%E3%82%92bundle_&_minify%E3%81%99%E3%82%8B/script.ts:5:3
/takker/scrapbox-bundlerを使えばいいのかな
https://scrapbox.io/motoso/%E3%83%8B%E3%82%B3%E3%83%8B%E3%82%B3%E3%81%AEURL%E3%81%8B%E3%82%89%E3%82%B5%E3%83%A0%E3%83%8D%E3%82%A4%E3%83%AB%E7%94%BB%E5%83%8F%E3%82%92%E8%B2%BC%E3%82%8A%E4%BB%98%E3%81%91%E3%82%8BUserScript
compile button
この結果をScrapboxに貼り付けようとすると、imput is too largeになってしまう
コードをScrapbox上で動かすのは諦めてAPIにすることにした
Cloud functionsにデプロイしてみる?
Denoはそのままだと対応していない
ここまで大げさにするならタイトルとコメント載っけるのをSVGを作るまでやりたい
ダッシュボードでエラーが出て進めなかった
AWSにデプロイしてみる
https://github.com/hayd/deno-lambda を使ったけど実行に失敗した
https://github.com/hayd/deno-lambda/blob/master/QUICK-START.md の手順通りにすると
derror: Module not found "file:///var/runtime/mod.ts".
https://github.com/hayd/deno-lambda/blob/56d8b4e4030c0096f7b5c589ba1194201e2f97dc/QUICK-START.md でも同様
zipでダウンロードするサンプルコードが誤っている
罠すぎる基素.icon
https://zenn.dev/kawarimidoll/scraps/44009b392ed9f2#:~:text=適当にログを出すよう書き換えてDeploy もサンプルコードの同じで動いていると書いてあるのでどこかのタイミングでは動いていたのかもしれない
修正したら動いた
なんやかんやあり完成
https://p56crwq5p6.execute-api.ap-northeast-1.amazonaws.com/production/getNicovideoThumbnailUrl?watchUrl=https://www.nicovideo.jp/watch/sm39969896
ブラウザのセキュリティがあるからこれをそのまま使うことはできない
takkerさんのURL開くのはどうやっているか見てみたらScrapboxのAPIを使っていた
https://github.com/takker99/scrapbox-userscript-std/blob/4ab754360c40a9b3cda96ae0c5d088d0561beaf9/rest/auth.ts
というわけでUserScriptを作った nicovideo-thumbnail-proxy
/takker/GM xmlhttpRequest
CLI版(動作確認のために作った副産物)
使い方
code:zsh
deno run --allow-net https://scrapbox.io/api/code/motoso/✅ニコニコのURLからサムネイル画像を貼り付けるUserScriptを作る/cli.ts --url https://www.nicovideo.jp/watch/sm37587127
URLは正しい前提
code:cli.ts
import { DOMParser, Element } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
async function getThumUrlAndTitle(url: string): Promise<string[]> {
let res = await fetch(url);
let text = await res.text();
let doc = new DOMParser().parseFromString(text, "text/html");
// HTMLからog:imageを取得する
if (doc) {
const meta = doc.querySelector('metaproperty="og:image"');
if (meta) {
const thumbUrl = meta.getAttribute("content");
if (thumbUrl) {
const titleNode = doc.querySelector("head > title");
const title = titleNode ? titleNode.textContent : "";
return thumbUrl, title;
}
}
}
// fallback: 動画IDを抽出してXML APIから取得する
const videoIdMatch = url.match(/watch\/(^/?+)/);
if (!videoIdMatch) {
throw new Error("動画IDを抽出できませんでした");
}
const videoId = videoIdMatch1;
const fallbackUrl = https://ext.nicovideo.jp/api/getthumbinfo/${videoId};
res = await fetch(fallbackUrl);
text = await res.text();
// DenoのDOMParserは"text/xml"をサポートしていないため、"text/html"でパースする
const xmlDoc = new DOMParser().parseFromString(text, "text/html");
if (!xmlDoc) {
throw new Error("XMLのパースに失敗しました");
}
const thumbNode = xmlDoc.querySelector("thumbnail_url");
if (!thumbNode || !thumbNode.textContent) {
throw new Error("XMLからthumbnail_urlを取得できませんでした");
}
const thumbUrlFallback = thumbNode.textContent;
const titleNodeFallback = xmlDoc.querySelector("title");
const titleFallback = titleNodeFallback ? titleNodeFallback.textContent : "";
return thumbUrlFallback, titleFallback;
}
if (import.meta.main) {
// コマンドライン引数からURLを取得。指定がなければデフォルトでsm41994908を利用
const url = Deno.args0 || "https://www.nicovideo.jp/watch/sm41994908";
try {
const thumbnailUrl, title = await getThumUrlAndTitle(url);
const result = { thumbnailUrl, title };
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error("Error:", error);
Deno.exit(1);
}
}
code:cli-old1.ts
import { parse } from "https://deno.land/std@0.136.0/flags/mod.ts";
import {
DOMParser,
Element
} from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
async function getThumUrl(url: string): Promise<string> {
const res = await fetch(url);
const doc = new DOMParser().parseFromString(await res.text(), "text/html");
if (doc == null) {
throw new Error("document not found");
}
const node = doc.querySelectorAll('metaproperty="og:image"')0 as Element;
const title = doc.querySelectorAll('head > title')0 as Element;
console.log(title.textContent);
const thumbUrl = node.getAttribute("content");
if (thumbUrl == null) {
throw new Error("document not found");
}
console.log(thumbUrl);
return thumbUrl;
}
const parsedArgs = parse(Deno.args);
getThumUrl(parsedArgs.url);
--   
DenoのDOMはElement classしかないらしい(2021年11月)
https://github.com/b-fuze/deno-dom/issues/72
So far Deno DOM only implements the Element class. .href is a property of HTMLAnchorElement, a more specific DOM element implementation, of which there are many, and I haven't got around to implementing yet. So for now you can use the getAttribute("href") method of Element.
DOMのparseはdeno-dom-wasmよりcheerioの方が便利らしい
/villagepump/@takker#6270eca11280f0000068e5fb
tsconfigを追加したら大量のエラーが出るようになった
https://deno-ja.vercel.app/manual@v1.8.3/typescript/configuration
Deno は TypeScript の設定ファイルをサポートしていますが、他と違い、設定ファイルの検出を使用は自動ではありません。Deno で TypeScript 設定ファイルを使うには、コマンドラインでパスを指定する必要があります