グラブルと呟遞並びにChrome擴張、その絕妙な食合せ

ヒヒのドロップ率が下振れ過ぎて頭のをかしくなつた騎空士によるtwitter救援コピペツールが一段落ついたので、制作の過程のあれこれをメモしておく。やりたいことはChrome擴張がバックグラウンドで最新救援ツイートを拾つてクリップボードに保存することで、外部の救援サイトを開いてグラブル畫面と行つたり來たりする必要をなくすことなのだが、twitter APIとグラブル、更にはChrome擴張の仕樣の嚙合せが絕妙に惡く、とんでもなくおぞましいアーキテクチャと化した。

救援コピペツール

Chrome擴張のポップアップ

取敢へず擴張のポップアップは上の畫像のやうに纏めた。まさかのReact製。最初はベタにHTMLを書いてちよこつとJQueryみたいな方針で始めたが、なんだかんだでJQueryを書くことはもうできない體になつてゐた。Reactでやるとなるとどうやつて擴張用のファイル群を吐くのかと云ふことになるが、これには幸ひなことにChrome擴張を作るvite用のプラグインとして@crxjs/vite-pluginが存在する。これを使へばviteの早いビルド環境で開發が可能になる。慾を言へばソースコードを差分檢出して自動ビルドして欲しいところなのだが、それには殘念ながら非對應のやうである。viteの開發サーバが使へればHMRも利くので效率が上がるのだが、Chrome擴張はビルド結果のディレクトリをChromeに食はせて動作を確認するしかないので、コードを變更する每に律儀にbuildコマンドを打つ煩はしさはある。

SkyLeapモード

「SkyLeapモード」はSkyLeapと云ふブラウザにある參戰者IDペースト機能をなんとかしてChromeに入れられないかと云ふことで試行錯誤してゐるのだが、中々うまい方法が思ひついてゐない。作ること自體はグラブルのDOMを弄ればいいだけなのでそこまで難しくはないのだが、要するにグラブルのUIに直接手を入れるとBANされるのではないかと云ふ懸念が生ずるのである。そもそもDOMを弄ればコピーしたIDを入力覽に自動でペーストすることもできてしまふわけで、しかしさういふツールが存在しないと云ふことは、それは運營の禁止する自動化行爲として外部ツール關係者の間でもやらないと云ふ暗默の諒解があると云ふことなのだらう。
ではSkyLeapは入力覽をタップでペースト機能があるのだから、Chromeで入力欄をクリックしてペーストするのはセーフなのではと云ふグレーゾーンに就いてはどうか。そもそもこの行爲はゲーミングマウス等で特定のボタンにCtrl+Vを割當てればほゞワンクリックと同じ操作で救援IDを入れることが可能であるし、その行爲をBANしたと云ふ話は聞いたことがないし、したら恐らく大問題である。
ところがTwitter世論を見れば、SkyLeapがやつてゐるからと云つてPCブラウザ擴張でやつていい理由にはならないと云ふ意見が目につく。確かにSkyLeapはグラブル公認のブラウザだし、それと野良Chrome擴張を同列に語るリスクはある。さらにスマホブラウザがどう云ふ實裝をしてゐるのかはよく知らないが、それがブラウザであると云ふことはDOMの解釋をブラウザが握つてゐることになり、從つてグラブルの特定の入力欄だけ特別扱ひすることは、グラブルのDOM自體をいぢることなしに可能と言へば可能である。
しかしChrome擴張であればDOMを弄つてその入力覽のonclickハンドラにペーストのコマンドを仕込むしかなく、これがUI改變だと言はれれば確かにさうなる。一方でグラブル運營は禁止してゐるUI改變の例としてマスクデータの表示等を上げてゐるので、他の公認ツールがやつてゐる程度のことを再現する改變であれば見逃される可能性もなくはない。こればかりは何とも言へないが、SkyLeapモードの實裝は現狀かなりのリスクを伴ふと云ふことは確かだらう。

救援ボスの取得

さて、今回觸れたいのは「SkyLeapモード」の方ではなく、「Twitterから最新救援をコピー」の方である。これにチェックを入れて、フォロー一覽のボスを選ぶと、Twitterからそのボスの救援ツイートをコピーしてくる。ポップアップ下の+ボタンを押すと、ボス一覽モーダルが出るので、そこからフォローするボスを6種類まで選擇できるやうにした。その際に名前やタグで絞込み機能も付けた。

ボス追加モーダル

グラブルのボスの種類はバックエンドのサーバーを立てて、そこで"参戦ID\n参加者募集!"と"Battle ID\nneed backup!"を10分おきに100件づつ探してきて、そこで發見されたボスを自動で追加することにした。そのため最近追加されたアトゥムもボス一覽データに手動で登錄すると云ふ操作は不要になつてゐる。タグだけは自動で判定するのが難しいので手動で更新しないといけないが、最惡これはサボつても新しいボスが何時までたつてもモーダルに出てこないと云ふ事態は避けることが可能である。現狀このデータはredisに持たせてあり、キーにexpireを設定して、一定期間發見されなかつたボスはexpireが更新されないので自動で消えて行くやうにしてあるので、昔のイベントのボスがいつまでも殘ると云ふこともない。
日英で同じボスかどうかの判定には、

で使はれてゐるロジックを恐らくそのまま採用してゐる。グラブルの救援ツイートにはボスの畫像が使はれてゐるが、同じボスの場合その畫像は日英で下に表示される名前くらゐしか目ぼしい差がないので、上部75%でphashを取つて一致すれば同じボスと見做してよいと云ふだらうと云ふものである。メドゥーサHLだけはなぜか背景畫像も違ふのでこのロジックが使へないので、名前で特別に判定する分岐をつけてゐるのも同じである。そして、phashを自力で實裝するのが面倒だつたゆゑに、今回のバックエンドサーバーにはこの手のライブラリが充實してゐる言語PythonのFlaskを使ふことにした。
ボス一覽を返す時に必要になる全キーを走査してその要素を取るみたいな操作がredisはあまり得意ではないが、せいぜい日英合せて200種にも滿たないボス數であればO(200)程度なのでそこまで大した問題にはならない讀みで愚直にkeysで走査してゐる。吹飛んだところで大して影響もないデータなので今の所この方針で續けるつもりだが、最新救援IDの抽出のためのキャッシュにもredisを使ふとすると、途端にkeyの數が殖えるのでこの作りだと問題が生じさうではある。dbのindexを分けられればいいのだが、利用してゐるredis cloudの無料プランでは、dbは1つしか持てないらしい。とすればボス一覽はpostgresにでも持つて、每日發見タイムスタンプが古いすぎるボスを消すみたいなバッチ處理を走らせる、みたいな方向にした方がいいかもしれない。herokuの無料プランだと10000件しかレコードを持てないが、ボスの數がこゝまで膨れることも恐らくないだらう。
ちなみにボスの種類の檢索はリアルタイム性はさほどいらないので、v2しか使へない新規アプリでもStreaming APIを使つてもよかつたのだが、Streamingを繫げつぱなしにしておくと、1秒間に5ツイートくらゐ拾つてくるので、月制限の200萬ツイートは餘裕で越えさうな勢ひが出てゐた。そこでやむなく10分おきに100件づつ探すと云ふ地味なスケジューリングになつた。他の救援サイトは多分Stremaing API V1.1で拾つたものをwss通信で個人に配信してゐる形式だと思ふのだが、200萬制限にはかからないのだらうか(あるいはもつと上のグレードのプランを使つてゐるか英語での檢索をせずに數を減らしてゐるとか?)。
元々最初はChrome擴張だけで完結してバックエンドのサーバーは立てないでできるだけミニマムにやりたかつたのだが、Twitter APIの仕樣が絕妙に相性が惡く、結局バックエンドサーバーもいるなと諦めて作り始めたので、折角立てるならとボス自動檢出の機能を持つことになつたと云ふ次第である。

Twitter認證

前提として擴張は個人個人で使ふため、個人認證は必須である。個人で認證して得たトークンでの檢索はその個人の制限にしかかからないので、アプリの他利用者がその影響を被ることもないし、ボスの種類を檢索してゐるのもアプリのトークンなので、同じく無關係に檢索することが可能である(アプリ全體で一月200萬ツイートしか拾へない制限は全體に影響するが、15分以內に150回しか檢索できないと云ふ類のものは個々のトークンに對してしか影響がない筈)。そこでバックエンドサーバーが必要になつたのは、Twitter API v1.1が認證でOAtuth1しか使へないからである。これがもしOAuth2が使へれば、Public Client扱ひにしてChrome擴張だけで完結することもできたが、OAuth1となると、Chrome擴張のコードにAPIの祕密鍵の方も埋込まないといけないので、どうにも抵抗があつた。javascriptに埋込みとなると、exe等のバイナリにひつそりと入つてゐると云ふものに比べて數段おぞましい感じがする。一方でAPI v2にすればOAuth2が使へるが、これでは遲延10秒が基本なので、新鮮な救援IDが欲しい騎空士には全く役に立たない。
そこで前出のFlaskサーバーにTwiter連繫の認證機能を持たせた。このFlaskサーバー上で認證を通すと(具體的には救援IDを檢索しにFlaskサーバーを叩いて401が返つた時に、twitterの認證畫面を別タブで開くのでそこでする)、以降はこのサーバーにボスの檢索を依賴すれば、それを受けてサーバーがtwitter APIを叩いて得た新鮮な救援ID情報が返つて來ると云ふ流れになる。
Flaskサーバーはherokuに置いたのだが、これも無料プランなのでリージョンは米國である。なのでChrome擴張からTwitter檢索APIを直接叩いて救援IDを得るより中間に別サーバーを通すので大分ラグが出るのではと思つたのだが、どうもさう云ふこともなささうである。

救援IDコピー通知

Chrome擴張がFlaskサーバーに問合せて返つて來た救援IDをChromeの通知機能で表示する時に、その救援IDのツイートがされた時刻とChrome擴張がIDをコピーして通知を出した時刻の差分をfloorで丸めてラグとして表示させてゐるのだが、槪1~2秒と他の救援サイトと比べても遜色ない結果となつてゐる。なんならローカルにFlaskサーバーを立ててもこのくらゐのラグは出てゐたので、ボトルネックはPCとheroku間ではないのかもしれない(Twitter API自體のラグが米國同士より日米間の方が長いと云ふ話もあるらしい)。
なほ、Flaskサーバーに問合せが發生するのは、今アクティブなタブが救援ID入力畫面のURLにゐる時のみにしたので、迅速にIDをコピペして毆り、を繰返してゐればTwitter APIを叩く回數はそこまで多くはならない筈である(逆に言へば救援ID入力畫面で放置してゐればすぐに上限に到達するので注意が必要である)。

救援IDコピー

いよいよ最後の要素、救援IDのコピーである。グラブルがhttp通信なことが災ひして、これが結構面倒なことをしなければならなくなつた。Flaskサーバーとやりとりをしてゐるのはservice workerなのだが、こゝではクリップボードに文字をコピーすることができない。そこでchrome擴張の內、今表示してゐるページに對して直接仕込むcontent scriptに救援IDを送信して、それでクリップボードにコピーすると云ふ手間をかけないといけないのだが、この時便利に使へさうな

navigator.clipboard.writeText(text: string);

と云ふ函數はhttps通信で表示してゐるページで動いてゐるcontent scriptでしか使へないのである。そこで代替となるのは古の

document.execCommand('copy');

を使ふ方法だが、これはコピーする文字列を引數に取らないことからわかるやうに、今選擇されてゐる要素の文字列をコピーするのである。從つてグラブル內にダミーの要素を作り、その要素に救援IDを書込み、それを選擇することでコピーすると云ふ實裝にしなければならない。しかしたゞ函數を呼ぶだけではなく、ページに要素を足すとなるとこれまた一氣にUI改變の疑義をかけられさうになつてくるのでできればこれは避けたい。他にもグラブルのページ上で新規救援IDが來る度にコピーする處理を走らせたら、肝腎なグラブル自體のパフォーマンスが落ちさうと云ふ懸念もあるので、Flaskサーバーがtwitter認證後にredirectするトップページのtextareaをその領域とすることにした。

救援IDコピー用のtextareaを持つサイト

このFlaskテンプレートベタ書きでcssも何もない簡素なトップページがそれである。このタブを閉ぢるとservice workerが救援IDをコピーする器を失ふので、この擴張は機能しなくなると云ふ名状しがたきアーキテクチャとなつた。このページはアクティブに開いてゐる必要はないが、もし見える狀態にしておけば、救援IDの通知の表示と同時にそのIDがtextareaに書込まれてゐる樣が見られるはずである。

色々な要素が嚙合はなすぎてそれを回避して行つた先にはなんだかよくわからないアーキテクチャとなつたが、それなりに快適なChrome擴張にはなつたと思はれる。


この記事が気に入ったらサポートをしてみませんか?