IFTTT+pythonのslackbotライブラリで、出社したらほめてくれるマーリンbotを作るまで

# 導入
会社の同僚が出社したら元気が出る画像を投稿するslackbotを作っていたので、自分もやってみたくなりました。
毎日出社したらほめてくれるマーリン[^1]のslackbotを作ります。
細かいトラップを回避しながらがんばって作ります。

[^1]: マーリンといえばアーサー王伝説に登場する魔術師のおじいさんですが、ここではFate/Grand Orderに出てくる白髪の美青年（しかも櫻井孝宏の声で喋る）を指します

# システム構成
slackbotライブラリ → https://github.com/lins05/slackbot/
なおこのツールは、RTM APIを利用する方式のようです。
参考：https://qiita.com/namutaka/items/233a83100c94af033575

システム構成は以下のようになります。

![こうせいず.png](https://qiita-image-store.s3.amazonaws.com/0/123125/1d1f5c13-b614-62a3-a53a-b48ebe2a858e.png)
1. 出社すると、スマホのIFTTTアプリが特定の範囲に入ったことを検知し、IFTTTのトリガーが起動
2. トリガーを受けてIFTTTがでslackに「しゅっしゃ」と送る
3. slackがslackbotに投稿したことを送る
4. slackbotは「しゅっしゃ」という文字列が入っていたら回答「おはよう！」を生成
5. slackbotがslackに「おはよう！」を投稿する
※slackbotは最終的にheroku上にデプロイしますが、ローカルでも動作します


# 開発環境
Mac OS X 10.12.6
iOS 12.1.2

# おおまかな手順
[0. 事前準備](#0-事前準備)
[1. slackbotを動かしてみる](#1-slackbotを動かしてみる)
[2. IFTTTのAppletを作ってslackに投稿する](#2-iftttのappletを作ってslackに投稿する)
[3. slackbotをIFTTTに対応させる](#3-slackbotをiftttに対応させる)
[4. いろんなパターンでほめてもらう処理を作る](#4-いろんなパターンでほめてもらう処理を作る)
[5. herokuにデプロイ](#5-herokuにデプロイ)

# 0. 事前準備
## PCでやっておくこと
* IFTTTのアカウント作成
* python3.6系インストール（今回は3.6.6）
* heroku cliインストール [参考](https://devcenter.heroku.com/articles/getting-started-with-python#set-up)
* gitインストール
* （slackアプリのインストール）
* slackにbot用のワークスペースとチャンネルを用意しておく

## スマホでやっておくこと（今回はiOS）
* iPhoneにIFTTTアプリをインストール

# 1. slackbotを動かしてみる
まずは、slackで何か発言したら反応を返すslackbotを作ります。
構成図③〜⑤の部分です。
slack側でBotを登録し、そのBotとWebSocket通信するpythonツールを作ります。

## 1-1. Bot登録
slack側のBot登録は以下のページを開いて、「設定を追加」ボタンから。
https://slack.com/apps/A0F7YS25R-bots
するとBot登録画面になるので、適当にBotの名前を入力しましょう。
ここでは「merlin_bot」としています。
<img src="https://qiita-image-store.s3.amazonaws.com/0/123125/9dfbe439-d0a5-05e0-4651-ebad47eb505c.png" width="600">
「ボットインテグレーションを追加する」を押すと、botが作成され、インテグレーション設定画面に遷移します。
APIトークンが表示されますので、どこかにメモしておきましょう。
<img src="https://qiita-image-store.s3.amazonaws.com/0/123125/7817a20a-0a31-bd1e-62f7-6f71f386d159.png" width="600">

このbotはslackからメンバとして見ることができます。
この時点で、bot用のチャンネル（今回はchaldeaにしました）に、このbotを招待しておきます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/123125/a9345364-5912-22c0-9cc9-2b7b44c48e12.png" width="500">

## 1-2. slackbotの動作確認

次にslackbotを動かします。
（参考：https://qiita.com/sukesuke/items/1ac92251def87357fdf6）

まずは適当な場所にgit cloneでslackbotリポジトリをクローンします。
※ pip installでも入りますが、そのままだとIFTTTに対応してないので、あとで書き換え作業をするためにクローンします

```
git clone https://github.com/lins05/slackbot.git
```

そして、cloneしてきたリポジトリはいったんそのままにしておき、
別の場所に作業用のディレクトリを作ります。

```
mkdir merlin_bot
cd merlin_bot
```

さきほどクローンしたslackbotリポジトリから必要なファイルをコピーしてきます。
（ついでに今回は使わないファイルは消しておきます）
また、ボット起動用のファイルやpluginディレクトリ（あとで利用）もつくります。

```
# slackbotリポジトリのslackbotディレクトリをまるっとコピー
cp -r ~/git/slackbot/slackbot . 
# 使わないファイルを削除
rm slackbot/VERSION
rm -r slackbot/plugins/
# requirementsもコピー
cp ~/git/slackbot/requirements.txt .
# bot起動用ファイル作成
touch run.py
touch slackbot_settings.py
# plugin
mkdir plugins
touch plugins/__init__.py
```

ディレクトリ構成はこんな感じ。

```
merlin_bot/
├── plugins
│   └── __init__.py
├── requirements.txt
├── run.py
├── slackbot
│   ├── __init__.py
│   ├── bot.py
│   ├── dispatcher.py
│   ├── manager.py
│   ├── settings.py
│   ├── slackclient.py
│   └── utils.py
└── slackbot_settings.py
```
requirementsをインストールします。

```
pip install -r requirements.txt 
```

run.pyを以下のように編集します。

```run.py
# -*- encoding: utf-8 -*-
from slackbot.bot import Bot

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

if __name__ == '__main__':
    main()
```

slackbot_settings.pyを以下のように編集します。

```slackbot_settings.py
# -*- encoding: utf-8 -*-

# botアカウントのトークンを指定
API_TOKEN = "(さきほどメモっておいたAPIトークン)"

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

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

# 投稿するslack上のチャンネル
ERRORS_TO = 'chaldea'
```

ここまでできたら、botを起動して動作確認します。

```
python run.py
```

chaldeaチャンネルで、merlin_bot宛にメッセージを送ると、デフォルトの発言が返ってきます。
![moshimoshi.png](https://qiita-image-store.s3.amazonaws.com/0/123125/d82050e9-52ce-01a4-a7cf-ef54d89155c9.png)

## 1-3. 特定の文字列に反応させる
listen_toデコレータを使うと簡単にできます。
run.pyで、listen_toのインポートを追加し、listen_func関数を追加。

```run.py
# -*- encoding: utf-8 -*-
from slackbot.bot import Bot
from slackbot.bot import listen_to

@listen_to('しゅっしゃ')
def listen_func(message):
    message.send('おはよう！')

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

if __name__ == '__main__':
    main()
```
runしてslackに「しゅっしゃ」と投げると...

```
python run.py
```
![しゅっしゃ.png](https://qiita-image-store.s3.amazonaws.com/0/123125/6f6e19cb-4b1c-d244-94da-2bc5d5ca7681.png)

出社に反応してくれるようになりました！

# 2. IFTTTのAppletを作ってslackに投稿する
構成図①〜②の部分です。
※今回はiOSの解説になります

IFTTTのNew Appletから登録します。
今回は、位置情報トリガの本番用と、ボタンをポチッと押すデバッグ用の2つのAppletをつくります。

## 2-1. 位置情報トリガのIFTTT
「this」をタップすると、「Location」というメニューが出てくるのでタップ。
![ifttt.png](https://qiita-image-store.s3.amazonaws.com/0/123125/3e400f39-5ea9-7df1-f1f0-e56a5347fecb.png)
今回は出社なので、「You enter an area」を選択。
<img width="200" alt="IMG_3084.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/123125/e3d17f13-c81a-28c8-39a8-f5ec9be59b60.png">
次のページの位置情報選択で、自分の会社の位置を選択します。
（なお私はしがないエンジニアなので皇居勤務ではありません...）
<img width="200" alt="IMG_3087.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/123125/49e8acc5-42b6-5aa0-b24b-4ca60603bcbe.png">
create an triggerでトリガーが作れます。

次は「that」を選択。
search servicesからslackと入れるとslackがでてきます。
![スクリーンショット 2019-01-20 14.42.36.png](https://qiita-image-store.s3.amazonaws.com/0/123125/3dd0d61f-62e2-0163-c16e-f36882c4b931.png)
Post to channelと選択し、ワークスペースやチャンネルの情報を入れます。
「Message」には「しゅっしゃ」を入れます。
<img width="200" alt="IMG_3091.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/123125/8f9cc383-21d6-6439-da0d-1fbc1ca62881.png">
「Create action」を押すと新しいAppletができます。

## 2-2. ボタントリガのIFTTT
位置情報トリガだと、動作確認のために毎回スマホを持って行ったり来たりする必要があります。
なので、ボタンを押すとslackに投稿するデバッグ用IFTTTを作っておきます。

「this」の部分で、buttonと入力すると「Button widget」が出てきます。
次の画面で「Button press」をタップすると「this」のところがボタンになります。
![ボタン.png](https://qiita-image-store.s3.amazonaws.com/0/123125/56373c4d-5262-f779-185b-0ab498bfb06a.png)
thatの部分は2-1と同じ、slackへの投稿を設定します。

ボタンは、MyAppletsから登録したAppletを選択し、「Widget settings」を選択、次の画面でボタンアイコンをタップするとボタンを押す画面に遷移します
![ぼたん.png](https://qiita-image-store.s3.amazonaws.com/0/123125/2092213c-950e-c8e4-7803-f83d9b740b47.png)
ボタンを押すと、slackのチャンネルにIFTTTから投稿が確認できます。
![スクリーンショット 2019-01-20 15.01.02.png](https://qiita-image-store.s3.amazonaws.com/0/123125/22067220-b849-3bdd-5a62-c9baea68216d.png)

ただし、このままではslackbotが反応しません。

# 3. slackbotをIFTTTに対応させる

[こちらの記事](https://qiita.com/yakan10/items/6fa70ab7643e66022f3b)に書いたんですが、IFTTTの投稿フォーマットが通常のそれと異なるため、このままではIFTTTの発言にbotが反応してくれません。
なので、slackbot/dispacher.pyというファイルを修正します。

```(編集前)dispacher.py
    def _dispatch_msg_handler(self, category, msg):
        responded = False
        for func, args in self._plugins.get_plugins(category, msg.get('text', None)):
            if func:
                responded = True
                try:
```

```(編集後)dispacher.py
    def _dispatch_msg_handler(self, category, msg):
        responded = False
        text = None
        if 'text' in msg:
            text = msg.get('text', None)
        elif 'attachments' in msg and len(msg.get('attachments')) > 0:
            text = msg['attachments'][0].get('pretext', None)

        for func, args in self._plugins.get_plugins(category, text):
            if func:
                responded = True
                try:
```

修正後、slackbotを起動してからIFTTT経由で投稿すると、ちゃんと反応が返ってきます。
![スクリーンショット 2019-01-20 15.27.16.png](https://qiita-image-store.s3.amazonaws.com/0/123125/6f9cadfe-2395-c549-55ec-1f87f4ce70b4.png)


# 4. いろんなパターンでほめてもらう処理を作る
「おはよう！」だけだと味気ないので、毎日いろんなパターンで出社をほめてもらいます。

## 4-1. pluginsを作成
さきほどのlisten_toの処理をplugins以下に新しくファイルを作って移動します。

```
touch plugins/good_morning.py
```

```run.py
# -*- encoding: utf-8 -*-
from slackbot.bot import Bot

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

if __name__ == '__main__':
    main()
```

```plugins/good_morning.py
# -*- encoding: utf-8 -*-
from slackbot.bot import listen_to

@listen_to('しゅっしゃ')
def listen_func(message):
    message.send('おはよう！')
```

## 4-2. ほめセリフを収集
出社したらほめてくれるといえば[コウペンちゃん](https://twitter.com/k_r_r_l_l_)です。
今回は[コウペンちゃんbot](https://twitter.com/koupenchan_bot)から出社したり起きたりしたらほめてくれるセリフを収集し、マーリン風に加筆修正したものを使います。
一行一セリフの以下のようなテキストファイルを作りpluginsの下に置きます。

```plugins/serif_merlin.txt
今日も出勤するなんて、君はえらいね。
今日もえらいね。
今日も1日頑張ってね。
ちゃんと起きられたなんて、えらいじゃないか！
電車に乗れたのかい？えらいね！
```

これを読み込んでランダムに出すようにgood_morning.pyを修正します。

```plugins/good_morning.py
from slackbot.bot import listen_to
import random

serif = []
serif_file = "plugins/serif_merlin.txt"
with open(serif_file, 'r') as f:
    serif = [l.strip() for l in f.readlines()]

prefix = ['おはよう！', 'おはようマスター。']

@listen_to('しゅっしゃ')
def listen_func(message):
    message.send(random.choice(prefix) + random.choice(serif))
```
python run.pyして動作確認します。
![スクリーンショット 2019-01-20 15.42.07.png](https://qiita-image-store.s3.amazonaws.com/0/123125/bcd4b634-514a-0de9-31a1-5a84d878399d.png)
だいぶマーリンに近づいてきました。

## 4-3. アイコンや名前を整える
slackのアイコンや名前がデフォルトのままだったので、slackのBot管理画面から編集します。
APIトークンが表示されてた画面を下にスクロールすると画像や名前を設定できます。
![スクリーンショット 2019-01-20 15.48.04.png](https://qiita-image-store.s3.amazonaws.com/0/123125/d846c2f6-c06c-358b-d9b2-1c158c598b6f.png)
インテグレーションを保存して、slack画面に戻ると名前やアイコンが反映されてます。
![スクリーンショット 2019-01-20 15.49.12.png](https://qiita-image-store.s3.amazonaws.com/0/123125/786b9467-3861-bf4a-2803-3a394c9c6b21.png)
おお！！マーリンがほめてくれた！！

# 5. herokuにデプロイ
実際に運用する際、ローカルでpythonを実行しつづけるのは辛いので、herokuにデプロイします。

## 5-1. デプロイ用のファイルを準備
merlin_botディレクトリ直下にファイルを作ります。

```
cat <<EOF > Procfile
worker: python run.py
EOF
```

```
cat <<EOF > runtime.txt
python-3.6.7
EOF
```

また、[このへん](https://github.com/github/gitignore/blob/master/Python.gitignore)を参考に.gitignoreを作ります。

現在のディレクトリ構成はこんな感じ。

```
merlin_bot
├── .gitignore
├── Procfile
├── plugins
│   ├── __init__.py
│   ├── good_morning.py
│   └── serif_merlin.txt
├── requirements.txt
├── run.py
├── runtime.txt
├── slackbot
│   ├── __init__.py
│   ├── bot.py
│   ├── dispatcher.py
│   ├── manager.py
│   ├── settings.py
│   ├── slackclient.py
│   └── utils.py
└── slackbot_settings.py
```

## 5-2. herokuにデプロイ

gitのfirst commitを作っておく。

```
git init
git add .
git commit -m "first commit"
```

herokuにログインして、新しくアプリを作る。

```
heroku login
heroku create
```
herokuにデプロイ

```
git push heroku master
```

しばらく待つとデプロイが完了します。

Herokuのプロセスをworkerにすると、デプロイしただけではプロセスが立ち上がりません。
なので、Herokuの管理画面の「Resources」から、workerをオンにします。
![スクリーンショット 2019-01-21 21.55.22.png](https://qiita-image-store.s3.amazonaws.com/0/123125/7f0c074a-089e-a6b9-cc75-15cf789da6d0.png)


```
heroku ps
Free dyno hours quota remaining this month: 998h 20m (99%)
Free dyno usage for this app: 0h 0m (0%)
For more information on dyno sleeping and how to upgrade, see:
https://devcenter.heroku.com/articles/dyno-sleeping

=== release (Free): /bin/sh -c 'if curl $HEROKU_RELEASE_LOG_STREAM --silent --connect-timeout 10 --retry 3 --retry-delay 1 >/tmp/log-stream; then
  chmod u+x /tmp/log-stream
  /tmp/log-stream /bin/sh -c '"'"'python run.py'"'"'
else
  python run.py
fi' (1)
release.2746: up 2019/01/20 16:24:03 +0900 (~ 21s ago)  ### あがってます
```
ちゃんと動いています。
![スクリーンショット 2019-01-20 16.26.05.png](https://qiita-image-store.s3.amazonaws.com/0/123125/f3680187-3908-af6b-9a05-345e309b6638.png)
できあがりです！

# いかがでしたか？
マーリンのセリフはちょっとバリエーションが少ないので、同じ仕組みでキュケオーン([@QK_otabe](https://twitter.com/qk_otabe))とトリスタン([@wtsknsi](https://twitter.com/wtsknsi))も追加してみました[^2]。
弊カルデアは今日も平和です。
![スクリーンショット 2019-01-20 16.56.23.png](https://qiita-image-store.s3.amazonaws.com/0/123125/e99349e4-a644-9cf7-33c1-a3cc03744bff.png)

[^2]: なお、弊カルデア（iPhone）にキュケオーンとトリスタンはいません

# ソースコード
https://github.com/yakan10/ifttt_slackbot

# 大いなる参考記事
* [Slack Botの種類と大まかな作り方](https://qiita.com/namutaka/items/233a83100c94af033575)
* [PythonのslackbotライブラリでSlackボットを作る](https://qiita.com/sukesuke/items/1ac92251def87357fdf6)
* [PythonのslackbotライブラリでSlackボットとIFTTTを連携](https://qiita.com/zen7sky/items/fafe10a126e20f79fa36)
* [Google Home MiniからIFTTTを経由し、Slackに投稿してPS4を操作する](http://katsuwoo.hatenablog.com/entry/2018/11/25/182521)
* [Heroku（Free Dyno）のWorkerでSlackのBotkitを動かしてみよう！](https://blog.engineer.adways.net/entry/47511432)
* [コウペンちゃんbot](https://twitter.com/koupenchan_bot)
* [キュケオーンをお食べ](https://twitter.com/qk_otabe)
* [悲しいトリスタン卿bot](https://twitter.com/wtsknsi)