詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

ターミナルでSlackを読む

Slackはそのクライアントがそれなりに、かなりよくできていて、これでほとんど困ることはないんだけど、そうは言ってももうちょっとプログラマブルに取り扱いたいこともある。

そういう場合にもよいAPIが用意されていて、Real Time Messaging API ってのがある。こいつはWebSocketでSlackの発言をはじめ、あらゆるイベントのJSONを送りつけてくれるやつ。ひとまずこれを標準出力に流すことができれば、あとは好きに料理できるはずだ。

というわけで作ったのがこちら。書いたことなかったのでRustです。ちょうどいいネタだった。

GitHub - motemen/slack-stream-json

slack-stream-json というバイナリが、SLACK_TOKEN 環境変数を設定した上で起動してやると、RTM APIによって得られたイベントのJSONをそのまま標準出力に流してくれる、それだけのツール。あとは適当なツールやプログラムで欲しい情報を取り出せばよい。

コマンドラインオプション

というだけだとさすがに素朴すぎる(curl + jq + wscat と変わらない)ので、Slack用の便利オプションがいくつかある。

--inflate-fields

Slack RTM でやってくるイベントは、以下のような形状をしている。

{
  "type": "message",
  "channel": "C2147483705",
  "user": "U2147483697",
  "text": "Hello world",
  "ts": "1355517523.000005"
}

これをそのまま標準出力に出力されてもあまりうれしくない……。どんなユーザがどんなチャンネルに投稿したのか、直にはわからないからだ。そこでこの userchannel フィールドを、対応するオブジェクトに展開してやるオプション。

このオプションをつけると、上掲のJSONは以下のように出力される。

{
  "type": "message",
  "channel": {
    "id": "C2147483705",
    "name": "fun",
    "created": 1360782804,
    "creator": "U024BE7LH",
    ...
   },
   "user": {
     "id":"U2147483697",
     "name":"motemen",
     ...
   },
   "text": "Hello world",
   "ts": "1355517523.000005"
}

これは便利。ネストされたフィールドに関してはまだサポートしてないのと、途中から増えたチャンネルやユーザもまだ未対応。

--format-message

同様にSlackにおける発言も決まったフォーマットに則っていて、たとえばメンションは

{
  "text": "Hey <@U024BE7LH>, thanks for submitting your report.",
  ...
}

みたいな形でやってくる。こういうやつを解決して

{
  "text": "Hey @motemen, thanks for submitting your report.",
  ...
}

にしてくれる。テキストにするので、リンクのURLとかは失われます。

<https://www.example.com/|example> my site <https://www.example.com/>

example my site https://www.example.com/

になる感じ。:emoji: の展開はしません。emojifyを使うと絵文字も展開できるが、emojifyはGitHubスタイルの:emoji:にしか対応しておらず、GitHubとSlackは微妙に違うので展開できないものもある(:thinking_face:とか)。 ​

--print-start-response

チャンネルやユーザの情報は、rtm.start APIを叩いた時に返ってくるJSONに含まれている。JSONのストリームをプログラムから利用したい場合は、この情報もあったほうが便利ですよねー。ということで最初のJSONとしてこのレスポンスを表示します。

jqと組み合わせてSlackを読む

そういうわけで、jq と組み合わせればこういう使い方ができます:

% SLACK_TOKEN=xxx slack-stream-json -f -i | jq --raw-output --unbuffered 'select(.type == "message" and (.text | length) and .user) | "\u001b[30m\(.ts | tonumber | localtime | strftime("%X")) \u001b[32m#\(.channel.name) \u001b[33m@\(.user.name)\u001b[m \(.text)"'

全部のチャンネルの投稿が流れてくるので、往年のIRCクライアントみたいなビューが実現できて感激。色までつけちゃってます。

f:id:motemen:20191121201058p:plain
ひとりで喋っている

fzfでインタラクティブにフィルタする

とはいえすべてのチャンネルの発言がフラットに流れてくるだけだと普通に読みづらい……どうしたものか、と考えて編み出したのがfzfと組み合わせてログをフィルタする方法! 上述のストリームを fzf に食わせれば、気になった発言を見たときにチャンネル名 #xxx とか発言者 @yyy でコンテキストを絞り込むことができる。これはやべー!

% SLACK_TOKEN=xxx slack-stream-json -f -i | jq --raw-output --unbuffered 'select(.type == "message" and (.text | length) and .user) | "\u001b[30m\(.ts | tonumber | localtime | strftime("%X")) \u001b[32m#\(.channel.name) \u001b[33m@\(.user.name)\u001b[m \(.text)"' | fzf --ansi --no-sort --tac --nth=2..

f:id:motemen:20191121202718g:plain

まあ、ただ、出力をどんどん溜め込むのでメモリを食っちゃうことは予想できる。つい最近出たfzf 0.19.0のreloadってのを使ったらマシになるのかもしれない。

ネットワークが切れたときとか、復帰はうまくいかないと思うのだけど、それは宿題ということにしておく。どうぞご利用ください。


スクショ撮るのに適切なチームがなかったのでオンラインサロンMOTEMENを使いました^^;

はてなで一緒に働きませんか?