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

release-1.10release-1.7release-1.8release-1.9
Last change on this file since a8a0b0a was a8a0b0a, checked in by Nelson Elhage <nelhage@mit.edu>, 15 years ago
Don't use verify_credentials. Twitter apparently ratelimits verify_credentials far more than other requests, so just ask for our timeline to verify credentials, instead of relying on verify_credentials.
  • Property mode set to 100644
File size: 6.5 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;
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
30my $use_reply_to = 0;
31if($Net::Twitter::VERSION >= 2.06) {
32    $use_reply_to = 1;
33}
34
35sub new {
36    my $class = shift;
37    my $cfg = shift;
38
39    $cfg = {
40        account_nickname => '',
41        default_sender   => 0,
42        poll_for_tweets  => 1,
43        poll_for_dms     => 1,
44        publish_tweets   => 1,
45        %$cfg
46       };
47
48    my $self = {
49        'cfg'  => $cfg,
50        'twitter' => undef,
51        'last_poll' => 0,
52        'last_direct_poll' => 0,
53        'last_id' => undef,
54        'last_direct' => undef,
55    };
56
57    bless($self, $class);
58
59    my %twitter_args = @_;
60
61    $self->{twitter}  = Net::Twitter->new(%twitter_args);
62
63    my $timeline = $self->{twitter}->friends_timeline({count => 1});
64
65    if(!defined($timeline)) {
66        $self->fail("Invalid credentials");
67    }
68
69    eval {
70        $self->{last_id} = $timeline->[0]{id};
71    };
72    $self->{last_id} = 1 unless defined($self->{last_id});
73
74    eval {
75        $self->{last_direct} = $self->{twitter}->direct_messages()->[0]{id};
76    };
77    $self->{last_direct} = 1 unless defined($self->{last_direct});
78
79    eval {
80        $self->{twitter}->{ua}->timeout(1);
81    };
82
83    return $self;
84}
85
86sub twitter_error {
87    my $self = shift;
88
89    my $ratelimit = $self->{twitter}->rate_limit_status;
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 = $self->{twitter}->friends_timeline( { since_id => $self->{last_id} } );
114    unless(defined($timeline) && ref($timeline) eq 'ARRAY') {
115        $self->twitter_error();
116        return;
117    };
118    if ( scalar @$timeline ) {
119        for my $tweet ( reverse @$timeline ) {
120            if ( $tweet->{id} <= $self->{last_id} ) {
121                next;
122            }
123            my $msg = BarnOwl::Message->new(
124                type      => 'Twitter',
125                sender    => $tweet->{user}{screen_name},
126                recipient => $self->{cfg}->{user} || $self->{user},
127                direction => 'in',
128                source    => decode_entities($tweet->{source}),
129                location  => decode_entities($tweet->{user}{location}||""),
130                body      => decode_entities($tweet->{text}),
131                status_id => $tweet->{id},
132                service   => $self->{cfg}->{service},
133                account   => $self->{cfg}->{account_nickname},
134               );
135            BarnOwl::queue_message($msg);
136        }
137        $self->{last_id} = $timeline->[0]{id} if $timeline->[0]{id} > $self->{last_id};
138    } else {
139        # BarnOwl::message("No new tweets...");
140    }
141}
142
143sub poll_direct {
144    my $self = shift;
145
146    return unless ( time - $self->{last_direct_poll}) >= 120;
147    $self->{last_direct_poll} = time;
148    return unless BarnOwl::getvar('twitter:poll') eq 'on';
149
150    my $direct = $self->{twitter}->direct_messages( { since_id => $self->{last_direct} } );
151    unless(defined($direct) && ref($direct) eq 'ARRAY') {
152        $self->twitter_error();
153        return;
154    };
155    if ( scalar @$direct ) {
156        for my $tweet ( reverse @$direct ) {
157            if ( $tweet->{id} <= $self->{last_direct} ) {
158                next;
159            }
160            my $msg = BarnOwl::Message->new(
161                type      => 'Twitter',
162                sender    => $tweet->{sender}{screen_name},
163                recipient => $self->{cfg}->{user} || $self->{user},
164                direction => 'in',
165                location  => decode_entities($tweet->{sender}{location}||""),
166                body      => decode_entities($tweet->{text}),
167                isprivate => 'true',
168                service   => $self->{cfg}->{service},
169                account   => $self->{cfg}->{account_nickname},
170               );
171            BarnOwl::queue_message($msg);
172        }
173        $self->{last_direct} = $direct->[0]{id} if $direct->[0]{id} > $self->{last_direct};
174    } else {
175        # BarnOwl::message("No new tweets...");
176    }
177}
178
179sub twitter {
180    my $self = shift;
181
182    my $msg = shift;
183    my $reply_to = shift;
184
185    if($msg =~ m{\Ad\s+([^\s])+(.*)}sm) {
186        $self->twitter_direct($1, $2);
187    } elsif(defined $self->{twitter}) {
188        if($use_reply_to && defined($reply_to)) {
189            $self->{twitter}->update({
190                status => $msg,
191                in_reply_to_status_id => $reply_to
192               });
193        } else {
194            $self->{twitter}->update($msg);
195        }
196    }
197}
198
199sub twitter_direct {
200    my $self = shift;
201
202    my $who = shift;
203    my $msg = shift;
204    if(defined $self->{twitter}) {
205        $self->{twitter}->new_direct_message({
206            user => $who,
207            text => $msg
208           });
209        if(BarnOwl::getvar("displayoutgoing") eq 'on') {
210            my $tweet = BarnOwl::Message->new(
211                type      => 'Twitter',
212                sender    => $self->{cfg}->{user} || $self->{user},
213                recipient => $who, 
214                direction => 'out',
215                body      => $msg,
216                isprivate => 'true',
217                service   => $self->{cfg}->{service},
218               );
219            BarnOwl::queue_message($tweet);
220        }
221    }
222}
223
224sub twitter_atreply {
225    my $self = shift;
226
227    my $to  = shift;
228    my $id  = shift;
229    my $msg = shift;
230    if(defined($id)) {
231        $self->twitter("@".$to." ".$msg, $id);
232    } else {
233        $self->twitter("@".$to." ".$msg);
234    }
235}
236
2371;
Note: See TracBrowser for help on using the repository browser.