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、行数、透過度の設定を行うことができます。

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/
ポップアップ側で実行されるスクリプトです。 大まかな処理の流れは次の通りです。
- 初期状態のセット
chrome.storage.local.get
でローカルストレージに保存された設定値を取得します。- 取得した設定値を UI に反映します。
- イベントリスナーの追加
- 次のイベントが発生したときに、ローカルストレージに設定値を保存し、コンテンツスクリプトにメッセージを送信します。
- ON/OFF スイッチのクリックイベント
- 行数の変更イベント
- 透過度の変更イベント
webpack の設定
yarn build
(yarn webpack --mode=production
) コマンドで、webpack.config.js
に従って build
ディレクトリにビルドされます。
これにより、Chrome 拡張機能として読み込むことができる形式で build
ディレクトリにファイルが生成されます。
- バンドル
src/content
およびsrc/popup
以下の TS ファイル群がそれぞれ、content.js
とpopup.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 の使い方
もともとは自分で使用するために作成した拡張機能でしたが、実際に公開してみたら思ったよりも多くの方に利用していただけるようになりました。
今後も、ユーザーの方々からのフィードバックをもとに、保守や機能の改善を行っていきたいと思っています。