From 5ce2dafff83681702c4c8b2cdd5d1264668aab2b Mon Sep 17 00:00:00 2001 From: mitty Date: Mon, 13 Sep 2010 12:21:59 +0000 Subject: [PATCH] * retweert twitter bot * load configuration from YAML * search hashtag (combined with 'OR') * get mentions (replies) * skip retweeted tweets * save the latest reweeted tweet to YAML git-svn-id: https://lab.mitty.jp/svn/lab/trunk@48 7d2118f6-f56c-43e7-95a2-4bb3031d96e7 --- twitter/twitterbot.pl | 207 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 twitter/twitterbot.pl diff --git a/twitter/twitterbot.pl b/twitter/twitterbot.pl new file mode 100644 index 0000000..e4d1b62 --- /dev/null +++ b/twitter/twitterbot.pl @@ -0,0 +1,207 @@ +#! /usr/bin/perl -w + +use strict; +use warnings; +use utf8; + +## IMPORTANT ## +# When Net::Twitter::Lite encounters a Twitter API error or a network error, +# it throws a Net::Twitter::Lite::Error object. +# You can catch and process these exceptions by using eval blocks and testing $@ +## from http://search.cpan.org/perldoc?Net::Twitter::Lite#ERROR_HANDLING +use Net::Twitter::Lite; +use FindBin qw($Bin); +use YAML::Tiny; + +my $conf = loadconf("$Bin/config.yml"); +if (! defined $conf) { + die "$0: cannot parse config file.\n"; +} +my $stat = loadconf("$Bin/status.yml"); +if (! defined $stat) { + $stat = { + # do not set to 0 + since_id => 1, + }; +} + +my $bot = login($conf); +if (! $bot->authorized) { + die "$0: this client is not yet authorized.\n"; +} + +my %tweets; +my $tweet; + +$tweet = or_search($bot, $conf->{hashtag}, $stat->{since_id}); +if ($tweet) { + %tweets = (%tweets, %$tweet); +} + +$tweet = mentions_ids($bot, $stat->{since_id}); +if ($tweet) { + %tweets = (%tweets, %$tweet); +} + +foreach my $id (sort keys %tweets) { + if ($tweets{$id} eq 'retweet') { + next; + } + sleep($conf->{sleep}); + # retweet found tweet + # $tweets->{$id} eq 'search' => found by search API + # eq 'mention' => found by mention API + my $res; + eval { + $res = $bot->retweet($id); + }; + if ($@) { + evalrescue($@); + warn "status_id => $id\n"; + next; + } + + $stat->{since_id} = $id; +} + +if (%tweets) { + # save last status to yaml file + YAML::Tiny::DumpFile("$Bin/status.yml", $stat); +} + + +sub loadconf { + # load configration data from yaml formatted file + # param => scalar string of filename + # ret => hash object of yaml data + + my $file = shift @_; + + my $yaml = YAML::Tiny->read($file); + + if ($!) { + warn "$0: '$file' $!\n"; + } + + return $yaml->[0]; +} + +sub login { + # make Net::Twitter::Lite object and login + # param => hash object of configration + # ret => Net::Twitter::Lite object + + my $conf = shift @_; + + my $bot = Net::Twitter::Lite->new( + consumer_key => $conf->{consumer_key}, + consumer_secret => $conf->{consumer_secret}, + ); + + $bot->access_token($conf->{access_token}); + $bot->access_token_secret($conf->{access_token_secret}); + + return $bot; +} + +sub or_search { + # search tweets containing keywords + # param => Net::Twitter::Lite object, ArrayRef of keywords, since_id + # ret => HashRef of status_id (timeline order is destroyed) + # or undef (none is found) + + my $bot = shift @_; + my $keywords = shift @_; + my $since_id = shift @_; + + my $key = ""; + foreach my $word (@$keywords) { + if ($key) { + $key .= " OR $word"; + } + else { + $key = $word; + } + } + + my $res; + my $ids = {}; + eval { + if ($key) { + $res = $bot->search( + { + q => $key, + since_id => $since_id, + } + ); + } + if ($res->{results}) { + foreach my $tweet (@{$res->{results}}) { + my $res = $bot->show_status($tweet->{id}); + if ($res->{retweeted_status}) { + $ids->{$tweet->{id}} = 'retweet'; + } + else { + $ids->{$tweet->{id}} = 'search'; + } + } + } + }; + if ($@) { + evalrescue($@); + } + + return $ids; +} + +sub mentions_ids { + # return status_ids mentioned to me + # param => Net::Twitter::Lite object, since_id + # ret => HashRef of status_id (timeline order is destroyed) + # or undef (none is found) + + my $bot = shift @_; + my $since_id = shift @_; + + my $res; + eval { + $res = $bot->mentions( + { + since_id => $since_id, + } + ); + }; + if ($@) { + evalrescue($@); + } + + my $ids; + if ($res && @{$res}) { + $ids = { + map { $_->{id} => 'mention' } @{$res} + }; + } + + return $ids; +} + +sub evalrescue { + # output error message at eval error + + use Scalar::Util qw(blessed); + + if (blessed $@ && $@->isa('Net::Twitter::Lite::Error')) { + warn $@->error; + if ($@->twitter_error) { + my %twitter_error = %{$@->twitter_error}; + map { + $twitter_error{"$_ => "} = $twitter_error{$_} . "\n"; + delete $twitter_error{$_} + } keys %twitter_error; + warn join("", %twitter_error); + } + } + else { + warn $@; + } +} -- 1.7.9.5