TypeScript + clasp で Google Calender API を操作するGoogle Apps Script(以下、gas)を作成する。
TypeScript + clasp の環境構築については過去の記事を参照。
gasプロジェクトを作成する
まずは対象となるgasプロジェクトを作成及びTypeScript用の環境設定を行う
# 任意のディレクトリへ移動する
cd /path/to/project
# Google Calendar API を操作するプロジェクトを作成する
mkdir calendar-api-sample
# gasプロジェクトを作成する
clasp create --type standalone
# gas用のTypeScript定義をインストール
npm i -S @types/google-apps-script
ここまでのフォルダ構成
$ tree -a -L 1
.
├── .clasp.json
├── appsscript.json
├── node_modules
├── package-lock.json
└── package.json
プロジェクトにGoogle Calendar APIを追加する
GCPのプロジェクトIDを使わずにclasp
上で追加する方法がわからなかったので、gasのコンソール上で追加した後、pullをする。
ローカルプロジェクトのアップデート(clasp pull
)
clasp pull
Warning: files in subfolder are not accounted for unless you set a '/workspaces/gas-sample/calendar-api-sample/.claspignore' file.
Cloned 1 file.
└─ /workspaces/gas-sample/calendar-api-sample/appsscript.json
appscript.json
にCalenderへのdependenciesが追加されていることを確認する。
※つまりはappscript.json
のdependnciesに以下の内容を手書きすればgasのコンソール上じゃなくてもできるようです。
"dependencies": {
"enabledAdvancedServices": [
{
"userSymbol": "Calendar",
"version": "v3",
"serviceId": "calendar"
}
]
}
Google Caldendar APIを使った実装を試してみる
Google Calendarの変更をトリガーにしたアプリケーションを作ってみます。
機能の流れとしては以下を想定しています。
- Google Calendarからの変更通知を受ける
- 変更されたカレンダーのイベントを取得する
- 変更されたカレンダーのイベントの内容をGoogle Chatに送信する
Google Calendarからの通知を受け取る
まずは変更を受け取るための関数を用意します。
Google Calendarからどういったイベントが通知されるのか、公式ドキュメントから確認します。
上記より、Google CalendarからEventが発生した場合、関数に渡されるEventObjectは以下のプロパティが設定されていることがわかります。
property name | description |
---|---|
authMode | ScriptApp.AuthMode の値。 例: FULL
|
calendarId | イベントの更新が発生したカレンダーのID。 例: susan@example.com |
triggerUid | このイベントを生成したトリガーのID。 例: 4034124084959907503
|
つまりEventからcalendarId
を取得したい場合は以下となります。
interface CalendarUpdatedEvnet {
authMode: GoogleAppsScript.Script.AuthMode;
calendarId: string;
triggerUid: string;
}
function onUpdatedEvent(e: CalendarUpdatedEvnet) {
console.log(`calendarId: ${e.calendarId}`);
}
上記のソースをclasp push
でgasプロジェクトへUploadしましょう。
// Compiled using undefined undefined (TypeScript 4.5.5)
"use strict";
function onUpdatedEvent(e) {
const calendarId = e.calendarId;
console.log(`calendarId: ${calendarId}`);
}
(interface
はjsへトランスコンパイルされると消えてしまう悲しいさだめをもったオブジェクトです)
トリガーを作成し、先ほど作ったonUpdatedEvent
を実行する関数として指定します。
先ほどトリガーのソースとして指定したカレンダーを更新すると、無事トリガーが動作し、ログが表示されました。
変更されたカレンダーの情報を取得する
公式ドキュメントを参考に変更されたカレンダーのイベントを取得するスクリプトを書いていきます。
interface CalendarUpdatedEvnet {
authMode: GoogleAppsScript.Script.AuthMode;
calendarId: string;
triggerUid: string;
}
function onUpdatedEvent(e: CalendarUpdatedEvnet) {
console.log(`calendarId: ${e.calendarId}`);
getUpdatedEvents(e.calendarId).forEach((event)=>{
console.log(`title:${event.summary}`)
})
}
/**
* 本機能で使用するOptionのみを抜粋している。
* Optionの全定義についてはlinkを参照。
*
* @link https://developers.google.com/calendar/api/v3/reference/events/list#parameters
*/
class CalendarQueryOptions {
maxResults?: number;
syncToken?: string;
timeMin?: string;
}
/**
* 指定されたCalendarIdに紐付くイベントを前回取得した時から更新があったイベントのみ取得する。
* 過去に一度もイベントを取得していない場合、過去30日分を取得する。
*
* @param {string} calendarId カレンダーを一意に識別するID
* @returns {GoogleAppsScript.Calendar.Schema.Event[]} イベントの一覧(最大100件)
*/
function getUpdatedEvents(
calendarId: string,
): GoogleAppsScript.Calendar.Schema.Event[] {
const properties = PropertiesService.getUserProperties();
const key = `syncToken:${calendarId}`;
const syncToken = properties.getProperty(key);
let options: CalendarQueryOptions = { maxResults: 100 };
if (syncToken) {
options = { ...options, syncToken: syncToken };
} else {
options = { ...options, timeMin: getRelativeDate(-30, 0).toISOString() };
}
const events = Calendar.Events?.list(calendarId, options);
if (events?.nextSyncToken) {
properties.setProperty(key, events?.nextSyncToken);
}
return events?.items ? events.items : [];
}
/**
* Helper function to get a new Date object relative to the current date.
* @param {number} daysOffset The number of days in the future for the new date.
* @param {number} hour The hour of the day for the new date, in the time zone
* of the script.
* @return {Date} The new date.
*/
function getRelativeDate(daysOffset: number, hour: number) {
var date = new Date();
date.setDate(date.getDate() + daysOffset);
date.setHours(hour);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date;
}
書き終わったら再度clasp push
をします。
以下は実際にアップロードされたソースです。
// Compiled using undefined undefined (TypeScript 4.5.5)
"use strict";
function onUpdatedEvent(e) {
console.log(`calendarId: ${e.calendarId}`);
getUpdatedEvents(e.calendarId).forEach((event) => {
console.log(`title:${event.summary}`);
});
}
/**
* 本機能で使用するOptionのみを抜粋している。
* Optionの全定義についてはlinkを参照。
*
* @link https://developers.google.com/calendar/api/v3/reference/events/list#parameters
*/
class CalendarQueryOptions {
}
/**
* 指定されたCalendarIdに紐付くイベントを前回取得した時から更新があったイベントのみ取得する。
* 過去に一度もイベントを取得していない場合、過去30日分を取得する。
*
* @param {string} calendarId カレンダーを一意に識別するID
* @returns {GoogleAppsScript.Calendar.Schema.Event[]} イベントの一覧(最大100件)
*/
function getUpdatedEvents(calendarId) {
var _a;
const properties = PropertiesService.getUserProperties();
const key = `syncToken:${calendarId}`;
const syncToken = properties.getProperty(key);
let options = { maxResults: 100 };
if (syncToken) {
options = Object.assign(Object.assign({}, options), { syncToken: syncToken });
}
else {
options = Object.assign(Object.assign({}, options), { timeMin: getRelativeDate(-30, 0).toISOString() });
}
const events = (_a = Calendar.Events) === null || _a === void 0 ? void 0 : _a.list(calendarId, options);
if (events === null || events === void 0 ? void 0 : events.nextSyncToken) {
properties.setProperty(key, events === null || events === void 0 ? void 0 : events.nextSyncToken);
}
return (events === null || events === void 0 ? void 0 : events.items) ? events.items : [];
}
/**
* Helper function to get a new Date object relative to the current date.
* @param {number} daysOffset The number of days in the future for the new date.
* @param {number} hour The hour of the day for the new date, in the time zone
* of the script.
* @return {Date} The new date.
*/
function getRelativeDate(daysOffset, hour) {
var date = new Date();
date.setDate(date.getDate() + daysOffset);
date.setHours(hour);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date;
}
トリガーが設定されているCalendarを更新し、イベントがロギングされることを確認します。
無事カレンダーのタイトルが表示されました。
変更されたカレンダーのイベントの内容をGoogle Chatに送信する
最後は、完全に蛇足ですが、取得したイベントをGoogle Chatに送信します。
Google ChatはGoogle Workspacesがないと使用できない(はず)です。
公式サイトを参考にコードを書いていきます。
gasにおけるHTTPリクエストについてはこちらを使用するようです。
interface CalendarUpdatedEvnet {
authMode: GoogleAppsScript.Script.AuthMode;
calendarId: string;
triggerUid: string;
}
const WEBHOOK_URL = "<WEBHOOK_URL>"
function onUpdatedEvent(e: CalendarUpdatedEvnet) {
console.log(`calendarId: ${e.calendarId}`);
getUpdatedEvents(e.calendarId).forEach((event) => {
let message: string;
if (event.status === "cancelled") {
message = `
イベントが削除されました。
`
} else {
message = `
イベントが更新されました。\n
*${event.summary}*
==================================================\n
開始日時:${event.start?.dateTime}\n
終了日時:${event.end?.dateTime}\n
説明 :${event.description}\n
<${event.htmlLink}|LINK>
`
}
sendMessageToChat(WEBHOOK_URL, message)
});
}
/**
* 本機能で使用するOptionのみを抜粋している。
* Optionの全定義についてはlinkを参照。
*
* @link https://developers.google.com/calendar/api/v3/reference/events/list#parameters
*/
class CalendarQueryOptions {
maxResults?: number;
syncToken?: string;
timeMin?: string;
}
/**
* 指定されたCalendarIdに紐付くイベントを前回取得した時から更新があったイベントのみ取得する。
* 過去に一度もイベントを取得していない場合、過去30日分を取得する。
*
* @link https://developers.google.com/calendar/api/v3/reference/events#resource
*
* @param {string} calendarId カレンダーを一意に識別するID
* @returns {GoogleAppsScript.Calendar.Schema.Event[]} イベントの一覧(最大100件)
*/
function getUpdatedEvents(
calendarId: string
): GoogleAppsScript.Calendar.Schema.Event[] {
const properties = PropertiesService.getUserProperties();
const key = `syncToken:${calendarId}`;
const syncToken = properties.getProperty(key);
let options: CalendarQueryOptions = { maxResults: 100 };
if (syncToken) {
options = { ...options, syncToken: syncToken };
} else {
options = { ...options, timeMin: getRelativeDate(-30, 0).toISOString() };
}
const events = Calendar.Events?.list(calendarId, options);
if (events?.nextSyncToken) {
properties.setProperty(key, events?.nextSyncToken);
}
return events?.items ? events.items : [];
}
/**
* Helper function to get a new Date object relative to the current date.
* @param {number} daysOffset The number of days in the future for the new date.
* @param {number} hour The hour of the day for the new date, in the time zone
* of the script.
* @return {Date} The new date.
*/
function getRelativeDate(daysOffset: number, hour: number) {
var date = new Date();
date.setDate(date.getDate() + daysOffset);
date.setHours(hour);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date;
}
/**
* 引数として与えられたWebhookURLを元にGoogle Chatへメッセージを送付する
*
* @link https://developers.google.com/chat/api/guides/message-formats/basic
* @param webHookURL Web Hook URL
* @param message チャットに送付するメッセージ
*/
function sendMessageToChat(webHookURL: string, message: string): void {
const params: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = {
payload: JSON.stringify({
text: message
}),
method: "post",
contentType: "application/json",
};
const response = UrlFetchApp.fetch(webHookURL, params);
console.log(`repsonse:${response}`);
}
こちらもclasp push
した後、トリガーが紐づくカレンダーを更新することで、Google Chatに通知されるようになります。
以上です。