OPENREC のチャットを流す Chrome 拡張機能

2022/12/17

OPENREC.tv のチャットを、画面上に表示する Chrome 拡張機能を作成しました。

ありがたいことに、現在は約 1500 人のユーザーに利用していただいています。(2024/11 時点)

機能

  • OPENREC.tv の配信・キャプチャーのチャットを画面上に流します。
  • 拡張機能のアイコンをクリックして、設定パネルから、ON/OFF、行数、透過度の設定を行うことができます。

使用技術

  • TypeScript
  • webpack
    • 複数の TS ファイルから JS ファイルを生成したり、拡張機能として必要なファイル群を build ディレクトリ以下にまとめるために使用しました。
  • Bootstrap
    • 拡張機能のオンオフスイッチや、レンジスライダーなどの UI に使用しました。

chrome 拡張機能について

以下が公式のドキュメントです。Chrome 拡張機能で使用可能な API や、サンプルなどを確認することができます。本拡張機能の作成においては、コンテンツスクリプトとストレージなどについてのドキュメントを参考にしました。

本拡張機能は chrome.storage API を使用して、設定内容をローカルに保存しています。次の Chrome 拡張機能を使用することで、保存したデータの確認や削除を容易に行うことができます。(コンソールで直接 chrome.storage.local.get(null, ((data) => {console.log(data)})); を実行することでも、データの確認ができます。)

ディレクトリ構成

  • /src/content/ 以下の TS ファイルはビルド時に content.js にまとめられます。content.js はコンテンツスクリプトとして対象のサイトで実行され、DOM 操作などを行います。
  • /src/popup/ 以下の TS ファイルはビルド時に popup.js にまとめられます。popup.js は拡張機能のアイコンをクリックしたときに表示されるポップアップで実行されます。
  • /src/popup.html はポップアップの HTML ファイルです。この HTML 内で popup.js を読み込んでいます。
  • /webpack.config.js は webpack の設定ファイルです。
  • /manifest.json は Chrome 拡張機能の設定ファイルです。
.
├── src
│   ├── content
│   │   ├── index.ts
│   │   ├── scroller.ts
│   │   ├── settings.ts
│   │   ├── site.ts
│   │   └── types.ts
│   ├── popup
│   │   ├── index.ts
│   │   └── messageSender.ts
│   └── popup.html
├── webpack.config.js
├── manifest.json
...

manifest.json の設定

以下が本拡張機能の manifest.json の設定です。

  • permissions: 拡張機能が使用する権限を指定します。本拡張機能では storage を使用しています。
  • action: 拡張機能のアイコンをクリックしたときに表示されるポップアップの HTML ファイルを指定します。
  • content_scripts: コンテンツスクリプトを指定します。本拡張機能では、OPENREC.tv の配信ページとキャプチャーページに対してスクリプトを実行しています。
{
  "name": "OPENREC.tv Chat on Screen",
  "description": "OPENREC.tvのチャットを、スクリーン上に表示します。",
  "version": "X.Y.Z",
  "manifest_version": 3,
  "icons": {
    "16": "icons/icon-16.png",
    "32": "icons/icon-32.png",
    "48": "icons/icon-48.png",
    "128": "icons/icon-128.png"
  },
  "permissions": ["storage"],
  "action": {
    "default_popup": "popup.html"
  },
  "content_scripts": [
    {
      "matches": [
        "https://www.openrec.tv/live/*",
        "https://www.openrec.tv/capture/*"
      ],
      "js": ["content.js"]
    }
  ]
}

src/popup.html

  • 次のような UI で、ON/OFF、行数、透過度の設定を行うことができます。
popup.html

src/content/

  • index.ts
    • コンテンツスクリプトのエントリーポイントです。Scroller インスタンスの生成や、popup スクリプトからのメッセージを受け取ったときの処理を行います。chrome.storage.local.get でローカルストレージに保存された設定値を Scroller インスタンスのフィールドとして設定します。また、下記のように chrome.runtime.onMessage.addListener でメッセージを受け取ったときに、それに応じて Scroller インスタンスのフィールドを変更します。
...
  chrome.runtime.onMessage.addListener((request) => {
    if (request.message === "switchOnOff") {
      scroller.setIsRunning(request.isRunning);
    } else if (request.message === "changeNumOfLines") {
      scroller.setMaxLines(request.numOfLines);
      scroller.modify(true);
    } else if (request.message === "changeOpacity") {
      scroller.setOpacity(request.opacity * 0.01);
      scroller.setDisplayedOpacity();
    }
  });
...

  • scroller.ts
    • Scroller クラスを定義しています。Scroller クラスは、チャットを画面上に流す処理を行います。
メソッド名 説明
init サイトのデータを取得して、フィールドを初期化します。また、MutationObserver を用いて、チャットの変更の監視を開始します。その他、スタイルの追加やスクロール処理を行うメソッドを呼び出します。
addStyle スタイルを追加します。
modify フォントサイズなどのチャットのスタイルを変更します。
attachComment 新規に追加されたコメントを lines フィールドに追加します。
scrollComments lines フィールドに格納されたコメントを画面上に流します。
  • settings.ts
    • 文字色、縁取りの色や幅、1 行あたりの高さ、コメントの表示時間、FPS が定数として定義されています。
  • site.ts
    • サイトの情報を取得するためのオブジェクトが定義されています。
  • types.ts
    • コンテンツスクリプトで使用するための型定義を行っています。

src/popup/

ポップアップ側で実行されるスクリプトです。 大まかな処理の流れは次の通りです。

  1. 初期状態のセット
  • chrome.storage.local.get でローカルストレージに保存された設定値を取得します。
  • 取得した設定値を UI に反映します。
  1. イベントリスナーの追加
  • 次のイベントが発生したときに、ローカルストレージに設定値を保存し、コンテンツスクリプトにメッセージを送信します。
    • ON/OFF スイッチのクリックイベント
    • 行数の変更イベント
    • 透過度の変更イベント

webpack の設定

yarn build (yarn webpack --mode=production) コマンドで、webpack.config.js に従って build ディレクトリにビルドされます。 これにより、Chrome 拡張機能として読み込むことができる形式で build ディレクトリにファイルが生成されます。

  • バンドル
    • src/content および src/popup 以下の TS ファイル群がそれぞれ、content.jspopup.js にまとめられます。
  • ファイルのコピー
    • manifest.json, src/popup.html, icons, bootstrap のファイル・ディレクトリを build ディレクトリにコピーします。
const CopyWebpackPlugin = require("copy-webpack-plugin");
const glob = require("glob");
const path = require("path");

const contentEntries = glob
  .sync("./src/content/*.ts")
  .map((entry) => "./" + entry);
const popupEntries = glob.sync("./src/popup/*.ts").map((entry) => "./" + entry);

module.exports = {
  mode: "production",
  entry: {
    content: contentEntries,
    popup: popupEntries,
  },
  output: {
    path: path.resolve(__dirname, "build"),
    filename: "[name].js",
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: "ts-loader",
      },
    ],
  },
  resolve: {
    extensions: [".ts", ".js"],
  },
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        { from: "./manifest.json", to: "manifest.json" },
        { from: "./src/popup.html", to: "popup.html" },
        { from: "./icons", to: "icons" },
        { from: "./bootstrap", to: "bootstrap" },
      ],
    }),
  ],
};

工夫点

  • 可読性の向上
    • 適当な粒度でファイルを分割し、それぞれのファイルに責務を持たせることで、可読性を向上させました。
    • 一連の処理を関数に分けたり、クラスを使用することで、エントリーポイントからの処理の流れがわかりやすくなるよう努めました。
  • asdf による node のバージョン管理
  • eslint によるコードの品質向上

まとめ

本拡張機能の開発を通して、以下のようなことを学びました。

  • Chrome 拡張機能の開発手順
    • コンテンツスクリプトによる DOM 操作
    • storage API
    • ポップアップのスクリプトとコンテンツスクリプト間のメッセージ通信
  • webpack の使い方

もともとは自分で使用するために作成した拡張機能でしたが、実際に公開してみたら思ったよりも多くの方に利用していただけるようになりました。

今後も、ユーザーの方々からのフィードバックをもとに、保守や機能の改善を行っていきたいと思っています。

© 2024 seelx3