rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / irc / irc.c
blobf157d66cfe47bb8e82a292cc7a648f78e544babb
1 /**
2 * purple
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
24 #include "internal.h"
25 #include <purple.h>
27 #include "irc.h"
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;
51 static gint
52 irc_uri_handler_match_server(PurpleAccount *account, const gchar *match_server)
54 const gchar *protocol_id;
55 const gchar *username;
56 gchar *server;
58 protocol_id = purple_account_get_protocol_id(account);
60 if (!purple_strequal(protocol_id, "prpl-irc") ||
61 !purple_account_is_connected(account)) {
62 return -1;
65 if (match_server == NULL || match_server[0] == '\0') {
66 /* No server specified, match any IRC account */
67 return 0;
70 username = purple_account_get_username(account);
71 server = strchr(username, '@');
73 /* +1 to skip '@' */
74 if (server == NULL || !purple_strequal(match_server, server + 1)) {
75 return -1;
78 return 0;
81 static gboolean
82 irc_uri_handler(const gchar *scheme, const gchar *uri, GHashTable *params)
84 gchar *target;
85 gchar *server;
86 GList *accounts;
87 GList *account_node;
88 gchar **target_tokens;
89 PurpleAccount *account;
90 gchar **modifier;
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 */
97 return FALSE;
100 if (g_str_has_prefix(uri, "//")) {
101 /* Skip initial '//' if it exists */
102 uri += 2;
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);
112 return FALSE;
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",
125 server);
126 g_free(server);
127 return FALSE;
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")) {
139 isnick = TRUE;
140 break;
144 g_strfreev(target_tokens);
146 if (isnick) {
147 PurpleIMConversation *im;
149 /* 'server' isn't needed here. Free it immediately. */
150 g_free(server);
152 /* +1 to skip '#' target prefix */
153 im = purple_im_conversation_new(account, target + 1);
154 g_free(target);
156 purple_conversation_present(PURPLE_CONVERSATION(im));
158 if (params != NULL) {
159 const gchar *msg = g_hash_table_lookup(params, "msg");
161 if (msg != NULL) {
162 purple_conversation_send_confirm(
163 PURPLE_CONVERSATION(im), msg);
167 return TRUE;
168 } else {
169 GHashTable *components;
171 components = g_hash_table_new_full(g_str_hash, g_str_equal,
172 NULL, g_free);
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");
181 if (key != NULL) {
182 g_hash_table_insert(components, "password",
183 g_strdup(key));
187 purple_serv_join_chat(purple_account_get_connection(account),
188 components);
189 g_hash_table_destroy(components);
190 return TRUE;
193 return FALSE;
196 static void irc_view_motd(PurpleProtocolAction *action)
198 PurpleConnection *gc = action->connection;
199 struct irc_conn *irc;
200 char *title, *body;
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");
204 return;
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));
212 return;
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);
217 g_free(title);
218 g_free(body);
221 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
223 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
224 if (len == -1) {
225 len = strlen(buf);
227 irc_send_len(irc, buf, len);
228 return len;
231 static void
232 irc_push_bytes_cb(GObject *source, GAsyncResult *res, gpointer data)
234 PurpleQueuedOutputStream *stream = PURPLE_QUEUED_OUTPUT_STREAM(source);
235 PurpleConnection *gc = data;
236 gboolean result;
237 GError *error = NULL;
239 result = purple_queued_output_stream_push_bytes_finish(stream,
240 res, &error);
242 if (!result) {
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);
247 return;
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);
259 int len;
260 GBytes *data;
262 purple_signal_emit(_irc_protocol, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
264 if (tosend == NULL)
265 return 0;
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);
271 g_free(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));
279 g_bytes_unref(data);
281 return len;
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) {
288 return TRUE;
291 g_hash_table_foreach(irc->buddies, (GHFunc)irc_ison_buddy_init,
292 (gpointer *)&irc->buddies_outstanding);
294 irc_buddy_query(irc);
296 return TRUE;
299 void irc_buddy_query(struct irc_conn *irc)
301 GList *lp;
302 GString *string;
303 struct irc_buddy *ib;
304 char *buf;
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)
311 break;
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);
317 if (string->len) {
318 buf = irc_format(irc, "vn", "ISON", string->str);
319 irc_send(irc, buf);
320 g_free(buf);
321 irc->ison_outstanding = TRUE;
322 } else
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)
336 char *buf;
338 if (irc->buddies_outstanding != NULL) {
339 irc->buddies_outstanding = g_list_append(irc->buddies_outstanding, ib);
340 return;
343 ib->new_online_status = FALSE;
344 buf = irc_format(irc, "vn", "ISON", ib->name);
345 irc_send(irc, buf);
346 g_free(buf);
350 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b)
352 return "irc";
355 static GList *irc_status_types(PurpleAccount *account)
357 PurpleStatusType *type;
358 GList *types = NULL;
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),
366 NULL);
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);
372 return types;
375 static GList *irc_get_actions(PurpleConnection *gc)
377 GList *list = NULL;
378 PurpleProtocolAction *act = NULL;
380 act = purple_protocol_action_new(_("View MOTD"), irc_view_motd);
381 list = g_list_append(list, act);
383 return list;
386 static GList *irc_chat_join_info(PurpleConnection *gc)
388 GList *m = NULL;
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";
400 pce->secret = TRUE;
401 m = g_list_append(m, pce);
403 return m;
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));
415 return defaults;
418 static void irc_login(PurpleAccount *account)
420 PurpleConnection *gc;
421 struct irc_conn *irc;
422 char **userparts;
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")));
436 return;
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);
462 return;
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 :
473 IRC_DEFAULT_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;
480 char *server;
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);
486 #endif
488 if (pass && *pass) {
489 #ifdef HAVE_CYRUS_SASL
490 if (use_sasl)
491 buf = irc_format(irc, "vv:", "CAP", "REQ", "sasl");
492 else /* intended to fall through */
493 #endif
494 buf = irc_format(irc, "v:", "PASS", pass);
495 if (irc_send(irc, buf) < 0) {
496 g_free(buf);
497 return FALSE;
499 g_free(buf);
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) {
512 *buf = '_';
516 if (*irc->server == ':') {
517 /* Same as hostname, above. */
518 server = g_strdup_printf("0%s", irc->server);
519 } else {
520 server = g_strdup(irc->server);
523 buf = irc_format(irc, "vvvv:", "USER", tmp ? tmp : identname, "*", server,
524 *realname == '\0' ? IRC_DEFAULT_ALIAS : realname);
525 g_free(tmp);
526 g_free(server);
527 if (irc_send(irc, buf) < 0) {
528 g_free(buf);
529 return FALSE;
531 g_free(buf);
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) {
537 g_free(buf);
538 return FALSE;
540 g_free(buf);
542 irc->recv_time = time(NULL);
544 return TRUE;
547 static void
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),
556 res, &error);
558 if (conn == NULL) {
559 g_prefix_error(&error, "%s", _("Unable to connect: "));
560 purple_connection_take_error(gc, error);
561 return;
564 irc = purple_connection_get_protocol_data(gc);
565 irc->conn = conn;
566 irc->output = purple_queued_output_stream_new(
567 g_io_stream_get_output_stream(G_IO_STREAM(irc->conn)));
569 if (do_login(gc)) {
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);
583 if (irc == NULL)
584 return;
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);
604 if (irc->timer)
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);
609 if (irc->motd)
610 g_string_free(irc->motd, TRUE);
611 g_free(irc->server);
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);
622 if(irc->sasl_mechs)
623 g_string_free(irc->sasl_mechs, TRUE);
624 #endif
627 g_free(irc);
630 static int irc_im_send(PurpleConnection *gc, PurpleMessage *msg)
632 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
633 char *plain;
634 const char *args[2];
636 args[0] = irc_nick_skip_mode(irc, purple_message_get_recipient(msg));
638 purple_markup_html_to_xhtml(purple_message_get_contents(msg),
639 NULL, &plain);
640 args[1] = plain;
642 irc_cmd_privmsg(irc, "msg", NULL, args);
643 g_free(plain);
644 return 1;
647 static void irc_get_info(PurpleConnection *gc, const char *who)
649 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
650 const char *args[2];
651 args[0] = who;
652 args[1] = NULL;
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;
660 const char *args[1];
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))
667 return;
669 args[0] = NULL;
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'))
674 args[0] = _("Away");
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);
688 if (ib != NULL) {
689 ib->ref++;
690 purple_protocol_got_user_status(irc->account, bname,
691 ib->online ? "available" : "offline", NULL);
692 } else {
693 ib = g_new0(struct irc_buddy, 1);
694 ib->name = g_strdup(bname);
695 ib->ref = 1;
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 */
702 if (irc->timer)
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));
717 static void
718 irc_read_input_cb(GObject *source, GAsyncResult *res, gpointer data)
720 PurpleConnection *gc = data;
721 struct irc_conn *irc;
722 gchar *line;
723 gsize len;
724 gsize start = 0;
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);
733 return;
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")));
739 return;
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')
753 ++start;
755 if (start < len) {
756 irc_parse_msg(irc, line + start);
759 g_free(line);
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);
769 const char *args[2];
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));
784 const char *args[2];
786 if (!convo) {
787 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n");
788 return;
790 args[0] = name;
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));
800 const char *args[2];
802 if (!convo)
803 return;
805 args[0] = purple_conversation_get_name(convo);
806 args[1] = NULL;
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));
815 const char *args[2];
816 char *tmp;
818 if (!convo) {
819 purple_debug(PURPLE_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
820 return -EINVAL;
822 purple_markup_html_to_xhtml(purple_message_get_contents(msg), NULL, &tmp);
823 args[0] = purple_conversation_get_name(convo);
824 args[1] = tmp;
826 irc_cmd_privmsg(irc, "msg", NULL, args);
828 /* TODO: use msg */
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));
832 g_free(tmp);
833 return 0;
836 static guint irc_nick_hash(const char *nick)
838 char *lc;
839 guint bucket;
841 lc = g_utf8_strdown(nick, -1);
842 bucket = g_str_hash(lc);
843 g_free(lc);
845 return bucket;
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)
855 g_free(ib->name);
856 g_free(ib);
859 static void irc_chat_set_topic(PurpleConnection *gc, int id, const char *topic)
861 char *buf;
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)));
869 if (name == NULL)
870 return;
872 buf = irc_format(irc, "vt:", "TOPIC", name, topic);
873 irc_send(irc, buf);
874 g_free(buf);
877 static PurpleRoomlist *irc_roomlist_get_list(PurpleConnection *gc)
879 struct irc_conn *irc;
880 GList *fields = NULL;
881 PurpleRoomlistField *f;
882 char *buf;
884 irc = purple_connection_get_protocol_data(gc);
886 if (irc->roomlist)
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");
903 irc_send(irc, buf);
904 g_free(buf);
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;
915 if (gc == NULL)
916 return;
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);
935 static gssize
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. */
940 return 417;
943 static void
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);
989 #endif
992 static void
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;
1003 static void
1004 irc_protocol_class_finalize(G_GNUC_UNUSED IRCProtocolClass *klass)
1008 static void
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;
1016 static void
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;
1027 static void
1028 irc_protocol_im_iface_init(PurpleProtocolIMInterface *im_iface)
1030 im_iface->send = irc_im_send;
1033 static void
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;
1046 static void
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;
1053 static void
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(
1085 "id", "prpl-irc",
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,
1095 NULL
1099 static gboolean
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);
1107 if (!_irc_protocol)
1108 return FALSE;
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);
1127 return TRUE;
1130 static gboolean
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))
1139 return FALSE;
1141 return TRUE;
1144 PURPLE_PLUGIN_INIT(irc, plugin_query, plugin_load, plugin_unload);