株式会社ライブキャストロゴ 株式会社ライブキャスト

TranslatAIRのTweet機能をXAuth対応する(準備編)の続きです。

ActionScriptのOAuthライブラリや、twitterのAPIライブラリを使おうかとも思ったのですが、コールするAPIは以下の2つだけなので、今回は自前で作ることにしました。

  • https://api.twitter.com/oauth/access_token(アクセストークンを取得する)
  • http://twitter.com/statuses/update.json(ステータスを更新する)

※ 後者は前から使ってる翻訳結果をtweetするAPIです。

ライブラリのソースコードはかなり参考にさせていただきました。
q-oauth – Project Hosting on Google Code
oauth-as3 – Project Hosting on Google Code
などです。

特にq-outhというライブラリの方には、それをコールするサンプルアプリのソースコードも入っていましたので、非常に参考になりました。

パラメータで必要になってくる署名を生成するところには、ライブラリを利用しています。
as3crypto – Project Hosting on Google Codeというライブラリです。

まずは、アクセストークンを取得します。

●HTTPリクエスト

  • POST

●URL

  • https://api.twitter.com/oauth/access_token

●パラメータ

  • oauth_consumer_key:twitter登録時に発行されるコンシューマキー
  • oauth_nonce:ユニークID
  • oauth_signature_method:HMAC-SHA1固定
  • oauth_timestamp:現在時刻のタイムスタンプ
  • oauth_version:1.0固定
  • x_auth_mode:client_auth固定
  • x_auth_username:twitterアカウント
  • x_auth_password:twitterパスワード
  • oauth_signature:署名

※ 署名を生成する部分は後述します。

●応答結果

  • oauth_token:アクセストークン
  • oauth_token_secret:署名キー
  • user_id:twitter側で管理しているユーザID(だと思います)
  • screen_name:表示名
  • x_auth_expires:トークンの有効期限

※ このうち上2つを使います。
※ 有効期限は0が返ってくるので無期限になります。

やりかたはいろいろあると思いますが、私はまず、XAuthでのみ使用する情報をまとめるモデルクラスを作りました。

ソースコードはこんな感じです。
※ 6、7行目のキーはアプリケーション登録時に発行されるものに読み替えてください。

package
{
	public class XAuthModel
	{
		private static var access_token_url:String = "https://api.twitter.com/oauth/access_token"
		private static var consumer_key:String = "Consumer key";
		private static var consumer_secret:String = "Consumer secret";
		private var _status:String;
		private var oauth_token:String;
		private var oauth_token_secret:String;
		private var oauth_signature:String;
		private var _authorized:Boolean;

		public function XAuthModel()
		{
			_status = "";
			oauth_token = "";
			oauth_token_secret = "";
			oauth_signature = "";
			_authorized = false;
		}

		public function get AccessTokenUrl():String {
			return access_token_url;
		}

		public function set Status(status:String):void {
			_status = status;
		}

		public function get Status():String {
			return _status;
		}

		public function set OAuthSignature(signature:String):void {
			oauth_signature = signature;
		}

		public function get OAuthSignature():String {
			return oauth_signature;
		}

		public function set OAuthToken(token:String):void {
			oauth_token = token;
		}

		public function get OAuthToken():String {
			return oauth_token;
		}

		public function set OAuthTokenSecret(secret:String):void {
			oauth_token_secret = secret;
		}

		public function get OAuthTokenSecret():String {
			return oauth_token_secret;
		}

		public function get ConsumerKey():String {
			return consumer_key;
		}

		public function get ConsumerSecret():String {
			return consumer_secret;
		}

		public function set isAuthorized(authorized:Boolean):void {
			_authorized = authorized;
		}

		public function get isAuthorized():Boolean {
			return _authorized;
		}

	}
}

アクセストークンを取得する部分はこんな感じです(ちょっと長いですが)。
※ 49、50行目のusername、passwordは、適宜置き換えてください。

package
{
	import com.adobe.serialization.json.JSON;
	import com.hurlant.crypto.Crypto;
	import com.hurlant.crypto.hash.HMAC;
	import com.hurlant.util.Base64;
	import com.hurlant.util.Hex;

	import flash.net.URLVariables;
	import flash.utils.ByteArray;

	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.http.HTTPService;
	import mx.utils.UIDUtil;

	public class TwitterManager
	{
		private var _model:XAuthModel;

		public function TwitterManager()
		{
		}

		public function initTwitterManager(model:XAuthModel):void {
			_model = model;
		}

		public function Tweet(message:String):void {
			if (!_model.isAuthorized) {
				getAuthToken(message);
			}
			else {
				//setStatus(message);
			}
		}

		private function getAuthToken(status:String):void {
			_model.Status = status;

			var requestTokenService:HTTPService = new HTTPService();
			requestTokenService.method = "POST";
			requestTokenService.url = _model.AccessTokenUrl;
			requestTokenService.addEventListener(ResultEvent.RESULT, onAuthTokenResult);
			requestTokenService.addEventListener(FaultEvent.FAULT, onAuthTokenFault);

			var forms:URLVariables = new URLVariables();
			forms.x_auth_mode = "client_auth";
			forms.x_auth_password = "password";
			forms.x_auth_username = "username";
			forms.oauth_signature = getSignature(requestTokenService.method, requestTokenService.url, forms);

 			requestTokenService.request = forms;
			requestTokenService.send();
		}

		private function getSignature(method:String, url:String, forms:URLVariables):String {
			var now:Date = new Date();

			forms.oauth_consumer_key = _model.ConsumerKey;
			forms.oauth_nonce = UIDUtil.getUID(now);
			forms.oauth_signature_method = "HMAC-SHA1";
			forms.oauth_timestamp = now.time.toString().substring(0, 10);
			forms.oauth_version = "1.0";
			if (_model.OAuthToken.length) {
				forms.oauth_token = _model.OAuthToken;
			}

			var sortArr:Array = UrlVariableToArray(forms);
			var sigBase:String = URLEncoding.encode(method) + "&" +  URLEncoding.encode(url)  + "&" + URLEncoding.encode(sortArr.join("&"));
			var sigkeybase:String = URLEncoding.encode(_model.ConsumerSecret) + "&";
			if (_model.OAuthTokenSecret.length){
				sigkeybase += URLEncoding.encode(_model.OAuthTokenSecret);
			}

			var hmac:HMAC = Crypto.getHMAC("sha1");
			var sig_key:ByteArray = Hex.toArray(Hex.fromString(sigkeybase));

			var data:ByteArray = Hex.toArray(Hex.fromString(sigBase));
			var signature:String = Base64.encodeByteArray(hmac.compute(sig_key, data));

			return signature;
		}

		private function UrlVariableToArray(variables:URLVariables):Array{
			var arr:Array = new Array();
			for(var key:String in variables){
				arr.push(key + "=" + URLEncoding.encode(variables[key]));
			}
			arr.sort();
			return arr;
		}

		private function onAuthTokenResult(event:ResultEvent):void {
			var results:Array  = event.result.toString().split("&");

			for each (var token:String in results) {
				if (_model.OAuthToken == "" || _model.OAuthTokenSecret == "") {
					var values:Array = token.split("=");

					if (values.length == 2) {
						if (values[0] == "oauth_token") {
							_model.OAuthToken = values[1];
						}
						else if (values[0] == "oauth_token_secret") {
							_model.OAuthTokenSecret = values[1];
						}
					}
				}
				else {
					break;
				}
			}

			_model.isAuthorized = true;
		}

		private function onAuthTokenFault(event:FaultEvent):void {
			trace("投稿失敗...n" + event.fault.toString());
		}
	}
}

57~83行目のgetSignature関数は署名を生成する処理です。
SHA1方式のハッシュ値を署名として、パラメータに付加します。

●対象となるテキスト

  • HTTPリクエスト:POST
  • URL:コールするAPIのURL
  • パラメータ:POSTするパラメータのキーと値を「&」ですべて繋げたもの

これらをURLエンコードして、さらに「&」で繋げます。

●署名キー

  • consumer_secret
但し、ActionScriptの関数、encodeURIComponentではエンコードできない文字があるようです(「!」「*」とか)。
Extended UTF-8 in OAuth ActionScript library
こちらのソースコードを、ものすごく参考にさせていただきました!

Twitter API Wiki / Twitter REST API Method: oauth access_token for xAuthには、署名の対象となるテキストの一例があります。こんな感じになります。

Example Signature Base String:

POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Faccess_token&oauth_consumer_key%3Dri8JxYK2ZdwSV5xIUfNNvQ%26oauth_nonce%3DqfQ4ux5qRH9GaH8tVwDCwInLy6z8snR6wiq8lKcD6s%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1267817662%26oauth_version%3D1.0%26x_auth_mode%3Dclient_auth%26x_auth_password%3Dxyz12242134%26x_auth_username%3Depisod

翻訳結果をtweetするところも基本的には同じですが、不要なパラメータと追加するパラメータ、追加する署名キーがあります。

●不要なパラメータ

  • x_auth_mode
  • x_auth_username
  • x_auth_password

※ アクセストークンが取得できているので、これらを再度送る必要はありません。

●追加するパラメータ

  • oauth_token

●追加する署名キー

  • oauth_token_secret

※ このキーを追加するのはgetSignature関数に実装されています。

tweetするところは、こんな感じです。

		private function setStatus(status:String):void {
			var updateService:HTTPService = new HTTPService;
			updateService.method = "POST";
			updateService.url = "http://twitter.com/statuses/update.json";
			updateService.resultFormat = "text";
			updateService.addEventListener(ResultEvent.RESULT, onResult);
			updateService.addEventListener(FaultEvent.FAULT, onFault);

			var forms:URLVariables = new URLVariables();
			forms.status = status;
			forms.oauth_signature = getSignature(updateService.method, updateService.url, forms);
			updateService.request = forms;
			updateService.send();
		}

		private function onResult(event:ResultEvent):void {
			var json:Object = JSON.decode(event.result.toString());
			trace(json.text);
		}

		private function onFault(event:FaultEvent):void {
			trace("投稿失敗...n" + event.fault.toString());
		}

このソースコードを上のTwitterManagerクラスに付け足し、setStatus関数をコールするコードを、onAuthTokenResult(コールバック)関数の最後に付け足せば、XAuthで認証した流れで、翻訳結果をtweetすることが出来るようになります。

ということで、XAuth対応したTranslatAIR、是非使ってみてください!

サンプルソース