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");
}

*1:2.xも3.xも同じくダメだった。@masami256さんによるとKVMは問題ないとのこと。