4 * Copyright (C) 2003, Robbert Haarman <purple@inglorion.net>
5 * Copyright (C) 2003, 2012 Ethan Blanton <elb@pidgin.im>
6 * Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com>
7 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
29 #define PING_TIMEOUT 60
31 static void irc_ison_buddy_init(char *name
, struct irc_buddy
*ib
, GList
**list
);
33 static const char *irc_blist_icon(PurpleAccount
*a
, PurpleBuddy
*b
);
34 static GList
*irc_status_types(PurpleAccount
*account
);
35 static GList
*irc_get_actions(PurpleConnection
*gc
);
36 /* static GList *irc_chat_info(PurpleConnection *gc); */
37 static void irc_login(PurpleAccount
*account
);
38 static void irc_login_cb(GObject
*source
, GAsyncResult
*res
, gpointer user_data
);
39 static void irc_close(PurpleConnection
*gc
);
40 static int irc_im_send(PurpleConnection
*gc
, PurpleMessage
*msg
);
41 static int irc_chat_send(PurpleConnection
*gc
, int id
, PurpleMessage
*msg
);
42 static void irc_chat_join (PurpleConnection
*gc
, GHashTable
*data
);
43 static void irc_read_input_cb(GObject
*source
, GAsyncResult
*res
, gpointer data
);
45 static guint
irc_nick_hash(const char *nick
);
46 static gboolean
irc_nick_equal(const char *nick1
, const char *nick2
);
47 static void irc_buddy_free(struct irc_buddy
*ib
);
49 PurpleProtocol
*_irc_protocol
= NULL
;
52 irc_uri_handler_match_server(PurpleAccount
*account
, const gchar
*match_server
)
54 const gchar
*protocol_id
;
55 const gchar
*username
;
58 protocol_id
= purple_account_get_protocol_id(account
);
60 if (!purple_strequal(protocol_id
, "prpl-irc") ||
61 !purple_account_is_connected(account
)) {
65 if (match_server
== NULL
|| match_server
[0] == '\0') {
66 /* No server specified, match any IRC account */
70 username
= purple_account_get_username(account
);
71 server
= strchr(username
, '@');
74 if (server
== NULL
|| !purple_strequal(match_server
, server
+ 1)) {
82 irc_uri_handler(const gchar
*scheme
, const gchar
*uri
, GHashTable
*params
)
88 gchar
**target_tokens
;
89 PurpleAccount
*account
;
91 gboolean isnick
= FALSE
;
93 g_return_val_if_fail(uri
!= NULL
, FALSE
);
95 if (!purple_strequal(scheme
, "irc")) {
96 /* Not a scheme we handle here */
100 if (g_str_has_prefix(uri
, "//")) {
101 /* Skip initial '//' if it exists */
105 /* Find the target (aka room or user) */
106 target
= strchr(uri
, '/');
108 /* [1] to skip the '/' */
109 if (target
== NULL
|| target
[1] == '\0') {
110 purple_debug_warning("irc",
111 "URI missing valid target: %s", uri
);
115 server
= g_strndup(uri
, target
- uri
);
117 /* Find account with correct server */
118 accounts
= purple_accounts_get_all();
119 account_node
= g_list_find_custom(
120 accounts
, server
, (GCompareFunc
)irc_uri_handler_match_server
);
122 if (account_node
== NULL
) {
123 purple_debug_warning("irc",
124 "No account online on '%s' for handling URI",
130 account
= account_node
->data
;
132 /* Tokenize modifiers, +1 to skip the initial '/' */
133 target_tokens
= g_strsplit(target
+ 1, ",", 0);
134 target
= g_strdup_printf("#%s", target_tokens
[0]);
136 /* Parse modifiers, start at 1 to skip the actual target */
137 for (modifier
= target_tokens
+ 1; *modifier
!= NULL
; ++modifier
) {
138 if (purple_strequal(*modifier
, "isnick")) {
144 g_strfreev(target_tokens
);
147 PurpleIMConversation
*im
;
149 /* 'server' isn't needed here. Free it immediately. */
152 /* +1 to skip '#' target prefix */
153 im
= purple_im_conversation_new(account
, target
+ 1);
156 purple_conversation_present(PURPLE_CONVERSATION(im
));
158 if (params
!= NULL
) {
159 const gchar
*msg
= g_hash_table_lookup(params
, "msg");
162 purple_conversation_send_confirm(
163 PURPLE_CONVERSATION(im
), msg
);
169 GHashTable
*components
;
171 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
174 /* Transfer ownership of these to the hash table */
175 g_hash_table_insert(components
, "server", server
);
176 g_hash_table_insert(components
, "channel", target
);
178 if (params
!= NULL
) {
179 const gchar
*key
= g_hash_table_lookup(params
, "key");
182 g_hash_table_insert(components
, "password",
187 purple_serv_join_chat(purple_account_get_connection(account
),
189 g_hash_table_destroy(components
);
196 static void irc_view_motd(PurpleProtocolAction
*action
)
198 PurpleConnection
*gc
= action
->connection
;
199 struct irc_conn
*irc
;
202 if (gc
== NULL
|| purple_connection_get_protocol_data(gc
) == NULL
) {
203 purple_debug(PURPLE_DEBUG_ERROR
, "irc", "got MOTD request for NULL gc\n");
206 irc
= purple_connection_get_protocol_data(gc
);
207 if (irc
->motd
== NULL
) {
208 purple_notify_error(gc
, _("Error displaying MOTD"),
209 _("No MOTD available"),
210 _("There is no MOTD associated with this connection."),
211 purple_request_cpar_from_connection(gc
));
214 title
= g_strdup_printf(_("MOTD for %s"), irc
->server
);
215 body
= g_strdup_printf("<span style=\"font-family: monospace;\">%s</span>", irc
->motd
->str
);
216 purple_notify_formatted(gc
, title
, title
, NULL
, body
, NULL
, NULL
);
221 static int irc_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
223 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
227 irc_send_len(irc
, buf
, len
);
232 irc_push_bytes_cb(GObject
*source
, GAsyncResult
*res
, gpointer data
)
234 PurpleQueuedOutputStream
*stream
= PURPLE_QUEUED_OUTPUT_STREAM(source
);
235 PurpleConnection
*gc
= data
;
237 GError
*error
= NULL
;
239 result
= purple_queued_output_stream_push_bytes_finish(stream
,
243 purple_queued_output_stream_clear_queue(stream
);
245 g_prefix_error(&error
, "%s", _("Lost connection with server: "));
246 purple_connection_take_error(gc
, error
);
251 int irc_send(struct irc_conn
*irc
, const char *buf
)
253 return irc_send_len(irc
, buf
, strlen(buf
));
256 int irc_send_len(struct irc_conn
*irc
, const char *buf
, int buflen
)
258 char *tosend
= g_strdup(buf
);
262 purple_signal_emit(_irc_protocol
, "irc-sending-text", purple_account_get_connection(irc
->account
), &tosend
);
267 if (purple_debug_is_verbose()) {
268 gchar
*clean
= purple_utf8_salvage(tosend
);
269 clean
= g_strstrip(clean
);
270 purple_debug_misc("irc", "<< %s\n", clean
);
274 len
= strlen(tosend
);
275 data
= g_bytes_new_take(tosend
, len
);
276 purple_queued_output_stream_push_bytes_async(irc
->output
, data
,
277 G_PRIORITY_DEFAULT
, irc
->cancellable
, irc_push_bytes_cb
,
278 purple_account_get_connection(irc
->account
));
284 /* XXX I don't like messing directly with these buddies */
285 gboolean
irc_blist_timeout(struct irc_conn
*irc
)
287 if (irc
->ison_outstanding
) {
291 g_hash_table_foreach(irc
->buddies
, (GHFunc
)irc_ison_buddy_init
,
292 (gpointer
*)&irc
->buddies_outstanding
);
294 irc_buddy_query(irc
);
299 void irc_buddy_query(struct irc_conn
*irc
)
303 struct irc_buddy
*ib
;
306 string
= g_string_sized_new(512);
308 while ((lp
= g_list_first(irc
->buddies_outstanding
))) {
309 ib
= (struct irc_buddy
*)lp
->data
;
310 if (string
->len
+ strlen(ib
->name
) + 1 > 450)
312 g_string_append_printf(string
, "%s ", ib
->name
);
313 ib
->new_online_status
= FALSE
;
314 irc
->buddies_outstanding
= g_list_delete_link(irc
->buddies_outstanding
, lp
);
318 buf
= irc_format(irc
, "vn", "ISON", string
->str
);
321 irc
->ison_outstanding
= TRUE
;
323 irc
->ison_outstanding
= FALSE
;
325 g_string_free(string
, TRUE
);
328 static void irc_ison_buddy_init(char *name
, struct irc_buddy
*ib
, GList
**list
)
330 *list
= g_list_append(*list
, ib
);
334 static void irc_ison_one(struct irc_conn
*irc
, struct irc_buddy
*ib
)
338 if (irc
->buddies_outstanding
!= NULL
) {
339 irc
->buddies_outstanding
= g_list_append(irc
->buddies_outstanding
, ib
);
343 ib
->new_online_status
= FALSE
;
344 buf
= irc_format(irc
, "vn", "ISON", ib
->name
);
350 static const char *irc_blist_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
355 static GList
*irc_status_types(PurpleAccount
*account
)
357 PurpleStatusType
*type
;
360 type
= purple_status_type_new(PURPLE_STATUS_AVAILABLE
, NULL
, NULL
, TRUE
);
361 types
= g_list_append(types
, type
);
363 type
= purple_status_type_new_with_attrs(
364 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
365 "message", _("Message"), purple_value_new(G_TYPE_STRING
),
367 types
= g_list_append(types
, type
);
369 type
= purple_status_type_new(PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
);
370 types
= g_list_append(types
, type
);
375 static GList
*irc_get_actions(PurpleConnection
*gc
)
378 PurpleProtocolAction
*act
= NULL
;
380 act
= purple_protocol_action_new(_("View MOTD"), irc_view_motd
);
381 list
= g_list_append(list
, act
);
386 static GList
*irc_chat_join_info(PurpleConnection
*gc
)
389 PurpleProtocolChatEntry
*pce
;
391 pce
= g_new0(PurpleProtocolChatEntry
, 1);
392 pce
->label
= _("_Channel:");
393 pce
->identifier
= "channel";
394 pce
->required
= TRUE
;
395 m
= g_list_append(m
, pce
);
397 pce
= g_new0(PurpleProtocolChatEntry
, 1);
398 pce
->label
= _("_Password:");
399 pce
->identifier
= "password";
401 m
= g_list_append(m
, pce
);
406 static GHashTable
*irc_chat_info_defaults(PurpleConnection
*gc
, const char *chat_name
)
408 GHashTable
*defaults
;
410 defaults
= g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, g_free
);
412 if (chat_name
!= NULL
)
413 g_hash_table_insert(defaults
, "channel", g_strdup(chat_name
));
418 static void irc_login(PurpleAccount
*account
)
420 PurpleConnection
*gc
;
421 struct irc_conn
*irc
;
423 const char *username
= purple_account_get_username(account
);
424 GSocketClient
*client
;
425 GError
*error
= NULL
;
427 gc
= purple_account_get_connection(account
);
428 purple_connection_set_flags(gc
, PURPLE_CONNECTION_FLAG_NO_NEWLINES
|
429 PURPLE_CONNECTION_FLAG_NO_IMAGES
);
431 if (strpbrk(username
, " \t\v\r\n") != NULL
) {
432 purple_connection_take_error(gc
, g_error_new_literal(
433 PURPLE_CONNECTION_ERROR
,
434 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
435 _("IRC nick and server may not contain whitespace")));
439 irc
= g_new0(struct irc_conn
, 1);
440 purple_connection_set_protocol_data(gc
, irc
);
441 irc
->account
= account
;
442 irc
->cancellable
= g_cancellable_new();
444 userparts
= g_strsplit(username
, "@", 2);
445 purple_connection_set_display_name(gc
, userparts
[0]);
446 irc
->server
= g_strdup(userparts
[1]);
447 g_strfreev(userparts
);
449 irc
->buddies
= g_hash_table_new_full((GHashFunc
)irc_nick_hash
, (GEqualFunc
)irc_nick_equal
,
450 NULL
, (GDestroyNotify
)irc_buddy_free
);
451 irc
->cmds
= g_hash_table_new(g_str_hash
, g_str_equal
);
452 irc_cmd_table_build(irc
);
453 irc
->msgs
= g_hash_table_new(g_str_hash
, g_str_equal
);
454 irc_msg_table_build(irc
);
456 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
458 client
= purple_gio_socket_client_new(account
, &error
);
460 if (client
== NULL
) {
461 purple_connection_take_error(gc
, error
);
465 /* Optionally use TLS if it's set in the account settings */
466 g_socket_client_set_tls(client
,
467 purple_account_get_bool(account
, "ssl", FALSE
));
469 g_socket_client_connect_to_host_async(client
, irc
->server
,
470 purple_account_get_int(account
, "port",
471 g_socket_client_get_tls(client
) ?
472 IRC_DEFAULT_SSL_PORT
:
474 irc
->cancellable
, irc_login_cb
, gc
);
475 g_object_unref(client
);
478 static gboolean
do_login(PurpleConnection
*gc
) {
479 char *buf
, *tmp
= NULL
;
481 const char *nickname
, *identname
, *realname
;
482 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
483 const char *pass
= purple_connection_get_password(gc
);
484 #ifdef HAVE_CYRUS_SASL
485 const gboolean use_sasl
= purple_account_get_bool(irc
->account
, "sasl", FALSE
);
489 #ifdef HAVE_CYRUS_SASL
491 buf
= irc_format(irc
, "vv:", "CAP", "REQ", "sasl");
492 else /* intended to fall through */
494 buf
= irc_format(irc
, "v:", "PASS", pass
);
495 if (irc_send(irc
, buf
) < 0) {
502 realname
= purple_account_get_string(irc
->account
, "realname", "");
503 identname
= purple_account_get_string(irc
->account
, "username", "");
505 if (identname
== NULL
|| *identname
== '\0') {
506 identname
= g_get_user_name();
509 if (identname
!= NULL
&& strchr(identname
, ' ') != NULL
) {
510 tmp
= g_strdup(identname
);
511 while ((buf
= strchr(tmp
, ' ')) != NULL
) {
516 if (*irc
->server
== ':') {
517 /* Same as hostname, above. */
518 server
= g_strdup_printf("0%s", irc
->server
);
520 server
= g_strdup(irc
->server
);
523 buf
= irc_format(irc
, "vvvv:", "USER", tmp
? tmp
: identname
, "*", server
,
524 *realname
== '\0' ? IRC_DEFAULT_ALIAS
: realname
);
527 if (irc_send(irc
, buf
) < 0) {
532 nickname
= purple_connection_get_display_name(gc
);
533 buf
= irc_format(irc
, "vn", "NICK", nickname
);
534 irc
->reqnick
= g_strdup(nickname
);
535 irc
->nickused
= FALSE
;
536 if (irc_send(irc
, buf
) < 0) {
542 irc
->recv_time
= time(NULL
);
548 irc_login_cb(GObject
*source
, GAsyncResult
*res
, gpointer user_data
)
550 PurpleConnection
*gc
= user_data
;
551 GSocketConnection
*conn
;
552 GError
*error
= NULL
;
553 struct irc_conn
*irc
;
555 conn
= g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source
),
559 g_prefix_error(&error
, "%s", _("Unable to connect: "));
560 purple_connection_take_error(gc
, error
);
564 irc
= purple_connection_get_protocol_data(gc
);
566 irc
->output
= purple_queued_output_stream_new(
567 g_io_stream_get_output_stream(G_IO_STREAM(irc
->conn
)));
570 irc
->input
= g_data_input_stream_new(
571 g_io_stream_get_input_stream(
572 G_IO_STREAM(irc
->conn
)));
573 g_data_input_stream_read_line_async(irc
->input
,
574 G_PRIORITY_DEFAULT
, irc
->cancellable
,
575 irc_read_input_cb
, gc
);
579 static void irc_close(PurpleConnection
*gc
)
581 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
586 if (irc
->conn
!= NULL
)
587 irc_cmd_quit(irc
, "quit", NULL
, NULL
);
589 if (irc
->cancellable
!= NULL
) {
590 g_cancellable_cancel(irc
->cancellable
);
591 g_clear_object(&irc
->cancellable
);
594 if (irc
->conn
!= NULL
) {
595 purple_gio_graceful_close(G_IO_STREAM(irc
->conn
),
596 G_INPUT_STREAM(irc
->input
),
597 G_OUTPUT_STREAM(irc
->output
));
600 g_clear_object(&irc
->input
);
601 g_clear_object(&irc
->output
);
602 g_clear_object(&irc
->conn
);
605 g_source_remove(irc
->timer
);
606 g_hash_table_destroy(irc
->cmds
);
607 g_hash_table_destroy(irc
->msgs
);
608 g_hash_table_destroy(irc
->buddies
);
610 g_string_free(irc
->motd
, TRUE
);
613 g_free(irc
->mode_chars
);
614 g_free(irc
->reqnick
);
616 #ifdef HAVE_CYRUS_SASL
617 if (irc
->sasl_conn
) {
618 sasl_dispose(&irc
->sasl_conn
);
619 irc
->sasl_conn
= NULL
;
621 g_free(irc
->sasl_cb
);
623 g_string_free(irc
->sasl_mechs
, TRUE
);
630 static int irc_im_send(PurpleConnection
*gc
, PurpleMessage
*msg
)
632 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
636 args
[0] = irc_nick_skip_mode(irc
, purple_message_get_recipient(msg
));
638 purple_markup_html_to_xhtml(purple_message_get_contents(msg
),
642 irc_cmd_privmsg(irc
, "msg", NULL
, args
);
647 static void irc_get_info(PurpleConnection
*gc
, const char *who
)
649 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
653 irc_cmd_whois(irc
, "whois", NULL
, args
);
656 static void irc_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
658 PurpleConnection
*gc
= purple_account_get_connection(account
);
659 struct irc_conn
*irc
;
661 const char *status_id
= purple_status_get_id(status
);
663 g_return_if_fail(gc
!= NULL
);
664 irc
= purple_connection_get_protocol_data(gc
);
666 if (!purple_status_is_active(status
))
671 if (purple_strequal(status_id
, "away")) {
672 args
[0] = purple_status_get_attr_string(status
, "message");
673 if ((args
[0] == NULL
) || (*args
[0] == '\0'))
675 irc_cmd_away(irc
, "away", NULL
, args
);
676 } else if (purple_strequal(status_id
, "available")) {
677 irc_cmd_away(irc
, "back", NULL
, args
);
681 static void irc_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
, const char *message
)
683 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
684 struct irc_buddy
*ib
;
685 const char *bname
= purple_buddy_get_name(buddy
);
687 ib
= g_hash_table_lookup(irc
->buddies
, bname
);
690 purple_protocol_got_user_status(irc
->account
, bname
,
691 ib
->online
? "available" : "offline", NULL
);
693 ib
= g_new0(struct irc_buddy
, 1);
694 ib
->name
= g_strdup(bname
);
696 g_hash_table_replace(irc
->buddies
, ib
->name
, ib
);
699 /* if the timer isn't set, this is during signon, so we don't want to flood
700 * ourself off with ISON's, so we don't, but after that we want to know when
701 * someone's online asap */
703 irc_ison_one(irc
, ib
);
706 static void irc_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
708 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
709 struct irc_buddy
*ib
;
711 ib
= g_hash_table_lookup(irc
->buddies
, purple_buddy_get_name(buddy
));
712 if (ib
&& --ib
->ref
== 0) {
713 g_hash_table_remove(irc
->buddies
, purple_buddy_get_name(buddy
));
718 irc_read_input_cb(GObject
*source
, GAsyncResult
*res
, gpointer data
)
720 PurpleConnection
*gc
= data
;
721 struct irc_conn
*irc
;
725 GError
*error
= NULL
;
727 line
= g_data_input_stream_read_line_finish(
728 G_DATA_INPUT_STREAM(source
), res
, &len
, &error
);
730 if (line
== NULL
&& error
!= NULL
) {
731 g_prefix_error(&error
, "%s", _("Lost connection with server: "));
732 purple_connection_take_error(gc
, error
);
734 } else if (line
== NULL
) {
735 purple_connection_take_error(gc
, g_error_new_literal(
736 PURPLE_CONNECTION_ERROR
,
737 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
738 _("Server closed the connection")));
742 irc
= purple_connection_get_protocol_data(gc
);
744 purple_connection_update_last_received(gc
);
746 if (len
> 0 && line
[len
- 1] == '\r')
747 line
[len
- 1] = '\0';
749 /* This is a hack to work around the fact that marv gets messages
750 * with null bytes in them while using some weird irc server at work
752 while (start
< len
&& line
[start
] == '\0')
756 irc_parse_msg(irc
, line
+ start
);
761 g_data_input_stream_read_line_async(irc
->input
,
762 G_PRIORITY_DEFAULT
, irc
->cancellable
,
763 irc_read_input_cb
, gc
);
766 static void irc_chat_join (PurpleConnection
*gc
, GHashTable
*data
)
768 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
771 args
[0] = g_hash_table_lookup(data
, "channel");
772 args
[1] = g_hash_table_lookup(data
, "password");
773 irc_cmd_join(irc
, "join", NULL
, args
);
776 static char *irc_get_chat_name(GHashTable
*data
) {
777 return g_strdup(g_hash_table_lookup(data
, "channel"));
780 static void irc_chat_invite(PurpleConnection
*gc
, int id
, const char *message
, const char *name
)
782 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
783 PurpleConversation
*convo
= PURPLE_CONVERSATION(purple_conversations_find_chat(gc
, id
));
787 purple_debug(PURPLE_DEBUG_ERROR
, "irc", "Got chat invite request for bogus chat\n");
791 args
[1] = purple_conversation_get_name(convo
);
792 irc_cmd_invite(irc
, "invite", purple_conversation_get_name(convo
), args
);
796 static void irc_chat_leave (PurpleConnection
*gc
, int id
)
798 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
799 PurpleConversation
*convo
= PURPLE_CONVERSATION(purple_conversations_find_chat(gc
, id
));
805 args
[0] = purple_conversation_get_name(convo
);
807 irc_cmd_part(irc
, "part", purple_conversation_get_name(convo
), args
);
808 purple_serv_got_chat_left(gc
, id
);
811 static int irc_chat_send(PurpleConnection
*gc
, int id
, PurpleMessage
*msg
)
813 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
814 PurpleConversation
*convo
= PURPLE_CONVERSATION(purple_conversations_find_chat(gc
, id
));
819 purple_debug(PURPLE_DEBUG_ERROR
, "irc", "chat send on nonexistent chat\n");
822 purple_markup_html_to_xhtml(purple_message_get_contents(msg
), NULL
, &tmp
);
823 args
[0] = purple_conversation_get_name(convo
);
826 irc_cmd_privmsg(irc
, "msg", NULL
, args
);
829 purple_serv_got_chat_in(gc
, id
, purple_connection_get_display_name(gc
),
830 purple_message_get_flags(msg
),
831 purple_message_get_contents(msg
), time(NULL
));
836 static guint
irc_nick_hash(const char *nick
)
841 lc
= g_utf8_strdown(nick
, -1);
842 bucket
= g_str_hash(lc
);
848 static gboolean
irc_nick_equal(const char *nick1
, const char *nick2
)
850 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
853 static void irc_buddy_free(struct irc_buddy
*ib
)
859 static void irc_chat_set_topic(PurpleConnection
*gc
, int id
, const char *topic
)
862 const char *name
= NULL
;
863 struct irc_conn
*irc
;
865 irc
= purple_connection_get_protocol_data(gc
);
866 name
= purple_conversation_get_name(PURPLE_CONVERSATION(
867 purple_conversations_find_chat(gc
, id
)));
872 buf
= irc_format(irc
, "vt:", "TOPIC", name
, topic
);
877 static PurpleRoomlist
*irc_roomlist_get_list(PurpleConnection
*gc
)
879 struct irc_conn
*irc
;
880 GList
*fields
= NULL
;
881 PurpleRoomlistField
*f
;
884 irc
= purple_connection_get_protocol_data(gc
);
887 g_object_unref(irc
->roomlist
);
889 irc
->roomlist
= purple_roomlist_new(purple_connection_get_account(gc
));
891 f
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING
, "", "channel", TRUE
);
892 fields
= g_list_append(fields
, f
);
894 f
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT
, _("Users"), "users", FALSE
);
895 fields
= g_list_append(fields
, f
);
897 f
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING
, _("Topic"), "topic", FALSE
);
898 fields
= g_list_append(fields
, f
);
900 purple_roomlist_set_fields(irc
->roomlist
, fields
);
902 buf
= irc_format(irc
, "v", "LIST");
906 return irc
->roomlist
;
909 static void irc_roomlist_cancel(PurpleRoomlist
*list
)
911 PurpleAccount
*account
= purple_roomlist_get_account(list
);
912 PurpleConnection
*gc
= purple_account_get_connection(account
);
913 struct irc_conn
*irc
;
918 irc
= purple_connection_get_protocol_data(gc
);
920 purple_roomlist_set_in_progress(list
, FALSE
);
922 if (irc
->roomlist
== list
) {
923 irc
->roomlist
= NULL
;
924 g_object_unref(list
);
928 static void irc_keepalive(PurpleConnection
*gc
)
930 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
931 if ((time(NULL
) - irc
->recv_time
) > PING_TIMEOUT
)
932 irc_cmd_ping(irc
, NULL
, NULL
, NULL
);
936 irc_get_max_message_size(PurpleConversation
*conv
)
938 /* TODO: this static value is got from pidgin-otr, but it depends on
939 * some factors, for example IRC channel name. */
944 irc_protocol_init(IRCProtocol
*self
)
946 PurpleProtocol
*protocol
= PURPLE_PROTOCOL(self
);
947 PurpleAccountUserSplit
*split
;
948 PurpleAccountOption
*option
;
950 protocol
->id
= "prpl-irc";
951 protocol
->name
= "IRC";
952 protocol
->options
= OPT_PROTO_CHAT_TOPIC
| OPT_PROTO_PASSWORD_OPTIONAL
|
953 OPT_PROTO_SLASH_COMMANDS_NATIVE
;
955 split
= purple_account_user_split_new(_("Server"), IRC_DEFAULT_SERVER
, '@');
956 protocol
->user_splits
= g_list_append(protocol
->user_splits
, split
);
958 option
= purple_account_option_int_new(_("Port"), "port", IRC_DEFAULT_PORT
);
959 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
961 option
= purple_account_option_string_new(_("Encodings"), "encoding", IRC_DEFAULT_CHARSET
);
962 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
964 option
= purple_account_option_bool_new(_("Auto-detect incoming UTF-8"), "autodetect_utf8", IRC_DEFAULT_AUTODETECT
);
965 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
967 option
= purple_account_option_string_new(_("Ident name"), "username", "");
968 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
970 option
= purple_account_option_string_new(_("Real name"), "realname", "");
971 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
974 option = purple_account_option_string_new(_("Quit message"), "quitmsg", IRC_DEFAULT_QUIT);
975 protocol->account_options = g_list_append(protocol->account_options, option);
978 option
= purple_account_option_bool_new(_("Use SSL"), "ssl", FALSE
);
979 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
981 #ifdef HAVE_CYRUS_SASL
982 option
= purple_account_option_bool_new(_("Authenticate with SASL"), "sasl", FALSE
);
983 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
985 option
= purple_account_option_bool_new(
986 _("Allow plaintext SASL auth over unencrypted connection"),
987 "auth_plain_in_clear", FALSE
);
988 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
993 irc_protocol_class_init(IRCProtocolClass
*klass
)
995 PurpleProtocolClass
*protocol_class
= PURPLE_PROTOCOL_CLASS(klass
);
997 protocol_class
->login
= irc_login
;
998 protocol_class
->close
= irc_close
;
999 protocol_class
->status_types
= irc_status_types
;
1000 protocol_class
->list_icon
= irc_blist_icon
;
1004 irc_protocol_class_finalize(G_GNUC_UNUSED IRCProtocolClass
*klass
)
1009 irc_protocol_client_iface_init(PurpleProtocolClientInterface
*client_iface
)
1011 client_iface
->get_actions
= irc_get_actions
;
1012 client_iface
->normalize
= purple_normalize_nocase
;
1013 client_iface
->get_max_message_size
= irc_get_max_message_size
;
1017 irc_protocol_server_iface_init(PurpleProtocolServerInterface
*server_iface
)
1019 server_iface
->set_status
= irc_set_status
;
1020 server_iface
->get_info
= irc_get_info
;
1021 server_iface
->add_buddy
= irc_add_buddy
;
1022 server_iface
->remove_buddy
= irc_remove_buddy
;
1023 server_iface
->keepalive
= irc_keepalive
;
1024 server_iface
->send_raw
= irc_send_raw
;
1028 irc_protocol_im_iface_init(PurpleProtocolIMInterface
*im_iface
)
1030 im_iface
->send
= irc_im_send
;
1034 irc_protocol_chat_iface_init(PurpleProtocolChatInterface
*chat_iface
)
1036 chat_iface
->info
= irc_chat_join_info
;
1037 chat_iface
->info_defaults
= irc_chat_info_defaults
;
1038 chat_iface
->join
= irc_chat_join
;
1039 chat_iface
->get_name
= irc_get_chat_name
;
1040 chat_iface
->invite
= irc_chat_invite
;
1041 chat_iface
->leave
= irc_chat_leave
;
1042 chat_iface
->send
= irc_chat_send
;
1043 chat_iface
->set_topic
= irc_chat_set_topic
;
1047 irc_protocol_roomlist_iface_init(PurpleProtocolRoomlistInterface
*roomlist_iface
)
1049 roomlist_iface
->get_list
= irc_roomlist_get_list
;
1050 roomlist_iface
->cancel
= irc_roomlist_cancel
;
1054 irc_protocol_xfer_iface_init(PurpleProtocolXferInterface
*xfer_iface
)
1056 xfer_iface
->send_file
= irc_dccsend_send_file
;
1057 xfer_iface
->new_xfer
= irc_dccsend_new_xfer
;
1060 G_DEFINE_DYNAMIC_TYPE_EXTENDED(
1061 IRCProtocol
, irc_protocol
, PURPLE_TYPE_PROTOCOL
, 0,
1063 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CLIENT
,
1064 irc_protocol_client_iface_init
)
1066 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_SERVER
,
1067 irc_protocol_server_iface_init
)
1069 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_IM
,
1070 irc_protocol_im_iface_init
)
1072 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CHAT
,
1073 irc_protocol_chat_iface_init
)
1075 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_ROOMLIST
,
1076 irc_protocol_roomlist_iface_init
)
1078 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_XFER
,
1079 irc_protocol_xfer_iface_init
));
1081 static PurplePluginInfo
*
1082 plugin_query(GError
**error
)
1084 return purple_plugin_info_new(
1086 "name", "IRC Protocol",
1087 "version", DISPLAY_VERSION
,
1088 "category", N_("Protocol"),
1089 "summary", N_("IRC Protocol Plugin"),
1090 "description", N_("The IRC Protocol Plugin that Sucks Less"),
1091 "website", PURPLE_WEBSITE
,
1092 "abi-version", PURPLE_ABI_VERSION
,
1093 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL
|
1094 PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD
,
1100 plugin_load(PurplePlugin
*plugin
, GError
**error
)
1102 irc_protocol_register_type(G_TYPE_MODULE(plugin
));
1104 irc_xfer_register(G_TYPE_MODULE(plugin
));
1106 _irc_protocol
= purple_protocols_add(IRC_TYPE_PROTOCOL
, error
);
1110 purple_prefs_remove("/plugins/prpl/irc/quitmsg");
1111 purple_prefs_remove("/plugins/prpl/irc");
1113 irc_register_commands();
1115 purple_signal_register(_irc_protocol
, "irc-sending-text",
1116 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
1117 PURPLE_TYPE_CONNECTION
,
1118 G_TYPE_POINTER
); /* pointer to a string */
1119 purple_signal_register(_irc_protocol
, "irc-receiving-text",
1120 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
1121 PURPLE_TYPE_CONNECTION
,
1122 G_TYPE_POINTER
); /* pointer to a string */
1124 purple_signal_connect(purple_get_core(), "uri-handler", plugin
,
1125 PURPLE_CALLBACK(irc_uri_handler
), NULL
);
1131 plugin_unload(PurplePlugin
*plugin
, GError
**error
)
1133 irc_unregister_commands();
1135 purple_signal_disconnect(purple_get_core(), "uri-handler", plugin
,
1136 PURPLE_CALLBACK(irc_uri_handler
));
1138 if (!purple_protocols_remove(_irc_protocol
, error
))
1144 PURPLE_PLUGIN_INIT(irc
, plugin_query
, plugin_load
, plugin_unload
);