1 package Thrasher
::Protocol
;
6 use Thrasher
::Log
qw(:all);
7 use Thrasher
::Constants
qw(:all);
8 use Thrasher
::Callbacks
qw(:all);
9 use Thrasher
::Roster
qw(roster_diff);
13 Thrasher::Protocol - base class/interface for protocol differences.
17 Thrasher::Protocol abstracts out the various protocol differences
18 into a simple interface to implement them. Because there are minor
19 differences, especially in registration and such, it's slightly
22 Note that, theoretically, Thrasher can turn anything that conforms
23 to the API protocol defined in this module into a transport; it
24 doesn't actually have to back to libpurple! Twitter transport,
27 If you're adding a libpurple protocol, you probably want to subclass
28 from Thrasher::Protocol::LibPurple which will do a lot of the
29 libpurple work for you.
31 Protocol differences include, but are not limited to:
41 Registration instructions.
45 Registration result; this object will recieve notification
46 when a registration is completed.
50 How to actually perform the various work parts of the protocol.
54 Methods to reflect actions from the protocol back out to the
55 component. The Thrasher::Protocol superclass will implement the
56 communication with the ::Component, and that may be sufficient for
57 your protocol, but the ::Protocol gets a chance at the call just
58 in case you need something.
62 Some of the methods below have a useful default implementation that
63 will work for the vast majority of implementations, but some don't.
64 Some methods can be entirely overridden, some must call the superclass.
68 Only the top-level configuration file will call:
74 C<new>($configuration, $backend): The configuration argument hash is
75 what is passed in by component.pl.
77 The default implementation copies these into keys of $self.
81 =head2 Component Interface
83 These are things that the ::Component will call the ::Protocol with.
94 $self->{configuration
} = shift;
95 $self->{backend
} = shift;
104 C<registration_xml>(): Should return the instructions for
105 registration, in the XML format favored by Thrasher::XMPPStreamOut.
106 Be sure to put the tags in the correct $NS_REGISTER namespace.
108 If the user is already registered according to the backend,
109 a <registered/> tag should be emitted, in accordance with the
110 standard section 4.2.1 #2, as well as returning the relevant
111 values for the registration.
113 This will automatically be used as the children of a <query> tag,
114 so you should just return the children (in an array ref).
116 A default implementation is provided, based on the C<registered>
117 method of the backend, as long the protocol only needs a
118 standard username and password.
122 sub registration_xml
{
126 my $registration = $self->{backend
}->registered($jid);
128 return [[[$NS_REGISTER, 'instructions'], {},
129 ['Please provide your username and password for '
131 ($registration ?
([[$NS_REGISTER, 'registered'], {}, []])
133 [[$NS_REGISTER, 'username'], {},
134 [$registration->{username
} ?
($registration->{username
}) : ()]],
135 [[$NS_REGISTER, 'password'], {},
136 [$registration->{password
} ?
($registration->{password
}) : ()]]];
143 C<registration_items>: Should return a list of elements that are
144 required to login. This will be verified against the user's
145 registration at login time to verify that they have all required
148 The default implementation returns qw(username password).
150 If the user only has some pieces, the user is completely
155 # This addresses a bug where users were somehow able to have only a
156 # password in the DB. I'm not sure how this is possible, but since
157 # it's a segfault for libpurple for this to happen, it has to be
158 # stopped, both by fixing the real problem and by making sure that
159 # even if it happens it still doesn't crash us.
161 sub registration_items
{
162 return qw(username password);
169 C<registration_defaults>: Should return a hashref of values indicating
170 the defaults for registration, if given. If the user fails to pass
171 back a value, but there is a default, the default will be used.
173 A value must be given for each item or the registration will fail.
174 username and password should not be given defaults (unless you have
175 a really good reason of some kind), but since other clients sometimes
176 choke on anything other than having a username and password, we
177 need to be able to provide defaults for such clients.
179 The default implementation returns {}, no surprise.
183 # The temptation to write
184 # sub registartion defaults { { } }
186 sub registration_defaults
{
194 C<name>(): Should return the name of the protocol, suitable for
195 concatenation with the string " Gateway" for describing the gateway.
197 The default implementation returns $self->{name}, but that won't be
198 useful if you don't defined a ->{name}.
203 return $_[0]->{name
};
210 C<identifier>(): Should return a lowercase-letter name of the protocol
211 suitable for use in the domain of the component. i.e., the MSN
212 transport might return "Microsoft Messenger" for the call to C<name>,
213 but should return "msn" for this method.
215 The default implemenation returns $self->{identifier}, but that won't
216 be useful if you didn't define a ->{identifier}.
221 return $_[0]->{identifier
};
228 C<event_loop>(): Should return a Perl module conforming to the
229 interface set out in Thrasher::EventLoop that can be used to set
230 up the event loop for Thrasher Bird, including both whatever
231 loop is needed for the protocol loop and for talking to the XMPP
237 return 'Thrasher::EventLoop';
244 C<gateway_desc>($lang): Should return the thing you want to prompt the
245 user with, in order to ask them for their user name. See the examples,
246 or see XEP-0100 section 6.3. This corresponds to the
247 <desc> tag, hence the klunky name.
249 $xml_lang will be the language requested by the user, as seen in their
250 xml language attribute in the query. It will already have been
251 normalized to 'en' if not specified.
253 The default implementation returns undef, which means "don't give
254 a description", which the standard permits.
267 C<gateway_prompt>($lang): Should return the simple name of the
268 thing you are asking for when adding a contact. See XEP-0100
269 section 6.3. This corresponds to the <prompt> tag, hence the method
272 You must implement this.
278 die "Method gateway_prompt not implemented in " . ref($self);
285 C<registration>($from_jid, $registration_info): $registration_info is
286 a hash ref containing whatever the user sent back.
288 This should return a list with the following elements:
294 A boolean indicating if the registration was successful.
298 An identifier which is a key in the C<Thrasher::Component::IQ_ERRORS>
299 which represents the error to be sent back to the user.
303 The second is not needed if the registration was successful.
305 Note that XEP-0100 specifies that the error that should result
306 if the user's username and password failed to verify is
307 'not_acceptable' (note Component.pm uses an underscore).
309 The default implementation passes this on to the backend's
310 C<register> method, after processing the registration defaults.
317 my $registration_info = shift;
319 log("Registration defaults: " . Dumper
($self->registration_defaults));
321 my $registration_defaults = $self->registration_defaults;
322 for my $key (keys %$registration_defaults) {
323 $registration_info->{$key} ||= $registration_defaults->{$key};
326 log("Registering with: " . Dumper
($registration_info));
328 $self->{backend
}->register($jid, $registration_info);
335 C<remove>($from_jid): The given JID (with no resource) is
336 unregistering from the transport. The protocol likely doesn't care,
337 but it might. The backend certainly does. The default implementation
338 passes this along to the backend.
340 You should probably extend this to disconnect the user from the
349 $self->{backend
}->remove($jid);
352 # FIXME - rewrite this for clarity.
358 C<login>($continuation, $registration_info, $full_jid, $component): This
359 should generate either a Thrasher::Connection object corresponding to
360 the given login information (possibly an existing object if we somehow
361 are logging in without having logged off), OR an string corresponding
362 to an entry in C<%Thrasher::Component::IQ_ERRORS> indicating
365 $full_jid is the JID of the user logging in, with resource.
367 $registration_info will be a hash corresponding to the one requested
368 by C<registration_xml>.
370 $component is a reference to the component generating the login
371 request. This should be used to reflect the initial presence
372 state of the legacy subscribed users back out along the connection.
374 Note the use of "generate" rather than "return"; the generated
375 Thrasher::Connection or error string should be passed into the
376 continuation. This is because logging on could be a lengthy
377 operation and we of course can't pause for that. Each continuation is
378 thus a user-specific closure--wires will get crossed if the wrong
379 continuation is called for an account.
381 The Thrasher::Protocol object is also expected to remember the connection.
383 The protocol specifies that you should try to translate the error
384 you receive into the set of XMPP errors you can send.
386 This method's default implementation is to forward these parameters
387 along to the create_login_session, which will either create a
388 session and return it, or return a string corresponding to
389 an error. This method will then store that session with:
391 $self->{logged_in}->{$registration_info->{username}} = $session;
393 which is necessary for routing info later.
395 Note this is accomplished by adding on to the continuation, so
396 create_login_session is still documented correctly; it, too, must
397 call the continuation.
399 Generally, you won't need to override this method; what you want
400 to override is C<create_login_session>.
406 my $continuation = shift;
407 my $registration_info = shift;
409 my $new_continuation = sub {
410 my ($session_or_error, @args) = @_;
412 if (ref($session_or_error)) {
413 $self->{username_to_session
}->{$registration_info->{username
}} =
417 $continuation->($session_or_error, @args);
420 $self->create_login_session
421 ($new_continuation, $registration_info, @_);
428 C<logout>($session, $continuation): This should log off the user
429 from the legacy protocol. $session will be a Thrasher::Session
430 object, which you should have loaded up with any necessary information
431 in your login method call.
433 Once the logoff has been completed, call the $continuation with
436 C<logout> must not fail, and should run $continuation in some reasonable
439 Be sure to consider the state of the current connection.
441 The default implementation does not run $continuation and is thus
442 never sufficient. It exists only to manage some internal bookkeeping
443 for other methods' default implementations.
450 my $continuation = shift;
452 # TODO: displaynames are allowed to persist while the protocol object does.
454 my $legacy_login = $session->{'legacy_login'};
455 delete($self->{'username_to_session'}->{$legacy_login});
462 C<subscribe>($session, $legacy_name, $continuation): This should
463 subscribe the user to the given $legacy_name on the legacy service.
464 $legacy_name has been converted to be the user name expected by
465 the legacy service already, you don't need to "unescape" it.
467 The continuation should be called with either a true value,
468 indicating the subscription was successful, or a false value,
469 indicating it failed for any reason. (The standard assumes it
470 was because the remote user rejected it, so things like "the
471 user doesn't exist" will be simply failures.)
473 This has no default implementation.
479 my $legacy_name = shift;
480 my $continuation = shift;
482 die "subscribe method not implemented in " . ref($self);
489 C<unsubscribe>($session, $legacy_name, $continuation): This
490 should unsubscribe the user to the given $legacy_name on the
491 legacy service. The continuation should be called when
492 this is complete, with no arguments. (XMPP basically assumes
493 that unsubscriptions always complete.)
500 die "unsubscribe method not implemented in " . ref($self);
507 C<send_message>($session, $to, $body_text, $type, $error_sub):
508 Send the given message to the given user of the given (XMPP message)
509 type. That is, the XMPP user has sent a message to
510 somebody@transport.type, and this method needs to implement the
511 sending of that message along the transport.
513 If the message sending fails, call the $error_sub with the appropriate
514 error name. The standard specifies (converted to Thrasher's
521 C<item_not_found>: Legacy User address is not valid.
525 C<registration_required>: Jabber User is not registered with Gateway.
527 C<service_unavailable>: Legacy User is offline and Legacy Service (or
528 Gateway) does not provide offline message storage.
530 C<remote_server_timeout>: Legacy Service cannot be reached.
534 C<$to> will be the legacy name, not the JID. If you need them, you can
535 either convert them back out or get the sender JID from the session.
537 This has no default implementation.
543 die "send_message method not implemented in " . ref($self);
550 C<outgoing_chatstate>($session, $to, $chatstate):
551 Called when a chatstates stanza is received from the XMPP user
552 directed at the legacy username C<$to>. C<$to> is as for send_message.
554 The default implementation is to ignore chatstates.
558 sub outgoing_chatstate
{
559 my ($self, $session, $to, $chatstate) = @_;
567 C<subscribed>($session, $component, $legacy_username): The user has accepted
568 the subscription request by the given legacy username; handle it
569 as you should. ::Session actually takes care of the presence tags.
571 The default implementation will propogate presence information stored
572 in $session->{presence_waiting_for_subscribe} as appropriate. You will
573 need to override this method, but the override should call this back.
581 my $component = shift;
582 my $legacy_username = shift;
584 if (my $presence_info =
585 delete $session->{presence_waiting_for_subscribe
}->{$legacy_username}) {
586 log("Using stored presence information for $legacy_username");
587 $component->send_presence($session->{jid
}, $legacy_username,
589 $session->resend_displayname($legacy_username);
597 C<unsubscribed>($session, $component, $legacy_username): The user has rejected
598 a subscription request or removed a subscription. Handle it as
599 you should for your protocol.
601 This has no default implementation.
607 die "unsubscribed method not implemented in " . ref($self);
614 C<ft_local_ready>($ft_id):
615 Called when the file transfer proxy to the user is ready for I/O to
618 Must return true of the protocol should continue to receive this
619 notification when the proxy is ready. If the protocol will somehow
620 manage this itself, return false.
626 C<ft_id>: Protocol's file transfer identifier or object.
633 my ($self, $id) = @_;
634 die 'ft_local_ready method not implemented in ' . ref($self);
641 C<get_displayname>($jid, $legacy_username):
642 Returns the displayname of the legacy user as it should be presented to $jid.
644 The default implementation should be sufficient.
648 sub get_displayname
{
649 my ($self, $jid, $legacy_username) = @_;
651 return $self->{'displayname'}->{$jid}->{$legacy_username};
658 =head2 Protocol Interface
660 These are methods that you will call in your protocol handler to
661 inform the component about events. Default implementations are
662 provided in Thrasher::Protocol, and are likely to be sufficient
665 Note how all these method names have gerund names.
671 C<adding_contact>($legacy_username_from, $jid_to): A legacy user
672 is adding a user on this transport to their roster. The component will
673 handle the XML going out to the user when you call
674 C<$component->add_contact($jid_to, $legacy_username_from)>.
676 The default implementation of this does that and is probably
683 my $legacy_username_from = shift;
684 my $subscription_to = shift;
686 my $subscription_from = $self->{backend
}->legacy_name_to_jid
687 ($subscription_to, $legacy_username_from,
688 $self->{component
}->{component_name
}, 'en');
690 log("Adding contact: $legacy_username_from -> $subscription_to");
692 my $session = $self->{component
}->session_for($subscription_to);
693 if (!defined($session)) {
694 log("Getting a contact addition request for a username that "
695 ."doesn't seem to be logged in: $legacy_username_from is "
696 ."asking to subscribe to $subscription_to with the "
697 .$self->name . " protocol.");
701 my $component = $session->{component
};
702 my $jid_to = $session->{jid
};
703 # Implements section 5.1.1 #2
704 $component->add_contact($jid_to, $legacy_username_from);
706 my $state = $self->{backend
}->get_roster_user_state($session->{jid
},
707 $legacy_username_from);
708 debug
("roster state was $state");
709 if ($state != $self->{backend
}->subscribed()) {
710 $self->{backend
}->set_roster_user_state(
712 $legacy_username_from,
713 $self->{backend
}->want_subscribe
722 C<deleting_contact>($subscription_from, $subscription_to): A
723 legacy user is removing the XMPP user from their subscription
726 The default implementation of this is probably sufficient.
730 sub deleting_contact
{
732 my $subscription_from = shift;
733 my $subscription_to = shift;
735 log("Deleting contact: $subscription_from -> $subscription_to");
737 my $session = $self->{username_to_session
}->{$subscription_to};
738 if (!defined($session)) {
739 log("Getting a contact removal request for a username that "
740 ."doesn't seem to be logged in: $subscription_from is "
741 ."asking to unsubscribe from $subscription_to with the "
742 .$self->name . " protocol.");
746 my $component = $session->{component
};
747 my $jid_to = $session->{jid
};
748 $component->delete_contact($jid_to, $subscription_from);
750 $self->{backend
}->set_roster_user_state
751 ($session->{jid
}, $subscription_from,
752 $self->{backend
}->unsubscribed);
759 C<sending_message>($legacy_from, $legacy_to, $message, $is_xhtml_ish):
760 A legacy user has sent a message. $is_xhtml_ish is propagated to the
761 ::Component::send_message method.
763 The default implementation of this is probably sufficient.
767 sub sending_message
{
769 my $legacy_from = shift;
770 my $legacy_to = shift;
772 my $is_xhtml_ish = shift;
774 my $session = $self->{username_to_session
}->{$legacy_to};
775 my $component = $session->{component
};
776 my $jid_to = $session->{jid
};
778 my $jid_from = $self->{backend
}->legacy_name_to_jid
779 ($jid_to, $legacy_from, $component->{component_name
},
780 'en'); # FIXME: Should know lang in component
782 $component->send_message($jid_from, $jid_to, $message, {
783 is_xhtml_ish
=> $is_xhtml_ish,
784 children
=> [ [[ $NS_CHATSTATES, 'active' ], {}, []] ],
792 C<initial_login>($session): This is a chance to be called to be
793 notified about the initial successful login for a given session,
794 an opportunity to do something with the session.
796 The default behavior of this function is to set the current
797 session state to "online".
805 $self->set_session_state($session, 'online');
812 C<incoming_chatstate>($session, $sender, $state_tag): Send a message
813 from the legacy $sender with only the given chatstate $state_tag for
818 sub incoming_chatstate
{
819 my ($self, $session, $sender, $state_tag) = @_;
821 my $clean_sender = $self->process_remote_username($sender);
822 my $component = $session->{'component'};
823 my $jid_from = $self->{'backend'}->legacy_name_to_jid(
826 $component->{'component_name'},
827 # FIXME: Should know lang in component
831 $component->xml_out([
832 [$NS_COMPONENT, 'message'], {
834 to
=> $session->{'full_jid'},
837 [ [[ $NS_CHATSTATES, $state_tag ], {}, []] ]
845 C<set_session_state>($session, $state): The session's state is one
846 of several strings indicating what the state of the session is.
848 The first state is 'disconnected'. The user is disconnected, and
849 quite frankly we shouldn't even have a session unless they're
850 trying to connect right now.
852 The second state is 'logging in'; the user has indicated they
853 wish to log in and they are in the process of doing so. In this
854 state, everything that would normally go out the connection needs
855 to be stored away with the connection is being made, and if the
856 connection fails, actions may need to be taken.
858 The third state is 'online', which means the user is online and
859 all actions can be taken immediately.
861 In particular, calling C<set_session_state($session, 'online')>
862 will cause all the deferred processing to take place, which is
863 the primary purpose of this method. Going from "logging in"
864 to "login failed" will cause all deferred error processing to
865 occur, then the state will be switched to "disconnected".
869 sub set_session_state
{
874 my $current_state = $session->{protocol_state
};
875 $session->{protocol_state
} = $state;
877 if ($state eq 'online' && $current_state eq 'logging in') {
878 succeeded
('legacy_login_' . $session->{internal_id
});
881 if ($state eq 'login failed' && $current_state eq 'logging in') {
882 failed
('legacy_login_' . $session->{internal_id
});
890 C<user_presence_update($session, $type, $show, $status)>: One of our
891 XMPP users has sent us a presence update, and we need to reflect
892 that back out to the transport.
894 For whatever action you need to take, you really ought to use
895 C<do_when_logged_in>, so you wait until the user is fully
896 online before trying to send the update.
898 This is the "general" presence update.
902 sub user_presence_update
{
904 die "user_presence_update not implemented in " . ref($self);
911 C<user_targeted_presence_update($session, $type, $show, $status,
912 $target_user)>: The XMPP user has sent a targetted presence update,
913 at the $target_user (which will already be converted to the
916 Note that targeted presence updates I<generally> accompany
917 general presence updates (as processed in C<user_presence_update>),
918 so the naive implementation that sets the presence on the legacy
919 user is probably not desirable; you should do something more
920 intelligent, though I'm still not sure exactly what.
924 sub user_targeted_presence_update
{
926 die "user_targeted_presence_update not implemented in "
934 C<legacy_presence_update($session, $legacy_name, $type, $show,
935 $status)>: A legacy user has sent a presence update of some
936 kind. Translate it into the $type, $show, and $status of XMPP,
937 and call this method. Implemented in Thrasher::Protocol and
938 probably doesn't need to be overridden.
942 sub legacy_presence_update
{
945 my $legacy_name = shift;
950 # If the XMPP user has still not accepted this presence,
951 # store it away in the session
952 if ((my $state = $self->{backend
}->get_roster_user_state
953 ($session->{jid
}, $legacy_name)) !=
954 $self->{backend
}->subscribed) {
955 log("Storing presence information for $legacy_name because state is $state");
956 $session->{presence_waiting_for_subscribe
}->{$legacy_name} =
957 [$type, $show, $status];
960 log("Not storing presence info for $legacy_name because state is $state");
963 $session->{component
}->send_presence
964 ($session->{jid
}, $legacy_name, $type, $show, $status);
971 C<disconnecting>($session): Call this when the protocol is
972 disconnecting from the remote service, regardless of the reason.
973 Thrasher will work out whether the user asked for it or not,
974 and take appropriate action.
982 my $state = $session->{protocol_state
};
984 # If the user is already at "disconnected", then the user
985 # requested this and life is good.
986 if ($state eq 'disconnected') {
989 if ($state eq 'logging in') {
990 # FIXME: Login evidently failed, shouldn't we find out
993 if ($state eq 'online') {
994 # Note we can only get here if we successfully logged in,
995 # so we shouldn't see the error case of using all
996 # connections because the user gave the wrong password.
997 log("Connection for " . $session->{full_jid
} .
998 "unexpectedly dropped, scheduling re-connection.");
1000 $session->{component
}->login($session->{full_jid
});
1008 =head2 Protocol Services
1010 These are methods that multiple protocols will likely need to use,
1011 so we centralize them here. However, you are not required to use them.
1017 C<set_current_legacy_roster>($session, $current_roster): The backend is
1018 required to maintain a copy of the legacy roster, as reflected
1019 in the user's XMPP roster. (Which can go out of sync if they fiddle
1020 with it while not connected to the gateway, or if the gateway isn't
1021 connected, but we can't do anything about in a standards-complaint
1022 way, so far as I know.) If you call this with the current roster,
1023 with the roster working as defined in L<Thrasher::Roster> (a hash
1024 with the legacy user names as keys and values corresponding to
1025 their current subscription state), this will compare it to the
1026 roster as stored in the backend, and issue the necesary <presence>
1027 tags to bring the user's XMPP roster up-to-date, and also handle
1028 the logic necessary to initiate some changes that need to
1029 go longer term (if the user is subscribed remotely and unsubscribed
1030 locally, the move to want_subscribe, not subscribed).
1034 my $subscribed = Thrasher
::Roster
::subscribed
;
1035 my $unsubscribed = Thrasher
::Roster
::unsubscribed
;
1036 my $want_subscribe = Thrasher
::Roster
::want_subscribe
;
1038 # The actions to take for a given presence transition
1039 my %presence_table =
1041 "$subscribed,$unsubscribed" =>
1042 [['unsubscribed', 'unsubscribe'], $unsubscribed],
1043 # This is a bit crazy, but it's also an unlikely transition
1044 "$subscribed,$want_subscribe" =>
1045 [['unsubscribe', 'unsubscribed', 'subscribe'], $want_subscribe],
1046 "$unsubscribed,$subscribed" =>
1047 [['subscribe', 'subscribed'], $want_subscribe],
1048 "$unsubscribed,$want_subscribe" =>
1049 [['subscribe'], $want_subscribe],
1050 "$want_subscribe,$subscribed" =>
1051 [['subscribe', 'subscribed'], $want_subscribe],
1052 "$want_subscribe,$unsubscribed" =>
1053 [['unsubscribe', 'unsubscribed'], $unsubscribed]
1056 sub set_current_legacy_roster
{
1058 my $session = shift;
1059 my $current_legacy_roster = shift;
1061 my $jid = $session->{jid
};
1063 my $current_roster =
1064 $self->{backend
}->get_roster($jid);
1066 my $roster_diffs = roster_diff
($current_roster,
1067 $current_legacy_roster);
1069 my $component = $session->{component
};
1071 for my $legacy_username (sort keys %$roster_diffs) {
1072 my $key = join ",", @
{$roster_diffs->{$legacy_username}};
1074 # FIXME: We need to centralize the process of unsubscribing
1075 # and make that a callback, so that avatar handling can
1076 # catch unsubscriptions and remove dead avatars.
1078 my ($presence_to_send, $new_state) =
1079 @
{$presence_table{$key}};
1081 for my $presence (@
$presence_to_send) {
1082 $component->send_presence($jid, $legacy_username,
1086 # Set the actual current value as processed through the
1088 $current_legacy_roster->{$legacy_username} = $new_state;
1091 $self->{backend
}->set_roster($jid, $current_legacy_roster);
1098 C<set_displayname>($jid, $legacy_username, $displayname_value):
1099 Stores the displayname of the legacy user as it should be presented to $jid.
1101 If this is overridden, get_displayname() should be, too.
1105 sub set_displayname
{
1106 my ($self, $jid, $legacy_username, $displayname) = @_;
1108 debug
("set_displayname($jid, $legacy_username, $displayname) called\n");
1110 if (not $displayname) {
1112 delete($self->{'displayname'}->{$jid}->{$legacy_username});
1113 return $displayname;
1116 my $displayname_was = $self->get_displayname($jid, $legacy_username);
1117 if ($displayname_was && $displayname_was eq $displayname) {
1118 # Don't send update if unchanged.
1119 return $displayname;
1122 $self->{'displayname'}->{$jid}->{$legacy_username} = $displayname;
1124 # Push change into $jid's roster.
1125 my $component = $self->{'component'};
1126 my $legacy_jid = $component->legacy_name_to_xmpp($jid, $legacy_username);
1127 $component->set_roster_name($jid, $legacy_jid, $displayname);
1129 return $displayname;
1136 =head2 PRE-CANNED ERRORS
1138 Some errors in the protocol that need to be reflected back to the user
1139 are fairly common across protocols. These methods can be called
1140 to provide certain canned error messages back out to the user.
1141 If you're I<really> lucky, they'll even be localized!
1143 These will also handle logging out the user if appropriate
1144 (specifically, the component showing as "offline") and setting
1145 an appropriate detailed presence for the component.
1151 C<wrong_authentication>($jid): Will send a message back of the form "The
1152 username and password you have tried to use is being reported as
1153 invalid by the remote service."
1157 # These will eventually be localized as well
1162 my $error_type = shift;
1164 log("Handling error: $error");
1166 $self->{component
}->send_error_message($jid, $error, $error_type);
1169 sub wrong_authentication
{
1173 $self->_handle_error($jid, 'The username and password you have '
1174 .'tried to use is being reported as '
1175 .'invalid by the remote service.',
1183 C<name_in_use>($jid): "The name you are using with this service is
1184 logged in at another location."
1186 Note that if the protocol in question already automatically sends
1187 out a message like this, you should not send another. (AIM for
1188 instance will automatically send this out.)
1196 $self->_handle_error($jid, 'The name you are using with this '
1197 .'service is logged in at another location.',
1205 C<invalid_username>($jid): "The username you are trying to
1206 use with this service is an invalid username, according to
1209 Use this when the username is syntactically invalid. Incorrect
1210 authentication credentials for an otherwise syntactically-valid
1211 username is C<wrong_authentication>.
1215 sub invalid_username
{
1219 $self->_handle_error
1220 ($jid, "The username you are trying to use with "
1221 ."this service is an invalid username, according "
1222 ."to this service.", 'not_acceptable');
1229 C<network_error>($jid): "There was a network error while
1230 attempting to connect to the remote service."
1238 $self->_handle_error($jid, 'There was a network error while '
1239 .'attempting to connect to the remote '
1240 .'service.', 'service_unavailable');
1247 C<process_remote_username>($username): This gives the backend an
1248 opportunity to munge the username if needed. This gives you the
1249 opportunity to make a protocol-specific hack for things like
1250 case sensitivity or other protocol-specific things. AIM, for instance,
1251 will very freely send "Prof Gilzot", "profgilzot", and "ProfGilzot",
1252 all within the same session, but in general Thrasher will (properly,
1253 IMHO) consider that three separate names unless you normalize
1254 them with this function.
1256 The default, of course, is to do nothing to the string.
1260 sub process_remote_username
{
1262 my $username = shift;
1271 C<fake_up_a_legacy_name>($user_jid, $jid, $legacy_guess): Gives the
1272 protocol a chance to change the backend's $legacy_guess for $jid's
1273 legacy ID. The default is to accept the guess the backend has already
1274 made; a protocol may override this and return a different string to
1275 improve upon that guess.
1277 The default is to do return the backend's guess unmodified.
1281 sub fake_up_a_legacy_name
{
1282 my ($self, $user_jid, $jid, $legacy_guess) = @_;
1284 return $legacy_guess;