はじめに
- Google Calendarの内容をSlackに通知したい。
- けどSlackにその連携を追加する余裕がない・・・。
- (Slackは無料版だとAppsやIntegrationは10種類までなので・・・。)
となりました。
最初は直接Google Calendar APIを叩くことも考えましたが、10分前に通知なんてことができないので諦めようかと思っていたところ、チームをもう一つ作ってOutgoing WebHooksを用いて普段使っているSlackに通知できないか?と考えました。
そんなことしたい人が他にいるかわかりませんが、実現させるまでを私がつまづいた点を中心に記事にしてみます。
こういう人に役立つかも?
- 別チームで連携したGoogle Calendarの内容をSlackに通知したい
- とあるチャンネルのメッセージを逐次処理したい
- 連携したGoogle Calendarが投稿する内容を知りたい
ポイント1 SlackのOutgoing WebHooks受信メソッドはdoPostが正解
Slack BotをGASでいい感じで書くためのライブラリを作った - Qiita
にdoGet
とあったのでそれで作っていたのですが、全く反応を見せませんでした。
経験者がいたので聞いたらdoPost
でやってました。
記事が書かれた当時との仕様変更の線も否定できませんが、私はその人に聞くまで数時間の間悩んでしまいました。
ポイント2 連携するときカレンダーごとにチャンネルを作った方が良い
ポイント3 AttachmentsはOutgoingされない
Slackに投稿されたカレンダーのイベント(作成・変更など設定したもの)がOutgoingされると次のような内容が得られます。
*こちらはテスト時の確認用にJSON.stringify()したもを加工しています、実際はJSのオブジェクトです
{
"parameter":{
"channel_name":"チャンネル名",
"bot_name":"",
"user_id":"USLACKBOT",
"user_name":"slackbot",
"service_id":"サービスID",
"team_domain":"チーム名",
"team_id":"チームID",
"text":"",
"channel_id":"C1RKZDVGB",
"bot_id":"B1RMK8B6Z",
"token":"DvFLGzgSn6pVz7NF4L9pYgro",
"timestamp":"1468578010.000070"
},
"contextPath":"",
"contentLength":231,
"queryString":null,
"parameters":{
"channel_name":["チャンネル名"],
"bot_name":[""],
"user_id":["USLACKBOT"],
"user_name":["slackbot"],
"service_id":["サービスID"],
"team_domain":["チーム名"],
"team_id":["チームID"],
"text":[""],
"channel_id":["チャンネルID"],
"bot_id":["BotID"],
"token":["Outgoing WebHooksのトークン"],
"timestamp":["1468578010.000070"]
},
"postData":{
"type":"application/x-www-form-urlencoded",
"length":231,
"contents":"token=Outgoing WebHooksのトークン&team_id=チームID&team_domain=チーム名&service_id=サービスID&channel_id=チャンネルID&channel_name=チャンネル名×tamp=1468578010.000070&user_id=USLACKBOT&user_name=slackbot&text=&bot_id=BotID&bot_name=",
"name":"postData"
}
}
見て分かりますでしょうか?
カレンダー連携すると投稿の名前=カレンダーの名前
みたいになっているのですが、それをOutgoingするとSlackBot
扱いになります。
とりあえず3つのカレンダーを1つのチャンネルと連携していたのですが、いざOutgoingされるとそれがどのカレンダーのものかわかりませんでした。
なので、カレンダーごとにチャンネルを分けたほうが良いです。
また、カレンダーに関するイベント(作成や更新など設定したもの)の情報が一切ありません。
なぜならあの情報はAttachmentであり、Outgoing WebHooksではAttachmentsは含まれない仕様です。
Please note that the content of message attachments will not be included in the outgoing POST data.
(適当訳:メッセージのattachmentsはOutgoing WebHooksで送信されるデータには含まれないので注意してください。)
参考:Outgoing Webhooks | Slack
なので、channel.historyを用いてタイムスタンプからメッセージを取得する必要があります。
ポイント4 channel.historyはoldestやlatestのメッセージは取得できない
ピンポイントのメッセージが欲しかったので最初タイムスタンプの指定はoldestもlatestも同じくOutgoing WebHooksで得られたタイムスタンプを指定していたのですが、それではメッセージが取得できませんでした。
channels.historyのテストページでテストしたところ、channel.historyはoldestやlatestのメッセージは取得できないということがわかりました。
つまり、ピンポイントのメッセージを取得するにそのタイムスタンプを前後にずらしたものを指定する必要があります。
oldestやlatestの計算コードはこんな感じです。
var timestamp = Number(e.parameter.timestamp)*1000000;
var oldest = String((timestamp - 1)/1000000);
if(oldest.length < 17){
oldest += "0";
}
var latest = String((timestamp + 1)/1000000);
if(latest.length < 17){
latest += "0";
}
足し算の前後で同じ数かけて割るのは計算誤差を防ぐためです。
桁数が重要らしく、必要に応じて0パディングをしないとピンポイントにならなかったり何も取得できなかったりします。
もしかしたら内部でも結局掛け算して小数を整数にしていたりするのかもしれません。
(でないと小数の最後に0つける意味が・・・タイムスタンプが数値ではなくて文字列扱いとか?)
ポイント5 スケジュールまとめは通常のものと形式が異なる
通常は先ほど書いたようにカレンダーイベントの内容はAttachmentにあってテキストにはありません。
ですが、スケジュールまとめでは(「There are 4 events today:」など)がテキストで投稿されます。
また、スケジュールの数だけAttachmentがあります。
ポイント6 サンプルデータでコードを試すと良い
Google Calendar・Outgoing WebHooks・GASの3種類が絡むウェブアプリケーションになるため、スプレッドシートをGASで操作する以上にデバッグが面倒な部分もあります。
まず更新をウェブアプリケーションに反映させるにはいちいちバージョンを更新する必要があります。
私はカレンダーイベントが無事Outgoingされるようになるまでも幾つもバージョンを重ねてしまいましたし、さらにメインチームに流す前に英語の変換もしていた分でも幾つもバージョンを重ねてしまいました。
最終的にバージョンは53ですw
それでも途中サンプルデータで関数のテストをした分があるので少々バージョン更新を抑えていると思いますが、だいたい本番検証していたりしょうもないミスしていたりでそんな数になりました。
まとめ
- SlackのOutgoing WebHooks受信メソッドはdoPostが正解
- 連携するときカレンダーごとにチャンネルを作った方が良い
- AttachmentsはOutgoingされない
- channel.historyはoldestやlatestのメッセージは取得できない
- スケジュールまとめは通常のものと形式が異なる
- サンプルデータでコードを試すと良い
最後のおまけ
英語変換のコードを置いておきます。
function getMessage(message) {
var text = message.text;
if(text == null){
return null;
}
text = text.replace(/There are \*(\d+)\* events today:/,"今日は予定が *$1* つあります。");
text = text.replace(/There are no events today./, "今日予定されているスケジュールはありません。");
return text;
}
function createAttachments(message) {
var attachments = message.attachments;
if(attachments == void 0){
return null;
}
for (var n = 0; n < attachments.length; n++) {
attachments[n].pretext = getPretext(attachments[n]);
attachments[n].fallback = getFallback(attachments[n]);
}
return JSON.stringify(attachments);
}
function getPretext(attachment) {
var pretext = attachment.pretext;
switch(pretext){
case "New calendar event created":
return "予定が作成されました。\n";
case "Calendar event updated":
return "予定が更新されました。\n";
case "Calendar event was cancelled":
return "予定がキャンセルされました。\n";
case "Event starting in 10 minutes:":
return "予定の10分前になりました。\n";
default:
return pretext;
}
}
function getFallback(attachment) {
var fallback = attachment.fallback;
fallback = fallback.replace(/Event created: /,"予定が作成されました:");
fallback = fallback.replace(/Event updated: /,"予定が更新されました:");
fallback = fallback.replace(/Event cancelled: /,"予定がキャンセルされました:");
return fallback;
}
表示される日付も日本語表記にしたかったのですが、日付が昨日になったらYesterdayと表示してくれる仕様を活用してというのは無理そうなので諦めました。
p.s.
このBOTはとある人の話がきっかけで作ったのですが、その人が反応を示してくれていないようで少々残念です・・・。
現在お試しで一部のカレンダーの内容をとあるチャンネルに流していて、そのことを#generalに書いていたのですが、私以外誰もjoinしてないですw
私自身そのスケジュールはほとんど不要なのですが、私がjoinしなくなったら忘れ去られるだけになりそうなのでしばらく受信し続けます。
遊び系BOTが流行らなかったり周りにあまりBOT作っている人がいなかったりで、チーム内ではいまいちBOTに興味を持たれていない気もします・・・。