【② Slack編】当日の天気を知らせてくれるSlack Botを作ってみた(Python + Selenium + Slack + AWS ECS + Github Actions)

毎日、当日の天気を能動的に調べるのは面倒なので、天気を知らせてくれるSlack Botを作ってみました。
処理の流れは以下のようになっています。

```
天気予報のWebページを取得 --> 当日の天気予報のスクリーンショットを撮る --> Slackへアップロード
```

天気予報のAPIから情報を取得・整形してSlackへ通知するようなBotでもよかったのですが、いい感じのAPIがなかったのと、リッチな通知にしたかったのでこのようになりました。
実装からAWS ECSへのデプロイ・スケジューリングまでを技術ごとに分けて、どのように作成したかを説明していこうと思います。
今回は【Slack編】です。

# 構成
- ① [Selenium編](https://qiita.com/homines22/items/9fe2fa3de08dd978d31b)
- ② Slack編 <-- いまここ
- ③ Github Actions編
- ④ AWS ECS編

# 実装

## 環境
- Python 3.7
- pipenv
- requests 2.22.0

## Slack Botの用意
Slack Botを[こちら](https://api.slack.com/bot-users)を参考にして用意してください。Botを作成すると`Bot User OAuth Access Token`が取得できます。今回はこのアクセストークンを使用してSlackへスクショを投稿します。


## ソースコード
SlackのAPIを簡単にPythonから利用できる[SDK](https://github.com/slackapi/python-slackclient)もありますが、スクショした画像をSlackへ送信するだけなので今回は使用しません。
まず、Slack Bot用のACCESS_TOKENとCHANNELは環境変数から取得します。CHANNELの方は特に指定がなければデフォルトでgeneralチャンネルが設定されますが、ACCESS_TOKENは設定が必須となっています。
次に、SlackのファイルアップロードAPIの仕様に合わせてHTTPリクエストを作成します。今回設定したパラメーターは以下の通りです。Slack APIの仕様詳細は[こちら](https://api.slack.com/methods/files.upload)を参照してください。


|設定項目|値|
|:--:|:--:|
|URL|https://slack.com/api/files.upload|
|メソッド|POST|
|Content-Type|multipart/form-data|
|token|ACCESS_TOKEN|
|channel(option)|CHANNEL|
|filetype(option)|binary|

requests.postの引数のfilesに`{'filename': open(filepath, 'rb')}`を渡してあげると、Content-Typeに`multipart/form-data`が自動的にセットされるのでheaders引数を指定する必要はありません。中でどのように処理されているかは[requests/models.py](https://github.com/psf/requests/blob/bedd9284c9646e50c10b3defdf519d4ba479e2c7/requests/models.py#LC109)と[urllib3/filepost.py](https://github.com/urllib3/urllib3/blob/f7a4bed04085975918b3469bf94e195df9faf29a/src/urllib3/filepost.py#L63)を読むと分かります。
最後にファイルのアップロードが成功したかどうかをチェックします。Slackからのレスポンスが`{"ok": true}`であればアップロード完了です。`{"ok": false, "error": "error_message"}`であればアップロード失敗となるので例外を発生させます。

```slack.py
import os
import sys
import requests


URI = 'https://slack.com/api/files.upload'
CHANNEL = os.getenv('CHANNEL', 'general')

try:
    ACCESS_TOKEN = os.environ['ACCESS_TOKEN']
except KeyError:
    sys.exit('You need to set evironment variable called "ACCESS_KEY"')


def upload(filepath):
    """upload filepath to slack

    Args:
        filepath: relative or absolute path of png file

    Returns:
        True
        Exception
    """

    try:
        files = {'file': open(filepath, 'rb')}
        params = {
            'token': ACCESS_TOKEN,
            'channels': CHANNEL,
            'filetype': 'binary',
        }

        res = requests.post(URI, params=params, files=files, timeout=10)

        if not res.ok:
            raise Exception(res.error)

        return True

    except Exception as err:
        raise err

if __name__ == '__main__':
    try:
        filepath = sys.argv[1]
    except Exception:
        sys.exit('Error: Please specify a filepath')

    upload(filepath)
```

以下のようなコマンドを実行するとSlackへtest.pngがアップロードされます。

```bash
$ pipenv run python3 slack.py test.png
```

# 終わりに
[Selenium編](https://qiita.com/homines22/items/9fe2fa3de08dd978d31b)とSlack編で紹介したソースコードはあえて個々で独立して動作するようにしていました。一つのアプリケーションとしての完成形のソースコードは[こちら](https://gist.github.com/homoluctus/6633da08af271340e71cf4309ab17182)を参照してください。

次は【Github Actions編】です。 Github Actionsを使ってCI/CDを実現する方法を説明します。