🧾

Googleリマインダーを登録するAPIを構築する

2022/03/05に公開約5,000字2件のコメント

What is this?

Slack slashコマンドからGoogleリマインダーを登録できるようにしたい」の前編です。後編はこちらから。
Googleリマインダーを登録するAPIを構築します。え、そのAPIはGoogleが提供してないの?と思いますよね。どうやら公式APIは無いようなのです。
しかし、目標達成のためにはslashコマンドを受け取るためのAPIが必要なので、構築してみました。

※Slack slashコマンドを新規作成する方法は情報が豊富だと思いますので、本記事では割愛します

前提知識

本記事を読むために下記の知識を想定しています。

  • 基礎的なAPIの理解
  • python3 / pip
  • Docker

Googleリマインダーとは

https://support.google.com/calendar/answer/6285327?hl=ja&co=GENIE.Platform%3DDesktop
Googleカレンダーに付随するリマインダー機能です。他のGoogleサービス(Keepやタスク)でも共有されているようです。

2022/03/01現在、作成ボタンからは選択肢がないのですが、
Googleカレンダー作成ボタン

作成ウィンドウから「リマインダー」を選択できます。
リマインダー作成画面

タスクとの違いは3つ

  • 「毎日」や「毎週」のような繰り返し設定が可能
  • 完了しないと、翌日に持ち越される
  • タイトル以外の細かい説明は追加できない

公式APIはないが、非公式ラッパーが公開されている

Googleは多くのサービスのAPIを公開していると思っていたのですが、散々探した結果、Googleリマインダーに関しては公開されていないようです。2019年からあるサービスらしいのですが、そういうこともあるんですね。

そこで、google-reminder-api-wrapperという非公式Pythonパッケージがあったので、今回はこちらを使用しました。

https://github.com/windmark/google-reminder-api-wrapper

※2022/03/01時点での動作確認(新規リマインダー作成のみ)はできていますが、非公式ラッパーであるため今後の動作保証はできません

驚くべくことに、GoogleカレンダーのUIが飛ばしている情報をもとに開発したそうです。なお、今回無事に仕様できたので、最後にアップデートされている2019年からAPI仕様に変化はない模様。

pipでインストールし、クッキー情報を環境変数に設定することで利用可能です。

google-reminder-api-wrapperに必要なクッキー情報の探し方

クッキー情報はChromeデベロッパーツールを開いて、Networkタブを選択肢、クエストヘッダー情報内から必要な変数名を探してください。Networkタブを開いた状態で、UI上からリマインダー登録をした上で、reminderで検索すると探しやすいです。

フォルダ構成

|-Dockerfile
|-requirements.txt
└src
  └-main.py

Dockerファイルによる環境設定

後編で紹介するGoogle Cloud Runを使用するためにDockerで環境構築します。GCRのデフォルトポートが8080なので空けておきます。上記Googleカレンダーのクッキー情報やSlackアプリのキーはご自身のものを使用してください。

※本当はDockerファイルにベタ打ちしないほうがセキュリティ上よいのですが、今回はプライベートな利用であり、悪用されてもリマインダーがハックされるだけなので簡便さを重視しました。

Dockerfile
FROM python:3
USER root

EXPOSE 8080:8080

# Envoirment variable for google reminder wrapper
ENV SID='xxxxxxxxxxxxxxxxxxx'
ENV HSID='xxxxxxxxxxxxxxxxxxx'
ENV SSID='xxxxxxxxxxxxxxxxxxx'
ENV APISID='xxxxxxxxxxxxxxxxxxx'
ENV SAPISID='xxxxxxxxxxxxxxxxxxx'
ENV authorization='xxxxxxxxxxxxxxxxxxx'
ENV key='xxxxxxxxxxxxxxxxxxx'

# Envoirment variable for Slack app
ENV SLACK_SIGNING_SECRET="xxxxxxxxxxxxxxxxxxx"
ENV SLACK_OAUTH_ACCESS_TOKEN="xxxxxxxxxxxxxxxxxxx"

RUN apt-get update
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
ADD requirements.txt /root/
RUN pip install -r /root/requirements.txt

ADD "/src/." /root/src/
WORKDIR "/root/src"
CMD ["python", "main.py"]

FlaskによるAPI実装

slashコマンドを実装する上で、既存情報が多いflaskを使用しました。

requirements.txt
Flask
google-reminder-api-wrapper

認証部分

slashコマンドからの認証部分は下記記事を参考にしています。

https://qiita.com/ao_log/items/6bcc73d93bba1a068f2e

Slack公式にも同様の例があるようです。

https://api.slack.com/authentication/verifying-requests-from-slack
main.py
import os
import hashlib
import hmac
from flask import Flask, request

SLACK_SIGNING_SECRET = os.environ['SLACK_SIGNING_SECRET']
SLACK_OAUTH_ACCESS_TOKEN = os.environ['SLACK_OAUTH_ACCESS_TOKEN']

def verify(request):
    try:
        slack_secret = bytes(SLACK_SIGNING_SECRET, 'utf-8')
        timestamp = request.headers['X-Slack-Request-Timestamp']
        request_data = request.get_data().decode('utf-8')
        base_string = f"v0:{timestamp}:{request_data}".encode('utf-8')

        my_signature = 'v0=' + \
            hmac.new(slack_secret, base_string, hashlib.sha256).hexdigest()

        result = hmac.compare_digest(
            my_signature, request.headers['X-Slack-Signature'])
    except:
        return False

    return result

Googleリマインダーの呼び出し

google-reminder-api-wrapperの利用は上記クッキー情報以外は特にトラップもなく、スムーズに実行できました。

日時の指定フォーマットはyyyy-MM-dd hh:mmです。日付テキストの抽出がどうしようもないレベルの低さなのは許してください…まずはなるべく早く動かすこと重視だったので、そのうちちゃんとします。

main.py
from google_reminder_api_wrapper import ReminderApi
datetime_index = 16

def set_reminder(text: str, dt: str):
    try:
        api = ReminderApi()
        new_reminder = api.create(text, dt)
    except:
        return False
    return True

def define_request_texts(text):
    title = text[:-datetime_index-1]
    dt = text[-datetime_index:]
    return title, dt

チュートリアル通りのflask実装

返り値がひどいのもゆるして、、、

main.py
app = Flask(__name__)

@app.route('/goremind', methods=['POST'])
def main():
    if verify(request):
        title, dt = define_request_texts(request.form['text'])
        if set_reminder(title, dt):
            return "done"
        else:
            return ('Google reminder query was not allowed', 405)
    else:
        return ('Request failed verification.', 401)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=os.environ.get('PORT', '8080'))

ソースコード全体

Dockerファイル以外のコードはGithubへ公開しています。

https://github.com/daikichidaze/slack-command-to-google-reminder

API部分はこれで完了!

終わってみるとすごくスッキリしていますが、ここまでくるのにものすごく紆余曲折ありました。もし、その悪戦苦闘の形跡に興味がある方がいればこちらへ。

https://zenn.dev/daikichidaze/scraps/fde4327de935bc

後編では、このAPIをGoogle Cloud Runへデプロイします!

Discussion

追記

開発完了から約2月経過。google reminder wrapperが動かなくなる。デバッグしていくと、クッキー認証がうまくできていない。どうやら、一定期間経つとクッキー認証情報が変わってしまう模様。新しく値を取得して、変更すると修正できた。

ログインするとコメントできます