source: lib/BarnOwl/Module/Twitter/Handle.pm @ 159aaad

release-1.7release-1.8release-1.9
Last change on this file since 159aaad was 159aaad, checked in by Nelson Elhage <nelhage@mit.edu>, 12 years ago
Multiple account support Accounts are specified as a list of hashes in the ~/.owl/twitter file. Adds 'poll_for_tweets', 'poll_for_dms', 'publish_tweets', 'default_sender', and 'account_nickname' options to the twitter account hashes. They do about what they say on the tin. Add arguments to :twitter, :twitter-direct, and :twitter-atreply to specify the service to use (by nickname), with sane defaults, plus documentation.
  • Property mode set to 100644
File size: 6.4 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;
21sub fail {
22    my $self = shift;
23    my $msg = shift;
24    undef $self->{twitter};
25    BarnOwl::admin_message('Twitter Error', $msg);
26    die("Twitter Error: $msg\n");
27}
28
29my $use_reply_to = 0;
30if($Net::Twitter::VERSION >= 2.06) {
31    $use_reply_to = 1;
32}
33
34sub new {
35
36    my $class = shift;
37    my $cfg = shift;
38
39    my %obj = (
40        'user' => undef,
41        'cfg'  => $cfg,
42        'twitter' => undef,
43        'last_poll' => 0,
44        'last_direct_poll' => 0,
45        'last_id' => undef,
46        'last_direct' => undef,
47    );
48
49    my %twitter_args = @_;
50
51    $obj{twitter}  = Net::Twitter->new(%twitter_args);
52
53    if(!defined($obj{twitter}->verify_credentials())) {
54        fail("Invalid twitter credentials");
55    }
56
57    unless(defined($obj{last_id})) {
58        eval {
59            $obj{last_id} = $obj{twitter}->friends_timeline({count => 1})->[0]{id};
60        };
61        $obj{last_id} = 0 unless defined($obj{last_id});
62    }
63
64    unless(defined($obj{last_direct})) {
65        eval {
66            $obj{last_direct} = $obj{twitter}->direct_messages()->[0]{id};
67        };
68        $obj{last_direct} = 0 unless defined($obj{last_direct});
69    }
70
71    eval {
72        $obj{twitter}->{ua}->timeout(1);
73    };
74
75    return bless {%obj}, $class;
76
77}
78
79sub twitter_error {
80    my $self = shift;
81
82    my $ratelimit = $self->{twitter}->rate_limit_status;
83    unless(defined($ratelimit) && ref($ratelimit) eq 'HASH') {
84        # Twitter's just sucking, sleep for 5 minutes
85        $self->{last_direct_poll} = $self->{last_poll} = time + 60*5;
86        # die("Twitter seems to be having problems.\n");
87        return;
88    }
89    if(exists($ratelimit->{remaining_hits})
90       && $ratelimit->{remaining_hits} <= 0) {
91        $self->{last_direct_poll} = $self->{last_poll} = $ratelimit->{reset_time_in_seconds};
92        die("Twitter: ratelimited until " . $ratelimit->{reset_time} . "\n");
93    } elsif(exists($ratelimit->{error})) {
94        die("Twitter: ". $ratelimit->{error} . "\n");
95        $self->{last_direct_poll} = $self->{last_poll} = time + 60*20;
96    }
97}
98
99sub poll_twitter {
100    my $self = shift;
101
102    return unless ( time - $self->{last_poll} ) >= 60;
103    $self->{last_poll} = time;
104    return unless BarnOwl::getvar('twitter:poll') eq 'on';
105
106    my $timeline = $self->{twitter}->friends_timeline( { since_id => $self->{last_id} } );
107    unless(defined($timeline) && ref($timeline) eq 'ARRAY') {
108        $self->twitter_error();
109        return;
110    };
111    if ( scalar @$timeline ) {
112        for my $tweet ( reverse @$timeline ) {
113            if ( $tweet->{id} <= $self->{last_id} ) {
114                next;
115            }
116            my $msg = BarnOwl::Message->new(
117                type      => 'Twitter',
118                sender    => $tweet->{user}{screen_name},
119                recipient => $self->{cfg}->{user} || $self->{user},
120                direction => 'in',
121                source    => decode_entities($tweet->{source}),
122                location  => decode_entities($tweet->{user}{location}||""),
123                body      => decode_entities($tweet->{text}),
124                status_id => $tweet->{id},
125                service   => $self->{cfg}->{service},
126                account   => $self->{cfg}->{account_nickname},
127               );
128            BarnOwl::queue_message($msg);
129        }
130        $self->{last_id} = $timeline->[0]{id} if $timeline->[0]{id} > $self->{last_id};
131    } else {
132        # BarnOwl::message("No new tweets...");
133    }
134}
135
136sub poll_direct {
137    my $self = shift;
138
139    return unless ( time - $self->{last_direct_poll}) >= 120;
140    $self->{last_direct_poll} = time;
141    return unless BarnOwl::getvar('twitter:poll') eq 'on';
142
143    my $direct = $self->{twitter}->direct_messages( { since_id => $self->{last_direct} } );
144    unless(defined($direct) && ref($direct) eq 'ARRAY') {
145        $self->twitter_error();
146        return;
147    };
148    if ( scalar @$direct ) {
149        for my $tweet ( reverse @$direct ) {
150            if ( $tweet->{id} <= $self->{last_direct} ) {
151                next;
152            }
153            my $msg = BarnOwl::Message->new(
154                type      => 'Twitter',
155                sender    => $tweet->{sender}{screen_name},
156                recipient => $self->{cfg}->{user} || $self->{user},
157                direction => 'in',
158                location  => decode_entities($tweet->{sender}{location}||""),
159                body      => decode_entities($tweet->{text}),
160                isprivate => 'true',
161                service   => $self->{cfg}->{service},
162                account   => $self->{cfg}->{account_nickname},
163               );
164            BarnOwl::queue_message($msg);
165        }
166        $self->{last_direct} = $direct->[0]{id} if $direct->[0]{id} > $self->{last_direct};
167    } else {
168        # BarnOwl::message("No new tweets...");
169    }
170}
171
172sub twitter {
173    my $self = shift;
174
175    my $msg = shift;
176    my $reply_to = shift;
177
178    if($msg =~ m{\Ad\s+([^\s])+(.*)}sm) {
179        $self->twitter_direct($1, $2);
180    } elsif(defined $self->{twitter}) {
181        if($use_reply_to && defined($reply_to)) {
182            $self->{twitter}->update({
183                status => $msg,
184                in_reply_to_status_id => $reply_to
185               });
186        } else {
187            $self->{twitter}->update($msg);
188        }
189    }
190}
191
192sub twitter_direct {
193    my $self = shift;
194
195    my $who = shift;
196    my $msg = shift;
197    if(defined $self->{twitter}) {
198        $self->{twitter}->new_direct_message({
199            user => $who,
200            text => $msg
201           });
202        if(BarnOwl::getvar("displayoutgoing") eq 'on') {
203            my $tweet = BarnOwl::Message->new(
204                type      => 'Twitter',
205                sender    => $self->{cfg}->{user} || $self->{user},
206                recipient => $who, 
207                direction => 'out',
208                body      => $msg,
209                isprivate => 'true',
210                service   => $self->{cfg}->{service},
211               );
212            BarnOwl::queue_message($tweet);
213        }
214    }
215}
216
217sub twitter_atreply {
218    my $self = shift;
219
220    my $to  = shift;
221    my $id  = shift;
222    my $msg = shift;
223    if(defined($id)) {
224        $self->twitter("@".$to." ".$msg, $id);
225    } else {
226        $self->twitter("@".$to." ".$msg);
227    }
228}
229
2301;
Note: See TracBrowser for help on using the repository browser.