1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Googleカレンダーのブックマークレットを作る過程を淡々と記録する

Last updated at Posted at 2023-07-02

この記事の目的

Googleカレンダーを便利に使うブックマークレットを作る際の中間生成物をじょじょに上げていく。

作りたいもの

  • Googleカレンダーで予定を仮押さえした情報をプレーンテキストで取り出したい(日程調整によく使うやつ)
  • それをクップボードにコピーしたい
  • Gooleカレンダーには検索結果一覧画面があるのでそこから情報を取り出すブックマークレットを作りたい
  • 過去の予定がヒットすることもあるので、過去の予定は無視したい

まず検索結果一覧のクラスを特定

image.png

この画面からこの一覧を取得するCSSセレクタを特定

普通だったら右クリックで要素を特定するけどこの画面は右クリックが機能として奪われてる
image.png

こういうときにはデベロッパーツールの検索機能で対象文字列で探す
image.png

予定一覧のありかがわかったのであとはそこから徐々に上にたどりながら一覧を取得するdivを探し当てる。

image.png

どうもdiv[role=rowgroup]で行けそうだとも思うが、実際にdiv[role=rowgroup]で検索してみると、

image.png

左の月間カレンダーにもヒットしています。もうちょっと特徴はないか。

div[role=rowgroup] div[role=row]:has(div[role=gridcell]) までCSSセレクタを絞り込むことで、各行を特定できた。

image.png

右下に2 of 3と表示されていることから、3件ヒットしていることが分かる。

JS化する

ここまでくれば、あとは各行の要素を個別に取り出せばいい。JSをゴリゴリ書くんだけど、まずはconsoleでデバッグしてみる。

$$('div[role=rowgroup] div[role=row]:has(div[role=gridcell])').forEach(
  row => {
    console.log("---");
    console.log(row.innerText);
  }
)

これをDevConsoleに貼り付け。

image.png

3行でてるし、画面上に出てるテキストがデバッグできてるから期待通り。

あとは各要素を取り出せばいい。このブックマークレットで欲しいのは以下の要素。

  • 月日
  • 曜日
  • 時刻のfrom-to
  • 年月日(過去の予定を取り除くためのフルの日付)
$$('div[role=rowgroup] div[role=row]:has(div[role=gridcell])').forEach(
  row => {
    console.log("---");
    console.log(row.querySelector('[aria-label]').getAttributeNode("aria-label").value);
    console.log(row.querySelectorAll('[role=gridcell]')[1].innerText);
    console.log(row.querySelector('[role=gridcell] [role=button]').getAttributeNode("aria-label").value);
  }
)

デバッグ

image.png

期待通り。

ここまできたら次は、メールに貼り付ける文字列を作る

let lines = []
$$('div[role=rowgroup] div[role=row]:has(div[role=gridcell])').forEach(
  row => {
    console.log("---");
    let mda = row.querySelector('[aria-label]').getAttributeNode("aria-label").value
    console.log(mda);
    let time_range = row.querySelectorAll('[role=gridcell]')[1].innerText 
    lines.push(mda + " " + time_range);
    console.log(row.querySelector('[role=gridcell] [role=button]').getAttributeNode("aria-label").value);
  }
)
console.log(lines);

実行結果

image.png

期待通り

クリップボードにコピーする機能を調べる

次にクリップボードにコピーしてみよう。
クリップボードアクセスはセキュリティ制限があれこれあって何度もアーキテクチャが変わってる。
ここの仕様の安定性は期待せずにさがしてみると、クリップボードAPIというのがあるらしい。

まずは単体で使ってみよう。

まずは手元のChromeで使えるかどうかを確認。

image.png

クリップボードオブジェクトが帰ってきた。何か設定をいじることなく使えるみたい。

クリップボードにコピーするメソッドを使ってみる。

image.png

ドキュメントにフォーカスが無いよというエラーが出てきた。ドキュメントにフォーカスがないとダメなのね。DevTools上にフォーカスがあるからそりゃぁそうか。このあたりはDevToolsではテストできなくなる。制約があるなら仕方ない。

じゃぁ本物のブックマークレットではクリップボードアクセスできるのかどうかをテスト。

まずは、ブックマークレットの雛形を用意。

javascript:(function(){alert('ya')})();

これをブックマークにセット。

image.png

クリックして実行

image.png

うごいたうごいた。

次にブックマークレットの中でクリップボードオブジェクトが使えるか試す。

javascript:(function(){alert(navigator.clipboard)})();

このJSをクリップボードにセット

image.png

テスト実行

image.png

クリップボードオブジェクトは使える。

次にwritetextが使えるか。
(以降、ブックマークレットの編集画面はすっ飛ばします)

javascript:(function(){navigator.clipboard.writeText("<empty clipboard>")})();

これをブックマークレットとして実行。

image.png

はいった。目標達成。

じゃぁってことで、元のGoogleカレンダーの取得のロジックと組み合わせたい。

ブックマークレットの制約を調べる

そういや、ブックマークレットって複数行のJSが入るとどうなるんだろう。
試しにミニマムのテストをやってみる。

javascript:(
  function(){
    let lines = [];
    lines.push('aa');
    lines.push('bb');
    navigator.clipboard.writeText(lines.join("\n"));
  }
)();

改行ありのままブックマークレットの編集でペースト

image.png

改行込でもペーストできた。

そのまま実行してみると。

image.png

ちゃんと実行できた。

徐々にDevTools上で動いていたロジックを移植。

DevTools上では動いていたけど、ブックマークにペーストしたとたんに動かなくなるポイントがある。

  • $$はDevTools上でしか動かないので、document.querySelectorAllに変更(すっかり忘れてた)
  • DevTools上では改行があるので動いていたけど、ブックマークレットだと一行になるので、セミコロン忘れると動かなくなるのでその辺を修正(普段JS書き慣れてないと雑になりがち)

それでできあがりがこれ

javascript:(
  function(){
    let lines = [];
    document.querySelectorAll('div[role=rowgroup] div[role=row]:has(div[role=gridcell])').forEach(
      row => {
        let mda = row.querySelector('[aria-label]').getAttributeNode("aria-label").value;
        let time_range = row.querySelectorAll('[role=gridcell]')[1].innerText;
        lines.push(mda + " " + time_range);
        console.log(row.querySelector('[role=gridcell] [role=button]').getAttributeNode("aria-label").value);
      }
    );
    console.log(lines);
    navigator.clipboard.writeText(lines.join("\n"));
  }
)();

動かしてみると

image.png

予定の一覧がクリップボードに入った。

非優先機能を見極める

最初にやりたかった「過去の予定を排除」というロジックを入れたかったけど、そもそも予定の仮押さえは「〇〇会議仮」みたいに仮押さえということが分かるようにするから、過去の仮押さえ予定がヒットしすることが稀だよなということを思った。あとここまで完成すると熱意が減ってきた。

試しに過去の予定がヒットした状態でブックマークレットを実行してみた。

image.png

この画面でブックマークレットを実行すると、こう

2006年 4月 1日 (土曜日) 終日
2007年 4月 2日 (月曜日) 13:00~18:00
2009年 4月 2日 (木曜日) 12:00~21:30
2010年 4月 3日 (土曜日) 14:00~17:00

万が一去年の予定が混じっても、去年の予定だということも分かるからまぁいいか。過去の予定排除機能はこのブックマークレットには機能としては入れない。

完成形

ということで完成形のブックマークレットはこれ。

javascript:(
  function(){
    let lines = [];
    document.querySelectorAll('div[role=rowgroup] div[role=row]:has(div[role=gridcell])').forEach(
      row => {
        let mda = row.querySelector('[aria-label]').getAttributeNode("aria-label").value;
        let time_range = row.querySelectorAll('[role=gridcell]')[1].innerText;
        lines.push(mda + " " + time_range);
      }
    );
    navigator.clipboard.writeText(lines.join("\n"));
  }
)();

ざっくり2時間でできる割にはまぁまぁ有用なツールができた。もっと早くに作っておけばよかった。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?