1 package Thrasher
::Protocol
::Purple
;
7 Thrasher::Protocol::Purple - test protocol for Thrasher Bird
11 This package is the base class for protocols using libpurple.
12 Each protocol may have slightly different details w.r.t. how they work
13 with Thrasher, which is handled by specializing them further,
14 such as with Thrasher::Protocol::Purple::AIM. As you find such
15 differences, please be sure to add hooks in here, not hack on
20 use base
'Thrasher::Protocol';
22 use Thrasher
::Component
qw(strip_resource);
23 use Thrasher
::Log
qw(log logger debug);
25 use Thrasher
::Session
;
26 use Thrasher
::XMPPStreamIn
;
27 use Glib
qw(G_PRIORITY_DEFAULT);
33 use Thrasher
::Protocol
::Purple
::Vars
qw(:all);
37 use Thrasher
::Plugin
::Vcard
;
38 use Thrasher
::Plugin
::EntityCapabilities
;
43 THPPW
::thrasher_purple_debug
($DEBUG);
45 # Initialize the wrapper
46 THPPW
::thrasher_wrapper_init
47 (Thrasher
::error_wrap
("timeout_add", \
&_timeout_add
),
48 Thrasher
::error_wrap
("input_add", \
&_input_add
),
49 Thrasher
::error_wrap
("source_remove", \
&_source_remove
),
50 Thrasher
::error_wrap
("incoming_msg", \
&_incoming_msg
),
51 Thrasher
::error_wrap
("presence_in", \
&_presence_in
),
52 Thrasher
::error_wrap
("subscription_add", \
&_subscription_add
),
53 Thrasher
::error_wrap
("legacy_user_add_user", \
&_legacy_user_add_user
),
54 Thrasher
::error_wrap
("connection_error", \
&_connection_error
),
55 Thrasher
::error_wrap
("connection", \
&_connection
),
56 Thrasher
::error_wrap
("incoming_chatstate", \
&_incoming_chatstate
),
59 # Initialize the remainder
60 THPPW
::thrasher_init
();
62 # Globalize component object so we can receive messages
63 our $global_component;
66 return 'Thrasher::EventLoop::Glib';
69 # This internal routine allows us to group the initial
70 # subscribe additions and throw them back.
74 if (! $session || !$session->{'jid'}) {
75 debug
("_initial_roster: No session. Must be post-logoff?");
79 debug
("_initial_roster($session->{jid}) called\n");
83 foreach my $username (keys %{$session->{initial_roster
}}) {
84 $username = $session->{protocol
}->process_remote_username($username);
85 $roster->{$username} = Thrasher
::Roster
::subscribed
();
86 # We should also handle presence info here (?)
89 $session->{protocol
}->set_current_legacy_roster($session, $roster);
91 delete $session->{initial_roster_timeout_active
};
92 $session->{initial_roster
} = [];
94 # Thrasher can now be sure the protocol is completely online and
95 # ready to e.g. send IMs queued while login was in progress.
96 if ($session->{'protocol_state'} eq 'logging in') {
97 $session->{'protocol'}->set_session_state($session, 'online');
100 # We don't want the timeout to loop, so destroy with 0
104 # This appears to only be called for things on our initial roster.
105 # FIXME: If that's true, change this.
106 sub _subscription_add
{
107 my $orig_jid = shift;
108 my $orig_sender = shift;
111 my $jid = Encode
::decode
("UTF-8", $orig_jid);
112 my $sender = Encode
::decode
("UTF-8", $orig_sender);
114 debug
("_subscription_add($orig_jid, $orig_sender) called\n");
116 my $session = $global_component->session_for($jid);
118 # Set a timeout if we have no previous jid information
119 if (not defined $session->{initial_roster
}) {
121 # Set a flag so we don't push subscription additions
123 $session->{initial_roster_timeout_active
} = 1;
125 # Heuristically manage our initial roster as we cannot
126 # tell when the libpurple protocols are done giving us the
127 # subscribe user list.
128 Glib
::Timeout
->add(5000,
134 # Verify we're actually within a timeout
135 if ($session->{initial_roster_timeout_active
}) {
136 # This is a bit ugly, but it allows us to bind sender/status
137 # info to JIDs for timeouts
138 $session->{initial_roster
}{$sender} = $status;
140 # We aren't in a timeout, we need to send new subscribe info up
144 # Subscription-in information has the presence information
145 # loaded onto it too, at least for AIM
147 _presence_in
($orig_jid, $orig_sender, undef, undef, $status);
150 debug
("_subscription_add done\n");
155 sub _legacy_user_add_user
{
156 my $jid_target = shift;
157 my $legacy_username_adding = shift;
159 $jid_target = Encode
::decode
("UTF-8", $jid_target);
160 $legacy_username_adding = Encode
::decode
("UTF-8", $legacy_username_adding);
162 log("$legacy_username_adding requesting add for $jid_target");
164 my $session = $global_component->session_for($jid_target);
167 $session->{protocol
}->adding_contact($legacy_username_adding,
170 log("Got request to add user $jid_target, but $jid_target is "
175 sub process_message
{ return $_[1]; }
177 # Callback of presence in
186 $jid = Encode
::decode
("UTF-8", $jid);
187 $sender = Encode
::decode
("UTF-8", $sender);
188 $alias = Encode
::decode
("UTF-8", $alias);
189 $group = Encode
::decode
("UTF-8", $group);
190 $status = Encode
::decode
("UTF-8", $status);
191 $message = Encode
::decode
("UTF-8", $message);
193 debug
("_presence_in($jid, $sender, $status) called\n");
195 my $session = $global_component->session_for($jid);
197 # HACK: No _connection{,_error} yet, but there's *something*
198 # alive on the purple side or we wouldn't be getting presence
199 # from it (e.g. MSN). Note this ASAP in case logout() is called.
200 $session->{'purple_connection_created'} = 1;
203 my $self = $session->{protocol
};
204 if (!defined($self)) {
205 debug
("No session defined for $jid, must be post-logoff?");
209 my $clean_sender = $self->process_remote_username($sender);
212 $message =~ s/([\x80-\xff])/'&#' . ord($1) . ';'/ge;
215 # Nothing is done with protocol?
216 my $xmpp_presence = $purple_presence_to_xmpp{$status};
217 if ($xmpp_presence) {
218 my ($type, $show) = @
{$xmpp_presence};
219 $self->legacy_presence_update($session,
226 log("Unknown presence status of $status was sent by "
227 ."$clean_sender to $jid.");
231 debug
("_presence_in done\n");
235 sub _incoming_chatstate
{
236 my ($orig_jid, $orig_sender, $state) = @_;
237 debug
("_incoming_chatstate($orig_jid, $orig_sender, $state) called\n");
240 # loosely <http://xmpp.org/extensions/xep-0085.html>
241 if ($state == $THPPW::PURPLE_TYPING
) {
242 $state_tag = 'composing';
244 elsif ($state == $THPPW::PURPLE_TYPED
) {
245 $state_tag = 'paused';
247 elsif ($state == $THPPW::PURPLE_NOT_TYPING
) {
248 $state_tag = 'inactive';
254 my $jid = Encode
::decode
('UTF-8', $orig_jid);
255 my $sender = Encode
::decode
('UTF-8', $orig_sender);
257 my $session = $global_component->session_for($jid);
259 debug
("No session?!!\n");
262 if (! $session->{'protocol'}) {
263 debug
("No session protocol?!!\n");
266 $session->{'protocol'}->incoming_chatstate($session, $sender, $state_tag);
272 my $jid = Encode
::decode
("UTF-8", $orig_jid);
273 debug
("_connection($jid) called\n");
275 my $session = $global_component->session_for($jid);
277 # Component::logout and thrasher.c:thrasher_logout() will
278 # happily destroy the session and thrasher_connection while
279 # libpurple is waiting asynchronously for connection events.
280 # Once the connection completes and libpurple starts firing
281 # callbacks, weird errors arise because the session is gone
282 # and Thrasher has lost track of what is connected.
284 # Maybe we should reject the logout and defer it to
285 # _connection{,_error}, relying on one of them always being
287 log("_connection($jid): No session? Assuming already logged out.");
288 # Ensure the thrasher_connection gets gone.
289 Glib
::Timeout
->add(1,
291 # Log off just after logon finishes, not during.
293 # Turns out purple_connection_set_state()
294 # (which called the connected ui_op) crashes
295 # if prpl_info is yanked out from under it.
296 THPPW
::thrasher_action_logout
($orig_jid);
303 my $protocol = $session->{'protocol'};
305 log("_connection($jid): No protocol?!!");
309 $session->{'purple_connection_created'} = 1;
310 delete($protocol->{'connection_started_at'}->{$jid});
311 my $continuation = delete($session->{'connection_cb'});
313 $continuation->($session);
316 log("_connection($jid): No connection_cb?!!");
320 # But libpurple prpl might not be ready to send IMs queued during
321 # login. Wait until _initial_roster() for online protocol_state.
323 # If after no _subscription_add()/_initial_roster() happens
324 # (perhaps the account has no current legacy roster at all?)
325 # ensure session is eventually set online anyway.
328 if (! $session->{'initial_roster_timeout_active'}
329 && ! $session->{'initial_roster'}
330 && $session->{'protocol_state'} eq 'logging in') {
331 debug
("Never called _initial_roster($session->{jid})?\n");
332 $protocol->set_session_state($session, 'online');
334 return 0; # No repeat
343 sub _connection_error
{
345 my $error_code = shift;
348 $jid = Encode
::decode
("UTF-8", $jid);
349 $message = Encode
::decode
("UTF-8", $message);
351 debug
("_connection_error($jid)\n");
353 my $session = $global_component->session_for($jid);
355 log("No session?!! Error was $error_code/'$message'.");
358 my $protocol = $session->{protocol
};
360 # Clear connection state.
361 delete($protocol->{'connection_started_at'}->{$jid});
364 log("_connection($jid): No protocol?!!");
368 my $attempt_reconnect = 0;
371 if ($session->{status
} =~ /disconnecting/) {
372 log("Got error code $error_code, but ignoring it since "
373 ."we're in the middle of disconnecting.");
377 # Some of these cases are poorly tested since it's either
378 # hard or borderline impossible for them to occur.
379 # We also have to think about whether to attempt reconnection
381 switch
($error_code) {
382 case
($ERROR_NETWORK_ERROR) {
383 $protocol->network_error($jid);
384 $error = "Network error, attempting reconnection";
385 $attempt_reconnect = 1;
387 case
($ERROR_INVALID_USERNAME) {
388 $protocol->invalid_username($jid);
389 $error = "Remote server reports invalid username; please reregister";
391 case
($ERROR_AUTHENTICATION_FAILED) {
392 $protocol->wrong_authentication($jid);
393 $error = "Username or password invalid; please register with correct information";
395 case
($ERROR_AUTHENTICATION_IMPOSSIBLE) {
396 $protocol->_handle_error
397 ($jid, 'Thrasher Bird can not negotiate an '
398 .'authentication technique with the remote '
399 .'service', 'service_unavailable');
400 # This is a bad one, we don't know what to do.
401 $error = "Authentication impossible";
403 case
($ERROR_NO_SSL_SUPPORT) {
404 $protocol->_handle_error
405 ($jid, 'libpurple was compiled without SSL '
406 .'support, but SSL is required by the '
407 .'remote service.', 'service_unavailable');
408 $error = "Thrasher Bird is unable to connect";
410 case
($ERROR_ENCRYPTION_ERROR) {
411 $protocol->_handle_error
412 ($jid, 'There was an error negotiating SSL with '
413 .'the remote service, or the remote service '
414 .'does not support encryption but an account '
415 .'option was set to require it.',
416 'service_unavailable');
417 $error = "Thrasher Bird is unable to connect";
419 case
($ERROR_NAME_IN_USE) {
420 $protocol->name_in_use($jid);
421 $error = "The remote service reports your username is in use";
423 case
($ERROR_INVALID_SETTINGS) {
424 $protocol->invalid_username($jid);
425 $error = "Remote server reports invalid username; please reregister";
427 case
($ERROR_OTHER_ERROR) {
428 my $error_message = "Unknown connection error.";
430 $error_message .= ' The legacy service reported: '
433 $protocol->_handle_error
434 ($jid, $error_message, 'internal_server_error');
437 log("Got connection error: $error_code for $jid");
441 # This needs to be kept in sync with libpurple's
442 # connection.c -> purple_connection_is_fatal, which
443 # tracks whether libpurple is going to automatically
444 # log out our connection in purple_connection_error_reason.
445 my $purple_will_kill = !($error_code == $ERROR_NETWORK_ERROR ||
446 $error_code == $ERROR_ENCRYPTION_ERROR);
447 $session->{purple_will_kill
} = $purple_will_kill;
448 $session->{purple_will_kill
} ||= $protocol->purple_forces_kill;
450 $protocol->{component
}->logout($session, undef,
453 # Probe the user's presence to trigger a re-connect attempt
454 # if they are still online. They may have gone offline in the
455 # meantime, in which case we don't want to reconnect.
456 if ($attempt_reconnect) {
457 my $full_jid = $session->{full_jid
};
459 $protocol->{component
}->send_presence_xml($full_jid,
464 log("Going to attempt reconnect in 15 seconds for $session->{full_jid}");
466 _timeout_add
(15000, $callback, undef, "Reconnect $session->{full_jid}");
469 my $continuation = delete($session->{'connection_cb'});
471 $continuation->(undef);
474 log("_connection_error($jid): No connection_cb?!!");
477 # If you want C-end handling, we need to throw some returns above
481 # Callback for incoming messages
483 my ($jid, $sender, $alias, $message, $flags) = @_;
485 $jid = Encode
::decode
("UTF-8", $jid);
486 $sender = Encode
::decode
("UTF-8", $sender);
487 $message = Encode
::decode
("UTF-8", $message);
489 debug
("_incoming_msg from $sender for $jid\n");
491 my $session = $global_component->session_for($jid);
492 my $protocol = $session->{protocol
};
494 my $clean_sender = $session->{protocol
}->process_remote_username($sender);
496 $message =~ s/([\x80-\xff])/'&#' . ord($1) . ';'/ge;
498 if ($flags & $THPPW::PURPLE_MESSAGE_AUTO_RESP
) {
499 $message = '(auto-reply) ' . $message;
502 # Type is currently hard coded...
503 $protocol->sending_message($clean_sender, $session->{legacy_login
},
506 debug
("_incoming_msg done\n");
508 # Thrasher::Protocol::sending_message currently has no returned value
515 my $registration_info = shift;
517 debug
("###registration($jid) called");
519 # As a special case, if the registration info's username is
520 # "fail", we return an error given by $registration_info->{password}.
521 if ($registration_info->{username
} eq 'fail') {
522 return 0, $registration_info->{password
};
524 return $self->SUPER::registration
($jid, $registration_info);
528 # This really should be overridden
529 sub name
{ 'Purple' }
531 sub identifier
{ 'aim' }
533 # This method identifies which protocol we're using in Pidgin.
537 die "prpl not set up for " . ref($self);
540 sub create_login_session
{
542 my $continuation = shift;
543 my $registration_info = shift;
544 my $full_jid = shift;
545 my $component = shift;
546 my $jid = strip_resource
($full_jid);
548 debug
("###create_login_session($full_jid)");
550 # FIXME: Check for existing logins.
551 my $session = new Thrasher
::Session
($full_jid,
554 $registration_info->{username
});
555 $global_component = $component;
556 $self->set_session_state($session, 'logging in');
557 $component->set_session_for($jid, $session);
559 for my $key (keys %$registration_info) {
560 $registration_info->{$key} =
561 Encode
::encode
("UTF-8", $registration_info->{$key});
564 if (!$self->valid_id($registration_info->{username
}) ||
565 !$self->valid_password($registration_info->{password
})) {
566 $self->wrong_authentication($full_jid);
567 $continuation->('not_acceptable');
568 $component->logout($session);
572 my $jid_enc = Encode
::encode
('UTF-8', $jid);
575 proto
=> $self->prpl,
577 %{ $self->{'configuration'}->{'extra_login_args'} || {} },
579 my $login_error = THPPW
::thrasher_action_login
(\
%login_args);
580 my $last_connection_started_at = $self->{'connection_started_at'}->{$jid};
582 # PurpleAccount already exists. But if component called here,
583 # the session must already be gone. Thus, must have logged out
584 # during the async libpurple connection attempt and now trying
586 if ($login_error == 2
587 && $last_connection_started_at
588 && time() - $last_connection_started_at > 600) {
589 # Async libpurple login started more than 10 minutes ago but
590 # _connection{,_error} has still not come back. Destroy the
591 # old login attempt and start a new one.
593 # E.g. the PURPLE_CONNECTED state was never reached due to a
594 # MSN ADL/FQY counting bug?
595 debug
('Discarding aged PurpleAccount attempt from '
596 . $last_connection_started_at);
597 THPPW
::thrasher_action_logout
($jid_enc);
598 $login_error = THPPW
::thrasher_action_login
(\
%login_args);
599 # In theory, logout removed the PurpleAccount so the new
600 # $login_error can't be 2. But--don't risk it!
602 if ($login_error == 2) {
603 # Reject for now. Eventually _connection or _connection_error
604 # will come back and login attempts will be possible again.
606 # Must not be confused with the bad credentials case lest
607 # Component put the failure in authentication_errors and lock
608 # logins until the registration changes.
610 # Could have this session "take over" the PurpleAccount, but
611 # what if credentials differ? Or if libpurple never finishes?
612 $continuation->('conflict', 1);
613 $component->logout($session);
614 if (not $self->{'connection_started_at'}->{$jid}) {
615 $self->{'connection_started_at'}->{$jid} = time();
620 elsif ($login_error != 0) {
621 # Rejected before we're even trying to connect pretty
622 # much means syntactically invalid credentials
623 $continuation->('not_acceptable');
624 $component->logout($session);
628 $self->{'connection_started_at'}->{$jid} = time();
629 $session->{'connection_cb'} = $continuation;
636 debug
("###initial_login called");
638 $session->{logged_in
} = 1;
645 # FIXME: Can occur if the first action after aim.transport comes
646 # online is to unregister.
647 if ($global_component) {
648 my $session = $global_component->session_for($jid);
650 $self->{component
}->logout($session);
654 log("What? No \$global_component in remove?!?");
657 # A user who attempted to unregister while the transport was
658 # offline won't log in when it comes back up (and thus doesn't
659 # need to log out) but might still be registered with the backend.
660 $self->{backend
}->remove($jid);
666 my $target_name = shift;
667 my $continuation = shift;
669 debug
("###subscribe($session->{jid}, $target_name) called");
671 $session->{subscribed
}->{$target_name} = 1;
673 THPPW
::thrasher_action_buddy_add
(Encode
::encode
("UTF-8",
675 Encode
::encode
("UTF-8", $target_name));
683 my $target_name = shift;
684 my $continuation = shift;
686 debug
("###unsubscribe($session->{jid}, $target_name) called");
688 if (!(delete $session->{subscribed
}->{$target_name})) {
689 print STDERR
"Warning, removing nonexistant contact\n";
692 THPPW
::thrasher_action_buddy_remove
(Encode
::encode
("UTF-8",
694 Encode
::encode
("UTF-8", $target_name));
701 my ($session, $continuation) = @_;
703 debug
("###logout($session->{jid}) called");
705 if ($session->{purple_connection_created
}
706 && !$session->{purple_will_kill
}) {
707 THPPW
::thrasher_action_logout
(Encode
::encode
("UTF-8", $session->{jid
}));
709 elsif (! $session->{purple_connection_created
}) {
710 debug
('No purple connection created to log out.');
712 # Update component and ConnectionManager with the status of
713 # this connection attempt before the connection_cb
714 # continuation is thrown out. If the attempt does succeed,
715 # _connection() will immediately log it out anyway.
716 my $connection_cb = delete($session->{'connection_cb'});
717 if ($connection_cb) {
718 $connection_cb->(undef, 1);
722 $continuation->($session);
723 return $self->SUPER::logout
(@_);
726 sub debug_logged_in
{
727 my $component = $global_component;
729 debug
("No component?!!\n");
733 my $protocol = $component->{'protocol'};
735 debug
("No protocol?!!\n");
739 print STDERR
'prpl = ' . $protocol->prpl() . "\n";
741 if ($protocol->{'username_to_session'}) {
742 print STDERR
"protocol->username_to_session:\n";
743 while (my ($legacy_name, $session)
744 = each(%{$protocol->{'username_to_session'}})) {
745 print STDERR
"\t$legacy_name => $session\n";
749 debug
("No username_to_session?!!\n");
752 if ($component->{'sessions'}) {
753 print STDERR
"component->sessions:\n";
754 while (my ($jid, $session) = each(%{$component->{'sessions'}})) {
755 print STDERR
"\t$jid => $session\n";
759 debug
("No component sessions?!!\n");
762 THPPW
::thrasher_action_debug_logged_in
();
768 my ($session, $to, $body_text, $type, $error_sub) = @_;
770 debug
("###send_message called");
771 if ($session->{'protocol_state'} eq 'logging in') {
772 debug
("###send_message deferred; $session->{jid} still logging in.\n");
773 $session->on_connection_complete(sub {
774 $self->send_message(@orig_args);
779 $body_text = $self->process_message($body_text);
781 debug
("###Message From: ".$session->{jid
}.", To: $to, body: $body_text\n");
783 my $result = THPPW
::thrasher_action_outgoing_msg
784 (Encode
::encode
("UTF-8", $session->{jid
}),
785 Encode
::encode
("UTF-8", $to),
786 Encode
::encode
("UTF-8", $body_text));
787 debug
("Message send result: $result\n");
790 sub outgoing_chatstate
{
791 my ($self, $session, $to, $chatstate) = @_;
792 debug
("###outgoing_chatstate($session->{jid}, $to, $chatstate)\n");
794 our $chatstate_to_purple ||= {
795 'composing' => $THPPW::PURPLE_TYPING
,
796 'paused' => $THPPW::PURPLE_TYPED
,
797 'inactive' => $THPPW::PURPLE_NOT_TYPING
,
798 'active' => $THPPW::PURPLE_NOT_TYPING
,
800 my $purple_typing_state = $chatstate_to_purple->{$chatstate};
801 if (! defined($purple_typing_state)) {
802 debug
("Untranslated chatstate: '$chatstate'\n");
806 THPPW
::thrasher_action_outgoing_chatstate
($session->{'jid'},
808 $purple_typing_state);
815 my $component = shift;
816 my $legacy_username = shift;
818 debug
("###subscribed called: $legacy_username permitted for $session->{jid}");
820 THPPW
::thrasher_action_buddy_authorize
821 (Encode
::encode
("UTF-8", $session->{jid
}),
822 Encode
::encode
("UTF-8", $legacy_username));
824 $self->SUPER::subscribed
($session, $component, $legacy_username);
830 my $component = shift;
831 my $legacy_username = shift;
833 debug
("###unsubscribed($session->{jid}, $legacy_username) called");
835 if (!defined($legacy_username)) {
836 confess
"Unsubscribing an undef user; shouldn't be called.";
839 THPPW
::thrasher_action_buddy_deauthorize
840 (Encode
::encode
("UTF-8", $session->{jid
}),
841 Encode
::encode
("UTF-8", $legacy_username));
845 my ($self, $id) = @_;
847 THPPW
::thrasher_action_ft_ui_ready
($id);
848 return 1; # repeat this notification.
855 return "Gateway prompt";
861 return $self->{gateway_desc
};
864 sub user_presence_update
{
867 my $type = shift || '';
868 my $show = shift || '';
869 my $status = shift || '';
871 debug
("user_presence_update called\n");
875 # State table for type/show to purple_status
876 if ($show eq 'away') {
880 $purple_status = $purple_presence{'xaway'};
884 $purple_status = $purple_presence{'away'};
888 logger
("Unknown type/show of [$type/$show]");
891 elsif ($show eq 'chat' || $show eq '') {
894 # This seems like it might have more states
895 $purple_status = $purple_presence{'available'};
897 elsif ($type eq 'unavailable') {
899 $purple_status = $purple_presence{'offline'};
902 logger
("Unknown type/show of [$type/$show]");
905 elsif ($show eq 'xa' || $show eq 'xaway') {
906 $purple_status = $purple_presence{'xaway'};
908 elsif ($show eq 'dnd') {
909 $purple_status = $purple_presence{'unavailable'};
912 logger
("Unknown type/show of [$type/$show] (show is completely unrecognized)");
915 if (defined($purple_status)) {
916 THPPW
::thrasher_action_presence
(
917 Encode
::encode
("UTF-8", $session->{jid
}),
918 $purple_status, # integer does not need encoding
919 Encode
::encode
("UTF-8", $status),
923 #debug("User presence update: type: $type, show: $show, purple: $purple_status, status: $status");
926 # Don't do anything with this right now.
927 sub user_targeted_presence_update
{
931 my $type = shift || '';
932 my $show = shift || '';
933 my $status = shift || '';
934 my $target_user = shift || '';
936 #log("User presence update to $target_user: type: $type, show: $show, status: $status");
939 # Subrefs for which to satiate the libpurple monster
941 my $interval = shift;
945 debug
("perl::timeout_add called\n", 3);
947 debug
("\tinterval = $interval\n", 3) if $interval;
948 debug
("\tcode = $code\n", 3) if $code;
949 debug
("\ttrigger = $trigger\n", 3) if $trigger;
951 my $ret = Glib
::Timeout
->add($interval,
955 debug
("Glib::Timeout->add returned [$ret]\n", 3);
961 debug
("perl::timeout_remove called with $_[0]\n", 3);
963 return Glib
::Source
->remove($_[0]);
973 debug
("_input_add\n", 3);
977 debug
("\t$i = $_\n");
982 debug
("\tfd = $fd\n", 3) if $fd;
983 debug
("\tcond = $cond\n", 3) if $cond;
984 debug
("\tcode = $code\n", 3) if $code;
985 debug
("\ttrigger = $trigger\n", 3) if $trigger;
987 $cond = ($cond == 1) ?
'G_IO_IN' : 'G_IO_OUT';
989 $cond = [$cond, 'G_IO_ERR', 'G_IO_HUP', 'G_IO_NVAL'];
991 my $ret = Glib
::IO
->add_watch($fd,
997 debug
("Glib::IO->add_watch returned [$ret]\n", 3);
999 debug
("_input_add done\n", 3);
1004 # Returns if the given ID is a valid id for the service. This avoids
1005 # some problems that services have when you jam illegal logins in.
1006 # For instance, log in to Yahoo with a Japanese username, and it
1007 # just hangs on the connection, rather than doing anything.
1008 # Note that this is more about not sending in logins that confuse
1009 # the remote services so badly we get no errors, NOT about precisely
1010 # labelling which fields are possible. If the remote service correctly
1011 # determines the password is invalid, then everything's fine.
1013 my ($self, $username) = @_;
1015 if ($username =~ m{/}) {
1022 sub valid_password
{
1023 my ($self, $password) = @_;
1025 # If the prpl requires a password, _purple_connection_new() will
1026 # fail when password is NULL or zero-length without returning an
1027 # error thrasher_login() can detect. Worse, the check in
1028 # purple_account_connect() is slightly different so it wouldn't
1029 # even be detectable through purple_account_request_password() and
1032 # Registering with an empty password therefore begins an
1033 # apparently successful async login that never completes or
1034 # errors. The user also can't re-register or log out because
1035 # they're already "logging in". :(
1037 # If the prpl has OPT_PROTO_PASSWORD_OPTIONAL or OPT_PROTO_NO_PASSWORD
1038 # the corresponding subclass should override this.
1042 sub purple_forces_kill
{ return 0; }