HAVRMの空空活動誌

「空空」は「うつらうつら」と読むらしいです.まぁ常に寝不足なもんで...

Slack Botの製作 ~2:よくわからないけど受信してみる~

こんにちは.
HAVRMです.

Slack botの作成の二回目です.前回は文字の送信だけ行いました.
havrm.hatenablog.com
今回はとりあえずShellScriptで受信してみます(他の言語で用意されたライブラリ等使った方が圧倒的に楽だと思います(ブログとかにまとめてあるので)が,やはりShellScriptでやります).
なお前回言ってませんでしたが今回はWindows Subsystem for Linux(WSL)でUbuntu 18.04 LTSを起動して行っています.

とりあえず受信する

URL取得

今回botで使うAPIはReal Time Messageing API(以降RTM API)です.このAPIの場合,固有IPアドレスは不要で受信できますが,起動中はWeb Socketを使って受信し続ける必要があります.Slackのドキュメントとか知識不足で苦労しながら読んだところRTM APIで受信するためにはrtm.connectメソッドを送ってURLを得る必要があります.
ということでとりあえず送信します.

curl -s -X GET -H 'Content-Type:application/json' -H 'Authorization: Bearer <トークンID>' https://slack.com/api/rtm.connect

これを送信すると次のようなデータがJSONで帰ってきます(jqコマンド*1で整えてます).

{
  "ok": true,
  "url": "<URL>",
  "team": {
    "id": "<ワークスペースID>",
    "name": "<ワークスペース名>",
    "domain": "<ワークスペースドメイン(hoge.slack.comのhoge)>"
  },
  "self": {
    "id": "<botのID>",
    "name": "<bot名>"
  }
}

この2行目にあるURLを使います.

Web Socketで読んでみる

ということでこのURLでアクセスすればいいです.URLは30秒しか有効でないので発行したらすぐに接続する必要があります.簡単に確認するにはwscatコマンド*2で見ることができます.コマンドは次のようにします.

wscat <URL>

ということで何のひねりもないコマンドで読むことができます.送信されるデータはJSONです.次のような感じに帰ってきます.

connected (press CTRL+C to quit)
< {"type": "hello"}
< ...
< {"type": "goodbye", "source": "gateway_server"}
disconnected

接続成功時に type:hello が,自動で閉じられるときは type:goodbye が来るようです.イベントが発生するごとにJSONが届きます.botで受け取れるイベントは公式サイトにまとめてあります.
api.slack.com

事前に見ていた時は数秒間に一回送信しないといけない(ダミー用にpingがある)みたいに書いてありましたが(流し読みしていたので違うかもしれません),何もしなくても数時間ぐらいは使えてました.
なお最初接続成功しても,チャンネルの#generalに送信してもイベントをWeb Socketで受信できませんでした.これはbotをチャンネルに招待していなかったので起きたもので,招待すれば普通に読むことができるようになりました.

ShellScriptで読む

ということで受信に成功しました.しかし改めて調べてみるとwscatではShellScriptにデータを渡せないようです.そのため別のツールwebsocatコマンドを使います.

websocatのインストール

websocatはGithubに公開されています.
github.com

いろいろインストール方法等書いてありますが,簡単にパッケージでインストールしましょう.パッケージは次の場所にアップロードされています.
github.com

このうち,websocat_1.5.0_ssl1.1_amd64.deb(2020/4/12現在)をダウンロードし書いてあるようにgdebiコマンド*3でインストールします.

sudo gdebi websocat_1.5.0_ssl1.1_amd64.deb

読むだけならコマンドはwscatと同様に次のようなコマンドで読めます.

websocat <URL>

この時の受信データもwscatと同じですが,標準出力で出力されます.

websocatで読んだデータをshellscriptに渡す.

読んだデータをshellscriptに送信します.以下の返答がまんま答えです参考になります.
stackoverflow.com

個人的な好みとしてShellScriptはファイルとして独立させたいのでこのようにexportは使わないです.ということで受信用のShellScript(read.sh)を作ります.
~/read.sh

#!/bin/bash
A=( "1" "2" "3" "4" "5" )
echo "" > ~/read.txt
for B in "${A[@]}"
do
        read -r LINE
        echo "$B is $LINE" >> ~/read.txt
done
kill "$PPID"

なお最初に空文字をread.txtに上書きしている(echo "" > ~/read.txt)のは中身をクリアするためです.今回はテストのために5回受信すれば終了するようにしています(要するに2回文字を別ユーザが送信すれば終了.helloイベントと,メッセージ送信イベント2回,メッセージ記入中イベント2回).killしているのは親プロセスIDなので終了する形になります.
送信するためのShellScript(connect.sh)としては次のようになります.
~/connect.sh

#!/bin/bash
websocat --text $1 sh-c:'exec bash -c ~/read.sh'

ここで--textのオプションを付けているのは注意されたためです.ShellScript側から送信するデータ形式を指定しろとのことでした.これでこのShellScriptの変数にURLを打てばつなげてくれます.あとread.shのほうが Permission denied されたのでとりあえず両方に実行権限を与えました.

chmod a+x ~/read.sh
chmod a+x ~/connect.sh

これでコマンド次のように実行すると~/read.txtにデータが保存されます.

. connect.sh <URL>

~/read.txt

1 is {"type": "hello"}
2 is {"type":"user_typing","channel":"<チャンネルID>","user":"<ユーザID>"}
3 is {"client_msg_id":"<メッセージID>","suppress_notification":false,"type":"message","text":"a","user":"<ユーザID>","team":"<ワークスペースID>","blocks":[{"type":"rich_text","block_id":"<ブロックID>","elements":[{"type":"rich_text_section","elements":[{"type":"text","text":"a"}]}]}],"source_team":"<ワークスペースID>","user_team":"<ワークスペースID>","channel":"<チャンネルID>","event_ts":"1586667548.012700","ts":"1586667548.012700"}
4 is {"type":"user_typing","channel":"<チャンネルID>","user":"<ユーザID>"}
5 is {"client_msg_id":"<メッセージID>","suppress_notification":false,"type":"message","text":"a","user":"<ユーザID>","team":"<ワークスペースID>","blocks":[{"type":"rich_text","block_id":"<ブロックID>","elements":[{"type":"rich_text_section","elements":[{"type":"text","text":"a"}]}]}],"source_team":"<ワークスペースID>","user_team":"<ワークスペースID>","channel":"<チャンネルID>","event_ts":"1586667549.012900","ts":"1586667549.012900"}

(連続で「a」一文字送信しました)

一連の動作をShellScriptにまとめる

ここまでWeb Socketにつなげるたびに毎回 rtm.connectメソッド を手動で送信しURLをコピーしています.ただひたすらに面倒なのでこのURL抜き出す作業もShellScriptで行いましょう.jqコマンドでJSONから簡単にデータを抜き出すことができます.

curl -s -X GET -H 'Content-Type:application/json' -H 'Authorization: Bearer '<トークンID>' https://slack.com/api/rtm.connect | jq -r '.url'

jqコマンドの -rオプション はダブルクォーテーションを削除するためです.
これを先ほど作った ~/connect.sh に追加すると次のようになります.

#!/bin/bash
URL=$(curl -s -X GET -H 'Content-Type:application/json' -H 'Authorization: Bearer '<トークンID>' https://slack.com/api/rtm.connect | jq -r '.url')
websocat --text $URL sh-c:'exec bash -c ~/read.sh'

これで受信し,ShellScriptで処理するところまでできました.

まとめ

ということでShellScriptでデータを受信し,処理するところまでできました.今後やりたいことなどを整理しながらコードを作っていこうと思います(要するに無計画).

*1:jqコマンドは sudo apt install jq でインストールできます

*2:wscatコマンドは sudo apt install node-ws でインストールできます

*3:gdebiコマンドは sudo apt install gdebi-core でインストールできます