AtCoder の茶色と緑色が見にくいので Chrome の拡張機能を作った

# 紹介
ここで配布しています。インストール方法なども書いてあります。

https://github.com/gj5752/AtCoder-Universal-Color

AtCoder のユーザ名やレーティングの茶色・緑色、ついでに水色も変更されます。AtCoder Problems も同様の変更が起こります。

こんな感じです

↓↓ これが

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/aea7d3fc-b42f-4485-ae01-a7ce9fe217d7.png)

↓↓ こうなる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/8b041702-9ae9-4cd1-867a-ab57fb86e43c.png)

見やすい、かな？

以下からはだらだらした説明と、実装方法が書かれているので読んでくれる人は読んでね！

# はじめに

改めましてどうも、わたしです。AtCoder をせこせこ頑張っているわたしですが、つい先日緑Coder になることができましたありがとうございます。ところで、わたしは色覚異常なので、色が見づらいことがあるんですよね。たとえばこれ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/6163223e-bb50-4572-947d-e73f8156c97f.png)

AtCoder のレーティングなんですけど、上が茶色で下が緑色です。わかります？まあ、普通はわかるのかもしれない。でも、ぱっと見せられたらわたしには区別ができないし、どっちが何色なのかもわからないんです。ただ、レートは 400 刻みなので数字の認識から違う色だとわからなくもないが、それは置いといて

もいっこおまけに AtCoder Problems も

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/e54c07f3-1136-47b1-86f5-8e12908c4bae.png)

これだけ近づけば、茶色と緑かなってわかりますけど、あの表でぱっと見せられるとすぐには認識できないんですよ。なので仕方なく、このリンクを「検証」して、要素の style 属性の中身をみて、「あ、brown なんだ、green なんだ」とかやって diff を判断してたんです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/068603d8-fff6-4b10-b0a2-5f306cf215c9.png)
↑ HTML の中身を見ると色の答えが書いてある

ってことで、これをなんとか解決したいと思ってたんです。Chrome の拡張機能でいけそうだなとは思っていて、ストアも調べたんですがなくて、Twitter とか調べれば同じ症状の人いそうですが、まあ自分で作ってしまおうと思いました。

# スタイルを変える

さて、今回は「Web ページの書き換え」を行って色の変更を試みるという感じになりますね。

まずは AtCoder の方を調べてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/41f762b8-7f8a-4a7a-927d-5f5ef854d55c.png)

自分の成績表に書いてあるここから、この色がどう決まっているのかを開発者ツールでのぞき見します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/13700cf7-afcb-44a6-8cd7-da6b14890e65.png)

どうやら、`user-green` というクラスで決められているようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/3620d872-7ec3-4389-9b5b-4d557903b4a9.png)

この `user-green` は `base.css` によって、スタイルの設定がされていて、`color` が　`#008000` になっていますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/62323897-3590-4534-8e2e-374a725a9568.png)

`base.css` はこのようになっていて、色の指定が丸裸です。各色ごとにクラスがあって、color がスタイルとして指定されているということは、これらのスタイルを書き換えてしまえばいいです。CSS のスタイルなら上書きができるので行けそうですね！

次のファイルを作成してみましょう。

```style.css
.user-brown {
	color: #8a3b2c !important;  /* #804000 から変更 */
}

.user-green {
	color: #78c496 !important;  /* #008000 から変更 */
}

.user-cyan {
	color: #68c8f2 !important;  /* #00C0C0 から変更 */
}
```

`!important` はスタイルの上書きを強制的にできる合言葉です。「重要！！」ってことなので無視しないでね、みたいな感じ？
色はそれぞれユニバーサルデザインとされているものを採用しました。今回は緑と茶色ですが、水色もなんかあれだったので一緒に変えます。

茶 :  `#804000` -> `#8a3b2c`
緑 :  `#008000` -> `#78c496`  
水 :  `#00C0C0` -> `#68c8f2`

こんな感じ。緑色はだいぶ明るくなっています。コントラストが変わるとみやすさがだいぶ違うんですよね。

つぎはこれを適用させてみましょう。ここから、拡張機能を作っていきます。

# 拡張機能をつくる

Chrome の拡張機能を作るには `manifest.json` という JSON のファイルが必須です。さっきのスタイル `style.css` と一緒に同じディレクトリにおきましょう。


> atcoder-universal-color/
└ src/
　├ style.css
　└ manifest.json

この `manifest.json` に必要な情報を書いていきます。

```manifest.json
{
	"name": "AtCoder Universal Color",
	"description": "もう緑と茶色で迷わない！",
	"version": "1.0",
	"manifest_version": 3,
	"content_scripts": [
		{
			"matches": ["https://atcoder.jp/*"],
			"css": ["style.css"]
		}
	]
}

```

`name` はこの拡張機能の名前です。今回は AtCoder Universal Color としました。
`description` は説明を書きます。拡張機能の管理画面で出てくるちょっとした説明書きです。
`version` はこの拡張機能本体のバージョンです。公開するなら設定とかしとくといい？
`manifest_version` これはマニフェストの形式のバージョンで現時点では 3 が最新なので 3 を書いておきます。
`content_scripts` ここが本体です。ここに、どのサイトで実行して、何を適用するかを指定します。今回は AtCoder のサイトで、さっきのスタイルを適用したいので `matches` に AtCoder の URL を、スタイルなので `css` に `style.css` を指定してあげます。

これで拡張機能は完成です。なんとあっさり！これを早速導入してみよう！

# 自作拡張機能の導入

まず、Chrome の拡張機能管理に行きます。アドレスバーに `chrome://extensions` としてもいいし、拡張機能のアイコンを押して、管理ページ飛んでもいいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/b2e1cdf9-615b-4355-9da3-e5fbea11f31e.png)

こんな画面が出てくると思います（真っ白でモザイクをかけてる）。ここの右上にある「デベロッパーモード」を ON にしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/f50d1c52-cb56-4ce4-aedb-52ca6e8772d5.png)

すると、なんかタブが出てきます。この「パッケージ化されていない拡張機能を読み込む」を押すと、フォルダを指定する画面が出てきます。ここではさっき作った `manifest.json` や `style.css` があるディレクトリ（今回だと `src` ディレクトリ）を指定してあげます。これで導入は OK です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/f73f38b5-90c4-4849-a23b-f6afe149729f.png)

しっかり拡張機能として認識できているようです。ここで何か失敗があるなら、`manifest.json` などが間違えてしまっていると思うので修正しましょう！

さて、もういけます。AtCoder のサイトにアクセスすると ...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/387a6b8b-05cc-4c2d-89c1-bec4e41f29af.png)

ちゃんと変わりました！すごい！見やすくなった！！
でも、上向きの矢印は変わってないですね。これ、画像ファイルっぽいので、スタイルの変更じゃできないところでした。まあ、パフォはすごく見やすくなったので OK かな？
（一部表の順序を変更しています）

# AtCoder Problems の番

もう行けるでしょう、ってことで Problems の方もやっちゃいましょう。
さっきと同じで、どうやって色が決まっているのかを開発者ツールからひっそりのぞきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/b2110325-3d57-446e-9861-e23651f03696.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/d6e1f0b4-8d52-4a56-8fd5-f03ffbcea76b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/67adb1f3-c07b-4268-a529-65752aa4e090.png)

お、どうやらこれもクラスでスタイルが決められているようですね！さっきと一緒でスタイルシートを上書きしちゃえばよさそうです。`style.css` に追記しましょう！

```style.css
.user-brown {
	color: #8a3b2c !important;  /* #804000 から変更 */
}

.user-green {
	color: #78c496 !important;  /* #008000 から変更 */
}

.user-cyan {
	color: #68c8f2 !important;  /* #00C0C0 から変更 */
}

.difficulty-brown {
	color: #8a3b2c !important;
}

.difficulty-green {
	color: #78c496 !important;
}

.difficulty-cyan {
	color: #68c8f2 !important;
}
```

これで上書き保存して OK です！マニフェストも忘れずに書き換えましょう。

```manifest.json
{
	"name": "AtCoder Universal Color",
	"description": "もう緑と茶色で迷わない！",
	"version": "1.0",
	"manifest_version": 3,
	"content_scripts": [
		{
			"matches": ["https://atcoder.jp/*"],
			"css": ["style.css"]
		},
        {
			"matches": ["https://kenkoooo.com/atcoder"],
			"css": ["style.css"]
		}
	]
}

```

適用する Web ページを増やすには `content_scripts` に追記していきます。AtCoder Problems の URL を入れて OK です。この変更を Chrome に反映させます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/8c98e87a-c154-42f3-8352-a05a476b941b.png)

拡張機能の管理画面でここを押せば反映できます。さあ AtCoder Problems のサイトにアクセスしてみると ...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/7bae10b2-9e16-4fb1-ac03-994d2b1e448e.png)

おーーー変わってい、ますけど、あれ？問題名の色だけが変わって、難易度の丸が変わってないですね ...

そうなんです、実は `difficulty-color` というクラス、問題名にしか使われていないんです。この丸はどうやら別で色が決まっているみたい？さっきの HTML をもっかい見てみましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/d6e1f0b4-8d52-4a56-8fd5-f03ffbcea76b.png)

どうやら丸を作っているのは `span` 要素で、これのスタイルによって直書きされていますね。でもクラスとして `difficulty-circle` があるので、ここのスタイルとしてできそう？いや、さっきは `difficulty-brown` ってクラスが色ごとに分かれていたからできたけど、これは一緒になっちゃってるし、色ごとにクラスが分かれていそうにもないのでスタイルの上書きはできなさそうです。困った！

ここの色指定、JS ファイルとかをたどっていくと正体とかが分かるんですが、既存の JS ファイルの書き換えはできない（はずな）のでもう最終手段しかありません。

そうです、置換です。

# JS で置換

もう HTML 文書そのものを書き換えてしまいましょう。それをするには JavaScript を使います。あら便利、そんなこともできるのね

:::note
このセクションでの説明は古いものです。追記に最新の置換方法が書いてあります
:::

具体的な説明は省いて、次のようなスクリプトを用意します。

```script.js
var circles = document.getElementsByClassName("table-problem");

let old_green = "rgb(0, 128, 0)";
let old_brown = "rgb(128, 64, 0)";
let old_cyan = "rgb(0, 192, 192)";

let new_green = "rgb(120, 196, 150)";
let new_brown = "rgb(138, 59, 44)";
let new_cyan = "rgb(104, 200, 242)";

for (let i = 0; i < circles.length; i++) {
    var circ = circles[i].innerHTML;
    circ = circ.replaceAll(old_green, new_green);
	circ = circ.replaceAll(old_brown, new_brown);
	circ = circ.replaceAll(old_cyan, new_cyan);
	document.getElementsByClassName("table-problem")[i].innerHTML = circ;
}
```

まず、`document` はこの Web ページ全体です。これに対してメソッド `getElementsByClassName(class_name)` を起こすと、`class_name` に指定したクラスを持つ要素の中身がリストみたいな感じで入手できます。今回は `table-problem` というクラス名を指定していますが、これはさっきの難易度の丸の中身を見たときに `td` 要素につけられていたクラスです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/1edc3c20-4163-43dd-889e-3b39a4a14e55.png)

これで、<span から始まるこの `td` 要素の中身をすべて取得できます。そして、`circles` にはクラス `table-problem` をもつ全 `td` 要素の中身が入っています。ということはこの `circles` の各メンバーに対して、書き換えを行っていけばいいですね！

`script.js` の続く `let` は色指定です。HTML 文書上だと RGB 表記になっていたので、RGB 表記で書いています。置換をするので元の方は合わせなければなりません。

次の for 文で置換の処理を行っています。

```
for (let i = 0; i < circles.length; i++) {
    var circ = circles[i].innerHTML;
    circ = circ.replaceAll(old_green, new_green);
	circ = circ.replaceAll(old_brown, new_brown);
	circ = circ.replaceAll(old_cyan, new_cyan);
	document.getElementsByClassName("table-problem")[i].innerHTML = circ;
}
```

まず、`circles` のメンバーの数だけ for を回します。で、そのメンバーを取り出すんですけど、`innerHTML` を後ろに付けます。これをすることで、<span> とかのタグなどを残したまま HTML 文書の文字列として取得することができます。

JS の文字列は `replaceAll` で置換ができます。置換前、置換後の順で書きます。3 色分置換するので 3 行書きましょう。ちなみに `replaceAll` は書き換えたものを新しく作るので、置換は元の変数に再代入する必要があります。

最後に、変更を反映します。さっきと同様にクラス `table-problem` を取り出し、変更後の文字列を代入してあげます。これで OK です。もう Web ページの書き換えができちゃいます！！

じゃ、早速拡張機能に入れる、前に、本当にできるか試してみましょう。開発者ツールのコンソールにさっきのスクリプトを貼り付けて実行してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/df2df637-18ed-4cac-a740-f41f121609df.png)

するとこの通り！！！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/ec186f98-94e3-452d-b6f6-3c1313cc3c45.png)

ちゃんと難易度の丸も色が変わっていますね！！やったー

よーしこれで晴れて拡張機能にも入れられます。JS の指定はマニフェストに次のように書けば OK です！

```manifest.json
{
	"name": "AtCoder Universal Color",
	"description": "もう緑と茶色で迷わない！",
	"version": "1.0",
	"manifest_version": 3,
	"content_scripts": [
		{
			"matches": ["https://atcoder.jp/*"],
			"css": ["style.css"]
		},
        {
			"matches": ["https://kenkoooo.com/atcoder"],
			"css": ["style.css"],
			"js": ["script.js"]
		}
	]
}

```

最後の行に `js` という部分が追加されています。これで OK です。よーし拡張機能の修正を反映していくぞー、と？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/7a55f232-12bc-44fc-b3b2-dfe8e6330117.png)

あれれー丸が変わってなーーい、なんで？コンソールからやったら OK だったのに、うーんなんでー

って感じで、失敗してしまいます。

# スクリプトの実行を遅らせる

どうして失敗してしまうんだ！！これはたぶん、この難易度の丸が生成される前に拡張機能のスクリプト実行されてしまうのが原因だと思います。難易度の丸、どう考えても内部スクリプトで作られてるはずなので、実行のタイミングが大事なんですね。

結論からいうと、スクリプトを次のように変更します。

```script.js
window.addEventListener("load", main, false);

function main(e) {
	const jsInitCheckTimer = setInterval(jsLoaded, 500);
	function jsLoaded() {
		if (document.getElementsByClassName("table-problem").length > 0) {
			clearInterval(jsInitCheckTimer);

			var circles = document.getElementsByClassName("table-problem");

			let old_green = "rgb(0, 128, 0)";
			let old_brown = "rgb(128, 64, 0)";
			let old_cyan = "rgb(0, 192, 192)";

			let new_green = "rgb(120, 196, 150)";
			let new_brown = "rgb(138, 59, 44)";
			let new_cyan = "rgb(104, 200, 242)";

			for (let i = 0; i < circles.length; i++) {
				var circ = circles[i].innerHTML;
				circ = circ.replaceAll(old_green, new_green);
				circ = circ.replaceAll(old_brown, new_brown);
				circ = circ.replaceAll(old_cyan, new_cyan);
				document.getElementsByClassName("table-problem")[i].innerHTML = circ;
			}
		}
	}
}

```

まず、`window` の `addEventListener` で `load` が発火するのを待ちます。`load` は HTML や CSS、画像の読み込みが完了すると発火するので、ページの読み込みが終わると、`main` という関数が呼ばれる感じになっています。

つぎでは `setInterval` で `jsLoaded` という関数を 500 ミリ秒ずつ繰り返し処理します。これに `jsInitCheckTimer` という名前をつけておき、あとでキルするのに使います。

`jsLoaded` では何をするかというと、さっきまでの問題はおそらくクラス `table-problem` が完成する前にスクリプトを実行しちゃったので色が変わらなかった、という問題だったと思います。なので、`table-problem` がしっかりモノを持つまで待ち続ける必要があります。`getElementsByClassName` は配列が返ってくるので長さが 1 以上になるまで待ちます。

`table-problem` が完成したら if の中に入ってくれます。もう待つ必要がないので、`jsInitCheckTimer` は殺しておきます。あとはさっきまでの色置換の処理を行えば OK です！！

改めて変更を反映して、AtCoder Problems のサイトにつないでみると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/b2d9e4fd-3d1a-4015-917f-ed7badb164be.png)

1 秒くらい経ったあとにしっかり難易度の丸も色が変わりました！！成し遂げたぜ

# おわりに

という感じで、拡張機能を実装しました。CSS の上書きと、要素の置換という 2 つを行っていますが、結構勉強になることも多かったです。

ゆくゆくは上向き矢印もちゃんと変わるようにアプデしたいね。

# 参考

[2. 実践編 - Chrome 拡張機能を作ろう](https://www2.kobe-u.ac.jp/~tnishida/programming/ChromeExtension-02.html)
[JavaScript:class要素のHTMLをreplace。](https://fuuno.net/web/class_js/class_Js.html#google_vignette)
[動的なページの読み込みが完了してからChrome拡張機能を実行する方法 #JavaScript - Qiita](https://qiita.com/3mc/items/c3c580ca5de4a2d3990d)

# 追記

### innerHTML に注意

バグ修正をしました。[リリース v1.2](https://github.com/gj5752/AtCoder-Universal-Color/releases/tag/v1.2) です。

何を修正したかというと、難易度の丸にマウスオーバーしたときのこのポップアップ表示が実は前のバージョンまではされなくなってしまっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/b1a0ab9b-b6b8-4582-a7bb-97be38098d2b.png)

これはこれで不便なので修正したということです。

`innerHTML` での HTML ファイル変更が悪さをしていました。このポップアップ表示はイベントリスナーとして待機されているのですが、`innerHTML` を使用して書き換えをするとイベントリスナーが失われてしまうようです。

よって、`innerHTML` ではなく `setAttribute` を使って属性値の書き換えで対応することにしてます。これだとイベントリスナーが失われずにちゃんとポップアップ表示ができます！

まず、`querySelectorAll(.difficulty-circle)` で、クラス `difficulty-circle` のすべての要素を取り出します。次のその要素たちそれぞれに対し、`getAttribute("style")` をして、style 属性を文字列で取り出します。ここで、上でやったような色の置換をして、`setAttribute` でもとに戻せば OK です！

```javascript
const circles = document.querySelectorAll(".difficulty-circle");
circles.forEach((circle) => {
	var style = circle.getAttribute("style");
	if (style) {
		style = style.replaceAll(old_green, new_green);
		style = style.replaceAll(old_brown, new_brown);
		style = style.replaceAll(old_cyan, new_cyan);
		circle.setAttribute("style", style);
	}
};
```

# 変更履歴
2025.06.08 追記