Google Form の勤怠管理を自動化したい
todo
- ヒストリーバックを禁止
自作フォームに入力した内容をGoogle Form にデフォルト値として設定したリンクを作れる。
フォームの入力内容を取得する
Google Form のHTMLは素直な実装がされてない。input
タグが記入欄と送信用で分けられている。試しに「あああ」と入力し、DevToolsで「あああ」を検索すれば2箇所ヒットする。記入欄はinput
のtype
がtext
になっていて、送信用のタグはinput
のtype
がhidden
になっている。表にして比較しておく。なお、入力欄のinput
は不要な属性がたくさんあったので省略している。
入力欄 | 送信用 |
---|---|
<input type="text" data-initial-value="ああああ" > |
<input type="hidden" name="entry.1210620850" value="ああああ"> |
入力したデータをquerySelector()
で取得したいので、その値の場所を示す一意なCSSセレクターを探す。今回は送信用のinput
から取得したい。周辺のinput
タグを見るとname
属性がentry.XXXXXXXX
と共通しているので、これをCSSセレクタにする。
このセレクタで値が取得できるかは、DevToolsのコンソールから確かめることができる。CSSセレクタが正しければ、コードの下に取得した結果が表示される。
入力済みURLの作成
上の方法は手動でしなきゃいけないが、Google Formは入力済みのページを作る機能が提供されていたのでもっと簡単にできる。entry.XXXX
という値も簡単に取得できる。
入力する内容はあえてアルファベットで書いた。日本語だとエンコードされて、入力した内容と対応するパラメーターを探すのに手間がかかるからだ。一方、日本語で書いても正しく動作するか確認するため、あとで日本語の回答も入れたらいいだろう。今回はたまたま必須事項に日本語入力があるので仕方なく日本語がついたURlが作成される。
TSで入力済みURLの再現
先ほど取得したリンクをTSで使いまわしたい。
URLからパラメーターのキーとバリューを取りたいからルURL
オブジェクトに変換して、searchParams
でパラメーターを取得し、ループを回して収集した。ついでに値をコピペするのは大変なので、変数に代入する雛形を書いてコピペする手間を減らした。
const url = new URL("https://docs.google.com/forms/d/e/1FAIpQLSfzp0GFGnlKpiC04D3bnhNye-AjBB44RnlzZsISeOOrB2vUGA/viewform?usp=pp_url&entry.877086558=%E8%BF%94%E4%BF%A1%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E3%83%A1%E3%83%BC%E3%83%AB%E3%81%A8%E3%81%97%E3%81%A6xxx@gmail.com%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B&entry.173198784=%E4%BD%9C%E6%A5%AD%E9%96%8B%E5%A7%8B%E5%A0%B1%E5%91%8A&entry.1498135098=10:00~15:00&entry.1424661284=sagyonaiyo&entry.2606285=sonota")
let params = url.searchParams;
for (const [key, value] of params) {
console.log(`const = "${key}"`);
console.log(`let = "${value}"`);
}
このコードを実行すると、entry.XXX
はconst
として、入力値はlet
として代入できる。自動入力のURLを作成すると?usp=pp_url
というパラメーターが自動でついてくるので一応つけておく。
次に取得したURLからパラメーターを取り除き、再びつなげて元の入力済みページのURLが再現するコードを書いた。?usp=pp_url"
という部分は自動でついているパラメーターなので、取り除かずにそのままにしてある。
const form_url = "https://docs.google.com/forms/d/e/1FAIpQLSfzp0GFGnlKpiC04D3bnhNye-AjBB44RnlzZsISeOOrB2vUGA/viewform?usp=pp_url"
const email = "entry.877086558"
const report_type = "entry.173198784"
const duration = "entry.1498135098"
const todo = "entry.1424661284"
const others = "entry.2606285"
const email_ans = "返信に表示するメールとしてxxx@gmail.comを記録する"
const report_type_ans = "作業開始報告"
const duration_ans = "10:00~15:00"
const todo_ans = "sagyonaiyo"
const others_ans = "sonota"
// &, = の入力ミス防止のため改行して書いている
const params = `&${email}=${email_ans}` +
`&${report_type}=${report_type_ans}` +
`&${duration}=${duration_ans}` +
`&${todo}=${todo_ans}` +
`&${others}=${others_ans}`;
const url = new URL(form_url + params)
console.log(url.toString())
さらっと書いてるが $
や=
の入力漏れや、空白の消し忘れでかなり手こずった。URLの文字列は長くてややこしいから目視で確認せず、差分チェッカーのサイトを使い、何が間違っているか確認しつつ修正した。
パラメーターに日本語を含める場合、自分でエンコードしなくてもいい。URLオブジェクトに代入したら自動でエンコードされる。
使う人によって変わるタイプのフォーム
Google Form の仕様として、チェックボックスもテキストエリアもボタンも、全て送信するときは文字列に変換される。1番目の返信に使うメールアドレスの確認用チェックボックスもテキストに変換しなければならない。しかしメールアドレスはログインしているユーザーによって異なるので、先ほどのようにURLのパラメーターに埋め込むのは無理そうだ。
そこで、Google Form に訪れたとき、要素をquerySelector()
で取得し、click()
を使うことでチェックボックスを入れることにする。チェックボックスはinput
タグで実装されてないのでどの要素をクリックすればチェックがつくのか調べる必要がある。(そもそもclick
イベントでチェックされるのかも分からない)。
返信に表示するメール
という文字列を属性のどこかに含みクリックしたら反応する条件にあったDOMを探す。もしそういうのがなくても、子孫や兄弟要素にあればいい。探してみると見つけた。
とても複雑なタグだが、文字列を含みさえすればいい。次のCSSセレクタでDOMにアクセスできるかコンソールタブを開いて確かめた。
document.querySelector('div[aria-label*="返信に表示するメール"]').click()
するとチェックボックスにチェックが入ったのでこのCSSセレクタを使うことにする。次のような関数を作った。この関数を、ページを読み込んだときに実行すればいい。
function selectReplyCheckBox() {
const checkBox = document.querySelector('div[aria-label*="返信に表示するメール"]') as HTMLBRElement;
checkBox.click();
}