File Coverage

blib/lib/AnyEvent/XMPP/IM/Roster.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package AnyEvent::XMPP::IM::Roster;
2 1     1   2432 use AnyEvent::XMPP::IM::Contact;
  0            
  0            
3             use AnyEvent::XMPP::IM::Presence;
4             use AnyEvent::XMPP::Util qw/prep_bare_jid bare_jid cmp_bare_jid/;
5             use AnyEvent::XMPP::Namespaces qw/xmpp_ns/;
6             use strict;
7             no warnings;
8              
9             =head1 NAME
10              
11             AnyEvent::XMPP::IM::Roster - Instant messaging roster for XMPP
12              
13             =head1 SYNOPSIS
14              
15             my $con = AnyEvent::XMPP::IM::Connection->new (...);
16             ...
17             my $ro = $con->roster;
18             if (my $c = $ro->get_contact ('test@example.com')) {
19             $c->make_message ()->add_body ("Hello there!")->send;
20             }
21              
22             =head1 DESCRIPTION
23              
24             This module represents a class for roster objects which contain
25             contact information.
26              
27             It manages the roster of a JID connected by an L.
28             It manages also the presence information that is received.
29              
30             You get the roster by calling the C method on an L
31             object. There is no other way.
32              
33             =cut
34              
35             sub new {
36             my $this = shift;
37             my $class = ref($this) || $this;
38             bless { @_ }, $class;
39             }
40              
41             sub update {
42             my ($self, $node) = @_;
43              
44             my ($query) = $node->find_all ([qw/roster query/]);
45             return unless $query;
46              
47             my @upd;
48              
49             for my $item ($query->find_all ([qw/roster item/])) {
50             my $jid = $item->attr ('jid');
51              
52             my $sub = $item->attr ('subscription'),
53             $self->touch_jid ($jid);
54              
55             if ($sub eq 'remove') {
56             my $c = $self->remove_contact ($jid);
57             $c->update ($item);
58             } else {
59             push @upd, $self->get_contact ($jid)->update ($item);
60             }
61             }
62              
63             @upd
64             }
65              
66             sub update_presence {
67             my ($self, $node) = @_;
68             my $jid = $node->attr ('from');
69             # XXX: should check whether C<$jid> is nice JID.
70              
71             my $type = $node->attr ('type');
72             my $contact = $self->touch_jid ($jid);
73              
74             my %stati;
75             $stati{$_->attr ('lang') || ''} = $_->text
76             for $node->find_all ([qw/client status/]);
77              
78             if ($type eq 'subscribe') {
79             $self->{connection}->event (
80             contact_request_subscribe => $self, $contact, $stati{''});
81              
82             } elsif ($type eq 'subscribed') {
83             $self->{connection}->event (
84             contact_subscribed => $self, $contact, $stati{''});
85              
86             } elsif ($type eq 'unsubscribe') {
87             $self->{connection}->event (
88             contact_did_unsubscribe => $self, $contact, $stati{''});
89              
90             } elsif ($type eq 'unsubscribed') {
91             $self->{connection}->event (
92             contact_unsubscribed => $self, $contact, $stati{''});
93              
94             } else {
95             return $contact->update_presence ($node)
96             }
97             return ($contact)
98             }
99              
100             sub touch_jid {
101             my ($self, $jid, $contact) = @_;
102             my $bjid = prep_bare_jid ($jid);
103              
104             if (cmp_bare_jid ($jid, $self->{connection}->jid)) {
105             $self->{myself} =
106             $contact
107             || AnyEvent::XMPP::IM::Contact->new (
108             connection => $self->{connection},
109             jid => AnyEvent::XMPP::Util::bare_jid ($jid),
110             is_me => 1,
111             );
112             return $self->{myself}
113             }
114              
115             unless ($self->{contacts}->{$bjid}) {
116             $self->{contacts}->{$bjid} =
117             $contact
118             || AnyEvent::XMPP::IM::Contact->new (
119             connection => $self->{connection},
120             jid => AnyEvent::XMPP::Util::bare_jid ($jid),
121             )
122             }
123              
124             $self->{contacts}->{$bjid}
125             }
126              
127             sub remove_contact {
128             my ($self, $jid) = @_;
129             my $bjid = prep_bare_jid ($jid);
130             delete $self->{contacts}->{$bjid};
131             }
132              
133             sub set_retrieved {
134             my ($self) = @_;
135             $self->{retrieved} = 1;
136             }
137              
138             =head1 METHODS
139              
140             =over 4
141              
142             =item B
143              
144             Returns true if this roster was fetched from the server or false if this
145             roster hasn't been retrieved yet.
146              
147             =cut
148              
149             sub is_retrieved {
150             my ($self) = @_;
151             return $self->{retrieved}
152             }
153              
154             =item B
155              
156             This method sends a roster item creation request to
157             the server. C<$jid> is the JID of the contact.
158             C<$name> is the nickname of the contact, which can be
159             undef. C<$groups> should be a array reference containing
160             the groups this contact should be in.
161              
162             The callback in C<$cb> will be called when the creation is finished. The first
163             argument will be the C object if no error occured. The
164             second argument will be an L object if the request
165             resulted in an error.
166              
167             Please note that the contact you are given in that callback might not yet
168             be on the roster (C still returns a false value), if the
169             server did send the roster push after the iq result of the roster set, so
170             don't rely on the fact that the contact is on the roster.
171              
172             =cut
173              
174             sub new_contact {
175             my ($self, $jid, $name, $groups, $cb) = @_;
176              
177             $groups = [ $groups ] unless ref $groups;
178              
179             my $c = AnyEvent::XMPP::IM::Contact->new (
180             connection => $self->{connection},
181             jid => bare_jid ($jid)
182             );
183             $c->send_update (
184             sub {
185             my ($con, $err) = @_;
186             unless ($err) {
187             $self->touch_jid ($jid, $con);
188             }
189             $cb->($con, $err);
190             },
191             (defined $name ? (name => $name) : ()),
192             groups => ($groups || [])
193             );
194             }
195              
196             =item B
197              
198             This method will send a request to the server to delete this contact
199             from the roster. It will result in cancelling all subscriptions.
200              
201             C<$cb> will be called when the request was finished. The first argument
202             to the callback might be a L object if the
203             request resulted in an error.
204              
205             =cut
206              
207             sub delete_contact {
208             my ($self, $jid, $cb) = @_;
209              
210             $jid = prep_bare_jid $jid;
211              
212             $self->{connection}->send_iq (
213             set => sub {
214             my ($w) = @_;
215             $w->addPrefix (xmpp_ns ('roster'), '');
216             $w->startTag ([xmpp_ns ('roster'), 'query']);
217             $w->emptyTag ([xmpp_ns ('roster'), 'item'],
218             jid => $jid,
219             subscription => 'remove'
220             );
221             $w->endTag;
222             },
223             sub {
224             my ($node, $error) = @_;
225             $cb->($error) if $cb
226             }
227             );
228             }
229              
230             =item B
231              
232             Returns the contact on the roster with the JID C<$jid>.
233             (If C<$jid> is not bare the resource part will be stripped
234             before searching)
235              
236             B This method will also return contacts that we
237             have only presence for. To be sure the contact is on the
238             users roster you need to call the C method on the
239             contact.
240              
241             The return value is an instance of L.
242              
243             =cut
244              
245             sub get_contact {
246             my ($self, $jid) = @_;
247             my $bjid = AnyEvent::XMPP::Util::prep_bare_jid ($jid);
248              
249             if (cmp_bare_jid ($bjid, $self->{connection}->jid)) {
250             return $self->get_own_contact;
251             }
252              
253             $self->{contacts}->{$bjid}
254             }
255              
256             =item B
257              
258             Returns the contacts that are on this roster as
259             L objects.
260              
261             NOTE: This method only returns the contacts that have
262             a roster item. If you haven't retrieved the roster yet
263             the presence information is still stored but you have
264             to get the contacts without a roster item with the
265             C method. See below.
266              
267             =cut
268              
269             sub get_contacts {
270             my ($self) = @_;
271             grep { $_->is_on_roster } values %{$self->{contacts}}
272             }
273              
274             =item B
275              
276             Returns the contacts that are not on the roster
277             but for which we have received presence.
278             Return value is a list of L objects.
279              
280             See also documentation of C method of L above.
281              
282             =cut
283              
284             sub get_contacts_off_roster {
285             my ($self) = @_;
286             grep { not $_->is_on_roster } values %{$self->{contacts}}
287             }
288              
289             =item B
290              
291             This method returns a L object
292             which stands for ourself. It will be used to keep track of
293             our own presences.
294              
295             =cut
296              
297             sub get_own_contact {
298             my ($self) = @_;
299             $self->touch_jid ($self->{connection}->jid);
300             }
301              
302             =item B
303              
304             This prints the roster and all it's contacts
305             and their presences.
306              
307             =cut
308              
309             sub debug_dump {
310             my ($self) = @_;
311             print "### ROSTER BEGIN ###\n";
312             my %groups;
313             for my $contact ($self->get_contacts) {
314             push @{$groups{$_}}, $contact for $contact->groups;
315             push @{$groups{''}}, $contact unless $contact->groups;
316             }
317              
318             for my $grp (sort keys %groups) {
319             print "=== $grp ====\n";
320             $_->debug_dump for @{$groups{$grp}};
321             }
322             if ($self->get_contacts_off_roster) {
323             print "### OFF ROSTER ###\n";
324             for my $contact ($self->get_contacts_off_roster) {
325             push @{$groups{$_}}, $contact for $contact->groups;
326             push @{$groups{''}}, $contact unless $contact->groups;
327             }
328              
329             for my $grp (sort keys %groups) {
330             print "=== $grp ====\n";
331             $_->debug_dump for grep { not $_->is_on_roster } @{$groups{$grp}};
332             }
333             }
334              
335             print "### ROSTER END ###\n";
336             }
337              
338             =back
339              
340             =head1 AUTHOR
341              
342             Robin Redeker, C<< >>, JID: C<< >>
343              
344             =head1 SEE ALSO
345              
346             L, L, L
347              
348             =head1 COPYRIGHT & LICENSE
349              
350             Copyright 2007, 2008 Robin Redeker, all rights reserved.
351              
352             This program is free software; you can redistribute it and/or modify it
353             under the same terms as Perl itself.
354              
355             =cut
356              
357              
358              
359             1; # End of AnyEvent::XMPP