ターミナルからGoogleカレンダーに予定を追加したい


## 背景
Googleカレンダーを使っていて入力が鬱陶しかったのでターミナルから行えるようにした。需要はありそうだが記事が見当たらなかったので残しておく。誰かの参考になればとてもとても嬉しいです⭐️

## 流れ
GoogleカレンダーのAPIを叩けばできるかと思ったが、認証やら権限やらがわりとめんどくさかったので断念(さいごにに続く)。代わりに最近遊んでいるGAS(Google Apps Script)を使用した。

流れは下の画像の通り。
1. ターミナルでPythonファイルを実行し入力からjsonファイルを作成。
2. curlコマンドでjsonファイルをGASのウェブアプリへPOST。
3. GASでカレンダーに情報を記録。
![スクリーンショット 2021-06-29 23.37.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1696002/876ac5e6-4e27-ccbb-029d-1400abe303ea.png)

## 具体的な機能
Pythonファイルを実行すると以下のような入力状態に入る。

```shell-session
$ python add_schedule.py

予定の追加
日付　開始時間　予定時間　予定
```

[日付　開始時間　予定時間　予定]
この4項目を入力するとGoogleカレンダーが更新される。
![d2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1696002/8022aa44-16ff-b245-d35f-0caa9dc9023a.png)


## 入力について
### 日付・開始時間
日付・開始時間の入力はそれそれ`210630 2100`と想定しているが、入力を簡単にできるようにPythonファイル内で変換を行なった。

例：2021年6月29日22:00に実行したことを想定する。

|入力|変換後|
|--|--|
|30 21|210630 2100|
|3 930|210703 0930|
|423 8|220423 0800|

ようするに「次の3日の8時からの予定を入れたい」ときに`3 8`だけで済むようにした。詳細は後述のコードを参照。

### 予定時間・予定
予定時間の単位はhourで、例えば`1.5`とすれば1時間半となる。
予定は好きなものを書けばそのままカレンダーに反映される。

他にも記録するカレンダーを指定したり、場所を指定したりできるので各々オプションをつけてもおもしろそう。

## 実装

### Pythonファイルの作成

ここでやりたいことは以下の二つ。

1 入力をフォーマットに落としこみ以下のようなschedule.jsonに記述をする。

```json
{   
    "start_datetime": "2021/07/04 15:00", 
    "length": "2", 
    "content": "映画を見る"
}
```
後述のソースコードでは変数の意味がわかりにくいのでガイドを用意した。

![スクリーンショット 2021-06-30 12.09.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1696002/44c4756d-8ec3-297a-e5fd-3f571a60cb55.png)


2  curlコマンドを実行し、GASで作るURLへschedule.jsonをPOSTする。実行するコマンドは次のものであり、プロフェクトのURLはのちほどGASをデプロイした際に発行されるものを貼り付ける。

```shell
curl POST -H 'Content-Type: application/json' -d @schedule.json {GASで作成したプロジェクトのURL}"
```

#### ソースコード 

```Python
import subprocess
from datetime import date
from math import floor
import json

tmp = input("予定の追加\n日付　開始時間　予定時間　予定\n").split()

input_date, start_time, length = tmp[0], tmp[1], tmp[2]
# 予定の入力のスペースを許容
content = ' '.join(map(str, tmp[3:]))

today = date.today()
# 予定の時間を現在で初期化
schedule_year, schedule_month, schedule_day = today.year, today.month, today.day

input_day = int(input_date[-2:])
# 入力した日にちが今日より小さい場合翌月に設定する。(等しい場合は今月)
schedule_month += int(input_day < today.day)
schedule_day = input_day
# 入力の下２桁を捨てる。
input_date = input_date[:-2]

# 入力が3桁以上ある(月が指定されている)場合
if input_date != '':
    input_month = int(input_date[-2:])
    schedule_year += int(input_month < today.month)
    input_date = input_date[:-2]
    schedule_month = input_month

# 入力が5桁以上ある(年が指定されている)場合
if input_date != '':
    schedule_year = '20' + input_date

# スタート時間のフォーマットを整える
l = len(start_time)
if l == 1:
    start_time = '0'+start_time+'00'
elif l == 2:
    start_time = start_time+'00'
elif l == 3:
    start_time = '0'+start_time

# GASでDate型にキャストできるように整形
start_datetime = '{year}/{month:02}/{day:02} {hour:02}:{minute:02}'.format(
    year=schedule_year, month=schedule_month, day=schedule_day,
    hour=int(start_time[:2]), minute=int(start_time[2:]))

# 辞書型にまとめる
json_data = {
    "start_datetime": start_datetime,
    "length": length,
    "content": content
}
# jsonファイルに書き込む
with open("schedule.json", 'w') as file:
    json.dump(json_data, file, ensure_ascii=False)

# コマンドの指定
command = "curl POST -H 'Content-Type: application/json' -d @schedule.json {GASで作成したプロジェクトのURL}"
# コマンドの実行
subprocess.call(command.split(' '))
subprocess.run('clear')

```
---

### GASファイルの作成

Google Driveから新規Google Apps Scriptを作成する。下画像の場所にない場合はアプリを追加で見つかるはず。

![スクリーンショット 2021-06-30 1.22.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1696002/a4a317ca-0af1-1fee-37ad-eb5fbe0c3630.png)

GASでは、doPost(e)の引数としてcurlコマンドで渡された情報を受け取ることができる。これをjsonにパースすることでその情報にアクセスすることができる。

GASでカレンダーに記録するにはcalendarオブジェクトを生成し、
`createEvent('入力', 開始時間, 終了時間)`を実行すればよい。

当然終了時間は開始時間に予定時間を加えることで算出できる。

https://developers.google.com/apps-script/reference/calendar/calendar#createEvent(String,Date,Date)

#### ソースコード
```javascript
function doPost(e) {

  const json = JSON.parse(e.postData.contents);
  const start_time = new Date(json.start_datetime);
  const length = parseFloat(json.length) * 60;
  
  let end_time = new Date(start_time.getTime())
  end_time.setMinutes(start_time.getMinutes() + length)
  
  const calendar = CalendarApp.getDefaultCalendar();
  calendar.createEvent(
    json.content,
    new Date(start_time),
    new Date(end_time)
  );
}
```


---

### デプロイ
GASで記述したコードをアクティブにするにはデプロイする必要がある。以下の手順で進めればよい。

1 <b>新しいデプロイ</b>からデプロイする。コードを変更したときはデプロイを管理から更新する。

![d1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1696002/515d5acb-69c2-dd19-f88b-fee9af15d015.png)

2 <b>種類の選択</b>からウェブアプリを選択 

![d34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1696002/8ea82b5b-a4a7-2e4a-98f7-945064c68383.png)

3 説明を記述し、アクセスするユーザーを<b>全員</b>に指定。デプロイをクリック。

![d56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1696002/2dbd4f45-9008-1a55-99be-6fb066f6bdcc.png)


4 URLをコピーし完了。このURLをPythonで保留にしていた箇所に貼り付ける。
これでcurlのPOSTが届くようになった。これで完成なのでPythonファイルを実行していただきたい。グーグルカレンダーに内容が記録されていたら完璧だ⭐️


![d3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1696002/f5709f9e-2b4d-404a-4429-ddeea422a636.png)

<br>

(補足) GASでコードを変更したとき、再度デプロイする必要があり。ここで新たなデプロイを押してもいいのだが、URLが変わるため各所の修正が必要になってしまう。これを避けるには<b>デプロイを管理</b>の画面で編集モードにし、アクティブになっているもののバージョンを新バージョンにするという手順をとる必要がある。

![d5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1696002/1989bf10-35a2-8cdd-dffc-b734072d821c.png)

---

### さらなるラクさを求めて

せっかくターミナルから予定を追加できるようになったので手数少なく予定を追加していきたい。作戦は３つ

1. Pythonファイルをホームディレクトリの近くに置く。
<p class="indent">自分はホームディレクトリ直下に「a」というディレクトリを作りました。今後も何かいつでも実行できるようにしたいファイルはここにつっこむ予定。</p>


2. iTerm2のHot Keyを使う。
<p class="indent">さんざんターミナルと言ってきたが自分はiTerm2を使っている。これは設定したHot Keyでいつでも起動させることができる。これによりコントロールキーを2回叩くだけでiTerm2を起動することができる。</p>

3. aliasの設定
<p class="indent">`as`というコマンドでaディレクトリ内のadd_schedule.pyを実行するaliasを設定した。</p>

```zsh:.zshrc
alias as="python ~/a/add_schedule.py"
```
以上３つの工夫から
^^
as⏎
という5タイプでどこからでも予定入力に入ることができるようになった。マジ快適


## さいごに
やりたいことができて、Qiitaも初めて書いてみて、体裁とか大丈夫かな〜って他の方の記事を改めて漁っていたらGoogleカレンダーのAPIを叩いてる記事が何個も出てきまして。普通にできるんじゃんってすごく悲しい気持ちになりました。。けど自分なりに試行錯誤するのはめちゃくちゃ楽しかったし、curlのこととか勉強になったからよし。それにスプレッドシートとか、Gmailの触りやすさも考えたらこのアプローチもあながちなしではないかも？
何はともあれこんな感じのツールをまた作っていきたいです⭐️

<br>

#### 参考にさせてもらったサイト

[【GAS】固定のURLで内容を最新にする方法［ウェブアプリ］](https://www.cg-method.com/gas-deploy-fixed-url/)
[curl コマンド 使い方メモ](https://qiita.com/yasuhiroki/items/a569d3371a66e365316f)
[iTerm2のHotkeyを使わないなんてもったいない！！](https://qiita.com/okamu_/items/a5086d2d5ba405f35acb)
[【時短】zshでエイリアスを設定する方法](https://qiita.com/terufumi1122/items/1bbb1cf96e376e30e9fc)