* Tagging twitter bot to version 0.1 twitter
authormitty <mitty@7d2118f6-f56c-43e7-95a2-4bb3031d96e7>
Tue, 5 Oct 2010 06:30:48 +0000 (06:30 +0000)
committermitty <mitty@7d2118f6-f56c-43e7-95a2-4bb3031d96e7>
Tue, 5 Oct 2010 06:30:48 +0000 (06:30 +0000)
git-svn-id: https://lab.mitty.jp/svn/lab/tags/twitter@66 7d2118f6-f56c-43e7-95a2-4bb3031d96e7

0.1/get_oauth.pl [new file with mode: 0755]
0.1/nt_bot.pl [new file with mode: 0755]
0.1/show_status.pl [new file with mode: 0755]
0.1/twitterbot.pl [new file with mode: 0755]
0.1/user_timeline.pl [new file with mode: 0755]

diff --git a/0.1/get_oauth.pl b/0.1/get_oauth.pl
new file mode 100755 (executable)
index 0000000..ca2199c
--- /dev/null
@@ -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 = <STDIN>;
+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 (executable)
index 0000000..5b005ca
--- /dev/null
@@ -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 (executable)
index 0000000..1c76079
--- /dev/null
@@ -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 (executable)
index 0000000..d1dd229
--- /dev/null
@@ -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 (executable)
index 0000000..36fd046
--- /dev/null
@@ -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 $@;
+    }
+}