From e1b3114d8b7edc676f621335df8e061a5a8777c2 Mon Sep 17 00:00:00 2001 From: mitty Date: Tue, 5 Oct 2010 06:30:48 +0000 Subject: [PATCH 1/1] * Tagging twitter bot to version 0.1 git-svn-id: https://lab.mitty.jp/svn/lab/tags/twitter@66 7d2118f6-f56c-43e7-95a2-4bb3031d96e7 --- 0.1/get_oauth.pl | 34 +++++++ 0.1/nt_bot.pl | 23 +++++ 0.1/show_status.pl | 61 +++++++++++++ 0.1/twitterbot.pl | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++ 0.1/user_timeline.pl | 128 +++++++++++++++++++++++++++ 5 files changed, 486 insertions(+) create mode 100755 0.1/get_oauth.pl create mode 100755 0.1/nt_bot.pl create mode 100755 0.1/show_status.pl create mode 100755 0.1/twitterbot.pl create mode 100755 0.1/user_timeline.pl diff --git a/0.1/get_oauth.pl b/0.1/get_oauth.pl new file mode 100755 index 0000000..ca2199c --- /dev/null +++ b/0.1/get_oauth.pl @@ -0,0 +1,34 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use utf8; +use Net::Twitter; + +use YAML::Tiny; +my $config = (YAML::Tiny->read('config.yml'))->[0]; + +my $consumer_key = $config->{'consumer_key'}; +my $consumer_key_secret = $config->{'consumer_secret'}; +my $access_token = $config->{'access_token'}; +my $access_token_secret = $config->{'access_token_secret'}; + +my $nt = Net::Twitter->new( + traits => ['API::REST', 'OAuth'], + consumer_key => $consumer_key, + consumer_secret => $consumer_key_secret, +); +print 'access this url by bot account : '.$nt->get_authorization_url."\n"; +print 'input verifier PIN : '; +my $verifier = ; +chomp $verifier; + +my $token = $nt->request_token; +my $token_secret = $nt->request_token_secret; + +$nt->request_token($token); +$nt->request_token_secret($token_secret); + +my($at, $ats) = $nt->request_access_token(verifier => $verifier); + +print "Access token : ".$at."\n"; +print "Access token secret : ".$ats."\n"; diff --git a/0.1/nt_bot.pl b/0.1/nt_bot.pl new file mode 100755 index 0000000..5b005ca --- /dev/null +++ b/0.1/nt_bot.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use utf8; +use Net::Twitter; + +use YAML::Tiny; +my $config = (YAML::Tiny->read('config.yml'))->[0]; + +my $consumer_key = $config->{'consumer_key'}; +my $consumer_key_secret = $config->{'consumer_secret'}; +my $access_token = $config->{'access_token'}; +my $access_token_secret = $config->{'access_token_secret'}; + +my $nt = Net::Twitter->new( + traits => ['API::REST', 'OAuth'], + consumer_key => $consumer_key, + consumer_secret => $consumer_key_secret, +); +$nt->access_token($access_token); +$nt->access_token_secret($access_token_secret); + +my $res = $nt->update({ status => "Perl から Twiitter を更新するテストですよー" }); diff --git a/0.1/show_status.pl b/0.1/show_status.pl new file mode 100755 index 0000000..1c76079 --- /dev/null +++ b/0.1/show_status.pl @@ -0,0 +1,61 @@ +#! /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 Data::Dumper; + +my $bot = Net::Twitter::Lite->new; + +eval { + foreach my $id (@ARGV) { + my $res = $bot->show_status($id); + foreach my $line (split /\n/, Dumper $res) { + if ($line =~ /undef/) { next; } + unless ($line =~ / => {/ + || $line =~ / = / + || $line =~ /status/ + || $line =~ /'text'/ + || $line =~ /created/ + || $line =~ /'id'/ + || $line =~ /name/ + || $line =~ / },/ + || $line =~ / };/ + ) { next; } + print $line, "\n"; + } + } +}; +if ($@) { + evalrescue($@); +} +print "truncated output done\n"; + + +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 $@; + } +} diff --git a/0.1/twitterbot.pl b/0.1/twitterbot.pl new file mode 100755 index 0000000..d1dd229 --- /dev/null +++ b/0.1/twitterbot.pl @@ -0,0 +1,240 @@ +#! /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; +use Date::Parse qw(str2time); + +my $_execmode = $ARGV[0] || 0; +sub VERBOSE () { $_execmode eq 'verbose' }; +sub DEBUG () { VERBOSE or $_execmode eq 'debug' }; +use Data::Dumper; + +DEBUG and warn "$0: debug mode"; + +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 = {}; +} + +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->{search}); +if ($tweet) { + %$tweets = (%$tweets, %$tweet); +} + +$tweet = mentions_ids($bot, $stat->{mention}); +if ($tweet) { + %$tweets = (%$tweets, %$tweet); +} + +foreach my $id (sort keys %$tweets) { + # $tweets->{$id}{type} eq 'search' => found by search API + # eq 'mention' => found by mention API + if ($tweets->{$id}{type} eq 'retweet') { + next; + } + DEBUG or sleep($conf->{sleep}); + + # do retweet found tweets + my $res; + eval { + DEBUG or $res = $bot->retweet($id); + DEBUG and warn "retweet($id) => ", Dumper($tweets->{$id}); + }; + if ($@) { + evalrescue($@); + warn "status_id => $id\n"; + next; + } + + $stat->{$tweets->{$id}{type}} = $id; +} + +if ($tweets) { + # save last status to yaml file + DEBUG or YAML::Tiny::DumpFile("$Bin/status.yml", $stat); + DEBUG and warn "status.yml => ", Dumper($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 @_ || 1; + + my $key = ""; + foreach my $word (@$keywords) { + if ($key) { + $key .= " OR $word"; + } + else { + $key = $word; + } + } + DEBUG and warn "searching '$key'"; + + my $res; + my $ids = {}; + eval { + if ($key) { + $res = $bot->search( + { + q => $key, + since_id => $since_id, + } + ); + } + VERBOSE and warn Dumper($res); + if ($res->{results}) { + foreach my $tweet (@{$res->{results}}) { + my $res = $bot->show_status($tweet->{id}); + VERBOSE and warn Dumper($res); + + my $id = { + date => str2time($res->{created_at}), + screen_name => $res->{user}{screen_name}, + status_id => $res->{id}, + text => $res->{text}, + user_id => $res->{user}{id}, + }; + if ($res->{retweeted_status}) { + $id->{retweet_of} = $res->{retweeted_status}{id}; + $id->{type} = 'retweet'; + } + else { + $id->{type} = 'search'; + } + $ids->{$tweet->{id}} = $id; + } + } + }; + if ($@) { + evalrescue($@); + } + + DEBUG and warn "search result => ", Dumper($ids); + 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 @_ || 1; + + my $res; + eval { + $res = $bot->mentions( + { + since_id => $since_id, + } + ); + VERBOSE and warn Dumper($res); + }; + if ($@) { + evalrescue($@); + } + + my $ids = {}; + if ($res && @{$res}) { + $ids = { + map { + $_->{id} => { + date => str2time($_->{created_at}), + screen_name => $_->{user}{screen_name}, + status_id => $_->{id}, + text => $_->{text}, + type => 'mention', + user_id => $_->{user}{id}, + } + } @{$res} + }; + } + + DEBUG and warn "mentions result => ", Dumper($ids); + 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 $@; + } +} diff --git a/0.1/user_timeline.pl b/0.1/user_timeline.pl new file mode 100755 index 0000000..36fd046 --- /dev/null +++ b/0.1/user_timeline.pl @@ -0,0 +1,128 @@ +#! /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; +use Data::Dumper; +use Encode; + +if (@ARGV < 1) { + die "usage: $0 screen_name [number_of_pages|all [dump]]\n"; +} +my $screen_name = $ARGV[0]; +my $pages = $ARGV[1] || 1; +if ($pages eq 'all') { + $pages = -1; +} +my $dump = $ARGV[2] || 0; + +my $conf = loadconf("$Bin/config.yml"); +if (! defined $conf) { + die "$0: cannot parse config file.\n"; +} + +my $bot = login($conf); +if (! $bot->authorized) { + die "$0: this client is not yet authorized.\n"; +} + + +eval { + my $page = 0; + while ($pages - $page && $page <= 160) { + $page++; + my $res = $bot->user_timeline( + { + screen_name => $screen_name, + page => $page, + } + ); + + if ($dump) { + foreach my $line (split /\n/, Dumper $res) { + if ($line =~ /undef/) { next; } + print $line, "\n"; + } + } + else { + foreach my $status (@{$res}) { + my $text = ""; + $text .= $status->{user}{name}; + $text .= " [" . $status->{created_at} . "]"; + $text .= " (". $status->{id} . ")"; + $text .= " ". encode('utf8', $status->{text}); + $text =~ s/\n//; + print $text, "\n"; + } + } + } +}; +if ($@) { + evalrescue($@); +} +print "done\n"; + + +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 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