Python3系でSlack Botの作成〜基礎的な対話を実装する

# はじめに
PythonでSlackのBotを作成する機会があったので、自分のメモも兼ねて、Botユーザ作成〜基本的なやりとりの実装について記載します。

# 環境
- Ubuntu 16.04.5
- Python 3.5.2
    - slackbot 0.5.3
- Slack
    - SlackのUIなどはバージョンにより変わる可能性有り

# Slack Bot利用の事前準備
## Botユーザを作成する
[こちら](https://api.slack.com/)にアクセスし、「Create ap app」をクリックします。
<img width="1242" alt="スクリーンショット_2018-10-26_18_59_02.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/fca77190-8040-e72b-2f53-2f86bf9b458a.png">

Bot名(チャンネル上で表示される名前ではないです)と追加するワークスペースを指定して「Create App」をクリックします。
<img width="562" alt="8-10-26_18_59_31.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/2334122e-2f52-728b-3017-cc7a61db49d0.png">

## Botをユーザとして認識させる
Botをユーザとして認識させるためには、まずBotに権限を設定する必要がある。以下の画像に示す「permission scope」をクリックします。
<img width="1246" alt="スクリーンショット 2018-10-26 19.12.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/f23cb481-0bb6-2b02-8992-5e9b929b63b6.png">

作成したBotの用途に合わせて権限を選択します。
例えば「メッセージの変更ができるような権限」「メッセージをPinする権限」などです。(今回はAdmin権限で作成)
<img width="1245" alt="スクリーンショット 2018-10-26 19.12.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/2493d305-b671-f4a2-8d59-7ddb0ad1333b.png">

先ほどの画面に戻ると「Install App to Workspace」が有効化されているので選択し、以下の画像のような画面に遷移するので「Authorize」をクリックします。。
<img width="1247" alt="スクリーンショット_2018-10-26_19_17_00.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/01962370-9fd8-5cd2-49e7-d435ea78ca15.png">

Botのユーザとしての表示名と「Always Show My Bots as Online」をOnに設定します。(必要に応じてアイコンも設定しておく)
<img width="1239" alt="スクリーンショット 2018-10-26 19.51.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/44f4d982-ac82-a8e7-5991-5f2da1d9e7f2.png">

これでチャンネルに招待できるようになりました。
<img width="299" alt="スクリーンショット_2018-10-26_19_54_07.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/09dd6e4b-561c-f152-5198-361f2e354559.png">

#　Botの作成
導入部分は[こちら](https://qiita.com/sukesuke/items/1ac92251def87357fdf6)の記事を参考にさせていただきました。

## slackbotパッケージのインストール
以下のコマンドでslackbotパッケージをインストールします。

```
$ pip3 install slackbot
```

## Botの初期設定
以下のような構成でファイル/ディレクトリを作成します。

```
root@ubuntu-xenial:/vagrant_data/slack_bot# tree
.
├── plugins                # botで動かすスクリプトを格納するディレクトリ
├── run.py                 # 実行のトリガーとなるスクリプト
└── slackbot_settings.py   # botの設定を記述するファイル
```

run.pyとslackbot_settings.pyの中身はそれぞれ以下のようになっています。

```run.py
# coding: utf-8

from slackbot.bot import Bot

def main():
    bot = Bot()
    bot.run()

if __name__ == "__main__":
    print('starting slackbot')
    main()
```

```slackbot_settings.py
# coding: utf-8

# SlackのAPIを利用するためのトークン
# Botの設定ページから「OAuth & Permissions」のページに遷移し、
# 「Bot User OAuth Access Token」をコピーして貼り付ける
API_TOKEN = "*****************************"

# 対応するメッセージがなかった場合に反応するメッセージ
DEFAULT_REPLY = "I dont't understand you."

# Botが実行するスクリプトを配置するディレクトリパスのリスト
PLUGINS = ['plugins']
```

## Botの起動
`run.py` を実行してBotを起動します。

```
root@ubuntu-xenial:/vagrant_data/slack_bot# python3.5 run.py
starting slackbot
```
<img width="376" alt="スクリーンショット_2018-10-26_20_22_47.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/069d4743-5bdc-172a-87d5-9da2285173ec.png">
無事返事が返ってきました。

# Botスクリプトの実装
## 特定の単語に反応するようにする
Botの基本です。Botといえば、何かしら命令してその処理を行わせるものですが、その命令を読み取るための仕組みがこちらになります。

pluginsディレクトリの下に、Botが実行するスクリプト `hello.py` (名前はなんでも良い) と空ファイルの `__init__.py` を作成してください。 `__init__py` が存在しないと動かない点に注意してください。

```
root@ubuntu-xenial:/vagrant_data/slack_bot# tree
.
├── plugins
│   ├── hello.py
│   ├── __init__.py
〜以下略〜
```

```hello.py
# coding: utf-8

from slackbot.bot import respond_to

@respond_to('Hello')
def reply_hello(message):
    message.reply('Hello')
```

<img width="227" alt="スクリーンショット_2018-10-26_20_43_52.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/d9975449-d90a-a2f6-9fc2-6d83081c001f.png">
無事こちらのHelloに反応して、Helloと返してくれました。

## メッセージを変数として受け取る
受け取りたいメッセージが固定の文字列だけとは限らないと思います。
その場合、渡すメッセージを変数として定義し、その内容に応じて処理をさせる、ということもできます。

respond_toの中で `()` で囲んだ部分が変数となり、関数の引数で定義した変数名で参照することができます。以下の例では、 `.*` で何かしらの文字列を読み取り、それを変数 `arg` に格納しています。

```
@respond_to('(.*)')
def reply_hello(message, arg):
    message.reply(arg)
```

<img width="337" alt="スクリーンショット_2018-10-26_22_32_02.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/e518b430-45b5-b417-f277-493afb17db4e.png">

以下のように、変数は複数個別々に定義することも可能です。
例えばコマンドとオプションを別々に渡す、というような状況で使えます。

```
@respond_to('(.*) (.*)')
def reply_hello(message, arg1, arg2):
    message.reply(arg1 + ': ' + arg2)
```
<img width="329" alt="スクリーンショット_2018-10-26_22_37_33.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/59d62b4f-b259-0dc0-2f74-5fbec594b254.png">
定義した通り、コロンで区切ってメッセージを返してくれました。

## メッセージを正規表現で表す
反応するメッセージは正規表現で表すことも可能です。
詳しくは[Pythonの正規表現](https://docs.python.jp/3/library/re.html)を参照してください。

下記の例だと `[a-zA-Z]` はアルファベットの文字列、 `\s` はスペースを表します。

```
@respond_to('[a-zA-Z]\sうさぎ')
def reply_hello(message):
    message.reply('どうしたの？')
```

<img width="351" alt="スクリーンショット_2018-10-26_22_44_40.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/2ea93557-1cad-9bb6-48cd-f84daaf66dbe.png">
スペースを含まなかった場合には反応せず、スペースを含んだ文字列には反応してくれました。

## 記号を含むメッセージに反応させる
変数を別々に渡すことで、コマンドとオプションのように読み取ることができる、と書きましたが、記号はそのままでは読んでくれません。

記号を使うときは `\` でエスケープが必要になります。
下記の例ではハイフンをエスケープ (`\-`) しており、何かしらの文字 + スペース + ハイフン付きのアルファベットに反応します。

```
@respond_to('(.*)\s(\-[a-zA-Z])')
def reply_hello(message, command, arg):
    message.reply(command + ' ' + arg + 'ですね')
```
<img width="357" alt="スクリーンショット_2018-10-26_22_53_53.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/63c39a47-523e-5a62-63eb-bac61cb339bc.png">

## 前の発言にスタンプをつける
slackといえばスタンプです！スタンプがつくと嬉しいと思うので、Botにスタンプを押してもらいましょう。

```
@respond_to('(スライムベス|スライム)')
def reply_hello(message, name):
    if name == 'スライムベス':
        message.react('slimebess')
    elif name == 'スライム':
        message.react('slime')
```
<img width="276" alt="スクリーンショット_2018-10-27_0_46_06.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/357eb231-aa89-2059-03ab-b25ce6835268.png">


## ファイルを添付する
何か処理をし、その結果のログやファイルなどをファイルとして取得する…みたいな処理もさせることができます。

以下の例では `get_file ~~~.txt` にマッチするメッセージを送ると、ファイルが返ってくるという仕組みです。

```
@respond_to('get_file\s([a-zA-Z]*\.txt)')
def reply_hello(message, file_name):
    # fname: slack上で表示するファイル名
    # fpath: ファイルが置いてあるパス
    # initial_comment: slack上で表示するコメント
    message.channel.upload_file(fname=file_name, fpath=file_name, initial_comment=file_name + 'です')
```
<img width="301" alt="スクリーンショット_2018-10-26_23_21_15.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/1b7e9684-96c1-3904-4fa6-b33c489461c2.png">


zipファイルだとこのような表示になります。
<img width="318" alt="スクリーンショット_2018-10-27_1_37_29.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/0740c342-39a3-73b9-679b-5071f52d56dc.png">

## メッセージを整形する
Botからのメッセージを整形することができます。
使えるパラメータは[こちら](https://api.slack.com/docs/messages/builder?msg=%7B%22attachments%22%3A%5B%7B%22fallback%22%3A%22Required%20plain-text%20summary%20of%20the%20attachment.%22%2C%22color%22%3A%22%2336a64f%22%2C%22pretext%22%3A%22Optional%20text%20that%20appears%20above%20the%20attachment%20block%22%2C%22author_name%22%3A%22Bobby%20Tables%22%2C%22author_link%22%3A%22http%3A%2F%2Fflickr.com%2Fbobby%2F%22%2C%22author_icon%22%3A%22http%3A%2F%2Fflickr.com%2Ficons%2Fbobby.jpg%22%2C%22title%22%3A%22Slack%20API%20Documentation%22%2C%22title_link%22%3A%22https%3A%2F%2Fapi.slack.com%2F%22%2C%22text%22%3A%22Optional%20text%20that%20appears%20within%20the%20attachment%22%2C%22fields%22%3A%5B%7B%22title%22%3A%22Priority%22%2C%22value%22%3A%22High%22%2C%22short%22%3Afalse%7D%5D%2C%22image_url%22%3A%22http%3A%2F%2Fmy-website.com%2Fpath%2Fto%2Fimage.jpg%22%2C%22thumb_url%22%3A%22http%3A%2F%2Fexample.com%2Fpath%2Fto%2Fthumb.png%22%2C%22footer%22%3A%22Slack%20API%22%2C%22footer_icon%22%3A%22https%3A%2F%2Fplatform.slack-edge.com%2Fimg%2Fdefault_application_icon.png%22%2C%22ts%22%3A123456789%7D%5D%7D)を参照してください。

```
@respond_to('usagi')
def reply_hello(message):
    attachments = [
        {
            "color": "#3104B4",
            "fields": [
                {
                    "title": "場所",
                    "value": "東京駅"
                },
                {
                    "title": "時間",
                    "value": "19:00"
                }
            ],
            "footer": "usagi-san",
            "footer_icon": "https://pics.prcm.jp/db36726f85742/67433428/jpeg/67433428.jpeg"
        }
    ]
    message.send_webapi('集合場所', json.dumps(attachments))
```

<img width="244" alt="スクリーンショット_2018-10-27_0_12_15.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/75ee6940-9e04-43ae-7d5e-7145f4dc3c7c.png">


`short` をつけることで、以下のように横に並べて表示することもできます。

```
@respond_to('usagi help')
def reply_hello(message):
    attachments = [
        {
            'color': "#FF8000",
            'fields': [
                {'title': "コマンド", 'value': "usagi help", 'short': True},
                {'title': "説明", 'value': "ヘルプを表示します", 'short': True},
            ]
        }
    ]
    message.send_webapi('コマンド一覧', json.dumps(attachments))
```

<img width="494" alt="スクリーンショット_2018-10-27_1_18_20.png" src="https://qiita-image-store.s3.amazonaws.com/0/113160/3a9055fc-d214-8fc9-5251-be6f8332f8a4.png">


# おわりに
本記事ではPythonでslackのBotを作成する方法を記載しました。

このようにメッセージを渡し、そこから他のAPIと連携してJenkinsのジョブのビルドしたり、AWSのAPI Gatewayを叩いたりできます。
使っていく中でまた何かあれば、追記(もしくは別の記事にて)しようと思います。

また「こういう使い方は便利だよ」とか「こういう風に書いた方が良いよ」みたいな指摘も大歓迎です。

# 参考ページ
- [Github - lins05/slackbot](https://github.com/lins05/slackbot/tree/develop/slackbot)
- [Slack API](https://api.slack.com/web)
- [Pythonの正規表現操作](https://docs.python.jp/3/library/re.html)
- [Qiita - PythonのslackbotライブラリでSlackボットを作る](https://qiita.com/sukesuke/items/1ac92251def87357fdf6)