たった10分で雑談Slack Botを作成して導入する方法

# 追記
本記事で紹介する「Chaplus雑談対話API」の後継となるAPIを公開しました。下記の記事で紹介をしているので、合わせてこちらもご覧ください。
https://qiita.com/maKunugi/items/14f1b82a2c0b6fa5c202
本記事で紹介するAPIよりも精度が向上している他、文脈を考慮した会話ができるようになっています。また、実装不要でSlack Botでの利用もできるようになりました。

<img width="933" alt="スクリーンショット 2020-03-13 23.41.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66341/e2865530-bb15-df04-12cb-0156f81cae4b.png">

# 対象読者
- Slackボットの作成に興味がある方
- Slackチャンネルの雰囲気を和ませたい方
- Slackチャンネルに雑談ができるBotを入れてみたい方

# 概要

Slack Bot系の記事はネット上に多く存在しているので目にすることも多いと思いますが、この記事では「雑談ができるSlackボット」を素早くお使いのチャンネルに導入する方法を紹介しています。

# どんな雑談Slack Botを作るのか
- 一息つきたいときにたわいもない雑談ができる
- チャンネルの趣旨に合わせて手軽にセリフのカスタマイズができる
- 飽きてきたら口調を変えられる

# 作り方
さっそく作り方を解説していきます。（所要時間、10分程度）

## 利用する技術
- Google Apps Script (GAS)

GASでSlackチャンネルへの投稿を受け取りログを残しつつ、雑談APIから応答文を取得してSlackに投稿します。

- [Chaplus API - 雑談対話API](https://www.chaplus.jp/)

Chaplus APIというユーザの発話に対しての応答を取得できるAPIを利用します。
様々な雑談が可能なことに加え、ボットのセリフのカスタマイズや口調の変更等ができます。
Chaplus APIは下記の記事で詳しく紹介をしています。
https://qiita.com/maKunugi/items/b1afb6441571119729a7

## Slackワークスペースでカスタムインテグレーションを準備する

お使いのSlackワークスペースの設定から今回利用するカスタムインテグレーションを設定していきます。

今回利用するカスタムインテグレーションは以下の３つです。
- Incoming Webhook
- Bots
- Outgoing Webhook

![](https://i.imgur.com/NHRBuwB.png)

### 1. Incoming Webhookを準備する

お使いのSlackワークスペースの下記アドレスにアクセスします。
https://<ワークスペース名>.slack.com/apps

![](https://i.imgur.com/L5Nrf4v.png)

「マストアプリを追加」をクリックします。

アプリ一覧から「Incoming Webhook」を探して選択します。
Incoming WebhookはSlackボットがチャンネルにコメントを投稿するために利用します。

![](https://i.imgur.com/B3jJQJz.png)

「Incoming Webhook」を見つけたら「Slackに追加」をします。
追加すると利用するチャンネル名を聞かれるので、今回作る雑談ボットを動かすチャンネルを選択しましょう。

次に「Incoming Webhook」の設定をします。
![](https://i.imgur.com/URSMSRT.png)

設定するのは先ほど設定したチャンネルに加えて下記の内容です。

- 名前をカスタマイズ
Slackに投稿されたときに表示されるBotの名前になります。

- アイコン
Slackに投稿する際のボットのアイコンです。

Webhook URLは後ほどGASを記述する際に利用するので控えておいてください。
設定を入力したら「設定を保存する」を押して「Incoming Webhook」の設定は終了です。

## 2. Bots を準備する

次にアプリ一覧から「Bots」を追加します。

![](https://i.imgur.com/mqayeP8.png)
「Slackに追加」を押しましょう。
このBotsインテグレーションは、何かの動作をさせるというよりは、利用するチャンネルに参加しているように見せかけることや、メンションの候補に作成するボットが表示されるようにするためだけの用途に追加をします。

設定する項目は下記です。

- 名前
ボットの名前になります。Incomming Webhookで設定した名前を設定しましょう。

- アイコン
ボットのアイコンです。こちらもIncoming Webhookで設定したアイコンを設定しましょう。他の

その他の項目に関しては今回利用しません。

![](https://i.imgur.com/refIzTL.png)


Botsを利用するとSlackのチャンネルに作成したBotを招待できるようになります。
Botsインテグレーションを作成しておくと、下記の画像のようにメッセージ送信の際に「@」で名前が補完されるようになります。

![](https://i.imgur.com/XXkOHla.png)

以上でBotsインテグレーションの設定は終了です。


## 3. Outcomming Webhook を準備する

最後のカスタムインテグレーションの追加になります。
アプリ一覧から「Outgoing Webhook」を選択して追加します。

![](https://i.imgur.com/SAOSgVf.png)

Outgoing WebhookはSlackチャンネルでユーザの投稿があったことを検知するために利用します。

設定する項目は下記です。Outgoing Webhookに関してはGAS記述後再度設定をしに戻ってきます。このタブは残しておきましょう。

- 引き金となる言葉
ボットが反応をするきっかけとなる言葉です。
今回は人間のユーザと同様、「@bot名」のメンションの形でメッセージが投稿されたら反応をすることを想定します。ただし、Slackの仕様上「@+bot名」はこの「引き金となる言葉」に直接設定することができません。(@usenameはエンコードされ別文字列扱いになってしまうためです。) 一旦「sample_bot:」のような適当な文言を入れておきます。GASを使った後にもう一度設定をしに戻ってきます。

- URL
GASで記述したスクリプトをデプロイして生成されたURLを入力します。こちらもGASの準備が完了したら設定をしに戻ってきます。

- トークン
この後のGASでスクリプトを記述する際に利用します。控えておきましょう。

![](https://i.imgur.com/QxgIGYA.png)


## GASを使ってSlackの投稿をハンドリングする

### スプレッドシートの用意
![](https://i.imgur.com/zFG8qaw.png)

Slackの投稿を管理するスプレッドシートを用意します。
用意ができたら、「スクリプトエディタ」を開きましょう

![](https://i.imgur.com/XyU8MnB.png)

### Slackの投稿を受け取る

まずはボットを利用するSlackチャンネルの投稿を受け取り、スプレッドシートに投稿内容を出力する処理を記述しましょう。

```javascript

function doPost(e) {
  if (e.parameter.token != "<Outcoming Webhookのtoken>") {
    return;
  }
  
  var text = e.parameter.text;
  appendRow(text);
}

function appendRow(text) {
  var spreadsheetId = "<スプレッドシートのID>";
  var sheetName = "<シート名>";
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName(sheetName);
  sheet.appendRow([new Date(),text]);
  return text;  
}
```

スプレッドシートのIDはスプレッドシートファイルのURLに記載があります。
```例: https://docs.google.com/spreadsheets/d/<スプレッドシートのID>/edit#gid=0```

tokenやスプレッドシートのID、シート名は任意に変更してください。

ここまで記述したら、一旦スクリプトをデプロイします。
![](https://i.imgur.com/dzm4sMM.png)

「公開」-> 「ウェブアプリケーションとして導入」を選択します。
![](https://i.imgur.com/z85EA9P.png)

![](https://i.imgur.com/7GCbsZl.png)

公開をするとアプリケーションのURL(Current web app URL)が取得できます。
こちらのURLをOutgoing Webhookの**URL**に登録します。

※ GASを編集した場合、編集した機能をSlackで利用するには**編集後に毎回デプロイをする必要があります**。(念の為)

![](https://i.imgur.com/Wud7dPD.png)

上記までが完了したら、「引き金となる言葉」を使って、設定したSlackチャンネルで投稿をしてみましょう。

スプレッドシートに送信した文言が追加されるはずです。

![](https://i.imgur.com/EMcF95J.png)

![](https://i.imgur.com/RK2XuIT.png)

この動作検証のついでに、先ほど触れた「usernameがエンコードされてしまう」件を解決しましょう。「引き金となる言葉」に続けて「@ボットの名前」を入力し、Slackで投稿してみましょう。

![](https://i.imgur.com/cOTbM2N.png)

![](https://i.imgur.com/t4ORK5y.png)

すると、スプレッドシートにusernameがエンコードされた文字列が返ってきます。
この<@xxxxxx>という文字列を「引き金となる言葉」に登録をすると、「@bot名」を文頭につけた投稿に反応ができるようになります。再度Outgoing Webhookで設定をしましょう。

![](https://i.imgur.com/80PgTGJ.png)

### Slackに投稿する

Slackチャンネルへの投稿を受け取ることができたので、今度はGASからSlackへメッセージを投稿していきます。先ほど、@ボット名で投稿を引っ掛けられるようになったため、下記のサンプルではdoPostメソッドも少し修正しつつ、slackに投稿をするpostSlack()メソッドを実装しています。

```javascript

function doPost(e) {
  if (e.parameter.token != "<Outcoming Webhookのtoken>") {
    return;
  }
  array = e.parameter.text.split(" ");  
  if (array.length > 1) {
      // 一旦おうむ返しで投稿する
      postSlack(array[1], e.parameter.user_id);
      appendRow(array[1]);
  }
}

function appendRow(text) {
  var spreadsheetId = "<スプレッドシートID>";
  var sheetName = "<シート名>";
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName(sheetName);
  sheet.appendRow([new Date(),text]);
  return text;  
}

function postSlack(text, userId) {
  var url = "<Incoming WebhooksのURL>";  
  var payload = {
    text: "<@"+ userId + "> " + text
  };
  var options = {
    "method" : "POST",
    "headers": {"Content-type": "application/json"},
    "payload": JSON.stringify(payload)
  };
  UrlFetchApp.fetch(url, options); 
}
```

ここまで実装をしたら先ほど同様、デプロイ(ウェブアプリケーションの公開)をしてください。
Slackでボットに話しかけるとおうむ返しをするようになったはずです。

![](https://i.imgur.com/3ELXm3r.png)

### 投稿に対する応答文を取得する

ここまででSlackチャンネルへの投稿の取得と、Slackチャンネルへの投稿ができるようになりました。ただし、このままではユーザの投稿に対するおうむ返しなので、メッセージをユーザの投稿に対して柔軟に変更していきます。

この応答文を取得するには、先述したChaplus APIという雑談対話APIを利用します。

![](https://i.imgur.com/Pjwj6QB.png)

[公式サイト](https://www.chaplus.jp/)にて利用規約の確認とメールアドレスの登録を行い、メールに送られてくるAPI KEYを入手します。

※ 送られてくるメールが迷惑メールに振り分けられることがあるので注意

API KEYが取得できたらChaplus APIを利用して応答文を取得しましょう。
応答文を取得し、一番精度の高い返答を返すメソッドは以下のような実装になります。


```javascript

function getChaplusMessage(mes, username) {
  var dialogue_options = {
    'utterance': mes,
    'username' : username,
    'agentState' : {
      'agentName' : 'sample_bot',
      'age' : '0歳',
      'tone' : 'kansai'
    },
  }
  var options = {
    'method': 'POST',
    'contentType': 'text/json',
    'payload': JSON.stringify(dialogue_options)
  };
  var chaplusUrl = "https://www.chaplus.jp/v1/chat?apikey=<APIKEY>";
  var response = UrlFetchApp.fetch(chaplusUrl, options);
  var content = JSON.parse(response.getContentText());  
  return content.bestResponse.utterance;
}
```
API KEYは任意の値に変更してください。
上記では下記の項目を指定しています。

- utterance: ユーザの発話
- username: ユーザの名前
- agentState
    - agentName: ボットの名前
    - age: ボットの年齢(設定)
    - tone:ボットの口調

ボットの口調は、normal(default)、kansai(関西弁風)、koshu(甲州弁風)、dechu(赤ちゃん言葉風)を指定できます。(応答の変化の例は下記イメージ)

- 関西弁風
![](https://i.imgur.com/3Xb4kgi.png)
- 甲州弁風
![](https://i.imgur.com/FiAwXXx.png)
- 赤ちゃん言葉風
![](https://i.imgur.com/J7jAR46.png)


APIの仕様については詳しく触れません。下記の記事をご参照ください。
https://qiita.com/maKunugi/items/b1afb6441571119729a7

doPost()メソッドも少し変更します。

```javascript

function doPost(e) {
  if (e.parameter.token != "<Outcoming Webhookのtoken>") {
    return;
  }
  
  array = e.parameter.text.split(" ");  
  if (array.length > 1) {
    message = getChaplusMessage(array[1], e.parameter.user_name);
    appendRow(message);
    postSlack(message, e.parameter.user_id);
  }

}
```

これでユーザ発話に応じて柔軟な応答を返すようになりました！あっという間ですね。

![](https://i.imgur.com/FA7Wp7U.png)

#### GAS サンプル全文

それではここまでのGASの全文サンプルを掲載しておきます。

```javascript

function doPost(e) {
  if (e.parameter.token != "<Outcoming Webhookのtoken>") {
    return;
  }
  
  array = e.parameter.text.split(" ");  
  if (array.length > 1) {
    message = getChaplusMessage(array[1], e.parameter.user_name);
    appendRow(message);
    postSlack(message, e.parameter.user_id);
  }

}

function appendRow(text) {
  var spreadsheetId = "<スプレッドシートのID>";
  var sheetName = "<シート名>";
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName(sheetName);
  sheet.appendRow([new Date(),text]);
  return text;  
}

function postSlack(text, userId) {
  var url = "<Incoming Webhooks URL>";  
  var payload = {
    text: "<@"+ userId + "> " + text
  };
  var options = {
    "method" : "POST",
    "headers": {"Content-type": "application/json"},
    "payload": JSON.stringify(payload)
  };
  UrlFetchApp.fetch(url, options); 
}

function getChaplusMessage(mes, username) {
  var dialogue_options = {
    'utterance': mes,
    'username' : username,
    'agentState' : {
      'agentName' : 'sample_bot',
      'age' : '0歳',
      'tone' : 'kansai'
    },
  }
  var options = {
    'method': 'POST',
    'contentType': 'text/json',
    'payload': JSON.stringify(dialogue_options)
  };

  var chaplusUrl = "https://www.chaplus.jp/v1/chat?apikey=APIKEY";
  var response = UrlFetchApp.fetch(chaplusUrl, options);
  var content = JSON.parse(response.getContentText());
  return content.bestResponse.utterance;
}
```

### 応答文をカスタマイズする (おまけ1)

ここまでで一旦雑談ができるBotの作成は完了しました。
ここからはChaplus APIを利用して応答をカスタマイズする方法を紹介します。

Chaplus APIは利用者がセリフを追加するためのリクエストパラメータ(utterancePairs)を提供しています。そのパラメータを利用することでセリフを追加できます。

```javascript

function getChaplusMessage(mes, username) {
  var utterancePairs = [
    {
      'utterance': 'コロナ心配だね・・・',
      'response' : '手洗いうがい・・・そしてリモートワークだよ！'
    }
  ];
  var dialogue_options = {
    'utterance': mes,
    'username' : username,
    'agentState' : {
      'agentName' : 'sample_bot',
      'age' : '0歳',
      'tone' : 'kansai'
    },
    'addition': {
        'utterancePairs': utterancePairs,
    },
  }
  var options = {
    'method': 'POST',
    'contentType': 'text/json',
    'payload': JSON.stringify(dialogue_options)
  };

  var chaplusUrl = "https://www.chaplus.jp/v1/chat?apikey=APIKEY";
  var response = UrlFetchApp.fetch(chaplusUrl, options);
  var content = JSON.parse(response.getContentText());
  return content.bestResponse.utterance;
}
```
utterancePairsには、ユーザの発話とそれに対する応答文を指定できます。
今回は、

```
発話: 「コロナ心配だね・・・」
応答: 「手洗いうがい・・・そしてリモートワークだよ！」
```
を指定してみています。APIのリクエストにutterancePairsを追加することでAPIの応答がカスタマイズできます。

![](https://i.imgur.com/sXSvaLu.png)

ある程度表記揺れや類似表現が考慮されるため、追加したセリフと完全に一致しなくともChaplus APIがよしなに判断をして応答を採用してくれます。(口調等も適応される)

ボットのキャラクター設定やチャンネルの文化等に応じてセリフを追加しましょう！

ちなみにですが、NGワードも設定できます。
APIに渡すリクエストパラメータにngwordsを文字列の配列で指定できます。

```javascript

  var dialogue_options = {
    'utterance': mes,
    'username' : username,
    'agentState' : {
      'agentName' : 'sample_bot',
      'age' : '0歳',
      'tone' : 'kansai'
    },
    'addition': {
      'utterancePairs': utterancePairs,
      'ngWords': [
        "やる気ない",
        "知らんけど",
      ],
    },
  }
```

NGワードに指定されたワードが含まれる応答はAPIから返されなくなります。

### 応答候補をスコア付きで表示してみる (おまけ2)

Chaplus APIはユーザ発話に対する応答の候補をScore付きで複数返しています。
先ほどは応答候補の中でもっともScoreの高いBestResponseをSlackに投稿をしていました。
需要があるかはわかりませんが、応答候補全てをスコア付きで返す方法も紹介します。

```javascript


function getChaplusMessage(mes, username) {
  var utterancePairs = [
    {
      'utterance': '新型コロナどうなるんだろう・・・',
      'response' : '手洗いうがい・・・そしてリモートワークだよ！',
    }
  ];
  var dialogue_options = {
    'utterance': mes,
    'username' : username,
    'agentState' : {
      'agentName' : 'sample_bot',
      'age' : '0歳',
      'tone' : 'kansai'
    },
    'addition': {
      'utterancePairs': utterancePairs,
      'ngWords': [
        "やる気ない",
        "知らんけど",
      ],
    },
  }
  var options = {
    'method': 'POST',
    'contentType': 'text/json',
    'payload': JSON.stringify(dialogue_options)
  };

  var chaplusUrl = "https://www.chaplus.jp/v1/chat?apikey=APIKEY";
  var response = UrlFetchApp.fetch(chaplusUrl, options);
  var content = JSON.parse(response.getContentText());
  
  var answer = content.bestResponse.utterance;
  var index = 1;
  var answer = content.bestResponse.utterance + "(Score: " + content.bestResponse.score +  ")" + "\n```";
  content.responses.forEach(function( value ) {
    answer += index + "位(Score: " + value.score + ")"  + value.utterance + "\n";
    index += 1;
  });
  answer += "口調: " + content.agentState.tone + "\n";
  answer += "```";
  return answer;
}
```
レスポンスの```responses```の内容を全て出力してあげます。
(出力部分、雑に書いており汚くてすみません)

getChaplusMessage()を上記のように変えてあげると、下記の画像のように全ての応答候補が表示されるようになります。

![](https://i.imgur.com/mbeoJfn.png)

応答がごちゃごちゃしますが、ボットの頭の中を覗けます。
応答候補の下には、設定されている口調も表示しています。
<img width="1163" alt="スクリーンショット 2020-03-15 15.21.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66341/0795bfd1-9246-263e-b742-dfabde555455.png">


# まとめ

GAS + Chaplus API を利用してSlackの雑談ボットを作成する方法について紹介しました。SlackチャンネルのBotコミュニケーションに興味のある方の参考になれば幸いです！

![](https://i.imgur.com/1zD4aHK.png)