File Coverage

blib/lib/AnyEvent/XMPP/Client.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::Client;
2 19     19   1601 use strict;
  19         42  
  19         762  
3 19     19   100 use AnyEvent;
  19         41  
  19         352  
4 19     19   22928 use AnyEvent::XMPP::IM::Connection;
  0            
  0            
5             use AnyEvent::XMPP::Util qw/stringprep_jid prep_bare_jid dump_twig_xml bare_jid cmp_bare_jid/;
6             use AnyEvent::XMPP::Namespaces qw/xmpp_ns/;
7             use AnyEvent::XMPP::Extendable;
8             use AnyEvent::XMPP::IM::Account;
9             use Object::Event;
10             use Scalar::Util;
11              
12             #use XML::Twig;
13             #
14             #sub _dumpxml {
15             # my $data = shift;
16             # my $t = XML::Twig->new;
17             # if ($t->safe_parse ("$data")) {
18             # $t->set_pretty_print ('indented');
19             # $t->print;
20             # print "\n";
21             # } else {
22             # print "[$data]\n";
23             # }
24             #}
25              
26             our @ISA = qw/Object::Event AnyEvent::XMPP::Extendable/;
27              
28             =head1 NAME
29              
30             AnyEvent::XMPP::Client - XMPP Client abstraction
31              
32             =head1 SYNOPSIS
33              
34             use AnyEvent::XMPP::Client;
35             use AnyEvent;
36              
37             my $j = AnyEvent->condvar;
38              
39             my $cl = AnyEvent::XMPP::Client->new;
40             $cl->start;
41              
42             $j->wait;
43              
44             =head1 DESCRIPTION
45              
46             This module tries to implement a straight forward and easy to
47             use API to communicate with XMPP entities. L
48             handles connections and timeouts and all such stuff for you.
49              
50             For more flexibility please have a look at L
51             and L, they allow you to control what
52             and how something is being sent more precisely.
53              
54             =head1 METHODS
55              
56             =head2 new (%args)
57              
58             Following arguments can be passed in C<%args>:
59              
60             =over 4
61              
62             =item debug => 1
63              
64             This will install callbacks which produce debugging output. This will
65             require L to be installed (as it is used for pretty printing
66             the "XML" output).
67              
68             =back
69              
70             =cut
71              
72             sub new {
73             my $this = shift;
74             my $class = ref($this) || $this;
75             my $self = { @_ };
76             bless $self, $class;
77              
78             if ($self->{debug}) {
79             $self->reg_cb (
80             debug_recv => sub {
81             my ($self, $acc, $data) = @_;
82             printf "recv>> %s\n%s", $acc->jid, dump_twig_xml ($data)
83             },
84             debug_send => sub {
85             my ($self, $acc, $data) = @_;
86             printf "send<< %s\n%s", $acc->jid, dump_twig_xml ($data)
87             },
88             )
89             }
90             return $self;
91             }
92              
93             sub add_extension {
94             my ($self, $ext) = @_;
95             $self->add_forward ($ext, sub {
96             my ($self, $ext, $ev, $acc, @args) = @_;
97             return if $ext->{inhibit_forward}->{$ev};
98             $ext->_event ($ev, $acc->connection (), @args);
99             });
100             }
101              
102             =head2 add_account ($jid, $password, $host, $port, $connection_args)
103              
104             This method adds a jabber account for connection with the JID C<$jid>
105             and the password C<$password>.
106              
107             C<$host> and C<$port> can be undef and their default will be the domain of the
108             C<$jid> and the default for the C parameter to the constructor of
109             L (look there for details about DNS-SRV lookups).
110              
111             C<$connection_args> must either be undef or a hash reference to
112             additional arguments for the constructor of the L
113             that will be used to connect the account.
114              
115             Returns 1 on success and undef when the account already exists.
116              
117             =cut
118              
119             sub add_account {
120             my ($self, $jid, $password, $host, $port, $connection_args) = @_;
121             my $bj = prep_bare_jid $jid;
122              
123             my $acc = $self->{accounts}->{$bj};
124             if ($acc) {
125             $acc->{password} = $password;
126             $acc->{host} = $host;
127             $acc->{port} = $port;
128             $acc->{args} = $connection_args;
129             return;
130             }
131              
132             $acc =
133             $self->{accounts}->{$bj} =
134             AnyEvent::XMPP::IM::Account->new (
135             jid => $jid,
136             password => $password,
137             host => $host,
138             port => $port,
139             args => $connection_args,
140             );
141              
142             $self->event (added_account => $acc);
143              
144             $self->update_connections
145             if $self->{started};
146              
147             $acc
148             }
149              
150             =head2 start ()
151              
152             This method initiates the connections to the XMPP servers.
153              
154             =cut
155              
156             sub start {
157             my ($self) = @_;
158             $self->{started} = 1;
159             $self->update_connections;
160             }
161              
162             =head2 update_connections ()
163              
164             This method tries to connect all unconnected accounts.
165              
166             =cut
167              
168             sub update_connections {
169             my ($self) = @_;
170              
171             Scalar::Util::weaken $self;
172              
173             for (values %{$self->{accounts}}) {
174             my $acc = $_;
175              
176             if (!$acc->is_connected && !$self->{prep_connections}->{$acc->bare_jid}) {
177             my %args = (initial_presence => 10);
178              
179             if (defined $self->{presence}) {
180             if (defined $self->{presence}->{priority}) {
181             $args{initial_presence} = $self->{presence}->{priority};
182             }
183             }
184              
185             my $con = $acc->spawn_connection (%args);
186             $self->{prep_connections}->{$acc->bare_jid} = $con;
187              
188             $con->add_forward ($self, sub {
189             my ($con, $self, $ev, @arg) = @_;
190             $self->_event ($ev, $acc, @arg);
191             });
192              
193             $con->reg_cb (
194             session_ready => sub {
195             my ($con) = @_;
196             delete $self->{prep_connections}->{$acc->bare_jid};
197             $self->event (connected => $acc);
198             if (defined $self->{presence}) {
199             $con->send_presence (undef, undef, %{$self->{presence} || {}});
200             }
201             $con->unreg_me
202             },
203             disconnect => sub {
204             my ($con, $h, $p, $err) = @_;
205             $self->event (connect_error => $acc, $err);
206             delete $self->{prep_connections}->{$acc->bare_jid};
207             $con->unreg_me;
208             },
209             after_disconnect => sub {
210             my ($con, $h, $p, $err) = @_;
211             $con->remove_forward ($self);
212             }
213             );
214              
215             $con->connect;
216             }
217             }
218             }
219              
220             =head2 disconnect ($msg)
221              
222             Disconnect all accounts.
223              
224             =cut
225              
226             sub disconnect {
227             my ($self, $msg) = @_;
228             for my $acc (values %{$self->{accounts}}) {
229             if ($acc->is_connected) { $acc->connection ()->disconnect ($msg) }
230             }
231             }
232              
233             =head2 remove_accounts ($reason)
234              
235             Removes all accounts and disconnects. C<$reason> should be some descriptive
236             reason why this account was removed (just for logging purposes).
237              
238             =cut
239              
240             sub remove_accounts {
241             my ($self, $reason) = @_;
242             for my $acc (keys %{$self->{accounts}}) {
243             $self->remove_account ($acc, $reason);
244             }
245             }
246              
247             =head2 remove_account ($acc, $reason)
248              
249             Removes and disconnects account C<$acc> (which is a L object).
250             The reason for the removal can be given via C<$reason>.
251              
252             =cut
253              
254             sub remove_account {
255             my ($self, $acc, $reason) = @_;
256             my $acca = $self->{accounts}->{$acc};
257             $self->event (removed_account => $acca);
258             if ($acca->is_connected) { $acca->connection ()->disconnect ($reason) }
259             delete $self->{accounts}->{$acc};
260             }
261              
262             =head2 set_accounts (%$accounts)
263              
264             Sets the set of (to be connected) accounts. C<$accounts> must be a hash
265             reference which contains the JIDs of the accounts as keys and the values for
266             C<$password>, C<$domain>, C<$port> and C<$connection_args> as described in
267             C above.
268              
269             If the account is not yet connected it will be connected on the next call to
270             C and if an account is connected that is not in
271             C<$accounts> it will be disconnected.
272              
273             =cut
274              
275             sub set_accounts {
276             my ($self, $accounts) = @_;
277              
278              
279             for my $accid (keys %{$self->{accounts}}) {
280             my $acca = $self->{accounts}->{$accid};
281             if (!grep { cmp_bare_jid ($acca->jid, $_) } keys %$accounts) {
282             $self->remove_account ($accid, "removed from set");
283             }
284             }
285              
286             for my $acc_jid (keys %$accounts) {
287             $self->add_account ($acc_jid, @{$accounts->{$acc_jid}});
288             }
289             }
290              
291             =head2 send_message ($msg, $dest_jid, $src, $type)
292              
293             Sends a message to the destination C<$dest_jid>.
294             C<$msg> can either be a string or a L object.
295             If C<$msg> is such an object C<$dest_jid> is optional, but will, when
296             passed, override the destination of the message.
297              
298             NOTE: C<$dest_jid> is transformed into a bare JID and the routing
299             is done by the conversation tracking mechanism which keeps track of
300             which resource should get the message.
301              
302             C<$src> is optional. It specifies which account to use
303             to send the message. If it is not passed L will try
304             to find an account itself. First it will look through all rosters
305             to find C<$dest_jid> and if none found it will pick any of the accounts that
306             are connected.
307              
308             C<$src> can either be a JID or a L object as returned
309             by C and C.
310              
311             C<$type> is optional but overrides the type of the message object in C<$msg>
312             if C<$msg> is such an object.
313              
314             C<$type> should be 'chat' for normal chatter. If no C<$type> is specified
315             the type of the message defaults to the value documented in L
316             (should be 'normal').
317              
318             =cut
319              
320             sub send_message {
321             my ($self, $msg, $dest_jid, $src, $type) = @_;
322              
323             unless (ref $msg) {
324             $msg = AnyEvent::XMPP::IM::Message->new (body => $msg);
325             }
326              
327             if (defined $dest_jid) {
328             my $jid = stringprep_jid $dest_jid
329             or die "send_message: \$dest_jid is not a proper JID";
330             $msg->to ($jid);
331             }
332              
333             $msg->type ($type) if defined $type;
334              
335             my $srcacc;
336             if (ref $src) {
337             $srcacc = $src;
338             } elsif (defined $src) {
339             $srcacc = $self->get_account ($src)
340             } else {
341             $srcacc = $self->find_account_for_dest_jid ($dest_jid);
342             }
343              
344             unless ($srcacc && $srcacc->is_connected) {
345             die "send_message: Couldn't get connected account for sending"
346             }
347              
348             $srcacc->send_tracked_message ($msg);
349             }
350              
351             =head2 get_account ($jid)
352              
353             Returns the L account object for the JID C<$jid>
354             if there is any such account added. (returns undef otherwise).
355              
356             =cut
357              
358             sub get_account {
359             my ($self, $jid) = @_;
360             $self->{accounts}->{prep_bare_jid $jid}
361             }
362              
363             =head2 get_accounts ()
364              
365             Returns a list of Ls.
366              
367             =cut
368              
369             sub get_accounts {
370             my ($self) = @_;
371             values %{$self->{accounts}}
372             }
373              
374             =head2 get_connected_accounts ()
375              
376             Returns a list of connected Ls.
377              
378             Same as:
379              
380             grep { $_->is_connected } $client->get_accounts ();
381              
382             =cut
383              
384             sub get_connected_accounts {
385             my ($self, $jid) = @_;
386             my (@a) = grep $_->is_connected, values %{$self->{accounts}};
387             @a
388             }
389              
390             =head2 find_account_for_dest_jid ($jid)
391              
392             This method tries to find any account that has the contact C<$jid>
393             on his roster. If no account with C<$jid> on his roster was found
394             it takes the first one that is connected. (Return value is a L
395             object).
396              
397             If no account is connected it returns undef.
398              
399             =cut
400              
401             sub find_account_for_dest_jid {
402             my ($self, $jid) = @_;
403              
404             my $any_acc;
405             for my $acc (values %{$self->{accounts}}) {
406             next unless $acc->is_connected;
407              
408             # take "first" active account
409             $any_acc = $acc unless defined $any_acc;
410              
411             my $roster = $acc->connection ()->get_roster;
412             if (my $c = $roster->get_contact ($jid)) {
413             return $acc;
414             }
415             }
416              
417             $any_acc
418             }
419              
420             =head2 get_contacts_for_jid ($jid)
421              
422             This method returns all contacts that we are connected to.
423             That means: It joins the contact lists of all account's rosters
424             that we are connected to.
425              
426             =cut
427              
428             sub get_contacts_for_jid {
429             my ($self, $jid) = @_;
430             my @cons;
431             for ($self->get_connected_accounts) {
432             my $roster = $_->connection ()->get_roster ();
433             my $con = $roster->get_contact ($jid);
434             push @cons, $con if $con;
435             }
436             return @cons;
437             }
438              
439             =head2 get_priority_presence_for_jid ($jid)
440              
441             This method returns the presence for the contact C<$jid> with the highest
442             priority.
443              
444             If the contact C<$jid> is on multiple account's rosters it's undefined which
445             roster the presence belongs to.
446              
447             =cut
448              
449             sub get_priority_presence_for_jid {
450             my ($self, $jid) = @_;
451              
452             my $lpres;
453             for ($self->get_connected_accounts) {
454             my $roster = $_->connection ()->get_roster ();
455             my $con = $roster->get_contact ($jid);
456             next unless defined $con;
457             my $pres = $con->get_priority_presence ($jid);
458             next unless defined $pres;
459             if ((not defined $lpres) || $lpres->priority < $pres->priority) {
460             $lpres = $pres;
461             }
462             }
463              
464             $lpres
465             }
466              
467             =head2 set_presence ($show, $status, $priority)
468              
469             This sets the presence of all accounts. For a meaning of C<$show>, C<$status>
470             and C<$priority> see the description of the C<%attrs> hash in
471             C method of L.
472              
473             =cut
474              
475             sub set_presence {
476             my ($self, $show, $status, $priority) = @_;
477              
478             $self->{presence} = {
479             show => $show,
480             status => $status,
481             priority => $priority
482             };
483              
484             for my $ac ($self->get_connected_accounts) {
485             my $con = $ac->connection ();
486             $con->send_presence (undef, undef, %{$self->{presence}});
487             }
488             }
489              
490             =head1 EVENTS
491              
492             In the following event descriptions the argument C<$account>
493             is always a L object.
494              
495             All events from L are forwarded to the client,
496             only that the first argument for every event is a C<$account> object.
497              
498             Aside fom those, these events can be registered on with C:
499              
500             =over 4
501              
502             =item connected => $account
503              
504             This event is sent when the C<$account> was successfully connected.
505              
506             =item connect_error => $account, $reason
507              
508             This event is emitted when an error occured in the connection process for the
509             account C<$account>.
510              
511             =item error => $account, $error
512              
513             This event is emitted when any error occured while communicating
514             over the connection to the C<$account> - after a connection was established.
515              
516             C<$error> is an error object which is derived from L.
517             It will reveal human readable information about the error by calling the C
518             method (which returns a descriptive error string about the nature of the error).
519              
520             =item added_account => $account
521              
522             Called whenever an account is added.
523              
524             =item removed_account => $account
525              
526             Called whenever an account is removed.
527              
528             =back
529              
530             =head1 AUTHOR
531              
532             Robin Redeker, C<< >>, JID: C<< >>
533              
534             =head1 COPYRIGHT & LICENSE
535              
536             Copyright 2007, 2008 Robin Redeker, all rights reserved.
537              
538             This program is free software; you can redistribute it and/or modify it
539             under the same terms as Perl itself.
540              
541             =cut
542              
543             1; # End of AnyEvent::XMPP::Client