- Mastodon の各種タイムラインは WebSocket でストリーミングされている
- Node.js の Stream の学習を兼ねて、これを様々なターミナルに出力できるようにする
- 「Stream を制するものは、 Node.js を制す」らしい
$ node -e 'new (require("ws"))("ws://friends.nico/api/v1/streaming/?access_token=...&stream=public").on("message", (data, flags) => console.log(data))'
{"event":"update","payload":"{\"id\":1920985,\"created_at\":\"2017-04-24T16:10:03.248Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{...
- これを parse() して Stream を作成し transform して出力
const stream = new require("stream").Transform({
objectMode: true,
transform: function(chunk, encoding, callback) { ... }
});
new (require("ws"))("ws://friends.nico/api/v1/streaming/?access_token=...&stream=public")
.on("message", (data, flags) => stream.write(JSON.parse(data)));
stream.pipe(process.stdout);
- Stream は TCP Socket に pipe() でつなぐことができる
- 標準入力の Stream を pipe() して、ポート 12345 に接続すると ping の結果が流れてくるサンプル
$ ping localhost | node -e 'process.stdin.resume();require("net").createServer(socket => process.stdin.pipe(socket)).listen(12345);'
$ telnet localhost 12345
Trying ::1...
Connected to localhost.
Escape character is '^]'.
64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.065 ms
64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.056 ms
64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.095 ms
64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.049 ms
64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.054 ms
^]
telnet> Connection closed.
- pipe() によって、何回でも Stream の分岐や合流が可能
- クライアントが複数接続しても、それぞれに同じデータを送ることができる
- resume() しておくことで pipe() されているものが何もない時のデータは捨てる
- これを利用して、同様にタイムラインが流れるようにした
- ここまでは順調だったが、あるクライアントが応答しなくなると、他のクライアントへの送信が止まる問題が発覚
- クライアントがデータを受け取らなくなり Socket の Stream のバッファがいっぱいになると、上流の Stream が pause() されるらしい
- pause() されそうになったら、下流を unpipe() することで解決
- その他
- Stream を加工したり、フィルタするツール
- 連合以外の、ローカルタイムラインやホームタイムラインも指定
- access_token の取得
- たまに WebSocket が切れるので再接続
- 簡単なコマンドラインインターフェース
$ telnet tootcat.0j0.jp
$ nc tootcat.0j0.jp 7007
-
- friends.nico の連合タイムラインから friends.nico のみを抽出(≒ローカルタイムライン)
$ telnet tootcat.0j0.jp 7008
-
- friends.nico の連合タイムラインから mstdn.jp のみを抽出
$ telnet tootcat.0j0.jp 7009
-
- friends.nico の連合タイムラインから pawoo.net のみを抽出
$ telnet tootcat.0j0.jp 7010
$ nc tootcat.0j0.jp 7006 | head -c 1000
{"id":6021698,"created_at":"2017-05-06T07:18:52.994Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","application":{"name":"Web","website":null},"account":{"id":0,"username":"username","acct":"acct","display_name":"display_name","locked":false,"created_at":"2017-04-21T12:07:59.324Z","followers_count":0,"following_count":0,"statuses_count":1,"note":"note","url":"https://friends.nico/url","avatar":"avatar","avatar_static":"avatar_static","header":"header","header_static":"header_static","nico_url":"nico_url"},"media_attachments":[],"mentions":[],"tags":[],"uri":"tag:friends.nico,2017-05-06:objectId=6021698:objectType=Status","content":"content1\r\ncontent2","url":"url","reblogs_count":0,"favourites_count":0,"reblog":null}{...
$ nc tootcat.0j0.jp 7006 | docker run -i --rm -e "TZ=Asia/Tokyo" asannou/jq --unbuffered -r '.prefix = (.created_at | sub(".[0-9]*Z";"Z") | fromdate | strflocaltime("%H:%M")) | .prefix += " (" + .account.username + ") " | .prefix as $prefix | select(.uri | test("^tag:friends.nico,")) | .content | split("\r\n") | join("\r\n" + $prefix) | $prefix + .'
16:18 (username) content1
16:18 (username) content2
-
- IRC 風
- strflocaltime がまだ development version のみの機能だったので asannou/jq を作った
- --unbuffered で滞りなく出力される