Slackのくじ引きbotを無料でサクッと作る - Google App Script

[この記事](https://qiita.com/tanabee/items/2c51681396fe12b6a0e4)を見てGASが便利すぎることに気づき、色々と触りはじめました。
下記のような特徴があるため、botサーバとして利用するケースが特に便利だと思います。

- 無料で24時間稼働してくれる
- スプレッドシートを簡易的なDBとして利用できる
- 関数単位で簡単にAPIのエンドポイントを作れる

hubotなどを利用する場合は、node.jsが利用できるサーバを用意してコードをデプロイして、データに変更があればJSONを修正して..など準備も運用もそれなりに大変です。
GASを使うことによって、準備・運用も簡単になるのでツールを作る際のハードルがかなり下がります。

ということで、今回はslackで「くじ引き」を行ってくれるbotを作りました。

## ユースケース

次の2パターンで使えます。

1. レビュアーや〇〇当番など、チームメンバーをランダムで選びたいとき
2. 与えたリストからランダムで要素を選んで欲しいとき

メインは1ですが、特定のメンバー達から選んで欲しいとき・特定のメンバーを除外したいときは2が使えます。

こういう感じで動作します↓
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36492/ed5e3958-3923-e437-4578-b008b44e1a32.png" alt="slackのスクショ" width="320">

## 作成手順

詰まった場合は[こちら](https://qiita.com/sew_sou19/items/723feb39ea4aa7becfab)の記事を参考にしてください。

### GASとSlackアプリの準備
1. 新しくGoogle Apps Script(以下gas)とスプレッドシートを作成して、スプレッドシートのIDとURLを控えておく    
Google Drive > 左上の「新規」ボタンから作成できる
2. gasをウェブアプリとして公開したURLを控えておく  
右上の「デプロイ」から「種類の選択:ウェブアプリ、アクセスできるユーザ:全員」にしてデプロイ  
3. [slack api](https://api.slack.com/apps/)からslackアプリを作成する
4. slackアプリのVerification Tokenを控えておく  
Settings > Basic Information > App Credentials > Verification Token
5. slackアプリに任意のslashコマンド(`/member_kuji`など)とgasの公開URLを登録する  
Features > Slash Commands > Create New Command

### データの準備
事前に作成したスプレッドシートをチームメンバーの情報を保存するDBとして利用します。
スクリーンショットのようにデータを入れてください。

![スクリーンショット 2020-12-22 22.48.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36492/6b8b21b6-58ee-e838-4295-a36786609cb7.png)

- メンバーの情報を `nickname, slack_name, icon, description` の順で入力  
(今回、descriptionは使っていません)
- シート名を `members` にする
- G1に `=countif(A:A, "*") - 1` という式を入れてメンバーの数をカウントした集計値が入るようにする

なお、iconにはslackで登録したカスタム絵文字を登録しています。

### 開発環境の準備

- 環境構築

リポジトリには不要なコードも含まれているので、気になる場合は `gas/slack_bot_kuji` のディレクトリのみダウンロードしてお使いください。

```
$ git clone git@github.com:tkc310/gas.git
$ cd slack_bot_kuji
# npm install (node@v14.15.3が必要)
$ npm ci
```

- 環境変数

.envファイルを作成して環境変数を追加しておく

```
$ vi .env
```

```.env
SLACK_VERIFICATION_TOKEN='控えたslackのVerification Token'
SPREAD_SHEET_ID='控えたスプレッドシートのID'
SPREAD_SHEET_URL='控えたスプレッドシートのURL'
```

- gas用の.clasp.jsonを作成
事前に作成したgasのURLからIDを転記する

```
$ touch .clasp.json
$ echo '{ "scriptId": "控えたgasのID", "rootDir": "dist" }' >> .clasp.json
```

### gasに反映

下記のコマンドでビルドされたコードがgasに反映されます。

```
$ npm run untest_push
```

slackからコマンドを打って動作確認してみてください。

```
# スプレッドシートのメンバーから抽出
/member_kuji
# 与えたリストからランダムに抽出
/member_kuji foo, bar, baz
```

gas実行時に、gasからスプレッドシートを操作するための認証などが必要になるため、
うまくgasが実行されていない場合は、スプレッドシートの認証周りを確認してください。
(gasからtest関数を実行すればスプレッドシートに認証が求められます)

## コードの説明

最後にコードの説明を軽くします。

最終的なコードは[こちら](https://github.com/satotaka-sakotsu/gas/tree/master/slack_bot_kuji)
claspというgasのコードをローカルで開発できるようにするツールを利用しています。
(gasにコードをpush、gasからコードをpullできる)

併せてwebpackとtypescriptが利用できるようにしています。

メイン部分のロジックが記載されている `doPost.ts` のコードだけ説明します。

```src/doPost.ts
// 1. doPost関数はgasをウェブアプリとして公開した際、リクエスト時に実行される関数
const doPost = (e: doPostEventType): GoogleAppsScript.Content.TextOutput => {
  // 2. slackからリクエストがきた際にtokenが正しいか検証する
  if (SLACK_VERIFICATION_TOKEN != e.parameter.token) {
    throw new Error('Token Invalid');
  }

  const result = run(e.parameter);
  return ContentService.createTextOutput(
    JSON.stringify({
      // 3. slack apiのパラメータ in_channelの場合はチャンネルユーザ全てにbotのやり取りを公開
      response_type: 'in_channel',
      text: result,
    })
  ).setMimeType(ContentService.MimeType.JSON);
};

export const run = (params: doPostEventType['parameter'] | undefined): string => {
  let result = '';

  if (params && params.text) {
    // 4. foo, bar, bazのようなリストを渡された時はランダムに抽出
    const items = params.text
      .split(',')
      .map((item) => item.trim())
      .filter((item) => !!item);
    const randomIdx = getRandomNumber(0, items.length);

    result = items[randomIdx];
  } else {
    // 5. リストがない場合はスプレッドシートからチームメンバーの情報を抽出
    const spreadsheet = SpreadsheetApp.openById(SPREAD_SHEET_ID);
    const sheet = spreadsheet.getSheetByName('members');
    const length = sheet.getRange('G1').getValues()[0][0];
    const range = 'A2:D' + (parseInt(length) + 1);
    const values = sheet.getRange(range).getValues();
    const randomIdx = getRandomNumber(0, parseInt(length));
    const nickNameIdx = 0;
    const slackNameIdx = 1;
    const iconIdx = 2;
    const item = values[randomIdx];

    result = `
${item[iconIdx]} ${item[slackNameIdx]} (${item[nickNameIdx]}さん) \n
memberの編集は<${SPREAD_SHEET_URL}|こちら>
`;
  }

  // 6. gasの実行ログから確認できる
  Logger.log(result);

  return result;
};
```

1. doPost関数  
gasをウェブアプリとして公開した際、リクエスト時に実行される関数になる。(gasの仕様)  
2. tokenの検証
コード上のverification tokenとslackからのリクエストのtokenが合致しているか確認している
3. slack apiパラメータ  
botとのやり取りをチャンネルのメンバーに公開するかのオプション。他にも色々ある
4. slackのコマンド入力時にリストを渡された時の処理  
リストからランダムに要素を抽出して返している
5. slackのコマンド入力時にリストを渡されなかった時の処理  
スプレッドシートからチームメンバーの情報を取得している
6. gasの管理画面から実行ログを確認することができる


以上です。