【GAS】メッセージ受取/転送(投稿)/リアクションするSlack Appの作り方
前回の記事の続きのようなものになります。
前回の記事では、「1.4 Botの開発」「2.2 Event API使用のための準備」「2.4 Appの開発」の箇所で、具体的な実装については記載していなかったため、こちらの記事ではGoogle Apps Script(GAS)による実装にフォーカスして書いていきたいと思います。
(ちなみに、私が実装するときには前の記事の順番ではなく、2.2→1.4と2.4並行といった感じで実装したので、この記事でも実装を考えた順に説明します。)
どんなApp(Bot)を作るの?
「特定のワードが入ったメッセージが投稿されたら、そのメッセージにリアクションを付けた後、そのメッセージのリンクを特定のチャンネルに投稿する(以降「転送」と呼ぶ)」Botを作ります。おまけ機能として、転送したメッセージの内容などをスプレッドシートに記録します。
このBotの用途として、「iOSに関する質問です」というワードを含むメッセージが投稿されたら#question-iosチャンネルに、「Androidに関する質問です」というワードを含むメッセージが投稿されたら#question-androidチャンネルに、「hoge(他の条件に当てはまらない文字列)に関する質問です」というワードを含むメッセージが投稿されたら#question-othersチャンネルにそれぞれのメッセージへのリンクを投稿するといった運用をしました。リアクションは転送済みの証、スプレッドシートの記録は後ほど使うかもしれないので念の為Slackから消えても残るようにしたかった…ような扱いです。
単純に言えば、「ユーザからのメッセージ投稿を受け取り、そのメッセージにリアクションを付ける&そのメッセージ内容に応じてBotもメッセージ投稿をする&スプレッドシートにメッセージの情報を記録する」というものですので、必要な部分だけ抜き出せば他の使用例にも応用しやすいかと思います。
コード全体は以下のGitHub Repositoryで公開しています。
基本的にはそちらのコードを参照し、この記事ではそのコードに至った考え方や補足などを確認していただければと思います。(この記事の文章中には、あまりコードが載っていません…)
ということで、以下の流れで作っていきます。
- Events APIやBotの情報を設定する(前の記事の2.2の内容を含む)
- 転送部分を作る
- リアクション部分を作る
- 履歴記録部分を作る
- メッセージ投稿を受け取る部分などを作る
※今回は、スプレッドシートを作成して「スクリプト エディタ」からGASのコードを書く前提で進めます
1. Events APIやBotの情報を設定する
1.1 スクリプトのプロパティ設定
Bot User OAuth Access TokenやAppのVerification Tokenは、コード内に載せるとオンラインで公開/管理する際によろしくないです。
GASの場合は、そういった値を「スクリプトのプロパティ」に設定します。
以前はUI上から設定可能だったのですが、私がこのBotを開発している時期くらいにGASのWeb UIが変わり、どうやらUI上で設定できなくなったようです。そのため、PropertiesService.getScriptProperties().setProperty
やPropertiesService.getScriptProperties().setProperties
などでコード上で設定する方法しか現状見つかっていません。
プロパティ設定用の関数を作り、公開するときにはその部分を除外するのが良いでしょうか…?(このあたりの知見はあまりないので、良い方法を知っている方がいれば教えて下さい!)
とりあえず今回は以下のような感じで、Bot User OAuth Access Token, Verification Token, そしてワークスペースのURLの一部(xxx.slack.comのxxx部分)を設定しました。
function setProperties() {
PropertiesService.getScriptProperties().setProperty("bot_token", "xoxb-xxx...");
PropertiesService.getScriptProperties().setProperty("verification_token", "xxx...");
PropertiesService.getScriptProperties().setProperty("workspace", "xxx...");
}
もちろん、Propertiesのほうを使ってまとめて設定しても良いですね。その場合は公式ドキュメントを参照すればできると思います。
そしてメインとなる関数であるdoPost(e)
の序盤でsetProperties()
を呼び出せば設定できます。
ちなみに、旧UIに戻して設定する場合は、ボタンポチポチからの入力で設定できます。
1.2 Events API用の記述
「特定のワードが入ったメッセージが投稿されたら~」の部分の下地として、doPost(e)
内でEvents APIを使うための記述をします。
// Events APIからのPOSTを取得
// 参考→https://api.slack.com/events-api
const json = JSON.parse(e.postData.getDataAsString());
// Events APIからのPOSTであることを確認
if (prop.getProperty("verification_token") != json.token) {
throw new Error("invalid token.");
}
// Events APIを使用する初回、サーバの存在確認?みたいなのがあるので、そのための記述
if (json.type == "url_verification") {
return ContentService.createTextOutput(json.challenge);
}
ほぼ前回記事の2.2と同じですが…
このような感じで、POSTを取得しパースしたあと、それがEvents APIからのPOSTであることを確認し、その後初回のurl_verificationに対処するための記述をしています。
2. 転送部分を作る
コード中の forwardMessage
関数にあたる部分です。
2.1 基本的なメッセージ投稿部分の記述について
コード中にコメントで記載している通り、メッセージを送るときはhttps://api.slack.com/methods/chat.postMessage を参照して必要な情報を用意しましょう。
上記サイトで必須(Required)と記載されているのはtoken
とchannel
の2つのみですが、現実的にはtext
も必要でしょう。
今回のコードでは、payload
変数にtoken
channel
text
を設定しています。
今回は、Events APIで取得したメッセージのリンクをlink
という定数に代入済みとして、
let payload = {
"token" : token,
"text" : link
};
こんな感じでpayload
の初期化時に渡しています。
channel
はその後のfor文内if文の条件によってどのチャンネルに送るか決めた時にpayload
に渡しています。
(これを書いてから、「channel
の値は適当な定数に代入しておいて、for文終わった後にpayload
の初期化自体をすれば、payload
も定数で良かったな…というかparams
に直接代入も可能だったな…」と気づきました。)
そして上記の情報をAPIエンドポイントにPOSTします。
GASのUrlFetchApp
クラスのfetch
メソッドを使います。(公式リファレンス)
const params = {
"method" : "post",
"payload" : payload
};
const r = UrlFetchApp.fetch(forward_url, params);
こんな感じでpost
と先程のpayload
をparams
に代入し、UrlFetchApp.fetch
の引数に渡せばOKです。
レスポンスは後ほど、このPOSTが成功したかの判定に使うことを見越して、r
に代入してあります。
2.2 この用途独自の部分について
基本的にメッセージ投稿は2.1の内容でできますが、今回の用途のために記述した部分も念の為いくつか説明します。
今回のBotでは「特定のワード」に応じて「特定のチャンネル」にメッセージを投稿するため、そのワードとチャンネルの対応をkey/valueとしてchannels
に代入しています。(他に良い方法が思いつかなかった…)
そして、for文内で、受け取ったメッセージにchannels
のkeyのいずれかが含まれているかを判定し、含まれていれば転送するチャンネルのIDをpayload
に代入するようにしています。
channels
のvalueに使うチャンネルのIDは、以下のように、チャンネル名を右クリックから「リンクをコピー」で取得できます。
また、今回Botで投稿するメッセージの内容は、ユーザが投稿したメッセージへのリンクです。
メッセージのリンクは
ワークスペースのURL + /archives/ + 投稿されたメッセージのチャンネルID + /p + メッセージのタイムスタンプ
といった文字列で構成されています。
ワークスペースとチャンネルとタイムスタンプでメッセージを一意に特定できるということですね!
ここで必要となるチャンネルIDとメッセージのタイムスタンプは、Events API経由で取得したユーザが投稿したメッセージの情報に含まれています。
タイムスタンプは.
が含まれる形式で取得されるものの、URL上では.
が使用されないため、今回のコードではreplace(".","")
を用いて.
を取り除いています。
3. リアクション部分を作る
コード中の addReaction
関数にあたる部分です。
コード中にコメントで記載している通り、メッセージに絵文字リアクションをつける場合はhttps://api.slack.com/methods/reactions.add を参照して必要な情報を用意します。
必須項目はtoken
channel
name
timestamp
です。
name
は絵文字の名前です。(日本語が使えるかは未確認ですが、ユーザが登録した絵文字が日本語名しか付いていない場合もあると思うので、対応しているかな…?)
channel
とtimestamp
は、リアクションを付ける対象となるメッセージが投稿されたチャンネルとタイムスタンプを表しています。ちょうど2.2で説明したメッセージのリンクを構成するために必要な文字列と同じで、リアクション対象となるメッセージを一意に特定するための情報です。
それらの必須項目が用意できたら、あとはほぼメッセージ投稿(転送)と同じようにUrlFetchApp.fetch
でPOSTすればOKです。
4. 履歴記録部分を作る
コード中の recordHistory
関数にあたる部分です。
この部分は3までと異なり、スプレッドシートの扱いについての説明になります。
まず、記録先のスプレッドシートを取得します。
let sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
次に、下に向かって追記していきたいため、スプレッドシートの最終行を取得します。
let lastrow = sheet.getLastRow();
(コード内では上記の2行をdoPost(e)
内で実行していましたが、recordHistory
内でやっても良かったかなと思います。)
最後に、最終行の次の行、書き込みたい列を指定してその箇所に書き込みたい文字列をセットします。(下記の例では最終行の次の行の3列目にtextを書き込む)
sheet.getRange(lastrow+1,3).setValue(text);
以上です。簡単ですね!
5. メッセージ投稿を受け取る部分などを作る
最後の仕上げとして、メインであるdoPost(e)
に戻り、メッセージ投稿受け取り→各種関数呼び出しを実装します。
メッセージ投稿の受け取りは、1.2で用意したEvents APIを使用します。
コード中にコメントで記載している通り、https://api.slack.com/events/message.channelsを参照すると、どのような形式で情報が得られるかわかります。
今回は、(認証のためにtoken
が必要なのは当然として、)event
内のtype
channel
text
ts
が他の関数のために必要となります。
const json = JSON.parse(e.postData.getDataAsString());
でJSONをパースしたのち、json.event.type
などの指定で必要な情報を取り出せます。
そしてメッセージの転送をするために、2で作成したforwardMessage
を呼び、その結果(response
)が正常だった場合はaddReaction
recordHistory
を呼びます。
以上です。
今回の内容ができれば、他にも簡単な応答をしてくれるBotなら作れると思います!
実際に私は、「褒めてとメッセージ投稿したら、リアクションをしてからランダムに褒めるメッセージを投稿してくれるBot」も同様の形式で実装して、研究室のワークスペースで稼働させています。(褒めbot ver.2)
前回の記事と同様、質問や困ったことがあればお気軽にどうぞ!
Discussion