ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-XMPP2/lib/Net/XMPP2/Client.pm
Revision: 1.18
Committed: Thu Jul 19 11:36:33 2007 UTC (19 years, 2 months ago) by elmex
Branch: MAIN
Changes since 1.17: +19 -7 lines
Log Message:
added initial_presence argument to the IM::Connection and the
Client. added and upgraded some examples. further work on the
registration forms.

File Contents

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