ケータイがあること前提の社会と
クレジットカードがあること前提の社会だけは許さない
既に2ちゃんからは失われ、Twitter へ移ったと言って過言ではあるまい. 法的にはアウトであるが、テレビ映像のキャプチャ画像はTwitter で流れることが多く、 実際、キャプチャ画像を貼りながら実況するようなアカウントというのがいくつかあって (e.g. @youtonjoe, @Story_terror) そのようなアカウントに対する需要はあるものと見受けられる. 実際に実況してる時にはテレビ映像を見てるのだから、そんなキャプチャ画像は必要ないようにも思えるが、 ちょうど良いシーンのキャプチャに成功すれば、そのシーンについて言及するのに、その画像が役に立つ.
ちょうど先週、 ぱちおさん (@patioglass) が作者である pretty-watch.net というものを知り (だって本人が宣伝してたからね) 自分が欲しかったものが、良い感じのデザインでそこにあったので、大変に感心した.
特別に技術的にすごいところは無いが、2つめのアイデアは上手いと思った. 最近に得られた画像の1枚を、そこに表示し続けることで、 擬似的に動画を見てるような体験がある. (しかもそれはどこかのユーザーが勝手に投稿したものである.)
ただ画像が流れるだけの掲示板とも違った. 私は2ちゃんをソースにして含まれてる画像をただ流すだけのものを作って満足していたが、 固定観念に囚われていたことを恥じた.
確かに、画像を蒐集する目的でなく、実況を円滑にするための画像だと考えるならば、こちらのほうが良い.
8:45頃にもなるとtweetの流速に耐え切れずにFirefox が死ぬので実用に耐えられない. patioglass/precure_watch にソースがあるが、ちょっと私にはいじれない (複雑すぎませんかね). 作者に直接問い合わせると、調整中だそうで、それに熱心に開発してるようなので、あと数週間もすれば良い物になってると期待しています.
これだけアイデアのオリジナルについて言及すれば、堂々とパクっても良いだろう.
同じことを、もっと軽い環境で、実現する. シェルスクリプト (bash) で. 完成品は cympfh/precure-watch に置いてある.
twurl
で statuses/filter
API を叩くtwurl -t -d track=#precure -A 'Accept-Encoding: text' \
-H userstream.twitter.com \
/1.1/statuses/filter.json 2>/dev/null
-t
オプションでバッファに貯めずに出力させる. streaming 系統には必須. 1tweet が1行JSONとして出力される.
filter
の仕様なのか、APIの叩き方が悪いのか、偶にただ空の行が来ることがある. 後の処理で気にする必要があるかもしれない.
statuses/filter
から築次的に受け取るレスポンスを jq
でパースしていく.
レスポンスから欲しいのは次の3つ.
.user.screen_name
(@xxx).text
(ツイート本文).media[].media_url
(ツイートに含む画像のurl. これは複数ありえる)Bashでパイプで繋いで一行で書きたいので、次の書式のファイルをログとして延々吐き続けることにする:
T screen_name text
M media_url
その行の頭が T
ならその行はツイート本文を表現してて、半角空白区切りでアレとアレが並んでる. 頭が M
なら、それに続く文字列は画像のurlである.
例えば、
{}
{"text":"body","media":[{"media_url":"http://a.jpg"},{"media_url":"http://b.jpg"}]}
{"text":"body2"}
こんな標準入力を
T body
M http://a.jpg
M http://b.jpg
T body2
に変換したい.
N.B.
status/filter
のレスポンスはストリーム. 一行ずつリアリタイムにやってくるとして、一行ずつ表示するのが理想です. しかしながら実際に処理する場合にはある程度データが溜まってから処理が行われます. つまり勝手にバッファを取ります.grep
の場合、--line-buffered
オプションを附けることで、一行データが来たらバッファを解放してくれます.jq
はよくわからないです.jq
の後ろにパイプでgrep --line-buffered
をつなげるとどっかにバッファが大きくトラれます.
filter.sh
#!/bin/bash
Q="#precure"
twurl -t -d track=$Q -A 'Accept-Encoding: text' -H userstream.twitter.com \
/1.1/statuses/filter.json 2>/dev/null |
jq --unbuffered -r \
'(if has("text") then "T " + .text else "" end) + "\n" + (if has ("media") then (.media | map(.media_url) | map("M " + .) | join("\n") ) else "" end)'
もちろん jq
じゃなくて ruby -n
などでもいいが、バッファさせたらいけない. つまり、statuses/filter
からのレスポンスをがんがん画面に出力させたい. jq
の場合は --unbuffered
でそれが実現できる. ruby
の場合どうするかは調べたが不明.
filter.sh
: filter API からリアルタイム検索して、ツイート本文と画像を保存 /tmp/tw
server.sh
: 鯖です
/
: index.html
を返す/media
: /tmp/tw
から最近画像一枚の url を返す/tw
: /tmp/tw
から最近ツイート20件を引っ張ってきて返すindex.html
: クライアント
/media
を叩いて最新の画像一枚を表示/tw
を叩いて最新 20件のツイートを表示server.sh
Bash で鯖書くのは、一度要領を覚えてしまえば簡単. 調べるのが大変だったけれど、人は man
に立ち帰るのだった.
man nc
There is no -c or -e option in this netcat, but you still can execute a command after connection being
established by redirecting file descriptors. Be cautious here because opening a port and let anyone con‐
nected execute arbitrary command on your site is DANGEROUS. If you really need to do this, here is an
example:
On ‘server’ side:
$ rm -f /tmp/f; mkfifo /tmp/f
$ cat /tmp/f | /bin/sh -i 2>&1 | nc -l 127.0.0.1 1234 > /tmp/f
On ‘client’ side:
$ nc host.example.com 1234
$ (shell prompt from host.example.com)
例えば /media
とそれ以外とを識別するだけの鯖は次のように
#!/bin/bash
PORT=8080
rm -f /tmp/f; mkfifo /tmp/f
trap "rm -f /tmp/f; exit" INT
run() {
head -1 > /tmp/req
if ( grep "^GET /media" /tmp/req >/dev/null ); then
echo media
elif ( grep "^GET /tw" /tmp/req >/dev/null ); then
echo tw
else
echo "HTTP/1.1 200 OK"; echo
cat index.html
fi
}
echo http://127.0.0.1:$PORT/
while :; do
cat /tmp/f | run | nc -l 127.0.0.1 $PORT > /tmp/f
done
これで不取敢、鯖っぽいものの骨格ができた.
完成版はgithubに置いておくので.
run
はパイプで初め存在しない /tmp/f
を読んでるので、cat
などですべてを読もうとするとブロックしてしまう. head -1
みたいな必要最小限だけ読む.
すべからくすべし
<script>
var xml = new XMLHttpRequest();
setInterval(function () {
xml.open("GET", "media", false);
xml.send();
I.src = xml.responseText;
xml.open("GET", "tw", false);
xml.send();
U.innerHTML = xml.responseText.split("\n").map(line => `<li>${line}</li>`).join("");
}, 100);
</script>
みたいな感じ.
バックグラウンドで filter.sh
と server.sh
を実行してやるだけのスクリプト. running on http://127.0.0.1:8000
なんて言ってやれば気が利いてると思われる.
鯖なので停められるまで勝手に動き続ける. C-c
で終わるようにしたい. run.sh
じゃなくてバックのもちゃんと止まらないといけない.
次を run.sh
の最後に仕込んでおく. こうすることで自分に属する (つまりバックで実行した子供) プロセスにSIGINTを送れる.
trap "kill -TERM -$$" SIGINT
wait
bash filter.sh | tee /tmp/tw
したらバッファとるし
bash filter.sh >> /tmp/tw
でもそうだしもう全然ダメ!
と思ったら、そうだ (参考; unix - Force line-buffering of stdout when piping to tee - Stack Overflow)
stdbuf -o L filter.sh | tee /tmp/tw
だ!!!
#!/bin/sh
trap "kill -TERM -$$" SIGINT
: >/tmp/tw
bash server.sh &
stdbuf -o L bash filter.sh | tee /tmp/tw
# firefox http://127.0.0.1:8080/
wait
こんな感じです。
無駄に疲れたわ