[Micropython]IoT機器からSlackの投稿を監視

## はじめに
ある企画にて、IoT機器からSlackの指定チャンネルの投稿で新着があるかを監視する必要がありその処理をMicropythonを使って実装しました。そのときの実装ポイントの備忘録となります。
(企画についてはご興味があるかたはこちらを御覧ください)

https://qiita.com/aramss/items/7e129d29c82e1334a91e

## TL;DR
* PythonにはSlask SDKを使って簡単にSlack APIを叩くことができるがMicropythonにはないのでurequestsを使ってHTTPリクエストを行う
* ユーザートークンを渡すときはBearer Tokenとしてヘッダーで指定
* Slackの投稿の監視は`conversations.history`のAPIを使って記事のタイムスタンプをチェックする

## 前提
監視の対象となるSlackのチャンネルの投稿に対して以下の情報がすでにわかっているものとします。
* アクセスに必要なユーザートークンとアクセス権限(SlackアプリのScopeでいうところの`channels:history`)
* 監視の対象となるチャンネルのID

## 使用するSlack API
Slackのチャンネルの投稿内容を取得するための以下の`conversations.history`APIを使用します。

https://api.slack.com/methods/conversations.history

この`conversations.history`APIの引数の`token`と`channel`は必須で、他の引数は任意となっています。`latest`や`oldest`の引数で指定する時間を起点にしてそこから前後何個分の投稿を取得するかを`limit`で指定できるようになっています。

今回実装したのは一番最新の投稿だけ取得できればよいのと、`latest`には何も指定しなければ現在時刻が起点となるので`limit`に1を指定するだけにします。

PythonではSlack SDKがありSlack APIにパラメータを指定するのも比較的容易に実装できますが、今回使うのはMicropythonということもありSlack SDKは使用できないので、Micropythonのurequestsモジュールを使ってSlack APIを実行することにしました。

## Slack最新記事のチェック方法
Slack APIの`conversations.history`を使って投稿をチェックします。
`conversations.history`APIのレスポンスは次のようなかたちになります(slack apiの例を転記)。

```json
{
    "ok": true,
    "messages": [
        {
            "type": "message",
            "user": "U123ABC456",
            "text": "I find you punny and would like to smell your nose letter",
            "ts": "1512085950.000216"
        },
        {
            "type": "message",
            "user": "U222BBB222",
            "text": "What, you want to smell my shoes better?",
            "ts": "1512104434.000490"
        }
    ],
    "has_more": true,
    "pin_count": 0,
    "response_metadata": {
        "next_cursor": "bmV4dF90czoxNTEyMDg1ODYxMDAwNTQz"
    }
}
```

投稿内容として書かれる文面は`messages`配列の`text`要素からわかります。
同列にある`ts`要素は投稿の「タイムスタンプ」を表しています。

APIによって最新の投稿内容を取得できますが、ユーザーがその投稿を一度見ていればユーザーにとっては最新ではありません。

そこで、今回はユーザーが投稿に対して「リアクション」したときのタイムスタンプを保存しておき、そのタイムスタンプと比較することで最新かどうかを判断するようにします。
(実際には、APIを使って「リアクション」するとき、どの投稿に対して「リアクション」するかをタイムスタンプで指定する必要があるので、リアクションのAPIで指定したタイムスタンプの値を保存することになります)

こちらが、APIを使ってSlackの指定チャンネルの最新の投稿記事のタイムスタンプを取得したときのMicropythonのコードです。

```python
import urequests
import ujson

latesttime = "0" # タイムスタンプ初期値(文字列)

def check_latest():
    # 投稿内容を取得するためのURL
    history_url = 'https://slack.com/api/conversations.history'
    # ユーザートークン
    token = 'xoxp-xxxxxxxxxxxxx.xxxxxxxxxxxxx'
    # チェック対象のSlackチャンネル
    channel = 'Cxxxxxxxx'
    # ヘッダーの作成
    header = {}
    header['Content-Type'] = 'application/json; charset=utf-8'
    header['Authorization'] = 'Bearer ' + token # Bearer Tokenの設定
    # 投稿チェック時のパラメータを設定
    body = {}
    body['channel'] = channel
    body['limit'] = 1	# 最新の1件分だけ取得
    res = urequests.post(
        url = history_url,
        data = ujson.dumps(body),
        headers = header
    )
    resjson = res.json()
    if not resjson['ok']: # レスポンスがOKかチェック
        print('cannot check latest message!')
        return
    if not resjson['messages']: # チャンネルに投稿があるかをチェック
        print('no message!')
        return
    #タイムスタンプで新着かどうかをチェック
    messages = resjson['messages']
    if latesttime < messages[0]['ts']:　　#タイムスタンプ文字列比較
        latesttime = messages[0]['ts']
        print('new message exites!')
```

ポイントだけ説明します。
```python
    # ヘッダーの作成
    header = {}
    header['Content-Type'] = 'application/json; charset=utf-8'
    header['Authorization'] = 'Bearer ' + token # Bearer Tokenの設定
```
ユーザートークンはヘッダ内に`Authorization`でBearer Tokenとして設定します。
```python
    # 投稿チェック時のパラメータを設定
    body = {}
    body['channel'] = channel
    body['limit'] = 1	# 最新の1件分だけ取得
```
`conversations.history`のAPIに渡す引数を設定しています。
`channel`にはチェックの対象となるチャンネルID(Cで始まる値)を設定します。
`limit`に1を設定するだけで、「現時点時間から遡って最初の1件だけ」の設定になります。
```python
    #タイムスタンプで新着かどうかをチェック
    messages = resjson['messages']
    if latesttime < messages[0]['ts']:　　#タイムスタンプ文字列比較
        latesttime = messages[0]['ts']
        print('new message exites!')
```
messages[0]には最新の投稿の情報が入っているので、タイムスタンプが書かれている`messages[0]['ts']`と保持しているタイムスタンプ値`latesttime`を比較することでユーザーにとって最新かどうかをチェックしています。

## 最後に
今回、Slack APIを叩くだけのMicropythonのコードを以下のチップが載っているCPUボードで試してみました。

* ESP32-C3
* ESP32-S3
* RP2040

それぞれ特徴的な動きをしていましたが、それ以上に驚いたのは作ったコードをそのままなんの設定も変えずファイルを移動しただけ動くMicropythonが便利すぎたことでした。

これで、組込系のPoCもかなり捗る気がしています。