OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

はじめてのGoogle Chrome拡張機能開発メモ:YouTubeに高評価率を表示

YouTubeでビデオに対する低評価の数が見えなくなりました。

ヘイトからクリエイターを守るという意図はわかるものの、あらかじめおおよその評価を知ることができなくなり、ユーザ視点ではやっぱり不便になった。

せめて、再生回数に対する高評価数の割合を判断材料にしたいと思い、勉強がてらChrome拡張を作ってみることにしました。

作成するプラグインのイメージ

  • YouTube のURLを開くと勝手に起動する
  • ビデオの一覧画面(トップのフィードや検索結果)に 高評価数 / 視聴回数 のパーセント表示を追加する
  • オプション画面から、YouTube Data API キーを設定できるようにする

ビデオの視聴回数や高評価数は YouTube Data API で取れるが、API を利用するにはユーザ側で Key を取得する必要がある。
ユーザによって Key が設定されていれば API を使うものとして、Key がないときも一応スクレイピングで頑張って取得する。

できたもの

ストア登録までやってみた。

ソース:https://github.com/oika/youtube-upvote-proportion

f:id:kd1:20220128140542p:plain

ストア登録にあたってはスクリーンショットやらアイコンやらが必要になる。
あと developer アカウント登録に5ドルくらいかかったが、1回だけ払えばいいようなので払った。

審査に時間かかりますよと書いてあったが、審査結果のメールが全然届かず、気づいたらストア登録されていた。メールは見落としたのかそもそも来ないものなのか不明。

参考にした情報

まずは拡張開発の概要・全体像について、以下の記事を参考にさせていただいた。

ただし、どちらも Manifest V2 に準拠した内容になっており、これから開発するなら V3 に従って作成しないとダメだと思うのでそこだけ注意。

まあ V2 の書き方で書いていると動かなかったりエラーになったりすると思うので、そしたら V3 の書き方を確認する、という感じでもあまり問題ないかも。
公式の V3 移行ガイドは こちら

今回Viewは最低限の設定画面くらいしか作らないので、React や Vue は使わない。
TypeScript だけあれば良くて、バンドラとして webpack を使うことにする。

ソースの基本構成は以下のページを参考にした。

自分で webpack の構成書くと毎回微妙にハマる。今回も以下のあたりがよくわからなくなって苦戦した。

考えられる構成パターン

試行錯誤したところを整理しておく。
特定のページで動く拡張を作る場合、マニフェストの構成パターンは以下のような感じになるのかなと。自信ないので参考程度にしてください。

構成1)content_scripts のみ

いちばんシンプルな構成として、特定のURLパターンにマッチする画面を表示したときに特定のスクリプトを動かすだけなら、 content_scripts に実行するJSファイルを定義するだけで良い。

構成2)content_scripts + ServiceWorker

各画面のスクリプト間で情報の受け渡しやデータの使いまわしを行いたい場合、 background > service_worker として、バックグラウンドで動かすスクリプトを定義する。

今回の拡張でいうと、1度取得したビデオのレート情報はキャッシュして使えるように、バックグラウンドにストアになるインスタンスを置いている。

画面側のスクリプトとバックグラウンドのデータやりとりには Message passing を使う。

あるいは、ブラウザを閉じた後も保持したいデータの置き場には Storage API を使うことができる。
これは ServiceWorker を経由せず画面側のスクリプトから直接アクセスすることも可能。

構成3)ServiceWorker で tabs.update を検知してスクリプト注入

あるページから別のページへの移動を連続的に追いかけて実行スクリプトを制御する、みたいな必要がある場合、 content_scripts に画面のスクリプトを定義する代わりに、ServiceWorker 側で tabs.onUpdated を監視し、 executeScript で実行スクリプトを注入することもできる。

その他の部品

上記3種類とあわせて以下の部品の利用を検討できる。

  • オプション画面:以下のいずれかで定義
    • options_page : 新しいタブで開かせるオプションページ
    • options_ui : 拡張機能の管理画面内でオプションウィンドウをモーダル表示
  • アクション: action で拡張機能アイコンクリック時の動作を定義

ハマったところ

SPAだと、URLごとに content_scripts を定義してもうまくいかない

最初、YouTube内の /feed/explore , /results といったURLごとに content_scripts を定義していたが、SPAなのでURLが変わるタイミングとDOMの内容が切り替わるタイミングが同期せず、期待したタイミングで動いてくれなかった。

また同じURLのままビデオ一覧だけが更新されるようなケースでは、どのみちURLだけでなくDOMの変更も監視する必要がある。

なので最終的に、URLはドメイン部分が一致するかどうかだけを確認し、すべて同じスクリプトで受け取ってから、DOMの中身で処理を振り分けるようにした。

DOMの更新監視には MutationObserver を使用。

オプション画面でJSの動作確認ができない

options_ui で定義したオプション画面の html から、JavaScript が実行されていることをとりあえず alert() とか console.log() とかで確認しようとしたら、これがちっとも動いてくれない。

まず、公式の情報を見つけられていないが、そもそも alert()console.log() がオプションウィンドウ内のスクリプトでは動かないっぽい。
試していないが 古いStackOverflow によると、chrome.extension.getBackgroundPage().alert() なら動作するらしい。

そして、拡張機能のセキュリティポリシーとして、html 内のインラインスクリプトは動かないのだということにも気づくのが遅れた*1

さらに、options_ui は埋め込みの部品として表示されるので、 document.onload を実行のトリガにしていると動かないという罠も。
サンプルでも DOMContentLoaded をトリガにしていた。

余談

  • どうやら Edge や Firefox 用の拡張機能も、manifest の構成含め開発方法がそこそこ似ているっぽいので、真面目にやるなら最初からマルチブラウザ対応を考慮して設計するのが良さそう
  • Return YouTube Dislike という拡張機能というかプロジェクトがあって、これはどうやら自分らでサーバ持って低評価数をストックしておくみたいなガチなことをやっているっぽい
  • 今回の拡張を動かしてみた感じ、再生数が多いビデオほど、評価数(高+低)の割合は低くなる傾向がありそう
    • なので高評価が同じ 5% であっても、 50 / 1,000 より 50,000 / 1,000,000 のほうが凄いと思われるので、そこを加味できる計算式があると良いのかもしれない

*1:一応 manifest の content_security_policy で許可はできるのかも。試していない