【GAS】予定をMS Teamsのチャンネルに流してくれるBotを作ってみた

JavaScriptのイベントに参加する前に作っていたのだけど、時間がなくてブログにはまとめていなかったので、改めてまとめた。この出来事もあってJSのイベントに参加しようと思い立った。

引き継いで思った「本当に共有カレンダー1つで可能?」

数週間前に同じチームの同僚から「他のチームからもリクエストあるし、オフィスの全員が予定をチェックできるよう共通のカレンダーを作ろうと思う」という内容のメールをもらった。これは、私も何度も挑戦していたことだったので、「ぜひやってみたら良いよ。但し、以下の点は理解が誤っていると思うので、ITベンダに教えてもらうなどして認識を改めといてね」という返信をした。

その後、彼女は2週間病欠。他のチームからのリクエストということもあって、さすがに早く対応する必要あるんだろう、と思って、代わりに対応しておくことにした。

求められていること

  • PCだけでなく携帯からもアクセスできること
  • ボスを含めたスタッフの外出予定がわかること

私が入社したころ、うちの会社にはExchangeがなく、残念ながらスタッフがカレンダーの使い方を知らなかった。Office 365への移行を機に「カレンダー利用しようよ」と言い続けたものの浸透せず、という経緯がある。

同僚から相談メールをもらったときには、「彼女が管理するって言ってるし、やりたいようにしてもらえばいっか」と思っていたけど、代理で対応することになって考えたときに、彼女のプランで運用可能なのかやや疑問な部分が出てきた。(もしかすると大丈夫なのかもしれないけど。)

全員ではなくても、例えば5人だけでも同じ日・同時刻に休暇や外出予定が入ってしまったら、非常に見にくい。

「じゃ、カレンダーを分ければいいじゃん」という話になるのだけれど、そうすると、リクエストされたこととは少し異なる感じ。多分、いろんなカレンダーを使い分けるなんてことできない。

カレンダーを分けても、全体スケジュールを把握する方法は?

「もういい加減、Outlookの使い方とか勉強したら?」と思うけど、期待するのは無駄。私に課されたミッションは、他の人にとってできるだけ簡単な方法で全体の予定を通知すること。

多分、リクエストしてきたチームが求めているのは、こういうことなんだと思う。

この方法なら、複数のカレンダーの内容を一回で通知可能。

実際に作ってみよう

基本的には、前述のサイトの方法でできあがる。 で、ここで少し欲張ってしまうところが私の悪いとこ。「どうせなら1ヶ月とか1週間の予定があった方が良くない?」とカスタマイズすることにしてみた。

使うカレンダー

  • 上司の予定
  • スタッフの外出・休暇予定
  • 本社・地域本社のイベント
  • 事務所のイベント
  • 〆切日(提出物など)
  • オフィスの休業日

そして作ったスクリプトが、以下。

//スケジュールアシスタントが今後7日間の予定を投稿してくれる
function GoogleCalToMSTeams() {
var list = '';
var calEvents = '';
var calendarInUse = [
// 使用するカレンダーのアドレスとそのラベリング
['xxxxxxxxxxxxxxxxxxxxxxx@group.calendar.google.com', '\n##■XXX\'s schedule\n'],
['xxxxxxxxxxxxxxxxxxxxxxx@group.calendar.google.com', '\n##■Out of office (staff)\n'],
['xxxxxxxxxxxxxxxxxxxxxxx@group.calendar.google.com', '\n##■XXX events\n'],
['xxxxxxxxxxxxxxxxxxxxxxx@group.calendar.google.com', '\n##■XXX Office events\n'],
['xxxxxxxxxxxxxxxxxxxxxxx@group.calendar.google.com', '\n##■Due dates\n'],
['xxxxxxxxxxxxxxxxxxxxxxx@group.calendar.google.com', '\n##■Office closing dates\n']
];
// 登録されたイベントをカレンダーごとにリストアップ
for (var i = 0; i < calendarInUse.length; i++) {
calEvents = listUpEvents(calendarInUse[i][0]);
if (calEvents) {
list += calendarInUse[i][1] + calEvents + '\n';
}
}
if (list) {
var payload = {
"text": "Hi, here's the latest schedule for the next 7 days. \n" + " \n" + list + " \n" + "\n###Abbreviations: \n" + "* BT = Business trip \n* OOO = Out of office",
}
postTeams(payload);
}
}
//1週間の予定をリストアップ
function listUpEvents(cal_id) {
var list = '';
var cal = CalendarApp.getCalendarById(cal_id);
var calEvents = '';
//1週間分の予定を確認
var now = new Date();
var end = new Date(now.getTime() + (7 * 24 * 60 * 60 * 1000)); // 1週間後
var events = cal.getEvents(now, end);
for (var i = 0; i < events.length; i++) {
if (events[i].isAllDayEvent()) {
var startDate = new Date(events[i].getStartTime());
var endDate = new Date(events[i].getEndTime() - (1 * 24 * 60 * 60 * 1000));
if ((endDate - startDate) / (24 * 60 * 60 * 1000) < 2) {
calEvents += Utilities.formatDate(events[i].getStartTime(), "GMT+0900", "dd MMM ");
} else {
calEvents += Utilities.formatDate(events[i].getStartTime(), "GMT+0900", "dd MMM ");
calEvents += Utilities.formatDate(endDate, "GMT+0900", "- dd MMM ");
}
} else {
calEvents += Utilities.formatDate(events[i].getStartTime(), "GMT+0900", "dd MMM HH:mm");
calEvents += Utilities.formatDate(events[i].getEndTime(), "GMT+0900", "-HH:mm ");
}
calEvents += events[i].getTitle();
list += calEvents + " \n";
}
return list;
}
//MS Teamsへポスト
function postTeams(payload) {
// POSTオプション
var options = {
"method": 'POST',
"payload": JSON.stringify(payload)
}
// アクセス先
var url = "XXXXXXXXXXXXXXXXXXXXX"; //WebHook URL
// POSTリクエスト
var response = UrlFetchApp.fetch(url, options);
// HTML結果を取得(引数のcharsetは設定したほうが良い)
var content = response.getContentText('UTF-8');
}

カスタマイズしていて、つまづいた点

地味にちょこちょこ悩んだ。以下が時間がかかった部分。

イベント終了日

出張など日をまたぐ予定の終了日がきちんと表示されない!何とかなったと思ったら、今度は1日長い。(4月1日〜2日までの予定が、4月1日〜3日までと表示されてしまう。)

もっとスマートな書き方があるような気がしているけど、現時点ではここまでで諦めた。

表示のされ方(改行されない)

Slackでは\nで改行できるのに、なぜかMS Teamsでは改行されなかった。Markdownが使えるから、スペース2つで何とかなるかなと試してみたけど、できない。「なんで???」と発狂寸前だったけど、よくよく考えたら、「Headingを設定したら自動的に改行されんじゃん」と思って##を付け足してあっさり解決。

メール通知版も作ってみた

ここまでのことで、オフィス内の予定共有は完了。ただ、上司の管下の事務所は他にもある。「せっかくだからこれを利用しちゃおう」と思い立つ。ただ、この場合、MS Teamに新たにチームを作って、関係のある人達を招待して、という手順が必要。

面倒くさい。(あまりにもチームが乱立しすぎちゃう。。。)

ということで、彼らがどれだけMS Teamを利用しているかもわからないので、メール通知ということにした。上司のスケジュールや〆切日など関係ありそうなスケジュールだけでいい。

//今後7日間の予定をメールで送る。
function GoogleCalToEmail() {
var list = "";
var s;
s = listupEvent("XXXXXXXXXXXXXXXXX@group.calendar.google.com"); //Boss' schedule
if (s != "") list += " \n##■XXX's schedule \n" + s + " \n";
s = listupEvent("XXXXXXXXXXXXXXXXX@group.calendar.google.com"); //XXX Group's Events
if (s != "") list += " \n##■XXX events \n" + s + " \n";
s = listupEvent("XXXXXXXXXXXXXXXXX@group.calendar.google.com"); //Due dates
if (s != "") list += " \n##■Due dates \n" + s+ " \n";
s = listupEvent("XXXXXXXXXXXXXXXXX@group.calendar.google.com"); //Office closing dates
if (s != "") list += " \n##■Office closing dates \n" + s + " \n";
Logger.log(list);
if (list != "") {
var payload = "Hi, here's the latest schedule for the next 7 days. \n" + " \n" + list + " \n" + "\n###Abbreviations: \n" + "* BT = Business trip \n* OOO = Out of office";
EmailToStaff (payload)
}
}
//1週間の予定をリストアップ
function listupEvent(cal_id)
{
var list = "";
var cal = CalendarApp.getCalendarById(cal_id);
//1週間分の予定を確認
var now = new Date();
var end = new Date(now.getTime() + (7 * 24 * 60 * 60 * 1000));// 1週間後
var events = cal.getEvents(now, end);
for(var i=0; i < events.length; i++){
s = "";
if (events[i].isAllDayEvent()) {
var startDate = new Date(events[i].getStartTime());
var endDate = new Date(events[i].getEndTime()-(1 * 24 * 60 * 60 * 1000));
if ((endDate-startDate)/(24 * 60 * 60 * 1000) <2) {
s += Utilities.formatDate(events[i].getStartTime(),"GMT+0900","dd MMM ");
} else {
s += Utilities.formatDate(events[i].getStartTime(),"GMT+0900","dd MMM ");
s += Utilities.formatDate(endDate, "GMT+0900","- dd MMM ");
}
} else {
s += Utilities.formatDate(events[i].getStartTime(),"GMT+0900","dd MMM HH:mm");
s += Utilities.formatDate(events[i].getEndTime(), "GMT+0900","-HH:mm ");
}
s += events[i].getTitle();
Logger.log(s);
list += s + " \n";
}
return list;
}
//スタッフへメール連絡
function EmailToStaff (payload){
var recipient = 'XXXXXXXXX'; //宛先のメールアドレス。複数ある場合は【;】で区切る。
var copiedRecipient = 'XXXXXXXXX'; //必要に応じて
var sender = 'XXXXXXXXX'; //送信アドレスとして使いたいメールアドレスを設定。
var senderName = 'XXX XXX'; //必要に応じて
var now = new Date();
var end = new Date(now.getTime() + (7 * 24 * 60 * 60 * 1000)); //1週間後
now = Utilities.formatDate(now, "GMT+0900","dd MMM")
end = Utilities.formatDate(end, "GMT+0900","dd MMM")
var subject = 'XXX schedule from ' + now + ' - ' + end;
var body = payload;
GmailApp.sendEmail(recipient,
subject,
body,
{
bcc:'',
cc: copiedRecipient,
from: sender,
name: senderName
});
}

ちなみにメールはテキスト形式のメール。これで事足りるので、これ以上のことはしていない。

送信はOutlookのアドレスから

Gmailから送るのだけれど、受信者にもそれが表示されると今後誤ってGmailにメールを送ってくるような事態にもなりかねない。メールのやり取りはOutlookで統一しておきたいので、Outlookから送ったことにする。

Gmail側で、Outlookメールの設定が必要。

これで、Outlookで使用しているアドレスを送信者に設定すればよい。

今後改善が必要な部分

とりあえず、私の分かる範囲でみんなの予定を入れて作ったので、長期的な運用は、予定をポンポンと入力していってもらう必要がある。 Google CalendarとOutlookのカレンダーの同期が課題というよりは、入力の仕組みをシンプルにすることが重要。(Google Calendarへの登録は、MS Flowを使えばいい。)

このあたりも、ChatBotの仕組みを利用できないかと考えている。