Last active
November 14, 2022 03:21
-
-
Save toricls/2979154acaf71bf6206c3ec1cac2c092 to your computer and use it in GitHub Desktop.
A GAS (Google Apps Script) script for automated color labeling for Google (Workspace) Calendar
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ======================================================================== | |
// gcal-labeler.gs v1.2 | |
// | |
// See https://kaminashi-developer.hatenablog.jp/entry/2022/11/14/automated-labeling-for-google-calendar-using-google-apps-script for details. | |
// ======================================================================== | |
// ======================================================================== | |
// はじめてこのスクリプトを GAS に突っ込んでセットアップする際に使うパラメーター。 | |
// 詳細は↑の記事をご覧ください。 | |
// ======================================================================== | |
const ENABLE_PREPARATION_MODE = true; | |
// ======================================================================== | |
// ここの ID は人によって違うはずなので、各自で値を変更します | |
// 詳細は↑の記事をご覧ください。 | |
// ======================================================================== | |
const colorIdExternalEvent = '2'; | |
const colorIdInPersonEvent = '6'; | |
// ======================================================================== | |
// 複数のカレンダー作ってなければこのままでヨシ | |
// ======================================================================== | |
const calendarId = 'primary'; | |
// ======================================================================== | |
// 社内の予定とみなす参加者メアドのドメイン | |
// ======================================================================== | |
const internalDomains = [ | |
'@kaminashi.com', | |
]; | |
// ======================================================================== | |
// いかにも外部の人がいそうな予定のタイトル | |
// ======================================================================== | |
const externalishSummaries = [ | |
'面接', | |
'面談', | |
] | |
// ======================================================================== | |
// 外部の人がいそうな予定かどうかを判定する | |
// ======================================================================== | |
function couldBeExternal(event) { | |
return hasExternalAttendees(event) || hasExternalishSummary(event) | |
} | |
// 社外のドメインメアドを持つ人が参加者にいるか | |
function hasExternalAttendees(event) { | |
if (!event.attendees) return false; | |
// 全員が内部ドメインを持っているかどうかを確認 | |
return !event.attendees.every((attendee) => { | |
if (!attendee.email) return true; // メアドを持たない謎の参加者はチェックしない | |
return internalDomains.some((internalDomain) => { | |
return attendee.email.indexOf(internalDomain) > -1; | |
}) | |
}); | |
} | |
// タイトルからして外部の人がいそうな予定 | |
function hasExternalishSummary(event) { | |
var externalish = []; | |
if (event.summary) { | |
externalish = externalishSummaries.filter((externalishSummary) => { | |
return event.summary.indexOf(externalishSummary) > -1; | |
}); | |
} | |
return externalish.length > 0; | |
} | |
// ======================================================================== | |
// いかにも外出感を醸し出しているタイトル (カスタマイズ可) | |
// ======================================================================== | |
const inPersonishSummaries = [ | |
'出社', | |
'外部会場', | |
'オフサイト', | |
'飲み', | |
'展示会', | |
'オフィス', | |
] | |
// ======================================================================== | |
// オフィスに出社したり外出したりする予定っぽいやつかどうかを判定する | |
// ======================================================================== | |
function isInPerson(event) { | |
var inPersonish = []; | |
if (event.summary) { | |
inPersonish = inPersonishSummaries.filter(function(inpersonishSummary){ | |
return event.summary.indexOf(inpersonishSummary) > -1; | |
}); | |
} | |
return inPersonish.length > 0; | |
} | |
// ======================================================================== | |
// ここがエントリポイント | |
// ======================================================================== | |
function main() { | |
setLabelsOntoUpcomingEvents(); | |
setLabelsOntoUpcomingRecursiveEvents(); | |
} | |
// ======================================================================== | |
// 直近30件くらいの Standalone な予定(繰り返しじゃないやつ)を取得して | |
// 外部っぽい予定の場合はラベルを設定してあげる | |
// ======================================================================== | |
function setLabelsOntoUpcomingEvents() { | |
const optionalArgs = { | |
timeMin: (new Date()).toISOString(), | |
showDeleted: false, | |
singleEvents: true, | |
maxResults: 30, | |
orderBy: 'startTime' | |
}; | |
try { | |
const events = listEvents(optionalArgs); | |
if (events.length === 0) { | |
Logger.log('No upcoming events found'); | |
return; | |
} | |
setLabelsOntoEvents(events); | |
} catch (err) { | |
Logger.log('Failed with error %s', err.message); | |
} | |
} | |
// ======================================================================== | |
// 直近10件くらいの繰り返しっぽい予定を取得して | |
// 外出しなきゃいけないっぽいやつにはラベルを設定してあげる | |
// ======================================================================== | |
function setLabelsOntoUpcomingRecursiveEvents() { | |
const optionalArgs = { | |
timeMin: (new Date()).toISOString(), | |
showDeleted: false, | |
singleEvents: false, | |
maxResults: 10, | |
}; | |
try { | |
const events = listEvents(optionalArgs); | |
if (events.length === 0) { | |
Logger.log('No upcoming recursive events found'); | |
return; | |
} | |
setLabelsOntoEvents(events); | |
} catch (err) { | |
Logger.log('Failed with error %s', err.message); | |
} | |
} | |
// ======================================================================== | |
// 以下は G カレンダーの API 叩いてる | |
// ======================================================================== | |
function listEvents(param) { | |
try { | |
const response = Calendar.Events.list(calendarId, param); | |
return response.items; | |
} catch (err) { | |
Logger.log('Event listing failed with error %s', err.message); | |
return []; | |
} | |
} | |
function setLabelsOntoEvents(events) { | |
for (const event of events) { | |
if (ENABLE_PREPARATION_MODE === true) { | |
// Just prints colorId and title | |
Logger.log("ColorId: %s, Summary: %s", event.colorId, event.summary) | |
continue; | |
} | |
if (isInPerson(event)) { | |
markAsInPerson(event); | |
} | |
if (couldBeExternal(event)) { | |
markAsExternal(event); | |
} | |
} | |
} | |
function markAsExternal(event) { | |
try { | |
if (event.colorId) { | |
Logger.log('Looks external but skipped to mark since it already has colorId: %s - %s', event.colorId, event.summary); | |
return; | |
} | |
event.colorId = colorIdExternalEvent; | |
Calendar.Events.update(event, calendarId, event.id); | |
Logger.log('Marked as external: %s', event.summary); | |
} catch (err) { | |
// we don't throw the error and just log it here to keep running the script | |
Logger.log('Event update failed with error %s', err.message); | |
} | |
} | |
function markAsInPerson(event) { | |
try { | |
if (event.colorId) { | |
Logger.log('Looks in-person but skipped to mark since it already has colorId: %s - %s', event.colorId, event.summary); | |
return; | |
} | |
event.colorId = colorIdInPersonEvent; | |
Calendar.Events.update(event, calendarId, event.id); | |
Logger.log('Marked as in-person: %s', event.summary); | |
} catch (err) { | |
// we don't throw the error and just log it here to keep running the script | |
Logger.log('Event update failed with error %s', err.message); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment