Chrome Extension で自己満に浸る

# はじめに
今日の東京の最高気温は昨日よりも 4 度ほど高かったそうですが、それでも寒いですね。
例年思いますが、1 月には一体どうなってしまうのか。
すでに私も風邪を引きそうなのですが、なんとなんと、これは執務室になぜか冷房が入っていたからなんです。
入社 1 年目の研修で、いまの職場には私含め数人の新人が 1 週間ほど前から出勤しているのですが、今日の今日まで誰も気づかず！
私は特に寒がりなのでマフラーをしながら作業をしていたのですが、明日からはきっと屋内らしい見た目になりそうです。

さて突然ですが、みなさん TRPG はお好きですか。
私は大好きです。

「毎週セッションがあれば良いのに」

そう思うくらいには好きです。
しかし実際には、多くても月一程度の頻度でしかセッションができていません。
TRPG は複数人で集まってわいわいと遊ぶゲームです。
全員の都合がつくことはなかなかないのです。

現実に誰かの家に集まるなどして TRPG をすることをオフラインセッション（オフセ）と呼びますが、遠方の友達が居たりなどすれば物理的に集合するのは大変です。
そのため、チャットツールなどを用いてオンラインセッション（オンセ）をする人たちが私含め多くいらっしゃると思います。

本記事は、TRPG のオンセをするにあたって普段から少し不便に思っていたことがあったので、それを **初めて実装する Chrome Extension で解決しようとした**という話になります。
具体的には、最も有名な TRPG であると私の信ずる**クトゥルフ神話 TRPG で使う技能判定コマンドを、キャラクター情報を保管するサイトから生成する**というものです。
タイトルの通りですので、ご了承ください。

# TRPG とは
本題に移る前に、まずは TRPG をご存知のない方にも分かっていただけるよう軽く説明をします。
TRPG とは、非電源ゲームの一つであり、Tabletalk(Tabletop) Roll-Playing Game の略です。
頭に T の付かない RPG は皆さんご存知でしょう。
やはり何某クエストや何処ぞのファンタジーなどを想像する方が多いでしょうか。

これらのゲームでは、プレイヤーは、キャラクターを操作して様々なアクションを起こさせます。
NPC に話しかけさせ有益な情報を得たり、敵を倒し経験値を得させたり、などです。
そして最終的には、ラスボスと呼ばれる強大な存在に挑ませることになるのです。

基本的には TRPG も同様の形式を取ります。
TRPG と RPG の大きな違いのひとつは、シナリオの進行におけるその自由度でしょう。
RPG では、ゲームクリエイターたちが実装した仕様通りにしかキャラクターはアクションを起こせません。
勇者にベッドの下を覗かせようと思っても、そこにトリガーが用意されていなければどんなに A ボタンを連打しても何のイベントやアクションも起きないのです。
しかし TRPG では違います。
たとえ想定されていなかったことでも、シナリオを管理するキーパー（KP）がそれを許せば、プレイヤー（PL）は自身の操るキャラクター（PC）にベッドの下を覗かせることができます。
KP はその場の思い付きや判断で、何かしらの報酬を PC に提供することができるのです。
多くの場合、こういった際に報酬が得られるかどうかはダイスの出目によって決まります。

# 既存のオンラインセッション支援ツール
## キャラクターシート保管庫
TRPG のセッションでは、各 PL が自分だけの PC を作成することが多いです。
そのオリジナルキャラクターをオンライン上で管理できるサービスがあり、私もよく使わせていただいております。
今回のターゲットとした[キャラクター保管所](https://charasheet.vampire-blood.net/)は、クトゥルフ神話 TRPG をはじめとした多くのタイトルに対応した PC を保管できます。

以下は、保管されるクトゥルフ用 PC の[サンプル](https://charasheet.vampire-blood.net/md43a56b34c5722c17de452c47873f8a5)の画面を切り出したものです。
![chara.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105953/8dadfa0f-85e8-e557-21d6-7521cd775759.png)
様々な技能に対して、その技能値が算出されています。

## TRPG オンラインセッションソフト
先ほど軽く紹介しましたが、TRPG では PC のアクションが成功するかどうかをダイスの出目で判断することがよくあります。
たとえばクトゥルフ神話 TRPG には、何かめぼしい物を発見できるかもしれない技能「目星」が存在します。
先ほどの画面を見ると、＜探索技能＞の中に「目星」が存在していますね。そしてその技能値は 25 です。
これは、100 面ダイスを振った際にその出目が 25 以下であれば「目星」が成功することを意味しています。
オフセでは、参加している KP や PL 同士が直にダイスを振って出目を共有すれば良いのですが、オンセでは専用のダイスボットが実装されているチャットツールを使います。

国内デファクトスタンダードの TRPG オンラインセッションソフトである[どどんとふ](https://dodontof.onlinesession.app/)の画面を切り出したものが以下になります。
![dodontof.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105953/e125ec44-758b-0ac4-05f1-d67f4533b316.png)
「CCB<=25 目星」とチャットで発言することでクトゥルフ用のダイスボットが 100 面ダイス（1D100）を振ってくれています。
出目は 11 で 25 以下なので「目星」が成功しました！
わーい！

# 本題 - Chrome Extension で何がしたいのか
前置きが非常に長くなってしまいました。
結局やりたいことは、[どどんとふ](https://dodontof.onlinesession.app/)のチャット欄に入力する技能判定コマンドを[キャラクター保管所](https://charasheet.vampire-blood.net/)から自動生成したいという話です。

# Chrome Extension とは
Chrome Extension とは、その名の通り Google Chrome を拡張する機能で、Web ページの利用者が自分の都合の良いようにビューや機能を改善できます。
この記事を読んでくださっている方も、もし Google Chrome を使っているならブラウザの右上にいくつかの可愛らしいアイコンが顔を覗かせていることでしょう。

適当なディレクトリに manifest.json という設定ファイルと適宜必要な js や css などを入れればすぐに作れます。
ちなみに身も蓋もありませんが、初心者で Chrome Extension を作成したいと思った方は、この記事をすぐさま閉じ[公式チュートリアル](https://developer.chrome.com/extensions/getstarted)を読みに行ってサンプルを動かしてみることをオススメします。

# 今回の実装で扱ったコンポーネント

## Background Scripts
ページ遷移やタブの移動などをトリガに、何か動的に反応したい場合に用意するスクリプトです。

```manifest.json
{
// （省略）
    "background": {
        "scripts": [
            "background.js"
        ],
        "persistent": false
    },
// （省略）
}
```

```background.js
chrome.runtime.onInstalled.addListener(function () {
  chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
    chrome.declarativeContent.onPageChanged.addRules([{
      conditions: [new chrome.declarativeContent.PageStateMatcher({
        pageUrl: { hostEquals: 'charasheet.vampire-blood.net' },
      })
      ],
      actions: [new chrome.declarativeContent.ShowPageAction()]
    }]);
  });
});
```

### Declarative Content
URL を元に Page Action を制御する際に使います。
ページの切り替わりやタブの遷移を検出できるので、その際にまず無条件に Page Action を無効化し、その後閲覧している URL が該当するホスト名を持っていれば再び有効化しています。

### Page Action
ブラウザの右上に出るアイコンをクリックした際の表示を制御します。

以下のように、クリック時に popup.html が表示されるように設定しました。
アイコン画像も設定できます。

```manifest.json
{
// （省略）
    "page_action": {
        "default_popup": "popup.html",
        "default_icon": {
            "16": "images/get_started16.png",
            "32": "images/get_started32.png",
            "48": "images/get_started48.png",
            "128": "images/get_started128.png"
        }
    },
// （省略）
}
```

クリック時に、技能コマンドをクリップボードにコピーしてくれるボタンを表示させます。

```popup.html
<!DOCTYPE html>
<html>

<head>
    <style>
        button {
            outline: auto;
        }
    </style>
</head>

<body>
    <button id="copyCommand">copy all commands</button>
    <script src="popup.js"></script>
</body>

</html>
```

ちなみに、実際に技能判定コマンドをコピーする仕方としては、Context Menu を使う方法もあるでしょう。
右クリック時のメニューに項目を追加する方法です。
使い分けとしては、html でないと表現できないようなものであれば Page Action で制御せざるを得ないといったところでしょうか。

以下は、popup.html で表示したボタンをクリックしたときの処理を記述した js です。

```popup.js
let copyCommandBtn = document.getElementById('copyCommand');

copyCommandBtn.onclick = function () {
  chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    chrome.tabs.executeScript(
      tabs[0].id,
      { code: 'let prefix = "CCB";' },
      () => {
        chrome.tabs.executeScript(
          tabs[0].id,
          { file: "makeCommand.js" }
        )
      }
    )
  })
}
```

makeCommand.js は言わずもがな、[キャラクター保管所](https://charasheet.vampire-blood.net/)にある PC の技能値をクリップボードにコピーする js です。
popup.html は関係ないので、現在タブを探し、これに対して executeScript でコードや js を注入しています。
これは少し面倒なので、Context Menu を使ったほうが良いように思えてきました。

ちなみにこの executeScript ですが、複数タブに対して js を注入するようなケースで使えると思います。
今回のお題に沿ってたとえると、[どどんとふ](https://dodontof.onlinesession.app/)と[キャラクター保管所](https://charasheet.vampire-blood.net/)の 2 つのタブを開いてオンセをしていたとして、どどんとふ側で PC のステータス変化を検知したらそれを保管所側の PC ステータスに反映するといったものはどうでしょう。

全く検証しないで書いているので、深夜テンションだということもあり、言うことがめちゃくちゃになってきた気がします。
クロスサイトスクリプティングとか怖いですね！

最後に makeCommand.js を貼りますが、こういう頑張るしかないコードはデベロッパーツールで作りましょう！

```makeCommand.js
function copyArtsCommandToClipboard(prefix) {
    // copy 用に textareaを作る
    let textArea = document.createElement("textarea");
    textArea.style.cssText = "position:absolute;left:-100%";

    document.body.appendChild(textArea);

    // 技能名、技能値の取得
    const kinds = [
        "Table_battle_arts"
        , "Table_find_arts"
        , "Table_act_arts"
        , "Table_commu_arts"
        , "Table_know_arts"
    ]
    var text = "";
    for (var i = 0; i < kinds.length; i++) {
        text += "// " + kinds[i].slice(6) + "\n";
        var arts_table = document.getElementById(kinds[i])
        var table_rows = arts_table.rows.length;
        for (j = 1; j < table_rows; j++) {
            var name = arts_table.rows[j].cells[1].firstChild.data;
            if (name == "undefined" || name == undefined) {
                name = arts_table.rows[j].cells[1].firstChild.value;
            }else if(arts_table.rows[j].cells[1].lastChild.data == ")"){
                name = arts_table.rows[j].cells[1].firstChild.data + arts_table.rows[j].cells[1].firstElementChild.value + ")";
            }
            var num = arts_table.rows[j].cells[7].firstChild.value;
            text += prefix + "<=" + num + " " + name + "\n";
        }
    }

    textArea.value = text;

    textArea.select();
    document.execCommand("copy");

    document.body.removeChild(textArea);
}
copyArtsCommandToClipboard(prefix);
```


# おわりに

Chrome Extension を初めて作ったことに加え、あまり時間をかけていないのでおざなりなものができましたが、普段面倒に思っていた技能判定コマンドの手書きがこれで自動化できるので満足しています。
しかし、現状だと[キャラクター保管所](https://charasheet.vampire-blood.net/)にあるクトゥルフ用 PC 以外のページでも機能が有効化されてしまうのでどうにかしたいです。
（Web ページが参照している html 固有の ID みたいなものを探せば良さそう？）
アドバイスなどありましたらお願いいたします。

ちなみに作成したものは [GitHub](https://github.com/tofuchic/cocChatPallete) にあげておきました。

本記事を最後まで読む稀有な方がいらっしゃれば嬉しい限りです。
また、一人でも多くの方が TRPG に興味を抱いていただけたなら幸いです。