StackStormでSlackをTwitterクライアント化する

StackStormはIFTTTをローカルに持ってきたものと紹介されるような、色々なシステムを連携させるのに便利なツールです。
今回はこのStackStormを間に挟んでSlackをTwitterクライアント化します。

最終的にこんな感じです。

![slack_test4.PNG](https://qiita-image-store.s3.amazonaws.com/0/86684/bd20e11c-30e7-4b83-da95-1a1b46eb3e5d.png)


# 1人Slack
自分だけ参加しているSlackのチームを作ってそこに色々なものを連携させることで、日々、見ないといけないor見たい情報を一か所で見れるようにしています。

- [１人Slackのすすめ](https://www.slideshare.net/y_kohei/stackstorm1slack-80701051)

この中の一つとしてtwitterのタイムラインをSlackに連携させて、また、SlackからTwitterに投稿できるようにしています。実はSlack→TwitterはSlackのAPPからできます。一方Twitter→Slackはある特定ユーザorある特定条件でというのはできるが、ホームタイムラインと呼ばれるような自分とフォローユーザのつぶやき等を連携することはできません。
そこで間にStackStormを挟むことでこれを実現するというのが今回の話です。


# StackStormのインストール(Docker)
StackStormのインストールは簡単に行えるようにいくつか用意されていますが
今回はDockerを使うことにしました。(Dockerを使わない方法は[こちら](https://docs.stackstorm.com/install/index.html))

また、後半ChatOps Packというものを使います。そのためコンテナ起動前に少し作業が必要です。

```
# git clone https://github.com/StackStorm/st2-docker
# cd st2-docker/
```

続いて以下のファイルを作成(ChatOps Packを有効化するための前準備です)

- runtime/entrypoint.d/enable_chatops.sh

```
#!/bin/bash

sudo rm /etc/init/st2chatops.override
```

後は以下の手順でdocker-compose upしましょう。

```
# chmod +x runtime/entrypoint.d/enable_chatops.sh
# make env
# docker-compose up -d
```

これ以降の作業はstackstormコンテナ内で行います。

```
# docker-compose exec stackstorm bash
```

# Twitter連携

## Twitter Packインストール
StackStormではシステム間の連携のために自分で設定を書くこともできますが、便利な追加Packがたくさん公開されています([StackStorm/st2contrib](https://github.com/StackStorm/st2contrib))。
その中に[Twitter連携用のPack](https://github.com/StackStorm/st2contrib/tree/master/packs/twitter)もあるのでそれを利用します。

```
# st2 pack install twitter
# st2 pack list
+---------+---------+--------------------------------------------+---------+------------------+
| ref     | name    | description                                | version | author           |
+---------+---------+--------------------------------------------+---------+------------------+
| chatops | chatops | ChatOps integration pack                   | 1.0.0   | StackStorm, Inc. |
| core    | core    | Basic core actions.                        | 1.0.0   | StackStorm, Inc. |
| default | default | Default pack where all resources created   | 1.0.0   | StackStorm, Inc. |
|         |         | using the API with no pack specified get   |         |                  |
|         |         | saved.                                     |         |                  |
| linux   | linux   | Generic Linux actions                      | 1.0.1   | StackStorm, Inc. |
| packs   | packs   | Pack management functionality.             | 1.0.0   | StackStorm, Inc. |
| twitter | twitter | st2 content pack containing twitter        | 0.3.1   | StackStorm, Inc. |
|         |         | integrations                               |         |                  |
+---------+---------+--------------------------------------------+---------+------------------+
```
## Twitter Packの設定

Twitter Packを使うためには

- consumer_key
- consumer_secret
- access_token
- access_token_secret

が必要です。[https://apps.twitter.com/](https://apps.twitter.com/)で取得してください。
取得方法は解説サイトがたくさんあるので省略しますが、APIの権限としてRead and Writeが必要です(今回触れないですが、DirectMessage用の機能もあるので後あと使うならRead, Write and Access direct messages)。

続いてTwitter Packのconfigを設定します。対話形式のコマンドから設定する方法もあるのですが上手く動かないので直接以下のファイルを作成してください。ちなみに`query: []`はTwitterで検索する文字列を配列で指定するのですが、今回使わないので空配列を置いてます(空文字 or 未定義だと怒られました)。


- /opt/stackstorm/configs/twitter.yaml

```
access_token: <access_token>
access_token_secret: <access_token_secret>
consumer_key: <consumer_key>
consumer_secret: <consumer_secret>
count: 30
language: jp
query: []
```

ファイル作成後configを読み込ませます。

```
#  st2ctl reload --register-configs
Registering content...[flags = --config-file /etc/st2/st2.conf --register-configs]
2018-02-23 07:03:15,880 INFO [-] Connecting to database "st2" @ "mongo:27017" as user "None".
2018-02-23 07:03:16,145 INFO [-] =========================================================
2018-02-23 07:03:16,145 INFO [-] ############## Registering configs ######################
2018-02-23 07:03:16,145 INFO [-] =========================================================
2018-02-23 07:03:16,314 INFO [-] Registered 1 configs.
##### st2 components status #####
st2actionrunner PID: 78
st2api PID: 49
st2api PID: 331
st2stream PID: 51
st2stream PID: 249
st2auth PID: 40
st2auth PID: 199
st2garbagecollector PID: 38
st2notifier PID: 44
st2resultstracker PID: 42
st2rulesengine PID: 47
st2sensorcontainer PID: 37
st2chatops is not running.
mistral-server PID: 468
mistral.api PID: 465
mistral.api PID: 501
mistral.api PID: 502
```

出力の中にpythonのエラーが混ざっていたら設定できていないので注意してください。

## Twitter Packの動作確認
まずはStackStormからTwitterに投稿してみます。このようにStackStorm→○○○の連携をするものを「Action」と呼びます。今回はTwitter Packに入っているtwitter.update_statusを使用します。

```
# st2 run twitter.update_status status="test"
id: 5a8fbe4dc89756014b590e9f
status: succeeded
parameters: 
  status: test
result: 
  exit_code: 0
  result: true
  stderr: ''
  stdout: ''
```

この後自分のTwitterアカウントから「test」とつぶやかれていれば成功です。
次に逆方向Twitter→StackStormの連携を確認します。


次はTwitterの投稿を監視し、StackStormで扱えるようにしましょう。`mkdir -p /opt/stackstorm/packs/test/rules`を作って以下のファイルを作成してください。

- /opt/stackstorm/packs/test/rules/twitter_test.yaml


```
---
  name: "twitter_test"
  description: "Twitter sensor test."
  enabled: true

  trigger:
    type: "twitter.stream_matched_tweet"
    parameters: {}

  action:
    ref: "core.local_sudo"
    parameters:
        cmd: "echo '@{{trigger.user.screen_name}} tweeted on {{trigger.created_at}}: {{trigger.text}} - {{trigger.url}}' >> /root/test.txt"
```

ルールを新規作成したら再読み込みをかけます


```
st2ctl reload --register-all
```

これで、連携したTwitterから何かつぶやくと/root/test.txtに追記されます。



# Slack連携
## Chatops Pack
Slackとの連携用にChatops Packを使います。こちらはHubot経由でのやり取りになり、設定させしてしまえばそのあとは他のチャットツールとの連携も楽になるのでこちらを使います。また、インストールは必要なく'st2 pack list'を確認するとchatopsのPackが見つかるはずです。

## Slackの準備
まずはSlack側の準備をします。準備するのは以下の2点です。

- チャンネルの作成
- Hubot APP設定

連携させるSlackのチームにログインして、チャンネルを一つ作ってください。今回は「twitter」というチャンネルと連携させることにします。

次に[ここ](https://slack.com/apps/A0F7XDU93-hubot)からHubot APPをインストールします。

- 緑のInstallボタンをクリック
- Usernameを「hubot」としAdd Hubot Integration
- API Tokenが表示されているのメモしてSave Integration

終わったら、hubotをtwitterチャンネルに招待してください。

## Chatops Packの設定

以下のコマンドでST2_API_KEYを取得します。

```
# st2 apikey create -m '{"used_by": "chatops pack"}'
+------------+--------------------------------------------------------------+
| Property   | Value                                                        |
+------------+--------------------------------------------------------------+
| id         | 5aa9c269c56fb400d3884c9d                                     |
| created_at | 2018-03-15T00:46:33.149177Z                                  |
| enabled    | True                                                         |
| key        | **************************                                   |
| metadata   | {                                                            |
|            |     "used_by": "chatops pack"                                |
|            | }                                                            |
| uid        | api_key:**********                                           |
| user       | st2admin                                                     |
+------------+--------------------------------------------------------------+
```

これの3番目のkeyがST2_API_KEYになるのでどこかにメモしてください。

これで、Chatops Packに必要な情報がそろったのでChatops Packの設定ファイルを書き換えていきます。以下のファイルを開くと様々なチャットサービスの設定値がコメントアウトされています。以下の共通の設定一つとSlackに関する三つを設定してください。

- /opt/stackstorm/chatops/st2chatops.env

```
export ST2_API_KEY="<作成したST2_API_KEY>"
export HUBOT_ADAPTER=slack
export HUBOT_SLACK_TOKEN=<作成したHubot API Token>
export HUBOT_SLACK_EXIT_ON_DISCONNECT=1
```

設定後はサービスの再起動が必要です。

```
st2ctl reload --register-all
service st2chatops restart
```

うまく設定できているとSlackの左下、Hubotの横の丸が緑になっています。
もう一つ確認として以下を実行してください。

```
# st2ctl status
st2actionrunner PID: 104
st2api PID: 58
st2api PID: 208
st2stream PID: 60
st2stream PID: 210
st2auth PID: 46
st2auth PID: 205
st2garbagecollector PID: 43
st2notifier PID: 53
st2resultstracker PID: 49
st2rulesengine PID: 56
st2sensorcontainer PID: 42
st2chatops PID: 1511
mistral-server PID: 384
mistral.api PID: 378
mistral.api PID: 410
mistral.api PID: 411
```

成功していれば`st2chatops PID: 1511`となっていますが、`st2chatops is not running.`となっていればどこかで間違えていると思われます。

## Chatops Packの動作確認

まずはslack上のtwitterチャンネルに以下を投稿してください

```
!run hostname on localhost
```

するとこんな感じでhubotが反応してコンテナのhostnameを返してくれます。

![slack_test.PNG](https://qiita-image-store.s3.amazonaws.com/0/86684/82a4e3b4-ac47-c63d-74fc-109f8bce7f6d.png)

次にstackstorm側で以下のコマンドを実行します。

```
st2 run chatops.post_message channel=twitter message=test
```

するとhubotが投稿してくれます。
![slack_test2.PNG](https://qiita-image-store.s3.amazonaws.com/0/86684/ad3f0baf-e6f9-3904-c968-eca95793e0b6.png)


# Slack・Twitter連携
連携用の設定をmy_slackというディレクトリ下にしていきます。

```
mkdir -p /opt/stackstorm/packs/my_slack/{actions,rules,sensors,aliases}
```
## Twitter→Slack

Twitter packの確認で作ったものとほとんど同じです。以下のファイルを作成することで、twitterのタイムラインがslackに流れてくるようになります。

-  /opt/stackstorm/packs/my_slack/rules/twitter_stream_clone.yaml

```
---
  name: "twitter_stream_clone"
  description: "Clone home timeline"
  enabled: true

  trigger:
    type: "twitter.stream_matched_tweet"
    parameters: {}

  action:
    ref: "chatops.post_message"
    parameters:
        channel: "twitter"
        message: "@{{trigger.user.screen_name}} tweeted on {{trigger.created_at}}: {{trigger.text}} - {{trigger.url}}"
```

設定したらリロード

```
st2ctl reload --register-all
```

## Slack→Twitter
次にslack上からtwitterタイムラインへの投稿をできるようにします。今度はaliasesディレクトリに以下を作ります。

- /opt/stackstorm/packs/my_slack/aliases/post_tweet.yaml

```
---
name: "post_tweet"
action_ref: "twitter.update_status"
description: "post tweet from slack"
formats:
  - "tweet \"{{status}}\""
```

そしてリロード

```
# st2ctl reload --register-aliases
# service st2chatops restart
```

今回の手順で作成してあれば`!tweet "Slack から 投稿"`のように`!tweet`の後に「"」で囲まれた中身がtweetされます。

![slack_test3.PNG](https://qiita-image-store.s3.amazonaws.com/0/86684/29a952fe-2479-6b55-ce35-287886d2777d.png)

# もう少しいい感じにする

ここまででSlack上にTwitterのタイムラインが表示されるようになりましたが、アイコンが全てhubotで味気ないです。アイコンやユーザー名等を変更します。このために、StackStormにSlack Packを導入したいと思います。

## Webhook URLの取得

[ここ](https://slack.com/services/new/incoming-webhook)にアクセスして以下のように「twitter」チャンネルを指定します。

![webhook.PNG](https://qiita-image-store.s3.amazonaws.com/0/86684/17d83d06-34c9-4a79-0eb2-0f677b0c7bfa.png)

すると以下のようにWebhook URLが表示されるのでメモしてください。

![webhook2.PNG](https://qiita-image-store.s3.amazonaws.com/0/86684/043b2ff9-f61a-04bb-a2c0-e408785b34f6.png)

## Slack Pack

まずはSlack Packのconfigの設定です。

- /opt/stackstorm/configs/slack.yaml

```
---
# Used for post message action
post_message_action:
  webhook_url: "<Webhook URL>"
  channel: "#twitter"
  username: "twitter"
  icon_emoji: ":bird:"

# Used for Slack sensor
sensor:
  token: ""
  strip_formatting: false

action_token: "<hubot用に取得したHUBOT_SLACK_TOKENと同じ>"
admin:
  admin_token: ""
  organization: ""
  set_active: true
  attempts: 1
  auto_join_channels: []
```

そして、先ほど作ったtwitter_stream_cloneのaction部分をSlack Packを使用するように書き換えたtwitter_stream_clone2を作成します。

- /opt/stackstorm/packs/my_slack/rules/twitter_stream_clone2.yaml

```
---
  name: "twitter_stream_clone2"
  description: "Clone home timeline"
  enabled: true

  trigger:
    type: "twitter.stream_matched_tweet"
    parameters: {}

  action:
    ref: "slack.chat.postMessage"
    parameters:
        channel: "twitter"
        text: "{{trigger.created_at}}: {{trigger.text}} - {{trigger.url}}"
        username: "@{{trigger.user.screen_name}}"
        unfurl_links: "true"
        icon_url: "http://furyu.nazo.cc/twicon/{{trigger.user.screen_name}}"
```

これでアイコン・ユーザー名がTwitterのアカウントのものになり、URLがついているものは展開されるようになり、Twitterクライアントらしくなったのではないでしょうか。

ここまでできたら最初に作ったmy_slack.twitter_stream_cloneを無効化しておきます。

```
# st2 rule disable my_slack.twitter_stream_clone
```

# UnicodeEncodeError
日本語の投稿がslack.chat.postMessageではうまく処理できない模様。logを見るとUnicodeEncodeErrorが出ていたので応急処置的に以下のファイルを修正します。classが宣言される前にsys.setdefaultencoding('utf-8')を指定するようにしています。

- /opt/stackstorm/packs/slack/actions/run.py

```
from st2common.runners.base_action import Action

BASE_URL = 'https://slack.com/api/'

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

class SlackAction(Action):
～～
```

# まとめ
今回はSlack⇔StackStorm⇔Twitterを実現する方法を紹介しました。ここまで紹介したものはTwitterクライアントとしては最低限のものになりますが、もう少し作りこめばリプライ等の機能も追加可能です。また、これを一度構築してしまえば、別のチャットツールや別のSNS、それ以外のツールやサービスと連携させることも簡単になり、快適な1人Slack生活が送れます。