この記事を見てGASが便利すぎることに気づき、色々と触りはじめました。
下記のような特徴があるため、botサーバとして利用するケースが特に便利だと思います。
- 無料で24時間稼働してくれる
- スプレッドシートを簡易的なDBとして利用できる
- 関数単位で簡単にAPIのエンドポイントを作れる
hubotなどを利用する場合は、node.jsが利用できるサーバを用意してコードをデプロイして、データに変更があればJSONを修正して..など準備も運用もそれなりに大変です。
GASを使うことによって、準備・運用も簡単になるのでツールを作る際のハードルがかなり下がります。
ということで、今回はslackで「くじ引き」を行ってくれるbotを作りました。
ユースケース
次の2パターンで使えます。
- レビュアーや〇〇当番など、チームメンバーをランダムで選びたいとき
- 与えたリストからランダムで要素を選んで欲しいとき
メインは1ですが、特定のメンバー達から選んで欲しいとき・特定のメンバーを除外したいときは2が使えます。
作成手順
詰まった場合はこちらの記事を参考にしてください。
GASとSlackアプリの準備
- 新しくGoogle Apps Script(以下gas)とスプレッドシートを作成して、スプレッドシートのIDとURLを控えておく
Google Drive > 左上の「新規」ボタンから作成できる - gasをウェブアプリとして公開したURLを控えておく
右上の「デプロイ」から「種類の選択:ウェブアプリ、アクセスできるユーザ:全員」にしてデプロイ
- slack apiからslackアプリを作成する
- slackアプリのVerification Tokenを控えておく
Settings > Basic Information > App Credentials > Verification Token - slackアプリに任意のslashコマンド(
/member_kuji
など)とgasの公開URLを登録する
Features > Slash Commands > Create New Command
データの準備
事前に作成したスプレッドシートをチームメンバーの情報を保存するDBとして利用します。
スクリーンショットのようにデータを入れてください。
- メンバーの情報を
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
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関数を実行すればスプレッドシートに認証が求められます)
コードの説明
最後にコードの説明を軽くします。
最終的なコードはこちら
claspというgasのコードをローカルで開発できるようにするツールを利用しています。
(gasにコードをpush、gasからコードをpullできる)
併せてwebpackとtypescriptが利用できるようにしています。
メイン部分のロジックが記載されている 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;
};
- doPost関数
gasをウェブアプリとして公開した際、リクエスト時に実行される関数になる。(gasの仕様)
- tokenの検証 コード上のverification tokenとslackからのリクエストのtokenが合致しているか確認している
- slack apiパラメータ
botとのやり取りをチャンネルのメンバーに公開するかのオプション。他にも色々ある - slackのコマンド入力時にリストを渡された時の処理
リストからランダムに要素を抽出して返している - slackのコマンド入力時にリストを渡されなかった時の処理
スプレッドシートからチームメンバーの情報を取得している - gasの管理画面から実行ログを確認することができる
以上です。