source: lib/BarnOwl/Module/Twitter/Handle.pm @ 8462b38

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