PythonのslackbotライブラリでSlackボットを作る

# 前置き
***このページは基本的な使い方を説明するページです。使い方のすべてを説明するわけではありません。***
Pythonのslackbotライブラリを用いれば、Slackで特定のメッセージを受け取って、処理を行ったり、返事を行うことができます。
本ページでは、slackbotの導入と基本的な使い方を紹介します。

また、文字を扱うことが多いので、デフォルトでユニコードを扱えるPython3系で書くことにします。
Python2系でも作れるが、文字の比較などでユニコード文字列指定を行うなど、めんどくさいからです。
以降、Pythonという言葉は、断りがない限りPython3系を指します。

# slackbotのインストール


``` shell
$ sudo apt-get install python3-pip  # pip3を導入済みなら不要
$ sudo pip3 install slackbot        # slackbotライブラリをインストール
```

これで、Pythonでslackbotをインポートできるようになる。

# slackbotを動かすための用意
slackbotは複数のファイルを用意する必要がある。
以下のようなディレクトリ構造にします。

```
slackbot         # プログラムをまとめるディレクトリ。名前はなんでも良い
├─ run.py        # このプログラムを実行することで、ボットを起動する
├─ slackbot_settings.py   # botに関する設定を書くファイル
└─ plugins                # botの機能はこのディレクトリに追加する
   ├─ __init__.py         # モジュールを示すためのファイル。空で良い
   └─ my_mention.py       # 機能を各ファイル。任意の名前で良い
```

現在slackbotディレクトリにいるとして、以下のコマンドを実行して、ファイルを作成する。

``` shell
$ touch run.py slackbot_settings.py
$ mkdir plugins
$ cd plugins
$ touch __init__.py my_mention.py
```

# slackbotの初期設定

`run.py`の中身を以下のようにする。

``` python
# coding: utf-8

from slackbot.bot import Bot

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

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

`slackbot_settings.py`の中身を以下のようにする。

``` python
# coding: utf-8

# botアカウントのトークンを指定
API_TOKEN = "xxxx-xxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"

# このbot宛のメッセージで、どの応答にも当てはまらない場合の応答文字列
DEFAULT_REPLY = "何言ってんだこいつ"

# プラグインスクリプトを置いてあるサブディレクトリ名のリスト
PLUGINS = ['plugins']
```

Slackのトークンとデフォルトの返事、botの機能を記したファイルのあるディレクトリ名を指定する。

# slackbotを起動する
以下のコマンドを入力して、プログラムを起動する。

``` shell
$ python3 run.py
```

Slack上でbotへのダイレクトメッセージで何か投稿すると、デフォルトの返事が返ってきます。
botの参加しているチャンネルの場合は、メンションを付ける必要があります。

- ダイレクトメッセージの場合
![1.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/54e94a7f-c3f6-a8fa-8c7c-ac2f0a13bdb1.png)

- botの参加しているチャンネルの場合
![2.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/8bb01d85-40fa-9c59-4309-17efc37c2e65.png)

# botに機能を追加する
## ダイレクトメッセージやチャンネルの投稿を監視する

`my_mention.py`を編集することで、機能を追加することができる。
例として、以下を書き込む。

``` python
# coding: utf-8

from slackbot.bot import respond_to     # @botname: で反応するデコーダ
from slackbot.bot import listen_to      # チャネル内発言で反応するデコーダ
from slackbot.bot import default_reply  # 該当する応答がない場合に反応するデコーダ

# @respond_to('string')     bot宛のメッセージ
#                           stringは正規表現が可能 「r'string'」
# @listen_to('string')      チャンネル内のbot宛以外の投稿
#                           @botname: では反応しないことに注意
#                           他の人へのメンションでは反応する
#                           正規表現可能
# @default_reply()          DEFAULT_REPLY と同じ働き
#                           正規表現を指定すると、他のデコーダにヒットせず、
#                           正規表現にマッチするときに反応
#                           ・・・なのだが、正規表現を指定するとエラーになる？

# message.reply('string')   @発言者名: string でメッセージを送信
# message.send('string')    string を送信
# message.react('icon_emoji')  発言者のメッセージにリアクション(スタンプ)する
#                               文字列中に':'はいらない
@respond_to('メンション')
def mention_func(message):
    message.reply('私にメンションと言ってどうするのだ') # メンション

@listen_to('リッスン')
def listen_func(message):
    message.send('誰かがリッスンと投稿したようだ')      # ただの投稿
    message.reply('君だね？')                           # メンション
```

- ダイレクトメッセージの場合
![3.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/be263f52-be08-1a75-b756-d504f14a98f9.png)

- botの参加しているチャンネルの場合
![4.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/4f8c312a-89bb-d059-2693-1bdc74906efc.png)


これらの動作により、以下のことがわかる。

- `@respond_to('hoge')`デコレータを付けた関数は、botに向けた投稿で、引数の文字列が含まれるときに反応する。
- `@listen_to('hoge')`デコレータを付けた関数は、参加しているチャンネルで、***botに向けた投稿以外***で、引数の文字列が含まれるときに反応する。
- 反応する文字列は完全一致ではなく、含まれていればよい。

## リアクション（スタンプ）をする

`my_mention.py`にさらに以下を追加する。

``` python 
@respond_to('かっこいい')
def cool_func(message):
    message.reply('ありがとう。スタンプ押しとくね')     # メンション
    message.react('+1')     # リアクション
```

![5.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/68e73193-b45d-9ca4-54fb-23484bd06443.png)
botに向けて特定の文字列を含む投稿を行うと、リアクションを行うことができる。

## デフォルトの返事を高機能にする
先程までは、該当しない言葉への返事は毎回同じ文字列であった。
しかし、デフォルトの返事についてもプログラムで指定することができる。
`my_mention.py`にさらに以下を追加する。

``` python
count = 0

@default_reply()
def default_func(message):
    global count        # 外で定義した変数の値を変えられるようにする
    count += 1
    message.reply('%d 回目のデフォルトの返事です' % count)  # メンション
```

- ダイレクトメッセージに投稿
![6.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/11781f96-e88a-32dd-1097-7fc95e33f04c.png)

- 続けて、botの参加しているチャンネルで投稿
![7.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/d949bb0e-34a1-28a8-1494-017f6767de41.png)

このように、デフォルトの返事を行った回数をカウントすることができる。
これは、プログラムを再起動するとリセットされる。

## 反応する文字列を正規表現で指定する
反応する文字列は正規表現で指定することができる。
`my_mention.py`にさらに以下を追加する。

``` python
@respond_to(r'^ping\s+\d+\.\d+\.\d+\.\d+\s*$')
def ping_func(message):
    message.reply('それはpingのコマンドですね。実行できませんが')   # メンション
```

![8.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/b2d0234a-95aa-f1f6-1188-d2faea7811be.png)

pingの形式（厳密には違う）に反応させることができる。
指定する正規表現の中に`^`や`$`を入れないと、文字列の何処かにこの正規表現に引っかかる場合にも反応してしまう。

## 受け取ったメッセージを取り出す
slackbotは受け取ったメッセージを取り出すことができる。
`my_mention.py`で先程書いたデフォルトの返事に関する部分を以下のように書き換える。

``` python
@default_reply()
def default_func(message):
    text = message.body['text']     # メッセージを取り出す
    # 送信メッセージを作る。改行やトリプルバッククォートで囲む表現も可能
    msg = 'あなたの送ったメッセージは\n```' + text + '```'
    message.reply(msg)      # メンション
```

- ダイレクトメッセージの場合
![9.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/9c4a60d5-13d3-44d1-48a1-406953ab1f22.png)

- botの参加しているチャンネルの場合
![10.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/9f2fa8c4-45c9-5a6a-9f21-8eff6b66bb17.png)

`message.body['text']`でメッセージを取り出すことができる。形式はUnicode文字列です。Python2系でもUnicode文字列です。


## 改行を含む投稿の謎
Slackは`Shift + Enter`で改行を行うことができる。
しかし、改行を含む文字列の場合、思ったような動作を行わないことがある。
`slackbot_settings.py`の`DEFAULT_REPLY`を「何言ってんだこいつ」にする。
`my_mention.py`のデフォルトの返事の処理は、先程の、受け取ったメッセージを返すものにする。

- ダイレクトメッセージの場合
![11.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/07d31874-6853-09af-3409-f57780a2008f.png)

- botの参加しているチャンネルの場合
![12.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/f1b56df8-4f40-1120-67e8-8f68d29aa5f0.png)

ダイレクトメッセージで改行を含む文字列を送信した場合、作成した関数ではなく、`slackbot_settings.py`で指定した返事になってしまう。
botの参加しているチャンネルの場合は、そもそも応答しない。プログラムが止まっているわけではないので安心を。

次に、`my_mention.py`に以下を追加する。
正規表現的にすべての文字を受け取ることになる。

``` python
@respond_to(r'.+')
def all_respond_func(message):
    text = message.body['text']     # メッセージを取り出す
    # 送信メッセージを作る。改行やトリプルバッククォートで囲む表現も可能
    msg = 'あなたの送ったメッセージは\n```' + text + '```'
    message.reply(msg)      # メンション
```

- ダイレクトメッセージの場合
![13.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/978efb65-a231-1df9-ddba-2e1248d92396.png)

- botの参加しているチャンネルの場合
![14.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/3f4e4612-944b-13e7-39ea-fa4f8862739e.png)

デフォルトのメッセージでは、改行を含む投稿を受け取ることができなかったが、正規表現を指定した`respond_to`では、ダイレクトメッセージのみで受け取ることができた。

## コマンド形式の文字列を受け取る
例えば、以下の動作を行いたいとする。

- 「set デフォルトの返事」という形式を受け取って、デフォルトの返事を書き換える。
- setとデフォルトの返事には空白を1つ以上開ける。

`my_mention.py`にさらに以下を追加する。なお、先程書いたデフォルトの返事に関するプログラムは削除する。

``` python
def_word = 'デフォルトの返事です'

@default_reply()
def default_func(message):
    message.reply(def_word)     # def_wordの文字列を返す

@respond_to(r'^set\s+\S.*')
def set_default_func(message):
    text = message.body['text']     # メッセージを取り出す
    temp, word = text.split(None, 1)    # 設定する言葉を取り出す。tempには'set'が入る
    global def_word     # 外で定義した変数の値を変えられるようにする
    def_word = word     # デフォルトの返事を上書きする
    msg = 'デフォルトの返事を以下のように変更しました。\n```' + word + '```'
    message.reply(msg)
```

- ダイレクトメッセージの場合
![15.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/8378767d-1abc-bda5-78b9-d59d67623ba6.png)

- botの参加しているチャンネルの場合
![16.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/05665528-d354-b25e-f82f-ab7b5a3f5c06.png)

また、ダイレクトメッセージの場合は、改行を含む投稿も受け取ることができるため、以下のように改行を含む文字列をセットすることもできる。

![17.PNG](https://qiita-image-store.s3.amazonaws.com/0/145051/1413957a-4113-0137-d104-b551554917e3.png)

### プログラムの補足

``` python
r'^set\s+\S.*'
```

とは、

1. `^set`：「set」で始まり、
2. `\s+`：一つ以上の空白文字があり、
3. `\S`：空白以外の1文字があり、
4. `.*`：任意の文字の、任意の長さの文字がある。

という意味である。バックスラッシュが円マークになっているのは表示のせいです。

また、文字を分割する部分で、

``` python
temp, word = text.split(None, 1)
```

という書き方をしているが、今回の場合ならもっと簡単な書き方もあるかもしれない。
しかし、

- コマンド名 引数1 引数2 空白を含む引数3
- 各引数の間は任意個の空白を入れることができる。

のようなコマンドを処理したい場合も、

``` python
temp, arg1, arg2, arg3 = text.split(None, 3)
```

とすることで、うまく分割してくれる。

# 終わりに
以上のようなプログラムを作ることで、いろいろなことができると思います。
subprocessライブラリを用いて、ターミナルのコマンドを実行させることもできます。
しかし、ターミナルのコマンドを実行させる系の処理を行う場合は、不正な入力が送られてくる可能性があるため、正規表現でしっかりとブロックする必要があります。

それでは、拙い説明でしたが、Pythonのslackbotライブラリの導入と基本的な使い方の説明を、終わらせていただきます。

# 参考にさせていただいたページ
[GitHub - lins05/slackbot: A chat bot for Slack (https://slack.com).](https://github.com/lins05/slackbot)
[PythonでSlackbotを作る(1) &#8211; ビットログ](http://blog.bitmeister.jp/?p=3892)
[PythonでSlackbotを作る(2) &#8211; ビットログ](http://blog.bitmeister.jp/?p=3911)