はじめに
特定の個人を攻撃したり風紀を乱すような絵文字を追加する従業員の良からぬ行動によって、社内のSlackのモラルが下がる、場合や内容によってはハラスメントにつながる、というお話を小耳に挟みまして、こまめに絵文字を監視するより「絵文字追加したら皆に知れ渡るよ」ということで防止する、というアイディアを目の当たりにしたので、モラル崩壊云々はさておきネタとしても面白く、これは良いと思い早速作ってみました。
今回の流れ
-
Google Cloud Platform(以下GCP) でプロジェクトを作る
- OAuth同意画面を作る
-
Google Apps Script(以下 GAS )で外部から呼び出し可能なプロジェクトを作る
- GASのプロジェクトを新規作成する
- GCPのプロジェクトを関連付ける
- GASでHTTPのPOSTリクエストを受け付けるコードを用意する
- GASをウェブアプリとしてデプロイして外部から呼ばれるようにする
- GASでSlack Eventを受信してIncoming Webhookで通知する
- SlackでSlack Appを作る
- Incoming Webhookを有効化する
- Event Subscriptionsを有効化してSlack EventsをGASで受信できるようにしておく
- 受信したSlack EventをGASで処理する
1.1~3.2までは色々と検索したり記事を参照して各自で頑張ってみてください。🙋♀️
また、先にGitHubのソースを公開しておきます。
以下、本記事は下記(及びそれらを準備する知識)が揃っている前提となります。
前提として揃っているもの
- ウェブアプリとしてデプロイできるGAS
-
Botとして
Incoming Webhookが出来る
Slack App
実際にGASでBotを作ってみる
GAS側でSlack Eventに対応する準備をする
手始めに、GASの doPost
関数を以下のようにしてください。
function doPost(e) {
const rowData = e.postData.getDataAsString();
const jsonData = JSON.parse(rowData);
// return a response as HTML to confirm the challenge
if (jsonData['challenge']) {
const output = ContentService.createTextOutput(jsonData['challenge']);
output.setMimeType(ContentService.MimeType.TEXT);
return output;
}
const output = ContentService.createTextOutput("ok");
output.setMimeType(ContentService.MimeType.TEXT);
return output;
}
コード解説
doPost
は引数で受け取ったオブジェクトにPOSTで受信したデータが含まれているので、これをテキストとして読み込み、JSONとしてパースしてオブジェクトとして扱います。
const rowData = e.postData.getDataAsString();
const jsonData = JSON.parse(rowData);
Slack Bot
の [Events API](Slack Events API) はイベント通知先として指定したURLが正しいものかどうかの確認で下記のようなJSONを渡してくるので
{
"token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
"challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
"type": "url_verification"
}
この challenge
の部分をそっくりそのまま返す処理が必要になります。
if (jsonData['challenge']) {
const output = ContentService.createTextOutput(jsonData['challenge']);
output.setMimeType(ContentService.MimeType.TEXT);
return output;
}
💡GASを書き換えたら必ずデプロイして新しいコードで書かれたウェブアプリを更新しましょう。
Slack App で Event Subscriptions を有効化する
Slack Events を GAS で受信できるようにする
- Slack Appの一覧で、対応させるSlack Appを選びます。
- 左側メニュー
Event Subscriptions
をクリックしイベント設定画面へ入ります。 -
Enable Events
をONにし、Request URL
に、GASでデプロイしたウェブアプリのURLを入力します。
ここでGASがJSONに含まれるチャレンジを正しく返していれば、入力したURLにVerified
と表示されます。
受信するイベントを指定する
Subscribe to bot eventsをクリックして開き、Add Bot User Event
のボタンをクリックしてイベントを追加します。
追加するイベントはemoji_changedです。
イベントが追加されると、OAuthのスコープに emoji:read
が自動的に追加されます。
以上で、Slackワークスペースに絵文字が追加されたら、Slackイベントemoji_changed
が発生し、GASで作られたウェブアプリへPOSTされるところまで出来ました。
あとは、このPOSTを元に新しい絵文字が来たことをIncoming Webhookを使って通知するだけですね。😉
イベントの内容をSlackへ通知する
Slack AppからSlackへ通知を行うにはIncoming Webhookを使うだけでOKです。
では早速、GASのコードを以下にまるっと置き換えてみましょう。
7032/gas_emojichecker/main.gs
GASのスクリプトプロパティを設定する
スクリプトプロパティ Webhook-EMOJI
に、Slack Appで設定した Incoming Webhook
のURLを設定してください。
SlackへのIncoming Webhookによる通知
関数 _SendMessageToSlack(propName,message)
は引数を2つとり、以下の処理を行います。
-
propName
で指定されたスクリプトプロパティから値を読み出す - 読み出したスクリプトプロパティで指定されたURLへSlackメッセージとして
message
で指定された内容を通知する- HTTPヘッダー
Content-Type
にapplication/json
を指定する - JSONの下記の形式をボディとしてPOSTで送信する
- HTTPヘッダー
{ "text" : "送信したいメッセージの内容" }
実際の処理は以下の部分となります。
function _SendMessageToSlack(propName,message) {
const propService = PropertiesService.getScriptProperties();
const url = propService.getProperty(propName);
try {
var response = UrlFetchApp.fetch(
// URL stored in the property
url,
// Options set to header and payload
{
"method" : "POST",
"headers": { "Content-Type" : "application/json", },
"payload": JSON.stringify({ "text" : message }),
"muteHttpExceptions": true
}
);
}
catch (e) {
console.log("[Slack:WebHook error]"+JSON.stringify(e)+"\nURL:"+url);
}
}
受信したSlack Eventを処理してSlackへ通知する処理
Slackイベント emoji_changed
は、以下のようなJSONをPOSTしてきます。
{ "event" : {
"type": "emoji_changed",
"name": "絵文字の名前。コロンで挟まれた部分",
"value": "新しい絵文字が投稿された場合のURL、またはエイリアス元の絵文字名"
}
}
JSONキー | 内容 |
---|---|
event.type | 絵文字、絵文字のエイリアスが追加された場合、emoji_changed が入ります。 |
event.name | 絵文字の名前 = コロンで挟まれた :hogehoge: の hoge 部分が入ります。 |
event.value | 新規で絵文字が追加された場合は、その画像の保存先のURLが入ります。エイリアスの場合は alias:hoge という形式の文字列が入ります。 alias: というプレフィックスがついてエイリアス元の絵文字名が入ってくるので、ここで「新規で投稿された絵文字」か「エイリアスが追加されたのみ」かを判断します。 |
以上を元に通知メッセージを構築して、先述の _SendMessageToSlack
関数へ渡して通知を行います。
この処理のメインとなる部分が以下です。
if (jsonData["event"]) {
if (jsonData["event"]["type"]) {
switch(jsonData.event.type) {
// EMOJI
case "emoji_changed": {
// Add EMOJI
const emojiName = jsonData.event.name;
const emojiVal = jsonData.event.value;
const emojiAli = "alias:";
var strMessage= "*[New EMOJI]* :"+emojiName+": is added as *`:"+emojiName+":`* !!!\n";
if (emojiVal.slice(0,emojiAli.length) == emojiAli) {
const emojiOrginal = emojiVal.slice(emojiAli.length);
strMessage += "This is just an *alias* of *`:"+emojiOrginal+":`* :"+emojiOrginal+":";
} else {
strMessage += "Original picture is "+emojiVal;
}
_SendMessageToSlack("Webhook-EMOJI",strMessage);
}
break;
}
}
}
以上で、
- 絵文字が追加されると Slack Event
emoji_changed
が発生する - Slack Appが上記のSlack Eventを受け取る
- 受け取った内容を元にIncoming Webhookで通知する
という流れで、ワークスペースへ絵文字が追加されたら特定のチャンネルで通知するということが出来る様になりました。🙌🙌🙌
さいごに
ITのお知らせのチャンネルなどに流しても良いですし、絵文字の追加が活発なワークスペースであればネタチャンネルとして新着絵文字専用チャンネルなどを作っても良いかもしれませんね。