package Net::XMPP2::Ext::Disco; use Net::XMPP2::Namespaces qw/xmpp_ns/; use Net::XMPP2::Util qw/simxml/; use Net::XMPP2::Ext::Disco::Items; use Net::XMPP2::Ext::Disco::Info; use Net::XMPP2::Ext; our @ISA = qw/Net::XMPP2::Ext/; =head1 NAME Net::XMPP2::Ext::Disco - Service discovery manager class for XEP-0030 =head1 SYNOPSIS use Net::XMPP2::Ext::Disco; my $con = Net::XMPP2::IM::Connection->new (...); $con->add_extension (my $disco = Net::XMPP2::Ext::Disco->new); $disco->request_items ('romeo@montague.net', sub { my ($disco, $items, $error) = @_; if ($error) { print "ERROR".$error->string."\n" } else { ... do something with the $items ... } } ); =head1 DESCRIPTION This module represents a service discovery manager class. You make instances of this class and get a handle to send discovery requests like described in XEP-0030. It also allows you to setup a disco-info/items tree that others can walk and also lets you publish disco information. This class is derived from L and can be added as extension to objects that implement the L interface or derive from it. =head1 METHODS =over 4 =item B Creates a new disco handle. =cut sub new { my $this = shift; my $class = ref($this) || $this; my $self = bless { @_ }, $class; $self->init; $self } sub init { my ($self) = @_; $self->set_identity (client => console => 'Net::XMPP2'); $self->enable_feature (xmpp_ns ('disco_info')); $self->enable_feature (xmpp_ns ('disco_items')); $self->reg_cb ( iq_get_request_xml => sub { my ($self, $con, $node) = @_; if ($self->handle_disco_query ($con, $node)) { return 1; } () } ); } =item B This sets the identity of the top info node. The default is: C<$category = "client">, C<$type = "console"> and C<$name = "Net::XMPP2">. C<$name> is optional and can be undef. For a list of valid identites look at: http://www.xmpp.org/registrar/disco-categories.html Valid identity types for C<$category = "client"> may be: bot console handheld pc phone web =cut sub set_identity { my ($self, $category, $type, $name) = @_; $self->{iden}->{cat} = $category; $self->{iden}->{type} = $type; $self->{iden}->{name} = $name; } =item B This method enables the feature C<$uri>, where C<$uri> should be one of the values from the B column on: http://www.xmpp.org/registrar/disco-features.html These features are enabled by default: http://jabber.org/protocol/disco#info http://jabber.org/protocol/disco#items =cut sub enable_feature { my ($self, $feature) = @_; $self->{feat}->{$feature} = 1 } =item B This method enables the feature C<$uri>, where C<$uri> should be one of the values from the B column on: http://www.xmpp.org/registrar/disco-features.html These features are enabled by default: http://jabber.org/protocol/disco#info http://jabber.org/protocol/disco#items =cut sub disable_feature { my ($self, $feature) = @_; delete $self->{feat}->{$feature} } sub write_feature { my ($self, $w, $var) = @_; $w->emptyTag ([xmpp_ns ('disco_info'), 'feature'], var => $var); } sub write_identity { my ($self, $w, $cat, $type, $name) = @_; $w->emptyTag ([xmpp_ns ('disco_info'), 'identity'], category => $cat, type => $type, (defined $name ? (name => $name) : ()) ); } sub handle_disco_query { my ($self, $con, $node) = @_; my $q; if (($q) = $node->find_all ([qw/disco_info query/])) { $con->reply_iq_result ( $node, sub { my ($w) = @_; if ($q->attr ('node')) { simxml ($w, defns => 'disco_info', node => { ns => 'disco_info', name => 'query', attrs => [ node => $q->attr ('node') ] }); } else { $w->addPrefix (xmpp_ns ('disco_info'), ''); $w->startTag ([xmpp_ns ('disco_info'), 'query']); $self->write_identity ($w, $self->{iden}->{cat}, $self->{iden}->{type}, $self->{iden}->{name}, ); for (sort grep { $self->{feat}->{$_} } keys %{$self->{feat}}) { $self->write_feature ($w, $_); } $w->endTag; } }, to => $node->attr ('from') ); return 1 } elsif (($q) = $node->find_all ([qw/disco_items query/])) { $con->reply_iq_result ( $node, sub { my ($w) = @_; if ($q->attr ('node')) { simxml ($w, defns => 'disco_items', node => { ns => 'disco_items', name => 'query', attrs => [ node => $q->attr ('node') ] }); } else { simxml ($w, defns => 'disco_items', node => { ns => 'disco_items', name => 'query' }); } }, to => $node->attr ('from') ); return 1 } 0 } sub DESTROY { my ($self) = @_; $self->{connection}->unreg_cb ($self->{cb_id}) } =item B This method does send a items request to the JID entity C<$from>. C<$node> is the optional node to send the request to, which can be undef. C<$con> must be an instance of L or a subclass of it. The callback C<$cb> will be called when the request returns with 3 arguments: the disco handle, an L object (or undef) and an L object when an error occured and no items were received. $disco->request_items ($con, 'a@b.com', undef, sub { my ($disco, $items, $error) = @_; die $error->string if $error; # do something with the items here ;_) }); =cut sub request_items { my ($self, $con, $dest, $node, $cb) = @_; $con->send_iq ( get => sub { my ($w) = @_; $w->addPrefix (xmpp_ns ('disco_items'), ''); $w->emptyTag ([xmpp_ns ('disco_items'), 'query'], (defined $node ? (node => $node) : ()) ); }, sub { my ($xmlnode, $error) = @_; my $items; if ($xmlnode) { my (@query) = $xmlnode->find_all ([qw/disco_items query/]); $items = Net::XMPP2::Ext::Disco::Items->new ( jid => $dest, node => $node, xmlnode => $query[0] ) } $cb->($self, $items, $error) }, to => $dest ); } =item B This method does send a info request to the JID entity C<$from>. C<$node> is the optional node to send the request to, which can be undef. C<$con> must be an instance of L or a subclass of it. The callback C<$cb> will be called when the request returns with 3 arguments: the disco handle, an L object (or undef) and an L object when an error occured and no items were received. $disco->request_info ('a@b.com', undef, sub { my ($disco, $info, $error) = @_; die $error->string if $error; # do something with info here ;_) }); =cut sub request_info { my ($self, $con, $dest, $node, $cb) = @_; $con->send_iq ( get => sub { my ($w) = @_; $w->addPrefix (xmpp_ns ('disco_info'), ''); $w->emptyTag ([xmpp_ns ('disco_info'), 'query'], (defined $node ? (node => $node) : ()) ); }, sub { my ($xmlnode, $error) = @_; my $info; if ($xmlnode) { my (@query) = $xmlnode->find_all ([qw/disco_info query/]); $info = Net::XMPP2::Ext::Disco::Info->new ( jid => $dest, node => $node, xmlnode => $query[0] ) } $cb->($self, $info, $error) }, to => $dest ); } =back =head1 AUTHOR Robin Redeker, C<< >>, JID: C<< >> =head1 COPYRIGHT & LICENSE Copyright 2007 Robin Redeker, all rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1;