🔔

SlackAPI+GASでチャンネルメンバーへの指名Botを作った話

2024/03/25に公開

こんばんは!

今日は社内でのちょっとした活動に関して記録を残したく
少しニッチなお話です

とある日の事でした
マネージャー:Tips共有チャンネルを指名制にしたいんだよね〜

※当社ではTipsを気軽に発言する(してほしい)チャンネルが設けられています
勉強になることがとても多いので、メンバーからの発信頻度を増やしたい狙いがあります

この一言で私の密かなプロジェクトが始動しました

やりたい事

Tips共有チャンネルに所属しているメンバーからランダムに1名選抜し指名をするBotを作成せよ

要件をまとめる

  • Tips共有チャンネルに所属しているメンバーからランダムに1名選抜する
  • 選抜されたメンバーにメンションを付けて固定メッセージを投下
  • 週に一回発火させたい

↓(別件でも使いたいので今回要件に含まれたもの)↓

  • メンバーの一覧をスプレッドシートにまとめたい
  • チャンネルにメンバーが加入したら、そのスプレッドシートにレコードを追加したい
  • チャンネルからメンバーが退室したら、そのスプレッドシートからレコードを削除したい

実装の全てをお話しすると超大作になりますので
少しかいつまんで以下の3件についてお話したいと思います

  1. 集積されたユーザー情報からランダムに1名指名する
  2. チャンネルにメンバーが加入した時にスプレッドシートにレコードを追加する
  3. チャンネルからメンバーが退室したら、そのスプレッドシートからレコードを削除する

それでは最後までお付き合いくださいませ

実装ポイント① 集積されたユーザー情報からランダムに1名指名する

メッセージの送信に関して、フローを図式で表すと以下の通りになります

ここでは、「ランダムに指名する」部分のご説明をしていきます

【STEP① スプレッドシートからユーザーIDを取得する】

スプレッドシートのA列にSlackユーザーIDを集積しておりますので、A列の情報だけ取得します
何行プロットされているかが流動的になりますので、A列全て取得しています

SpreadsheetApp.gas
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('MY_SHEET_NAME');

// スプレッドシートからuser_idを取得
function getUserIdsFromSheet() {
  return sheet.getRange("A:A").getValues().filter(userId => userId[0] !== "").flat();
}

補足させていただきます

SpreadsheetApp.gas
sheet.getRange("A:A").getValues();

こちらで取得できるデータは

[[U012ABCDEFG], [U345HIJKLMN], [U678OPQRSTU], [""], [""], [""]]

この様に二次元配列になっており、データがないレコードには空文字が格納された状態で取得されます

従って、下記の様に
空文字はスキップして、ユーザーIDだけの1次元配列に書き換えています

SpreadsheetApp.gas
sheet.getRange("A:A").getValues().filter(userId => userId[0] !== "").flat();

これで、SlackユーザーIDの1次元配列が出来上がりました

【STEP② ユーザーIDの配列からランダムに1件取得する】

それでは、乱数を使ってランダムに1名取得していきたいと思います

SpreadsheetApp.gas
function getRandomUserId() {
  // STEP①のメソッドを活用してユーザーIDの配列を取得
  const userIds = getUserIdsFromSheet();
  const randomIndex = Math.floor(Math.random() * (userIds.length + 1));
  return userIds[randomIndex];
}

中でも、
min ~ maxの中からランダムに一つ選択する
という計算式は以下の通り実装いたしました

SpreadsheetApp.gas
Math.floor(Math.random() * (max + 1 - min)) + min;

例えば、min:0 ~ max:10の中からランダムに一つ選択する
というユースケースを想定した場合は、上記の計算式に値を代入して

SpreadsheetApp.gas
Math.floor(Math.random() * (10 + 1 - 0)) + 0;
  // => Math.floor(Math.random() * 11);

この様に算出しています

今回は、
min:0
max:userIds.length + 1
を代入して算出いたしました

これで集積されたユーザー情報からランダムに1名指名するミッションクリアです
後はメンションを付けてメッセージを送付するだけですね!

実装ポイント② チャンネルにメンバーが加入した時にスプレッドシートにレコードを追加する

こちらもまずはやっていく事を図で見ていきましょう

こちらはOutgoing Webhookを活用します
ここでGASが発火する関数はdoPost()となりますので、こちらで実装進めていきます

まずはソースコードの完成体から見ていきましょう

Main.gas
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で送信されたリクエスト情報を取得する方法は以下を参考に他にも色々と操作が可能です
https://developers.google.com/apps-script/guides/web?hl=ja
https://developers.google.com/apps-script/reference/base/blob?hl=ja

また、Slack APIのリファレンスから、どのようなデータ構造をリクエストしているか調べてみましょう
https://api.slack.com/events/member_joined_channel

簡潔にまとめますと

{
    "type": "member_joined_channel",
    "user": "W123ABC456",
    "channel": "C123ABC456",
    "channel_type": "C",
    "team": "T123ABC456",
    "inviter": "U123456789"
    "enterprise": "E123456789"
}

リクエスト情報の中のevent.userがユーザーIDであるとわかりますので、こちらを取得しています

では、取得できたユーザーIDをスプレッドシートに追記していきましょう

SpreadsheetApp.gas
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]);
}

シートの下に追記をしていきたいので、まず、スプレッドシートの最終行を取得します

SpreadsheetApp.gas
const sheetLastLow = sheet.getLastRow();

次に、スプレッドシートのどこの行のどのセルに情報を追加してほしいかを定めます

今回は、下記の様にレコードを追加しようとしています

  • 最終行の次の行に
  • A列のセルに
  • 1カラム、1レコード分

従って下記の通り指定することとなりました

SpreadsheetApp.gas
sheet.getRange(sheetLastLow + 1, 1, 1, 1)
  // sheet.getRange(行番号, 列番号, 行数, 列数)

もし、A列にユーザーID、B列にユーザー名を挿入したい場合は

SpreadsheetApp.gas
sheet.getRange(sheetLastLow + 1, 1, 1, 2)
  // sheet.getRange(行番号, 列番号, 行数, 列数)

このように指定します

これでチャンネルにメンバーが加入した時にスプレッドシートにレコードを追加するミッションクリアです

実装ポイント③ チャンネルからメンバーが退室したら、そのスプレッドシートからレコードを削除する

ほぼ上段の「ユーザーの追加」と処理は似ていますが、
今回は「削除したいユーザーを検索して、行ごと削除」をしていきます

処理を箇条書きにすると以下の通りになります

  1. スプレッドシートからユーザーIDを取得し、1次元の配列に加工する
  2. その配列から、削除したいユーザーIDを検索する
  3. 検索できた場合、そのインデックス番号+1行目を削除する

メンバーの追加時はmember_joined_channelでしたが、退室時はmember_left_channelを使用します
こちらのリファレンスは下記のリンクを参考にさせていただきました
https://api.slack.com/events/member_left_channel

それではソースコードの完成体から見ていきましょう

SpreadsheetApp.gas
// スプレットシードから特定の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()を使用しましたが
こちらは、公式リファレンスをご参照ください
https://developers.google.com/apps-script/reference/spreadsheet/sheet?hl=ja#deleteRow(Integer)
最初の行は1である点に注意しましょう!

以上で全てのミッションクリアとなりました
少々長かったですが最後までお付き合いいただき誠にありがとうございました

Divのメンバーが積極的にTipsの共有をしてくれる事を祈ります🙏

Arsaga Developers Blog

Discussion