C/C++でTwitterのXAuth認証を行う

前置き

なんか急にTwitterに書き込みを行うプログラムを書きたくなったので、どんな方法があるか調査してみました。

Twitterの読み込み動作に関しては特定ユーザのフィード情報を取得すれば良いだけなので簡単に実現できますが、書き込み動作になるとユーザ認証が必要になるのでチョット複雑になります。認証方法は大きくわけて2種類(OAuthとXAuth)あるみたいです(昔はAPIを実行する度に認証を行うベーシック認証ってのがあったらしいです)。
OAuth認証、XAuth認証のといった認証関連の仕組み、違いについてはここで説明しないので他のページを参照してください。軽く説明するとOAuthはTwitter/ユーザ/Webサービスの3者間で使われる認証で主にWebサービス上で利用される方式、XAuthはTwitter/ユーザの2者間で使われる認証でブラウザ操作を使わないスタンドアローンプログラムで利用される方式です。どちらもユーザ情報とパスワード情報に関連する情報を毎回通信しなくて済むようなフローになっています。(セキュアなHTTPクッキーをイメージしました)

さらに調査した結果、Twitterは色々な言語でライブラリ化されているらしく、単純な書き込み動作くらいだったら結構簡単に実装できそうだということが判りました。しかしWebサービスの開発言語として良く使われるPerlPHPのサンプルコードは見つかるものの、目的だったC/C++で実装する方法はあまり無かったので、今回はC/C++CUIプログラムで動作するように実装してみようと思います。前述のとおりスタンドアロンプログラムとして実装するのでXAuthを利用して認証させます。Twitter-devを見てみると、C/C++向けのOAuth用ライブラリとしてliboauthというものが推奨されてました。これはTwitterに限定されたものではなく、OAuthで認証を行うサービス全般で使えるライブラリみたいです。もちろんXAuthのライブラリとしても利用できます。

C/C++で実装するメリットとして、既存のC/C++で実装されたプログラム(ライブラリ)とリンクしやすい、という事があるとおもいます。それ以外の目的の場合、例えばWebサービスとして実装する場合はPHPPerlRubyなどの言語で実装するべきですし、たとえスタンドアロンプログラムでも、ゼロから実装する場合はC#やJavaなどTwitterのサポートが厚い言語で実装する方が良いとおもいます。

前置きはこのくらいにして、C/C++でXAuthの仕組みに沿ってaccess-keyの取得(読み込み保存機構付き)、access-keyを使ってTwitterに書き込んでみるというプログラムを書いてみようと思います。

ソースコード

#include 
#include 
#include 
#include 
extern "C" {
#include 
}

// アプリケーションで使用するXAuthアクセス可能なコンシュマーキーを指定してください
#define TWITTER_CONSUMER_KEY     ""
#define TWITTER_CONSUMER_SECRET  ""

int parse_reply(const char *reply, char **token, char **secret) {
	int rc;
	int ok = 1;
	char **rv = NULL;
	rc = oauth_split_url_parameters(reply, &rv);
	qsort(rv, rc, sizeof(char *), oauth_cmpstringp);
	int oauth_cnt = 0;
	for (int i = 0; i < rc; i++) {
		if (strncmp(rv[i], "oauth_token_secret=", 18) == 0) {
			if (secret) {
				*secret = strdup(&(rv[i][19]));
				oauth_cnt++;
			}
		} else if (strncmp(rv[i], "oauth_token=", 11) == 0) {
			if (token) {
				*token = strdup(&(rv[i][12]));
				oauth_cnt++;
			}
		}
	}
	if (rv) {
		free(rv);
	}
	if (oauth_cnt >= 2) {
		ok = 0;
	} else {
		ok = 1;
		free(*token);
		*token = NULL;
		free(*secret);
		*secret = NULL;
	}
	return ok;
}

int twitter_xauth_getaccesskey(char **token, char **secret,
		const char *username, const char *password) {
	const char *access_token_uri = "https://api.twitter.com/oauth/access_token";

	char *req_url = NULL;
	char *postarg = NULL;
	char *reply = NULL;

	int argc = 4;
	char argv_wark[512];
	char **argv = (char**) malloc(sizeof(char*) * 4);
	argv[0] = strdup(access_token_uri);
	argv[1] = strdup("x_auth_mode=client_auth");
	snprintf(argv_wark, sizeof(argv_wark), "x_auth_password=%s", password);
	argv[2] = strdup(argv_wark);
	snprintf(argv_wark, sizeof(argv_wark), "x_auth_username=%s", username);
	argv[3] = strdup(argv_wark);
	req_url = oauth_sign_array2(&argc, &argv, &postarg, OA_HMAC, NULL,
			TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, NULL, NULL);
	free(argv[3]);
	free(argv[2]);
	free(argv[1]);
	free(argv[0]);
	free(argv);
	reply = oauth_http_post(req_url, postarg);

	if (req_url)
		free(req_url);
	if (postarg)
		free(postarg);
	if (!reply)
		return 3;
	if (parse_reply(reply, token, secret))
		return 4;
	free(reply);

	return 0;
}

int twitter_status_write(const char *token, const char *secret, const char *str) {
	const char *test_call_uri = "http://api.twitter.com/1/statuses/update.json";

	char *req_url = NULL;
	char *postarg = NULL;
	char *reply = NULL;

	int argc = 2;
	char argv_wark[512];
	char **argv = (char**) malloc(sizeof(char*) * 2);
	argv[0] = strdup(test_call_uri);
	snprintf(argv_wark, sizeof(argv_wark), "status=%s", str);
	argv[1] = strdup(argv_wark);
	req_url = oauth_sign_array2(&argc, &argv, &postarg, OA_HMAC, NULL,
			TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, token, secret);
	free(argv[1]);
	free(argv[0]);
	free(argv);
	reply = oauth_http_post(req_url, postarg);

	printf("req_url:'%s'\n", req_url);
	printf("postarg:'%s'\n", postarg);
	printf("reply:'%s'\n", reply);
	if (req_url)
		free(req_url);
	if (postarg)
		free(postarg);
	if (reply)
		free(reply);

	return (0);
}

int twitter_load_key(const char *file, char **token, char **secret) {
	char work[256];
	int i;
	FILE *fp = fopen(file, "r");
	if (fp == NULL)
		return -1;

	fgets(work, sizeof(work), fp);
	for(i=0; i

動作に必要なもの

プログラム内容の説明に関しては割愛します。このプログラムを動作させるにはTwitter-devでプログラムを登録させアプリケーションキーを取得する必要があります。アプリ登録後に取得したCONSUMER_KEYとTWITTER_CONSUMER_SECRETを上記ソースコードの定数として設定させます。OAuthを使うだけならこの時点で動作させられるようになりますが、XAuthを使う場合は、さらにメール(api@twitter.com)にてXAuthの実行許可をもらう必要があります。XAuth権限をもらっていない状態だとaccess-key取得のために実行するPOSTメソッドに失敗します。テンプレを参考にメールを出し、XAuth権限を付与された段階で上記プログラムが動作するようになります。(大体1〜2日で権限を付与されます)