Googleフォーム上のクイズを楽に周回する方法
今回話すこと
Chrome拡張機能を書いて手早くWebサービスを改造しよう
Webブラウザ上でフォームを作成
他者の作成したフォームに回答
フォームの作成
https://scrapbox.io/files/67405f0123528dfbae974e51.png
フォームの回答
https://scrapbox.io/files/67405f4195102f8dfd50ae0f.png
テスト機能
実はGoogleフォームでテストを作成することができる
テスト機能の設定方法
https://scrapbox.io/files/6740476963f245de83af5345.png
採点画面
https://scrapbox.io/files/674048ab8354f565341f75f1.png
今回解決したい課題
クイズを満点にする作業を時短したい
Googleフォームの特徴
Googleフォームは、特別に設定を行わない限り、回答者は何度でも回答できる
しかし、回答しなおす場合、すべての入力をはじめからやり直さなければならない
今回の話
すでに正解した問題は再入力をスキップしつつ、クイズを周回したい
....というニッチな願いを叶える話です
方法を考えよう
1. ユーザの回答をパースして
2. 過去にパースした回答を復元する
→ どう実装する?
Chrome拡張機能
Google Chrome(ブラウザ)にプラグインのようなものを導入できる仕組み
Content Script
Chrome拡張機能の仕組みの一つ
WebページにカスタムのJavaScriptを挿入することができる
→ 使えそう
方法を考えよう
1. ✅️ユーザの回答をパースして
2. 過去にパースした回答を復元する
事前入力機能
Googleフォームの機能の一つ
フォーム管理者は、特定の欄に特定の入力が既に行われた状態のフォームを回答者に配布できる
事前入力機能の仕組み
たとえば次のようなURLが発行されるhttps://docs.google.com/forms/d/e/{formID}/viewform?entry.1498083974=A&entry.1498083974=B&entry.1498083974=C&entry.1670445268=D&entry.1832366805=Hello!
チェックボックスの問題
https://scrapbox.io/files/6740465b8706da5f037fd413.png
チェックボックスについて
entry.1498083974は1-1(チェックボックス式)に対応していそう
entry.{questionId}=["answerLabel1", "answerLabel2", ...]という形式?
ラジオボタンの問題
https://scrapbox.io/files/674046bd95102f8dfd4f3be3.png
ラジオボタンについて
entry.1670445268は1-2(ラジオボタン式)に対応していそう
entry.{questionId}="answerLabel"という形式?
テキストについて
https://scrapbox.io/files/674046d63662f9f51390d54b.png
テキストについて
entry.1832366805はText(テキスト式)に対応
entry.{questionId}="answer"という形式?
発想
回答者がURLを踏むと、その値をもとに自動で入力値がセットされる
→ 使えそう
方法を考えよう
1. ✅️ユーザの回答をパースして
2. ✅️ 過去にパースした回答を復元する
逆算的に考える
entry.{questionId}={answer}というクエリパラメータを作りたい
Content Scriptでこのパラメータを作るには、ページ内の情報から、questionIdとanswerの組を作らなければならないだろう
questionIdをページ内から見つけることはできる?
スコア画面で開発者ツールを開き、検索してみる
→ JavaScriptでFB_PUBLIC_LOAD_DATAというオブジェクトに格納されていそう
FB_PUBLIC_LOAD_DATA
よくわからない
https://scrapbox.io/files/6740498b951e84d94dd7b45b.png
構造を眺める
https://scrapbox.io/files/6740537e27982930903c15f6.png
発見
FB_PUBLIC_LOAD_DATA[1][1]の構造は、問題画面の各ブロックに対応していそう
1番目の要素
ブロックのタイトルが格納されている
code:example.js
const block = FB_PUBLIC_LOAD_DATA111; console.log(block1); // "1-1" 4番目の要素
問題のIDが格納されている
code:example.js
const block = FB_PUBLIC_LOAD_DATA111; console.log(block40); // 1498083974 → 使えそう
0番目の要素
問題を描画しているdiv要素に同じ値が格納されている
code:example.js
const block = FB_PUBLIC_LOAD_DATA111; console.log(block0); // 1610889796 実装する
1. FB_PUBLIC_LOAD_DATA[1][1]から問題IDと要素IDを取り出す
2. 要素IDから回答、点数を取得する(ここが大変)
3. クエリパラメータを作る
実行時の注意点
通常の拡張機能は、安全のためホストページ(今回はGoogleフォーム)のJavaScript実行環境とは隔離されている
https://scrapbox.io/files/67405be3dfd29a8514c21190.png
Manifest.json
Chrome拡張機能の設定は、manifest.jsonというファイルに記述する
https://scrapbox.io/files/67405d1686d1468ff9dbc36c.png
worldを設定する
worldを指定しなければ、拡張機能からホストページのJavaScriptオブジェクトは見えない
worldをMAINにするとFB_PUBLIC_LOAD_DATA_を参照できるようになる
実装した
250行くらい
デモ
2問目を間違ってしまった!
https://scrapbox.io/files/67405a4bb3fed14628829852.png
Retryボタンが増えている
Content ScriptでcreateElementした要素をappendChildした
https://scrapbox.io/files/67405a7faccc1ae7d669622d.png
クリックすると
正解した問題だけ自動入力されたフォームが表示される
https://scrapbox.io/files/67405af2c7139dd70115cb36.png
今後の展望
他のフォームシステムへの対応
Chromeウェブストアでの配布
他のフォームシステム
今度はMicrosoft Formsを使うことになった!
事前入力機能はフォーム管理者によるオプトインだが、いけるか...?
Chromeウェブストア
作成した拡張機能を配布できるストア
今回の機能はニッチだが、同じ需要を持つ人は世界に10人くらいはいるだろう
まとめ
Chrome拡張機能を使うとWebサービスの挙動をいじれる
ホストページのDOMやJavaScriptオブジェクトを参考にしながら作らざるを得ない場合もある
しかし内部仕様に依存するため壊れやすい