source: lib/BarnOwl/Module/Twitter/Handle.pm @ 36546fa

release-1.10release-1.7release-1.8release-1.9
Last change on this file since 36546fa was 36546fa, checked in by Nelson Elhage <nelhage@mit.edu>, 15 years ago
Replace the mainloop hook with BarnOwl's built-in timer support.
  • Property mode set to 100644
File size: 8.5 KB
RevLine 
[159aaad]1use warnings;
2use strict;
3
4=head1 NAME
5
6BarnOwl::Module::Twitter::Handle
7
8=head1 DESCRIPTION
9
10Contains everything needed to send and receive messages from a Twitter-like service.
11
12=cut
13
14package BarnOwl::Module::Twitter::Handle;
15
[0b13bbc]16use Net::Twitter::Lite;
[159aaad]17use HTML::Entities;
18
19use BarnOwl;
20use BarnOwl::Message::Twitter;
[7430aa4]21
[159aaad]22sub fail {
23    my $self = shift;
24    my $msg = shift;
25    undef $self->{twitter};
[7430aa4]26    my $nickname = $self->{cfg}->{account_nickname} || "";
27    die("[Twitter $nickname] Error: $msg\n");
[159aaad]28}
29
30sub new {
31    my $class = shift;
32    my $cfg = shift;
33
[385dd69]34    my $val;
35
36    if(!exists $cfg->{default} &&
37       defined($val = delete $cfg->{default_sender})) {
38        $cfg->{default} = $val;
39    }
40
41    if(!exists $cfg->{show_mentions} &&
42       defined($val = delete $cfg->{show_unsubscribed_replies})) {
43        $cfg->{show_mentions} = $val;
44    }
45
[efcd223]46    $cfg = {
47        account_nickname => '',
[385dd69]48        default          => 0,
[efcd223]49        poll_for_tweets  => 1,
50        poll_for_dms     => 1,
[26f9e2e]51        publish_tweets   => 0,
[385dd69]52        show_mentions    => 1,
[efcd223]53        %$cfg
54       };
55
[7430aa4]56    my $self = {
[159aaad]57        'cfg'  => $cfg,
58        'twitter' => undef,
59        'last_id' => undef,
60        'last_direct' => undef,
[36546fa]61        'timer'        => undef,
62        'direct_timer' => undef
[7430aa4]63    };
64
65    bless($self, $class);
[159aaad]66
67    my %twitter_args = @_;
68
[0b13bbc]69    $self->{twitter}  = Net::Twitter::Lite->new(%twitter_args);
[159aaad]70
[82e0f26]71    my $timeline = eval { $self->{twitter}->friends_timeline({count => 1}) };
72    warn "$@" if $@;
[a8a0b0a]73
74    if(!defined($timeline)) {
[7430aa4]75        $self->fail("Invalid credentials");
[159aaad]76    }
77
[a8a0b0a]78    eval {
79        $self->{last_id} = $timeline->[0]{id};
80    };
81    $self->{last_id} = 1 unless defined($self->{last_id});
[159aaad]82
[a8a0b0a]83    eval {
84        $self->{last_direct} = $self->{twitter}->direct_messages()->[0]{id};
85    };
[82e0f26]86    warn "$@" if $@;
[a8a0b0a]87    $self->{last_direct} = 1 unless defined($self->{last_direct});
[159aaad]88
89    eval {
[7430aa4]90        $self->{twitter}->{ua}->timeout(1);
[159aaad]91    };
[82e0f26]92    warn "$@" if $@;
[159aaad]93
[36546fa]94    $self->sleep(0);
95
[7430aa4]96    return $self;
[159aaad]97}
98
[36546fa]99=head2 sleep SECONDS
100
101Stop polling Twitter for SECONDS seconds.
102
103=cut
104
105sub sleep {
106    my $self  = shift;
107    my $delay = shift;
108
109    if($self->{cfg}->{poll_for_tweets}) {
110        $self->{timer} = BarnOwl::Timer->new({
111            after    => $delay,
112            interval => 60,
113            cb       => sub { $self->poll_twitter }
114           });
115    }
116
117    if($self->{cfg}->{poll_for_dms}) {
118        $self->{direct_timer} = BarnOwl::Timer->new({
119            after    => $delay,
120            interval => 120,
121            cb       => sub { $self->poll_direct }
122           });
123    }
124}
125
[159aaad]126sub twitter_error {
127    my $self = shift;
128
[82e0f26]129    my $ratelimit = eval { $self->{twitter}->rate_limit_status };
130    warn "$@" if $@;
[159aaad]131    unless(defined($ratelimit) && ref($ratelimit) eq 'HASH') {
[36546fa]132        # Twitter's probably just sucking, try again later.
133        $self->sleep(5);
[159aaad]134        return;
135    }
[36546fa]136
[159aaad]137    if(exists($ratelimit->{remaining_hits})
138       && $ratelimit->{remaining_hits} <= 0) {
[36546fa]139        $self->sleep(time - $ratelimit->{reset_time_in_seconds} + 60);
[159aaad]140        die("Twitter: ratelimited until " . $ratelimit->{reset_time} . "\n");
141    } elsif(exists($ratelimit->{error})) {
[36546fa]142        $self->sleep(60*20);
[159aaad]143        die("Twitter: ". $ratelimit->{error} . "\n");
144    }
145}
146
147sub poll_twitter {
148    my $self = shift;
149
150    return unless BarnOwl::getvar('twitter:poll') eq 'on';
151
[36546fa]152    BarnOwl::debug("Polling " . $self->{cfg}->{account_nickname});
153
[82e0f26]154    my $timeline = eval { $self->{twitter}->friends_timeline( { since_id => $self->{last_id} } ) };
155    warn "$@" if $@;
[159aaad]156    unless(defined($timeline) && ref($timeline) eq 'ARRAY') {
157        $self->twitter_error();
158        return;
159    };
[5da6ed8]160
[385dd69]161    if ($self->{cfg}->{show_mentions}) {
[82e0f26]162        my $mentions = eval { $self->{twitter}->mentions( { since_id => $self->{last_id} } ) };
163        warn "$@" if $@;
[5da6ed8]164        unless (defined($mentions) && ref($mentions) eq 'ARRAY') {
165            $self->twitter_error();
166            return;
167        };
168        #combine, sort by id, and uniq
169        push @$timeline, @$mentions;
170        @$timeline = sort { $b->{id} <=> $a->{id} } @$timeline;
171        my $prev = { id => 0 };
172        @$timeline = grep($_->{id} != $prev->{id} && (($prev) = $_), @$timeline);
173    }
174
[159aaad]175    if ( scalar @$timeline ) {
176        for my $tweet ( reverse @$timeline ) {
177            if ( $tweet->{id} <= $self->{last_id} ) {
178                next;
179            }
180            my $msg = BarnOwl::Message->new(
181                type      => 'Twitter',
182                sender    => $tweet->{user}{screen_name},
183                recipient => $self->{cfg}->{user} || $self->{user},
184                direction => 'in',
185                source    => decode_entities($tweet->{source}),
186                location  => decode_entities($tweet->{user}{location}||""),
187                body      => decode_entities($tweet->{text}),
188                status_id => $tweet->{id},
189                service   => $self->{cfg}->{service},
190                account   => $self->{cfg}->{account_nickname},
191               );
192            BarnOwl::queue_message($msg);
193        }
194        $self->{last_id} = $timeline->[0]{id} if $timeline->[0]{id} > $self->{last_id};
195    } else {
196        # BarnOwl::message("No new tweets...");
197    }
198}
199
200sub poll_direct {
201    my $self = shift;
202
203    return unless BarnOwl::getvar('twitter:poll') eq 'on';
204
[36546fa]205    BarnOwl::debug("Polling direct for " . $self->{cfg}->{account_nickname});
206
[82e0f26]207    my $direct = eval { $self->{twitter}->direct_messages( { since_id => $self->{last_direct} } ) };
208    warn "$@" if $@;
[159aaad]209    unless(defined($direct) && ref($direct) eq 'ARRAY') {
210        $self->twitter_error();
211        return;
212    };
213    if ( scalar @$direct ) {
214        for my $tweet ( reverse @$direct ) {
215            if ( $tweet->{id} <= $self->{last_direct} ) {
216                next;
217            }
218            my $msg = BarnOwl::Message->new(
219                type      => 'Twitter',
220                sender    => $tweet->{sender}{screen_name},
221                recipient => $self->{cfg}->{user} || $self->{user},
222                direction => 'in',
223                location  => decode_entities($tweet->{sender}{location}||""),
224                body      => decode_entities($tweet->{text}),
225                isprivate => 'true',
226                service   => $self->{cfg}->{service},
227                account   => $self->{cfg}->{account_nickname},
228               );
229            BarnOwl::queue_message($msg);
230        }
231        $self->{last_direct} = $direct->[0]{id} if $direct->[0]{id} > $self->{last_direct};
232    } else {
233        # BarnOwl::message("No new tweets...");
234    }
235}
236
237sub twitter {
238    my $self = shift;
239
240    my $msg = shift;
241    my $reply_to = shift;
242
243    if($msg =~ m{\Ad\s+([^\s])+(.*)}sm) {
244        $self->twitter_direct($1, $2);
245    } elsif(defined $self->{twitter}) {
[0b13bbc]246        if(defined($reply_to)) {
[159aaad]247            $self->{twitter}->update({
248                status => $msg,
249                in_reply_to_status_id => $reply_to
250               });
251        } else {
252            $self->{twitter}->update($msg);
253        }
254    }
255}
256
257sub twitter_direct {
258    my $self = shift;
259
260    my $who = shift;
261    my $msg = shift;
262    if(defined $self->{twitter}) {
263        $self->{twitter}->new_direct_message({
264            user => $who,
265            text => $msg
266           });
267        if(BarnOwl::getvar("displayoutgoing") eq 'on') {
268            my $tweet = BarnOwl::Message->new(
269                type      => 'Twitter',
270                sender    => $self->{cfg}->{user} || $self->{user},
271                recipient => $who, 
272                direction => 'out',
273                body      => $msg,
274                isprivate => 'true',
275                service   => $self->{cfg}->{service},
276               );
277            BarnOwl::queue_message($tweet);
278        }
279    }
280}
281
282sub twitter_atreply {
283    my $self = shift;
284
285    my $to  = shift;
286    my $id  = shift;
287    my $msg = shift;
288    if(defined($id)) {
289        $self->twitter("@".$to." ".$msg, $id);
290    } else {
291        $self->twitter("@".$to." ".$msg);
292    }
293}
294
[513da71]295sub twitter_follow {
296    my $self = shift;
297
298    my $who = shift;
299
300    my $user = $self->{twitter}->create_friend($who);
301    # returns a string on error
302    if (defined $user && !ref $user) {
303        BarnOwl::message($user);
304    } else {
305        BarnOwl::message("Following " . $who);
306    }
307}
308
309sub twitter_unfollow {
310    my $self = shift;
311
312    my $who = shift;
313
314    my $user = $self->{twitter}->destroy_friend($who);
315    # returns a string on error
316    if (defined $user && !ref $user) {
317        BarnOwl::message($user);
318    } else {
319        BarnOwl::message("No longer following " . $who);
320    }
321}
322
[8462b38]323sub nickname {
324    my $self = shift;
325    return $self->{cfg}->{account_nickname};
326}
327
[159aaad]3281;
Note: See TracBrowser for help on using the repository browser.