source: lab/branches/twitter-0.1/twitterbot.pl @ 103

Last change on this file since 103 was 68, checked in by mitty, 14 years ago
  • remove needless hash check
  • search keywords from follower's tweets and reply to them
  • Property svn:executable set to *
File size: 7.8 KB
Line 
1#! /usr/bin/perl -w
2
3use strict;
4use warnings;
5use utf8;
6
7## IMPORTANT ##
8# When Net::Twitter::Lite encounters a Twitter API error or a network error,
9# it throws a Net::Twitter::Lite::Error object.
10# You can catch and process these exceptions by using eval blocks and testing $@
11## from http://search.cpan.org/perldoc?Net::Twitter::Lite#ERROR_HANDLING
12use Net::Twitter::Lite;
13use FindBin qw($Bin);
14use YAML::Tiny;
15use Date::Parse qw(str2time);
16
17my $_execmode = $ARGV[0] || 0;
18sub VERBOSE () { $_execmode eq 'verbose' };
19sub DEBUG   () { VERBOSE or $_execmode eq 'debug' };
20use Data::Dumper;
21
22DEBUG and warn "$0: debug mode";
23
24my $conf = loadconf("$Bin/config.yml");
25if (! defined $conf) {
26    die "$0: cannot parse config file.\n";
27}
28my $stat = loadconf("$Bin/status.yml");
29if (! defined $stat) {
30    $stat = {};
31}
32
33my $bot = login($conf);
34if (! $bot->authorized) {
35    die "$0: this client is not yet authorized.\n";
36}
37
38my $tweets = {};
39%$tweets = (
40    %$tweets,
41    %{ or_search($bot, $conf->{hashtag}, $stat->{search}) }
42);
43%$tweets = (
44    %$tweets,
45    %{ mentions_ids($bot, $stat->{mention}) }
46);
47
48foreach my $id (sort keys %$tweets) {
49    # $tweets->{$id}{type} eq 'search'  => found by search API
50    #                      eq 'mention' => found by mention API
51    if ($tweets->{$id}{type} eq 'retweet') {
52        next;
53    }
54    DEBUG or sleep($conf->{sleep});
55   
56    # do retweet found tweets
57    my $res;
58    eval {
59        DEBUG  or $res = $bot->retweet($id);
60        DEBUG and warn "retweet($id) => ", Dumper($tweets->{$id});
61    };
62    if ($@) {
63        evalrescue($@);
64        warn "status_id => $id\n";
65        next;
66    }
67   
68    $stat->{$tweets->{$id}{type}} = $id;
69}
70
71foreach my $word (keys %{$conf->{response}}) {
72    # reply to follower's tweets containing keywords
73    $tweets = follower_search($bot, $word);
74    foreach my $id (sort keys %$tweets) {
75        if ($tweets->{$id}{type} eq 'retweet') {
76            next;
77        }
78        DEBUG or sleep($conf->{sleep});
79       
80        my $text = '@' . $tweets->{$id}{screen_name}
81            . " " . $conf->{response}{$word};
82        my $res;
83        eval {
84            DEBUG  or $res = $bot->update(
85                {
86                    in_reply_to_status_id   => $id,
87                    status                  => $text,
88                }
89            );
90            DEBUG and warn "update(", $text, ") => ",
91                Dumper($tweets->{$id});
92        };
93        if ($@) {
94            evalrescue($@);
95        }
96    }
97}
98
99if ($tweets) {
100    # save last status to yaml file
101    DEBUG  or YAML::Tiny::DumpFile("$Bin/status.yml", $stat);
102    DEBUG and warn "status.yml => ", Dumper($stat);
103}
104
105
106sub loadconf {
107    # load configration data from yaml formatted file
108    #   param   => scalar string of filename
109    #   ret     => hash object of yaml data
110   
111    my $file = shift @_;
112   
113    my $yaml = YAML::Tiny->read($file);
114   
115    if ($!) {
116        warn "$0: '$file' $!\n";
117    }
118   
119    return $yaml->[0];
120}
121
122sub login {
123    # make Net::Twitter::Lite object and login
124    #   param   => hash object of configration
125    #   ret     => Net::Twitter::Lite object
126   
127    my $conf = shift @_;
128   
129    my $bot = Net::Twitter::Lite->new(
130        consumer_key    => $conf->{consumer_key},
131        consumer_secret => $conf->{consumer_secret},
132    );
133   
134    $bot->access_token($conf->{access_token});
135    $bot->access_token_secret($conf->{access_token_secret});
136   
137    return $bot;
138}
139
140sub or_search {
141    # search tweets containing keywords
142    #   param   => Net::Twitter::Lite object, ArrayRef of keywords, since_id
143    #   ret     => HashRef of status_id (timeline order is destroyed)
144    #               or undef (none is found)
145   
146    my $bot      = shift @_;
147    my $keywords = shift @_;
148    my $since_id = shift @_ || 1;
149   
150    my $key = "";
151    foreach my $word (@$keywords) {
152        if ($key) {
153            $key .= " OR $word";
154        }
155        else {
156            $key = $word;
157        }
158    }
159    DEBUG and warn "searching '$key'";
160   
161    my $res;
162    my $ids = {};
163    eval {
164        if ($key) {
165            $res = $bot->search(
166                {
167                             => $key,
168                    since_id    => $since_id,
169                }
170            );
171        }
172        VERBOSE and warn Dumper($res);
173        if ($res->{results}) {
174            foreach my $tweet (@{$res->{results}}) {
175                my $res = $bot->show_status($tweet->{id});
176                VERBOSE and warn Dumper($res);
177               
178                my $id = {
179                    date        => str2time($res->{created_at}),
180                    screen_name => $res->{user}{screen_name},
181                    status_id   => $res->{id},
182                    text        => $res->{text},
183                    user_id     => $res->{user}{id},
184                };
185                if ($res->{retweeted_status}) {
186                    $id->{retweet_of}   = $res->{retweeted_status}{id};
187                    $id->{type}         = 'retweet';
188                }
189                else {
190                    $id->{type} = 'search';
191                }
192                $ids->{$tweet->{id}} = $id;
193            }
194        }
195    };
196    if ($@) {
197        evalrescue($@);
198    }
199   
200    DEBUG and warn "search result => ", Dumper($ids);
201    return $ids;
202}
203
204sub mentions_ids {
205    # return status_ids mentioned to me
206    #   param   => Net::Twitter::Lite object, since_id
207    #   ret     => HashRef of status_id (timeline order is destroyed)
208    #               or undef (none is found)
209   
210    my $bot      = shift @_;
211    my $since_id = shift @_ || 1;
212   
213    my $res;
214    eval {
215        $res = $bot->mentions(
216            {
217                since_id    => $since_id,
218            }
219        );
220        VERBOSE and warn Dumper($res);
221    };
222    if ($@) {
223        evalrescue($@);
224    }
225   
226    my $ids = {};
227    if ($res && @{$res}) {
228        $ids = {
229            map {
230                $_->{id} => {
231                    date        => str2time($_->{created_at}),
232                    screen_name => $_->{user}{screen_name},
233                    status_id   => $_->{id},
234                    text        => $_->{text},
235                    type        => 'mention',
236                    user_id     => $_->{user}{id},
237                }
238            } @{$res}
239        };
240    }
241   
242    DEBUG and warn "mentions result => ", Dumper($ids);
243    return $ids;
244}
245
246sub follower_search {
247    # search follower's tweets containing keywords
248    #   param   => Net::Twitter::Lite object, keyword string, since_id
249    #   ret     => HashRef of status_id (timeline order is destroyed)
250    #               or undef (none is found)
251   
252    my $bot      = shift @_;
253    my $keyword  = shift @_;
254    my $since_id = shift @_ || 1;
255   
256    my $tweets = or_search($bot, [ $keyword ], $since_id);
257    if (! $tweets) {
258        return {};
259    }
260   
261    my $followers;
262    eval {
263        $followers = $bot->followers_ids();
264        VERBOSE and warn Dumper($followers);
265    };
266    if ($@) {
267        evalrescue($@);
268    }
269   
270    my $ids = {};
271    foreach my $status_id (keys %$tweets) {
272        foreach my $user_id (@$followers) {
273            if ($tweets->{$status_id}{user_id} == $user_id) {
274                $ids->{$status_id} = $tweets->{$status_id};
275                last;
276            }
277        }
278    }
279   
280    DEBUG and warn "search result in followers => ", Dumper($ids);
281    return $ids;
282}
283
284sub evalrescue {
285    # output error message at eval error
286   
287    use Scalar::Util qw(blessed);
288   
289    if (blessed $@ && $@->isa('Net::Twitter::Lite::Error')) {
290        warn $@->error;
291        if ($@->twitter_error) {
292            my %twitter_error = %{$@->twitter_error};
293            map {
294                $twitter_error{"$_ => "} = $twitter_error{$_} . "\n";
295                delete $twitter_error{$_}
296            } keys %twitter_error;
297            warn join("", %twitter_error);
298        }
299    }
300    else {
301        warn $@;
302    }
303}
Note: See TracBrowser for help on using the repository browser.