- Node.js Stream 学習シリーズです
- Mastodon のタイムラインをターミナルに流す - 知らないけどきっとそう。 では Readable Stream の性質を使って Mastodon のタイムラインが流れ出てくるだけのものを作成しました
- 今回は Writable Stream も利用して、データを送り込めるようにします
- Mastodon は観察専門なので、使用頻度の高い Slack を題材とします
- 最初は Mastodon と同じく Slack のリアルタイムデータが流れ出てくる Stream を作ります
- Legacy tokens | Slack から Authentication token を入手
- rtm.connect method | Slack で token を渡して Real Time Messaging API の WebSocket エンドポイントを得る
- WebSocket から受け取った message を stream に書き込み
- stream から stdout に出力
let ws; const url = "https://slack.com/api/rtm.connect?token=xoxp-0000000000-0000000000-000000000000-0123456789abcdef0123456789abcdef"; const stream = new require("stream").PassThrough(); require("https").get(url, res => { res.on("data", chunk => { const obj = JSON.parse(chunk); ws = new (require("ws"))(obj.url); ws.on("message", data => stream.write(data + "\n")); }); }); stream.pipe(process.stdout);
$ node index.js {"type":"hello"} {"type":"reconnect_url","url":"wss://mpmulti-0a1g.slack-msgs.com/websocket/ZvwORwLc1uFhgMHJAZva-04tZg2Lp5iJEclZ8vnOCctMTnJC64LQvb7MZvgJNifnscAGXvki2UAEcJpnlwFwCY031HGGe-NizIM5cZZKfdMQcO9kbXBhb8LkUKQnhR3bx-Z5hxefvQgsVQVgZwSFO9huhgXo3cBvjCc44YTZklY="} {"type":"presence_change","presence":"active","user":"U5HCFCV5W"} ...
- ここに、書き込むと WebSocket にデータを送る Writable Stream のコードを追加して stdin と繋ぎます
const writable = new require("stream").Writable({ write: (chunk, encoding, callback) => { ws.send(chunk.toString()); } }); process.stdin.pipe(writable);
- stdin へチャンネルにメッセージを投稿するための JSON を入力します
$ node index.js ... {"type":"message","channel":"C5JT9C849","text":"test"}
- 直後に成功を表す JSON が送られてきたら、ブラウザ等から確認すると反映されていることがわかります
{"ok":true,"reply_to":0,"ts":"1495991511.232093","text":"test"}
- ここまでで、最もシンプルな Slack クライアントが完成しました
- 送られてくる JSON は channel や user の情報が "C5JT9C849" のような id となっていて扱いづらく、よって name に変換する Transform Stream を作って間にはさみます
- id と name のマップは rtm.connect method | Slack ではなく rtm.start method | Slack でリクエストすると送られてきます
const url = "https://slack.com/api/rtm.start?token=xoxp-0000000000-0000000000-000000000000-0123456789abcdef0123456789abcdef"; const stream = new require("stream").PassThrough(); require("https").get(url, res => { ... }); const transform = new require("stream").Transform({ transform: function(chunk, encoding, callback) { ... } }); stream.pipe(transform).pipe(process.stdout);
{"type":"message","user":{"id":"U5HCFCV5W","name":"@asannou"},"text":"test","ts":"1496083043.865560","channel":{"id":"C5JT9C849","name":"#vip"}}
- 出力が JSON のままでは可読性が低いので、jq を使って整形します
{"type":"message","user":{"id":"USLACKBOT","name":"@slackbot"},"text":"Hello, I’m Slackbot. I try to be helpful. (But I’m still just a bot. Sorry!) *Type something* and hit _enter_ to send your message.","ts":"1495732144.332015","channel":{"id":"D5J44CUDS","name":"@slackbot"}} {"type":"message","user":{"id":"USLACKBOT","name":"@slackbot"},"text":"Pleasure to meet you. Let me show you a couple things about Slack.","ts":"1495732157.337778","channel":{"id":"D5J44CUDS","name":"@slackbot"}}
170525170904.332015 [@slackbot] (@slackbot) Hello, I’m Slackbot. I try to be helpful. (But I’m still just a bot. Sorry!) *Type something* and hit _enter_ to send your message. 170525170917.337778 [@slackbot] (@slackbot) Pleasure to meet you. Let me show you a couple things about Slack.
- タイムスタンプ部分 "%y%m%d%H%M%S.%6N" が苦しいですが、生 JSON よりはマシでしょう
- 出力のすべては JSON であるがゆえに、特定のチャンネルをフィルタリングするなど、jq の能力が発揮されます
$ node index.js | jq -c --unbuffered 'select(.channel.name != "#general")'
- 入力についても、手打ち JSON は死ぬので jq に頼ります
$ echo "test" | jq -R -c --unbuffered '{type:"message",channel:{name:"@slackbot"},text:.}' {"type":"message","channel":{"name":"@slackbot"},"text":"test"}
- 入力と出力を jq で挟むことにより、ちょっとだけまともな Slack クライアントになりました
$ jq -R -c --unbuffered '{type:"message",channel:{name:"@slackbot"},text:.}' | node index.js | jq --unbuffered -r -f format.jq help! 170528175842.970929 [@slackbot] (@asannou) help! 170528175842.970933 [@slackbot] (@slackbot) I can help by answering simple questions about how Slack works. I'm just a bot, though! If you need more help, try our <https://get.slack.help/hc/en-us/|Help Center> for loads of useful information about Slack ― it's easy to search! Or simply type */feedback* followed by your question or comment, and a human person will get back to you. :smile:
- 入力時にカーソル操作ができなかったり、メッセージが送られてくると割り込んで表示されるストレスは Readline | Node.js v10.9.0 Documentation を利用して解決します
- node-slacat/rwlap.js at master · asannou/node-slacat · GitHub のようなスクリプトを書いて、以下のとおりに使いましょう
$ ./rwlap.js "jq -R -c --unbuffered '{type:\"message\",channel:{name:\"@slackbot\"},text:.}' | node index.js | jq --unbuffered -r -f format.jq"
- Readline は、他にもタブキーによる入力補完もサポートしているため、公式クライアントのような、ユーザ名の補完なども実現可能です
- Slack といえば通知ですが、指定された文字列に色を付けると同時に BEL (\x07) を出力する node-slacat/highlight.js at master · asannou/node-slacat · GitHub を最後にパイプすると目的を達成できます
$ ./rwlap.js "jq -R -c --unbuffered '{type:\"message\",channel:{name:\"@slackbot\"},text:.}' | node index.js | jq --unbuffered -r -f format.jq | ./highlight.js asannou"
170528175842.970929 [@slackbot] (@asannou) help!
- もちろん、同様のことができるコマンドで、代替してもかまいません
- その他いろいろ
- Slack には https://get.slack.help/hc/ja/articles/201727913 があるが、管理者に許可してもらう必要があり、セキュリティ上推奨もされていません
- 個人的に Slack の機能はこれで足りているので、しばらく使ってみようと思います