Googleカレンダーの招待を自動化したら地味に人生が楽になった話¶
はじめに¶
ハニーとの予定をGoogleカレンダーで共有することにした。
ただ、予定を入れるときに共有を忘れることが多い。後から「あ、共有してなかった」と気づいて、慌てて招待する。これの繰り返し。というか直前に予定を行って怒られている。
「自動化しなければ」と思い立って、GASで毎日4時に自動でハニーを招待する仕組みを作った。派手な機能じゃないけど、日常のストレスは確実に減った。
何を作ったか¶
毎日4時に以下の処理を自動実行:
今日以降の予定をチェック
自分が作った予定だけを対象
タイトル末尾に
[skip]がついていない予定に限定指定したメールアドレスをゲストとして追加
実装のポイント¶
### 1. isOwnedByMe() で自分の予定だけを対象
招待された予定には自動で招待を追加しない。これが重要。
if (!event.isOwnedByMe()) {
continue;
}
### 2. [skip] で制御
タイトルに [skip] をつけるだけで除外。シンプルな運用ルール。
if (title.endsWith(skipSuffix)) {
continue;
}
### 3. 既に招待済みならスキップ
同じ人を何度も招待しない。
const guestList = event.getGuestList().map(g => g.getEmail().toLowerCase());
if (guestList.includes(guestEmail.toLowerCase())) {
continue;
}
### 4. 主催者の参加状態もセット
ゲストを追加する際、主催者(自分)のステータスも「参加(YES)」に変更する。 これをしないと、カレンダー上では「未承諾」のままになってしまう。
if (adminEmail) {
event.addGuest(adminEmail);
event.setMyStatus(CalendarApp.GuestStatus.YES)
}
実装全体¶
TypeScriptで書いて、claspでローカル管理。
function setConfig(): void {
const props = PropertiesService.getScriptProperties();
props.setProperties({
ADMIN_EMAIL: "admin@sample.com",
TARGET_GUEST_EMAIL: "info@sample.com",
CALENDAR_ID: "primary",
SKIP_SUFFIX: "[skip]"
});
}
function inviteGuestToUpcomingEvents(): void {
const props = PropertiesService.getScriptProperties();
const guestEmail = props.getProperty("TARGET_GUEST_EMAIL");
const adminEmail = props.getProperty("ADMIN_EMAIL");
const calendarId = props.getProperty("CALENDAR_ID") || "primary";
const skipSuffix = props.getProperty("SKIP_SUFFIX") || "[skip]";
if (!guestEmail) {
throw new Error("TARGET_GUEST_EMAIL が未設定です。setConfig() を実行してください。");
}
const calendar =
calendarId === "primary"
? CalendarApp.getDefaultCalendar()
: CalendarApp.getCalendarById(calendarId);
if (!calendar) {
throw new Error(`カレンダーが見つかりません: ${calendarId}`);
}
const now = new Date();
const oneYearLater = new Date(now);
oneYearLater.setFullYear(oneYearLater.getFullYear() + 1);
const events = calendar.getEvents(now, oneYearLater);
for (const event of events) {
const title = (event.getTitle() || "").trim();
if (title.endsWith(skipSuffix) || !event.isOwnedByMe()) {
continue;
}
const guestList = event.getGuestList().map(g => g.getEmail().toLowerCase());
if (guestList.includes(guestEmail.toLowerCase())) {
continue;
}
try {
if (adminEmail) {
event.addGuest(adminEmail);
event.setMyStatus(CalendarApp.GuestStatus.YES)
}
event.addGuest(guestEmail);
console.log(`追加: ${title} -> ${guestEmail}`);
} catch (e) {
console.error(`追加失敗: ${title} / ${e}`);
}
}
}
function createDailyTrigger(): void {
const functionName = "inviteGuestToUpcomingEvents";
const triggers = ScriptApp.getProjectTriggers();
for (const trigger of triggers) {
if (trigger.getHandlerFunction() === functionName) {
console.log("既存トリガーあり");
return;
}
}
ScriptApp.newTrigger(functionName)
.timeBased()
.everyDays(1)
.atHour(4)
.create();
console.log("毎日4時のトリガーを作成しました");
}
function updateGuestEmail(newEmail: string): void {
PropertiesService.getScriptProperties().setProperty("TARGET_GUEST_EMAIL", newEmail);
}
function updateAdminEmail(newEmail: string): void {
PropertiesService.getScriptProperties().setProperty("ADMIN_EMAIL", newEmail);
}
セットアップ手順¶
### 1. clasp をインストール
npm install -g @google/clasp
### 2. ログイン
clasp login
### 3. 既存GASをpull(またはclone)
スクリプトIDはURLから取得:
clasp clone <SCRIPT_ID>
### 4. 初期設定を実行
Google Apps Script エディタで以下を実行:
setConfig()
### 5. トリガーを作成
createDailyTrigger()
失敗談¶
最初は全予定に招待を飛ばしていて、招待されたミーティングにも勝手に人が追加されるという事故が発生。
isOwnedByMe() チェックを入れてからは安定した。
運用ルール¶
タイトル末尾に
[skip]をつけると除外される (家賃の支払い予定など)自分が作った予定だけが対象
既に招待済みなら何もしない
メールアドレスは
updateGuestEmail()で変更可能
ちょっとした工夫で生活が楽になる¶
こういう「ちょっとした面倒」を仕組みで消していくのは結構好きで、派手な機能じゃないけど、日常のストレスは確実に減る。
こういうのを積み上げると、生活の解像度が少し上がる気がする。
次のステップ¶
Slackへの通知追加
複数メールアドレスへの対応
カレンダーごとの設定分岐