We All Get Old - Naskin Diary

Yet Another My Life On The Web

はてなハイクボット作成に挑戦してみた - 技術っぽい事篇

開発環境

今手元にあって稼働するマシンがMacBookしかないので必然的にMac OS Xでの開発。ちなみに初めてのMac&いろいろ躊躇していたのでSnow Leopard(10.6)には更新してない*1


言語はPerl 5.8.9。
エディタはviでバリバリ(やめて!)書くスタイル。Eclipseとかの統合開発環境は仕事で使ったことがあるけど、家でやる分にはviで足りてしまう、ビル・ジョイ万歳。


実行環境も同じで、作成したPerlスクリプトをシェルからバックグラウンドで実行。


外部サーバーを借りていないのでパソコンをシャットダウンした時はボットも停止、SSHログイン出来る(デバッグ)ができるサーバー借りようかな、他にもボットとかWebツールとか作るかもしれないし。


で借りているサービスはCGIは動かせるけど、FTPアップロードしか出来ないのでデバッグ出来ないのが難点。


PerlCPANとパスの変更くらいで他のUnixライクなOSでも動くはず。

参考

初めてのPerl 第5版

初めてのPerl 第5版

環境構築

CPANで必要なモジュールを設定。
モジュールが上手くインストール出来なくて焦るが、sudoを忘れていただけだった。

認証

投稿データの取得はHTTPのGETリクエストなので問題なかったが、ボットからの投稿はPOSTになりベーシック認証が必要になる。
はてなハイク- API 認証

    $ua->header(Authorization => encode_base64("$username:$password"));        

説明ページにある方法だとうまくヘッダを設定できなかっので、ぐぐったりしてHTTP::Request側で設定する方法を発見。


$ua->header()は「$ua = LWP::UserAgent->new();」作ったオブジェクトじゃないのか?

    my $req = HTTP::Request->new(POST => $url_post);
    $req->authorization_basic($username, $password);
    $req->content_type('application/x-www-form-urlencoded');

HTTP::Request::Commonというものもあるらしい。理解が足りない。


この時点でベーシック認証のヘッダを設定するだけなら少しは慣れのあるPHPでもいけるんじゃないかと挑戦するも失敗。PerlPHPの狭間で挫折しそうになる。

仕組み

処理の流れは

  1. 投稿データ取得(GET):ステータス・タイムライン(投稿データ)をstatuses/keyword_timelineからデータを取得
  2. XMLを取り込み:XML::Simple->XMLin()で取り込み
  3. 文字列検索:エントリ内容の文字列を正規表現でマッチする物を取り出しPOST(エントリへのReply)する。

これを特定のキーワード毎に繰り返したら、スリープで待機してまたGETから再実行する。

ハマったポイント:RSS取得/XML解析の処理

RSS取得/XMLの解析を検索しながら手探りで作ったのだが、この記事でid:naoyaが3年も前に書いていた。。。
ボット作成中に見つけられなかった><
作って学ぶ、今どきのWebサービス:第2回 RSSフィードの料理はLWPとXML::RSSにおまかせ (1/2) - ITmedia エンタープライズ

ハマったポイント2:ポスト投稿処理

理解するまで時間がかかったのはエントリー更新の説明


APIのURLに「http://h.hatena.ne.jp/api/statuses/update.format」(format は xml or json)、自分はxmlで設定。


「Method:POST」、「Auth:必要あり」は認証のところで確認したので問題なし。


悩んだところはParametersの理解。
まずURLにupdate.formatと書いてあったので投稿する時もXML形式かJSON形式の文字列で投稿データを用意しないといけないものと勘違いしてしまった。


実際は「HTTP::Request->content()」のに設定する文字列としてParametersに記載されてあるstatus、in_reply_to_status_id、keyword、file、sourceを以下のように設定してあげればよいだけだった。


例えばキーワード「今日のリポD」に「今日はリポビタン全種類飲んで疲れを吹き飛ばしましょう! 」というエントリーをしたければ

	$content = "status=" . uri_escape_utf8("今日はリポビタン全種類飲んで疲れを吹き飛ばしましょう! ")
		. "&keyword=" . uri_escape_utf8("今日のリポD") . "&source=bot/Pay_By_Ripo type D";

	$req->content($content);

リクエストオブジェクトのcontent()に設定してやるだけだった。
他のアカウントのエントリーに応答するなら"&in_reply_to_status_id={応答先のid}"を追加すればOK。

他にはまったこと

今日のリポD - はてなハイク」に書き込んだ人にリポDをランダムに勧めるエントリー機能を作成したところ、ボットエントリーにリプライされるとまたボットが反応してしまうという無限エントリーする羽目に(苦笑


XMLの中に誰へのリプライか記載されてりるので、ボットへのリプライならスキップするように修正。


とりえあえずボットを作成するを目標に強引にコーディングを進めたので、再理解のためにXML解析処理、エラー処理等を再実装して理解を深める。

*1:元気になったしそろそろアップデートに挑戦するか