ニコニコ動画のコメントを正規表現でNGするユーザースクリプト

ニコ動は楽しいコメントが流れてくるのが良いところだが、 不快なコメントも流れてくるのが悪いところである。 楽しもうとして不快になるのは本末転倒だ。 これを回避するためにNG機能というものがあって、 登録したワードやアカウントに該当するコメントを非表示にすることができる。

しかしこのNG機能、クライアントサイドで処理するくせに、 何故か登録数の上限が40件しかないので使っているとすぐ上限に達してしまう。 それから、登録したワードが含まれているコメントは全て非表示になるので、 例えば「←○○○○○」みたいな形のコメント(コメントでバトルする人がよく使う)をNGしようとすると左端以外の場所に「←」がある無実のコメントも消えてしまう。

そこでコメントを正規表現でNGするスクリプトを書いた。 正規表現なので|で区切ればいくらでも追加できるし、 さっきの例はパターン^←.+を使えば冤罪がすこし減る。

雑に作ったので使い勝手に少々難があるのだが、 スクリプトの解説も書いておくので適当に改良して使ってもらえると嬉しい。

説明

ニコニコ動画のコメントを正規表現でNGするスクリプト · GitHub

↑コードをGistに載せた。Tampermonkeyとかに登録すれば使える。 Google Chromeでしか動作確認をしていないが、変な機能は使ってないのでFirefoxでも動くと思う。

動画再生ページでEscキーを押すとNGパターン設定のプロンプトが開く。 この設定はLocal Storageに保存されるので永続する。

NGパターン設定

なお、このスクリプトが非表示にするのは動画に流れるコメントだけで、 動画の右側に出るコメントリストは非表示にしない。

そういえば、スクリプトではなく素のNGに引っ掛かって「字幕が視聴者の総意」というコメントが消えてしまっているが、これは楽しいコメントであって冤罪だな。まあ正規表現であっても文脈を読むことはできない……

解説

ニコ動のコメントは次の手順で描画される。

  1. document.createElementCanvasを生成する。
  2. 生成されたCanvasにコメントをひとつ描画する。
  3. drawImageでコメント表示用のCanvas (.CommentRenderer > canvas) にコピーする。

これを踏まえて以下のようなスクリプトを実行することで正規表現によるNGを実現した。

まずdocument.createElementを書きかえて、 Canvasが生成された場合にfillTextを監視するようにする。 改造されたfillTextCanvasと文字列の対応をWeakMapに保存する。

// createElementで生成されたcanvasのfillTextを監視
const canvas_to_text = new WeakMap();
const createElement = document.createElement;
document.createElement = function(tagName, ...args) {
    const elem = createElement.call(this, tagName, ...args);
    if (tagName === 'canvas') {
        const ctx = elem.getContext('2d');
        const fillText = ctx.fillText;
        ctx.fillText = function(text, ...args) {
            canvas_to_text.set(elem, text);
            return fillText.call(this, text, ...args);
        };
    }
    return elem;
};

つぎにコメント表示用のCanvasdrawImageを書きかえる。 改造されたdrawImageは、第一引数がCanvasであるとき、 対応する文字列をWeakMapから取り出す。 これがNGのパターンとマッチする場合に書き込みをキャンセルすることで、 正規表現によるNG機能が実現する。

// コメント表示用canvasのdrawImageを監視
let image_to_is_safe = new WeakMap();
const commentCanvas = document.querySelector('.CommentRenderer > canvas');
const commentCtx = commentCanvas.getContext('2d');
const drawImage = commentCtx.drawImage;
commentCtx.drawImage = function(image, ...args) {
    let is_safe = image_to_is_safe.get(image);
    if (is_safe === undefined) {
        const text = canvas_to_text.get(image);
        if (text === undefined) {
            is_safe = true;
        } else {
            const m = text.match(regexp);
            if (m) {
                console.log('NG', m[0]);
                is_safe = false;
            } else {
                is_safe = true;
            }
        }
        image_to_is_safe.set(image, is_safe);
    }
    if (is_safe) {
        drawImage.call(this, image, ...args);
    }
};

確認済みのCanvasimage_to_is_safeというWeakMapに格納することで同じ文字列に何度も同じ正規表現のマッチが行われることを避けている (パフォーマンスにどれくらい影響するのかよくわからないが)。 NGパターンが変更された際にはimage_to_is_safeがリセットされるようになっていて、新しいNGパターンが即座に反映される。

ところで

実はニコ動のLocal Storageには公式NG機能の設定がJSONで保存されている。

Local Storageに保存された公式NG機能の設定

これを適当に編集してページをリロードすると……驚くべきことに上限を突破してNG設定を登録できてしまう。しかもちゃんと動く。

上限突破

わざわざCanvasをいじるコードを書くよりLocal Storageの書きかえを支援するコードを書いた方が良かったかもしれない。 正直なところ正規表現が使えてもそんなに嬉しくないからなぁ。

一応デメリットもある。

  • 設定の反映にリロードが要るので、NG設定の追加が即座に反映されない。
  • NG設定が上限を超えているとき、NGの追加ボタンや削除ボタンが機能しなくなる。
  • 上限を超えたNG設定はサーバーに反映されない。