source: lib/BarnOwl/Module/Twitter/Handle.pm @ 5da6ed8

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