source: lib/BarnOwl/Module/Twitter/Handle.pm @ 7424a5b

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