SlackAPI+GASでチャンネルメンバーへの指名Botを作った話
こんばんは!
今日は社内でのちょっとした活動に関して記録を残したく
少しニッチなお話です
とある日の事でした
マネージャー:Tips共有チャンネルを指名制にしたいんだよね〜
※当社ではTipsを気軽に発言する(してほしい)チャンネルが設けられています
勉強になることがとても多いので、メンバーからの発信頻度を増やしたい狙いがあります
この一言で私の密かなプロジェクトが始動しました
やりたい事
Tips共有チャンネルに所属しているメンバーからランダムに1名選抜し指名をするBotを作成せよ
要件をまとめる
- Tips共有チャンネルに所属しているメンバーからランダムに1名選抜する
- 選抜されたメンバーにメンションを付けて固定メッセージを投下
- 週に一回発火させたい
↓(別件でも使いたいので今回要件に含まれたもの)↓
- メンバーの一覧をスプレッドシートにまとめたい
- チャンネルにメンバーが加入したら、そのスプレッドシートにレコードを追加したい
- チャンネルからメンバーが退室したら、そのスプレッドシートからレコードを削除したい
実装の全てをお話しすると超大作になりますので
少しかいつまんで以下の3件についてお話したいと思います
- 集積されたユーザー情報からランダムに1名指名する
- チャンネルにメンバーが加入した時にスプレッドシートにレコードを追加する
- チャンネルからメンバーが退室したら、そのスプレッドシートからレコードを削除する
それでは最後までお付き合いくださいませ
実装ポイント① 集積されたユーザー情報からランダムに1名指名する
メッセージの送信に関して、フローを図式で表すと以下の通りになります
ここでは、「ランダムに指名する」部分のご説明をしていきます
【STEP① スプレッドシートからユーザーIDを取得する】
スプレッドシートのA列にSlackユーザーIDを集積しておりますので、A列の情報だけ取得します
何行プロットされているかが流動的になりますので、A列全て取得しています
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('MY_SHEET_NAME');
// スプレッドシートからuser_idを取得
function getUserIdsFromSheet() {
return sheet.getRange("A:A").getValues().filter(userId => userId[0] !== "").flat();
}
補足させていただきます
sheet.getRange("A:A").getValues();
こちらで取得できるデータは
[[U012ABCDEFG], [U345HIJKLMN], [U678OPQRSTU], [""], [""], [""]]
この様に二次元配列になっており、データがないレコードには空文字が格納された状態で取得されます
従って、下記の様に
空文字はスキップして、ユーザーIDだけの1次元配列に書き換えています
sheet.getRange("A:A").getValues().filter(userId => userId[0] !== "").flat();
これで、SlackユーザーIDの1次元配列が出来上がりました
【STEP② ユーザーIDの配列からランダムに1件取得する】
それでは、乱数を使ってランダムに1名取得していきたいと思います
function getRandomUserId() {
// STEP①のメソッドを活用してユーザーIDの配列を取得
const userIds = getUserIdsFromSheet();
const randomIndex = Math.floor(Math.random() * (userIds.length + 1));
return userIds[randomIndex];
}
中でも、
min ~ maxの中からランダムに一つ選択する
という計算式は以下の通り実装いたしました
Math.floor(Math.random() * (max + 1 - min)) + min;
例えば、min:0 ~ max:10の中からランダムに一つ選択する
というユースケースを想定した場合は、上記の計算式に値を代入して
Math.floor(Math.random() * (10 + 1 - 0)) + 0;
// => Math.floor(Math.random() * 11);
この様に算出しています
今回は、
min:0
max:userIds.length + 1
を代入して算出いたしました
これで集積されたユーザー情報からランダムに1名指名するミッションクリアです
後はメンションを付けてメッセージを送付するだけですね!
実装ポイント② チャンネルにメンバーが加入した時にスプレッドシートにレコードを追加する
こちらもまずはやっていく事を図で見ていきましょう
こちらはOutgoing Webhookを活用します
ここでGASが発火する関数はdoPost()となりますので、こちらで実装進めていきます
まずはソースコードの完成体から見ていきましょう
function doPost(e){
const params = JSON.parse(e.postData.getDataAsString());
if (params.event.type === 'member_joined_channel')
// 以下の関数はこれから実装していきます
writeUserId(params.event.user);
}
リクエストからユーザーIDを取得する処理は、要するに下記のようになります
JSON.parse(e.postData.getDataAsString()).event.user
Webhookで送信されたリクエスト情報を取得する方法は以下を参考に他にも色々と操作が可能です
また、Slack APIのリファレンスから、どのようなデータ構造をリクエストしているか調べてみましょう
簡潔にまとめますと
{
"type": "member_joined_channel",
"user": "W123ABC456",
"channel": "C123ABC456",
"channel_type": "C",
"team": "T123ABC456",
"inviter": "U123456789"
"enterprise": "E123456789"
}
リクエスト情報の中のevent.userがユーザーIDであるとわかりますので、こちらを取得しています
では、取得できたユーザーIDをスプレッドシートに追記していきましょう
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('MY_SHEET_NAME');
// スプレットシードへuser_idを一括で書き込む
function writeUserId(userId) {
// スプレッドシートの最終行を取得しています
const sheetLastLow = sheet.getLastRow();
sheet.getRange(sheetLastLow + 1, 1, 1, 1).setValues([userId]);
}
シートの下に追記をしていきたいので、まず、スプレッドシートの最終行を取得します
const sheetLastLow = sheet.getLastRow();
次に、スプレッドシートのどこの行のどのセルに情報を追加してほしいかを定めます
今回は、下記の様にレコードを追加しようとしています
- 最終行の次の行に
- A列のセルに
- 1カラム、1レコード分
従って下記の通り指定することとなりました
sheet.getRange(sheetLastLow + 1, 1, 1, 1)
// sheet.getRange(行番号, 列番号, 行数, 列数)
もし、A列にユーザーID、B列にユーザー名を挿入したい場合は
sheet.getRange(sheetLastLow + 1, 1, 1, 2)
// sheet.getRange(行番号, 列番号, 行数, 列数)
このように指定します
これでチャンネルにメンバーが加入した時にスプレッドシートにレコードを追加するミッションクリアです
実装ポイント③ チャンネルからメンバーが退室したら、そのスプレッドシートからレコードを削除する
ほぼ上段の「ユーザーの追加」と処理は似ていますが、
今回は「削除したいユーザーを検索して、行ごと削除」をしていきます
処理を箇条書きにすると以下の通りになります
- スプレッドシートからユーザーIDを取得し、1次元の配列に加工する
- その配列から、削除したいユーザーIDを検索する
- 検索できた場合、そのインデックス番号+1行目を削除する
メンバーの追加時はmember_joined_channel
でしたが、退室時はmember_left_channel
を使用します
こちらのリファレンスは下記のリンクを参考にさせていただきました
それではソースコードの完成体から見ていきましょう
// スプレットシードから特定のuser_idを削除する
function deleteUserId(userId) {
// STEP①のメソッドを活用してユーザーIDの配列を取得
const users = getUserIdsFromSheet().flat();
// リクエストされたユーザーIDと一致した時に行を削除
const userCount = users.length - 1;
for (let i = userCount; i >= 0; i--) {
if (users[i] === userId) sheet.deleteRow(i + 1);
}
}
スプレッドシートの行を削除する為にdeleteRow()
を使用しましたが
こちらは、公式リファレンスをご参照ください
最初の行は1である点に注意しましょう!
以上で全てのミッションクリアとなりました
少々長かったですが最後までお付き合いいただき誠にありがとうございました
Divのメンバーが積極的にTipsの共有をしてくれる事を祈ります🙏
Discussion