mark PurpleImageClass as private
[pidgin-git.git] / libpurple / protocols / irc / irc.c
blobeb57f6f22ea4c0d0f2dcfd3110122c1286398460
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(PurpleAccount *account, const gchar *match_server)
68 const gchar *protocol_id;
69 const gchar *username;
70 gchar *server;
72 protocol_id = purple_account_get_protocol_id(account);
74 if (!purple_strequal(protocol_id, "prpl-irc") ||
75 !purple_account_is_connected(account)) {
76 return -1;
79 if (match_server == NULL || match_server[0] == '\0') {
80 /* No server specified, match any IRC account */
81 return 0;
84 username = purple_account_get_username(account);
85 server = strchr(username, '@');
87 /* +1 to skip '@' */
88 if (server == NULL || !purple_strequal(match_server, server + 1)) {
89 return -1;
92 return 0;
95 static gboolean
96 irc_uri_handler(const gchar *scheme, const gchar *uri, GHashTable *params)
98 gchar *target;
99 gchar *server;
100 GList *accounts;
101 GList *account_node;
102 gchar **target_tokens;
103 PurpleAccount *account;
104 gchar **modifier;
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 */
111 return FALSE;
114 if (g_str_has_prefix(uri, "//")) {
115 /* Skip initial '//' if it exists */
116 uri += 2;
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);
126 return FALSE;
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",
139 server);
140 g_free(server);
141 return FALSE;
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")) {
153 isnick = TRUE;
154 break;
158 g_strfreev(target_tokens);
160 if (isnick) {
161 PurpleIMConversation *im;
163 /* 'server' isn't needed here. Free it immediately. */
164 g_free(server);
166 /* +1 to skip '#' target prefix */
167 im = purple_im_conversation_new(account, target + 1);
168 g_free(target);
170 purple_conversation_present(PURPLE_CONVERSATION(im));
172 if (params != NULL) {
173 const gchar *msg = g_hash_table_lookup(params, "msg");
175 if (msg != NULL) {
176 purple_conversation_send_confirm(
177 PURPLE_CONVERSATION(im), msg);
181 return TRUE;
182 } else {
183 GHashTable *components;
185 components = g_hash_table_new_full(g_str_hash, g_str_equal,
186 NULL, g_free);
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");
195 if (key != NULL) {
196 g_hash_table_insert(components, "password",
197 g_strdup(key));
201 purple_serv_join_chat(purple_account_get_connection(account),
202 components);
203 g_hash_table_destroy(components);
204 return TRUE;
207 return FALSE;
210 static void irc_view_motd(PurpleProtocolAction *action)
212 PurpleConnection *gc = action->connection;
213 struct irc_conn *irc;
214 char *title, *body;
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");
218 return;
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));
226 return;
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);
231 g_free(title);
232 g_free(body);
235 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
237 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
238 if (len == -1) {
239 len = strlen(buf);
241 irc_send_len(irc, buf, len);
242 return len;
245 static void
246 irc_push_bytes_cb(GObject *source, GAsyncResult *res, gpointer data)
248 PurpleQueuedOutputStream *stream = PURPLE_QUEUED_OUTPUT_STREAM(source);
249 PurpleConnection *gc = data;
250 gboolean result;
251 GError *error = NULL;
253 result = purple_queued_output_stream_push_bytes_finish(stream,
254 res, &error);
256 if (!result) {
257 purple_queued_output_stream_clear_queue(stream);
259 g_prefix_error(&error, _("Lost connection with server: "));
260 purple_connection_take_error(gc, error);
261 return;
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);
273 int len;
274 GBytes *data;
276 purple_signal_emit(_irc_protocol, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
278 if (tosend == NULL)
279 return 0;
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);
285 g_free(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));
293 g_bytes_unref(data);
295 return len;
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) {
302 return TRUE;
305 g_hash_table_foreach(irc->buddies, (GHFunc)irc_ison_buddy_init,
306 (gpointer *)&irc->buddies_outstanding);
308 irc_buddy_query(irc);
310 return TRUE;
313 void irc_buddy_query(struct irc_conn *irc)
315 GList *lp;
316 GString *string;
317 struct irc_buddy *ib;
318 char *buf;
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)
325 break;
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);
331 if (string->len) {
332 buf = irc_format(irc, "vn", "ISON", string->str);
333 irc_send(irc, buf);
334 g_free(buf);
335 irc->ison_outstanding = TRUE;
336 } else
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)
350 char *buf;
352 if (irc->buddies_outstanding != NULL) {
353 irc->buddies_outstanding = g_list_append(irc->buddies_outstanding, ib);
354 return;
357 ib->new_online_status = FALSE;
358 buf = irc_format(irc, "vn", "ISON", ib->name);
359 irc_send(irc, buf);
360 g_free(buf);
364 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b)
366 return "irc";
369 static GList *irc_status_types(PurpleAccount *account)
371 PurpleStatusType *type;
372 GList *types = NULL;
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),
380 NULL);
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);
386 return types;
389 static GList *irc_get_actions(PurpleConnection *gc)
391 GList *list = NULL;
392 PurpleProtocolAction *act = NULL;
394 act = purple_protocol_action_new(_("View MOTD"), irc_view_motd);
395 list = g_list_append(list, act);
397 return list;
400 static GList *irc_chat_join_info(PurpleConnection *gc)
402 GList *m = NULL;
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";
414 pce->secret = TRUE;
415 m = g_list_append(m, pce);
417 return m;
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));
429 return defaults;
432 static void irc_login(PurpleAccount *account)
434 PurpleConnection *gc;
435 struct irc_conn *irc;
436 char **userparts;
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")));
450 return;
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);
476 return;
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 :
487 IRC_DEFAULT_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;
494 char *server;
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);
500 #endif
502 if (pass && *pass) {
503 #ifdef HAVE_CYRUS_SASL
504 if (use_sasl)
505 buf = irc_format(irc, "vv:", "CAP", "REQ", "sasl");
506 else /* intended to fall through */
507 #endif
508 buf = irc_format(irc, "v:", "PASS", pass);
509 if (irc_send(irc, buf) < 0) {
510 g_free(buf);
511 return FALSE;
513 g_free(buf);
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) {
526 *buf = '_';
530 if (*irc->server == ':') {
531 /* Same as hostname, above. */
532 server = g_strdup_printf("0%s", irc->server);
533 } else {
534 server = g_strdup(irc->server);
537 buf = irc_format(irc, "vvvv:", "USER", tmp ? tmp : identname, "*", server,
538 *realname == '\0' ? IRC_DEFAULT_ALIAS : realname);
539 g_free(tmp);
540 g_free(server);
541 if (irc_send(irc, buf) < 0) {
542 g_free(buf);
543 return FALSE;
545 g_free(buf);
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) {
551 g_free(buf);
552 return FALSE;
554 g_free(buf);
556 irc->recv_time = time(NULL);
558 return TRUE;
561 static void
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),
570 res, &error);
572 if (conn == NULL) {
573 g_prefix_error(&error, _("Unable to connect: "));
574 purple_connection_take_error(gc, error);
575 return;
578 irc = purple_connection_get_protocol_data(gc);
579 irc->conn = conn;
580 irc->output = purple_queued_output_stream_new(
581 g_io_stream_get_output_stream(G_IO_STREAM(irc->conn)));
583 if (do_login(gc)) {
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);
597 if (irc == NULL)
598 return;
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);
618 if (irc->timer)
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);
623 if (irc->motd)
624 g_string_free(irc->motd, TRUE);
625 g_free(irc->server);
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);
636 if(irc->sasl_mechs)
637 g_string_free(irc->sasl_mechs, TRUE);
638 #endif
641 g_free(irc);
644 static int irc_im_send(PurpleConnection *gc, PurpleMessage *msg)
646 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
647 char *plain;
648 const char *args[2];
650 args[0] = irc_nick_skip_mode(irc, purple_message_get_recipient(msg));
652 purple_markup_html_to_xhtml(purple_message_get_contents(msg),
653 NULL, &plain);
654 args[1] = plain;
656 irc_cmd_privmsg(irc, "msg", NULL, args);
657 g_free(plain);
658 return 1;
661 static void irc_get_info(PurpleConnection *gc, const char *who)
663 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
664 const char *args[2];
665 args[0] = who;
666 args[1] = NULL;
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;
674 const char *args[1];
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))
681 return;
683 args[0] = NULL;
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'))
688 args[0] = _("Away");
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);
702 if (ib != NULL) {
703 ib->ref++;
704 purple_protocol_got_user_status(irc->account, bname,
705 ib->online ? "available" : "offline", NULL);
706 } else {
707 ib = g_new0(struct irc_buddy, 1);
708 ib->name = g_strdup(bname);
709 ib->ref = 1;
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 */
716 if (irc->timer)
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));
731 static void
732 irc_read_input_cb(GObject *source, GAsyncResult *res, gpointer data)
734 PurpleConnection *gc = data;
735 struct irc_conn *irc;
736 gchar *line;
737 gsize len;
738 gsize start = 0;
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);
747 return;
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")));
753 return;
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')
767 ++start;
769 if (len - start > 0)
770 irc_parse_msg(irc, line + start);
772 g_free(line);
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);
782 const char *args[2];
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));
797 const char *args[2];
799 if (!convo) {
800 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n");
801 return;
803 args[0] = name;
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));
813 const char *args[2];
815 if (!convo)
816 return;
818 args[0] = purple_conversation_get_name(convo);
819 args[1] = NULL;
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));
828 const char *args[2];
829 char *tmp;
831 if (!convo) {
832 purple_debug(PURPLE_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
833 return -EINVAL;
835 purple_markup_html_to_xhtml(purple_message_get_contents(msg), NULL, &tmp);
836 args[0] = purple_conversation_get_name(convo);
837 args[1] = tmp;
839 irc_cmd_privmsg(irc, "msg", NULL, args);
841 /* TODO: use msg */
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));
845 g_free(tmp);
846 return 0;
849 static guint irc_nick_hash(const char *nick)
851 char *lc;
852 guint bucket;
854 lc = g_utf8_strdown(nick, -1);
855 bucket = g_str_hash(lc);
856 g_free(lc);
858 return bucket;
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)
868 g_free(ib->name);
869 g_free(ib);
872 static void irc_chat_set_topic(PurpleConnection *gc, int id, const char *topic)
874 char *buf;
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)));
882 if (name == NULL)
883 return;
885 buf = irc_format(irc, "vt:", "TOPIC", name, topic);
886 irc_send(irc, buf);
887 g_free(buf);
890 static PurpleRoomlist *irc_roomlist_get_list(PurpleConnection *gc)
892 struct irc_conn *irc;
893 GList *fields = NULL;
894 PurpleRoomlistField *f;
895 char *buf;
897 irc = purple_connection_get_protocol_data(gc);
899 if (irc->roomlist)
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");
916 irc_send(irc, buf);
917 g_free(buf);
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;
928 if (gc == NULL)
929 return;
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);
948 static gssize
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. */
953 return 417;
956 static void
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);
1001 #endif
1004 static void
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;
1013 static void
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;
1021 static void
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;
1032 static void
1033 irc_protocol_im_iface_init(PurpleProtocolIMInterface *im_iface)
1035 im_iface->send = irc_im_send;
1038 static void
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;
1051 static void
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;
1058 static void
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(
1091 "id", "prpl-irc",
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,
1101 NULL
1105 static gboolean
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);
1113 if (!_irc_protocol)
1114 return FALSE;
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);
1133 return TRUE;
1136 static gboolean
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))
1145 return FALSE;
1147 return TRUE;
1150 PURPLE_PLUGIN_INIT(irc, plugin_query, plugin_load, plugin_unload);