Google Chrome拡張を作成してGoogleカレンダー連携機能を自ら生み出す話

## 概要
Chrome拡張機能を作成して、プログラミングスクール生の専用アプリ内のイベント情報を、
自分のGoogleカレンダーにサッと登録できるようにしました。
**今回の成果物だとHTML/CSS/JavaScript(ライブラリ無し)の知識で行けるのでハードルは高くなかったです。**

## この記事で取り扱うこと
- なぜ拡張機能を作ろうと思ったか（解決したい課題）
- Chrome拡張の概要と参考にした記事
- [Googleカレンダーへの予定追加用リンクの作り方](#googleカレンダーへの予定追加用リンクの作り方)　※今回の拡張機能に関係なく転用できそうです

## この記事では扱っていない技術
- TypeScript
- React,Vueなどのライブラリ・JSフレームワーク、Webpack
- Day.jsなどのJavaScriptのUtility系のライブラリ

できるだけ使いたい人の導入のコストを下げるため、かつ自分で弄ったりできるよう解説しにくいものは使いませんでした。
~~面倒だったからではないです。~~

## 完成品
RUNTEQ生で同じ悩みのある人はローカル端末に導入して使ってみてください。
非公式アプリなのでサイトの変更に伴って利用できなくなったり、データの種別によっては正常に動作しない場合があります。

https://github.com/da-yoshi-k/runteq-events-to-calendar

- イベントの詳細ページで拡張機能を起動すると以下のように画面がポップアップし、カレンダーへの連携ボタンが表示されます。　追記(11/14)：面談予約詳細ページにも対応
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498497/58cbd6ae-e281-ce22-9aec-1f61508373f2.png)
- 「Googleカレンダーへ登録」ボタンを押すと新しいタブでカレンダー追加用のページが開き、保存するだけです（データはデフォルトで入力済）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498497/a20d5352-cd5f-417a-2612-79d401af940d.png)

## なぜ拡張機能を作ろうと思ったか（解決したい課題）
RUNTEQでは受講生のみ利用できる学習カリキュラムやその他諸々の情報が載っているサイト（Webアプリ）があり、
その中の1機能としてRUNTEQ内で開催されているイベントの参加予約が行えます。
RUNTEQで開催されているイベントについては以下記事を参照ください。（毎日多種多様なイベントが行われてます！）

https://note.com/runteq_official/n/n2756273cdd37

困っている点として、Webアプリ内では何個か遷移すると自分が予約したイベント一覧を確認できるのですが、
他のスケジュールはGoogleカレンダーで管理していたため、カレンダーに登録し忘れた時はイベント直前まで忘れることがありました。
（イベント開始の2時間前にリマインダーメールが配信されるため、すっぽかすところまでは行きません）

予定を一元管理したいと言う思いがあり、自動化でなくともGoogleカレンダーに登録しやすくできないかなーと思っていたところ、[connpass](https://connpass.com)で以下のボタンを見つけました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498497/456e0333-cb9d-91cc-d8b6-52d66dca88c3.png)
このボタンは特殊なことをしているわけではなく、後述しますが特定のURLに対してクエリパラメータで予定の情報を付与しているだけのようです。

機能追加についてユーザーの1意見として改善提案することも考えられますが、
- 誰もがスケジュール管理にGoogleカレンダーを使っているわけではない
- いつ実装されるかはわからない

という観点からエンジニアだったら自分で作ったれやと思い立ち、今回はChrome拡張機能作成に至りました。~~というか作ってみたかったんや。~~
イベントページを開いてから3クリックで予定登録まで完了するので **かなり楽になりました。**　

## 本編
### Google Chrome拡張機能と今回作成した拡張機能の概要
外部サイトやQiitaなどでも参考となる記事は多数あるため、参考としたリンクを中心に紹介しつつ簡単に説明します。
- [Extensions（公式）](https://developer.chrome.com/docs/extensions/)
- [とほほのChrome拡張機能開発入門](https://www.tohoho-web.com/ex/chrome_extension.html)
- [Chrome Extension の作り方 (その1: 3つの世界)](https://qiita.com/sakaimo/items/416f36db1aa982d8d00c)

各サイトで触れられている通り`manifest.json`に、以下のような情報を登録します。
- 実行するアクション
- 拡張機能で各種ブラウザの情報にアクセスするためのパーミッション
- アイコンの情報
- ショートカットキー
etc..

今回の`manifest.json`は以下の通りです。
```json:manifest.json
{
  "name": "RUNTEQ event to goole calendar",
  "description": "RUNTEQのイベントをGoogleカレンダーに登録しやすくするための拡張機能です。",
  "version": "1.0",
  "manifest_version": 3,
  "permissions": ["activeTab", "scripting"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  }
}

```
また、ソースコードのフォルダ構成は以下の通りです。
```:フォルダ構成
└ README.md
└ icon.png       // 拡張機能のアイコン
└ manifest.json  // 拡張機能を規定するJSONファイル
└ popup.html     // アイコンをクリックした際に表示されるHTML
└ popup.js       // ポップアップ時の表示＋ボタン押下時の動作を記載
└ style.css      // ポップアップ内のスタイルを記載（別途BootStrapも利用）　　　　　　　　　　　　
```

今回の拡張機能は割とHelloWorldのコード+BootStrap導入+JavaScriptで追加用のコードを書いたくらいです。
（実装にかかった時間自体も1日程度）

### 拡張機能へのBootStrap導入について
BootStrap導入については以下の記事を参考にさせていただきました。
HTML中で4.x系のBootStrapをCDNから読み込むことで導入できました。
[【入門】Google Chrome Extension (拡張機能)でBootstrapを使用する](https://www.stay-home-build-app.com/entry/2021/01/19/211207)

### Googleカレンダーへの予定追加用リンクの作り方
connpassのボタンに設定されているリンクを見たり調べてわかったのですが、
`https://www.google.com/calendar/render`というURLに対して各種クエリパラメータを設定した状態でアクセスすることで、[完成品](#完成品)で示したように値が埋められた状態で予定追加ページに遷移できます。
以下の記事も参考にさせていただきました。

https://qiita.com/yuta_sawamura/items/738732565e2cfd11fb23

イベントの詳細ページにタイトルや日時があるので、今回は以下のようなマッピングにしています。
（何個かはconnpassの設定値を参考にしています）
|パラメータ|説明|設定値|
|----|----|----|
|action|基本「TEMPLATE」固定|TEMPLATE|
|text|予定の件名|イベントページ中のタイトル|
|dates|予定の時間。「YYYYMMDDThhmmss/YYYYMMDDThhmmss」で指定。(一つの日時のみ指定した場合は終日判定)|イベントページ中のイベント日時|
|details|予定の説明|対象のイベントページのURL|
|location|場所|「オンライン」固定|
|trp|外部向け公開オプション|「false」固定|

### 実装時に引っかかった点、実装を見送った点
1. Chrome拡張機能のお作法
Chrome拡張機能初心者ということが大きいですが、ブラウザのアクティイブなタブと拡張機能のポップアップのタブは見た通り別物なので、chromeのAPIである`chrome.tabs.query`([APIリファレンス](https://developer.chrome.com/docs/extensions/reference/tabs/))で情報を取得したり、`chrome.scripting.executeScript`([APIリファレンス](https://developer.chrome.com/docs/extensions/reference/scripting/))でスクリプトを実行するというお作法があることを学びました。

1. JavaScript系のライブラリ導入
勉強中のVue.jsや、イベントの日時の文字列を取り扱ったりするため[Day.js](https://day.js.org/)を導入するため、BootStrap同様にCDNから読み込んで使おうと思いました。
ただ、こちらは単純に読み込みだけを行うと以下記事のようにCORSポリシーの問題からエラーとなってしまいます。（[参考記事](https://note.com/churin_1116/n/n9155190204b3)）
webpackを利用してビルドをしたり、`manifest.json`に回避用の記述を書くという記事も見たのですが、今回は一旦課題解決の本質では無いかなと思い見送りました。

### さらにステップアップするなら...
1. JSライブラリ導入（技術面）
[vue-web-extension](https://github.com/Kocal/vue-web-extension)などを利用してVueを適用することを考えています。

1. ~~ショートカットキーの導入~~ 追記(11/3)：`Ctrl+Shift+U`として追加済み
`manifest.json`に少し記述を追加すれば、拡張機能を呼び出すためのショートカットキーの割り当てが行えます。
~~どーでもいい話ですが、どのキーをバインドしようか悩んだので今回は見送りました。~~
    <details><summary>以下の記述でショートカットキーを追加しました</summary>

    ```json:manifest.json
    (中略)
      "commands": {
        "_execute_action": {
          "suggested_key": {
            "default": "Ctrl+Shift+U",
            "mac": "MacCtrl+Shift+U"
          },
          "description": "拡張機能を起動します。"
        }
      }
    ```
    </details>

1. ~~別ページにも対応する（横展開）~~　追記(11/14)：面談予約詳細ページにも対応しました
似たような思いで、Webアプリ内の面談予約ページについてもGoogleカレンダーに登録しやすくしたいなーと思ってます。
（ページの構造は異なるので個別ロジック追加）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498497/6c863ff5-8382-f656-8c6e-035e3ee0ab55.png)

## 終わりに
今回はGoogle Chromeの拡張機能作成に入門しました。
予想していたよりずっと楽に実装ができたので、課題を解決するための色々な選択肢の一つに入りそうです。
とはいえまだ理解していない部分も多いので、ガッツリとした機能を作るならドキュメント等の読み込みが必要そうだなと感じました。

無い機能を嘆くのではなく、作ってみるという選択肢もアリだなと考える体験になりました。
以上です。