逆にこの時確かにそうなる。

逆に,このとき与式は確かに恒等式になる

にぽくんぼっとエクスプローラー

Twitterクライアントを作ってみたくて始めたことだけれど、結局面倒で放置してある。昨年だか一昨年だかにOAuth認証が上手くいったので、それ以降はぼちぼちやってるんだけど、クライアントを作らないといけない状況では無いので、やる気にならない。
去年の3月位にtwittbotか何かで定期postなんかをする人が増えてきて、なんとなく自分もやってみたくなって、まあ当然他のサービスを使うのは許せないので自分で組んだ。
今回は、「にぽくん」という単語に反応するbotを作ってみようと思って、やってみたのである。


久しぶりにDev.Twitter.Comを開いておどろいたのが、自分用にAccess Tokenを発行してくれるようになっていたことだ。今までだと、わざわざOAuth認証のプログラムを書いて、Tokenだけ取得してからやっていたけど、その手間が省けた。


英語の仕様書は苦手。
相変わらずの不勉強なのでめちゃくちゃなコードだと思う。

まずは検索する

検索は、特にAccess Tokenとかは要らないので、普通にGETでパラメタを渡して、もらうだけ。

use LWP::UserAgent;
use LWP::Simple;
$SEARCHDATA=get("http://search.twitter.com/search.json?q=%e3%81%ab%e3%81%bd%e3%81%8f%e3%82%93&rpp=5&include_entities=true&result_type=mixed");

これで、「にぽくん」を検索したデータをJSON形式で$SEARCHDATAに得ることができる。
result_typeによって、Twitterの検索でいう「TOP」と「All」を分けたり、混ぜたりできる。
rppは取得するツイイトの数だが、5という数字に意味は無い。適当。qはキーワードだが、これはUTF-8URIエンコードしておく。

んでね、ここからが問題だったの。

JSONを扱う

僕ね、JSON扱ったことなかったんだわ〜〜〜
まあ適当なライブラリを探してみたらJSONってのがあったのでそれをCPANから落としていれたのね。

use JSON qw/encode_json decode_json/;
$json=decode_json($SEARCHDATA);

これで$jsonに値が格納されるようなんだが...
???いったい何が格納されたの???
そもそもJSON形式について理解していないので、とりあえず$SEARCHDATAの中身を分析したら、とにかくカッコで大量にくくられて、最終的に〜:〜の形で値が入っていることが分かった。
では$jsonの中身は???printしてみるとHASH0xNNNNNNなどと出てくる。ん?????どうすればいいの???
JavaScriptだとオブジェクトが入っているイメージだなあとか思って、これはメモリ番地!!と勘づく。
で?????どうすればいいの?????
悩むこと小2時間(Perlをきちんと勉強している人ならここは悩むところでは無い)。JSONのデータとにらめっこしながら、ついに理解した!!!
配列とハッシュが何重にも入り組んでいたんだね。2次元配列とかめじゃない。中学生の時に買ったPerlポケットリファレンスを見ながら、矢印演算子について理解して、やっと書けた。
「にぽくん」を含むツイイトをした人へ返信(リプライ・メンション)をしたいので、

$post_name = $json -> {'results'} -> [$i] -> {'from_user'};#投稿者名
$post_id =  $json -> {'results'} -> [$i] -> {'id'};#ツイイトID

こうする。$iは、検索で取得したツイイトの何番目かということ。配列になっている。rpp=5なので、$iは0〜4まで指定することができる。

僕の使っているレンタルサーバは、最大で1時間に1回までしかcronが許可されていないので、リアルタイムに反応することは不可能。だからまあ、最新のツイイト3つくらいを調べることにした。

同じツイートに対して何回もメンションを送るのは馬鹿げているので、ツイイトIDを保存しておくことにした。
もし被っているツイイトがあったら、「にぽ〜ん」をSTDOUTに出力する。
cronを使うので、忘れずFindBinしておく。

use FindBin;
for($i=0;$i<3;$i++){
	$post_name = $json -> {'results'} -> [$i] -> {'from_user'};#投稿者名
	$post_id =  $json -> {'results'} -> [$i] -> {'id'};#ツイイトID
	open FH1, "$FindBin::Bin/ツイートIDのリストファイル";
	while($line = <FH1>){
		chomp $line;
		if ($line == $post_id){
			print "にぽ〜ん\n";
			$last=1;last;
		}
	}
	if($last==1){$last=0;next;}
	close FH1;
	open FH2, ">>$FindBin::Bin/ツイートIDのリストファイル";
	print FH2 $post_id."\n";
	close FH2;
	@TWEETs=('にぽぽぽーん★','にぽっ★☆★☆','にぽぽ★●◆▲∫dt');
	$temp=int(rand 3);
	$tweet="\@$post_name $TWEETs[$temp]";
	&tweet($tweet);
}
in reply toの設定をする

postする際に、パラメタとしてin_reply_to_status_idを付与することによって、特定のツイイトに対する返信となるが、このとき、かならず「@post主」をツイイト内に含まねばならないことになっている。
&tweetは、つぶやくためのサブルーチンであるが(引数につぶやく内容を指定)、in_reply_to_status_idを付与するために、今までのものとは少し異なる。これも苦労した。OAuthを使ってTwitterでつぶやく場合、HMAC_SHA1でハッシュ化しないといけないが、このときパラメータの順番はアルファベット順になるようにしないと反応しない。エラー。
これに気づかなくて、1時間ほど費やしてしまった。
結局こうなる。

use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex);
use MIME::Base64;
use XML::Simple;

sub tweet{
$tweet=$_[0];
$tweet =~ s/([^a-zA-Z0-9\.\-\~\_])/'%'.uc(unpack('H2', $1))/eg;
$tweet =~ s/\+/%20/g;$twee=$tweet;
$tweet =~ s/([^a-zA-Z0-9\.\-\~\_])/'%'.uc(unpack('H2', $1))/eg;
$tweet =~ s/\%0A//g;

$time=time;
$nonce=time+time*7;

$data="POST&http%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.xml&in_reply_to_status_id%3D$post_id%26oauth_consumer_key%3D$key%26oauth_nonce%3D$nonce%26oauth_signature_method%3D$method%26oauth_timestamp%3D$time%26oauth_token%3D$token%26oauth_version%3D$ver%26status%3D$tweet";
$digest = encode_base64(hmac_sha1($data, $secret));$base64or=$digest;
$digest =~ s/([\W])/'%'.uc(unpack('H2', $1))/eg; #URLエンコード
$digest =~ s/\%0A//g;
$query_string = "in_reply_to_status_id=$post_id&oauth_consumer_key=$key&oauth_nonce=$nonce&oauth_signature_method=$method&oauth_timestamp=$time&oauth_token=$token&oauth_version=$ver&status=$twee&oauth_signature=$digest";

$ua = LWP::UserAgent->new;
$url = 'http://api.twitter.com/1/statuses/update.xml';

$req = HTTP::Request->new(POST => $url);
$req->content_type('application/x-www-form-urlencoded');
$req->content($query_string);

$res = $ua->request($req);
}

これで完成!ローカルでのテスト成功!アップロード!


そしたらエラーで動かない。use CGI::Carp qw(fatalsToBrowser); してみると、JSON.pmにはencode_jsonもdecode_jsonも無いと出る。
は?????何言ってんのこいつ?????
文字コード変えたりなんかいろいろやって、ふと思いついたのが、
「もしかしてこのサーバJSONモジュールが最初から入っている?そしてそのJSONバージョンが古い?」
試しにJSON.pmを削除すると...同じエラー!!やっぱり!

ということで、@INCにローカルパスを追加して、さらにJSONを全てJSONNにリネーム。

BEGIN {
use FindBin;
push(@INC, "$FindBin::Bin");
}

これでやっと動いた〜!!!めでたしめでたし

              • -

本を買って、文法を一から学んでいくプログラミング言語は外国語に似ている気がする。中学校で英語を習う時も、一から教わる。
だけど母語の日本語はそうじゃなくて、暮らしている中でなんとなく見につけて、なんとなく使っている。もちろん後から文法をやるんだけど、そうではなくて、理由は分からないけれどこうは言わない、みたいなことが分かったりする。
僕はきちんとPerlを勉強しないで使い始めて、いろんな人のブログとかでソースコードを読んでは使ってみて修正して、経験的に得られた知識によってコーディングしているのでなんか母語みたいな感じかなあ〜w仮にそうだとしても3歳レベルだけどね。



「うわ〜、Perl母語とか気持ち悪っ、近寄らんとこ。」





ちなみに、にぽくんぼっとエクスプローラーという名前は、Windows(3.1以前はエクスプローラー無いけど)への愛と、インテリマウスエクスプローラーら辺からインスパイアされました。ええ、ただのM$好きですねハハハ。