Tokyo Tech Portal のマトリクスコード入力を自動化した

# はじめに

こんにちは、わたしです。今回は弊学の Web サービスである Tokyo Tech Portal における Matrix コードの入力を自動でやりたいなと思いました。以前は、Chrome の拡張機能で誰かが提供してくれていたのですが、数か月前に突然停止なってしまい、困ってしまっていました。後継というか、同じような機能の拡張機能がまた出現していたので、自作する必要はなかったのですが、どうせなら自分で作ってみるかと思い、作りました。

この記事ではどうやって実現してるのかを説明したいと思います。拡張機能の作成方法とかは、前記事「[AtCoder の茶色と緑色が見にくいので Chrome の拡張機能を作った](https://qiita.com/gj_5752/items/ccd7ab668cf3c48685cf)」をご覧ください、おもしろい記事ですよ？

まあ、マトリクス入力って学期の開始のときと成績公開のときに虚無 Web を見るくらいにしかもう使わないけどね〜、T2 ショラが Science Tokyo の方のポータルに移ったので学期中はこっちでいいし。ありがとう、Science Tokyo の情シスチーム、虚無 Web も移行待ってます！

# マトリクス入力

マトリクス入力の画面を眺めてみよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/f0c0a73c-541a-47d2-a08a-d9dee4beaeab.png)

まあ、見慣れた画面ですよね。この左側に書かれてる [B,5] を見て、自分の学生証の裏側とにらめっこし、該当する場所のアルファベットを入力しますよね。

ここを自動でやりたいんですよ。いちいち学生証出すのもめんどうですし、仮に写真とかを撮ってたとしてもそれを開いて、で、[B,5] はどこかなーと目を動かす作業、やですね〜。Science Tokyo 生なら誰しも経験しているはずです。まこれ、セキュリティ的には二要素認証みたいにはなってますけれど、めんどうさの方が勝ってしまうという。What I have ではなくて、What I am のがいいよね、Science Tokyo ポータルの方の FIDO2 はマジで神です。まあ、それはおいといて。

自動で入力したいのでとりあえずまずは、こいつの中身を見てみましょう。丸はだかにしてやりましょう。入力フォームのとこで「検証」してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/ac93e892-d35f-40d0-bee4-1801c90b9ef9.png)

ふむふむこんな感じになっているのか。どうやらここは表になっていて、[B,5] と位置を示すものが書かれた要素と、入力部分の input 要素があって、input の方は name 属性がついていますね。name がついていればここは取り出せそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/87f55588-d5ae-4c82-b263-b0e77198d188.png)

ちなみに他の 2 つはこんな感じ。`message3` `message4` `message5` という名前になっていると...

# JavaScript で自動入力をしてみる

さて、JS を思い出すと、`getElementsByName` というのがあったと思います。name 属性を使って要素を取り出せるやつですね。今回、入力部分は id じゃなくて name が割り当てられていますからこれを使って取り出してみましょう。このメソッドは Elements の名前の通り、引数にかかれた name を配列として入手するので 0 番を指定して取り出しましょう。（まあ、name 属性なので同じ名前は存在できるので配列として出てくるということですね。さっきも確認した通り、3 つの入力部分それぞれで名前は別物だったので今回は気にしなくていいでしょう。）

一旦、開発者ツールのコンソールを使ってやってみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/10670e53-e521-4901-9a88-b9104eb3d74e.png)

これで input 要素を取得できました！さて、input 要素はそういえば初期値を決めることができましたよね。value 属性です。この value 属性を使って、自動入力をすることにしましょう。

いま、input 要素を取得できてるので、value 属性を設定してみましょう。属性を設定するには `setAttribute` メソッドを使うのが良いですね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/a7c5fbc9-28e6-4978-98fd-3fd651653bc3.png)

コンソールで `setAttribute("value", "a")` とテキトーに a を初期値にして実行してみましょう。すると、ログイン画面の方のページにちゃんと黒丸が現れるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/3f7a314e-eb53-40fb-8587-15627fa31f0f.png)

よしよし、これで OK ですね。あとは同じように `message4` `message5` も要素を取得してセットアトリビュートすればいいです。

自動入力はこれでいけそうです！あとは、指定されてるマトリクスコードの場所を読み取って、正しいアルファベットを value 属性の初期値として指定したいですね。このやり方を考えていきましょう。

# 指定された座標を読み取る

ここからはアイデアです。どうやって座標を読み取って、自分のマトリクスコードから正しいアルファベットを取得するか。あ、座標といってるのは [B,5] みたいなやつのことです。

つぎのようなやり方はどうでしょうか。

① あらかじめマトリクスコードの羅列を 1 次元の文字列でもっておく
② Web のページから座標 [B,5] の部分を文字列で取得する
③ 取得した座標をひとつの数に対応させる
④ その数を使って正しい文字をゲットする
⑤ それを使って setAttribute をする

つまり、現状、2 次元になっているマトリクスコードをすべてつなげて 1 次元にします。そして、[B,5] のような 2 次元の座標を 1 次元のマトリクスコードとしっかり対応するように座標変換をし、正しいアルファベットを取得する、ということです。

ではまず、②の部分をどうするか考えましょう。さっき中身を見たところ、座標を表す部分の要素は特に id や name がつけられていないのでそこの要素だけを取り出すことはできなさそうです... ですが、その親要素を見ていくと、table という要素に id が振られていることに気づけると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/6776c3c3-4306-426d-aea1-6477a52c84d5.png)

`authentication` まあそのまんまの名前です。一番近くて、id や name が振られている要素はここなのでこいつを取り出すことを考えましょう。id のときは `getElementById` がありますよね。これを使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/ab81c3b5-6486-4d24-8d91-79601dd91b3e.png)


文字列として取得したいので `innerHTML` をつけて取り出しましょう。すると、ずらずらと文字列が取れたと思います。

この文字列の中で [B,5] を探し出したい！いろんなやり方はあると思いますが、今回は [ を手がかりにして、文字列検索を使ってみましょう。この表の中では [ の記号は座標にしか使われていないようです。なので、[ の位置がわかれば、そこから 3 文字分などとすれば座標を取得できそう。

JS の文字列検索は `indexOf(str, index)` というメソッドがあります。引数は 2 つあって str が検索する文字列、index はそのインデックス以降を調べる、というものです。

今回は [ が調べられればいいのでとりあえず `indexOf("[")` でやってみましょう。2 つ目の引数を省略した場合、前から検索して、一番最初に出てきたインデックスを返してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/0eec6047-e417-4240-aa95-2213d1a5ae08.png)

596 だそうです。ということは 597 ~ 599 の部分に B,5 がありそうです。JS では部分文字列を取り出すには `slice(start, end)` というメソッドがあり、インデックスがstart から end-1 までの部分を抜き出します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/ec67ecbe-d944-4721-a261-8216f738ccdd.png)

597 から 600 と指定すると、B,5 を取り出すことができました！いい感じです。ではあと 2 つを取り出しましょう。同じように文字列検索を `indexOf` でやってインデックスを取得したいですが、2 つ目は 596 より後ろなのに注意します。`indexOf("[", 597)` とするといいんじゃないでしょうか。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/95fea779-8da3-4336-94ea-6e8ab6878c2d.png)

3 つ目もついでに取り出してみました。これで座標を文字列として取得することができました！そしたら次はこれをひとつの数に対応させることですね

# 1 次元マトリクス

本来 2 次元のマトリクスを 1 次元にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/ae093bc9-0d5f-4288-b93c-b36335d7fefd.png)

これは例ですが、マトリクスコードはこんな感じになっていますよね。これをひとつの文字列にしてしまえばいいんです。わかりやすく横方向につなげましょう。1 行目のあとに 2 行目を続けて書く、そして 3 行目、4 行目と続けて、これをひとつの文字列として変数に格納しましょう。これは打つのが大変ですが、この 1 回きりになるのでがんばる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/3b492a0e-9ed6-41f7-918d-8d2c00c16941.png)

こういうことです。で、さっきの取得した座標を該当するインデックスに変換できればいいですね。

さて、B,5 はインデックスいくつに対応するでしょうか。マトリクスコードは 1 行あたり 10 個あって、これを行ごとにつなげているので、後ろの数字分 10 動かせばいいでしょう。ただし、0 から始まるので、1 引いた分だけ 10 を足します。あとはアルファベットを何番目と対応させればいいです。B は 1 番目で、5 から 1 ひいた 4 つだけ 10 動かします。なので 40 足して 41 番目が B,5 に対応するインデックスでしょう。今回だと `I` になりますね！

ではこの計算をコンピューターにやらせましょう。いま、`B,5` と文字列で取得しました。これを 41 に変換させます。つぎのようにやります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/6c5e7a24-1c03-4450-b816-4a0057b1686f.png)

取得した `B,5` は `pos` という名前がつけられていたとします。まず、`charCodeAt(ind)` は、その文字列の `ind` 番目の文字の ASCII コードを返します。これを使ってアルファベットを数字に変換させます。B なら 1 にしたいので、A の ASCII コードを引けばこの通りにできますね。その次は `B,5` の `5` の部分を `Number` で int に変換し、あとはさっき言った通り 1 引いて 10 かけて足せばいいです。しっかり 41 になっていますね。

もうこれでいけます。あとは今までやったことをプログラムとして書けばいいわけです！

# 実装

こんな感じの実装になりました

```javascript
function getMatrixIndex(pos) {
	const column = pos.charCodeAt(0) - "A".charCodeAt(0);
	const row = Number(pos[2]);
	return (row - 1) * 10 + column;
}

function extractMatrixPos(doc, ith) {
	const index = [...Array(ith + 1).keys()].reduce(
		(acc, _, i) => (i === 0 ? doc.indexOf("[") : doc.indexOf("[", acc + 1)),
		-1
	);
	return doc.slice(index + 1, index + 4);
}

function setValues() {
	const mainTable = document.getElementById("authentication").innerHTML;
	const matrix = "CEVSFCTYOKTAJVFRKWFGPSHOARRYUUFUCSZADBOHFIBYFAHWYXMVOKLKFMDGXECVXXXEKO";

	const pos1 = extractMatrixPos(mainTable, 0);
	const pos2 = extractMatrixPos(mainTable, 1);
	const pos3 = extractMatrixPos(mainTable, 2);

	document.getElementsByName("message3")[0].setAttribute("value", matrix[getMatrixIndex(pos1)]);
	document.getElementsByName("message4")[0].setAttribute("value", matrix[getMatrixIndex(pos2)]);
	document.getElementsByName("message5")[0].setAttribute("value", matrix[getMatrixIndex(pos3)]);
}

setValues();
```

関数 `getMatrixIndex(pos)` はさっきの座標から 1 次元のインデックスの計算です。

関数 `extractMatrixPos` は、id が `authentication` になっていた table 要素を文字列として全部取り出したとき、その文字列の中で [ の場所を探して、[B,5] の B,5 を文字列として取り出してくる関数です。引数 `doc` に table 要素が入ります。`ith` は何番目の [ かを表す数字です。中身の説明は省略しますが、コンソール上でやった [ の位置を探す実験を配列の畳み込みで表現しています。

`setValues` では、一番最初にやった value 属性の指定を行います。ここにマトリクスコードを 1 次元にしたものを置いておき、`extractMatrixPos` を使って、座標をそれぞれ取り出し、`pos1` などとしておいて、この文字列からインデックスを `getMatrixIndex` で計算し、value の初期値として `setAttribute` で設定をします。

これをコンソールで実行するとちゃんと入力されて、マトリクスが正しければ認証も通ると思います！

# おわりに

座標を調べる方法はほかにいいやり方もあるのかなと思いますが、わりと脳筋な感じでやってしまいましたね。動けばいいのだ動けば。マトリクスコードがクビになるまで、がんばってもらいましょう。意外とかんたんにできたなという印象でした。JavaScript はなんでもできてしまう。

これは完全に余談ですが、この記事も、このスクリプトも US キーボードで書いています。ずっと JIS ユーザーだったので特に " とか = とかの記号が大変でした。しかも、外付けの JIS キーボードを使ってるので見えてる記号と入力される記号が異なるという... US には時間をかけてこれからがんばって慣れていきたいと思います。US のがいいよと多方面に言われたので US の人になりたい。