slackへの書き込みをmattermostに連携してみた

## やりたかったこと

飲みすぎた・・・明日もう会社やすみたい・・・
でも、その手の連絡はmattermostでやることになっていて、家から連絡しようにもF/Wで閉じられてるしなぁ・・・
VPNを通せばいいのだけれど、それも面倒だし・・・
っていうかスマホからさくっと連絡すませたい・・・

ということで、slackとmattermostを両方使っているという謎運用を利用して、
slackの特定チャンネルに投げるとmattermostにも流れるようにできないか、とチャレンジしてみた。

使用言語はpythonだが、超初心者なのでその辺はご勘弁ください。

## 構成（イメージ）

* slack
    * 無料範囲で利用
    * mattermost連携用の専用チャンネルを作る
    * botを登録し、専用チャンネルに紐付ける
* mattermost
    * 5.1.0 on CentOS7（たぶんどのバージョンでも動く）
    * slackから投げられる専用チャンネルを作る
    * Incomming-Webhookを登録
* mattermostにhookできるIPを持ったサーバ上のツール
    * Python3.6.4 on CentOS7（python2系使うならurllib3まわりを修正する必要アリ）
        * 面倒なのでmattermostサーバに同居させた
        * INは絞られていてもかまわないが、OUTはslackへの到達性が必要なので、注意
    * slackに情報を取りに行く
    * その情報をmattermostに投げる
    * 常時起動しておきたいので、systemdに登録

## slackにBotを登録 ＆ mattermostにwebhookを登録する

webでもqiitaでもいい記事がいっぱいあるので、ここでは、割愛。

## slackからとってくるClass

[zukaさんのブログ](https://www.indetail.co.jp/blog/9403/)を参考に、[python-slackclient](https://github.com/slackapi/python-slackclient)を利用。
ちょっと古いバージョン(1.2.1)でよければ、githubからとって来なくとも普通に`pip install slackclient`でとってこれた。

```py:class_SlackReader
import time
from slackclient import SlackClient

slack_token        = "slackで発行したBotのtoken"

class SlackReader:
    def __init__(self):
        self.slack = SlackClient(slack_token)
        if self.slack.rtm_connect():
            while True:
                data = self.slack.rtm_read()
                if len(data) > 0:
                    for item in data:
                        if item["type"] == "message":
                            ### mattermostに投げる処理
                    continue
                else:
                    time.sleep(30)
        else:
            print("Connection Failed")
```

古いバージョンなら普通にインストール可能、と先に書いておきながら、
実は古いバージョンにはrtm_readした際にメッセージを１個しかとってきてくれない[バグ](https://github.com/slackapi/python-slackclient/issues/231)がある。
そのため、メッセージ取得に成功した場合は、sleepせずにcontinueで再度取得している。

## mattermostに投げるClass

これは、以前に自分で適当に作ったclassを少しいじって流用。
urllib3で所定のフォーマットで投げるだけ。

```py:class_MattermostSender
import json
import requests
import urllib3
from urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)

mattermost_url     = "mattermost-hookのURL"
mattermost_channel = "投げたいチャンネル名"
mattermost_iconurl = "/static/emoji/1f43c.png" # パンダ

class MattermostSender:
    def __init__(self, message_data):
        self.post_data = {
            "channel"  : mattermost_channel,
            "icon_url" : mattermost_iconurl,
            "username" : message_data["user"],
            "text"     : message_data["text"]
        }
    
    def send(self):
        headers = {'content-type': 'application/json;charset=UTF-8'}
        try:
            r = requests.post(mattermost_url, headers=headers, data=json.dumps(self.post_data), verify=False)
        except Exception as curl_error:
            print(str(curl_error))
            sys.exit()
```
InsecureRequestWarningしているのは、検証環境がオレオレのため。
正規証明書の環境でだけ動かすなら、もちろん消す方が良い。

## systemdで起動

上記を組み合わせた一枚モノのpythonコード（slack-feed.py:仮称）を、あとはsystemdで起動すればOK
以下はとりあえず動かす用でだいぶ適当な例

```/etc/systemd/system/slack-feed.service
[Unit]
Description=slack-feed
After=syslog.target network.target

[Service]
Type=simple
User=root
Group=root
ExecStart=/path/to/slack-feed.py
PrivateTmp=yes
Restart=no
RestartSec=30

[Install]
WantedBy=multi-user.target
```

## 一応、動きはした

改善点はあるものの、一応到達はした。
これで自分用のスマホからでも、会社に「ちょっと体調悪いことにして休みます〜」とメッセージが投げれるようになった！
よし、休もう！

* 改善点
    * slackの`user`はよくわからんIDになっているので、見てわかるユーザー名に変えてやる必要がある
    * そのついでに、アイコンがパンダ固定なのもなんとかしたいところ
    * slackサーバ側の負荷を考慮して同期間隔を30秒にしたが、もう少し短くても大丈夫かも
    * **そもそもslackをメインの運用にしてればこんなことしなくて済んだのに。**

## slack-feed.py全貌

説明の都合上小分けにしたので、最後にあらためてpythonファイルの全貌を。

```py:slack-feed.py
#!/path/to/python
# -*- coding: utf-8 -*-
import sys,os
import re

### slackからとってくるのに使用
import time
from slackclient import SlackClient

### mattermostに送るのに使用
import json
import requests
import urllib3
from urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)

### 設定
mattermost_url     = "https://mattermost/hooks/hogehoge"
mattermost_channel = "slack-channel"
mattermost_iconurl = "/static/emoji/1f43c.png"
slack_token        = "hogepiyo"

### mattermostにメッセージ送信するクラス
class MattermostSender:
    ### init：送信データを辞書形式で作成
    def __init__(self, message_data):
        self.post_data = {
            "channel"  : mattermost_channel,
            "icon_url" : mattermost_iconurl,
            "username" : message_data["user"],
            "text"     : message_data["text"]
        }
    
    ### データをmattermostに送信する
    def send(self):
        headers = {'content-type': 'application/json;charset=UTF-8'}
        try:
            r = requests.post(mattermost_url, headers=headers, data=json.dumps(self.post_data), verify=False)
        except Exception as curl_error:
            print(str(curl_error))
            sys.exit()
        
class SlackReader:
    def __init__(self):
        self.slack = SlackClient(slack_token)
        if self.slack.rtm_connect():
            while True:
                data = self.slack.rtm_read()
                if len(data) > 0:
                    for item in data:
                        if item["type"] == "message":
                            MattermostSender(item).send()
                    continue # slackclientのバグで一個しかとってこない(issue #231)ので、再度投げる
                else:
                    time.sleep(30)
        else:
            print("Connection Failed")
 
###### MAIN SCRIPT ######
if __name__ == '__main__':
    SlackReader()
```