スポンサーサイト

Tags :
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Twitterで『あの人』が自分をfollowしているかどうかを調べるAppleScript

ここんとこ仕事が立て込んでたので放置してしまいました。ちょっと軽めのネタを。

Twitterやってると突然、『follow整理します!』みたいな感じのPOSTが流れて来て、そんな時に自分のfollowerが減ってたりすると、『えっ?…オレ?』とか思ったりして、特に相手がお気に入りの人だったりするとちょっと気になったりするもんです。また、結構前にfollowしたけどfollow返されてたっけ?みたいに思ってもtwitterからの『followされました』メールをいちいちチェックするのも面倒です。

相手にremoveされたかを知るには、『りむったー』とか『Qwitter』等のサービスを利用すればremoveされた際に通知を受け取る事が出来ますが、そもそも一度もfollowされてない相手には関係がありません。

そこで、Twitter APIに用意されている『ユーザ間のfriend関係』を調べる機能を利用して『相手にfollowされてるかどうかを調べる』AppleScriptを書きました。この手のサービスやuser.jsも有りそうですが、探すヒマがあったら自分で書く、みたいな感じで。あと、高機能なTwitterクライアント使ってる人には必要無いかも知れません。

スクリプト中の『あなたのユーザ名』『パスワード』の部分を書き換えて実行し、気になる『あの人』のユーザ名を入力するとfollowされてるかどうかを表示します。





■動作確認
MacOSX 10.5.8
property user : "あなたのユーザ名"
property pass : "パスワード"

tell application "Finder"
	set friend to text returned of (display dialog "『あの人』のユーザ名を入力してください。" default answer "") as string
	try
		--set scpt to "curl --user \"" & user & ":" & pass & "\" -L \"http://twitter.com/friendships/exists.json?user_a=" & friend & "&user_b=" & user & "\""
		set scpt to "curl -L \"http://api.twitter.com/1/friendships/exists.json?user_a=" & friend & "&user_b=" & user & "\""
		set ret to do shell script scpt
	on error
		my myError()
	end try
	
	set mes to "『" & friend & "』さんは、あなたをfollowして"
	if ret is "true" then
		display dialog mes & "います。" buttons "OK" default button 1
	else if ret is "false" then
		display dialog mes & "いません。" buttons "OK" default button 1 with icon 2
	else
		my myError()
	end if
end tell

on myError()
	activate me
	display dialog "ユーザ名を正しく入力してください。" buttons "OK" default button 1
	error number -128
end myError
スポンサーサイト

InDesignでTwitterのAPIにアクセスし、タイムラインを取得してドキュメントに自動レイアウトしてみる

JavaScriptを使ってInDesignでTwitter APIにアクセスし、タイムラインのステータスを取得して、followしている人たちの発言をInDesignのドキュメントに自動でレイアウトしてみます。

TwitterのAPIではfollowしている人たちの発言の他にも、自分の発言のみ、@Repliesなんかを取得出来ますが、今回は『http://twitter.com/home』で表示される『自分がfollowしている人たちの発言』を取り出して、それをアイコン付きで生成したドキュメントにレイアウトして行く様にしてみました。

■参考URL 『Twitter API 仕様書 日本語訳』
http://watcher.moe-nifty.com/memo/docs/twitterAPI.txt

Socketを使ったWebへのアクセスには、以前のエントリで書いた、Basic認証とファイルのダウンロードが出来るスクリプトをそのまま使っています。Basic認証には、『ユーザID:パスワード』の文字列をBase64エンコードして渡す必要がありますが、今回はその部分は省略してあらかじめエンコードした文字列を用意しています。

MacOSXの場合はターミナルで、
php -r 'echo base64_encode("ユーザID:パスワード")."\n";'
とすればBase64エンコードされた文字列を生成出来ます。

スクリプトの実行結果はこんな感じになります(今回は単ページ)。ドキュメントは一から生成するので雛形は必要ありません。配置するアイコン等の画像は全てローカル(デフォルトではデスクトップ)にダウンロードして保存されます。

旬のDTP Boosterさんfollowさせてもらってます。

Webブラウザで見るのと似た様なレイアウトってのも芸が無い気がしますけど。テキストも最低限オーバーフローしないための処理しかしてないので組版なんて呼べる代物ではありませんが…

一応ドキュメントサイズの変更にはある程度追従出来る様な書き方にはなってますが、本当はレイアウトを自動生成する部分をもうちょっとキレイに、と言うか、CSS的な文脈で書ければ良いなと思ったりもしたんですけど今回は全然煮詰められませんでした(相変わらず思い付くままに書き殴ったようなコードです)。何かテンプレートエンジン的な物が用意出来れば良いんでしょうかね。

■動作確認
MacOSX 10.5.6
InDesign CS4
#target InDesign

//HTTPレスポンスを得る
function getHttpResponse(requests) {
	var parseUrl = function(url) {
		var urlObj = {};
		//[url, scheme, slash, host, port, path, query, fragment] via O'REILLY JavaScript: The Good Parts
		var url_re = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
		var m = url_re.exec(url);
		urlObj.host = m[3];
		urlObj.port = m[4] || '80';
		urlObj.path = m[5];
		urlObj.query = (m[6]) ? '?' +  m[6] : '';
		urlObj.frag = (m[7]) ? '#' + m[7] : '';
		return urlObj;
	}
	var urlObj = parseUrl(requests.url);
	var encoding = requests.encoding || 'UTF-8';
	var method = requests.method || 'GET';
	var auth = (requests.basic_auth != undefined) ? 'Authorization: Basic ' + requests.basic_auth + '\r\n' : '';
	var conn = new Socket;
	conn.timeout = 10;
	if (conn.open (urlObj.host + ':' + urlObj.port, encoding)) {
  	conn.write (method + ' /' + urlObj.path + urlObj.query + urlObj.frag + ' HTTP/1.0\r\n'
		+ 'Host: ' + urlObj.host + '\r\n'
		+ 'User-Agent: ' + 'InDesign/6.0' + '(Macintosh; U; Intel Mac OS X 10_5_6; ja-jp)' + '\r\n'
		+ auth
		+ '\r\n');
		var reply = conn.read(999999);
		conn.close();
		return reply; //ヘッダ込みで返す
	} else {
		return conn.error;
	}
}


//Webからファイルをダウンロードする
function downloadFile(url, localFile) {
	var rep = getHttpResponse({
		method: 'GET',
		url: url,
		encoding: 'BINARY',
	});
	if (rep.match(/HTTP.*\d{3}/).toString().indexOf('200') != -1) {
		//レスポンスのヘッダを除去 
		var body = rep.slice(rep.indexOf('\r\n\r\n') + 4);
		//ファイルに書き出し
		var f = new File(localFile);
		if (f.open("w")) {
			f.encoding = 'BINARY';
			f.write(body);
		}
		f.close();
		return f;
	} else {
		return false;	
	}
}


//RGBカラーを作成
function setRgb(cv) {
	return app.activeDocument.colors.add({
		model: ColorModel.process,
		space: ColorSpace.rgb,
		colorValue: cv
	});
}

TextFrame.prototype = { 
	getHeight: function() { return this.geometricBounds[2] - this.geometricBounds[0]; },
	getWidth: function() { return this.geometricBounds[3] - this.geometricBounds[1]; },
}


//初期設定
var id = 'your_twitter_account';
var b64_userpass = 'ZHVtbXk6ZHVtbXk='; //'username:password'をbase64エンコードした文字列(値はダミー)
var downloadPath = '~/Desktop/'; //ダウンロードしたファイルの保存場所
var cnt = 15; //1ページにレイアウトするPOSTの数
var doc = app.documents.add();
doc.documentPreferences.pageWidth = '110mm';
doc.documentPreferences.pageHeight = '150mm';
var fontName1 = app.fonts.item('小塚ゴシック Pro');
var tu = 'pt';
var default_txtSize = 7; //標準の文字サイズ
var default_txtLead = 9; //標準の行送り	
var container_margin = {
	top: 10,
	left: 7,
	right: 7,
	bottom: 3
};
var my_colors = {
	'white': setRgb([255, 255, 255]),
	'background': setRgb([148, 228, 232]),
	'profile': setRgb([218, 255, 130]),
	'link': setRgb([0, 0, 255])
};

var postRects = [];
var textRects = [];
var iconRects = [];
var iconImgs = [];

var p = doc.pages[0];
var p_b = p.bounds;

var logo = {
	url: 'http://assets0.twitter.com/images/twitter_logo_header.png',
	localFile: downloadPath + 'twitter_logo_header.png'
};

//APIでプロファイルを取得
var profile_info = getHttpResponse({
	url: 'http://twitter.com/users/show/' + id + '.json'
});

var profile_json = eval('(' + profile_info.match(/\{.*\}?/) + ')');
var profile_icon = {
	url: profile_json.profile_image_url,
	localFile: downloadPath + profile_json.profile_image_url.replace(/^.*\//, '')
}

//twitter_logo_header.png, profile_iconをダウンロード
var logo_file = downloadFile(logo.url, logo.localFile);
var profile_icon_file = downloadFile(profile_icon.url, profile_icon.localFile);

//APIでtimelineを取得
var timeline = getHttpResponse({
	url: 'http://twitter.com//statuses/friends_timeline.json?page=1',
	basic_auth: b64_userpass
});
timeline.replace('Connection', '');
tlObj = eval('(' + timeline.match(/\[\{.*\}\]?/) + ')');

//背景の矩形
var bgRect = p.textFrames.add({
	geometricBounds: [p_b[0] - 3, p_b[1] - 3, p_b[2] + 3, p_b[3] + 3],
	fillColor: my_colors.background
});
//ヘッダ部分
var logoRect = p.textFrames.add({
	geometricBounds: [p_b[0] + 2, p_b[1] + container_margin.left, p_b[0] + container_margin.top - 1, p_b[1] + 40],
	fillColor: my_colors.background,
	strokeWeight: 0
});
logoRect.place(logo_file);
logoRect.fit(FitOptions.proportionally);
var profile_rect = p.textFrames.add({
	geometricBounds: [p_b[0] + 2, (p_b[3] - p_b[1]) / 2, p_b[0] + container_margin.top - 1, p_b[3] - container_margin.right],
	fillColor: my_colors.profile,
	strokeWeight: 0
});
var pr_gb = profile_rect.geometricBounds;
var profile_icon_rect = p.textFrames.add({
	geometricBounds: [pr_gb[0], pr_gb[1], pr_gb[0] + profile_rect.getHeight(), pr_gb[1] + profile_rect.getHeight()],
	fillColor: my_colors.background,
	strokeWeight: 0
});
profile_icon_rect.place(profile_icon_file);
profile_icon_rect.fit(FitOptions.proportionally);
var title_rect = p.textFrames.add({
	geometricBounds: [pr_gb[0] + 2, profile_icon_rect.geometricBounds[3], pr_gb[2], pr_gb[3]],
	strokeWeight: 0,
	contents: id + '\'s timeline'
});
title_rect.paragraphs[0].properties = {
	pointSize: 9 + tu,
	appliedFont: fontName1,
	fontStyle: 'R',
	justification: 1667591796
};
//全てのPOSTを納める矩形
var container = p.textFrames.add({
	geometricBounds: [p_b[0] + container_margin.top, p_b[1] + container_margin.left, p_b[2] - container_margin.bottom, p_b[3] - container_margin.right],
	fillColor: my_colors.white
});
var container_gb = container.geometricBounds;
var container_h = container_gb[2] - container_gb[0];
var post_h = container_h / cnt; //各POSTの矩形の高さ


//各POSTをレイアウトして行く
for (var i=0; i<cnt; i++) {
	txtSize = default_txtSize;
	txtLead = default_txtLead;
	//JSONオブジェクトから情報を取り出す
	var tl_text = tlObj[i].text.replace(/</g, '<').replace(/>/g, '>').replace(/\r\n/g, ' ').replace(/\r/g, ' ').replace(/\n/g, ' ');
	var tl_name = tlObj[i].user.name;
	//各POSTの矩形
	var pr = postRects[i] = p.textFrames.add({
		geometricBounds: [container_gb[0] + post_h * i, container_gb[1] + 2, container_gb[0] + post_h * i + post_h, container_gb[3] - 2],
		fillColor: my_colors.white
	});
	var pr_gb = pr.geometricBounds;
	//textを納めるフレーム
	var tr = textRects[i] = p.textFrames.add();
	tr.properties = {
		geometricBounds: [pr_gb[0] + 1, pr_gb[1] + post_h - 1, pr_gb[0] + post_h - 1, pr_gb[3]],
		fillColor: my_colors.white,
		contents: tl_name + ' ' + tl_text,
	};
	tr.paragraphs[0].properties = {
		pointSize: txtSize + tu,
		leading: txtLead + tu,
		appliedFont: fontName1,
		fontStyle: 'R',
		kerningMethod: 'メトリクス'
	};
	//name部分のスタイル
	for (var j=0; j<tl_name.length; j++) {
		tr.paragraphs[0].characters[j].properties = {
			fillColor: my_colors.link,
			fontStyle: 'H',
			pointSize: '7.5' + tu
		};
	}
	//テキストがオーバーフローしている場合の処理
	tr_para = tr.paragraphs[0];
	while (tr.overflows && tr_para.horizontalScale > 90) {
		tr_para.horizontalScale--;
	}
	if (tr.overflows) {
		tr_para.horizontalScale = 100;
		tr_para.properties = {
			pointSize:'6' + tu,
			leading: '7' + tu
		};
		while (tr.overflows) {
			tr_para.horizontalScale--;
		}
	}
	//アイコンの情報を格納()
	iconImgs[i] = {
		url: encodeURI(tlObj[i].user.profile_image_url),
		localFile: downloadPath + tlObj[i].user.profile_image_url.replace(/^.*\//, '')
	};
	if (!new File(iconImgs[i].localFile).exists) {
		downloadFile(iconImgs[i].url, iconImgs[i].localFile); //アイコンの画像ををダウンロード
	}
	//アイコン用の矩形
	iconRects[i] = p.textFrames.add({
		geometricBounds: [pr_gb[0] + 1, pr_gb[1], pr_gb[0] + post_h - 1, pr_gb[1] + post_h - 2],
	});
	iconRects[i].place(new File(iconImgs[i].localFile)); //アイコンの画像を配置
	iconRects[i].fit(FitOptions.proportionally);
}

InDesignドキュメントのテキストをGoogleの翻訳APIを使って翻訳・置換してみる

『Google AJAX Language API』
http://code.google.com/apis/ajaxlanguage/documentation/
これと、InDesignのSocketオブジェクト使ってドキュメントのテキストを翻訳してみます。

■参考URL
http://d.hatena.ne.jp/C_L/20081012/indesign_socket_http

例えば、InDesignのドキュメントにこんな感じのテキストを用意して、


下にあるJavaScriptを実行すると、

こんな感じで翻訳されたテキストに置換します。

レスポンスは、ヘッダ部分を除くと
{"responseData": {"translatedText":"Ghost in the Shell"}, "responseDetails": null, "responseStatus": 200}
の様なJSONで返って来るので簡単に翻訳結果を取り出せます。一部の記号類は『&#39;』等の文字参照で返ってくるみたいです。
あと、今回のやり方だとテキストフレームの文字数が220文字前後(InDesignの情報パレットで)を境に400 Bad Requestが返って来るようになりました。

何らかの業務で使えるかどうかは非常に怪しいですが、単語レベルなら意外な使い道があるかも知れません。

で、これやってて思ったんですが、InDesignでSocketが使えるって事は、将来InDesignのドキュメントをインターネット越しに何らかの処理をするサービスなんかもあり得るわけですが、例えば第三者から渡されたドキュメントを処理する時、処理結果が悪意のあるスクリプトになるように仕組まれていた場合(今回試した翻訳の場合だと、翻訳結果がスクリプトとして意味のある文字列になる等。これだと環境設定の「添付スクリプトを有効にする」オプションは関係無いので)、取り出した値をeval()関数で評価するような書き方をしていてチェックが甘いとセキュリティの穴にもなり得る訳で、特にInDesignのスクリプトを書いたりするのは自分の様にWeb屋でない人も多いと思うので、その辺気を付ける必要も出てくるのかなぁと思った次第です。

まあこのあたりはインターネットかローカルかに関わらないですが、Webブラウザで実行されるJavaScriptと違って、ファイルシステムにもアクセス出来る訳だし、『inddスクリプトインジェクション』なんて攻撃法なんかが出て来たり…なんてのは夢が広がり過ぎですかね。

#target InDesign 

const HOST = 'ajax.googleapis.com';
const PATH = '/ajax/services/language/translate';
	
//google翻訳APIでテキストを翻訳
function getTranslation(str) {
	var src = 'ja';
	var dest = 'en';
	var query = '?v=1.0&q=' + encodeURI(str) + '&langpair=' + src + '%7C' + dest;
	var rep = "";
	var conn = new Socket;
	if (conn.open(HOST + ':80', 'UTF-8')) {
		conn.write ('GET ' + PATH + query + " HTTP/1.0\n"
			+ 'Host: ' + HOST + "\n"
			+ 'User-Agent: ' + 'InDesign/6.0 ' +'(Macintosh; U; Intel Mac OS X 10_5_6; ja-jp)' + "\n"
			+ "\n");
		rep = conn.read(99999);
		conn.close();
	} else {
		return false;
	}
	var rep_json = eval('(' + rep.match(/\{"responseData".*\}?/) + ')');
	return rep_json.responseData.translatedText;
}

var T = app.activeDocument.textFrames;
for (var i=0; i<T.length; i++) {
	T[i].contents = getTranslation(T[i].contents);
}
Profile
choco
Author : choco

印刷・製版の現場を経て、広告制作会社でPhotoshopを使ったビジュアル制作を担当。

→現在は車載機器開発ベンダにて、組み込み3Dデータ作成やUIデザインなどを行っています。

Categories
Favorites


Search
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。