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

release-1.7release-1.8release-1.9
Last change on this file since efcd223 was efcd223, checked in by Nelson Elhage <nelhage@mit.edu>, 12 years ago
Some code cleanup. Refactor finding a named account into a find_account function, and set defaults for config options initially, which lets us remove 'exists' calls everywhere.
  • Property mode set to 100644
File size: 6.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;
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    if(!defined($self->{twitter}->verify_credentials())) {
64        $self->fail("Invalid credentials");
65    }
66
67    unless(defined($self->{last_id})) {
68        eval {
69            $self->{last_id} = $self->{twitter}->friends_timeline({count => 1})->[0]{id};
70        };
71        $self->{last_id} = 1 unless defined($self->{last_id});
72    }
73
74    unless(defined($self->{last_direct})) {
75        eval {
76            $self->{last_direct} = $self->{twitter}->direct_messages()->[0]{id};
77        };
78        $self->{last_direct} = 1 unless defined($self->{last_direct});
79    }
80
81    eval {
82        $self->{twitter}->{ua}->timeout(1);
83    };
84
85    return $self;
86}
87
88sub twitter_error {
89    my $self = shift;
90
91    my $ratelimit = $self->{twitter}->rate_limit_status;
92    unless(defined($ratelimit) && ref($ratelimit) eq 'HASH') {
93        # Twitter's just sucking, sleep for 5 minutes
94        $self->{last_direct_poll} = $self->{last_poll} = time + 60*5;
95        # die("Twitter seems to be having problems.\n");
96        return;
97    }
98    if(exists($ratelimit->{remaining_hits})
99       && $ratelimit->{remaining_hits} <= 0) {
100        $self->{last_direct_poll} = $self->{last_poll} = $ratelimit->{reset_time_in_seconds};
101        die("Twitter: ratelimited until " . $ratelimit->{reset_time} . "\n");
102    } elsif(exists($ratelimit->{error})) {
103        die("Twitter: ". $ratelimit->{error} . "\n");
104        $self->{last_direct_poll} = $self->{last_poll} = time + 60*20;
105    }
106}
107
108sub poll_twitter {
109    my $self = shift;
110
111    return unless ( time - $self->{last_poll} ) >= 60;
112    $self->{last_poll} = time;
113    return unless BarnOwl::getvar('twitter:poll') eq 'on';
114
115    my $timeline = $self->{twitter}->friends_timeline( { since_id => $self->{last_id} } );
116    unless(defined($timeline) && ref($timeline) eq 'ARRAY') {
117        $self->twitter_error();
118        return;
119    };
120    if ( scalar @$timeline ) {
121        for my $tweet ( reverse @$timeline ) {
122            if ( $tweet->{id} <= $self->{last_id} ) {
123                next;
124            }
125            my $msg = BarnOwl::Message->new(
126                type      => 'Twitter',
127                sender    => $tweet->{user}{screen_name},
128                recipient => $self->{cfg}->{user} || $self->{user},
129                direction => 'in',
130                source    => decode_entities($tweet->{source}),
131                location  => decode_entities($tweet->{user}{location}||""),
132                body      => decode_entities($tweet->{text}),
133                status_id => $tweet->{id},
134                service   => $self->{cfg}->{service},
135                account   => $self->{cfg}->{account_nickname},
136               );
137            BarnOwl::queue_message($msg);
138        }
139        $self->{last_id} = $timeline->[0]{id} if $timeline->[0]{id} > $self->{last_id};
140    } else {
141        # BarnOwl::message("No new tweets...");
142    }
143}
144
145sub poll_direct {
146    my $self = shift;
147
148    return unless ( time - $self->{last_direct_poll}) >= 120;
149    $self->{last_direct_poll} = time;
150    return unless BarnOwl::getvar('twitter:poll') eq 'on';
151
152    my $direct = $self->{twitter}->direct_messages( { since_id => $self->{last_direct} } );
153    unless(defined($direct) && ref($direct) eq 'ARRAY') {
154        $self->twitter_error();
155        return;
156    };
157    if ( scalar @$direct ) {
158        for my $tweet ( reverse @$direct ) {
159            if ( $tweet->{id} <= $self->{last_direct} ) {
160                next;
161            }
162            my $msg = BarnOwl::Message->new(
163                type      => 'Twitter',
164                sender    => $tweet->{sender}{screen_name},
165                recipient => $self->{cfg}->{user} || $self->{user},
166                direction => 'in',
167                location  => decode_entities($tweet->{sender}{location}||""),
168                body      => decode_entities($tweet->{text}),
169                isprivate => 'true',
170                service   => $self->{cfg}->{service},
171                account   => $self->{cfg}->{account_nickname},
172               );
173            BarnOwl::queue_message($msg);
174        }
175        $self->{last_direct} = $direct->[0]{id} if $direct->[0]{id} > $self->{last_direct};
176    } else {
177        # BarnOwl::message("No new tweets...");
178    }
179}
180
181sub twitter {
182    my $self = shift;
183
184    my $msg = shift;
185    my $reply_to = shift;
186
187    if($msg =~ m{\Ad\s+([^\s])+(.*)}sm) {
188        $self->twitter_direct($1, $2);
189    } elsif(defined $self->{twitter}) {
190        if($use_reply_to && defined($reply_to)) {
191            $self->{twitter}->update({
192                status => $msg,
193                in_reply_to_status_id => $reply_to
194               });
195        } else {
196            $self->{twitter}->update($msg);
197        }
198    }
199}
200
201sub twitter_direct {
202    my $self = shift;
203
204    my $who = shift;
205    my $msg = shift;
206    if(defined $self->{twitter}) {
207        $self->{twitter}->new_direct_message({
208            user => $who,
209            text => $msg
210           });
211        if(BarnOwl::getvar("displayoutgoing") eq 'on') {
212            my $tweet = BarnOwl::Message->new(
213                type      => 'Twitter',
214                sender    => $self->{cfg}->{user} || $self->{user},
215                recipient => $who, 
216                direction => 'out',
217                body      => $msg,
218                isprivate => 'true',
219                service   => $self->{cfg}->{service},
220               );
221            BarnOwl::queue_message($tweet);
222        }
223    }
224}
225
226sub twitter_atreply {
227    my $self = shift;
228
229    my $to  = shift;
230    my $id  = shift;
231    my $msg = shift;
232    if(defined($id)) {
233        $self->twitter("@".$to." ".$msg, $id);
234    } else {
235        $self->twitter("@".$to." ".$msg);
236    }
237}
238
2391;
Note: See TracBrowser for help on using the repository browser.