【GAS】+【Slack api】で多機能なQ&Aボットを作ってみた

# はじめに
私は[株式会社アドベンチャー](http://jp.adventurekk.com/)にて、[skyticket](https://skyticket.jp/)を開発しています。
今回、ナレッジ共有を目的にGAS(Google Apps Script)とSlack apiを利用した【Q&Aボット】を作成してみました。
# 作成背景
**人は忘れる生き物です。**
職場でナレッジ資料を作り「ここにあるからね！」とURL周知しても
数日後に「〇〇の件どこに書いてある？」という質問が必ずやってきます。
そして数ヶ月後、ナレッジ共有した本人も忘れて探し始めます。

**人は怠ける生き物です。**
「資料があそこにある」と分かっていても、つい知ってそうな人に質問してしまうものです。
「知らないだろうな」と思いつつダメ元で聞く事も。
探しているものは、たいていどこかで見聞きした覚えのある資料だったりします。
要するに調査の『取っ掛かり』をくれる相談相手が欲しいのです。

**【解決策】**
そこでQ&Aボットの出番です。
ボットは忘れません。
嫌な顔ひとつせず24時間相談に応えてくれます。
GASならベースとなるスプレッドシートを共有すれば誰でも簡単に質問・回答を追加できます。
人は「ボットに聞いてくれ」のワンフレーズだけ覚えればいいのです。
# 機能説明
**1：質問に対する回答機能**
Slackでキーワードを投稿すると、対応する情報を返してくれる。
（質問と回答が `1 対 1` ）
![Usshi 0652.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/740ab301-855a-a7cc-f175-51ee1ef8c93f.png)
**2：抽選機能**
「メンテナンス当番を抽選で決めたい」場合などご自由に。
（質問と回答が `1 対 n`）
![Usshi 0655.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/94dc5f36-c188-ec57-2886-d84a664845b5.png)
**3：応援機能**
ネガティブなワードを投稿するとミス◯ル風の励ましポエムをランダムで返してくれます。
暇を持て余して作りました。ピンとこない方は無視してください。
(質問と回答が `n 対 n` ）
![Usshi 0655.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/1355edde-42dd-cb5a-99ff-154e8a29dbf1.png)
障害発生してしんどい、もうだめ、という職場環境でないことを忘れずに補足。
むしろホワイトです（なぜこの機能を作ったのだろうか）。
# 作成手順
**▼参考**
[Slack+GASでＱ&Aボットを作ってみた](https://qiita.com/tatsuya1970/items/43d72b16c9c11bef12a3)
[【GAS】スプレッドシートのランダムな行データを取得してツイート（GAS×TwitterBot⑤）](https://moripro.net/gas-spreadsheet-random-tweet/)
[【GAS】Google Apps ScriptでSpreadSheetのシートを指定する方法を解説](https://mo-gu-mo-gu.com/how-to-specify-sheet-by-gas/)

**▼素材**
[いらすとや](https://www.irasutoya.com/)
### Slack apiの作成
[こちら](https://api.slack.com/apps)でメッセージ投稿用のSlack apiを作成します。

ログインしたらCreate New Appを押下。
![slack api.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/3818bdcc-a80b-6642-6048-2b4582ddbf52.png)
From an app manifestを選択。
![Create an app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/a09a87e6-770b-67eb-7179-39429cfae6c5.png)
ボットを追加するSlackワークスペースを選択。
![Pick a workspace to develop your app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/9e71ad54-5745-5dcd-e0e5-90b70197a71a.png)
Nextを押下。
![Enter app manifest below BETA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/70b15a1c-96a8-f866-31e9-8d1b1ff2e2c0.png)
Createを押下。
![Review summary & create your app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/20130f9e-6115-960d-e111-e399ffd23d3e.png)
Incoming Webhooksを押下。
![Building Apps for Slack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/8c3fa9b4-9b67-8cf1-0778-5b03748f9e74.png)
メッセージ投稿用のWebhook URLを取得するため
まずはOnを押下。
![Incoming Webhooks.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/08799018-8266-1a1e-cc4c-a9ddea70ec72.png)
Add New Webhook to Workspaceを押下。
![If you deactivate incoming wethooks, new Wethook URLs will not be generated when.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/4cf58d2d-d0ad-8d3b-5bc5-e5e1232da5b1.png)
投稿先のチャンネルを求められるので、選択して「許可する」を押下。
![Pasted Graphic 18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/821d55fd-1e9f-d9f1-70c3-6359bbaba517.png)
アプリ作成時に使用するためWebhook URLをCopyして控えておく。
![Webhook URLs for Your Workspace.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/2e9f2e88-1938-5508-9a45-da1c8e1934bb.png)
こちらでSlack上の表示名を変更可能。
![Features.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/05634c33-cac4-c867-9549-ff45c7b0581a.png)

### GASでボットアプリを作成
googleスプレッドシートをベースにGASでボットアプリを作成します。

スプレッドシートのブックを作成し、機能別にシートを作成する。
![Pasted Graphic 21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/79f851ab-e222-81a4-5889-a7c6c1c24f29.png)
・[質問に対する回答機能](https://qiita.com/drafts/0cc818d41c2a81e206e0/edit#:~:text=%E6%8C%81%E3%81%9F%E3%81%9B%E3%82%8B%E6%A9%9F%E8%83%BD-,1%EF%BC%9A%E8%B3%AA%E5%95%8F%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E5%9B%9E%E7%AD%94%E6%A9%9F%E8%83%BD,-Slack%E3%81%A7%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89)用シート
質問キーワードをA列、対応する回答をB列に入力。
![Poem Answer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/b9891a84-a01f-e677-9fbb-d38231769228.png)
・[抽選機能](https://qiita.com/drafts/0cc818d41c2a81e206e0/edit#:~:text=%E8%BF%94%E3%81%97%E3%81%A6%E3%81%8F%E3%82%8C%E3%82%8B%E3%80%82-,2%EF%BC%9A%E6%8A%BD%E9%81%B8%E6%A9%9F%E8%83%BD,-%E3%80%8C%E3%83%A1%E3%83%B3%E3%83%86%E3%83%8A%E3%83%B3%E3%82%B9%E5%BD%93%E7%95%AA%E3%82%92)用シート
抽選対象をA列に入力。
![Pasted Graphic 25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/9de3d919-24ef-de98-7d70-069a269eb779.png)
・[応援機能](https://qiita.com/drafts/0cc818d41c2a81e206e0/edit#:~:text=%E3%81%94%E8%87%AA%E7%94%B1%E3%81%AB%E3%80%82-,3%EF%BC%9A%E7%99%92%E3%81%97%E6%A9%9F%E8%83%BD,-%E3%83%8D%E3%82%AC%E3%83%86%E3%82%A3%E3%83%96%E3%81%AB%E3%81%A4%E3%81%B6%E3%82%84%E3%81%8F)用シート
2つ作ります。
▼1つ目のシート
応援対象となるネガティブキーワードをA列に入力。
![Poem Answer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/e0b6bfe1-7a7f-1e17-1018-f427352f4737.png)
▼2つ目のシート
応援メッセージ用のポエムをA列に入力。
見返すと目も当てられない内容だったので黒塗りしました。
皆さんなりの応援メッセージを募集しております。
![Pasted Graphic 60.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/5c82073e-54f6-9d96-0214-7b95422fdb8e.png)

シートを作成したらいよいよGASでの作業。

スプレッドシートの拡張機能からApps Scriptを選択。
![Pasted Graphic 26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/ec2f21e7-d2c3-fcf9-34d1-6aa8bb7bb387.png)
コードを記述。
![Pasted Graphic 27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/bed2b838-80ca-903c-71de-520dd6ae098d.png)
### コード
```javascript:
var HTTP_METHOD_POST = 'post';
var LOTTERY_KEY_WORD = '今週の当番';
var TARGET_COLUMN_NUM_1 = 1;
var TARGET_COLUMN_NUM_2 = 2;
var UNKNOWN_ANSWER_TEXT = '分かりません:coffee:';

function doPost(e){

  // 疎通確認
  const params = JSON.parse(e.postData.getDataAsString());
  if('challenge' in params){
    return ContentService.createTextOutput(params.challenge);
  }

  // Botの投稿に反応しない
  if('subtype' in params.event) {
    return;
  }

  let contents = "";
  let postOption = "";
  if('text' in params.event) {
    let answer;

    const activeSpreadSheet = SpreadsheetApp.getActiveSpreadsheet();
    let sheet = activeSpreadSheet.getSheetByName('Poem_Keyword');
    // 受信メッセージが応援対象であるか判定
    const pkSearchRes = vlookup(params.event.text, sheet, TARGET_COLUMN_NUM_1);

    if(pkSearchRes !== UNKNOWN_ANSWER_TEXT){
    // 応援対象の場合はランダムでポエム返却
      sheet = activeSpreadSheet.getSheetByName('Poem_Answer');
      answer = getRandomRow(sheet, TARGET_COLUMN_NUM_1) + ' :coffee:';
    }else if(params.event.text === LOTTERY_KEY_WORD){
    // 抽選キーワードを受信した場合はランダムで結果返却
      sheet = activeSpreadSheet.getSheetByName('Member');
      answer = ':tada: ' + getRandomRow(sheet, TARGET_COLUMN_NUM_1) + ' :confetti_ball:';
    }else{
    // 上記に当てはまらない場合は質問とみなし対応する回答を返却
      sheet = activeSpreadSheet.getSheetByName('Q&A');
      answer = vlookup(params.event.text, sheet, TARGET_COLUMN_NUM_2);
    }

    const userName = params.event.user;
    contents = `<@${userName}> \n` + answer; 
  }

  postOption = makeHttpOption(HTTP_METHOD_POST, contents);

  // メッセージ投稿
  UrlFetchApp.fetch("SlackのWebhookURL", postOption);
}

// エクセルvlookupのような関数
function vlookup(value, sheet, columnNum) {
  let returnValue = UNKNOWN_ANSWER_TEXT;
  for (let i = 1; i <= sheet.getLastRow(); i++) {
    if(value === sheet.getRange(i,1).getValue()){
      returnValue = sheet.getRange(i,columnNum).getValue();
      break;
    }
  }    
  return returnValue;
}

// シートと列を指定してランダムに行を抽選
function getRandomRow(sheet, columnNum) {
  const lastRow = sheet.getLastRow();
  const rowNum = Math.ceil(Math.random() * (lastRow));  
  const rowTxt = sheet.getRange(rowNum, columnNum).getValue()
  return rowTxt;
}

// HTTPオプション作成
function makeHttpOption(httpMethod, contents) {
  const optionData =
  {
    "method" : httpMethod,
    "contentType" : "application/json",
    "payload" : JSON.stringify(
      {
        "text" : contents,
        link_names: 1
      }
    )
  };
  return optionData;
}
```
画面右上のボタンからデプロイ。
![Pasted Graphic 28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/6f66e8bc-878d-e3ea-50a8-425df47efe6e.png)
モーダルが表示されるのでウェブアプリを選択。
![Pasted Graphic 29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/7300c0c6-1099-b553-fe20-612755b1f5fe.png)
以下のように入力・選択しデプロイ実行。
![first deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/ac1973f2-000a-468d-fe94-bdbfe1fe53a6.png)
アクセスを承認。
![Pasted Graphic 31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/d228b5fc-e5fc-9ebe-9f70-872906e68002.png)
Googleアカウントを選択すると
アクセス許可を求められる。
![Google hasn't verified this app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/564a7713-40ba-bcc1-6ed3-f29ebbbc48c0.png)
デプロイが完了したらウェブアプリURLをコピーする。
![Pasted Graphic 44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/af476377-9c97-f56c-70a3-18c5a9b69e9c.png)

### Slack api ・ Slackに作成したボットアプリを追加
ここから再びSlack側の作業です。

Slack apiのEvent Subscriptions画面で以下の設定を行い、Save Changesを押下。
- Enable Events：`On`
- Request URL：`GASアプリのデプロイ時にコピーしたURL`
- Subscribe to bot events：`message.channels`
![Event Subscriptions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/66e8acd9-1b41-7bbe-2b8a-df20a6f2595e.png)
画面上部に権限変更の注意文言が表示されたら`reinstall your app`を押下。
![Pasted Graphic 46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/66694a55-7708-03ad-2ae2-d0f1dfc64038.png)
メッセージ投稿先にするSlackチャンネルを指定して許可する。
![Slack 7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/5345312a-cbae-07c2-14db-10ebdb952917.png)
指定チャンネルのインテグレーションに作成したAPIが追加される。
![Usshi 0833.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/f58c567b-2bfe-88d6-176f-ac96c645d9f3.png)
Slackチャンネル設定画面のインテグレーションで`アプリを追加する`を押下。
![Pasted Graphic 50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/22f63966-7e3f-65b7-c92a-4ab2d7fe7d03.png)
作成したアプリの`追加`を押下。
![Demo App.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/3900b7ba-ccdc-342d-6f41-0f7613aaf2c0.png)
チャンネルにボットがアカウント追加されたら完成！
![Pasted Graphic 53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2954530/ac97b961-2c8a-29b9-77b1-a8118692e7e1.png)
# 最後に
名曲を聴き、~~くだらない~~ポエムを考えながら思いました。
人の心を動かしてこそ、優れたプロダクトなのだと。
便利なだけでは記憶に残りません。
そう、応援機能がキモでした。何事もセンスです。

最後に真面目な事を言うと、
キーワードの「曖昧検索」に対応するのを忘れていました。
気が向いたら更新します。

読んでくださった皆さま、ありがとうございました。