File Coverage

blib/lib/AnyEvent/XMPP/IM/Contact.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package AnyEvent::XMPP::IM::Contact;
2 1     1   1005 use strict;
  1         3  
  1         43  
3 1     1   7 no warnings;
  1         2  
  1         48  
4 1     1   596 use AnyEvent::XMPP::Util qw/split_jid node_jid/;
  0            
  0            
5             use AnyEvent::XMPP::Namespaces qw/xmpp_ns/;
6             use AnyEvent::XMPP::IM::Presence;
7             use AnyEvent::XMPP::IM::Message;
8              
9             =head1 NAME
10              
11             AnyEvent::XMPP::IM::Contact - Instant messaging roster contact
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 contact objects which populate
25             a roster (L.
26              
27             There are two types of 'contacts' that are managed by this class.
28             The first are contacts that are on the users roster, and the second
29             are contacts that are B on the users roster.
30              
31             To find our whether this is a contact which is actually available
32             as roster item in the users roster, you should consult the C
33             method (see below).
34              
35             Another special kind of contact is the contact which stands for ourself
36             and is mostly only used for keeping track of our own presences and resources.
37             But note that even if the C method returns true, the C
38             method might also return a true value, in case we have a roster item
39             of ourself on the roster (which might happen in rare cases :).
40              
41             You can get an instance of this class only by calling the C
42             function on a roster object.
43              
44             =head1 METHODS
45              
46             =over 4
47              
48             =cut
49              
50             sub new {
51             my $this = shift;
52             my $class = ref($this) || $this;
53             bless { @_ }, $class;
54             }
55              
56             =item B
57              
58             This method updates a contact. If the request is finished
59             it will call C<$cb>. If it resulted in an error the first argument
60             of that callback will be a L object.
61              
62             The C<%upd> hash should have one of the following keys and defines
63             what parts of the contact to update:
64              
65             =over 4
66              
67             =item name => $name
68              
69             Updates the name of the contact. C<$name> = '' erases the contact.
70              
71             =item add_group => $groups
72              
73             Adds the contact to the groups in the array reference C<$groups>.
74              
75             =item remove_group => $groups
76              
77             Removes the contact from the groups in the array reference C<$groups>.
78              
79             =item groups => $groups
80              
81             This sets the groups of the contact. C<$groups> should be an array reference
82             of the groups.
83              
84             =back
85              
86             =cut
87              
88             sub send_update {
89             my ($self, $cb, %upd) = @_;
90              
91             if ($upd{groups}) {
92             $self->{groups} = $upd{groups};
93             }
94             for my $g (@{$upd{add_group} || []}) {
95             push @{$self->{groups}}, $g unless grep { $g eq $_ } $self->groups;
96             }
97             for my $g (@{$upd{remove_group} || []}) {
98             push @{$self->{groups}}, grep { $g ne $_ } $self->groups;
99             }
100              
101             $self->{connection}->send_iq (
102             set => sub {
103             my ($w) = @_;
104             $w->addPrefix (xmpp_ns ('roster'), '');
105             $w->startTag ([xmpp_ns ('roster'), 'query']);
106             $w->startTag ([xmpp_ns ('roster'), 'item'],
107             jid => $self->jid,
108             (defined $upd{name} ? (name => $upd{name}) : ())
109             );
110             for ($self->groups) {
111             $w->startTag ([xmpp_ns ('roster'), 'group']);
112             $w->characters ($_);
113             $w->endTag;
114             }
115             $w->endTag;
116             $w->endTag;
117             },
118             sub {
119             my ($node, $error) = @_;
120             my $con = undef;
121             unless ($error) { $con = $self }
122             $cb->($con, $error) if $cb
123             }
124             );
125             }
126              
127             =item B
128              
129             This method sends this contact a subscription request.
130              
131             =cut
132              
133             sub send_subscribe {
134             my ($self) = @_;
135             $self->{connection}->send_presence ('subscribe', undef, to => $self->jid);
136             }
137              
138             =item B
139              
140             This method accepts a contact's subscription request.
141              
142             =cut
143              
144             sub send_subscribed {
145             my ($self) = @_;
146             $self->{connection}->send_presence ('subscribed', undef, to => $self->jid);
147             }
148              
149             =item B
150              
151             This method sends this contact a unsubscription request.
152              
153             =cut
154              
155             sub send_unsubscribe {
156             my ($self) = @_;
157             $self->{connection}->send_presence ('unsubscribe', undef, to => $self->jid);
158             }
159              
160             =item B
161              
162             This method sends this contact a unsubscription request which unsubscribes
163             ones own presence from him (he wont get any further presence from us).
164              
165             =cut
166              
167             sub send_unsubscribed {
168             my ($self) = @_;
169             $self->{connection}->send_presence ('unsubscribed', undef, to => $self->jid);
170             }
171              
172              
173             =item B
174              
175             This method wants a L in C<$item> which
176             should be a roster item received from the server. The method will
177             update the contact accordingly and return it self.
178              
179             =cut
180              
181             sub update {
182             my ($self, $item) = @_;
183              
184             my ($jid, $name, $subscription, $ask) =
185             (
186             $item->attr ('jid'),
187             $item->attr ('name'),
188             $item->attr ('subscription'),
189             $item->attr ('ask')
190             );
191              
192             $self->{name} = $name;
193             $self->{subscription} = $subscription;
194             $self->{groups} = [ map { $_->text } $item->find_all ([qw/roster group/]) ];
195             $self->{ask} = $ask;
196              
197             $self
198             }
199              
200             =item B
201              
202             This method updates the presence of contacts on the roster.
203             C<$presence> must be a L object and should be
204             a presence packet.
205              
206             =cut
207              
208             sub update_presence {
209             my ($self, $node) = @_;
210              
211             my $type = $node->attr ('type');
212             my $jid = $node->attr ('from');
213             # XXX: should check whether C<$jid> is nice JID.
214              
215             $self->touch_presence ($jid);
216              
217             my $old;
218             my $new;
219             if ($type eq 'unavailable') {
220             $old = $self->remove_presence ($jid);
221             } else {
222             $old = $self->touch_presence ($jid)->update ($node);
223             $new = $self->touch_presence ($jid);
224             }
225              
226             ($self, $old, $new)
227             }
228              
229             sub remove_presence {
230             my ($self, $jid) = @_;
231             my $sjid = AnyEvent::XMPP::Util::stringprep_jid ($jid);
232             delete $self->{presences}->{$sjid}
233             }
234              
235             sub touch_presence {
236             my ($self, $jid) = @_;
237             my $sjid = AnyEvent::XMPP::Util::stringprep_jid ($jid);
238              
239             unless (exists $self->{presences}->{$sjid}) {
240             $self->{presences}->{$sjid} =
241             AnyEvent::XMPP::IM::Presence->new (connection => $self->{connection}, jid => $jid);
242             }
243             $self->{presences}->{$sjid}
244             }
245              
246             =item B
247              
248             This method returns a presence of this contact if
249             it is available. The return value is an instance of L
250             or undef if no such presence exists.
251              
252             =cut
253              
254             sub get_presence {
255             my ($self, $jid) = @_;
256             my $sjid = AnyEvent::XMPP::Util::stringprep_jid ($jid);
257             $self->{presences}->{$sjid}
258             }
259              
260             =item B
261              
262             Returns all presences of this contact in form of
263             L objects.
264              
265             =cut
266              
267             sub get_presences { values %{$_[0]->{presences}} }
268              
269             =item B
270              
271             Returns the presence with the highest priority.
272              
273             =cut
274              
275             sub get_priority_presence {
276             my ($self) = @_;
277              
278             my (@pres) =
279             sort {
280             $self->{presences}->{$b}->priority <=> $self->{presences}->{$a}->priority
281             } keys %{$self->{presences}};
282              
283             return unless defined $pres[0];
284             $self->{presences}->{$pres[0]}
285             }
286              
287             =item B
288              
289             Returns the list of groups (strings) this contact is in.
290              
291             =cut
292              
293             sub groups {
294             @{$_[0]->{groups} || []}
295             }
296              
297             =item B
298              
299             Returns the bare JID of this contact.
300              
301             =cut
302              
303             sub jid {
304             $_[0]->{jid}
305             }
306              
307             =item B
308              
309             Returns the (nick)name of this contact.
310              
311             =cut
312              
313             sub name {
314             $_[0]->{name}
315             }
316              
317             =item B
318              
319             Returns 1 if this is a contact that is officially on the
320             roster and not just a contact we've received presence information
321             for.
322              
323             =cut
324              
325             sub is_on_roster {
326             my ($self) = @_;
327             $self->{subscription} && $self->{subscription} ne ''
328             }
329              
330             =item B
331              
332             Returns a true value when this contacts stands for ourself
333             and is only used for receiving presences of our own resources.
334              
335             =cut
336              
337             sub is_me {
338             my ($self) = @_;
339             $self->{is_me}
340             }
341              
342             =item B
343              
344             Returns the subscription state of this contact, which
345             can be one of:
346              
347             'none', 'to', 'from', 'both'
348              
349             If the contact isn't on the roster anymore this method
350             returns:
351              
352             'remove'
353              
354             =cut
355              
356             sub subscription {
357             $_[0]->{subscription}
358             }
359              
360             =item B
361              
362             Returns 'subscribe' if we asked this contact for subscription.
363              
364             =cut
365              
366             sub ask {
367             $_[0]->{ask}
368             }
369              
370             =item B
371              
372             Returns true if this contact has a pending subscription.
373             That means: the contact has to aknowledge the subscription.
374              
375             =cut
376              
377             sub subscription_pending {
378             my ($self) = @_;
379             $self->{ask}
380             }
381              
382             =item B
383              
384             Returns the nickname of this contact (or, if none is set in the
385             roster, it returns the node part of the JID)
386              
387             =cut
388              
389             sub nickname {
390             my ($self) = @_;
391             my $n = $self->name;
392              
393             if ($n eq '') {
394             $n = node_jid ($self->jid);
395             }
396             $n
397             }
398              
399             sub message_class { 'AnyEvent::XMPP::IM::Message' }
400              
401             =item B
402              
403             This method returns a L
404             object with the to field set to this contacts JID.
405              
406             C<%args> are further arguments for the message constructor.
407              
408             =cut
409              
410             sub make_message {
411             my ($self, %args) = @_;
412             $self->message_class ()->new (
413             connection => $self->{connection},
414             to => $self->jid,
415             %args
416             );
417             }
418              
419             sub debug_dump {
420             my ($self) = @_;
421             printf "- %-30s [%-20s] (%s){%s}\n",
422             $self->jid,
423             $self->name || '',
424             $self->subscription,
425             $self->ask;
426              
427             for ($self->get_presences) {
428             $_->debug_dump;
429             }
430             }
431              
432             =back
433              
434             =head1 AUTHOR
435              
436             Robin Redeker, C<< >>, JID: C<< >>
437              
438             =head1 COPYRIGHT & LICENSE
439              
440             Copyright 2007, 2008 Robin Redeker, all rights reserved.
441              
442             This program is free software; you can redistribute it and/or modify it
443             under the same terms as Perl itself.
444              
445             =cut
446              
447             1; # End of AnyEvent::XMPP::Contact