PythonでSlackのchat botを実装する

PythonでSlackのOutgoing Webhooksに対応するbotの汎用的な実装、"[djehuty](https://github.com/xica/djehuty)/[djehuty-slack](https://github.com/xica/djehuty-slack)"を公開したので共有してみる。

## Hubotを使えば?

内製の、Pythonで実装したサーバーサイドモジュールをbotから直接使いたかった。

## SlackのOutgoing Webhooks integrationを設定する

### integrationの追加

botを用意する前に、そもそもSlack側でbotとやりとりするための設定をしておく必要がある。Slackのintegrationの一つに"Outgoing Webhooks"というものがあり、特定のワードで始まる入力を指定のURLにPOSTリクエストする仕組みである。djehutyでは、このintegrationを利用する。

Outgoing Webhooksの設定では、このintegrationを有効にするchannel、trigger wordなどの設定を行うことができる。Slackのアカウントがあるなら、恐らく[ここ](https://slack.com/services/new)から追加するintegrationの選択をすることができるだろう。

Outgoing Webhooksはリストの最下部の方にある。

![slackOutgoing.png](https://qiita-image-store.s3.amazonaws.com/0/30899/e6dfbeea-8639-7378-d9e7-9c8ce116ec46.png)

### integrationの設定

djehutyは、ここで設定したtrigger word以降最初に見つかったASCIIで始まる文字列をコマンドラインとして認識する。例えば、trigger wordに"mybot:"を設定していたとしてSlackに

```
mybot:yo -g hello
```

などと入力すれば、djehutyからは"yo -g hello"というコマンドラインとして認識される。URLには、自分の用意したアプリケーションサーバーのURLに'/slack'を付加したものを後で設定すればよい。

![slackIntegrationSettings.png](https://qiita-image-store.s3.amazonaws.com/0/30899/1f7d7226-44ea-c5a7-7c95-8020149ee87d.png)

### Outgoing Webhooks Tokenの確認

また、この画面では自身のSlackアカウントからのリクエストからであることを示すためのトークンを確認することができる。djehuty-slackの実装でもこのトークンを確認してリクエストをチェックしているためここで予めトークンを確認しておくとよい。

## djehuty

[djehuty](https://github.com/xica/djehuty)(ジェフティ)は、所謂web hookに対応することに特化したアプリケーションサーバーの実装を支援するためのPythonパッケージである。現状では非常に低機能だが、一応特徴としては

* "service"というかたちでweb hookを投げるサービス側への対応を自由に拡張することができる(Hubotで言えばadapter)
* "command"というかたちで特定の入力に対応する機能を自由に拡張することができる

serviceとして[djehuty-slack](https://github.com/xica/djehuty-slack)が既に実装されているので、これを利用すればSlackのOutgoing Webhooksに対応して何かしらレスポンスを返すbotを簡単に用意することができる。

パッケージ名については、pyramidベースで実装したのでエジプト(神話)関連の名前にしようということで[thoth](http://ja.wikipedia.org/wiki/%E3%83%88%E3%83%BC%E3%83%88)が一番それっぽいかと実装を進めていたが、PyPIに同名のパッケージが既に登録されていたので結局別名のdjehutyになった。

## djehutyサーバーを用意する

### 自分で立てる

ドキュメントが揃っていないので基本的にお勧めできない。

一応djehutyでpyramidのscaffoldを実装してあるので、それを利用してプロジェクトを作成しpyramidアプリとして起動することは簡単にできる。

```bash
$ pip install djehuty
$ pcreate -s djehuty_server PROJECT_NAME
$ cd PROJECT_NAME
...
$ pserve development.ini
```

Slackのbotとして動かすためには、djehutyやdjehuty-slackのREADMEにあるように対応するserviceや環境変数の設定が必要になる。

### Herokuで動かす

djehutyのscaffoldで生成されるファイルの中にはHerokuのための設定(requirements.txt、Procfile、Heroku用iniファイル)も含まれているので、SSL対応などは自分で行う前提でこちらの方が自分で環境を用意するよりは楽である。

```bash
$ pip install djehuty
$ pcreate -s djehuty_server PROJECT_NAME
$ cd PROJECT_NAME
$ heroku create
...
$ git push heroku master
```

Herokuへのデプロイや環境変数の設定などについては割愛する。

### djehutysampleのHeroku Buttonを使う

サンプル用の[djehuty-sample](https://github.com/xica/djehuty-sample)リポジトリが[Heroku Button](https://blog.heroku.com/archives/2014/8/7/heroku-button)に対応しており、djehuty-sampleは既に利用サービスとしてdjehuty-slackを指定あるので、Herokuアカウントがあればボタン一発でSlack用のchat botサーバーを用意することができる。尤も、あとでコマンドの追加などのためにコードを編集することを考えるとデプロイする前に一旦forkした方がいいだろう。

Heroku Buttonによるデプロイ時にdjehuty-slackのための環境変数の設定が求められるため、Outgoing Webhooksの設定で確認できるトークンをここで設定する。

いずれの方法でサーバーを用意したとしても、Outgoing WebhooksのURLの設定に用意したサーバーのURLに'/slack'を付加したものを指定することを忘れないようにしておこう。

## botを試してみる

Slackの設定とdjehutyサーバーの用意が正しく行われていれば、コマンドが正常に動作するはずである。djehuty-sampleを試していればyoコマンドとlgtmコマンドが有効になっているはずなので、設定で指定してあるチャンネルで以下のように入力して、yoという文字列が返ってきたり[LGTM](http://www.lgtm.in/)の画像がランダムに返ってきていれば成功だ。

```
YOUR_TRIGGER_WORD yo
YOUR_TRIGGER_WORD lgtm
```

## コマンドを実装する

いずれの方法でサーバーを用意したとしてもアプリケーションサーバーのPythonパッケージは手元にあるので、この中でコマンドを実装することでbotの機能を追加していくことができる。

コマンドの実装は、djehutyが提供しているクラスを継承してコマンドクラスを実装しそれを[setuptoolsのentry_points](https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins)に登録することで有効になる。

### djehuty.command.Command

djehutyにはdjehuty.command.Commandというクラスが定義されている。djehutyはコマンドライン管理機能に[cliff](http://cliff.readthedocs.org/en/latest/)というフレームワークを利用しており、djehuty.command.Commandもほぼcliff.command.Commandそのままと考えてよい。なので、基本的にコマンドクラスの実装はcliffのドキュメントに準ずることになる。

実際のコマンド名は後述のentry_pointsへの登録で指定するので、自分で実装するクラスの名前は特にそれと同じである必要はない。

### コマンドの中身の実装

djehuty.command.Commandの具象クラスはtake_actionというメソッドが実装されることが期待されていて、djehutyはこのメソッドの返す値をSlackへのレスポンスのメッセージとして利用する(この点は、cliffの想定とは異なっている)。例えば、入力したユーザーへのメンション付きで"yo"という文字列を返す実装は以下のようになる。

```py
from djehuty.command import Command


class Yo(Command):
    '''echo yo'''  # cliffは、クラスのドキュメントをヘルプとして用いる

    def take_action(self, parsed_args):
        return '@{} yo'.format(self.app_args.user)
```

parsed_argsについては後述する。self.app_argsはサービスに共通の引数が入っていて、Slackの場合userに入力したユーザーの名前、roomに入力されたチャンネルの名前が入っている。

基本的には、コマンドの実装はこれだけだ。

### コマンドの引数の実装

動的に値を与えられるようにするため、コマンドには引数がつきものである。djehuty(というかcliff)は引数の実装もサポートしているため、自分の実装したコマンドに宣言的に引数を追加することができる。

```py
from djehuty.command import Command


class Yo(Command):
    '''echo yo'''

    def get_parser(self, prog_name):
        parser = Command.get_parser(self, prog_name)
        parser.add_argument('-g', '--greeting',
                            default='yo',
                            help='greeting message')
        return parser

    def take_action(self, parsed_args):
        return '@{} {}'.format(self.app_args.user, parsed_args.greeting)
```

djehuty.command.Commandの具象クラスでget_parserというメソッドを実装すると、その実装に従って入力文字列が評価され、take_actionにparsed_argsとして引数が渡されてきて利用することができる。

get_parserは、基本的に

* 親クラスのget_parserを用いてparserを取得する
* parserに引数を追加する
* parserを返す

という形になっている。parserは[argparse](http://ja.pymotw.com/2/argparse/).ArgumentParser、parsed_argsはargparse.Namespaceのインスタンスなので、引数定義と値の取得の方法自体についてはargparseのドキュメントを参照のこと。基本的な使い方は上記のコードだけでもなんとなくつかめるとは思うが、もっと高度な定義の仕方もたくさんある。

### コマンドの登録

djehuty.command.Commandは、上述の通りsetuptoolsのentry_pointsの仕組みに登録することでdjehutyから認識され有効になる。登録は単にsetup.pyに当該の記述を追加するだけなので、例えばmydjehutyというパッケージでcommands.pyでMisawaというコマンドとHartmanというコマンドを実装したとしたら、mydjehuty/setup.pyのsetupのentry_points引数は以下のようになる。

```py
setup(
    name='mydjehuty',
    # ...
    entry_points={
        'djehuty.commands': [
            'misawa = mydjehuty.commands:Misawa',
            'hartman = mydjehuty.commands:Hartman',
        ],
    },
)
```

'djehuty.commands'をキーとするlistの値として、

```py
'実際に使う際のコマンド名 = 実装されているモジュール(ファイル名):実装したクラス名'
```

という文字列を追加していけばよい。

### コンソールでの動作

djehutyをインストールすると、フレームワークのコードやpyramidのscaffoldだけでなくdjehutyというCLIもインストールされる。これはPOSTリクエストとレスポンスの部分だけCLIで肩代わりしたものなので、実装したコマンドの振る舞いをコンソール上でエミュレートすることができる。

```bash
$ djehuty yo
yo
```

ただし、例えばdjehuty-slackのようなサービスで実装されているようなサービスに共通の引数の指定は自分で明示的に行う必要がある。

```bash
$ djehuty -u kiri yo
@kiri yo
```

### コマンドのみのパッケージ

自分のアプリケーションサーバー内で実装するだけでなくコマンドのみが実装されているPythonパッケージを実装するのも可能で、そのためのscaffoldも用意されている。

```bash
$ pcreate -s djehuty_command YOUR_COMMAND_PROJECT_NAME
```

特定の組織に依存しない機能で公開可能な実装であれば、これを利用してコマンドパッケージとして公開することも可能である。

## 終わり

実装したコマンドを含むパッケージをアプリケーションサーバーにデプロイして、Slackから実装したコマンドを動作させることができたら成功。お好みの機能をPythonで自在に実装して、良いSlackライフを。