Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / irc / irc.c
blobf71d45fce098f7b4f6a46dd523f731d9a753b427
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 "buddylist.h"
30 #include "conversation.h"
31 #include "debug.h"
32 #include "notify.h"
33 #include "protocol.h"
34 #include "plugins.h"
35 #include "purple-gio.h"
36 #include "util.h"
37 #include "version.h"
39 #include "irc.h"
41 #define PING_TIMEOUT 60
43 static void irc_ison_buddy_init(char *name, struct irc_buddy *ib, GList **list);
45 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b);
46 static GList *irc_status_types(PurpleAccount *account);
47 static GList *irc_get_actions(PurpleConnection *gc);
48 /* static GList *irc_chat_info(PurpleConnection *gc); */
49 static void irc_login(PurpleAccount *account);
50 static void irc_login_cb(GObject *source, GAsyncResult *res, gpointer user_data);
51 static void irc_close(PurpleConnection *gc);
52 static int irc_im_send(PurpleConnection *gc, PurpleMessage *msg);
53 static int irc_chat_send(PurpleConnection *gc, int id, PurpleMessage *msg);
54 static void irc_chat_join (PurpleConnection *gc, GHashTable *data);
55 static void irc_read_input(struct irc_conn *irc);
57 static guint irc_nick_hash(const char *nick);
58 static gboolean irc_nick_equal(const char *nick1, const char *nick2);
59 static void irc_buddy_free(struct irc_buddy *ib);
61 PurpleProtocol *_irc_protocol = NULL;
63 static void irc_view_motd(PurpleProtocolAction *action)
65 PurpleConnection *gc = action->connection;
66 struct irc_conn *irc;
67 char *title, *body;
69 if (gc == NULL || purple_connection_get_protocol_data(gc) == NULL) {
70 purple_debug(PURPLE_DEBUG_ERROR, "irc", "got MOTD request for NULL gc\n");
71 return;
73 irc = purple_connection_get_protocol_data(gc);
74 if (irc->motd == NULL) {
75 purple_notify_error(gc, _("Error displaying MOTD"),
76 _("No MOTD available"),
77 _("There is no MOTD associated with this connection."),
78 purple_request_cpar_from_connection(gc));
79 return;
81 title = g_strdup_printf(_("MOTD for %s"), irc->server);
82 body = g_strdup_printf("<span style=\"font-family: monospace;\">%s</span>", irc->motd->str);
83 purple_notify_formatted(gc, title, title, NULL, body, NULL, NULL);
84 g_free(title);
85 g_free(body);
88 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
90 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
91 if (len == -1) {
92 len = strlen(buf);
94 irc_send_len(irc, buf, len);
95 return len;
98 static void
99 irc_flush_cb(GObject *source, GAsyncResult *res, gpointer data)
101 PurpleConnection *gc = data;
102 gboolean result;
103 GError *error = NULL;
105 result = g_output_stream_flush_finish(G_OUTPUT_STREAM(source),
106 res, &error);
108 if (!result) {
109 g_prefix_error(&error, _("Lost connection with server: "));
110 purple_connection_take_error(gc, error);
111 return;
115 int irc_send(struct irc_conn *irc, const char *buf)
117 return irc_send_len(irc, buf, strlen(buf));
120 int irc_send_len(struct irc_conn *irc, const char *buf, int buflen)
122 char *tosend= g_strdup(buf);
123 int len;
124 GBytes *data;
126 purple_signal_emit(_irc_protocol, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
128 if (tosend == NULL)
129 return 0;
131 len = strlen(tosend);
132 data = g_bytes_new_take(tosend, len);
133 purple_queued_output_stream_push_bytes(irc->output, data);
134 g_bytes_unref(data);
136 if (!g_output_stream_has_pending(G_OUTPUT_STREAM(irc->output))) {
137 /* Connection idle. Flush data. */
138 g_output_stream_flush_async(G_OUTPUT_STREAM(irc->output),
139 G_PRIORITY_DEFAULT, irc->cancellable,
140 irc_flush_cb,
141 purple_account_get_connection(irc->account));
144 return len;
147 /* XXX I don't like messing directly with these buddies */
148 gboolean irc_blist_timeout(struct irc_conn *irc)
150 if (irc->ison_outstanding) {
151 return TRUE;
154 g_hash_table_foreach(irc->buddies, (GHFunc)irc_ison_buddy_init,
155 (gpointer *)&irc->buddies_outstanding);
157 irc_buddy_query(irc);
159 return TRUE;
162 void irc_buddy_query(struct irc_conn *irc)
164 GList *lp;
165 GString *string;
166 struct irc_buddy *ib;
167 char *buf;
169 string = g_string_sized_new(512);
171 while ((lp = g_list_first(irc->buddies_outstanding))) {
172 ib = (struct irc_buddy *)lp->data;
173 if (string->len + strlen(ib->name) + 1 > 450)
174 break;
175 g_string_append_printf(string, "%s ", ib->name);
176 ib->new_online_status = FALSE;
177 irc->buddies_outstanding = g_list_delete_link(irc->buddies_outstanding, lp);
180 if (string->len) {
181 buf = irc_format(irc, "vn", "ISON", string->str);
182 irc_send(irc, buf);
183 g_free(buf);
184 irc->ison_outstanding = TRUE;
185 } else
186 irc->ison_outstanding = FALSE;
188 g_string_free(string, TRUE);
191 static void irc_ison_buddy_init(char *name, struct irc_buddy *ib, GList **list)
193 *list = g_list_append(*list, ib);
197 static void irc_ison_one(struct irc_conn *irc, struct irc_buddy *ib)
199 char *buf;
201 if (irc->buddies_outstanding != NULL) {
202 irc->buddies_outstanding = g_list_append(irc->buddies_outstanding, ib);
203 return;
206 ib->new_online_status = FALSE;
207 buf = irc_format(irc, "vn", "ISON", ib->name);
208 irc_send(irc, buf);
209 g_free(buf);
213 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b)
215 return "irc";
218 static GList *irc_status_types(PurpleAccount *account)
220 PurpleStatusType *type;
221 GList *types = NULL;
223 type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE);
224 types = g_list_append(types, type);
226 type = purple_status_type_new_with_attrs(
227 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
228 "message", _("Message"), purple_value_new(G_TYPE_STRING),
229 NULL);
230 types = g_list_append(types, type);
232 type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE);
233 types = g_list_append(types, type);
235 return types;
238 static GList *irc_get_actions(PurpleConnection *gc)
240 GList *list = NULL;
241 PurpleProtocolAction *act = NULL;
243 act = purple_protocol_action_new(_("View MOTD"), irc_view_motd);
244 list = g_list_append(list, act);
246 return list;
249 static GList *irc_chat_join_info(PurpleConnection *gc)
251 GList *m = NULL;
252 PurpleProtocolChatEntry *pce;
254 pce = g_new0(PurpleProtocolChatEntry, 1);
255 pce->label = _("_Channel:");
256 pce->identifier = "channel";
257 pce->required = TRUE;
258 m = g_list_append(m, pce);
260 pce = g_new0(PurpleProtocolChatEntry, 1);
261 pce->label = _("_Password:");
262 pce->identifier = "password";
263 pce->secret = TRUE;
264 m = g_list_append(m, pce);
266 return m;
269 static GHashTable *irc_chat_info_defaults(PurpleConnection *gc, const char *chat_name)
271 GHashTable *defaults;
273 defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
275 if (chat_name != NULL)
276 g_hash_table_insert(defaults, "channel", g_strdup(chat_name));
278 return defaults;
281 static void irc_login(PurpleAccount *account)
283 PurpleConnection *gc;
284 struct irc_conn *irc;
285 char **userparts;
286 const char *username = purple_account_get_username(account);
287 GSocketClient *client;
288 GError *error = NULL;
290 gc = purple_account_get_connection(account);
291 purple_connection_set_flags(gc, PURPLE_CONNECTION_FLAG_NO_NEWLINES |
292 PURPLE_CONNECTION_FLAG_NO_IMAGES);
294 if (strpbrk(username, " \t\v\r\n") != NULL) {
295 purple_connection_take_error(gc, g_error_new_literal(
296 PURPLE_CONNECTION_ERROR,
297 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
298 _("IRC nick and server may not contain whitespace")));
299 return;
302 irc = g_new0(struct irc_conn, 1);
303 purple_connection_set_protocol_data(gc, irc);
304 irc->account = account;
305 irc->cancellable = g_cancellable_new();
307 userparts = g_strsplit(username, "@", 2);
308 purple_connection_set_display_name(gc, userparts[0]);
309 irc->server = g_strdup(userparts[1]);
310 g_strfreev(userparts);
312 irc->buddies = g_hash_table_new_full((GHashFunc)irc_nick_hash, (GEqualFunc)irc_nick_equal,
313 NULL, (GDestroyNotify)irc_buddy_free);
314 irc->cmds = g_hash_table_new(g_str_hash, g_str_equal);
315 irc_cmd_table_build(irc);
316 irc->msgs = g_hash_table_new(g_str_hash, g_str_equal);
317 irc_msg_table_build(irc);
319 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
321 client = purple_gio_socket_client_new(account, &error);
323 if (client == NULL) {
324 purple_connection_take_error(gc, error);
325 return;
328 /* Optionally use TLS if it's set in the account settings */
329 g_socket_client_set_tls(client,
330 purple_account_get_bool(account, "ssl", FALSE));
332 g_socket_client_connect_to_host_async(client, irc->server,
333 purple_account_get_int(account, "port",
334 g_socket_client_get_tls(client) ?
335 IRC_DEFAULT_SSL_PORT :
336 IRC_DEFAULT_PORT),
337 irc->cancellable, irc_login_cb, gc);
338 g_object_unref(client);
341 static gboolean do_login(PurpleConnection *gc) {
342 char *buf, *tmp = NULL;
343 char *server;
344 const char *nickname, *identname, *realname;
345 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
346 const char *pass = purple_connection_get_password(gc);
347 #ifdef HAVE_CYRUS_SASL
348 const gboolean use_sasl = purple_account_get_bool(irc->account, "sasl", FALSE);
349 #endif
351 if (pass && *pass) {
352 #ifdef HAVE_CYRUS_SASL
353 if (use_sasl)
354 buf = irc_format(irc, "vv:", "CAP", "REQ", "sasl");
355 else /* intended to fall through */
356 #endif
357 buf = irc_format(irc, "v:", "PASS", pass);
358 if (irc_send(irc, buf) < 0) {
359 g_free(buf);
360 return FALSE;
362 g_free(buf);
365 realname = purple_account_get_string(irc->account, "realname", "");
366 identname = purple_account_get_string(irc->account, "username", "");
368 if (identname == NULL || *identname == '\0') {
369 identname = g_get_user_name();
372 if (identname != NULL && strchr(identname, ' ') != NULL) {
373 tmp = g_strdup(identname);
374 while ((buf = strchr(tmp, ' ')) != NULL) {
375 *buf = '_';
379 if (*irc->server == ':') {
380 /* Same as hostname, above. */
381 server = g_strdup_printf("0%s", irc->server);
382 } else {
383 server = g_strdup(irc->server);
386 buf = irc_format(irc, "vvvv:", "USER", tmp ? tmp : identname, "*", server,
387 *realname == '\0' ? IRC_DEFAULT_ALIAS : realname);
388 g_free(tmp);
389 g_free(server);
390 if (irc_send(irc, buf) < 0) {
391 g_free(buf);
392 return FALSE;
394 g_free(buf);
395 nickname = purple_connection_get_display_name(gc);
396 buf = irc_format(irc, "vn", "NICK", nickname);
397 irc->reqnick = g_strdup(nickname);
398 irc->nickused = FALSE;
399 if (irc_send(irc, buf) < 0) {
400 g_free(buf);
401 return FALSE;
403 g_free(buf);
405 irc->recv_time = time(NULL);
407 return TRUE;
410 static void
411 irc_login_cb(GObject *source, GAsyncResult *res, gpointer user_data)
413 PurpleConnection *gc = user_data;
414 GSocketConnection *conn;
415 GError *error = NULL;
416 struct irc_conn *irc;
418 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
419 res, &error);
421 if (conn == NULL) {
422 g_prefix_error(&error, _("Unable to connect: "));
423 purple_connection_take_error(gc, error);
424 return;
427 irc = purple_connection_get_protocol_data(gc);
428 irc->conn = conn;
429 irc->output = purple_queued_output_stream_new(
430 g_io_stream_get_output_stream(G_IO_STREAM(irc->conn)));
432 if (do_login(gc)) {
433 irc->input = g_data_input_stream_new(
434 g_io_stream_get_input_stream(
435 G_IO_STREAM(irc->conn)));
436 irc_read_input(irc);
440 static void irc_close(PurpleConnection *gc)
442 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
444 if (irc == NULL)
445 return;
447 if (irc->conn != NULL)
448 irc_cmd_quit(irc, "quit", NULL, NULL);
450 if (irc->cancellable != NULL) {
451 g_cancellable_cancel(irc->cancellable);
452 g_clear_object(&irc->cancellable);
455 if (irc->conn != NULL) {
456 purple_gio_graceful_close(G_IO_STREAM(irc->conn),
457 G_INPUT_STREAM(irc->input),
458 G_OUTPUT_STREAM(irc->output));
461 g_clear_object(&irc->input);
462 g_clear_object(&irc->output);
463 g_clear_object(&irc->conn);
465 if (irc->timer)
466 purple_timeout_remove(irc->timer);
467 g_hash_table_destroy(irc->cmds);
468 g_hash_table_destroy(irc->msgs);
469 g_hash_table_destroy(irc->buddies);
470 if (irc->motd)
471 g_string_free(irc->motd, TRUE);
472 g_free(irc->server);
474 g_free(irc->mode_chars);
475 g_free(irc->reqnick);
477 #ifdef HAVE_CYRUS_SASL
478 if (irc->sasl_conn) {
479 sasl_dispose(&irc->sasl_conn);
480 irc->sasl_conn = NULL;
482 g_free(irc->sasl_cb);
483 if(irc->sasl_mechs)
484 g_string_free(irc->sasl_mechs, TRUE);
485 #endif
488 g_free(irc);
491 static int irc_im_send(PurpleConnection *gc, PurpleMessage *msg)
493 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
494 char *plain;
495 const char *args[2];
497 args[0] = irc_nick_skip_mode(irc, purple_message_get_recipient(msg));
499 purple_markup_html_to_xhtml(purple_message_get_contents(msg),
500 NULL, &plain);
501 args[1] = plain;
503 irc_cmd_privmsg(irc, "msg", NULL, args);
504 g_free(plain);
505 return 1;
508 static void irc_get_info(PurpleConnection *gc, const char *who)
510 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
511 const char *args[2];
512 args[0] = who;
513 args[1] = NULL;
514 irc_cmd_whois(irc, "whois", NULL, args);
517 static void irc_set_status(PurpleAccount *account, PurpleStatus *status)
519 PurpleConnection *gc = purple_account_get_connection(account);
520 struct irc_conn *irc;
521 const char *args[1];
522 const char *status_id = purple_status_get_id(status);
524 g_return_if_fail(gc != NULL);
525 irc = purple_connection_get_protocol_data(gc);
527 if (!purple_status_is_active(status))
528 return;
530 args[0] = NULL;
532 if (!strcmp(status_id, "away")) {
533 args[0] = purple_status_get_attr_string(status, "message");
534 if ((args[0] == NULL) || (*args[0] == '\0'))
535 args[0] = _("Away");
536 irc_cmd_away(irc, "away", NULL, args);
537 } else if (!strcmp(status_id, "available")) {
538 irc_cmd_away(irc, "back", NULL, args);
542 static void irc_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group, const char *message)
544 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
545 struct irc_buddy *ib;
546 const char *bname = purple_buddy_get_name(buddy);
548 ib = g_hash_table_lookup(irc->buddies, bname);
549 if (ib != NULL) {
550 ib->ref++;
551 purple_protocol_got_user_status(irc->account, bname,
552 ib->online ? "available" : "offline", NULL);
553 } else {
554 ib = g_new0(struct irc_buddy, 1);
555 ib->name = g_strdup(bname);
556 ib->ref = 1;
557 g_hash_table_replace(irc->buddies, ib->name, ib);
560 /* if the timer isn't set, this is during signon, so we don't want to flood
561 * ourself off with ISON's, so we don't, but after that we want to know when
562 * someone's online asap */
563 if (irc->timer)
564 irc_ison_one(irc, ib);
567 static void irc_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
569 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
570 struct irc_buddy *ib;
572 ib = g_hash_table_lookup(irc->buddies, purple_buddy_get_name(buddy));
573 if (ib && --ib->ref == 0) {
574 g_hash_table_remove(irc->buddies, purple_buddy_get_name(buddy));
578 static void
579 irc_read_input_cb(GObject *source, GAsyncResult *res, gpointer data)
581 PurpleConnection *gc = data;
582 struct irc_conn *irc;
583 gchar *line;
584 gsize len;
585 gsize start = 0;
586 GError *error = NULL;
588 line = g_data_input_stream_read_line_finish(
589 G_DATA_INPUT_STREAM(source), res, &len, &error);
591 if (line == NULL && error != NULL) {
592 g_prefix_error(&error, _("Lost connection with server: "));
593 purple_connection_take_error(gc, error);
594 return;
595 } else if (line == NULL) {
596 purple_connection_take_error(gc, g_error_new_literal(
597 PURPLE_CONNECTION_ERROR,
598 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
599 _("Server closed the connection")));
600 return;
603 irc = purple_connection_get_protocol_data(gc);
605 purple_connection_update_last_received(gc);
607 if (len > 0 && line[len - 1] == '\r')
608 line[len - 1] = '\0';
610 /* This is a hack to work around the fact that marv gets messages
611 * with null bytes in them while using some weird irc server at work
613 while (start < len && line[start] == '\0')
614 ++start;
616 if (len - start > 0)
617 irc_parse_msg(irc, line + start);
619 g_free(line);
621 irc_read_input(irc);
624 static void
625 irc_read_input(struct irc_conn *irc)
627 PurpleConnection *gc = purple_account_get_connection(irc->account);
629 g_data_input_stream_read_line_async(irc->input,
630 G_PRIORITY_DEFAULT, irc->cancellable,
631 irc_read_input_cb, gc);
634 static void irc_chat_join (PurpleConnection *gc, GHashTable *data)
636 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
637 const char *args[2];
639 args[0] = g_hash_table_lookup(data, "channel");
640 args[1] = g_hash_table_lookup(data, "password");
641 irc_cmd_join(irc, "join", NULL, args);
644 static char *irc_get_chat_name(GHashTable *data) {
645 return g_strdup(g_hash_table_lookup(data, "channel"));
648 static void irc_chat_invite(PurpleConnection *gc, int id, const char *message, const char *name)
650 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
651 PurpleConversation *convo = PURPLE_CONVERSATION(purple_conversations_find_chat(gc, id));
652 const char *args[2];
654 if (!convo) {
655 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n");
656 return;
658 args[0] = name;
659 args[1] = purple_conversation_get_name(convo);
660 irc_cmd_invite(irc, "invite", purple_conversation_get_name(convo), args);
664 static void irc_chat_leave (PurpleConnection *gc, int id)
666 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
667 PurpleConversation *convo = PURPLE_CONVERSATION(purple_conversations_find_chat(gc, id));
668 const char *args[2];
670 if (!convo)
671 return;
673 args[0] = purple_conversation_get_name(convo);
674 args[1] = NULL;
675 irc_cmd_part(irc, "part", purple_conversation_get_name(convo), args);
676 purple_serv_got_chat_left(gc, id);
679 static int irc_chat_send(PurpleConnection *gc, int id, PurpleMessage *msg)
681 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
682 PurpleConversation *convo = PURPLE_CONVERSATION(purple_conversations_find_chat(gc, id));
683 const char *args[2];
684 char *tmp;
686 if (!convo) {
687 purple_debug(PURPLE_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
688 return -EINVAL;
690 #if 0
691 if (*what == '/') {
692 return irc_parse_cmd(irc, convo->name, what + 1);
694 #endif
695 purple_markup_html_to_xhtml(purple_message_get_contents(msg), NULL, &tmp);
696 args[0] = purple_conversation_get_name(convo);
697 args[1] = tmp;
699 irc_cmd_privmsg(irc, "msg", NULL, args);
701 /* TODO: use msg */
702 purple_serv_got_chat_in(gc, id, purple_connection_get_display_name(gc),
703 purple_message_get_flags(msg),
704 purple_message_get_contents(msg), time(NULL));
705 g_free(tmp);
706 return 0;
709 static guint irc_nick_hash(const char *nick)
711 char *lc;
712 guint bucket;
714 lc = g_utf8_strdown(nick, -1);
715 bucket = g_str_hash(lc);
716 g_free(lc);
718 return bucket;
721 static gboolean irc_nick_equal(const char *nick1, const char *nick2)
723 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
726 static void irc_buddy_free(struct irc_buddy *ib)
728 g_free(ib->name);
729 g_free(ib);
732 static void irc_chat_set_topic(PurpleConnection *gc, int id, const char *topic)
734 char *buf;
735 const char *name = NULL;
736 struct irc_conn *irc;
738 irc = purple_connection_get_protocol_data(gc);
739 name = purple_conversation_get_name(PURPLE_CONVERSATION(
740 purple_conversations_find_chat(gc, id)));
742 if (name == NULL)
743 return;
745 buf = irc_format(irc, "vt:", "TOPIC", name, topic);
746 irc_send(irc, buf);
747 g_free(buf);
750 static PurpleRoomlist *irc_roomlist_get_list(PurpleConnection *gc)
752 struct irc_conn *irc;
753 GList *fields = NULL;
754 PurpleRoomlistField *f;
755 char *buf;
757 irc = purple_connection_get_protocol_data(gc);
759 if (irc->roomlist)
760 g_object_unref(irc->roomlist);
762 irc->roomlist = purple_roomlist_new(purple_connection_get_account(gc));
764 f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "channel", TRUE);
765 fields = g_list_append(fields, f);
767 f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT, _("Users"), "users", FALSE);
768 fields = g_list_append(fields, f);
770 f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE);
771 fields = g_list_append(fields, f);
773 purple_roomlist_set_fields(irc->roomlist, fields);
775 buf = irc_format(irc, "v", "LIST");
776 irc_send(irc, buf);
777 g_free(buf);
779 return irc->roomlist;
782 static void irc_roomlist_cancel(PurpleRoomlist *list)
784 PurpleAccount *account = purple_roomlist_get_account(list);
785 PurpleConnection *gc = purple_account_get_connection(account);
786 struct irc_conn *irc;
788 if (gc == NULL)
789 return;
791 irc = purple_connection_get_protocol_data(gc);
793 purple_roomlist_set_in_progress(list, FALSE);
795 if (irc->roomlist == list) {
796 irc->roomlist = NULL;
797 g_object_unref(list);
801 static void irc_keepalive(PurpleConnection *gc)
803 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
804 if ((time(NULL) - irc->recv_time) > PING_TIMEOUT)
805 irc_cmd_ping(irc, NULL, NULL, NULL);
808 static gssize
809 irc_get_max_message_size(PurpleConversation *conv)
811 /* TODO: this static value is got from pidgin-otr, but it depends on
812 * some factors, for example IRC channel name. */
813 return 417;
816 static void
817 irc_protocol_init(PurpleProtocol *protocol)
819 PurpleAccountUserSplit *split;
820 PurpleAccountOption *option;
822 protocol->id = "prpl-irc";
823 protocol->name = "IRC";
824 protocol->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL |
825 OPT_PROTO_SLASH_COMMANDS_NATIVE;
827 split = purple_account_user_split_new(_("Server"), IRC_DEFAULT_SERVER, '@');
828 protocol->user_splits = g_list_append(protocol->user_splits, split);
830 option = purple_account_option_int_new(_("Port"), "port", IRC_DEFAULT_PORT);
831 protocol->account_options = g_list_append(protocol->account_options, option);
833 option = purple_account_option_string_new(_("Encodings"), "encoding", IRC_DEFAULT_CHARSET);
834 protocol->account_options = g_list_append(protocol->account_options, option);
836 option = purple_account_option_bool_new(_("Auto-detect incoming UTF-8"), "autodetect_utf8", IRC_DEFAULT_AUTODETECT);
837 protocol->account_options = g_list_append(protocol->account_options, option);
839 option = purple_account_option_string_new(_("Ident name"), "username", "");
840 protocol->account_options = g_list_append(protocol->account_options, option);
842 option = purple_account_option_string_new(_("Real name"), "realname", "");
843 protocol->account_options = g_list_append(protocol->account_options, option);
846 option = purple_account_option_string_new(_("Quit message"), "quitmsg", IRC_DEFAULT_QUIT);
847 protocol->account_options = g_list_append(protocol->account_options, option);
850 option = purple_account_option_bool_new(_("Use SSL"), "ssl", FALSE);
851 protocol->account_options = g_list_append(protocol->account_options, option);
853 #ifdef HAVE_CYRUS_SASL
854 option = purple_account_option_bool_new(_("Authenticate with SASL"), "sasl", FALSE);
855 protocol->account_options = g_list_append(protocol->account_options, option);
857 option = purple_account_option_bool_new(
858 _("Allow plaintext SASL auth over unencrypted connection"),
859 "auth_plain_in_clear", FALSE);
860 protocol->account_options = g_list_append(protocol->account_options, option);
861 #endif
864 static void
865 irc_protocol_class_init(PurpleProtocolClass *klass)
867 klass->login = irc_login;
868 klass->close = irc_close;
869 klass->status_types = irc_status_types;
870 klass->list_icon = irc_blist_icon;
873 static void
874 irc_protocol_client_iface_init(PurpleProtocolClientIface *client_iface)
876 client_iface->get_actions = irc_get_actions;
877 client_iface->normalize = purple_normalize_nocase;
878 client_iface->get_max_message_size = irc_get_max_message_size;
881 static void
882 irc_protocol_server_iface_init(PurpleProtocolServerIface *server_iface)
884 server_iface->set_status = irc_set_status;
885 server_iface->get_info = irc_get_info;
886 server_iface->add_buddy = irc_add_buddy;
887 server_iface->remove_buddy = irc_remove_buddy;
888 server_iface->keepalive = irc_keepalive;
889 server_iface->send_raw = irc_send_raw;
892 static void
893 irc_protocol_im_iface_init(PurpleProtocolIMIface *im_iface)
895 im_iface->send = irc_im_send;
898 static void
899 irc_protocol_chat_iface_init(PurpleProtocolChatIface *chat_iface)
901 chat_iface->info = irc_chat_join_info;
902 chat_iface->info_defaults = irc_chat_info_defaults;
903 chat_iface->join = irc_chat_join;
904 chat_iface->get_name = irc_get_chat_name;
905 chat_iface->invite = irc_chat_invite;
906 chat_iface->leave = irc_chat_leave;
907 chat_iface->send = irc_chat_send;
908 chat_iface->set_topic = irc_chat_set_topic;
911 static void
912 irc_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *roomlist_iface)
914 roomlist_iface->get_list = irc_roomlist_get_list;
915 roomlist_iface->cancel = irc_roomlist_cancel;
918 static void
919 irc_protocol_xfer_iface_init(PurpleProtocolXferIface *xfer_iface)
921 xfer_iface->send = irc_dccsend_send_file;
922 xfer_iface->new_xfer = irc_dccsend_new_xfer;
925 PURPLE_DEFINE_TYPE_EXTENDED(
926 IRCProtocol, irc_protocol, PURPLE_TYPE_PROTOCOL, 0,
928 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE,
929 irc_protocol_client_iface_init)
931 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE,
932 irc_protocol_server_iface_init)
934 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
935 irc_protocol_im_iface_init)
937 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE,
938 irc_protocol_chat_iface_init)
940 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
941 irc_protocol_roomlist_iface_init)
943 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER_IFACE,
944 irc_protocol_xfer_iface_init)
947 static PurplePluginInfo *
948 plugin_query(GError **error)
950 return purple_plugin_info_new(
951 "id", "prpl-irc",
952 "name", "IRC Protocol",
953 "version", DISPLAY_VERSION,
954 "category", N_("Protocol"),
955 "summary", N_("IRC Protocol Plugin"),
956 "description", N_("The IRC Protocol Plugin that Sucks Less"),
957 "website", PURPLE_WEBSITE,
958 "abi-version", PURPLE_ABI_VERSION,
959 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
960 PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
961 NULL
965 static gboolean
966 plugin_load(PurplePlugin *plugin, GError **error)
968 irc_protocol_register_type(plugin);
970 _irc_protocol = purple_protocols_add(IRC_TYPE_PROTOCOL, error);
971 if (!_irc_protocol)
972 return FALSE;
974 purple_prefs_remove("/plugins/prpl/irc/quitmsg");
975 purple_prefs_remove("/plugins/prpl/irc");
977 irc_register_commands();
979 purple_signal_register(_irc_protocol, "irc-sending-text",
980 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
981 PURPLE_TYPE_CONNECTION,
982 G_TYPE_POINTER); /* pointer to a string */
983 purple_signal_register(_irc_protocol, "irc-receiving-text",
984 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
985 PURPLE_TYPE_CONNECTION,
986 G_TYPE_POINTER); /* pointer to a string */
988 return TRUE;
991 static gboolean
992 plugin_unload(PurplePlugin *plugin, GError **error)
994 irc_unregister_commands();
996 if (!purple_protocols_remove(_irc_protocol, error))
997 return FALSE;
999 return TRUE;
1002 PURPLE_PLUGIN_INIT(irc, plugin_query, plugin_load, plugin_unload);