Mastodon のタイムラインをターミナルに流す

  • 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 か nc でタイムラインを受信する
    • friends.nico の連合タイムライン
$ 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
  • ひとつの連合タイムラインを分岐しているので、サーバにブラウザ以上の負荷はかけない
  • 連合タイムラインは all public posts from all users "known" to your instance であるため、インスタンスごとにフィルターしてもローカルタイムラインと等しくならない
    • friends.nico のユーザにフォローされたユーザ、ブーストされたトゥートだけが現れる
  • テキストデータなので MVNO の速度制限 (200kbps) がかかった状態でも、今のところストレスがない
  • telnet が動くデバイスであれば利用可能

  • 未加工の JSON を受信できるようにした
$ 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}{...
  • jq などで、フィルタや加工できる
$ 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 で滞りなく出力される