source: lab/trunk/Dev/twitter/twitterbot.pl @ 104

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