Slack上でチームの雑談を促進するBotをつくる！(2/2)

# 概要
前回の続きです。

https://qiita.com/Tokoroteen/items/e954c597e918d0ca0869

前回はSlack Botの作成まで終わったので、本記事ではSlackへのメッセージの送信機能を実装します。
完成イメージは以下の通りです。

![スクリーンショット 2024-10-25 0.29.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/6a9a8146-bc17-ddc9-2a18-6302d98d78fd.png)

# 実装
## スプレッドシートの準備
メンバーと質問の管理をするためのシートをそれぞれ準備します。

### 1. 「送信先」シートの作成
質問を送るメンバーの名簿シートです。
シート名は「送信先」にし、メンバーを一意に特定するためのメンバーID、名前を記載します。
全員が平等に指名されるような実装をあとで行うので、出題回数の列も用意しておきます。

![スクリーンショット 2024-10-24 22.47.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/2b591c76-6994-b754-002e-3f45f370ae11.png)

メンバーIDはプロフィールから確認できます。

https://intercom.help/yoom/ja/articles/5480063-slack%E3%81%AE%E3%83%A1%E3%83%B3%E3%83%90%E3%83%BCid%E3%81%AE%E7%A2%BA%E8%AA%8D%E6%96%B9%E6%B3%95

### 2. 「質問」シートの作成
メンバーに投げかける質問を溜めておくシートです。
シート名は「質問」にし、質問を記載します。
こちらも先ほどと同様に、同じ質問が連続しないよう、出題回数の列を用意しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/5536cf1f-c5e5-094d-972e-8fec7f696377.png)

## GASの準備

拡張機能から「Apps Script」をクリックし、メッセージの送信機能を実装します！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/8e87451a-4b5c-f008-573b-ececda4a026a.png)

具体的には下記要件を満たすようなスクリプトを書きます。

* 質問シートにある、出題回数が最も少ない質問の中から、ランダムに1つ選ぶ
* 送信先シートにある、出題回数が最も少ない人の中から、ランダムに1人の回答者を選ぶ
* 回答者をSlack上でメンションして、質問を投げかける

### 1. 環境変数と定数の定義

GASの設定画面から、スクリプトプロパティを編集し、前回取得したBot User OAuth Tokenを入力します。

![スクリーンショット 2024-10-24 23.18.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/5582e852-a461-f6d5-ed2a-efa90311e893.png)

下記のように環境変数と定数を定義します。
（[SlackのチャンネルIDの確認方法](https://intercom.help/yoom/ja/articles/5480072)）

```javascript
const SLACK_TOKEN = PropertiesService.getScriptProperties().getProperty('SLACK_TOKEN'); // SlackのBot User OAuth Token
const SLACK_CHANNEL = 'XXXXX'; // 質問を投稿するSlackチャンネルのID

const question_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('質問'); // 質問シート
const answerer_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('送信先'); // 送信先シート
```

### 2. ランダムに質問や回答者を選択する関数
質問シートや送信先シートにある、出題回数が最も少ない項目の中からランダムに選ぶ関数を作成します。

```javascript
// 指定されたシートと列範囲から、最小値を持つ行番号を取得する
function getRowsWithMinCount(sheet, columnRange) {
  let columnData = sheet.getRange(columnRange).getValues();
  let filteredData = columnData.filter(row => row[0] !== '');
  const min = Math.min.apply(null, filteredData);

  return filteredData.reduce((rows, row, index) => {
    if (row[0] == min) rows.push(index + 2);
    return rows;
  }, []);
}

// ランダムな行番号を取得する
function getRandomRow(rows) {
  const randomIndex = Math.floor(Math.random() * rows.length);
  return rows[randomIndex];
}
```
`getRowsWithMinCount()`は、シートと列範囲を引数に取り、指定された列内で最小値を持つ行番号の配列を返します。
例えば、下記のような状態では、`getRowsWithMinCount(questionSheet, 'B2:B')`の返り値は`[3, 5]`になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/9ad657f8-a411-7a5e-9fd5-d5b14902f193.png)

`getRandomRow()`は、`getRowsWithMinCount()`で取得した行番号の配列の中から、ランダムに行番号を取得します。先ほどの例では、`[3, 5]`のうち、ランダムに`3`または`5`を返します。

### 3. 休日かどうかを判定する関数
土日はメッセージを投稿しないようにするため、土日かどうかを判定する関数を作成します。
```javascript
// 休日判定
function isWeekend() {
  const today = new Date();
  const dayOfWeek = today.getDay();
  return dayOfWeek === 0 || dayOfWeek === 6;
}
```

### 4. 出題回数を更新する関数
選ばれた回答者および質問の「出題回数」列の値を更新する（+1する）関数を作成します。
```javascript
// 出題回数を更新する
function updateCount(sheet, row, column) {
  const count = sheet.getRange(row, column).getValue();
  sheet.getRange(row, column).setValue(count + 1);
}
```

### 5. メッセージを投稿する関数
上記を組み合わせて、回答者をメンションして質問を投げかける関数を作成します。
```javascript
// 質問と回答者を投稿する
function postQuestion() {
  // 土日の場合は終了
  if (isWeekend()) return;

  // 質問を取得
  const questionRows = getRowsWithMinCount(questionSheet, 'B2:B');
  const randomQuestionRow = getRandomRow(questionRows);
  const question = questionSheet.getRange('A' + randomQuestionRow).getValue();

  // 回答者を取得
  const answererRows = getRowsWithMinCount(answererSheet, 'C2:C');
  const randomAnswererRow = getRandomRow(answererRows);
  const answerer = answererSheet.getRange('A' + randomAnswererRow).getValue();

  // Slackへの投稿
  const payload = {
    "channel": SLACK_CHANNEL,
    "text": "今日の質問",
    "blocks": [
      {
        "type": "section",
        "text": {
          "type": "mrkdwn",
          "text": "*<@" + answerer + ">さんに質問です！*\n\n" + question
        }
      }
    ]
  };

  const options = {
    'method': 'post',
    'contentType': 'application/json; charset=UTF-8',
    'headers': {
      'Authorization': 'Bearer ' + SLACK_TOKEN
    },
    'payload': JSON.stringify(payload),
    'muteHttpExceptions': true
  };

  try {
    const response = UrlFetchApp.fetch('https://slack.com/api/chat.postMessage', options);
    Logger.log(response.getContentText());

    // 出題回数の更新
    updateCount(answererSheet, randomAnswererRow, 3);
    updateCount(questionSheet, randomQuestionRow, 2);
  } catch (e) {
    Logger.log('エラー発生：' + e.message);
  }
}
```

メッセージ内に`<@SlackのユーザーID>`を含めることで、メンションをすることができます。

# 実行
「実行」をクリックして、実行してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/b84790ff-e530-1b90-1b68-35e692e781ee.png)

初回実行では認証を求められるので、権限を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/ef63b5ca-7e4e-176c-f75d-bb155cdb43af.png)

参考：

https://qiita.com/STSHISHO/items/f2ce468ab78682c26699

Botから質問が届きました！
![スクリーンショット 2024-10-25 0.29.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/5a27e227-8af1-ce0f-24e6-402d234bd9af.png)

# 定期実行の設定
毎日決まった時間に質問を投げかけられるように設定を行います。

左メニューの「トリガー」から

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/92a21c33-8b58-56ac-6d85-1546c68545f4.png)

右下の「トリガーを追加」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/75a690f9-2caf-ecc1-adc0-7e87e3b6832a.png)

下記画像のように設定すると、お昼時に質問が投げかけられます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/543d99b5-7df8-420f-103e-62c663109258.png)

これで完成です！！

# 作ってみての感想
運用を始めてみて、他の人のおすすめランチを食べに行く人がいたり、よく見ているYouTuberを紹介しあったり、「繋ぐ（ん）」が実現されているなと効果を感じています！

普段は見るだけの人もしりとりならスレッドの会話に参加しやすいのでは？と思い、「しりとりをしましょう！しりとりの**り**から！！」という質問でもない投げかけの投稿もしてみたのですが、「せっかく当てられたから質問して欲しかった」等の声もあり、賛否を呼びましたw

「回答者がメンションされるだけだと気付かない！」という声もあるので、`@here`もつけてみたり（GAS上では`<!here>`とするとつけられる）、投稿時間を変えてみたり、日々を改善を続けています。

# 今後の展望
運用面での課題は、「質問が尽きてしまうこと」が表面化していますが、いい解消法は思いついていません。

また、KPIとして、スレッドへの返信数や参加者数を追っていきたいと考えており、KPIをモニタリングできるような仕組みも時間があるときに作りたいです。

---

最後までお読みいただき、ありがとうございました！
一度作ってしまえばスプシを管理するだけで楽ちんなので、ぜひ作ってみてください！