
CalendarBot
個人利用2019年から稼働・7年
Google Apps Script / LINE Messaging API / Google カレンダー
翌日の予定と天気を、毎日18時に LINE へ送るボットです。 期間を送れば、その範囲の予定をカードで返します。
2019年6月に自分用として作り、以来ずっと毎日動いています。7年になります。
なぜ朝ではなく前日の夜なのか
カレンダーの通知は予定の直前に鳴ります。それでは手を打てません。
持ち物を用意するのも、早く寝るのも、予定を動かすのも、 前日のうちにしかできないことです。だから18時に翌日を送ります。
同じ理由で、天気は数字だけを出しません。 降水確率と天気から判断して、「傘を持って出てください」と書きます。 情報を渡すのではなく、その日にとるべき行動まで決めてから渡す。
予定が0件の日は、天気だけを送ります。 中身の無い通知を送ると、通知そのものが読まれなくなるからです。
主な機能
- 毎日18時に翌日の予定と天気を送る(時間主導型トリガー)
- 「今日」「明日」「今週」「今月」で予定を取得
- 「明日の天気」「週間天気」で予報を取得
- 降水確率と天気に応じた傘のリマインド
- 予定が0件の日は天気だけを送る
- すべての応答にクイックリプライを添える
- 6分割のリッチメニュー(画像も含めてコードから生成)
技術スタック
- 実行環境 — Google Apps Script(サーバーレス。サーバー費用0円)
- 外部 API — LINE Messaging API / Google Calendar API
- データ — Google スプレッドシート(受信ログ)
- 天気 — iCal 形式の週間天気予報カレンダーを購読してパース
- UI — LINE Flex Message(カード / カルーセル)、リッチメニュー
LINE Notify では、これは作れなかった
2019年当時、Apps Script から LINE へ通知する定番は LINE Notify でした。 トークンを1つ発行して POST するだけで送れます。 対する Messaging API は、チャネルを作り、トークンを管理し、 送信先のユーザー ID を扱う必要があり、明らかに手間がかかります。
それでも Messaging API にしたのは、作りたい形が Notify では作れなかったからです。 カードを横に並べることも、画面下にメニューを置くことも、 返信の候補を出すことも、Notify にはありません。 あるのは、文字と画像を一方向に流す機能だけです。
そして、LINE Notify は2025年3月31日でサービスを終了しました。 当時この方法で作られた同種のボットは、その日を境に動かなくなっています。
このボットは影響を受けていません。稼働開始から、移行作業は一度も発生していません。
手間のかかるほうを選んだ理由は、当時は単に「こう作りたかったから」でした。 それが6年後に効くことは、考えていませんでした。
署名検証ができない制約に、正面から対処した
LINE は Webhook の受信時に X-Line-Signature ヘッダーでの署名検証を求めています。
ところが Google Apps Script の doPost(e) では HTTP ヘッダーを取得できません。
つまりこの環境では、署名検証は原理的に実装できません。
代わりに、リクエストボディの destination(ボット自身の ID)と
source.userId の許可リストによる二段の検証を置いています。
そのうえで、これは署名検証と同等ではないとコードに明記しました。 できないことを黙って埋めると、後から読んだ人が「検証済み」と誤解します。 制約は、隠すのではなく書き残すほうが安全です。
API の仕様上限を前提に組む
カルーセルに入れられるバブルは最大12件です。 広い期間を指定されると、そのままでは送信が失敗します。
超過分は「他 N 件」のカードに集約し、何件指定されても送信が通るようにしました。
通数も削りました。初期実装ではテキスト・予定・天気を3通に分けていましたが、 1つのカルーセルに統合して1通にしています。 LINE の通数はメッセージオブジェクト単位で数えるため、同じ情報量のまま使用量が 1/3 になります。
外部画像への依存をゼロにした
初期実装では、天気アイコンを外部サイトから読み込んでいました。 権利の問題があり、リンク切れの危険もあります。
線形グラデーション(background.type: linearGradient)と絵文字による表現へ
全面的に置き換え、外部画像への依存を無くしました。
自分で管理していないものに、毎日の通知を預けないための判断です。
アクセストークンやカレンダー ID はスクリプトプロパティに集約し、 コードから秘密情報を排除しています。
7年動かして、初めて気づいた不具合
2026年に全面的に書き直し、9件の不具合を直しました。 そのうち3件は、7年間気づけなかったものです。
- 日付書式に
YYYY(週ベースの年)を使っていたため、 年末年始の数日だけ年が1つずれて表示されていた(正しくはyyyy) - 天気の文字列マッチが配列の範囲外を参照し、 特定の天気表現の日だけ通知が届かなくなっていた
- 想定外のメッセージやスタンプを受け取ると、応答処理が例外で止まっていた
いずれも「たまにしか起きない」ため、毎日の通知を見ているだけでは気づけません。 毎日届いていることは、正しく動いていることの証明にはなりませんでした。
いまは、外部サービスに依存する処理には必ず代替の経路を持たせています。
長く続けて分かったこと
個人開発でいちばん難しいのは、作ることではなく動かし続けることでした。
続けられた理由は、自分が管理しなくていい場所に置いたことと、 毎日自分がその出力を受け取っていることだと考えています。 使っていないものは、壊れても気づきません。