slack API + GASでリアクションがあったら指定チャンネルにメンション通知する
まえがき
Slackでリアクションされたら、メンション&リアクション
に一覧表示されますが、リアルタイムでリアクションに気づきたくて通知されるようにSlack API+GASで実装してみました。
というのも夫婦間でSlackを使っていて、家事育児をしていると仕事中のように常にSlackを開いているわけではないのでリアクション通知があったらいいなという気持ちで作ってみました。
こんな通知がくるようにしました。
ちなみにメンバーが多いと通知がきまくってうざいので、メンバー2人ならではの機能だとは思います。
構成
- Slackのリアクションイベントをトリガー
- GASでPOSTリクエストを受け取って処理
- GASからIncoming WebhooksでSlackの対象チャンネルにメッセージ投稿
*GASとGitHub連携はGoogle Apps Script GitHub アシスタントを使っています。
実装
GAS作成(公開)
サーバ側はGASで用意しました。 「ウェブアプリ」としてデプロイして、公開URLをメモしておきます。
Promise.all
を使ってチャンネル名とユーザIDを取得しておいて、Webhookで「リアクション+メンション+メッセージURL」を対象チャンネルに投稿するようにしています。
const props = PropertiesService.getScriptProperties().getProperties(); const oAuthAccessToken = props.OAUTH_ACCESS_TOKEN; const slackApiBase = 'https://slack.com/api/'; function doPost(e){ const json = JSON.parse(e.postData.getDataAsString()); const itemChannel = json.event.item.channel; const itemTs = json.event.item.ts; const reaction = json.event.reaction; Promise.all([ fetchChannel(itemChannel), fetchMessage(itemChannel, itemTs) ]) .then(function(data) { const [channelName, messageData] = data; const message = "<@" + messageData["userId"] + ">" + "[" + channelName + "]にリアクションがつきました。\n\n" + messageData["linkUrl"]; const jsonData = { "text": ":" +reaction + ":" + message }; const payload = JSON.stringify(jsonData); const options = { "method" : "post", "contentType" : "application/json", "payload" : payload }; UrlFetchApp.fetch(props.WEBHOOK_URL, options); }) .then(() => { return ContentService.createTextOutput(params.challenge); }) } // チャンネルリストを取得してチャンネルIDから対象のチャンネル名を取得 function fetchChannel(channelId) { return new Promise((resolve) => { const channelsUrl = slackApiBase + 'conversations.list?token=' + oAuthAccessToken + '&exclude_archived=1&pretty=1'; const channels = JSON.parse(UrlFetchApp.fetch(channelsUrl)).channels; const targetChannel = channels.find(channel => channel.id === channelId); const channelName = (targetChannel !== undefined) ? targetChannel.name : "チャンネル以外"; resolve(channelName); }) } // メンション用に対象メッセージ投稿者のユーザIDを取得 function fetchMessage(channel, ts) { return new Promise((resolve) => { const url = slackApiBase + "conversations.replies?token=" + oAuthAccessToken + "&channel=" + channel + "&ts=" + ts + "&latest=" + ts + "&limit=1&inclusive=true" const replies = JSON.parse(UrlFetchApp.fetch(url)); const targetMessage = replies.messages[0]; const isThread = (targetMessage.thread_ts !== undefined && targetMessage.ts !== targetMessage.thread_ts); let linkUrl = props.SLACK_WORKSPACE_URL + "archives/" + channel +"/p" + String(targetMessage.ts).replace(/\./g, ''); // スレッドの場合 if(isThread) { linkUrl += "?thread_ts=" + String(targetMessage.thread_ts).replace(/\./g, '') + "&cid=" + channel; } resolve({linkUrl: linkUrl, userId: targetMessage.user}); }) }
参考
環境変数
GASのプロパティストアを利用。
OAUTH_ACCESS_TOKEN
はSlackアプリ作成時のOAuth & Permissions > Scopes設定後に表示されるものを定義します。WEBHOOK_URL
はSlackアプリ作成時にIncoming WebhooksをONにしてURLを取得します。投稿先は#reaction
チャンネルを作成して設定しました。
新IDEではなぜかプロパティ編集画面がなくなっていてかなりつまづきました。 仕方ないのでプロパティストアの設定時だけ旧IDEに戻して設定後にまた新IDEに戻しました。
ちなみに旧IDEに戻す際に以下のようなアンケートが出たのでMissing script property editing
にチェックしておきましたw
Slackアプリ作成
Slackアプリの作成はこちらを参考にさせていただきました。
適当にリアクション通知
という名前で作成。
Event Subscriptions>Enable Eventsを有効にして先ほどメモしておいたGASのURLを貼り付けてVerified表示されることを確認します。
Subscribe to events on behalf of usersにreaction_added
を追加します。
OAuth & Permissions>Scopesで以下を設定してアプリをインストール。(App Installから)表示されるOAuth Access TokenをGASのプロパティ(OAUTH_ACCESS_TOKEN
)に定義します。
参考
Incoming Webhooks設定
GASからメッセージ投稿するためにIncoming Webhooks>Activate Incoming Webhooks を有効にします。
Webhook URLs for Your Workspaceでワークスペースのチャンネル(作成した#reaction
)に追加します。
*以前はicon_emoji
も投稿できたみたいですが、できなくなったようなのでtext
に絵文字リアクションを追加して投稿する実装にしています。
Reference: Message payloads | Slack
Slack での Incoming Webhook の利用 | Slack
つまづいた点
スレッド対応
元メッセージURLの生成とユーザID取得用にslack APIの conversations.history
を使っていましたが、スレッドの場合thread_ts
も必要だったのでそれがが取れるconversations.replies
を使うように変更しました。
スレッド時のURL構成やAPIのresponseなどドキュメントを調べたりけっこう時間かかりました。
conversations.replies method | Slack
非同期処理の制御(Promise→Promise.all)
そのままだとAPIでのデータ取得前にWebhook処理が走ってしまうので、Promise
を使って順次処理していましたが、APIでのデータ取得は順番を気にしなくていいのでPromise.all
を使うようにリファクタリングしました。
Promise.all
でコードがかなりスッキリしたと思います。
参考
google github アシスタントの連携
Chromeの拡張機能をインストールしてもGASの画面に全然反映されず困りましたが、こちらにある「v4.0.7
を手動でインストール」で解決しました。
Google Apps Script GitHub アシスタントが表示されない&Google認証でエラー This app is blocked - Qiita
Google Apps Script GitHub アシスタント - Chrome ウェブストア
運用
最初に書いたとおり夫婦間用Slackでメンバーは2人なのでメッセージにメンションはつけず通知設定をすべての新しいメッセージにしていて、#reaction
チャンネルだけメンションのみ設定で自分の投稿に対するリアクションだけ通知がくるようにしています。
最後に
実装から記事をまとめるまでちょっと時間が空いているので設定の流れなど違ってる部分があるかもしれませんm(__)m
#reaction
チャンネルに常にすべてのリアクション情報が投稿される点はかなり微妙ですが、Slack APIもGASも初めて触ったのでいろいろ勉強になりました。
何より、今までは「了解」などの返事 をいちいちメッセージでスタンプ投稿していたのが、リアクションでいけるようになったのは個人的にかなり良かったです。
追記(2021/2/22)
昨日から夫のAndroidにSlackのリアクション通知がくるようになったらしく...。iPhoneには来ていないのでAndroid版のみ実装されたのか、ベータ版で一部ユーザに公開されているのか、どちらにしてもリアクション通知が公式で導入されたらアプリ不要ですw