セキュリティ関連の学習の進捗があまり良くないので、一旦箸休め。
今回の目的
元々、会社のチャットツールにTeams使ってたんですが、Google Workspaceに乗り換えよう的な話が進みまして。
んで、結構困ったのが、Googleカレンダーってスケジュール追加した時、メールでしか通知できないんですね...
タスク管理にリソース割くのが嫌いな自分としては、(自分に対して)予定がある人はどんどんカレンダーに勝手にぶち込んでいってほしい。ただ、Teamsと違ってそれがチャットで連絡がこない...予定が追加されるごとにGmailのフォルダが膨れていくのなら少し話が変わってくるぞ...
という事で、「なんとかGoogle Chatにカレンダー追加通知飛ばせたら、Teamsと所感も変わらないし、タスクの見落としもなくなるんじゃないか?」 と思って方法を模索してみました。
目次
実行結果
結論、標準機能じゃ無理!!
ですがGoogleAppScriptを作成すれば、まあできなくはないよと。
色々ありましたが、最終的にはこんな通知がChatに届くようになりました。
隙間時間みつけて拡張させていきます。
新しい予定が追加されました
作成者: example@yourdomain.com
タイトル: プロジェクトA 定例会議
日時: 2025/10/02 15:00:00 - 2025/10/02 16:00:00
こんな通知がChatに来ます。
実行環境
- 環境: Google Workspace
- 接続元(ローカルPC): 特になし(ブラウザ上で完結)
- 使用言語: Google Apps Script (JavaScript)
- テスト対象: Google Calendar, Google Chat
開発ステップ
一応JavaScript全くの素人というわけではないにしろ、5年ぶりくらいに触れるので基本に忠実に...
まずは動くものを作る、という流れで進めました。
- ゴール設定: 「カレンダーに予定を入れたら、Chatに固定メッセージが飛ぶ」という最低限のゴールを設定。
- Webhook準備: Chat側でメッセージを受け取るための「Webhook URL」ってやつを取得。
- GAS初期実装: GASで新規プロジェクトを作って、とりあえずWebhook URL宛にメッセージを投げるコードを書く。
- トリガー設定: カレンダー側で「予定の更新時」にさっきのGASが動くように設定。
- 機能拡張: 基本通知が動いたのを確認して、作成者やタイトルみたいな動的な情報を含めるようにコードを修正。
-
権限エラーとの格闘:
CalendarApp
を追加したら動かなくなって、はまりました。手動実行で権限を再承認して解決。良いトラブルシューティング経験になった。 - ロジック改善: 「予定の更新時」トリガーが、作成だけじゃなく編集・削除でも動くことに気づく。「作成から60秒以内のイベントだけ通知」というロジックを追加して、やっと意図通りに。
-
フォーマット修正: Chatの太字って
**
じゃないのね...*
なのに気づいて修正。これで完成。
思考フローと問い
ステップに合わせて思考のフローと、仮説およびそのフィードバックはこんな感じでした。
-
「カレンダーとChatの連携ってデフォの機能じゃ無理なのか」
- 「いや、Googleのサービス同士なんだから、APIで呼び出しするなり機能拡張は想定されてるはず」と仮説を立てて調査開始。
-
Google Apps Script (GAS) っていうやつで連携できるらしい。
- 「コード云々書く前に、本当に連携できるのか・その時のフローを確認したい」
- 「だからとりあえずCalenderの事は無視。GASを手動実行→GoogleChatに連携のフローを優先するべき」
- 「"Google Chatに連携する"は抽象的すぎる。"どのChat(トーク)にメッセージを飛ばすか"に具体化」
- 「そもそも通知が飛ぶトリガーはなに?」
-
WebhookのURL宛にGASから定型メッセージを連携できた。
- 「じゃあ、Calenderからイベントを拾ってくるにはどうしたらいい?」
- 「そもそも、どんな情報を渡してくれんの?」
- 「Googleのサービスどうしだから、GASから標準モジュールとかでCalenderのイベント渡せなきゃおかしいよね?探そう」
etc...
開発中の気づき
- Webhookって案外手軽: もっと面倒かと思ったけど、ChatのWebhook URLは数クリックで発行できて便利。これ覚えておく。
-
イベントオブジェクト
(e)
ってなんぞ: トリガーが関数に渡す引数(e)
。GoogleColabでPython書いてるときも、情報調べてると大体eで書いてある。 -
権限の確認、後から来るんかい:元々初回のスクリプト実行時に権限の確認が要求されたが、
CalendarApp
みたいに新しいサービスをコードに追加したら、再度権限の承認が必要になる。しばらく通知こないな~ってイベントログ見たら...やったはずなのに...ってなったよ。
コード全文
// Googleカレンダーの更新をトリガーに実行される関数
function sendCalendarNotification(e) {
// ▼▼▼ Google Chatで取得した自身のWebhook URLに書き換える ▼▼▼
const webhookUrl = "ここにWebhook URLを貼り付け";
// ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
// 1. どのカレンダーで予定が更新されたか特定する
const calendar = CalendarApp.getCalendarById(e.calendarId);
// 2. そのカレンダーから、直近で作成された予定を1件見つけ出す
let latestEvent = null;
let latestCreationTime = new Date(0);
const oneDayAgo = new Date();
oneDayAgo.setDate(oneDayAgo.getDate() - 1);
const events = calendar.getEvents(oneDayAgo, new Date(Date.now() + 1000 * 60 * 60 * 24 * 365));
for (const event of events) {
if (event.getDateCreated() > latestCreationTime) {
latestCreationTime = event.getDateCreated();
latestEvent = event;
}
}
// もし最新の予定が見つからなかった場合は、ここで処理を終了する
if (latestEvent === null) {
return;
}
// ★★★ 追加:本当に新しい予定かを確認する処理 ★★★
// これが無いと、新しい予定追加の度に、24時間以内に作成された予定の通知がきちゃう。
const now = new Date();
const creationTime = latestEvent.getDateCreated();
const diffSeconds = (now.getTime() - creationTime.getTime()) / 1000;
// 作成されてから60秒以上経っている場合は、編集・削除とみなし通知しない
if (diffSeconds > 60) {
return;
}
// 3. 予定の詳細情報を取得する
const who = latestEvent.getCreators().join(', ');
const title = latestEvent.getTitle();
const startTime = new Date(latestEvent.getStartTime()).toLocaleString('ja-JP');
const endTime = new Date(latestEvent.getEndTime()).toLocaleString('ja-JP');
// 4. Chatに送信するメッセージを組み立てる(太字の記号を * に修正)
const messageText = `新しい予定が追加されました
---
*作成者:* ${who}
*タイトル:* ${title}
*日時:* ${startTime} - ${endTime}`;
const message = {
"text": messageText
};
const options = {
"method": "post",
"contentType": "application/json; charset=UTF-8",
"payload": JSON.stringify(message)
};
UrlFetchApp.fetch(webhookUrl, options);
}
コードの詳細な解説
-
function sendCalendarNotification(e)
- カレンダーのトリガーが動いた時に呼び出される関数。引数
e
にカレンダーIDとかの情報が入ってくる。
- カレンダーのトリガーが動いた時に呼び出される関数。引数
-
CalendarApp.getCalendarById(e.calendarId)
- 引数
e
からカレンダーIDを引っこ抜いて、操作するカレンダーを特定。
- 引数
-
if (diffSeconds > 60)
- このスクリプトの肝。イベント作成日時と今を比べて、60秒以上経ってたら「あ、これ編集か削除だな」と判断して通知を送らずに処理を終わらせる。
-
latestEvent.get...()
-
getCreators()
(作成者)とかgetTitle()
(タイトル)とか、CalendarEvent
オブジェクトが持ってるメソッドで予定の詳細を引っこ抜いてくる。
-
-
UrlFetchApp.fetch(webhookUrl, options)
- 整形したメッセージを、指定したWebhook URLへぶん投げる命令。これでChatにメッセージが投稿されるのね~を確認した。
実行方法
- Chatの準備: 通知用のスペース作って、スペース名▼メニューから「アプリと統合」→「Webhookを追加」で、Webhook URLをコピー。
- GASプロジェクト作成: Google Apps Script で新規プロジェクト作成。
-
コード貼り付け: 上の「コード全文」をエディタに貼って、
webhookUrl
の変数を自分のURLに書き換える。 -
トリガー設定: 左メニューの「トリガー」⏰から設定を追加。
-
実行する関数:
sendCalendarNotification
-
イベントのソース:
カレンダーから
-
イベントの種類:
予定の更新時(カレンダーの更新)
-
実行する関数:
- 権限の承認: 初回保存時とかに権限の承認を求められるんで、画面の指示に従って許可。
- テスト: 自分のカレンダーに新しい予定を追加して、Chatに通知が来たら成功。
まとめ
正直、「60秒以内に作成された予定以外破棄するなら、最初に24時間内の予定チェックいらんやん」っておもわれるとおもうんですが、まあそうなんですよね。
今後の拡張予定として、
- 編集・新規作成のメッセージを分ける。
が、あるので、一旦24時間以内のイベントチェックを残しておかないと..って感じです。
その他は、
-
定期リマインダー機能を追加
- 毎日、朝にその日のサマリー、夜に明日のサマリーを通知することで、予定の見落としを防ぐ。
- イベントの定刻前にリマインド通知
-
他の人にも提供できるように、上記の”定刻”の設定をコードから切り離す。
- 人によってサマリーほしい時間違うだろうから...
- ライブラリ化して別ファイル管理
-
複数人対応への拡張
- 自分が他の人のカレンダーに予定を入れた際に、相手に通知を送れるようにする。
- 「メールアドレス」と「相手のWebhook URL」を管理するスプレッドシートとの連携で多分行ける。
-
Webhookのやり方からBotに変更を目指します。
こんなロードマップの想定。
GAS、めちゃくちゃ強力。これを使えばGoogle Workspaceの自動化はだいたい何でもできるんじゃないか、と。だいぶ深堀りできた。
まだまだ遊べそうです。