timeline: Twitter reader
ターミナルで動くTwitterクライアントというかリーダーを書いてみた。データの取得形式はJSONになっているので、Plan9portのsmugfsで使われているJSONパーザを使わせてもらった。クライアントで表示される部分はわずかだけど、結構なデータ量がやりとりされているのね。tweetごとにユーザプロファイルとか。
本当はHTTP通信部分も自分で書こうと思ったのだが(webfsもよいかもしれない)、hgetを呼び出すだけになっている。
なんだかVMWare Fusion上だとsleep(2)が正確に動いてないか*1。タイマ割込みの仮想化がうまくいってないのかな。3倍以上の速度で進んであっという間にAPIを使い果たしてしまった。。。
とりあえずソースはgithubにさらしておく。
(追記)IRCクライアントが存在するので、IRC経由でTwitterを使うというのもありかもしれない。
(追記:2010-01-29)webfs経由に書き換えた。初版ではsystem(3)モドキの関数を作ってhgetを実行していた。
int fetch(char *file) { int pid, p[2], f; int nr, nw; char buf[1024]; char cmd[64]; if(lastid) sprint(cmd, "hget '%s?since_id=%lld'", HOME_TIMELINE_URL, lastid + 1); else sprint(cmd, "hget %s", HOME_TIMELINE_URL); if(pipe(p) < 0) sysfatal("pipe: %r"); switch(pid = fork()){ case -1: sysfatal("fork: %r"); case 0: close(p[0]); dup(p[1], 1); execl("/bin/rc", "rc", "-c", cmd, nil); default: close(p[1]); sprint(file, "/tmp/tweet.%d", pid); f = create(file, OWRITE, 0666); if(f < 0) sysfatal("open: %r"); while((nr = read(p[0], buf, sizeof(buf))) > 0){ nw = write(f, buf, nr); if(nw < 0) sysfatal("write: %r"); } if(nr < 0) sysfatal("read: %r"); close(f); close(p[0]); } return 0; }
で、webfs版はこんな感じになる。cloneファイルをopenしてctlファイルに"url http://..."を書き込むと、n/bodyをreadすることでページが取得できる。ただしBASIC認証時にhgetのようにパスワードを入力するインタフェースを作っていないので、あらかじめfactotumにパスワードを登録する必要がある。
int fetch(char *file) { int ctlfd, conn, fd, newfd; int nr, nw; char buf[1024]; char url[128]; if(lastid) sprint(url, "%s?since_id=%lld", HOME_TIMELINE_URL, lastid + 1); else sprint(url, "%s", HOME_TIMELINE_URL); ctlfd = open("/mnt/web/clone", ORDWR); if(ctlfd < 0) sysfatal("open /mnt/web/clone: %r"); nr = read(ctlfd, buf, sizeof(buf)); if(nr < 0) sysfatal("read: %r"); if(nr == 0) sysfatal("read clone failed"); buf[nr] = 0; conn = atoi(buf); if(fprint(ctlfd, "url %s", url) <= 0) sysfatal("write ctl failed 'url %s': %r", url); snprint(buf, sizeof(buf), "/mnt/web/%d/body", conn); fd = open(buf, OREAD); if(fd < 0) sysfatal("open %s: %r", buf); sprint(file, "/tmp/tweet.%d", conn); newfd = create(file, OWRITE, 0666); if(newfd < 0) sysfatal("create %s: %r", file); while((nr = read(fd, buf, sizeof(buf))) > 0){ nw = write(newfd, buf, nr); if(nw < 0) sysfatal("write: %r"); } if(nr < 0) sysfatal("read: %r"); close(newfd); close(fd); return 0; }
で、hgetのコードを見ながら、パスワード追加のコードを追加。たぶんこれでOK。auth_getuserpasswd関数はユーザ/パスワードの組を取得するものだが、第一引数にauth_getkey関数を指定すると、ユーザにパスワード問い合わせるようになる。
#include <auth.h> #define TWITTER_SERVER "api.twitter.com" #define TWITTER_REALM "Twitter API" void initauth() { UserPasswd *up; up = auth_getuserpasswd(auth_getkey, "proto=pass service=http server=%q realm=%q", TWITTER_SERVER, TWITTER_REALM); if(up == nil) sysfatal("auth_getuserpasswd: %r"); }