source: lib/BarnOwl/Module/Twitter/Handle.pm @ a3874bce

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