pythonのFlaskを使ってslack messageのOutgoing Webhookを設定する

# やること
- slackのメッセージ投稿をフックしてweb apiで利用できるようにする

## 環境
- python3.6
- Flask==1.0.2
- slackeventsapi==2.1.0

# 対象
- slackの投稿に対して何か処理をしたい人

# 前提
やりたいことそのまんまの名前をしているslackのmessage Outgoing Webhookは既に廃止😭
[slack api Outgoing Webhooks](https://api.slack.com/custom-integrations/outgoing-webhooks)

代わりに[event api](https://api.slack.com/events)を用いることでメッセージをフックする。

# 手順
- Flaskをインストールする
- python-slack-events-apiをインストールする
- slack apiコンソールからslackアプリを作成する
- Flaskでslack-events-apiを用いて、フックを受けるメソッドを定義する
- Event Subscriptionsにフック先のURLを登録する

## Flaskをインストールする
- 今回はFlaskの説明は割愛
- 知らない方はネットに説明がたくさんあるので、そちらを参考してください

というわけで、とりあえず

```
pip install flask
```

## python-slack-events-apiをインストールする
- slack event apiには便利なクライアントライブラリが用意されている
- 別にクライアントライブラリを使わなくても実装はできるが、後述する理由により、ライブラリを使う方が圧倒的に楽。

使うのはこれ→[event api ライブラリ](https://github.com/slackapi/python-slack-events-api)

こちらも

```
pip install slackeventsapi
```
公式でも導入方法が説明されている。英語に抵抗なければ[こちら](https://github.com/slackapi/python-slack-events-api)も参考に

## slack apiコンソールからslackアプリを作成する
- slackの[apiコンソール](https://api.slack.com/apps?new_app=1)を開く
- Create New Appからアプリを作成

<img width="713" alt="スクリーンショット 2019-05-17 0.49.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268579/3d59b371-344a-7d58-e31b-ce5ef1ac61ff.png">

- 導入先のワークスペースを選ぶ。アプリの名前は後からでも変えられるから適当に決める
<img width="571" alt="スクリーンショット 2019-05-17 0.50.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268579/7f828ddf-51af-01bf-e0bd-105f1edcb2ab.png">

- Event Subscriptionsを開いて、Enable EventsをONにする。
<img width="710" alt="スクリーンショット 2019-05-17 0.55.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268579/a0b6c207-7508-e8e7-3252-e514870acc85.png">

## Flaskでslack-events-apiを用いて、フックを受けるメソッドを定義する
- 適当な名前のpythonファイルを作成して、以下を書き込む

```slackHook.py
from flask import abort, Flask, jsonify, request
from slackeventsapi import SlackEventAdapter

app = Flask(__name__)

slack_events_adapter = SlackEventAdapter("SIGINING_SECRET", "/slack/events", app)
@slack_events_adapter.on("message")
def hookSlackEvents(event_data):
    app.logger.debug(event_data)
```
- この際のSIGINING_SECRETには、先ほど作成したslackアプリのBasic Informationに書いてある、SIGINING SECRETを入れる。
※図の中ではミスによりClients Secretにオレンジ枠がかかっているが、正しくはSigning Secretなので注意
<img width="699" alt="スクリーンショット 2019-05-17 1.04.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268579/6f63dbb6-45c1-11ab-d6fc-d9a31ef1fd87.png">

- Flaskサーバを起動する
- Flaskサーバをローカルで立ち上げた場合は、ngrok等を利用して、外部からアクセスできるようにする

## Event Subscriptionsにフック先のURLを登録する
- 先ほど作成したslackアプリのEvent Subscriptionsを開いて、Request Eventsにさっき立ち上げたサーバのエンドポイントを登録する
- `baseURL + /slack/events`というように、Flaskのslack_events_adapteに登録したリソースと同じように入力する

<img width="703" alt="スクリーンショット 2019-05-17 1.10.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268579/8c862b70-e635-33e6-897f-8ad2110c89d1.png">

- エンドポイントを入力してしばらくすると、Verifiedとでるので、こうなればフックが完了したこととなる
- 同じ画面の中間あたりのSubscribe to Workspace Eventsで、フックしたいイベントを選択する。今回は`message.channels`を
<img width="668" alt="スクリーンショット 2019-05-17 1.51.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268579/a9ae5b88-844a-f52b-83d6-5ab522034c2a.png">

- 画面を離れる前に画面下部のsave changeを押すのを忘れないように
- ちなみに、クライアントライブラリを使わなかった場合、[エンドポントが生きているかの認証処理](https://api.slack.com/events/url_verification)を自分で実装しなければならないのでめんどくさい（自分はうまくできなかった・・・）
    - 成功してる人いた→[Outgoing WebHooksの代わりをSlack APPで実装する](https://qiita.com/risuto_nishimura/items/342256f6ed6cb504059a)

## 確認
- どのチャンネルでもいいので、なにかしたメッセージを投稿すれば、コンソールにログがでるはず。

# うまく行かないとき
- apiを使うときにはいくつかの権限を付与する必要があるため、もし自分のslackアカウントがアプリに権限を与える権限を持っていない場合はうまく行かないかも

# ちょっと解説
わかる人か見れば明白なのだけど、一応この部分で何やってるかの解説

```slackHook.py
slack_events_adapter = SlackEventAdapter("SIGINING_SECRET", "/slack/events", app)
@slack_events_adapter.on("message")
def hookSlackEvents(event_data):
    app.logger.debug(event_data)
```
- 一行目ではただクライアントのインスタンスを作成しているだけ
- ＠から始まる二行目は、通常のFlaskアプリのルーティングの代わりの役目を果たす
- 三行目が、そのローティングに対して実行したいメソッド
- event_dataにイベントの内容がJsonで受け取れる
- 今回はメッセージの投稿をフックしたかったため、`"message"`を指定したが、別のイベントもフックすることができる。
- `"message"`の変わりに、使いたいイベントを`@slack_events_adapter.on("event")`に書いて、Subscribe to Workspace Eventsからフックしたいイベントを追加すればOK
    - イベント一覧[API Event Types](https://api.slack.com/events)


# あとがき
- 多分slack上のほぼ全てのイベントをフックできるから、使いこなせばワークスペースの神になれる
    - 新しいメンバーがジョインしたら自動で資料を送信する
    - ファイルがアップロードされたらそれをGoogle driveにアップ
    - 嫌いな奴が投稿するたび、その投稿を削除する
- slack web apiを使うためのpython-slackclientと、今回のpython-slack-events-apiは別ライブラリなので注意
- slack web apiでも[RTM api](https://github.com/slackapi/python-slackclient#basic-usage-of-the-rtm-client)を使えば、同じようにイベントのフックが可能っぽい  
参考↓

```rtm.py
import os
    import slack

    @slack.RTMClient.run_on(event='message')
    def say_hello(**payload):
        data = payload['data']
        web_client = payload['web_client']
        rtm_client = payload['rtm_client']
        if 'Hello' in data['text']:
            channel_id = data['channel']
            thread_ts = data['ts']
            user = data['user']

            web_client.chat_postMessage(
                channel=channel_id,
                text=f"Hi <@{user}>!",
                thread_ts=thread_ts
            )

    slack_token = os.environ["SLACK_API_TOKEN"]
    rtm_client = slack.RTMClient(token=slack_token)
    rtm_client.start()
```

- 自分はFlaskをzappaをつかって、lambdaにデプロイしたが、ワークスペースの全ての投稿のたびに呼びだされるから、さすがのlambdaも無料枠突破するのでは？　とちょっと心配
    - zappaでデプロイするとデフォルトで512MBになるから、無料枠は800,000秒
    - 一回の処理に多くみつもって1秒かかるとしても800,000投稿/月までは大丈夫。1日26,666投稿までは大丈夫と考えると全然余裕だった。
 


Twitterもやってるので、よければフォローお願いします。
→https://twitter.com/ObataGenta