社内のコミュニケーションツールとして、Slackを使用している企業も多いかと思います。
多くの人が参加するワークスペースでは、日々新たなユーザーが参加し、新たなチャンネルが生まれ、新たな絵文字が追加されていきます。
重要な情報を見逃さないためにも、それらのイベントを特定のチャンネルに通知するBOTをGoogle Apps Scriptで作って運用しています。
なぜGoogle Apps Scriptなのか
元々は、ノンコーディングで実現するためにZapierを使っていました。
しかしながら無料プランだとなにかと制約が厳しく、代替案の検討を余儀なくされました。
当時はまだ社内でAWSが使えなかったこともあり、ひとまずGoogle Apps Scriptで実装することにしたのです。
システム構成図
あえて図にするまでもありませんが、BOTのシステム構成図は以下の通りです。
Slackで発生したイベントをGoogle Apps Scriptに投げ、Google Apps Script側でメッセージを生成してSlackに投稿するといった流れです。
IFTTTで言うところの、トリガーとアクションの両方にSlackを設定している状態ですね。
仕組み
それでは、実際のソースコードを交えつつBOTの仕組みを見ていきましょう。
ここでは新規チャンネル通知BOTを例にあげますが、他のBOTも概ね同じ仕組みです。
Slackで発生したイベントをGoogle Apps Scriptに投げる
Slackで発生したイベントを外部に投げるには、Events APIを使用します。
Events APIでは、「ユーザーが参加した」「チャンネルが作成された」「絵文字が更新された」など、任意のイベントの発生時に指定したURLにHTTPリクエストを投げてくれます。
新規チャンネル通知BOTではchannel_createdというイベントを購読しているため、チャンネルが作成されたときに以下のようなリクエストが投げられます。
{
"type": "channel_created",
"channel": {
"id": "C024BE91L",
"name": "fun",
"created": 1360782804,
"creator": "U024BE7LH"
}
}
余談ですが、SlackはチャンネルIDがC
で始まりユーザーIDがU
で始まるなど、ID自体でそれが何を表しているのかが分かるようになっていて良いですね。
Google Apps Scriptでリクエストを受け取る
Google Apps ScriptではdoPost
関数でPOSTリクエストを受け取ることができるので、その中に処理を書いていきます。
function doPost (e) {
var request = normalizeRequest_(e)
var requestType = request.type
if (requestType === REQUEST_TYPE_URL_VERIFICATION) {
var challenge = request.challenge
return createTextOutput_(challenge)
} else if (requestType === REQUEST_TYPE_EVENT_CALLBACK) {
var event = request.event
if (event.type === EVENT_TYPE_CHANNEL_CREATED) {
var message = createMessage_(event)
postToSlack_(message)
}
}
}
認証のための仕組みとして、リクエストタイプがurl_verification
のときはリクエストに含まれるchallenge
の値をオウム返しする必要があります。
また、リクエストタイプがurl_verification
のときとevent_callback
のときとではリクエストの取り出し方が異なるようなので、normalizeRequest_
関数であらかじめ正規化しています。
function normalizeRequest_ (e) {
var postData = e.postData
var request = postData.contents || postData.getDataAsString()
return JSON.parse(request)
}
メッセージを生成する
リクエストに含まれているイベントのデータを元に、メッセージを生成します。
function createMessage_ (event) {
var channel = event.channel
channel.created = moment(channel.created * 1000).format(MESSAGE_TEMPLATE_DATE_FORMAT)
return Mustache.render(MESSAGE_TEMPLATE, channel)
}
メッセージの文面を環境変数(スクリプトのプロパティ)で柔軟に設定できるようにするために、以下のライブラリを使用しています。
たとえば、スクリプトのプロパティで以下のように値を設定したとします。
プロパティ | 値 |
---|---|
MESSAGE_TEMPLATE |
<#{{id}}> has created by <@{{creator}}> on {{created}}. |
MESSAGE_TEMPLATE_DATE_FORMAT |
YYYY[/]M[/]D H[:]mm[:]ss |
すると、テンプレート内の{{
と}}
で囲まれた変数がMustacheによって実際の値に置き換わり、以下のようなメッセージが生成されます。
#google_apps_script has created by @munieru_jp on 2018/12/7 18:17:13.
テンプレートを書く際の注意点として、APIから投稿する場合は#チャンネル名
や@ユーザー名
のように書いても単なる文字列として扱われてしまうので、それぞれ<#チャンネルID>
や<@ユーザーID>
のように書く必要があります。
Slackにメッセージを投稿する
Slackにメッセージを投稿するには、Incoming Webhooksを使用します。
Incoming Webhooksでは、Slackで生成した一意なURLにHTTPリクエストを投げることで特定のチャンネルにメッセージを投稿することができます(このURLが漏洩すると投稿され放題になってしまうので、扱いには留意する必要があります)。
単純なテキストメッセージの場合は、以下のようなJSONを投げるだけです。
{
"text": "Hello World"
}
Google Apps Scriptでは、UrlFetchApp.fetch()
関数でHTTPリクエストを送信することができます。
function postToSlack_ (message) {
var params = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
payload: '{"text":"' + message + '"}'
}
UrlFetchApp.fetch(WEBHOOK_URL, params)
}
動作イメージ
各BOTの実際の動作イメージは以下の通りです。
弊社の場合はそれぞれ専用のチャンネルに投稿していますが、ワークスペースの規模によっては1つのチャンネルにまとめてもいいかもしれません。
ソースコード
この記事で紹介したBOTのソースコードは、GitHubで公開しています。