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>, 12 years ago
Weakrefs
  • Property mode set to 100644
File size: 8.6 KB
Line 
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
16use Net::Twitter::Lite;
17use HTML::Entities;
18
19use Scalar::Util qw(weaken);
20
21use BarnOwl;
22use BarnOwl::Message::Twitter;
23
24sub fail {
25    my $self = shift;
26    my $msg = shift;
27    undef $self->{twitter};
28    my $nickname = $self->{cfg}->{account_nickname} || "";
29    die("[Twitter $nickname] Error: $msg\n");
30}
31
32sub new {
33    my $class = shift;
34    my $cfg = shift;
35
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
48    $cfg = {
49        account_nickname => '',
50        default          => 0,
51        poll_for_tweets  => 1,
52        poll_for_dms     => 1,
53        publish_tweets   => 0,
54        show_mentions    => 1,
55        %$cfg
56       };
57
58    my $self = {
59        'cfg'  => $cfg,
60        'twitter' => undef,
61        'last_id' => undef,
62        'last_direct' => undef,
63        'timer'        => undef,
64        'direct_timer' => undef
65    };
66
67    bless($self, $class);
68
69    my %twitter_args = @_;
70
71    $self->{twitter}  = Net::Twitter::Lite->new(%twitter_args);
72
73    my $timeline = eval { $self->{twitter}->friends_timeline({count => 1}) };
74    warn "$@" if $@;
75
76    if(!defined($timeline)) {
77        $self->fail("Invalid credentials");
78    }
79
80    eval {
81        $self->{last_id} = $timeline->[0]{id};
82    };
83    $self->{last_id} = 1 unless defined($self->{last_id});
84
85    eval {
86        $self->{last_direct} = $self->{twitter}->direct_messages()->[0]{id};
87    };
88    warn "$@" if $@;
89    $self->{last_direct} = 1 unless defined($self->{last_direct});
90
91    eval {
92        $self->{twitter}->{ua}->timeout(1);
93    };
94    warn "$@" if $@;
95
96    $self->sleep(0);
97
98    return $self;
99}
100
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
111    my $weak = weaken($self);
112
113    if($self->{cfg}->{poll_for_tweets}) {
114        $self->{timer} = BarnOwl::Timer->new({
115            after    => $delay,
116            interval => 60,
117            cb       => sub { $weak->poll_twitter if $weak }
118           });
119    }
120
121    if($self->{cfg}->{poll_for_dms}) {
122        $self->{direct_timer} = BarnOwl::Timer->new({
123            after    => $delay,
124            interval => 120,
125            cb       => sub { $weak->poll_direct if $weak }
126           });
127    }
128}
129
130sub twitter_error {
131    my $self = shift;
132
133    my $ratelimit = eval { $self->{twitter}->rate_limit_status };
134    warn "$@" if $@;
135    unless(defined($ratelimit) && ref($ratelimit) eq 'HASH') {
136        # Twitter's probably just sucking, try again later.
137        $self->sleep(5*60);
138        return;
139    }
140
141    if(exists($ratelimit->{remaining_hits})
142       && $ratelimit->{remaining_hits} <= 0) {
143        $self->sleep($ratelimit->{reset_time_in_seconds} - time + 60);
144        die("Twitter: ratelimited until " . $ratelimit->{reset_time} . "\n");
145    } elsif(exists($ratelimit->{error})) {
146        $self->sleep(60*20);
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
156    BarnOwl::debug("Polling " . $self->{cfg}->{account_nickname});
157
158    my $timeline = eval { $self->{twitter}->friends_timeline( { since_id => $self->{last_id} } ) };
159    warn "$@" if $@;
160    unless(defined($timeline) && ref($timeline) eq 'ARRAY') {
161        $self->twitter_error();
162        return;
163    };
164
165    if ($self->{cfg}->{show_mentions}) {
166        my $mentions = eval { $self->{twitter}->mentions( { since_id => $self->{last_id} } ) };
167        warn "$@" if $@;
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
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
209    BarnOwl::debug("Polling direct for " . $self->{cfg}->{account_nickname});
210
211    my $direct = eval { $self->{twitter}->direct_messages( { since_id => $self->{last_direct} } ) };
212    warn "$@" if $@;
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}) {
250        if(defined($reply_to)) {
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
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
327sub nickname {
328    my $self = shift;
329    return $self->{cfg}->{account_nickname};
330}
331
3321;
Note: See TracBrowser for help on using the repository browser.