『 引用βのTwitterボットを作りました 』

2009 年 3 月 2 日

inyo_beta

橋本商会さんのスーパーボット対戦の記事が素敵な感じだったので、僕もボットを作ってみたくなりました。

といっても、マルコフ連鎖とか高度な事はできないので(大学でやってたはずなんだけどなあ・・・)
引用βへの最新投稿を表示するTwitterボットを作ってみたのです。
その名も

「引用β」

ってまんまやーん。
怒られないかな、ドキドキ。

引用βって大好きなんですけど、つい見逃しちゃうことが多いのです。
そこで、TwitterFox ( FireFox の拡張機能で、Twitter の新着ポストをペコッと知らせてくれる優れもの)で知らせてくれると便利じゃんと思って作りました。
普通の RSS リーダーでも十分なんですが、ちょっと思うところがありまして Twitter ボットにしてみました。

実装は php で、heteml の cron で1時間に1回引用βの RSS をチェックしに行きます。
で、前回チェックしたときよりも最新の記事があれば Twitter に投稿します。
そんな動きなのですが、なぜか数回に1回、php の XML パーサがうまく動かなくて
RSSの一番上のアイテムをスルーしてしまう様子。
そのせいでたまに過去の引用を投稿しちゃうんだけれども、
そこは愛嬌って事で御願いします(o’c_,`人)・:*:・

以下ソースコード。
いらないって?まあそういわずに。
上記バグの原因を誰か教えてくれるといいなあ、なんつって。

Twitter に投稿するクラス(Twitter.php)

<?php
//テストコード
//$twitter = new Twitter("USER_NAME", "PASSWORD");
//$twitter->post("Hello Twitter !");

/* Twitterに投稿するクラス */
class Twitter {

    //-----------------------------------------
    // CONSTANT
    //-----------------------------------------
    const API = "http://twitter.com/statuses/update.xml?";

    //-----------------------------------------
    // VARIABLES
    //-----------------------------------------
    private $username;
    private $password;

    //-----------------------------------------
    // Constructor
    // @param    $username    Twitterのユーザーネーム
    // @param    $password    Twitterのパスワード
    //-----------------------------------------
    public function Twitter($username, $password) {
        $this->username = $username;
        $this->password = $password;
    }

    //-----------------------------------------
    // 投稿する
    // @param    $message    投稿するメッセージ
    //-----------------------------------------
    public function post($message) {
        $params = "status=".rawurlencode($message);
        $result = file_get_contents(Twitter::API.$params, false,
            stream_context_create(array(
                "http" => array(
                    "method" => "POST",
                    "header" => "Authorization: Basic ". base64_encode($this->username.":".$this->password)
                )
            ))
        );
    }
}
?>

引用βのRSSを取ってくるクラス(RssReader.php)

<?php
//テストコード
//$reader = new RssReader();
//$reader->load("http://feeds.feedburner.com/inyo/everyone", 5);

/* 引用βのRSSを取ってくるクラス */
class RssReader {

    //-----------------------------------------
    // CONSTANT
    //-----------------------------------------

    //-----------------------------------------
    // VARIABLES
    //-----------------------------------------
    private $url;
    private $xml;
    private $max_post;
    public $entries;

    //-----------------------------------------
    // Constructor
    //-----------------------------------------
    public function RssReader() {
    }

    //-----------------------------------------
    // load feed
    // @param    $url        RSSの場所
    // @param    $max_post    1回のチェックで拾ってくる最新のポスト数
    //-----------------------------------------
    public function load($url, $max_post=5) {

        $this->url = $url;
        $this->max_post = $max_post;

        if ($this->xml = simplexml_load_file($this->url)) {
            //取ってきたRSSを解析する
            $this->parseAsRSS();
        } else {
            return array();
        }
    }

    //-----------------------------------------
    // RSSを解析する
    //-----------------------------------------
    private function parseAsRSS() {

        //前回取ってきたRSSの最新記事が保存されているので読み込む
        if ($fp = fopen("log.dat", "r")) {
            $last = fgets($fp, 32000);
            fclose($fp);
        }

        //エントリをザーッと取ってくる
        $items = $this->xml->channel->item;

        //パーマリンクのURLを2重投稿防止用のチェックに使う
        $recent = (string) $items[0]->link;

        $c = 0;
        $this->entries = array();

        foreach ($items as $item) {

            //------------------------------------
            // 今回解析する最大記事数を超えていないかチェック
            //------------------------------------
            if (++$c > $this->max_post) break;

            //パーマリンクを取得する
            $permalink = (string) $item->link;

            //------------------------------------
            // 2重投稿ではないかチェック
            //------------------------------------
            if (strcmp($last, $permalink) == 0) break;

            //<p>タグおよびURL部分を除いて、120文字にトリムしたエントリ内容を取得する
            preg_match("/(</p><p>)([^\/]+)(< \/p>)/", (string) $item->description, $tmp);
            $text = mb_strimwidth(chop($tmp[2]), 0, 120, "...", "UTF-8");

            //------------------------------------
            // 配列に入れておく(この中のを後で投稿する)
            //------------------------------------
            array_push($this->entries,
                array(
                    "text" => $text,
                    "permalink" => $permalink
                )
            );
        }

        //今回取得したRSSの中で、最新記事のパーマリンクを次回読み込み時用に保存しておく
        if ($fp = fopen("log.dat", "w")) {
            flock($fp, LOCK_EX);
            fputs($fp, $recent);
            flock($fp, LOCK_UN);
            fclose($fp);
        }
    }
}
?>

上2つのクラスを使って、取得→投稿の一連の動作をしてくれるスクリプト

<?php
//-----------------------------------------
// クラスの読み込み
//-----------------------------------------
require_once("Twitter.php");
require_once("RssReader.php");

//-----------------------------------------
// それぞれのクラスのインスタンスを生成する
//-----------------------------------------
$twitter = new Twitter("inyo_beta", "********");
$reader = new RssReader();

//-----------------------------------------
// まず更新分のRSSを取得する
//-----------------------------------------
$reader->load("http://feeds.feedburner.com/inyo/everyone", 5);

//-----------------------------------------
// 更新分のRSSをTwitterに投稿する
//-----------------------------------------
if (count($reader->entries) > 0) {
    //逆順にしないと、最近記事が下になる
    $entries = array_reverse($reader->entries);
    foreach ($entries as $entry) {
        echo $entry["text"]." ".$entry["permalink"]."<br />\n";
        $twitter->post($entry["text"]." ".$entry["permalink"]);
        sleep(2);
    }
}
//ログを吐く
echo count($entries)." entries were posted.";
?>
« 
» 

Leave a Reply