Standardize all protocol header guard macros.
[pidgin-git.git] / libpurple / protocols / irc / irc.c
blob232f85794583d0456130d9d5ad0d5df93436dd78
1 /**
2 * @file irc.c
4 * purple
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
26 #include "internal.h"
28 #include "accountopt.h"
29 #include "action.h"
30 #include "buddylist.h"
31 #include "conversation.h"
32 #include "core.h"
33 #include "debug.h"
34 #include "notify.h"
35 #include "protocol.h"
36 #include "plugins.h"
37 #include "purple-gio.h"
38 #include "util.h"
39 #include "version.h"
41 #include "irc.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;
65 static gint
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;
72 gchar *server;
74 protocol_id = purple_account_get_protocol_id(account);
76 if (!purple_strequal(protocol_id, "prpl-irc") ||
77 !purple_account_is_connected(account)) {
78 return -1;
81 if (match_server == NULL || match_server[0] == '\0') {
82 /* No server specified, match any IRC account */
83 return 0;
86 username = purple_account_get_username(account);
87 server = strchr(username, '@');
89 /* +1 to skip '@' */
90 if (server == NULL || !purple_strequal(match_server, server + 1)) {
91 return -1;
94 return 0;
97 static gboolean
98 irc_uri_handler(const gchar *scheme, const gchar *uri, GHashTable *params)
100 gchar *target;
101 gchar *server;
102 GList *accounts;
103 GList *account_node;
104 gchar **target_tokens;
105 PurpleAccount *account;
106 gchar **modifier;
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 */
113 return FALSE;
116 if (g_str_has_prefix(uri, "//")) {
117 /* Skip initial '//' if it exists */
118 uri += 2;
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);
128 return FALSE;
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",
141 server);
142 g_free(server);
143 return FALSE;
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")) {
155 isnick = TRUE;
156 break;
160 g_strfreev(target_tokens);
162 if (isnick) {
163 PurpleIMConversation *im;
165 /* 'server' isn't needed here. Free it immediately. */
166 g_free(server);
168 /* +1 to skip '#' target prefix */
169 im = purple_im_conversation_new(account, target + 1);
170 g_free(target);
172 purple_conversation_present(PURPLE_CONVERSATION(im));
174 if (params != NULL) {
175 const gchar *msg = g_hash_table_lookup(params, "msg");
177 if (msg != NULL) {
178 purple_conversation_send_confirm(
179 PURPLE_CONVERSATION(im), msg);
183 return TRUE;
184 } else {
185 GHashTable *components;
187 components = g_hash_table_new_full(g_str_hash, g_str_equal,
188 NULL, g_free);
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");
197 if (key != NULL) {
198 g_hash_table_insert(components, "password",
199 g_strdup(key));
203 purple_serv_join_chat(purple_account_get_connection(account),
204 components);
205 g_hash_table_destroy(components);
206 return TRUE;
209 return FALSE;
212 static void irc_view_motd(PurpleProtocolAction *action)
214 PurpleConnection *gc = action->connection;
215 struct irc_conn *irc;
216 char *title, *body;
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");
220 return;
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));
228 return;
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);
233 g_free(title);
234 g_free(body);
237 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
239 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
240 if (len == -1) {
241 len = strlen(buf);
243 irc_send_len(irc, buf, len);
244 return len;
247 static void
248 irc_push_bytes_cb(GObject *source, GAsyncResult *res, gpointer data)
250 PurpleQueuedOutputStream *stream = PURPLE_QUEUED_OUTPUT_STREAM(source);
251 PurpleConnection *gc = data;
252 gboolean result;
253 GError *error = NULL;
255 result = purple_queued_output_stream_push_bytes_finish(stream,
256 res, &error);
258 if (!result) {
259 purple_queued_output_stream_clear_queue(stream);
261 g_prefix_error(&error, _("Lost connection with server: "));
262 purple_connection_take_error(gc, error);
263 return;
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);
275 int len;
276 GBytes *data;
278 purple_signal_emit(_irc_protocol, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
280 if (tosend == NULL)
281 return 0;
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);
287 g_free(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));
295 g_bytes_unref(data);
297 return len;
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) {
304 return TRUE;
307 g_hash_table_foreach(irc->buddies, (GHFunc)irc_ison_buddy_init,
308 (gpointer *)&irc->buddies_outstanding);
310 irc_buddy_query(irc);
312 return TRUE;
315 void irc_buddy_query(struct irc_conn *irc)
317 GList *lp;
318 GString *string;
319 struct irc_buddy *ib;
320 char *buf;
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)
327 break;
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);
333 if (string->len) {
334 buf = irc_format(irc, "vn", "ISON", string->str);
335 irc_send(irc, buf);
336 g_free(buf);
337 irc->ison_outstanding = TRUE;
338 } else
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)
352 char *buf;
354 if (irc->buddies_outstanding != NULL) {
355 irc->buddies_outstanding = g_list_append(irc->buddies_outstanding, ib);
356 return;
359 ib->new_online_status = FALSE;
360 buf = irc_format(irc, "vn", "ISON", ib->name);
361 irc_send(irc, buf);
362 g_free(buf);
366 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b)
368 return "irc";
371 static GList *irc_status_types(PurpleAccount *account)
373 PurpleStatusType *type;
374 GList *types = NULL;
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),
382 NULL);
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);
388 return types;
391 static GList *irc_get_actions(PurpleConnection *gc)
393 GList *list = NULL;
394 PurpleProtocolAction *act = NULL;
396 act = purple_protocol_action_new(_("View MOTD"), irc_view_motd);
397 list = g_list_append(list, act);
399 return list;
402 static GList *irc_chat_join_info(PurpleConnection *gc)
404 GList *m = NULL;
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";
416 pce->secret = TRUE;
417 m = g_list_append(m, pce);
419 return m;
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));
431 return defaults;
434 static void irc_login(PurpleAccount *account)
436 PurpleConnection *gc;
437 struct irc_conn *irc;
438 char **userparts;
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")));
452 return;
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);
478 return;
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 :
489 IRC_DEFAULT_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;
496 char *server;
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);
502 #endif
504 if (pass && *pass) {
505 #ifdef HAVE_CYRUS_SASL
506 if (use_sasl)
507 buf = irc_format(irc, "vv:", "CAP", "REQ", "sasl");
508 else /* intended to fall through */
509 #endif
510 buf = irc_format(irc, "v:", "PASS", pass);
511 if (irc_send(irc, buf) < 0) {
512 g_free(buf);
513 return FALSE;
515 g_free(buf);
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) {
528 *buf = '_';
532 if (*irc->server == ':') {
533 /* Same as hostname, above. */
534 server = g_strdup_printf("0%s", irc->server);
535 } else {
536 server = g_strdup(irc->server);
539 buf = irc_format(irc, "vvvv:", "USER", tmp ? tmp : identname, "*", server,
540 *realname == '\0' ? IRC_DEFAULT_ALIAS : realname);
541 g_free(tmp);
542 g_free(server);
543 if (irc_send(irc, buf) < 0) {
544 g_free(buf);
545 return FALSE;
547 g_free(buf);
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) {
553 g_free(buf);
554 return FALSE;
556 g_free(buf);
558 irc->recv_time = time(NULL);
560 return TRUE;
563 static void
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),
572 res, &error);
574 if (conn == NULL) {
575 g_prefix_error(&error, _("Unable to connect: "));
576 purple_connection_take_error(gc, error);
577 return;
580 irc = purple_connection_get_protocol_data(gc);
581 irc->conn = conn;
582 irc->output = purple_queued_output_stream_new(
583 g_io_stream_get_output_stream(G_IO_STREAM(irc->conn)));
585 if (do_login(gc)) {
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);
599 if (irc == NULL)
600 return;
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);
620 if (irc->timer)
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);
625 if (irc->motd)
626 g_string_free(irc->motd, TRUE);
627 g_free(irc->server);
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);
638 if(irc->sasl_mechs)
639 g_string_free(irc->sasl_mechs, TRUE);
640 #endif
643 g_free(irc);
646 static int irc_im_send(PurpleConnection *gc, PurpleMessage *msg)
648 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
649 char *plain;
650 const char *args[2];
652 args[0] = irc_nick_skip_mode(irc, purple_message_get_recipient(msg));
654 purple_markup_html_to_xhtml(purple_message_get_contents(msg),
655 NULL, &plain);
656 args[1] = plain;
658 irc_cmd_privmsg(irc, "msg", NULL, args);
659 g_free(plain);
660 return 1;
663 static void irc_get_info(PurpleConnection *gc, const char *who)
665 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
666 const char *args[2];
667 args[0] = who;
668 args[1] = NULL;
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;
676 const char *args[1];
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))
683 return;
685 args[0] = NULL;
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'))
690 args[0] = _("Away");
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);
704 if (ib != NULL) {
705 ib->ref++;
706 purple_protocol_got_user_status(irc->account, bname,
707 ib->online ? "available" : "offline", NULL);
708 } else {
709 ib = g_new0(struct irc_buddy, 1);
710 ib->name = g_strdup(bname);
711 ib->ref = 1;
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 */
718 if (irc->timer)
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));
733 static void
734 irc_read_input_cb(GObject *source, GAsyncResult *res, gpointer data)
736 PurpleConnection *gc = data;
737 struct irc_conn *irc;
738 gchar *line;
739 gsize len;
740 gsize start = 0;
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);
749 return;
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")));
755 return;
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')
769 ++start;
771 if (len - start > 0)
772 irc_parse_msg(irc, line + start);
774 g_free(line);
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);
784 const char *args[2];
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));
799 const char *args[2];
801 if (!convo) {
802 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n");
803 return;
805 args[0] = name;
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));
815 const char *args[2];
817 if (!convo)
818 return;
820 args[0] = purple_conversation_get_name(convo);
821 args[1] = NULL;
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));
830 const char *args[2];
831 char *tmp;
833 if (!convo) {
834 purple_debug(PURPLE_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
835 return -EINVAL;
837 purple_markup_html_to_xhtml(purple_message_get_contents(msg), NULL, &tmp);
838 args[0] = purple_conversation_get_name(convo);
839 args[1] = tmp;
841 irc_cmd_privmsg(irc, "msg", NULL, args);
843 /* TODO: use msg */
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));
847 g_free(tmp);
848 return 0;
851 static guint irc_nick_hash(const char *nick)
853 char *lc;
854 guint bucket;
856 lc = g_utf8_strdown(nick, -1);
857 bucket = g_str_hash(lc);
858 g_free(lc);
860 return bucket;
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)
870 g_free(ib->name);
871 g_free(ib);
874 static void irc_chat_set_topic(PurpleConnection *gc, int id, const char *topic)
876 char *buf;
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)));
884 if (name == NULL)
885 return;
887 buf = irc_format(irc, "vt:", "TOPIC", name, topic);
888 irc_send(irc, buf);
889 g_free(buf);
892 static PurpleRoomlist *irc_roomlist_get_list(PurpleConnection *gc)
894 struct irc_conn *irc;
895 GList *fields = NULL;
896 PurpleRoomlistField *f;
897 char *buf;
899 irc = purple_connection_get_protocol_data(gc);
901 if (irc->roomlist)
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");
918 irc_send(irc, buf);
919 g_free(buf);
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;
930 if (gc == NULL)
931 return;
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);
950 static gssize
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. */
955 return 417;
958 static void
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);
1003 #endif
1006 static void
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;
1015 static void
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;
1023 static void
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;
1034 static void
1035 irc_protocol_im_iface_init(PurpleProtocolIMInterface *im_iface)
1037 im_iface->send = irc_im_send;
1040 static void
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;
1053 static void
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;
1060 static void
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(
1093 "id", "prpl-irc",
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,
1103 NULL
1107 static gboolean
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);
1115 if (!_irc_protocol)
1116 return FALSE;
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);
1135 return TRUE;
1138 static gboolean
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))
1147 return FALSE;
1149 return TRUE;
1152 PURPLE_PLUGIN_INIT(irc, plugin_query, plugin_load, plugin_unload);