Hono x Cloudflare Workersを使ってGoogle Calendarの予定を取得してLINEに通知する
昔にGAS(Google Apps Script)を使ってGoogle Calendarの予定を取得してLINEに通知していました。
その際に使っていたのがLINE Notifyなのですが、2025年3月31日で使えなくなるということで、LINE Messaging APIを使って作り直してみました。
使った技術・サービス
- Hono
- Cloudflare Workers
- Google Calendar API
- LINE Messaging API
Honoの名前をよく見かけるようになってきたのと、Cloudflare系のサービスを使ったことが無いかつ、サクッと作れそうだったのでCloudflare Workersで試してみました。
実装
実装したものはこちら↓
準備
LINEに通知するために、通知用のLINE公式アカウントの作成と、LINE Messaging APIのトークンを取得する必要があります。
ここらへんの記事を参考にしました。
Google Calendar APIを使うために、GCPでGoogle Calendar APIを有効化 & サービスアカウント作成します。
こういうときって、OAuthで認証して、そのトークンを使ってAPIを叩くのが一般的だと思いますが、
自分だけしか使わないのでサービスアカウントを作成して、ターゲットのカレンダーにサービスアカウントを追加して使うようにしました。
Honoインストール
このコマンドを実行して使うテンプレートを選択したらアプリケーションが作成されます。
npm create hono@latest my-app
これでアプリケーションとCloudflare Workersにデプロイできる環境が作成されるのでとても便利です。
Google Calendar APIでイベント取得
サービスアカウントのクレデンシャルを使って、認証してそのトークンを使ってGoogle Calendar APIを叩きます。
このやり方なのでサクッとできる(これまでも使ったことがあるから)と思いましたが、Cloudflare Workersの実行環境だとNode.js標準モジュールが使えないので、googleapis
というライブラリでの認証ができませんでした。
代替としてNode.jsに依存していないライブラリを使いました。
その結果、このような実装になりました
const scopes = ["https://www.googleapis.com/auth/calendar.readonly"];
const googleAuth: GoogleKey = JSON.parse(env.GCP_SERVICE_ACCOUNT);
const oauth = new GoogleAuth(googleAuth, scopes);
const token = await oauth.getGoogleAuthToken();
const calendar = google.calendar({ version: "v3" });
const startOfDay = dayjs()
.tz(TIME_ZONE)
.subtract(15, "day")
.startOf("day")
.hour(0)
.minute(0)
.second(0);
const endOfDay = dayjs()
.tz(TIME_ZONE)
.add(15, "day")
.endOf("day")
.hour(23)
.minute(59)
.second(59);
const today = dayjs().tz(TIME_ZONE);
const yesterday = dayjs().tz(TIME_ZONE).subtract(1, "day");
const tomorrow = dayjs().tz(TIME_ZONE).add(1, "day");
const res = await calendar.events.list({
calendarId: env.GOOGLE_CALENDER_ID,
timeMin: startOfDay.format(),
timeMax: endOfDay.format(),
singleEvents: true,
orderBy: "startTime",
oauth_token: token,
});
-
env.GCP_SERVICE_ACCOUNT
にはjson形式のテキストが入っている - カレンダーの予定を15日前後取得しているのは、その日をまたいでいる予定も取得して表示したいから
- 認証は https://github.com/Schachte/cloudflare-google-auth を使って、そのトークンをAPI実行時に渡している
LINE Messaging APIで通知
ここは通知するテキストを作成して、各種トークンやシークレットをセットして、LINE Messaging APIを叩くだけでした。
// LINEへ送信
const client = new line.messagingApi.MessagingApiClient({
channelAccessToken: env.LINE_CHANNEL_ACCESS_TOKEN,
});
line.middleware({
channelSecret: env.LINE_CHANNEL_SECRET,
});
await client.pushMessage({
to: env.LINE_USER_ID,
messages: [{ type: "text", text: textMessages.join("\n") }],
});
ルーティングではなく、cronで実行
パブリックなリクエストではなく定期実行をしたかったのでcron trigger
を使いました。
デフォルトの形から、
export default app
以下のように設定。main
が実行する関数です。
const scheduled: ExportedHandlerScheduledHandler<Env> = async (
_event,
env,
ctx
) => {
ctx.waitUntil(main(env));
};
export default {
fetch: app.fetch,
scheduled,
};
wrangler.toml
にスケジュールの設定を追加します。
[triggers]
crons = [ "0 22 * * *"]
※ 実行環境のタイムゾーンがUTCなので、cronの時間はUTCで設定します。日本時間の午前7時に実行されるようにしています
Day.jsで少し苦戦
実行環境のタイムゾーンはUTCでカレンダーの予定の日時はJSTだったので、少し取り扱いに苦戦しましたが、
オブジェクトに対して.tz()でタイムゾーンを指定することで期待通りの結果が得られました。
dayjs().tz(TIME_ZONE)
所感
- 毎朝に予定をLINEで確認できるのが良い
- Slack通知でも良いかも
- Hono x Cloudflare Workersの開発 ~ デプロイがとてもスムーズで体験が良かった
- 自分だけの用途で使うなら無料の範囲内で使えそうなのもよい
- GCPのCloud functionsとかGASとかでちょろちょろ細かいコードを書いているので、ここにまとめるのも良さそう
Discussion