6 * Copyright (C) 2003, Robbert Haarman <purple@inglorion.net>
7 * Copyright (C) 2003, 2012 Ethan Blanton <elb@pidgin.im>
8 * Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com>
9 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 #include "accountopt.h"
30 #include "buddylist.h"
31 #include "conversation.h"
37 #include "purple-gio.h"
43 #define PING_TIMEOUT 60
45 static void irc_ison_buddy_init(char *name
, struct irc_buddy
*ib
, GList
**list
);
47 static const char *irc_blist_icon(PurpleAccount
*a
, PurpleBuddy
*b
);
48 static GList
*irc_status_types(PurpleAccount
*account
);
49 static GList
*irc_get_actions(PurpleConnection
*gc
);
50 /* static GList *irc_chat_info(PurpleConnection *gc); */
51 static void irc_login(PurpleAccount
*account
);
52 static void irc_login_cb(GObject
*source
, GAsyncResult
*res
, gpointer user_data
);
53 static void irc_close(PurpleConnection
*gc
);
54 static int irc_im_send(PurpleConnection
*gc
, PurpleMessage
*msg
);
55 static int irc_chat_send(PurpleConnection
*gc
, int id
, PurpleMessage
*msg
);
56 static void irc_chat_join (PurpleConnection
*gc
, GHashTable
*data
);
57 static void irc_read_input_cb(GObject
*source
, GAsyncResult
*res
, gpointer data
);
59 static guint
irc_nick_hash(const char *nick
);
60 static gboolean
irc_nick_equal(const char *nick1
, const char *nick2
);
61 static void irc_buddy_free(struct irc_buddy
*ib
);
63 PurpleProtocol
*_irc_protocol
= NULL
;
66 irc_uri_handler_match_server(gconstpointer a
, gconstpointer b
)
68 PurpleAccount
*account
= PURPLE_ACCOUNT(a
);
69 const gchar
*match_server
= b
;
70 const gchar
*protocol_id
;
71 const gchar
*username
;
74 protocol_id
= purple_account_get_protocol_id(account
);
76 if (!purple_strequal(protocol_id
, "prpl-irc") ||
77 !purple_account_is_connected(account
)) {
81 if (match_server
== NULL
|| match_server
[0] == '\0') {
82 /* No server specified, match any IRC account */
86 username
= purple_account_get_username(account
);
87 server
= strchr(username
, '@');
90 if (server
== NULL
|| !purple_strequal(match_server
, server
+ 1)) {
98 irc_uri_handler(const gchar
*scheme
, const gchar
*uri
, GHashTable
*params
)
104 gchar
**target_tokens
;
105 PurpleAccount
*account
;
107 gboolean isnick
= FALSE
;
109 g_return_val_if_fail(uri
!= NULL
, FALSE
);
111 if (!purple_strequal(scheme
, "irc")) {
112 /* Not a scheme we handle here */
116 if (g_str_has_prefix(uri
, "//")) {
117 /* Skip initial '//' if it exists */
121 /* Find the target (aka room or user) */
122 target
= strchr(uri
, '/');
124 /* [1] to skip the '/' */
125 if (target
== NULL
|| target
[1] == '\0') {
126 purple_debug_warning("irc",
127 "URI missing valid target: %s", uri
);
131 server
= g_strndup(uri
, target
- uri
);
133 /* Find account with correct server */
134 accounts
= purple_accounts_get_all();
135 account_node
= g_list_find_custom(accounts
, server
,
136 irc_uri_handler_match_server
);
138 if (account_node
== NULL
) {
139 purple_debug_warning("irc",
140 "No account online on '%s' for handling URI",
146 account
= account_node
->data
;
148 /* Tokenize modifiers, +1 to skip the initial '/' */
149 target_tokens
= g_strsplit(target
+ 1, ",", 0);
150 target
= g_strdup_printf("#%s", target_tokens
[0]);
152 /* Parse modifiers, start at 1 to skip the actual target */
153 for (modifier
= target_tokens
+ 1; *modifier
!= NULL
; ++modifier
) {
154 if (purple_strequal(*modifier
, "isnick")) {
160 g_strfreev(target_tokens
);
163 PurpleIMConversation
*im
;
165 /* 'server' isn't needed here. Free it immediately. */
168 /* +1 to skip '#' target prefix */
169 im
= purple_im_conversation_new(account
, target
+ 1);
172 purple_conversation_present(PURPLE_CONVERSATION(im
));
174 if (params
!= NULL
) {
175 const gchar
*msg
= g_hash_table_lookup(params
, "msg");
178 purple_conversation_send_confirm(
179 PURPLE_CONVERSATION(im
), msg
);
185 GHashTable
*components
;
187 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
190 /* Transfer ownership of these to the hash table */
191 g_hash_table_insert(components
, "server", server
);
192 g_hash_table_insert(components
, "channel", target
);
194 if (params
!= NULL
) {
195 const gchar
*key
= g_hash_table_lookup(params
, "key");
198 g_hash_table_insert(components
, "password",
203 purple_serv_join_chat(purple_account_get_connection(account
),
205 g_hash_table_destroy(components
);
212 static void irc_view_motd(PurpleProtocolAction
*action
)
214 PurpleConnection
*gc
= action
->connection
;
215 struct irc_conn
*irc
;
218 if (gc
== NULL
|| purple_connection_get_protocol_data(gc
) == NULL
) {
219 purple_debug(PURPLE_DEBUG_ERROR
, "irc", "got MOTD request for NULL gc\n");
222 irc
= purple_connection_get_protocol_data(gc
);
223 if (irc
->motd
== NULL
) {
224 purple_notify_error(gc
, _("Error displaying MOTD"),
225 _("No MOTD available"),
226 _("There is no MOTD associated with this connection."),
227 purple_request_cpar_from_connection(gc
));
230 title
= g_strdup_printf(_("MOTD for %s"), irc
->server
);
231 body
= g_strdup_printf("<span style=\"font-family: monospace;\">%s</span>", irc
->motd
->str
);
232 purple_notify_formatted(gc
, title
, title
, NULL
, body
, NULL
, NULL
);
237 static int irc_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
239 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
243 irc_send_len(irc
, buf
, len
);
248 irc_push_bytes_cb(GObject
*source
, GAsyncResult
*res
, gpointer data
)
250 PurpleQueuedOutputStream
*stream
= PURPLE_QUEUED_OUTPUT_STREAM(source
);
251 PurpleConnection
*gc
= data
;
253 GError
*error
= NULL
;
255 result
= purple_queued_output_stream_push_bytes_finish(stream
,
259 purple_queued_output_stream_clear_queue(stream
);
261 g_prefix_error(&error
, _("Lost connection with server: "));
262 purple_connection_take_error(gc
, error
);
267 int irc_send(struct irc_conn
*irc
, const char *buf
)
269 return irc_send_len(irc
, buf
, strlen(buf
));
272 int irc_send_len(struct irc_conn
*irc
, const char *buf
, int buflen
)
274 char *tosend
= g_strdup(buf
);
278 purple_signal_emit(_irc_protocol
, "irc-sending-text", purple_account_get_connection(irc
->account
), &tosend
);
283 if (purple_debug_is_verbose()) {
284 gchar
*clean
= purple_utf8_salvage(tosend
);
285 clean
= g_strstrip(clean
);
286 purple_debug_misc("irc", "<< %s\n", clean
);
290 len
= strlen(tosend
);
291 data
= g_bytes_new_take(tosend
, len
);
292 purple_queued_output_stream_push_bytes_async(irc
->output
, data
,
293 G_PRIORITY_DEFAULT
, irc
->cancellable
, irc_push_bytes_cb
,
294 purple_account_get_connection(irc
->account
));
300 /* XXX I don't like messing directly with these buddies */
301 gboolean
irc_blist_timeout(struct irc_conn
*irc
)
303 if (irc
->ison_outstanding
) {
307 g_hash_table_foreach(irc
->buddies
, (GHFunc
)irc_ison_buddy_init
,
308 (gpointer
*)&irc
->buddies_outstanding
);
310 irc_buddy_query(irc
);
315 void irc_buddy_query(struct irc_conn
*irc
)
319 struct irc_buddy
*ib
;
322 string
= g_string_sized_new(512);
324 while ((lp
= g_list_first(irc
->buddies_outstanding
))) {
325 ib
= (struct irc_buddy
*)lp
->data
;
326 if (string
->len
+ strlen(ib
->name
) + 1 > 450)
328 g_string_append_printf(string
, "%s ", ib
->name
);
329 ib
->new_online_status
= FALSE
;
330 irc
->buddies_outstanding
= g_list_delete_link(irc
->buddies_outstanding
, lp
);
334 buf
= irc_format(irc
, "vn", "ISON", string
->str
);
337 irc
->ison_outstanding
= TRUE
;
339 irc
->ison_outstanding
= FALSE
;
341 g_string_free(string
, TRUE
);
344 static void irc_ison_buddy_init(char *name
, struct irc_buddy
*ib
, GList
**list
)
346 *list
= g_list_append(*list
, ib
);
350 static void irc_ison_one(struct irc_conn
*irc
, struct irc_buddy
*ib
)
354 if (irc
->buddies_outstanding
!= NULL
) {
355 irc
->buddies_outstanding
= g_list_append(irc
->buddies_outstanding
, ib
);
359 ib
->new_online_status
= FALSE
;
360 buf
= irc_format(irc
, "vn", "ISON", ib
->name
);
366 static const char *irc_blist_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
371 static GList
*irc_status_types(PurpleAccount
*account
)
373 PurpleStatusType
*type
;
376 type
= purple_status_type_new(PURPLE_STATUS_AVAILABLE
, NULL
, NULL
, TRUE
);
377 types
= g_list_append(types
, type
);
379 type
= purple_status_type_new_with_attrs(
380 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
381 "message", _("Message"), purple_value_new(G_TYPE_STRING
),
383 types
= g_list_append(types
, type
);
385 type
= purple_status_type_new(PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
);
386 types
= g_list_append(types
, type
);
391 static GList
*irc_get_actions(PurpleConnection
*gc
)
394 PurpleProtocolAction
*act
= NULL
;
396 act
= purple_protocol_action_new(_("View MOTD"), irc_view_motd
);
397 list
= g_list_append(list
, act
);
402 static GList
*irc_chat_join_info(PurpleConnection
*gc
)
405 PurpleProtocolChatEntry
*pce
;
407 pce
= g_new0(PurpleProtocolChatEntry
, 1);
408 pce
->label
= _("_Channel:");
409 pce
->identifier
= "channel";
410 pce
->required
= TRUE
;
411 m
= g_list_append(m
, pce
);
413 pce
= g_new0(PurpleProtocolChatEntry
, 1);
414 pce
->label
= _("_Password:");
415 pce
->identifier
= "password";
417 m
= g_list_append(m
, pce
);
422 static GHashTable
*irc_chat_info_defaults(PurpleConnection
*gc
, const char *chat_name
)
424 GHashTable
*defaults
;
426 defaults
= g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, g_free
);
428 if (chat_name
!= NULL
)
429 g_hash_table_insert(defaults
, "channel", g_strdup(chat_name
));
434 static void irc_login(PurpleAccount
*account
)
436 PurpleConnection
*gc
;
437 struct irc_conn
*irc
;
439 const char *username
= purple_account_get_username(account
);
440 GSocketClient
*client
;
441 GError
*error
= NULL
;
443 gc
= purple_account_get_connection(account
);
444 purple_connection_set_flags(gc
, PURPLE_CONNECTION_FLAG_NO_NEWLINES
|
445 PURPLE_CONNECTION_FLAG_NO_IMAGES
);
447 if (strpbrk(username
, " \t\v\r\n") != NULL
) {
448 purple_connection_take_error(gc
, g_error_new_literal(
449 PURPLE_CONNECTION_ERROR
,
450 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
451 _("IRC nick and server may not contain whitespace")));
455 irc
= g_new0(struct irc_conn
, 1);
456 purple_connection_set_protocol_data(gc
, irc
);
457 irc
->account
= account
;
458 irc
->cancellable
= g_cancellable_new();
460 userparts
= g_strsplit(username
, "@", 2);
461 purple_connection_set_display_name(gc
, userparts
[0]);
462 irc
->server
= g_strdup(userparts
[1]);
463 g_strfreev(userparts
);
465 irc
->buddies
= g_hash_table_new_full((GHashFunc
)irc_nick_hash
, (GEqualFunc
)irc_nick_equal
,
466 NULL
, (GDestroyNotify
)irc_buddy_free
);
467 irc
->cmds
= g_hash_table_new(g_str_hash
, g_str_equal
);
468 irc_cmd_table_build(irc
);
469 irc
->msgs
= g_hash_table_new(g_str_hash
, g_str_equal
);
470 irc_msg_table_build(irc
);
472 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
474 client
= purple_gio_socket_client_new(account
, &error
);
476 if (client
== NULL
) {
477 purple_connection_take_error(gc
, error
);
481 /* Optionally use TLS if it's set in the account settings */
482 g_socket_client_set_tls(client
,
483 purple_account_get_bool(account
, "ssl", FALSE
));
485 g_socket_client_connect_to_host_async(client
, irc
->server
,
486 purple_account_get_int(account
, "port",
487 g_socket_client_get_tls(client
) ?
488 IRC_DEFAULT_SSL_PORT
:
490 irc
->cancellable
, irc_login_cb
, gc
);
491 g_object_unref(client
);
494 static gboolean
do_login(PurpleConnection
*gc
) {
495 char *buf
, *tmp
= NULL
;
497 const char *nickname
, *identname
, *realname
;
498 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
499 const char *pass
= purple_connection_get_password(gc
);
500 #ifdef HAVE_CYRUS_SASL
501 const gboolean use_sasl
= purple_account_get_bool(irc
->account
, "sasl", FALSE
);
505 #ifdef HAVE_CYRUS_SASL
507 buf
= irc_format(irc
, "vv:", "CAP", "REQ", "sasl");
508 else /* intended to fall through */
510 buf
= irc_format(irc
, "v:", "PASS", pass
);
511 if (irc_send(irc
, buf
) < 0) {
518 realname
= purple_account_get_string(irc
->account
, "realname", "");
519 identname
= purple_account_get_string(irc
->account
, "username", "");
521 if (identname
== NULL
|| *identname
== '\0') {
522 identname
= g_get_user_name();
525 if (identname
!= NULL
&& strchr(identname
, ' ') != NULL
) {
526 tmp
= g_strdup(identname
);
527 while ((buf
= strchr(tmp
, ' ')) != NULL
) {
532 if (*irc
->server
== ':') {
533 /* Same as hostname, above. */
534 server
= g_strdup_printf("0%s", irc
->server
);
536 server
= g_strdup(irc
->server
);
539 buf
= irc_format(irc
, "vvvv:", "USER", tmp
? tmp
: identname
, "*", server
,
540 *realname
== '\0' ? IRC_DEFAULT_ALIAS
: realname
);
543 if (irc_send(irc
, buf
) < 0) {
548 nickname
= purple_connection_get_display_name(gc
);
549 buf
= irc_format(irc
, "vn", "NICK", nickname
);
550 irc
->reqnick
= g_strdup(nickname
);
551 irc
->nickused
= FALSE
;
552 if (irc_send(irc
, buf
) < 0) {
558 irc
->recv_time
= time(NULL
);
564 irc_login_cb(GObject
*source
, GAsyncResult
*res
, gpointer user_data
)
566 PurpleConnection
*gc
= user_data
;
567 GSocketConnection
*conn
;
568 GError
*error
= NULL
;
569 struct irc_conn
*irc
;
571 conn
= g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source
),
575 g_prefix_error(&error
, _("Unable to connect: "));
576 purple_connection_take_error(gc
, error
);
580 irc
= purple_connection_get_protocol_data(gc
);
582 irc
->output
= purple_queued_output_stream_new(
583 g_io_stream_get_output_stream(G_IO_STREAM(irc
->conn
)));
586 irc
->input
= g_data_input_stream_new(
587 g_io_stream_get_input_stream(
588 G_IO_STREAM(irc
->conn
)));
589 g_data_input_stream_read_line_async(irc
->input
,
590 G_PRIORITY_DEFAULT
, irc
->cancellable
,
591 irc_read_input_cb
, gc
);
595 static void irc_close(PurpleConnection
*gc
)
597 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
602 if (irc
->conn
!= NULL
)
603 irc_cmd_quit(irc
, "quit", NULL
, NULL
);
605 if (irc
->cancellable
!= NULL
) {
606 g_cancellable_cancel(irc
->cancellable
);
607 g_clear_object(&irc
->cancellable
);
610 if (irc
->conn
!= NULL
) {
611 purple_gio_graceful_close(G_IO_STREAM(irc
->conn
),
612 G_INPUT_STREAM(irc
->input
),
613 G_OUTPUT_STREAM(irc
->output
));
616 g_clear_object(&irc
->input
);
617 g_clear_object(&irc
->output
);
618 g_clear_object(&irc
->conn
);
621 g_source_remove(irc
->timer
);
622 g_hash_table_destroy(irc
->cmds
);
623 g_hash_table_destroy(irc
->msgs
);
624 g_hash_table_destroy(irc
->buddies
);
626 g_string_free(irc
->motd
, TRUE
);
629 g_free(irc
->mode_chars
);
630 g_free(irc
->reqnick
);
632 #ifdef HAVE_CYRUS_SASL
633 if (irc
->sasl_conn
) {
634 sasl_dispose(&irc
->sasl_conn
);
635 irc
->sasl_conn
= NULL
;
637 g_free(irc
->sasl_cb
);
639 g_string_free(irc
->sasl_mechs
, TRUE
);
646 static int irc_im_send(PurpleConnection
*gc
, PurpleMessage
*msg
)
648 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
652 args
[0] = irc_nick_skip_mode(irc
, purple_message_get_recipient(msg
));
654 purple_markup_html_to_xhtml(purple_message_get_contents(msg
),
658 irc_cmd_privmsg(irc
, "msg", NULL
, args
);
663 static void irc_get_info(PurpleConnection
*gc
, const char *who
)
665 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
669 irc_cmd_whois(irc
, "whois", NULL
, args
);
672 static void irc_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
674 PurpleConnection
*gc
= purple_account_get_connection(account
);
675 struct irc_conn
*irc
;
677 const char *status_id
= purple_status_get_id(status
);
679 g_return_if_fail(gc
!= NULL
);
680 irc
= purple_connection_get_protocol_data(gc
);
682 if (!purple_status_is_active(status
))
687 if (purple_strequal(status_id
, "away")) {
688 args
[0] = purple_status_get_attr_string(status
, "message");
689 if ((args
[0] == NULL
) || (*args
[0] == '\0'))
691 irc_cmd_away(irc
, "away", NULL
, args
);
692 } else if (purple_strequal(status_id
, "available")) {
693 irc_cmd_away(irc
, "back", NULL
, args
);
697 static void irc_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
, const char *message
)
699 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
700 struct irc_buddy
*ib
;
701 const char *bname
= purple_buddy_get_name(buddy
);
703 ib
= g_hash_table_lookup(irc
->buddies
, bname
);
706 purple_protocol_got_user_status(irc
->account
, bname
,
707 ib
->online
? "available" : "offline", NULL
);
709 ib
= g_new0(struct irc_buddy
, 1);
710 ib
->name
= g_strdup(bname
);
712 g_hash_table_replace(irc
->buddies
, ib
->name
, ib
);
715 /* if the timer isn't set, this is during signon, so we don't want to flood
716 * ourself off with ISON's, so we don't, but after that we want to know when
717 * someone's online asap */
719 irc_ison_one(irc
, ib
);
722 static void irc_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
724 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
725 struct irc_buddy
*ib
;
727 ib
= g_hash_table_lookup(irc
->buddies
, purple_buddy_get_name(buddy
));
728 if (ib
&& --ib
->ref
== 0) {
729 g_hash_table_remove(irc
->buddies
, purple_buddy_get_name(buddy
));
734 irc_read_input_cb(GObject
*source
, GAsyncResult
*res
, gpointer data
)
736 PurpleConnection
*gc
= data
;
737 struct irc_conn
*irc
;
741 GError
*error
= NULL
;
743 line
= g_data_input_stream_read_line_finish(
744 G_DATA_INPUT_STREAM(source
), res
, &len
, &error
);
746 if (line
== NULL
&& error
!= NULL
) {
747 g_prefix_error(&error
, _("Lost connection with server: "));
748 purple_connection_take_error(gc
, error
);
750 } else if (line
== NULL
) {
751 purple_connection_take_error(gc
, g_error_new_literal(
752 PURPLE_CONNECTION_ERROR
,
753 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
754 _("Server closed the connection")));
758 irc
= purple_connection_get_protocol_data(gc
);
760 purple_connection_update_last_received(gc
);
762 if (len
> 0 && line
[len
- 1] == '\r')
763 line
[len
- 1] = '\0';
765 /* This is a hack to work around the fact that marv gets messages
766 * with null bytes in them while using some weird irc server at work
768 while (start
< len
&& line
[start
] == '\0')
772 irc_parse_msg(irc
, line
+ start
);
776 g_data_input_stream_read_line_async(irc
->input
,
777 G_PRIORITY_DEFAULT
, irc
->cancellable
,
778 irc_read_input_cb
, gc
);
781 static void irc_chat_join (PurpleConnection
*gc
, GHashTable
*data
)
783 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
786 args
[0] = g_hash_table_lookup(data
, "channel");
787 args
[1] = g_hash_table_lookup(data
, "password");
788 irc_cmd_join(irc
, "join", NULL
, args
);
791 static char *irc_get_chat_name(GHashTable
*data
) {
792 return g_strdup(g_hash_table_lookup(data
, "channel"));
795 static void irc_chat_invite(PurpleConnection
*gc
, int id
, const char *message
, const char *name
)
797 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
798 PurpleConversation
*convo
= PURPLE_CONVERSATION(purple_conversations_find_chat(gc
, id
));
802 purple_debug(PURPLE_DEBUG_ERROR
, "irc", "Got chat invite request for bogus chat\n");
806 args
[1] = purple_conversation_get_name(convo
);
807 irc_cmd_invite(irc
, "invite", purple_conversation_get_name(convo
), args
);
811 static void irc_chat_leave (PurpleConnection
*gc
, int id
)
813 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
814 PurpleConversation
*convo
= PURPLE_CONVERSATION(purple_conversations_find_chat(gc
, id
));
820 args
[0] = purple_conversation_get_name(convo
);
822 irc_cmd_part(irc
, "part", purple_conversation_get_name(convo
), args
);
823 purple_serv_got_chat_left(gc
, id
);
826 static int irc_chat_send(PurpleConnection
*gc
, int id
, PurpleMessage
*msg
)
828 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
829 PurpleConversation
*convo
= PURPLE_CONVERSATION(purple_conversations_find_chat(gc
, id
));
834 purple_debug(PURPLE_DEBUG_ERROR
, "irc", "chat send on nonexistent chat\n");
837 purple_markup_html_to_xhtml(purple_message_get_contents(msg
), NULL
, &tmp
);
838 args
[0] = purple_conversation_get_name(convo
);
841 irc_cmd_privmsg(irc
, "msg", NULL
, args
);
844 purple_serv_got_chat_in(gc
, id
, purple_connection_get_display_name(gc
),
845 purple_message_get_flags(msg
),
846 purple_message_get_contents(msg
), time(NULL
));
851 static guint
irc_nick_hash(const char *nick
)
856 lc
= g_utf8_strdown(nick
, -1);
857 bucket
= g_str_hash(lc
);
863 static gboolean
irc_nick_equal(const char *nick1
, const char *nick2
)
865 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
868 static void irc_buddy_free(struct irc_buddy
*ib
)
874 static void irc_chat_set_topic(PurpleConnection
*gc
, int id
, const char *topic
)
877 const char *name
= NULL
;
878 struct irc_conn
*irc
;
880 irc
= purple_connection_get_protocol_data(gc
);
881 name
= purple_conversation_get_name(PURPLE_CONVERSATION(
882 purple_conversations_find_chat(gc
, id
)));
887 buf
= irc_format(irc
, "vt:", "TOPIC", name
, topic
);
892 static PurpleRoomlist
*irc_roomlist_get_list(PurpleConnection
*gc
)
894 struct irc_conn
*irc
;
895 GList
*fields
= NULL
;
896 PurpleRoomlistField
*f
;
899 irc
= purple_connection_get_protocol_data(gc
);
902 g_object_unref(irc
->roomlist
);
904 irc
->roomlist
= purple_roomlist_new(purple_connection_get_account(gc
));
906 f
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING
, "", "channel", TRUE
);
907 fields
= g_list_append(fields
, f
);
909 f
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT
, _("Users"), "users", FALSE
);
910 fields
= g_list_append(fields
, f
);
912 f
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING
, _("Topic"), "topic", FALSE
);
913 fields
= g_list_append(fields
, f
);
915 purple_roomlist_set_fields(irc
->roomlist
, fields
);
917 buf
= irc_format(irc
, "v", "LIST");
921 return irc
->roomlist
;
924 static void irc_roomlist_cancel(PurpleRoomlist
*list
)
926 PurpleAccount
*account
= purple_roomlist_get_account(list
);
927 PurpleConnection
*gc
= purple_account_get_connection(account
);
928 struct irc_conn
*irc
;
933 irc
= purple_connection_get_protocol_data(gc
);
935 purple_roomlist_set_in_progress(list
, FALSE
);
937 if (irc
->roomlist
== list
) {
938 irc
->roomlist
= NULL
;
939 g_object_unref(list
);
943 static void irc_keepalive(PurpleConnection
*gc
)
945 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
946 if ((time(NULL
) - irc
->recv_time
) > PING_TIMEOUT
)
947 irc_cmd_ping(irc
, NULL
, NULL
, NULL
);
951 irc_get_max_message_size(PurpleConversation
*conv
)
953 /* TODO: this static value is got from pidgin-otr, but it depends on
954 * some factors, for example IRC channel name. */
959 irc_protocol_init(PurpleProtocol
*protocol
)
961 PurpleAccountUserSplit
*split
;
962 PurpleAccountOption
*option
;
964 protocol
->id
= "prpl-irc";
965 protocol
->name
= "IRC";
966 protocol
->options
= OPT_PROTO_CHAT_TOPIC
| OPT_PROTO_PASSWORD_OPTIONAL
|
967 OPT_PROTO_SLASH_COMMANDS_NATIVE
;
969 split
= purple_account_user_split_new(_("Server"), IRC_DEFAULT_SERVER
, '@');
970 protocol
->user_splits
= g_list_append(protocol
->user_splits
, split
);
972 option
= purple_account_option_int_new(_("Port"), "port", IRC_DEFAULT_PORT
);
973 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
975 option
= purple_account_option_string_new(_("Encodings"), "encoding", IRC_DEFAULT_CHARSET
);
976 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
978 option
= purple_account_option_bool_new(_("Auto-detect incoming UTF-8"), "autodetect_utf8", IRC_DEFAULT_AUTODETECT
);
979 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
981 option
= purple_account_option_string_new(_("Ident name"), "username", "");
982 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
984 option
= purple_account_option_string_new(_("Real name"), "realname", "");
985 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
988 option = purple_account_option_string_new(_("Quit message"), "quitmsg", IRC_DEFAULT_QUIT);
989 protocol->account_options = g_list_append(protocol->account_options, option);
992 option
= purple_account_option_bool_new(_("Use SSL"), "ssl", FALSE
);
993 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
995 #ifdef HAVE_CYRUS_SASL
996 option
= purple_account_option_bool_new(_("Authenticate with SASL"), "sasl", FALSE
);
997 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
999 option
= purple_account_option_bool_new(
1000 _("Allow plaintext SASL auth over unencrypted connection"),
1001 "auth_plain_in_clear", FALSE
);
1002 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
1007 irc_protocol_class_init(PurpleProtocolClass
*klass
)
1009 klass
->login
= irc_login
;
1010 klass
->close
= irc_close
;
1011 klass
->status_types
= irc_status_types
;
1012 klass
->list_icon
= irc_blist_icon
;
1016 irc_protocol_client_iface_init(PurpleProtocolClientInterface
*client_iface
)
1018 client_iface
->get_actions
= irc_get_actions
;
1019 client_iface
->normalize
= purple_normalize_nocase
;
1020 client_iface
->get_max_message_size
= irc_get_max_message_size
;
1024 irc_protocol_server_iface_init(PurpleProtocolServerInterface
*server_iface
)
1026 server_iface
->set_status
= irc_set_status
;
1027 server_iface
->get_info
= irc_get_info
;
1028 server_iface
->add_buddy
= irc_add_buddy
;
1029 server_iface
->remove_buddy
= irc_remove_buddy
;
1030 server_iface
->keepalive
= irc_keepalive
;
1031 server_iface
->send_raw
= irc_send_raw
;
1035 irc_protocol_im_iface_init(PurpleProtocolIMInterface
*im_iface
)
1037 im_iface
->send
= irc_im_send
;
1041 irc_protocol_chat_iface_init(PurpleProtocolChatInterface
*chat_iface
)
1043 chat_iface
->info
= irc_chat_join_info
;
1044 chat_iface
->info_defaults
= irc_chat_info_defaults
;
1045 chat_iface
->join
= irc_chat_join
;
1046 chat_iface
->get_name
= irc_get_chat_name
;
1047 chat_iface
->invite
= irc_chat_invite
;
1048 chat_iface
->leave
= irc_chat_leave
;
1049 chat_iface
->send
= irc_chat_send
;
1050 chat_iface
->set_topic
= irc_chat_set_topic
;
1054 irc_protocol_roomlist_iface_init(PurpleProtocolRoomlistInterface
*roomlist_iface
)
1056 roomlist_iface
->get_list
= irc_roomlist_get_list
;
1057 roomlist_iface
->cancel
= irc_roomlist_cancel
;
1061 irc_protocol_xfer_iface_init(PurpleProtocolXferInterface
*xfer_iface
)
1063 xfer_iface
->send_file
= irc_dccsend_send_file
;
1064 xfer_iface
->new_xfer
= irc_dccsend_new_xfer
;
1067 PURPLE_DEFINE_TYPE_EXTENDED(
1068 IRCProtocol
, irc_protocol
, PURPLE_TYPE_PROTOCOL
, 0,
1070 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT
,
1071 irc_protocol_client_iface_init
)
1073 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER
,
1074 irc_protocol_server_iface_init
)
1076 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM
,
1077 irc_protocol_im_iface_init
)
1079 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT
,
1080 irc_protocol_chat_iface_init
)
1082 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST
,
1083 irc_protocol_roomlist_iface_init
)
1085 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER
,
1086 irc_protocol_xfer_iface_init
)
1089 static PurplePluginInfo
*
1090 plugin_query(GError
**error
)
1092 return purple_plugin_info_new(
1094 "name", "IRC Protocol",
1095 "version", DISPLAY_VERSION
,
1096 "category", N_("Protocol"),
1097 "summary", N_("IRC Protocol Plugin"),
1098 "description", N_("The IRC Protocol Plugin that Sucks Less"),
1099 "website", PURPLE_WEBSITE
,
1100 "abi-version", PURPLE_ABI_VERSION
,
1101 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL
|
1102 PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD
,
1108 plugin_load(PurplePlugin
*plugin
, GError
**error
)
1110 irc_protocol_register_type(plugin
);
1112 irc_xfer_register(G_TYPE_MODULE(plugin
));
1114 _irc_protocol
= purple_protocols_add(IRC_TYPE_PROTOCOL
, error
);
1118 purple_prefs_remove("/plugins/prpl/irc/quitmsg");
1119 purple_prefs_remove("/plugins/prpl/irc");
1121 irc_register_commands();
1123 purple_signal_register(_irc_protocol
, "irc-sending-text",
1124 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
1125 PURPLE_TYPE_CONNECTION
,
1126 G_TYPE_POINTER
); /* pointer to a string */
1127 purple_signal_register(_irc_protocol
, "irc-receiving-text",
1128 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
1129 PURPLE_TYPE_CONNECTION
,
1130 G_TYPE_POINTER
); /* pointer to a string */
1132 purple_signal_connect(purple_get_core(), "uri-handler", plugin
,
1133 PURPLE_CALLBACK(irc_uri_handler
), NULL
);
1139 plugin_unload(PurplePlugin
*plugin
, GError
**error
)
1141 irc_unregister_commands();
1143 purple_signal_disconnect(purple_get_core(), "uri-handler", plugin
,
1144 PURPLE_CALLBACK(irc_uri_handler
));
1146 if (!purple_protocols_remove(_irc_protocol
, error
))
1152 PURPLE_PLUGIN_INIT(irc
, plugin_query
, plugin_load
, plugin_unload
);