package Net::XMPP2::IM::Contact; use strict; use Net::XMPP2::Util qw/split_jid/; use Net::XMPP2::Namespaces qw/xmpp_ns/; use Net::XMPP2::IM::Presence; use Net::XMPP2::IM::Message; =head1 NAME Net::XMPP2::IM::Contact - Instant messaging roster contact =head1 SYNOPSIS my $con = Net::XMPP2::IM::Connection->new (...); ... my $ro = $con->roster; if (my $c = $ro->get_contact ('test@example.com')) { $c->make_message ()->add_body ("Hello there!")->send; } =head1 DESCRIPTION This module represents a class for contact objects which populate a roster (L. You can get an instance of this class only by calling the C function on a roster object. =head1 METHODS =over 4 =cut sub new { my $this = shift; my $class = ref($this) || $this; bless { @_ }, $class; } =item B This method updates a contact. If the request is finished it will call C<$cb>. If it resulted in an error the first argument of that callback will be a L object. The C<%upd> hash should have one of the following keys and defines what parts of the contact to update: =over 4 =item name => $name Updates the name of the contact. C<$name> = '' erases the contact. =item add_group => $groups Addes the contact to the groups in the arrayreference C<$groups>. =item remove_group => $groups Removes the contact from the groups in the arrayreference C<$groups>. =item groups => $groups This sets the groups of the contact. C<$groups> should be an array reference of the groups. =back =cut sub send_update { my ($self, $cb, %upd) = @_; if ($upd{groups}) { $self->{groups} = $upd{groups}; } for my $g (@{$upd{add_group} || []}) { push @{$self->{groups}}, $g unless grep { $g eq $_ } $self->groups; } for my $g (@{$upd{remove_group} || []}) { push @{$self->{groups}}, grep { $g ne $_ } $self->groups; } $self->{connection}->send_iq ( set => sub { my ($w) = @_; $w->addPrefix (xmpp_ns ('roster'), ''); $w->startTag ([xmpp_ns ('roster'), 'query']); $w->startTag ([xmpp_ns ('roster'), 'item'], jid => $self->jid, (defined $upd{name} ? (name => $upd{name}) : ()) ); for ($self->groups) { $w->startTag ([xmpp_ns ('roster'), 'group']); $w->characters ($_); $w->endTag; } $w->endTag; $w->endTag; }, sub { my ($node, $error) = @_; $cb->($error) if $cb } ); } =item B This method sends this contact a subscription request. =cut sub send_subscribe { my ($self) = @_; $self->{connection}->send_presence ('subscribe', undef, to => $self->jid); } =item B This method accepts a contact's subscription request. =cut sub send_subscribed { my ($self) = @_; $self->{connection}->send_presence ('subscribed', undef, to => $self->jid); } =item B This method sends this contact a unsubscription request. =cut sub send_unsubscribe { my ($self) = @_; $self->{connection}->send_presence ('unsubscribe', undef, to => $self->jid); } =item B This method sends this contact a unsubscription request which unsubscribes ones own presence from him (he wont get any further presence from us). =cut sub send_unsubscribed { my ($self) = @_; $self->{connection}->send_presence ('unsubscribed', undef, to => $self->jid); } =item B This method wants a L in C<$item> which should be a roster item received from the server. The method will update the contact accordingly and return it self. =cut sub update { my ($self, $item) = @_; my ($jid, $name, $subscription, $ask) = ( $item->attr ('jid'), $item->attr ('name'), $item->attr ('subscription'), $item->attr ('ask') ); $self->{name} = $name; $self->{subscription} = $subscription; $self->{groups} = [ map { $_->text } $item->find_all ([qw/roster group/]) ]; $self->{ask} = $ask; $self } =item B This method updates the presence of contacts on the roster. C<$presence> must be a L object and should be a presence packet. =cut sub update_presence { my ($self, $node) = @_; my $type = $node->attr ('type'); my $jid = $node->attr ('from'); # XXX: should check whether C<$jid> is nice JID. $self->touch_presence ($jid); my $old; my $new; if ($type eq 'unavailable') { $old = $self->remove_presence ($jid); } else { $old = $self->touch_presence ($jid)->update ($node); $new = $self->touch_presence ($jid); } ($self, $old, $new) } sub remove_presence { my ($self, $jid) = @_; my $sjid = Net::XMPP2::Util::stringprep_jid ($jid); delete $self->{presences}->{$sjid} } sub touch_presence { my ($self, $jid) = @_; my $sjid = Net::XMPP2::Util::stringprep_jid ($jid); unless (exists $self->{presences}->{$sjid}) { $self->{presences}->{$sjid} = Net::XMPP2::IM::Presence->new (connection => $self->{connection}, jid => $jid); } $self->{presences}->{$sjid} } =item B This method returns a presence of this contact if it is available. The return value is an instance of L or undef if no such presence exists. =cut sub get_presence { my ($self, $jid) = @_; my $sjid = Net::XMPP2::Util::stringprep_jid ($jid); $self->{presences}->{$sjid} } =item B Returns all presences of this contact in form of L objects. =cut sub get_presences { values %{$_[0]->{presences}} } =item B Returns the presence with the highest priority. =cut sub get_priority_presence { my ($self) = @_; my (@pres) = sort { $self->{presences}->{$b}->priority <=> $self->{presences}->{$a}->priority } keys %{$self->{presences}}; return unless defined $pres[0]; $self->{presences}->{$pres[0]} } =item B Returns the list of groups (strings) this contact is in. =cut sub groups { @{$_[0]->{groups} || []} } =item B Returns the bare JID of this contact. =cut sub jid { $_[0]->{jid} } =item B Returns the (nick)name of this contact. =cut sub name { $_[0]->{name} } =item B Returns 1 if this is a contact that is officially on the roster and not just a contact we've received presence information for. =cut sub is_on_roster { my ($self) = @_; $self->{subscription} && $self->{subscription} ne '' } =item B Returns the subscription state of this contact, which can be one of: 'none', 'to', 'from', 'both' If the contact isn't on the roster anymore this method returns: 'remove' =cut sub subscription { $_[0]->{subscription} } =item B Returns true if this contact has a pending subscription. That means: the contact has to aknowledge the subscription. =cut sub subscription_pending { my ($self) = @_; $self->{ask} } =item B Returns the nickname of this contact. =cut sub nickname { my ($self) = @_; my $n = $self->name; if ($n eq '') { my ($user) = split_jid $self->jid; $n = $user; } $n } =item B This method returns a L object with the to field set to this contacts JID. =cut sub make_message { my ($self) = @_; Net::XMPP2::IM::Message->new ( connection => $self->{connection}, to => $self->jid ); } sub debug_dump { my ($self) = @_; printf "- %-30s [%-20s] (%s)\n", $self->jid, $self->name || '', $self->subscription; for ($self->get_presences) { $_->debug_dump; } } =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; # End of Net::XMPP2::Contact