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(PurpleAccount
*account
, const gchar
*match_server
)
68 const gchar
*protocol_id
;
69 const gchar
*username
;
72 protocol_id
= purple_account_get_protocol_id(account
);
74 if (!purple_strequal(protocol_id
, "prpl-irc") ||
75 !purple_account_is_connected(account
)) {
79 if (match_server
== NULL
|| match_server
[0] == '\0') {
80 /* No server specified, match any IRC account */
84 username
= purple_account_get_username(account
);
85 server
= strchr(username
, '@');
88 if (server
== NULL
|| !purple_strequal(match_server
, server
+ 1)) {
96 irc_uri_handler(const gchar
*scheme
, const gchar
*uri
, GHashTable
*params
)
102 gchar
**target_tokens
;
103 PurpleAccount
*account
;
105 gboolean isnick
= FALSE
;
107 g_return_val_if_fail(uri
!= NULL
, FALSE
);
109 if (!purple_strequal(scheme
, "irc")) {
110 /* Not a scheme we handle here */
114 if (g_str_has_prefix(uri
, "//")) {
115 /* Skip initial '//' if it exists */
119 /* Find the target (aka room or user) */
120 target
= strchr(uri
, '/');
122 /* [1] to skip the '/' */
123 if (target
== NULL
|| target
[1] == '\0') {
124 purple_debug_warning("irc",
125 "URI missing valid target: %s", uri
);
129 server
= g_strndup(uri
, target
- uri
);
131 /* Find account with correct server */
132 accounts
= purple_accounts_get_all();
133 account_node
= g_list_find_custom(
134 accounts
, server
, (GCompareFunc
)irc_uri_handler_match_server
);
136 if (account_node
== NULL
) {
137 purple_debug_warning("irc",
138 "No account online on '%s' for handling URI",
144 account
= account_node
->data
;
146 /* Tokenize modifiers, +1 to skip the initial '/' */
147 target_tokens
= g_strsplit(target
+ 1, ",", 0);
148 target
= g_strdup_printf("#%s", target_tokens
[0]);
150 /* Parse modifiers, start at 1 to skip the actual target */
151 for (modifier
= target_tokens
+ 1; *modifier
!= NULL
; ++modifier
) {
152 if (purple_strequal(*modifier
, "isnick")) {
158 g_strfreev(target_tokens
);
161 PurpleIMConversation
*im
;
163 /* 'server' isn't needed here. Free it immediately. */
166 /* +1 to skip '#' target prefix */
167 im
= purple_im_conversation_new(account
, target
+ 1);
170 purple_conversation_present(PURPLE_CONVERSATION(im
));
172 if (params
!= NULL
) {
173 const gchar
*msg
= g_hash_table_lookup(params
, "msg");
176 purple_conversation_send_confirm(
177 PURPLE_CONVERSATION(im
), msg
);
183 GHashTable
*components
;
185 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
188 /* Transfer ownership of these to the hash table */
189 g_hash_table_insert(components
, "server", server
);
190 g_hash_table_insert(components
, "channel", target
);
192 if (params
!= NULL
) {
193 const gchar
*key
= g_hash_table_lookup(params
, "key");
196 g_hash_table_insert(components
, "password",
201 purple_serv_join_chat(purple_account_get_connection(account
),
203 g_hash_table_destroy(components
);
210 static void irc_view_motd(PurpleProtocolAction
*action
)
212 PurpleConnection
*gc
= action
->connection
;
213 struct irc_conn
*irc
;
216 if (gc
== NULL
|| purple_connection_get_protocol_data(gc
) == NULL
) {
217 purple_debug(PURPLE_DEBUG_ERROR
, "irc", "got MOTD request for NULL gc\n");
220 irc
= purple_connection_get_protocol_data(gc
);
221 if (irc
->motd
== NULL
) {
222 purple_notify_error(gc
, _("Error displaying MOTD"),
223 _("No MOTD available"),
224 _("There is no MOTD associated with this connection."),
225 purple_request_cpar_from_connection(gc
));
228 title
= g_strdup_printf(_("MOTD for %s"), irc
->server
);
229 body
= g_strdup_printf("<span style=\"font-family: monospace;\">%s</span>", irc
->motd
->str
);
230 purple_notify_formatted(gc
, title
, title
, NULL
, body
, NULL
, NULL
);
235 static int irc_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
237 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
241 irc_send_len(irc
, buf
, len
);
246 irc_push_bytes_cb(GObject
*source
, GAsyncResult
*res
, gpointer data
)
248 PurpleQueuedOutputStream
*stream
= PURPLE_QUEUED_OUTPUT_STREAM(source
);
249 PurpleConnection
*gc
= data
;
251 GError
*error
= NULL
;
253 result
= purple_queued_output_stream_push_bytes_finish(stream
,
257 purple_queued_output_stream_clear_queue(stream
);
259 g_prefix_error(&error
, _("Lost connection with server: "));
260 purple_connection_take_error(gc
, error
);
265 int irc_send(struct irc_conn
*irc
, const char *buf
)
267 return irc_send_len(irc
, buf
, strlen(buf
));
270 int irc_send_len(struct irc_conn
*irc
, const char *buf
, int buflen
)
272 char *tosend
= g_strdup(buf
);
276 purple_signal_emit(_irc_protocol
, "irc-sending-text", purple_account_get_connection(irc
->account
), &tosend
);
281 if (purple_debug_is_verbose()) {
282 gchar
*clean
= purple_utf8_salvage(tosend
);
283 clean
= g_strstrip(clean
);
284 purple_debug_misc("irc", "<< %s\n", clean
);
288 len
= strlen(tosend
);
289 data
= g_bytes_new_take(tosend
, len
);
290 purple_queued_output_stream_push_bytes_async(irc
->output
, data
,
291 G_PRIORITY_DEFAULT
, irc
->cancellable
, irc_push_bytes_cb
,
292 purple_account_get_connection(irc
->account
));
298 /* XXX I don't like messing directly with these buddies */
299 gboolean
irc_blist_timeout(struct irc_conn
*irc
)
301 if (irc
->ison_outstanding
) {
305 g_hash_table_foreach(irc
->buddies
, (GHFunc
)irc_ison_buddy_init
,
306 (gpointer
*)&irc
->buddies_outstanding
);
308 irc_buddy_query(irc
);
313 void irc_buddy_query(struct irc_conn
*irc
)
317 struct irc_buddy
*ib
;
320 string
= g_string_sized_new(512);
322 while ((lp
= g_list_first(irc
->buddies_outstanding
))) {
323 ib
= (struct irc_buddy
*)lp
->data
;
324 if (string
->len
+ strlen(ib
->name
) + 1 > 450)
326 g_string_append_printf(string
, "%s ", ib
->name
);
327 ib
->new_online_status
= FALSE
;
328 irc
->buddies_outstanding
= g_list_delete_link(irc
->buddies_outstanding
, lp
);
332 buf
= irc_format(irc
, "vn", "ISON", string
->str
);
335 irc
->ison_outstanding
= TRUE
;
337 irc
->ison_outstanding
= FALSE
;
339 g_string_free(string
, TRUE
);
342 static void irc_ison_buddy_init(char *name
, struct irc_buddy
*ib
, GList
**list
)
344 *list
= g_list_append(*list
, ib
);
348 static void irc_ison_one(struct irc_conn
*irc
, struct irc_buddy
*ib
)
352 if (irc
->buddies_outstanding
!= NULL
) {
353 irc
->buddies_outstanding
= g_list_append(irc
->buddies_outstanding
, ib
);
357 ib
->new_online_status
= FALSE
;
358 buf
= irc_format(irc
, "vn", "ISON", ib
->name
);
364 static const char *irc_blist_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
369 static GList
*irc_status_types(PurpleAccount
*account
)
371 PurpleStatusType
*type
;
374 type
= purple_status_type_new(PURPLE_STATUS_AVAILABLE
, NULL
, NULL
, TRUE
);
375 types
= g_list_append(types
, type
);
377 type
= purple_status_type_new_with_attrs(
378 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
379 "message", _("Message"), purple_value_new(G_TYPE_STRING
),
381 types
= g_list_append(types
, type
);
383 type
= purple_status_type_new(PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
);
384 types
= g_list_append(types
, type
);
389 static GList
*irc_get_actions(PurpleConnection
*gc
)
392 PurpleProtocolAction
*act
= NULL
;
394 act
= purple_protocol_action_new(_("View MOTD"), irc_view_motd
);
395 list
= g_list_append(list
, act
);
400 static GList
*irc_chat_join_info(PurpleConnection
*gc
)
403 PurpleProtocolChatEntry
*pce
;
405 pce
= g_new0(PurpleProtocolChatEntry
, 1);
406 pce
->label
= _("_Channel:");
407 pce
->identifier
= "channel";
408 pce
->required
= TRUE
;
409 m
= g_list_append(m
, pce
);
411 pce
= g_new0(PurpleProtocolChatEntry
, 1);
412 pce
->label
= _("_Password:");
413 pce
->identifier
= "password";
415 m
= g_list_append(m
, pce
);
420 static GHashTable
*irc_chat_info_defaults(PurpleConnection
*gc
, const char *chat_name
)
422 GHashTable
*defaults
;
424 defaults
= g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, g_free
);
426 if (chat_name
!= NULL
)
427 g_hash_table_insert(defaults
, "channel", g_strdup(chat_name
));
432 static void irc_login(PurpleAccount
*account
)
434 PurpleConnection
*gc
;
435 struct irc_conn
*irc
;
437 const char *username
= purple_account_get_username(account
);
438 GSocketClient
*client
;
439 GError
*error
= NULL
;
441 gc
= purple_account_get_connection(account
);
442 purple_connection_set_flags(gc
, PURPLE_CONNECTION_FLAG_NO_NEWLINES
|
443 PURPLE_CONNECTION_FLAG_NO_IMAGES
);
445 if (strpbrk(username
, " \t\v\r\n") != NULL
) {
446 purple_connection_take_error(gc
, g_error_new_literal(
447 PURPLE_CONNECTION_ERROR
,
448 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
449 _("IRC nick and server may not contain whitespace")));
453 irc
= g_new0(struct irc_conn
, 1);
454 purple_connection_set_protocol_data(gc
, irc
);
455 irc
->account
= account
;
456 irc
->cancellable
= g_cancellable_new();
458 userparts
= g_strsplit(username
, "@", 2);
459 purple_connection_set_display_name(gc
, userparts
[0]);
460 irc
->server
= g_strdup(userparts
[1]);
461 g_strfreev(userparts
);
463 irc
->buddies
= g_hash_table_new_full((GHashFunc
)irc_nick_hash
, (GEqualFunc
)irc_nick_equal
,
464 NULL
, (GDestroyNotify
)irc_buddy_free
);
465 irc
->cmds
= g_hash_table_new(g_str_hash
, g_str_equal
);
466 irc_cmd_table_build(irc
);
467 irc
->msgs
= g_hash_table_new(g_str_hash
, g_str_equal
);
468 irc_msg_table_build(irc
);
470 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
472 client
= purple_gio_socket_client_new(account
, &error
);
474 if (client
== NULL
) {
475 purple_connection_take_error(gc
, error
);
479 /* Optionally use TLS if it's set in the account settings */
480 g_socket_client_set_tls(client
,
481 purple_account_get_bool(account
, "ssl", FALSE
));
483 g_socket_client_connect_to_host_async(client
, irc
->server
,
484 purple_account_get_int(account
, "port",
485 g_socket_client_get_tls(client
) ?
486 IRC_DEFAULT_SSL_PORT
:
488 irc
->cancellable
, irc_login_cb
, gc
);
489 g_object_unref(client
);
492 static gboolean
do_login(PurpleConnection
*gc
) {
493 char *buf
, *tmp
= NULL
;
495 const char *nickname
, *identname
, *realname
;
496 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
497 const char *pass
= purple_connection_get_password(gc
);
498 #ifdef HAVE_CYRUS_SASL
499 const gboolean use_sasl
= purple_account_get_bool(irc
->account
, "sasl", FALSE
);
503 #ifdef HAVE_CYRUS_SASL
505 buf
= irc_format(irc
, "vv:", "CAP", "REQ", "sasl");
506 else /* intended to fall through */
508 buf
= irc_format(irc
, "v:", "PASS", pass
);
509 if (irc_send(irc
, buf
) < 0) {
516 realname
= purple_account_get_string(irc
->account
, "realname", "");
517 identname
= purple_account_get_string(irc
->account
, "username", "");
519 if (identname
== NULL
|| *identname
== '\0') {
520 identname
= g_get_user_name();
523 if (identname
!= NULL
&& strchr(identname
, ' ') != NULL
) {
524 tmp
= g_strdup(identname
);
525 while ((buf
= strchr(tmp
, ' ')) != NULL
) {
530 if (*irc
->server
== ':') {
531 /* Same as hostname, above. */
532 server
= g_strdup_printf("0%s", irc
->server
);
534 server
= g_strdup(irc
->server
);
537 buf
= irc_format(irc
, "vvvv:", "USER", tmp
? tmp
: identname
, "*", server
,
538 *realname
== '\0' ? IRC_DEFAULT_ALIAS
: realname
);
541 if (irc_send(irc
, buf
) < 0) {
546 nickname
= purple_connection_get_display_name(gc
);
547 buf
= irc_format(irc
, "vn", "NICK", nickname
);
548 irc
->reqnick
= g_strdup(nickname
);
549 irc
->nickused
= FALSE
;
550 if (irc_send(irc
, buf
) < 0) {
556 irc
->recv_time
= time(NULL
);
562 irc_login_cb(GObject
*source
, GAsyncResult
*res
, gpointer user_data
)
564 PurpleConnection
*gc
= user_data
;
565 GSocketConnection
*conn
;
566 GError
*error
= NULL
;
567 struct irc_conn
*irc
;
569 conn
= g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source
),
573 g_prefix_error(&error
, _("Unable to connect: "));
574 purple_connection_take_error(gc
, error
);
578 irc
= purple_connection_get_protocol_data(gc
);
580 irc
->output
= purple_queued_output_stream_new(
581 g_io_stream_get_output_stream(G_IO_STREAM(irc
->conn
)));
584 irc
->input
= g_data_input_stream_new(
585 g_io_stream_get_input_stream(
586 G_IO_STREAM(irc
->conn
)));
587 g_data_input_stream_read_line_async(irc
->input
,
588 G_PRIORITY_DEFAULT
, irc
->cancellable
,
589 irc_read_input_cb
, gc
);
593 static void irc_close(PurpleConnection
*gc
)
595 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
600 if (irc
->conn
!= NULL
)
601 irc_cmd_quit(irc
, "quit", NULL
, NULL
);
603 if (irc
->cancellable
!= NULL
) {
604 g_cancellable_cancel(irc
->cancellable
);
605 g_clear_object(&irc
->cancellable
);
608 if (irc
->conn
!= NULL
) {
609 purple_gio_graceful_close(G_IO_STREAM(irc
->conn
),
610 G_INPUT_STREAM(irc
->input
),
611 G_OUTPUT_STREAM(irc
->output
));
614 g_clear_object(&irc
->input
);
615 g_clear_object(&irc
->output
);
616 g_clear_object(&irc
->conn
);
619 g_source_remove(irc
->timer
);
620 g_hash_table_destroy(irc
->cmds
);
621 g_hash_table_destroy(irc
->msgs
);
622 g_hash_table_destroy(irc
->buddies
);
624 g_string_free(irc
->motd
, TRUE
);
627 g_free(irc
->mode_chars
);
628 g_free(irc
->reqnick
);
630 #ifdef HAVE_CYRUS_SASL
631 if (irc
->sasl_conn
) {
632 sasl_dispose(&irc
->sasl_conn
);
633 irc
->sasl_conn
= NULL
;
635 g_free(irc
->sasl_cb
);
637 g_string_free(irc
->sasl_mechs
, TRUE
);
644 static int irc_im_send(PurpleConnection
*gc
, PurpleMessage
*msg
)
646 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
650 args
[0] = irc_nick_skip_mode(irc
, purple_message_get_recipient(msg
));
652 purple_markup_html_to_xhtml(purple_message_get_contents(msg
),
656 irc_cmd_privmsg(irc
, "msg", NULL
, args
);
661 static void irc_get_info(PurpleConnection
*gc
, const char *who
)
663 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
667 irc_cmd_whois(irc
, "whois", NULL
, args
);
670 static void irc_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
672 PurpleConnection
*gc
= purple_account_get_connection(account
);
673 struct irc_conn
*irc
;
675 const char *status_id
= purple_status_get_id(status
);
677 g_return_if_fail(gc
!= NULL
);
678 irc
= purple_connection_get_protocol_data(gc
);
680 if (!purple_status_is_active(status
))
685 if (purple_strequal(status_id
, "away")) {
686 args
[0] = purple_status_get_attr_string(status
, "message");
687 if ((args
[0] == NULL
) || (*args
[0] == '\0'))
689 irc_cmd_away(irc
, "away", NULL
, args
);
690 } else if (purple_strequal(status_id
, "available")) {
691 irc_cmd_away(irc
, "back", NULL
, args
);
695 static void irc_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
, const char *message
)
697 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
698 struct irc_buddy
*ib
;
699 const char *bname
= purple_buddy_get_name(buddy
);
701 ib
= g_hash_table_lookup(irc
->buddies
, bname
);
704 purple_protocol_got_user_status(irc
->account
, bname
,
705 ib
->online
? "available" : "offline", NULL
);
707 ib
= g_new0(struct irc_buddy
, 1);
708 ib
->name
= g_strdup(bname
);
710 g_hash_table_replace(irc
->buddies
, ib
->name
, ib
);
713 /* if the timer isn't set, this is during signon, so we don't want to flood
714 * ourself off with ISON's, so we don't, but after that we want to know when
715 * someone's online asap */
717 irc_ison_one(irc
, ib
);
720 static void irc_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
722 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
723 struct irc_buddy
*ib
;
725 ib
= g_hash_table_lookup(irc
->buddies
, purple_buddy_get_name(buddy
));
726 if (ib
&& --ib
->ref
== 0) {
727 g_hash_table_remove(irc
->buddies
, purple_buddy_get_name(buddy
));
732 irc_read_input_cb(GObject
*source
, GAsyncResult
*res
, gpointer data
)
734 PurpleConnection
*gc
= data
;
735 struct irc_conn
*irc
;
739 GError
*error
= NULL
;
741 line
= g_data_input_stream_read_line_finish(
742 G_DATA_INPUT_STREAM(source
), res
, &len
, &error
);
744 if (line
== NULL
&& error
!= NULL
) {
745 g_prefix_error(&error
, _("Lost connection with server: "));
746 purple_connection_take_error(gc
, error
);
748 } else if (line
== NULL
) {
749 purple_connection_take_error(gc
, g_error_new_literal(
750 PURPLE_CONNECTION_ERROR
,
751 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
752 _("Server closed the connection")));
756 irc
= purple_connection_get_protocol_data(gc
);
758 purple_connection_update_last_received(gc
);
760 if (len
> 0 && line
[len
- 1] == '\r')
761 line
[len
- 1] = '\0';
763 /* This is a hack to work around the fact that marv gets messages
764 * with null bytes in them while using some weird irc server at work
766 while (start
< len
&& line
[start
] == '\0')
770 irc_parse_msg(irc
, line
+ start
);
774 g_data_input_stream_read_line_async(irc
->input
,
775 G_PRIORITY_DEFAULT
, irc
->cancellable
,
776 irc_read_input_cb
, gc
);
779 static void irc_chat_join (PurpleConnection
*gc
, GHashTable
*data
)
781 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
784 args
[0] = g_hash_table_lookup(data
, "channel");
785 args
[1] = g_hash_table_lookup(data
, "password");
786 irc_cmd_join(irc
, "join", NULL
, args
);
789 static char *irc_get_chat_name(GHashTable
*data
) {
790 return g_strdup(g_hash_table_lookup(data
, "channel"));
793 static void irc_chat_invite(PurpleConnection
*gc
, int id
, const char *message
, const char *name
)
795 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
796 PurpleConversation
*convo
= PURPLE_CONVERSATION(purple_conversations_find_chat(gc
, id
));
800 purple_debug(PURPLE_DEBUG_ERROR
, "irc", "Got chat invite request for bogus chat\n");
804 args
[1] = purple_conversation_get_name(convo
);
805 irc_cmd_invite(irc
, "invite", purple_conversation_get_name(convo
), args
);
809 static void irc_chat_leave (PurpleConnection
*gc
, int id
)
811 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
812 PurpleConversation
*convo
= PURPLE_CONVERSATION(purple_conversations_find_chat(gc
, id
));
818 args
[0] = purple_conversation_get_name(convo
);
820 irc_cmd_part(irc
, "part", purple_conversation_get_name(convo
), args
);
821 purple_serv_got_chat_left(gc
, id
);
824 static int irc_chat_send(PurpleConnection
*gc
, int id
, PurpleMessage
*msg
)
826 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
827 PurpleConversation
*convo
= PURPLE_CONVERSATION(purple_conversations_find_chat(gc
, id
));
832 purple_debug(PURPLE_DEBUG_ERROR
, "irc", "chat send on nonexistent chat\n");
835 purple_markup_html_to_xhtml(purple_message_get_contents(msg
), NULL
, &tmp
);
836 args
[0] = purple_conversation_get_name(convo
);
839 irc_cmd_privmsg(irc
, "msg", NULL
, args
);
842 purple_serv_got_chat_in(gc
, id
, purple_connection_get_display_name(gc
),
843 purple_message_get_flags(msg
),
844 purple_message_get_contents(msg
), time(NULL
));
849 static guint
irc_nick_hash(const char *nick
)
854 lc
= g_utf8_strdown(nick
, -1);
855 bucket
= g_str_hash(lc
);
861 static gboolean
irc_nick_equal(const char *nick1
, const char *nick2
)
863 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
866 static void irc_buddy_free(struct irc_buddy
*ib
)
872 static void irc_chat_set_topic(PurpleConnection
*gc
, int id
, const char *topic
)
875 const char *name
= NULL
;
876 struct irc_conn
*irc
;
878 irc
= purple_connection_get_protocol_data(gc
);
879 name
= purple_conversation_get_name(PURPLE_CONVERSATION(
880 purple_conversations_find_chat(gc
, id
)));
885 buf
= irc_format(irc
, "vt:", "TOPIC", name
, topic
);
890 static PurpleRoomlist
*irc_roomlist_get_list(PurpleConnection
*gc
)
892 struct irc_conn
*irc
;
893 GList
*fields
= NULL
;
894 PurpleRoomlistField
*f
;
897 irc
= purple_connection_get_protocol_data(gc
);
900 g_object_unref(irc
->roomlist
);
902 irc
->roomlist
= purple_roomlist_new(purple_connection_get_account(gc
));
904 f
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING
, "", "channel", TRUE
);
905 fields
= g_list_append(fields
, f
);
907 f
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT
, _("Users"), "users", FALSE
);
908 fields
= g_list_append(fields
, f
);
910 f
= purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING
, _("Topic"), "topic", FALSE
);
911 fields
= g_list_append(fields
, f
);
913 purple_roomlist_set_fields(irc
->roomlist
, fields
);
915 buf
= irc_format(irc
, "v", "LIST");
919 return irc
->roomlist
;
922 static void irc_roomlist_cancel(PurpleRoomlist
*list
)
924 PurpleAccount
*account
= purple_roomlist_get_account(list
);
925 PurpleConnection
*gc
= purple_account_get_connection(account
);
926 struct irc_conn
*irc
;
931 irc
= purple_connection_get_protocol_data(gc
);
933 purple_roomlist_set_in_progress(list
, FALSE
);
935 if (irc
->roomlist
== list
) {
936 irc
->roomlist
= NULL
;
937 g_object_unref(list
);
941 static void irc_keepalive(PurpleConnection
*gc
)
943 struct irc_conn
*irc
= purple_connection_get_protocol_data(gc
);
944 if ((time(NULL
) - irc
->recv_time
) > PING_TIMEOUT
)
945 irc_cmd_ping(irc
, NULL
, NULL
, NULL
);
949 irc_get_max_message_size(PurpleConversation
*conv
)
951 /* TODO: this static value is got from pidgin-otr, but it depends on
952 * some factors, for example IRC channel name. */
957 irc_protocol_init(PurpleProtocol
*protocol
)
959 PurpleAccountUserSplit
*split
;
960 PurpleAccountOption
*option
;
962 protocol
->id
= "prpl-irc";
963 protocol
->name
= "IRC";
964 protocol
->options
= OPT_PROTO_CHAT_TOPIC
| OPT_PROTO_PASSWORD_OPTIONAL
|
965 OPT_PROTO_SLASH_COMMANDS_NATIVE
;
967 split
= purple_account_user_split_new(_("Server"), IRC_DEFAULT_SERVER
, '@');
968 protocol
->user_splits
= g_list_append(protocol
->user_splits
, split
);
970 option
= purple_account_option_int_new(_("Port"), "port", IRC_DEFAULT_PORT
);
971 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
973 option
= purple_account_option_string_new(_("Encodings"), "encoding", IRC_DEFAULT_CHARSET
);
974 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
976 option
= purple_account_option_bool_new(_("Auto-detect incoming UTF-8"), "autodetect_utf8", IRC_DEFAULT_AUTODETECT
);
977 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
979 option
= purple_account_option_string_new(_("Ident name"), "username", "");
980 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
982 option
= purple_account_option_string_new(_("Real name"), "realname", "");
983 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
986 option = purple_account_option_string_new(_("Quit message"), "quitmsg", IRC_DEFAULT_QUIT);
987 protocol->account_options = g_list_append(protocol->account_options, option);
990 option
= purple_account_option_bool_new(_("Use SSL"), "ssl", FALSE
);
991 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
993 #ifdef HAVE_CYRUS_SASL
994 option
= purple_account_option_bool_new(_("Authenticate with SASL"), "sasl", FALSE
);
995 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
997 option
= purple_account_option_bool_new(
998 _("Allow plaintext SASL auth over unencrypted connection"),
999 "auth_plain_in_clear", FALSE
);
1000 protocol
->account_options
= g_list_append(protocol
->account_options
, option
);
1005 irc_protocol_class_init(PurpleProtocolClass
*klass
)
1007 klass
->login
= irc_login
;
1008 klass
->close
= irc_close
;
1009 klass
->status_types
= irc_status_types
;
1010 klass
->list_icon
= irc_blist_icon
;
1014 irc_protocol_client_iface_init(PurpleProtocolClientInterface
*client_iface
)
1016 client_iface
->get_actions
= irc_get_actions
;
1017 client_iface
->normalize
= purple_normalize_nocase
;
1018 client_iface
->get_max_message_size
= irc_get_max_message_size
;
1022 irc_protocol_server_iface_init(PurpleProtocolServerInterface
*server_iface
)
1024 server_iface
->set_status
= irc_set_status
;
1025 server_iface
->get_info
= irc_get_info
;
1026 server_iface
->add_buddy
= irc_add_buddy
;
1027 server_iface
->remove_buddy
= irc_remove_buddy
;
1028 server_iface
->keepalive
= irc_keepalive
;
1029 server_iface
->send_raw
= irc_send_raw
;
1033 irc_protocol_im_iface_init(PurpleProtocolIMInterface
*im_iface
)
1035 im_iface
->send
= irc_im_send
;
1039 irc_protocol_chat_iface_init(PurpleProtocolChatInterface
*chat_iface
)
1041 chat_iface
->info
= irc_chat_join_info
;
1042 chat_iface
->info_defaults
= irc_chat_info_defaults
;
1043 chat_iface
->join
= irc_chat_join
;
1044 chat_iface
->get_name
= irc_get_chat_name
;
1045 chat_iface
->invite
= irc_chat_invite
;
1046 chat_iface
->leave
= irc_chat_leave
;
1047 chat_iface
->send
= irc_chat_send
;
1048 chat_iface
->set_topic
= irc_chat_set_topic
;
1052 irc_protocol_roomlist_iface_init(PurpleProtocolRoomlistInterface
*roomlist_iface
)
1054 roomlist_iface
->get_list
= irc_roomlist_get_list
;
1055 roomlist_iface
->cancel
= irc_roomlist_cancel
;
1059 irc_protocol_xfer_iface_init(PurpleProtocolXferInterface
*xfer_iface
)
1061 xfer_iface
->send_file
= irc_dccsend_send_file
;
1062 xfer_iface
->new_xfer
= irc_dccsend_new_xfer
;
1065 PURPLE_DEFINE_TYPE_EXTENDED(
1066 IRCProtocol
, irc_protocol
, PURPLE_TYPE_PROTOCOL
, 0,
1068 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT
,
1069 irc_protocol_client_iface_init
)
1071 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER
,
1072 irc_protocol_server_iface_init
)
1074 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM
,
1075 irc_protocol_im_iface_init
)
1077 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT
,
1078 irc_protocol_chat_iface_init
)
1080 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST
,
1081 irc_protocol_roomlist_iface_init
)
1083 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER
,
1084 irc_protocol_xfer_iface_init
)
1087 static PurplePluginInfo
*
1088 plugin_query(GError
**error
)
1090 return purple_plugin_info_new(
1092 "name", "IRC Protocol",
1093 "version", DISPLAY_VERSION
,
1094 "category", N_("Protocol"),
1095 "summary", N_("IRC Protocol Plugin"),
1096 "description", N_("The IRC Protocol Plugin that Sucks Less"),
1097 "website", PURPLE_WEBSITE
,
1098 "abi-version", PURPLE_ABI_VERSION
,
1099 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL
|
1100 PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD
,
1106 plugin_load(PurplePlugin
*plugin
, GError
**error
)
1108 irc_protocol_register_type(plugin
);
1110 irc_xfer_register(G_TYPE_MODULE(plugin
));
1112 _irc_protocol
= purple_protocols_add(IRC_TYPE_PROTOCOL
, error
);
1116 purple_prefs_remove("/plugins/prpl/irc/quitmsg");
1117 purple_prefs_remove("/plugins/prpl/irc");
1119 irc_register_commands();
1121 purple_signal_register(_irc_protocol
, "irc-sending-text",
1122 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
1123 PURPLE_TYPE_CONNECTION
,
1124 G_TYPE_POINTER
); /* pointer to a string */
1125 purple_signal_register(_irc_protocol
, "irc-receiving-text",
1126 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
1127 PURPLE_TYPE_CONNECTION
,
1128 G_TYPE_POINTER
); /* pointer to a string */
1130 purple_signal_connect(purple_get_core(), "uri-handler", plugin
,
1131 PURPLE_CALLBACK(irc_uri_handler
), NULL
);
1137 plugin_unload(PurplePlugin
*plugin
, GError
**error
)
1139 irc_unregister_commands();
1141 purple_signal_disconnect(purple_get_core(), "uri-handler", plugin
,
1142 PURPLE_CALLBACK(irc_uri_handler
));
1144 if (!purple_protocols_remove(_irc_protocol
, error
))
1150 PURPLE_PLUGIN_INIT(irc
, plugin_query
, plugin_load
, plugin_unload
);