File Coverage

blib/lib/AnyEvent/Superfeedr.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package AnyEvent::Superfeedr;
2              
3 1     1   25630 use strict;
  1         3  
  1         43  
4 1     1   6 use warnings;
  1         1  
  1         32  
5 1     1   26 use 5.008_001;
  1         8  
  1         56  
6              
7             our $VERSION = '0.04';
8 1     1   5 use Carp;
  1         1  
  1         83  
9              
10 1     1   13587 use AnyEvent;
  1         10678  
  1         47  
11 1     1   990 use AnyEvent::Superfeedr::Notification;
  0            
  0            
12             use AnyEvent::XMPP::Client;
13             use AnyEvent::XMPP::Ext::Superfeedr;
14             use AnyEvent::XMPP::Ext::Pubsub;
15             use XML::Atom::Entry;
16             use Scalar::Util();
17             use URI::Escape();
18              
19             our $SERVICE = 'firehoser.superfeedr.com';
20              
21             use constant SUBSCRIBE_CHUNK_SIZE => 30;
22              
23             # TODO:
24             # debug
25             # tests? worthwhile?
26             #
27             # Also, maybe more direct callbacks for sub/unsub
28              
29             sub new {
30             my $class = shift;
31             my %param = @_;
32              
33             my %filtered;
34             for ( qw{ jid password debug
35             on_notification on_connect on_disconnect on_error }) {
36             $filtered{$_} = delete $param{$_};
37             }
38             croak "Unknown option(s): " . join ", ", keys %param if keys %param;
39              
40             my $superfeedr = bless {
41             debug => $filtered{debug} || 0,
42             jid => $filtered{jid},
43             password => $filtered{password},
44             }, ref $class || $class;
45              
46             ## can be passed to connect() too
47             $superfeedr->{on_connect} = $filtered{on_connect}
48             if $filtered{on_connect};
49              
50             my $on_error = $filtered{on_error} || sub {
51             my ($cl, $acc, $err) = @_;
52             if (Scalar::Util::blessed($err)) {
53             if ($err->isa('AnyEvent::XMPP::Error')) {
54             $err = $err->string;
55             }
56             }
57             warn "Error: " . $err;
58             };
59              
60             my $cl = AnyEvent::XMPP::Client->new(
61             debug => $superfeedr->{debug},
62             );
63             my $pass = $superfeedr->{password};
64             my $jid = $superfeedr->{jid}
65             or croak "You need to specify your jid";
66              
67             $cl->add_account($jid, $pass, undef, undef, {
68             dont_retrieve_roster => 1,
69             });
70             $cl->add_extension(my $ps = AnyEvent::XMPP::Ext::Superfeedr->new);
71             $superfeedr->{xmpp_pubsub} = $ps;
72              
73             $cl->reg_cb(
74             error => $on_error,
75             connected => sub {
76             $superfeedr->{connected} = 1;
77             $superfeedr->{on_connect}->($superfeedr)
78             if $superfeedr->{on_connect};
79             },
80             disconnect => sub {
81             $superfeedr->{connected} = 0;
82             ( $filtered{on_disconnect}
83             || sub { warn "Got disconnected from $_[2]:$_[3], $_[4]" }
84             )->($superfeedr, @_);
85             },
86             connect_error => sub {
87             my ($cl, $account, $reason) = @_;
88             my $jid = $account->bare_jid;
89             $on_error->($cl, $account, "connection error for $jid: $reason");
90             },
91             );
92             if (my $on_notification = $filtered{on_notification} ) {
93             $ps->reg_cb(
94             superfeedr_notification => sub {
95             my $ps = shift;
96             my $notification = shift;
97             $on_notification->($notification);
98             },
99             );
100             }
101             $superfeedr->{xmpp_client} = $cl;
102             return $superfeedr;
103             }
104              
105             sub connect {
106             my $superfeedr = shift;
107             my $on_connect = shift;
108              
109             my $cl = $superfeedr->{xmpp_client}
110             or return;
111             if ($cl->{connected}) {
112             $superfeedr->event(error => "Already connected");
113             return;
114             }
115             $superfeedr->{on_connect} = $on_connect if $on_connect;
116             $cl->start;
117             }
118              
119             sub subscribe {
120             my $superfeedr = shift;
121             $superfeedr->pubsub_method('subscribe_nodes', @_);
122             }
123              
124             sub unsubscribe {
125             my $superfeedr = shift;
126             $superfeedr->pubsub_method('unsubscribe_nodes', @_);
127             }
128              
129             sub pubsub_method {
130             my $superfeedr = shift;
131             my($method, @feed_uris) = @_;
132             my $cb = ref $feed_uris[-1] eq 'CODE' ? pop @feed_uris : sub { };
133              
134             my $pubsub = $superfeedr->xmpp_pubsub;
135             unless ($pubsub) {
136             $superfeedr->event(error => "no pubsub extension available");
137             return;
138             }
139             my $con = $superfeedr->xmpp_connection;
140             unless ($con) {
141             $superfeedr->event(error => "Wait to be connected");
142             return;
143             }
144              
145             my @chunk = splice @feed_uris, 0, SUBSCRIBE_CHUNK_SIZE;
146              
147             my $res_cb;
148              
149             my $chunk_cb = sub {
150             my ($chunk, $res_cb) = @_;
151             my @xmpp_uris = map { xmpp_node_uri($_) } @$chunk;
152             $pubsub->$method($con, \@xmpp_uris, $res_cb);
153             };
154              
155             $res_cb = sub {
156             my $err = shift;
157             if ($err) {
158             $superfeedr->event(error => $err);
159             undef @chunk;
160             undef @feed_uris;
161             } else {
162             $cb->($_) for @chunk;
163             if (@feed_uris) {
164             @chunk = splice @feed_uris, 0, SUBSCRIBE_CHUNK_SIZE;
165             $chunk_cb->(\@chunk, $res_cb);
166             }
167             else {
168             undef $chunk_cb;
169             undef $res_cb;
170             }
171             }
172             };
173             $chunk_cb->(\@chunk, $res_cb);
174             }
175              
176             sub xmpp_node_uri {
177             my $enc_feed = URI::Escape::uri_escape_utf8(shift, "\x00-\x1f\x7f-\xff");
178             # work around what I think is a but in AnyEvent::XMPP
179             #return "xmpp:$SERVICE?;node=$enc_feed";
180             return "xmpp:$SERVICE?sub;node=$enc_feed";
181             }
182              
183             sub xmpp_pubsub {
184             my $superfeedr = shift;
185             return $superfeedr->{xmpp_pubsub};
186             }
187              
188             sub xmpp_connection {
189             my $superfeedr = shift;
190             my $con = $superfeedr->{xmpp_connection};
191             return $con if $con;
192              
193             my $client = $superfeedr->{xmpp_client} or return;
194             my $jid = $superfeedr->{jid};
195             my $account = $client->get_account($jid) or return;
196             $con = $account->connection;
197             $superfeedr->{xmpp_connection} = $con;
198             return $con;
199             }
200              
201             1;
202             __END__