2010/06/30
このページの情報はもう古いです。
公式リツイートを使いましょう。

twitterのAPIを使用してキーワードにマッチしたつぶやきをRTするbotをperlでつくりました。

出来上がってみれば100行ほどなのですが、半日近くかかってしまいました。

動作環境

以下のPerlモジュールが必要です。

  • Net::Twitter
  • URI::Escape
  • LWP::Simple
  • XML::DOM

Net::Twitterをインストールすれば後は勝手についてくる気もします。
私自身はMacOS 10.6 Snow Leopardで開発して動かしています。

仕様

Twitterサイト上で保存した「検索メモ」を取得して、記事を検索し一致した記事の先頭に「RT @ユーザー名: 」をつけて投稿し直します。

検索はNet::Twiterでもできるのですが、デバッグがやりにくかったので、LWPとDOMを使用してAtomフィードをパースしてます。

記事のIDの最大値をログに保存するようになっており、保存されたIDより古い記事はRTしません。
このログはホームディレクトリ内に「.twitbot.txt」というファイル名で保存されます。

使い方

Cronに登録してご利用ください。

なぜかlang=jaをつけると一定時間以上前の古い投稿が検索されないようです。
また、キーワードごとに15件までしか取得しておりませんので、キーワードによっては、Cronのインターバルを短かくしないと取りこぼしが生じると思います。

ソース

以下に直接ソースを貼りつけておきます。

#!/opt/local/bin/perl -wT

#binmode(STDOUT, ":utf8");

use strict;
use Encode;
use Net::Twitter;
use URI::Escape;
use LWP::Simple;
use XML::DOM;

# Config
my $twitter_user = ''; # twitterユーザー名
my $twitter_pass = ''; # twitterパスワード
my $lang         = 'ja';
my $api          = 'http://search.twitter.com/search.atom';

# do not need to edit
my $twt_clientname  = 'twitbot';
my $twt_clientver   = '0.1';
my $twt_clienturl   = 'http://www.theta.ne.jp/';
my $max_length      = 140;
my $home;
if (-d $ENV{'HOME'} && $ENV{'HOME'} =~ /^(\/.+)$/) {
    $home = $1;
}
my $log = $home.'/.twitbot.txt'; # 最新のIDを保存して次回以降はこのID以下は無視
if (!-e $log) {
    open (OUT, ">$log");
    close(OUT);
}

my $max_id = 0;
open (IN, $log);
my $n = <IN>;
if ($n) {
    chomp $n;
    $max_id = $n;
}
close(IN);

# connect to twitter
my $twt = Net::Twitter->new(
    username    => $twitter_user,
    password    => $twitter_pass,
    clientname  => $twt_clientname,
    clienturl   => $twt_clienturl,
    useragent   => $twt_clientname,
    source   => $twt_clientname,
    clientver   => $twt_clientver,
);

my $sch = $twt->saved_searches();
my %posted;
my $since = $max_id;
foreach (@$sch) {
    my $k = uri_escape_utf8($_->{query});
    my $url = $api.'?show_user=true&q='.$k.'&lang='.$lang;
    my $atom = get($url);
    my $parser = new XML::DOM::Parser;
    my $doc = $parser->parse ($atom);
    my $nodes = $doc->getElementsByTagName ("entry");
    for (my $i=0; $i<$nodes->getLength; $i++) {
        my $node = $nodes->item($i);
        my $txt = getvalue($node, 'title');
        my $id = getvalue($node, 'id');
        if ($id =~ /([0-9]+)$/) {
            $id = $1;
        } else {
            die ('can not get id');
        }
        if ($max_id < $id) {
            $max_id = $id;
        }
        # 以下に該当するものはRTしない
        # 自分の投稿
        # RT(スペース)が含まれる投稿
        # @が含まれる投稿
        # すでにRTした投稿
        # ログに保存されたIDより古い投稿
        if ($txt !~ /^$twitter_user/ && $txt !~ /RT\s/ && $txt !~ /\@/ && !$posted{$id} && $since < $id) {
            $posted{$id} = 1; # 重複投稿しないためのフラグ
            my $post = 'RT @'.$txt;
            $post = decode_utf8($post);
            if (length($post) > $max_length) {
                $post = substr($post, 0, 137)."...";
            }
#            print $post."\n";
#            print "----\n";
            eval {$twt->update($post)};
            if ($@) {
                warn "update failed because: ".$@."\n";
            }
        }
    }
}

open (OUT, "> $log");
print OUT $max_id;
close(OUT);

exit;

sub getvalue {
    my $node = shift @_;
    my $tag = shift @_;
    my $n = $node->getElementsByTagName($tag)->item(0);
    return $n->getFirstChild->getNodeValue;
}