Youtube用のChrome拡張機能を作ってストアに配信してみた。

私はずっとYoutubeに欲しい機能があったのです。
それはライブチャットのデフォルトを「すべてのチャット」にするという機能。

というのもチャット欄はデフォルトで「トップチャット」になっているのですが私は不適切なコメントでさえも見たいタイプなのでいつも「すべてのチャットに」切り替えていました。

だけど毎度毎度切り替えがめんどくさい!!

設定にありそうなのにどれだけ探しても無い…拡張機能にもない…
しょうがない。。以前から興味はあったので結局拡張機能を自作することにしました。

準備

まずは拡張機能の作り方を調べてみると最低限以下のファイルが必要とのこと。

ファイル構造
├── content.js(実際に動かすファイル)
├── manifest.json(設定ファイル) 
├── icon.png(拡張機能のアイコン画像)

他にも設定画面や、UIが必要な場合はHTMLやCSSが必要ですが今回は使用しませんでした。

JSはほぼ初心者ですが実際に手を動かしてみることにします。
テキストエディタはVisual Studio Codeを使いました。

実装

manifest.json(設定ファイル)

拡張機能の設定ファイルです。備忘録として今回設定した各項目についてまとめておきます。

JSON
{
  "name": "Show All YouTube Chats by Default",
  "version": "1.0.0",
  "manifest_version": 3,
  "description": "YouTubeのデフォルトチャット表示をトップチャットからすべてのチャットに変更します。",
  "icons": {
    "96": "icon48.png"
  },
  "content_scripts": [
    {
      "matches": ["*://www.youtube.com/*"],
      "js": ["content.js"]
    }
  ]
}

manifest.jsonの内容

name
拡張機能の名前。今回は「Show All YouTube Chats by Default」にしました。

version
拡張機能のバージョン。初回リリースなので「1.0.0」にしています。

manifest_version
拡張機能の仕様バージョン。最新のV3を使っています。V2のサポートが終わったみたいなのでV3が必須みたいです。

description
拡張機能の説明。

icons
拡張機能のアイコン設定。今回は「icon48.png」にしました。

content_scripts
Webページにスクリプトを埋め込む設定。”js” に実行するスクリプト、”matches” に適用するページを指定します。今回はすべてのYouTubeページに適用するようにしています。

content.js(実際に動かすファイル)

実際に動かすプログラムです。JSはほぼ知識がないので1週間以上かかってしまいました。
多分無駄な記述や間違いもあると思いますがなんとか動くものができました。

動いたときは少し感動しましたね…

JavaScript
/*チャット選択ボタンの生成判定後、存在すれば「すべてのチャット」ボタンをクリック*/
const buttonClick = () => {
  const chatFrame = document.getElementById("chatframe");
  if (chatFrame == null) {
    setTimeout(buttonClick, 1000);
    return;
  }
  const chatButton = chatFrame.contentWindow.document.querySelectorAll(
    ".yt-simple-endpoint.style-scope.yt-dropdown-menu"
  );
  if (chatButton[chatButton.length - 1] == null) {
    setTimeout(buttonClick, 1000);
    return;
  }
  chatButton[chatButton.length - 1].click();
};

/*ObserverがiconImageの変化を感知した際の処理*/
const observer = new MutationObserver(() => {
  /*ページ遷移の際にチャット欄が再生成されるので一度削除*/
  const chatFrame = document.getElementById("chatframe");
  if (chatFrame != undefined) {
    const chathtml = chatFrame.contentWindow.document.querySelector("html");
    chathtml.removeChild(chathtml.lastElementChild);
    buttonClick();
  }
});

/*Observer設定*/
const config = {
  attributes: true,
  attributeOldValue: true,
  attributeFilter: ["href"],
};

/*アイコン画像の生成待ち*/
const iconImagePolling = (iconImg) => {
  if (iconImg == null) {
    setTimeout(iconImagePolling(iconImg), 1000);
    return;
  }
};

/*初期処理*/
const init = setInterval(() => {
  const iconImg = document.querySelector(
    ".yt-simple-endpoint.style-scope.ytd-video-owner-renderer"
  );
  if (iconImg != null) {
    observer.observe(iconImg, config);
    clearInterval(init);
    buttonClick();
  } else {
    iconImagePolling(iconImg);
  }
}, 1000);

content.jsの内容をざっくり

その1
チャット選択ボタンを探してクリックする

YouTubeのチャット欄は動的に生成されるため、まず存在を確認する必要があります。
1秒ごとにチャット欄の iframe が生成されているかをチェックし、見つかったら次はチャット選択ボタンの存在を判定します。
ボタンも生成されたことを確認できたら、最後に一番最後のボタン(「すべてのチャット」ボタン)をクリックします。

悩んだポイント
最初、チャット内の要素がまったく取得できず苦戦しました。
実は iframe の中に要素があったため、通常の document.querySelector では取得できませんでした。
そこで、以下のように contentWindow.document を使うことで iframe 内の要素を取得できます。

const chatButton = chatFrame.contentWindow.document.querySelectorAll(
".yt-simple-endpoint.style-scope.yt-dropdown-menu"
);

その2
ページ移動時にチャット欄をリセット

YouTubeでは、別ページに移動するとチャット欄がリロードされます。
問題なのは、「すべてのチャット」ボタンをクリックした後にリロードが発生するため、
せっかく選択したのに 「トップチャット」に戻ってしまうことです。

これは結構強引ですがページ遷移が確認されたらチャット欄を削除する処理を入れました。
その後、もう一度 「すべてのチャット」ボタンをクリックする処理を実行して、リロード後も設定が維持されるようにしています。

その3
ページ遷移時の処理

ページ遷移を判定する方法として、 配信者のアイコン(チャンネルリンク)を監視し、画像が変わったらページが変わったと判断するようにしました。

本当はURLやタイトルタグの変化で判定したかったのですが、うまくいかなかったためこの方法にしました。
ページ遷移が確認できたら、「チャット欄を削除 → ボタンを押し直す」 の順で処理を実行します。

完成したのでストアに公開

完成した拡張機能はYouTubeライブやアーカイブを開くと以下のように自動ですべてのチャットに切り替わります。

実装後、結局 Chrome ウェブストアにも公開しちゃいました。

▼ 公開ページはこちら

意外にも現在25人もインストールしてくれているようです。

Chrome ウェブストアへの公開方法(ざっくり)

Chrome 拡張機能は500円の登録費を払えば誰でもウェブストアに公開できます。
一度登録すれば次からの公開は無料です。公開までの流れはこんな感じ。

拡張機能を準備

manifest.json を含むファイルを ZIP 圧縮

デベロッパー登録(500円

デベロッパーコンソール で支払い

拡張機能をZIP化してアップロード

新しいアイテムを追加から ZIP を選択

ストア情報を入力

タイトル・説明・アイコン・スクショを設定

審査を待つ(1~3日)

審査OKならストアに公開完了

まとめ

とにかく途中で投げ出さず最後まで完成させられて良かったです。
ボタン1つクリックさせるだけなのに思った以上に苦戦しました。プログラマーは大変ですね…
今後は、例えば固定コメントや登録者限定メッセージなど、個人的にあまり必要ない機能を非表示にできる設定などを追加できたらいいなぁとふんわり思ってます。

アップデートしたらまたブログに残そうと思います!