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_adding_user", \
&_legacy_user_adding_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_adding_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]; }
176 sub process_remote_username
{ return $_[1]; }
178 # Callback of presence in
187 $jid = Encode
::decode
("UTF-8", $jid);
188 $sender = Encode
::decode
("UTF-8", $sender);
189 $alias = Encode
::decode
("UTF-8", $alias);
190 $group = Encode
::decode
("UTF-8", $group);
191 $status = Encode
::decode
("UTF-8", $status);
192 $message = Encode
::decode
("UTF-8", $message);
194 debug
("_presence_in($jid, $sender, $status) called\n");
196 my $session = $global_component->session_for($jid);
197 my $self = $session->{protocol
};
198 if (!defined($self)) {
199 debug
("No session defined for $jid, must be post-logoff?");
203 my $clean_sender = $self->process_remote_username($sender);
206 $message =~ s/([\x80-\xff])/'&#' . ord($1) . ';'/ge;
209 # Nothing is done with protocol?
210 my $xmpp_presence = $purple_presence_to_xmpp{$status};
211 if ($xmpp_presence) {
212 my ($type, $show) = @
{$xmpp_presence};
213 $self->legacy_presence_update($session,
220 log("Unknown presence status of $status was sent by "
221 ."$clean_sender to $jid.");
225 debug
("_presence_in done\n");
229 sub _incoming_chatstate
{
230 my ($orig_jid, $orig_sender, $state) = @_;
231 debug
("_incoming_chatstate($orig_jid, $orig_sender, $state) called\n");
234 # loosely <http://xmpp.org/extensions/xep-0085.html>
235 if ($state == $THPPW::PURPLE_TYPING
) {
236 $state_tag = 'composing';
238 elsif ($state == $THPPW::PURPLE_TYPED
) {
239 $state_tag = 'paused';
241 elsif ($state == $THPPW::PURPLE_NOT_TYPING
) {
242 $state_tag = 'inactive';
248 my $jid = Encode
::decode
('UTF-8', $orig_jid);
249 my $sender = Encode
::decode
('UTF-8', $orig_sender);
251 my $session = $global_component->session_for($jid);
253 debug
("No session?!!\n");
256 if (! $session->{'protocol'}) {
257 debug
("No session protocol?!!\n");
260 $session->{'protocol'}->incoming_chatstate($session, $sender, $state_tag);
266 my $jid = Encode
::decode
("UTF-8", $orig_jid);
267 debug
("_connection($jid) called\n");
269 my $session = $global_component->session_for($jid);
271 # Component::logout and thrasher.c:thrasher_logout() will
272 # happily destroy the session and thrasher_connection while
273 # libpurple is waiting asynchronously for connection events.
274 # Once the connection completes and libpurple starts firing
275 # callbacks, weird errors arise because the session is gone
276 # and Thrasher has lost track of what is connected.
278 # Maybe we should reject the logout and defer it to
279 # _connection{,_error}, relying on one of them always being
281 log("_connection($jid): No session? Assuming already logged out.");
282 # Ensure the thrasher_connection gets gone.
283 Glib
::Timeout
->add(1,
285 # Log off just after logon finishes, not during.
287 # Turns out purple_connection_set_state()
288 # (which called the connected ui_op) crashes
289 # if prpl_info is yanked out from under it.
290 THPPW
::thrasher_action_logout
($orig_jid);
297 my $protocol = $session->{'protocol'};
299 log("_connection($jid): No protocol?!!");
303 $session->{'purple_connection_created'} = 1;
304 delete($protocol->{'connection_started_at'}->{$jid});
305 my $continuation = delete($session->{'connection_cb'});
307 $continuation->($session);
310 log("_connection($jid): No connection_cb?!!");
314 # But libpurple prpl might not be ready to send IMs queued during
315 # login. Wait until _initial_roster() for online protocol_state.
317 # If after no _subscription_add()/_initial_roster() happens
318 # (perhaps the account has no current legacy roster at all?)
319 # ensure session is eventually set online anyway.
322 if (! $session->{'initial_roster_timeout_active'}
323 && ! $session->{'initial_roster'}
324 && $session->{'protocol_state'} eq 'logging in') {
325 debug
("Never called _initial_roster($session->{jid})?\n");
326 $protocol->set_session_state($session, 'online');
328 return 0; # No repeat
337 sub _connection_error
{
339 my $error_code = shift;
342 $jid = Encode
::decode
("UTF-8", $jid);
343 $message = Encode
::decode
("UTF-8", $message);
345 debug
("_connection_error($jid)\n");
347 my $session = $global_component->session_for($jid);
349 log("No session?!! Error was $error_code/'$message'.");
352 my $protocol = $session->{protocol
};
354 # Clear connection state.
355 delete($session->{'connection_cb'});
356 delete($protocol->{'connection_started_at'}->{$jid});
359 log("_connection($jid): No protocol?!!");
363 my $attempt_reconnect = 0;
366 if ($session->{status
} =~ /disconnecting/) {
367 log("Got error code $error_code, but ignoring it since "
368 ."we're in the middle of disconnecting.");
372 # Some of these cases are poorly tested since it's either
373 # hard or borderline impossible for them to occur.
374 # We also have to think about whether to attempt reconnection
376 switch
($error_code) {
377 case
($ERROR_NETWORK_ERROR) {
378 $protocol->network_error($jid);
379 $error = "Network error, attempting reconnection";
380 $attempt_reconnect = 1;
382 case
($ERROR_INVALID_USERNAME) {
383 $protocol->invalid_username($jid);
384 $error = "Remote server reports invalid username; please reregister";
386 case
($ERROR_AUTHENTICATION_FAILED) {
387 $protocol->wrong_authentication($jid);
388 $error = "Username or password invalid; please register with correct information";
390 case
($ERROR_AUTHENTICATION_IMPOSSIBLE) {
391 $protocol->_handle_error
392 ($jid, 'Thrasher Bird can not negotiate an '
393 .'authentication technique with the remote '
394 .'service', 'service_unavailable');
395 # This is a bad one, we don't know what to do.
396 $error = "Authentication impossible";
398 case
($ERROR_NO_SSL_SUPPORT) {
399 $protocol->_handle_error
400 ($jid, 'libpurple was compiled without SSL '
401 .'support, but SSL is required by the '
402 .'remote service.', 'service_unavailable');
403 $error = "Thrasher Bird is unable to connect";
405 case
($ERROR_ENCRYPTION_ERROR) {
406 $protocol->_handle_error
407 ($jid, 'There was an error negotiating SSL with '
408 .'the remote service, or the remote service '
409 .'does not support encryption but an account '
410 .'option was set to require it.',
411 'service_unavailable');
412 $error = "Thrasher Bird is unable to connect";
414 case
($ERROR_NAME_IN_USE) {
415 $protocol->name_in_use($jid);
416 $error = "The remote service reports your username is in use";
418 case
($ERROR_INVALID_SETTINGS) {
419 $protocol->invalid_username($jid);
420 $error = "Remote server reports invalid username; please reregister";
422 case
($ERROR_OTHER_ERROR) {
423 my $error_message = "Unknown connection error.";
425 $error_message .= ' The legacy service reported: '
428 $protocol->_handle_error
429 ($jid, $error_message, 'internal_server_error');
432 log("Got connection error: $error_code for $jid");
436 # This needs to be kept in sync with libpurple's
437 # connection.c -> purple_connection_is_fatal, which
438 # tracks whether libpurple is going to automatically
439 # log out our connection in purple_connection_error_reason.
440 my $purple_will_kill = !($error_code == $ERROR_NETWORK_ERROR ||
441 $error_code == $ERROR_ENCRYPTION_ERROR);
442 $session->{purple_will_kill
} = $purple_will_kill;
443 $session->{purple_will_kill
} ||= $protocol->purple_forces_kill;
445 $protocol->{component
}->logout($session, undef,
448 # Probe the user's presence to trigger a re-connect attempt
449 # if they are still online. They may have gone offline in the
450 # meantime, in which case we don't want to reconnect.
451 if ($attempt_reconnect) {
452 my $full_jid = $session->{full_jid
};
454 $protocol->{component
}->send_presence_xml($full_jid,
459 log("Going to attempt reconnect in 15 seconds for $session->{full_jid}");
461 _timeout_add
(15000, $callback, undef, "Reconnect $session->{full_jid}");
463 # If you want C-end handling, we need to throw some returns above
467 # Callback for incoming messages
469 my ($jid, $sender, $alias, $message, $flags) = @_;
471 $jid = Encode
::decode
("UTF-8", $jid);
472 $sender = Encode
::decode
("UTF-8", $sender);
473 $message = Encode
::decode
("UTF-8", $message);
475 debug
("_incoming_msg from $sender for $jid\n");
477 my $session = $global_component->session_for($jid);
478 my $protocol = $session->{protocol
};
480 my $clean_sender = $session->{protocol
}->process_remote_username($sender);
482 $message =~ s/([\x80-\xff])/'&#' . ord($1) . ';'/ge;
484 if ($flags & $THPPW::PURPLE_MESSAGE_AUTO_RESP
) {
485 $message = '(auto-reply) ' . $message;
488 # Type is currently hard coded...
489 $protocol->sending_message($clean_sender, $session->{legacy_login
},
492 debug
("_incoming_msg done\n");
494 # Thrasher::Protocol::sending_message currently has no returned value
501 my $registration_info = shift;
503 debug
("###registration($jid) called");
505 # As a special case, if the registration info's username is
506 # "fail", we return an error given by $registration_info->{password}.
507 if ($registration_info->{username
} eq 'fail') {
508 return 0, $registration_info->{password
};
510 return $self->SUPER::registration
($jid, $registration_info);
514 # This really should be overridden
515 sub name
{ 'Purple' }
517 sub identifier
{ 'aim' }
519 # This method identifies which protocol we're using in Pidgin.
523 die "prpl not set up for " . ref($self);
526 sub create_login_session
{
528 my $continuation = shift;
529 my $registration_info = shift;
530 my $full_jid = shift;
531 my $component = shift;
532 my $jid = strip_resource
($full_jid);
534 debug
("###create_login_session($full_jid)");
536 # FIXME: Check for existing logins.
537 my $session = new Thrasher
::Session
($full_jid,
540 $registration_info->{username
});
541 $global_component = $component;
542 $self->set_session_state($session, 'logging in');
543 $component->set_session_for($jid, $session);
545 for my $key (keys %$registration_info) {
546 $registration_info->{$key} =
547 Encode
::encode
("UTF-8", $registration_info->{$key});
550 if (!$self->valid_id($registration_info->{username
}) ||
551 !$self->valid_password($registration_info->{password
})) {
552 $self->wrong_authentication($full_jid);
553 $continuation->('not_acceptable');
554 $component->logout($session);
558 my $jid_enc = Encode
::encode
('UTF-8', $jid);
561 proto
=> $self->prpl,
563 %{ $self->{'configuration'}->{'extra_login_args'} || {} },
565 my $login_error = THPPW
::thrasher_action_login
(\
%login_args);
566 my $last_connection_started_at = $self->{'connection_started_at'}->{$jid};
568 # PurpleAccount already exists. But if component called here,
569 # the session must already be gone. Thus, must have logged out
570 # during the async libpurple connection attempt and now trying
572 if ($login_error == 2
573 && $last_connection_started_at
574 && time() - $last_connection_started_at > 600) {
575 # Async libpurple login started more than 10 minutes ago but
576 # _connection{,_error} has still not come back. Destroy the
577 # old login attempt and start a new one.
579 # E.g. the PURPLE_CONNECTED state was never reached due to a
580 # MSN ADL/FQY counting bug?
581 debug
('Discarding aged PurpleAccount attempt from '
582 . $last_connection_started_at);
583 THPPW
::thrasher_action_logout
($jid_enc);
584 $login_error = THPPW
::thrasher_action_login
(\
%login_args);
585 # In theory, logout removed the PurpleAccount so the new
586 # $login_error can't be 2. But--don't risk it!
588 if ($login_error == 2) {
589 # Reject for now. Eventually _connection or _connection_error
590 # will come back and login attempts will be possible again.
592 # Must not be confused with the bad credentials case lest
593 # Component put the failure in authentication_errors and lock
594 # logins until the registration changes.
596 # Could have this session "take over" the PurpleAccount, but
597 # what if credentials differ? Or if libpurple never finishes?
598 $continuation->('conflict');
599 $component->logout($session);
603 elsif ($login_error != 0) {
604 # Rejected before we're even trying to connect pretty
605 # much means syntactically invalid credentials
606 $continuation->('not_acceptable');
607 $component->logout($session);
611 $self->{'connection_started_at'}->{$jid} = time();
612 $session->{'connection_cb'} = $continuation;
619 debug
("###initial_login called");
621 $session->{logged_in
} = 1;
628 # FIXME: Can occur if the first action after aim.transport comes
629 # online is to unregister.
630 if ($global_component) {
631 my $session = $global_component->session_for($jid);
633 $self->{component
}->logout($session);
637 log("What? No \$global_component in remove?!?");
640 # A user who attempted to unregister while the transport was
641 # offline won't log in when it comes back up (and thus doesn't
642 # need to log out) but might still be registered with the backend.
643 $self->{backend
}->remove($jid);
649 my $target_name = shift;
650 my $continuation = shift;
652 debug
("###subscribe($session->{jid}, $target_name) called");
654 $session->{subscribed
}->{$target_name} = 1;
656 THPPW
::thrasher_action_buddy_add
(Encode
::encode
("UTF-8",
658 Encode
::encode
("UTF-8", $target_name));
666 my $target_name = shift;
667 my $continuation = shift;
669 debug
("###unsubscribe($session->{jid}, $target_name) called");
671 if (!(delete $session->{subscribed
}->{$target_name})) {
672 print STDERR
"Warning, removing nonexistant contact\n";
675 THPPW
::thrasher_action_buddy_remove
(Encode
::encode
("UTF-8",
677 Encode
::encode
("UTF-8", $target_name));
684 my ($session, $continuation) = @_;
686 debug
("###logout($session->{jid}) called");
688 if ($session->{purple_connection_created
}
689 && !$session->{purple_will_kill
}) {
690 THPPW
::thrasher_action_logout
(Encode
::encode
("UTF-8", $session->{jid
}));
692 elsif (! $session->{purple_connection_created
}) {
693 debug
('No purple connection created to log out.');
696 $continuation->($session);
697 return $self->SUPER::logout
(@_);
700 sub debug_logged_in
{
701 my $component = $global_component;
703 debug
("No component?!!\n");
707 my $protocol = $component->{'protocol'};
709 debug
("No protocol?!!\n");
713 print STDERR
'prpl = ' . $protocol->prpl() . "\n";
715 if ($protocol->{'username_to_session'}) {
716 print STDERR
"protocol->username_to_session:\n";
717 while (my ($legacy_name, $session)
718 = each(%{$protocol->{'username_to_session'}})) {
719 print STDERR
"\t$legacy_name => $session\n";
723 debug
("No username_to_session?!!\n");
726 if ($component->{'sessions'}) {
727 print STDERR
"component->sessions:\n";
728 while (my ($jid, $session) = each(%{$component->{'sessions'}})) {
729 print STDERR
"\t$jid => $session\n";
733 debug
("No component sessions?!!\n");
736 THPPW
::thrasher_action_debug_logged_in
();
742 my ($session, $to, $body_text, $type, $error_sub) = @_;
744 debug
("###send_message called");
745 if ($session->{'protocol_state'} eq 'logging in') {
746 debug
("###send_message deferred; $session->{jid} still logging in.\n");
747 $session->on_connection_complete(sub {
748 $self->send_message(@orig_args);
753 $body_text = $self->process_message($body_text);
755 debug
("###Message From: ".$session->{jid
}.", To: $to, body: $body_text\n");
757 my $result = THPPW
::thrasher_action_outgoing_msg
758 (Encode
::encode
("UTF-8", $session->{jid
}),
759 Encode
::encode
("UTF-8", $to),
760 Encode
::encode
("UTF-8", $body_text));
761 debug
("Message send result: $result\n");
764 sub outgoing_chatstate
{
765 my ($self, $session, $to, $chatstate) = @_;
766 debug
("###outgoing_chatstate($session->{jid}, $to, $chatstate)\n");
768 our $chatstate_to_purple ||= {
769 'composing' => $THPPW::PURPLE_TYPING
,
770 'paused' => $THPPW::PURPLE_TYPED
,
771 'inactive' => $THPPW::PURPLE_NOT_TYPING
,
772 'active' => $THPPW::PURPLE_NOT_TYPING
,
774 my $purple_typing_state = $chatstate_to_purple->{$chatstate};
775 if (! defined($purple_typing_state)) {
776 debug
("Untranslated chatstate: '$chatstate'\n");
780 THPPW
::thrasher_action_outgoing_chatstate
($session->{'jid'},
782 $purple_typing_state);
789 my $component = shift;
790 my $legacy_username = shift;
792 debug
("###subscribed called: $legacy_username permitted for $session->{jid}");
794 THPPW
::thrasher_action_buddy_authorize
795 (Encode
::encode
("UTF-8", $session->{jid
}),
796 Encode
::encode
("UTF-8", $legacy_username));
798 $self->SUPER::subscribed
($session, $component, $legacy_username);
804 my $component = shift;
805 my $legacy_username = shift;
807 debug
("###unsubscribed($session->{jid}, $legacy_username) called");
809 if (!defined($legacy_username)) {
810 confess
"Unsubscribing an undef user; shouldn't be called.";
813 THPPW
::thrasher_action_buddy_deauthorize
814 (Encode
::encode
("UTF-8", $session->{jid
}),
815 Encode
::encode
("UTF-8", $legacy_username));
819 my ($self, $id) = @_;
821 THPPW
::thrasher_action_ft_ui_ready
($id);
822 return 1; # repeat this notification.
829 return "Gateway prompt";
835 return $self->{gateway_desc
};
838 sub user_presence_update
{
841 my $type = shift || '';
842 my $show = shift || '';
843 my $status = shift || '';
845 debug
("user_presence_update called\n");
849 # State table for type/show to purple_status
850 if ($show eq 'away') {
854 $purple_status = $purple_presence{'xaway'};
858 $purple_status = $purple_presence{'away'};
862 logger
("Unknown type/show of [$type/$show]");
865 elsif ($show eq 'chat' || $show eq '') {
868 # This seems like it might have more states
869 $purple_status = $purple_presence{'available'};
871 elsif ($type eq 'unavailable') {
873 $purple_status = $purple_presence{'offline'};
876 logger
("Unknown type/show of [$type/$show]");
879 elsif ($show eq 'xa' || $show eq 'xaway') {
880 $purple_status = $purple_presence{'xaway'};
883 logger
("Unknown type/show of [$type/$show] (show is completely unrecognized)");
886 if (defined($purple_status)) {
887 THPPW
::thrasher_action_presence
(
888 Encode
::encode
("UTF-8", $session->{jid
}),
889 $purple_status, # integer does not need encoding
890 Encode
::encode
("UTF-8", $status),
894 #debug("User presence update: type: $type, show: $show, purple: $purple_status, status: $status");
897 # Don't do anything with this right now.
898 sub user_targeted_presence_update
{
902 my $type = shift || '';
903 my $show = shift || '';
904 my $status = shift || '';
905 my $target_user = shift || '';
907 #log("User presence update to $target_user: type: $type, show: $show, status: $status");
910 # Subrefs for which to satiate the libpurple monster
912 my $interval = shift;
916 debug
("perl::timeout_add called\n", 3);
918 debug
("\tinterval = $interval\n", 3) if $interval;
919 debug
("\tcode = $code\n", 3) if $code;
920 debug
("\ttrigger = $trigger\n", 3) if $trigger;
922 my $ret = Glib
::Timeout
->add($interval,
926 debug
("Glib::Timeout->add returned [$ret]\n", 3);
932 debug
("perl::timeout_remove called with $_[0]\n", 3);
934 return Glib
::Source
->remove($_[0]);
944 debug
("_input_add\n", 3);
948 debug
("\t$i = $_\n");
953 debug
("\tfd = $fd\n", 3) if $fd;
954 debug
("\tcond = $cond\n", 3) if $cond;
955 debug
("\tcode = $code\n", 3) if $code;
956 debug
("\ttrigger = $trigger\n", 3) if $trigger;
958 $cond = ($cond == 1) ?
'G_IO_IN' : 'G_IO_OUT';
960 $cond = [$cond, 'G_IO_ERR', 'G_IO_HUP', 'G_IO_NVAL'];
962 my $ret = Glib
::IO
->add_watch($fd,
968 debug
("Glib::IO->add_watch returned [$ret]\n", 3);
970 debug
("_input_add done\n", 3);
975 # Returns if the given ID is a valid id for the service. This avoids
976 # some problems that services have when you jam illegal logins in.
977 # For instance, log in to Yahoo with a Japanese username, and it
978 # just hangs on the connection, rather than doing anything.
979 # Note that this is more about not sending in logins that confuse
980 # the remote services so badly we get no errors, NOT about precisely
981 # labelling which fields are possible. If the remote service correctly
982 # determines the password is invalid, then everything's fine.
984 my ($self, $username) = @_;
986 if ($username =~ m{/}) {
994 my ($self, $password) = @_;
996 # If the prpl requires a password, _purple_connection_new() will
997 # fail when password is NULL or zero-length without returning an
998 # error thrasher_login() can detect. Worse, the check in
999 # purple_account_connect() is slightly different so it wouldn't
1000 # even be detectable through purple_account_request_password() and
1003 # Registering with an empty password therefore begins an
1004 # apparently successful async login that never completes or
1005 # errors. The user also can't re-register or log out because
1006 # they're already "logging in". :(
1008 # If the prpl has OPT_PROTO_PASSWORD_OPTIONAL or OPT_PROTO_NO_PASSWORD
1009 # the corresponding subclass should override this.
1013 sub purple_forces_kill
{ return 0; }