Sat, 10 Oct 2020 02:05:12 -0500
Use the irc nick as the ident when the user has not specified on. Fixes PIDGIN-17435
Testing Done:
Connected to irc, whois'd myself and verified that the ident and real names were set to my handle and purple which are the defaults when they're not specified.
Bugs closed: PIDGIN-17435
Reviewed at https://reviews.imfreedom.org/r/153/
/** * purple * * Copyright (C) 2003, Robbert Haarman <purple@inglorion.net> * Copyright (C) 2003, 2012 Ethan Blanton <elb@pidgin.im> * Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com> * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <errno.h> #include <glib/gi18n-lib.h> #include <purple.h> #include "irc.h" #define PING_TIMEOUT 60 static void irc_ison_buddy_init(char *name, struct irc_buddy *ib, GList **list); static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b); static GList *irc_status_types(PurpleAccount *account); static GList *irc_get_actions(PurpleConnection *gc); /* static GList *irc_chat_info(PurpleConnection *gc); */ static void irc_login(PurpleAccount *account); static void irc_login_cb(GObject *source, GAsyncResult *res, gpointer user_data); static void irc_close(PurpleConnection *gc); static int irc_im_send(PurpleProtocolIM *im, PurpleConnection *gc, PurpleMessage *msg); static int irc_chat_send(PurpleConnection *gc, int id, PurpleMessage *msg); static void irc_chat_join (PurpleConnection *gc, GHashTable *data); static void irc_read_input_cb(GObject *source, GAsyncResult *res, gpointer data); static guint irc_nick_hash(const char *nick); static gboolean irc_nick_equal(const char *nick1, const char *nick2); static void irc_buddy_free(struct irc_buddy *ib); PurpleProtocol *_irc_protocol = NULL; static gint irc_uri_handler_match_server(PurpleAccount *account, const gchar *match_server) { const gchar *protocol_id; const gchar *username; gchar *server; protocol_id = purple_account_get_protocol_id(account); if (!purple_strequal(protocol_id, "prpl-irc") || !purple_account_is_connected(account)) { return -1; } if (match_server == NULL || match_server[0] == '\0') { /* No server specified, match any IRC account */ return 0; } username = purple_account_get_username(account); server = strchr(username, '@'); /* +1 to skip '@' */ if (server == NULL || !purple_strequal(match_server, server + 1)) { return -1; } return 0; } static gboolean irc_uri_handler(const gchar *scheme, const gchar *uri, GHashTable *params) { gchar *target; gchar *server; GList *accounts; GList *account_node; gchar **target_tokens; PurpleAccount *account; gchar **modifier; gboolean isnick = FALSE; g_return_val_if_fail(uri != NULL, FALSE); if (!purple_strequal(scheme, "irc")) { /* Not a scheme we handle here */ return FALSE; } if (g_str_has_prefix(uri, "//")) { /* Skip initial '//' if it exists */ uri += 2; } /* Find the target (aka room or user) */ target = strchr(uri, '/'); /* [1] to skip the '/' */ if (target == NULL || target[1] == '\0') { purple_debug_warning("irc", "URI missing valid target: %s", uri); return FALSE; } server = g_strndup(uri, target - uri); /* Find account with correct server */ accounts = purple_accounts_get_all(); account_node = g_list_find_custom( accounts, server, (GCompareFunc)irc_uri_handler_match_server); if (account_node == NULL) { purple_debug_warning("irc", "No account online on '%s' for handling URI", server); g_free(server); return FALSE; } account = account_node->data; /* Tokenize modifiers, +1 to skip the initial '/' */ target_tokens = g_strsplit(target + 1, ",", 0); target = g_strdup_printf("#%s", target_tokens[0]); /* Parse modifiers, start at 1 to skip the actual target */ for (modifier = target_tokens + 1; *modifier != NULL; ++modifier) { if (purple_strequal(*modifier, "isnick")) { isnick = TRUE; break; } } g_strfreev(target_tokens); if (isnick) { PurpleIMConversation *im; /* 'server' isn't needed here. Free it immediately. */ g_free(server); /* +1 to skip '#' target prefix */ im = purple_im_conversation_new(account, target + 1); g_free(target); purple_conversation_present(PURPLE_CONVERSATION(im)); if (params != NULL) { const gchar *msg = g_hash_table_lookup(params, "msg"); if (msg != NULL) { purple_conversation_send_confirm( PURPLE_CONVERSATION(im), msg); } } return TRUE; } else { GHashTable *components; components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); /* Transfer ownership of these to the hash table */ g_hash_table_insert(components, "server", server); g_hash_table_insert(components, "channel", target); if (params != NULL) { const gchar *key = g_hash_table_lookup(params, "key"); if (key != NULL) { g_hash_table_insert(components, "password", g_strdup(key)); } } purple_serv_join_chat(purple_account_get_connection(account), components); g_hash_table_destroy(components); return TRUE; } return FALSE; } static void irc_view_motd(PurpleProtocolAction *action) { PurpleConnection *gc = action->connection; struct irc_conn *irc; char *title, *body; if (gc == NULL || purple_connection_get_protocol_data(gc) == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "irc", "got MOTD request for NULL gc\n"); return; } irc = purple_connection_get_protocol_data(gc); if (irc->motd == NULL) { purple_notify_error(gc, _("Error displaying MOTD"), _("No MOTD available"), _("There is no MOTD associated with this connection."), purple_request_cpar_from_connection(gc)); return; } title = g_strdup_printf(_("MOTD for %s"), irc->server); body = g_strdup_printf("<span style=\"font-family: monospace;\">%s</span>", irc->motd->str); purple_notify_formatted(gc, title, title, NULL, body, NULL, NULL); g_free(title); g_free(body); } static int irc_send_raw(PurpleConnection *gc, const char *buf, int len) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); if (len == -1) { len = strlen(buf); } irc_send_len(irc, buf, len); return len; } static void irc_push_bytes_cb(GObject *source, GAsyncResult *res, gpointer data) { PurpleQueuedOutputStream *stream = PURPLE_QUEUED_OUTPUT_STREAM(source); PurpleConnection *gc = data; gboolean result; GError *error = NULL; result = purple_queued_output_stream_push_bytes_finish(stream, res, &error); if (!result) { purple_queued_output_stream_clear_queue(stream); g_prefix_error(&error, "%s", _("Lost connection with server: ")); purple_connection_take_error(gc, error); return; } } int irc_send(struct irc_conn *irc, const char *buf) { return irc_send_len(irc, buf, strlen(buf)); } int irc_send_len(struct irc_conn *irc, const char *buf, int buflen) { char *tosend = g_strdup(buf); int len; GBytes *data; purple_signal_emit(_irc_protocol, "irc-sending-text", purple_account_get_connection(irc->account), &tosend); if (tosend == NULL) return 0; if (purple_debug_is_verbose()) { gchar *clean = g_utf8_make_valid(tosend, -1); clean = g_strstrip(clean); purple_debug_misc("irc", "<< %s\n", clean); g_free(clean); } len = strlen(tosend); data = g_bytes_new_take(tosend, len); purple_queued_output_stream_push_bytes_async(irc->output, data, G_PRIORITY_DEFAULT, irc->cancellable, irc_push_bytes_cb, purple_account_get_connection(irc->account)); g_bytes_unref(data); return len; } /* XXX I don't like messing directly with these buddies */ gboolean irc_blist_timeout(struct irc_conn *irc) { if (irc->ison_outstanding) { return TRUE; } g_hash_table_foreach(irc->buddies, (GHFunc)irc_ison_buddy_init, (gpointer *)&irc->buddies_outstanding); irc_buddy_query(irc); return TRUE; } void irc_buddy_query(struct irc_conn *irc) { GList *lp; GString *string; struct irc_buddy *ib; char *buf; string = g_string_sized_new(512); while ((lp = g_list_first(irc->buddies_outstanding))) { ib = (struct irc_buddy *)lp->data; if (string->len + strlen(ib->name) + 1 > 450) break; g_string_append_printf(string, "%s ", ib->name); ib->new_online_status = FALSE; irc->buddies_outstanding = g_list_delete_link(irc->buddies_outstanding, lp); } if (string->len) { buf = irc_format(irc, "vn", "ISON", string->str); irc_send(irc, buf); g_free(buf); irc->ison_outstanding = TRUE; } else irc->ison_outstanding = FALSE; g_string_free(string, TRUE); } static void irc_ison_buddy_init(char *name, struct irc_buddy *ib, GList **list) { *list = g_list_append(*list, ib); } static void irc_ison_one(struct irc_conn *irc, struct irc_buddy *ib) { char *buf; if (irc->buddies_outstanding != NULL) { irc->buddies_outstanding = g_list_append(irc->buddies_outstanding, ib); return; } ib->new_online_status = FALSE; buf = irc_format(irc, "vn", "ISON", ib->name); irc_send(irc, buf); g_free(buf); } static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b) { return "irc"; } static GList *irc_status_types(PurpleAccount *account) { PurpleStatusType *type; GList *types = NULL; type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE); types = g_list_append(types, type); type = purple_status_type_new_with_attrs( PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(G_TYPE_STRING), NULL); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE); types = g_list_append(types, type); return types; } static GList *irc_get_actions(PurpleConnection *gc) { GList *list = NULL; PurpleProtocolAction *act = NULL; act = purple_protocol_action_new(_("View MOTD"), irc_view_motd); list = g_list_append(list, act); return list; } static GList *irc_chat_join_info(PurpleConnection *gc) { GList *m = NULL; PurpleProtocolChatEntry *pce; pce = g_new0(PurpleProtocolChatEntry, 1); pce->label = _("_Channel:"); pce->identifier = "channel"; pce->required = TRUE; m = g_list_append(m, pce); pce = g_new0(PurpleProtocolChatEntry, 1); pce->label = _("_Password:"); pce->identifier = "password"; pce->secret = TRUE; m = g_list_append(m, pce); return m; } static GHashTable *irc_chat_info_defaults(PurpleConnection *gc, const char *chat_name) { GHashTable *defaults; defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); if (chat_name != NULL) g_hash_table_insert(defaults, "channel", g_strdup(chat_name)); return defaults; } static void irc_login(PurpleAccount *account) { PurpleConnection *gc; struct irc_conn *irc; char **userparts; const char *username = purple_account_get_username(account); GSocketClient *client; GError *error = NULL; gc = purple_account_get_connection(account); purple_connection_set_flags(gc, PURPLE_CONNECTION_FLAG_NO_NEWLINES | PURPLE_CONNECTION_FLAG_NO_IMAGES); if (strpbrk(username, " \t\v\r\n") != NULL) { purple_connection_take_error(gc, g_error_new_literal( PURPLE_CONNECTION_ERROR, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, _("IRC nick and server may not contain whitespace"))); return; } irc = g_new0(struct irc_conn, 1); purple_connection_set_protocol_data(gc, irc); irc->account = account; irc->cancellable = g_cancellable_new(); userparts = g_strsplit(username, "@", 2); purple_connection_set_display_name(gc, userparts[0]); irc->server = g_strdup(userparts[1]); g_strfreev(userparts); irc->buddies = g_hash_table_new_full((GHashFunc)irc_nick_hash, (GEqualFunc)irc_nick_equal, NULL, (GDestroyNotify)irc_buddy_free); irc->cmds = g_hash_table_new(g_str_hash, g_str_equal); irc_cmd_table_build(irc); irc->msgs = g_hash_table_new(g_str_hash, g_str_equal); irc_msg_table_build(irc); purple_connection_update_progress(gc, _("Connecting"), 1, 2); client = purple_gio_socket_client_new(account, &error); if (client == NULL) { purple_connection_take_error(gc, error); return; } /* Optionally use TLS if it's set in the account settings */ g_socket_client_set_tls(client, purple_account_get_bool(account, "ssl", FALSE)); g_socket_client_connect_to_host_async(client, irc->server, purple_account_get_int(account, "port", g_socket_client_get_tls(client) ? IRC_DEFAULT_SSL_PORT : IRC_DEFAULT_PORT), irc->cancellable, irc_login_cb, gc); g_object_unref(client); } static gboolean do_login(PurpleConnection *gc) { char *buf = NULL; char *server; const char *nickname, *identname, *realname; struct irc_conn *irc = purple_connection_get_protocol_data(gc); const char *pass = purple_connection_get_password(gc); #ifdef HAVE_CYRUS_SASL const gboolean use_sasl = purple_account_get_bool(irc->account, "sasl", FALSE); #endif if (pass && *pass) { #ifdef HAVE_CYRUS_SASL if (use_sasl) buf = irc_format(irc, "vv:", "CAP", "REQ", "sasl"); else /* intended to fall through */ #endif buf = irc_format(irc, "v:", "PASS", pass); if (irc_send(irc, buf) < 0) { g_free(buf); return FALSE; } g_free(buf); } nickname = purple_connection_get_display_name(gc); realname = purple_account_get_string(irc->account, "realname", ""); if(realname == NULL || *realname == '\0') { realname = IRC_DEFAULT_ALIAS; } identname = purple_account_get_string(irc->account, "username", ""); if(identname == NULL || *identname == '\0') { identname = nickname; } if (*irc->server == ':') { /* Same as hostname, above. */ server = g_strdup_printf("0%s", irc->server); } else { server = g_strdup(irc->server); } buf = irc_format(irc, "vvvv:", "USER", identname, "*", server, realname); g_free(server); if (irc_send(irc, buf) < 0) { g_free(buf); return FALSE; } g_free(buf); buf = irc_format(irc, "vn", "NICK", nickname); irc->reqnick = g_strdup(nickname); irc->nickused = FALSE; if (irc_send(irc, buf) < 0) { g_free(buf); return FALSE; } g_free(buf); irc->recv_time = time(NULL); return TRUE; } static void irc_login_cb(GObject *source, GAsyncResult *res, gpointer user_data) { PurpleConnection *gc = user_data; GSocketConnection *conn; GError *error = NULL; struct irc_conn *irc; conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source), res, &error); if (conn == NULL) { g_prefix_error(&error, "%s", _("Unable to connect: ")); purple_connection_take_error(gc, error); return; } irc = purple_connection_get_protocol_data(gc); irc->conn = conn; irc->output = purple_queued_output_stream_new( g_io_stream_get_output_stream(G_IO_STREAM(irc->conn))); if (do_login(gc)) { irc->input = g_data_input_stream_new( g_io_stream_get_input_stream( G_IO_STREAM(irc->conn))); g_data_input_stream_read_line_async(irc->input, G_PRIORITY_DEFAULT, irc->cancellable, irc_read_input_cb, gc); } } static void irc_close(PurpleConnection *gc) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); if (irc == NULL) return; if (irc->conn != NULL) irc_cmd_quit(irc, "quit", NULL, NULL); if (irc->cancellable != NULL) { g_cancellable_cancel(irc->cancellable); g_clear_object(&irc->cancellable); } if (irc->conn != NULL) { purple_gio_graceful_close(G_IO_STREAM(irc->conn), G_INPUT_STREAM(irc->input), G_OUTPUT_STREAM(irc->output)); } g_clear_object(&irc->input); g_clear_object(&irc->output); g_clear_object(&irc->conn); if (irc->timer) g_source_remove(irc->timer); g_hash_table_destroy(irc->cmds); g_hash_table_destroy(irc->msgs); g_hash_table_destroy(irc->buddies); if (irc->motd) g_string_free(irc->motd, TRUE); g_free(irc->server); g_free(irc->mode_chars); g_free(irc->reqnick); #ifdef HAVE_CYRUS_SASL if (irc->sasl_conn) { sasl_dispose(&irc->sasl_conn); irc->sasl_conn = NULL; } g_free(irc->sasl_cb); if(irc->sasl_mechs) g_string_free(irc->sasl_mechs, TRUE); #endif g_free(irc); } static int irc_im_send(PurpleProtocolIM *im, PurpleConnection *gc, PurpleMessage *msg) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); char *plain; const char *args[2]; args[0] = irc_nick_skip_mode(irc, purple_message_get_recipient(msg)); purple_markup_html_to_xhtml(purple_message_get_contents(msg), NULL, &plain); args[1] = plain; irc_cmd_privmsg(irc, "msg", NULL, args); g_free(plain); return 1; } static void irc_get_info(PurpleConnection *gc, const char *who) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); const char *args[2]; args[0] = who; args[1] = NULL; irc_cmd_whois(irc, "whois", NULL, args); } static void irc_set_status(PurpleAccount *account, PurpleStatus *status) { PurpleConnection *gc = purple_account_get_connection(account); struct irc_conn *irc; const char *args[1]; const char *status_id = purple_status_get_id(status); g_return_if_fail(gc != NULL); irc = purple_connection_get_protocol_data(gc); if (!purple_status_is_active(status)) return; args[0] = NULL; if (purple_strequal(status_id, "away")) { args[0] = purple_status_get_attr_string(status, "message"); if ((args[0] == NULL) || (*args[0] == '\0')) args[0] = _("Away"); irc_cmd_away(irc, "away", NULL, args); } else if (purple_strequal(status_id, "available")) { irc_cmd_away(irc, "back", NULL, args); } } static void irc_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group, const char *message) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); struct irc_buddy *ib; const char *bname = purple_buddy_get_name(buddy); ib = g_hash_table_lookup(irc->buddies, bname); if (ib != NULL) { ib->ref++; purple_protocol_got_user_status(irc->account, bname, ib->online ? "available" : "offline", NULL); } else { ib = g_new0(struct irc_buddy, 1); ib->name = g_strdup(bname); ib->ref = 1; g_hash_table_replace(irc->buddies, ib->name, ib); } /* if the timer isn't set, this is during signon, so we don't want to flood * ourself off with ISON's, so we don't, but after that we want to know when * someone's online asap */ if (irc->timer) irc_ison_one(irc, ib); } static void irc_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); struct irc_buddy *ib; ib = g_hash_table_lookup(irc->buddies, purple_buddy_get_name(buddy)); if (ib && --ib->ref == 0) { g_hash_table_remove(irc->buddies, purple_buddy_get_name(buddy)); } } static void irc_read_input_cb(GObject *source, GAsyncResult *res, gpointer data) { PurpleConnection *gc = data; struct irc_conn *irc; gchar *line; gsize len; gsize start = 0; GError *error = NULL; line = g_data_input_stream_read_line_finish( G_DATA_INPUT_STREAM(source), res, &len, &error); if (line == NULL && error != NULL) { g_prefix_error(&error, "%s", _("Lost connection with server: ")); purple_connection_take_error(gc, error); return; } else if (line == NULL) { purple_connection_take_error(gc, g_error_new_literal( PURPLE_CONNECTION_ERROR, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Server closed the connection"))); return; } irc = purple_connection_get_protocol_data(gc); purple_connection_update_last_received(gc); if (len > 0 && line[len - 1] == '\r') line[len - 1] = '\0'; /* This is a hack to work around the fact that marv gets messages * with null bytes in them while using some weird irc server at work */ while (start < len && line[start] == '\0') ++start; if (start < len) { irc_parse_msg(irc, line + start); } g_free(line); g_data_input_stream_read_line_async(irc->input, G_PRIORITY_DEFAULT, irc->cancellable, irc_read_input_cb, gc); } static void irc_chat_join (PurpleConnection *gc, GHashTable *data) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); const char *args[2]; args[0] = g_hash_table_lookup(data, "channel"); args[1] = g_hash_table_lookup(data, "password"); irc_cmd_join(irc, "join", NULL, args); } static char *irc_get_chat_name(GHashTable *data) { return g_strdup(g_hash_table_lookup(data, "channel")); } static void irc_chat_invite(PurpleConnection *gc, int id, const char *message, const char *name) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); PurpleConversation *convo = PURPLE_CONVERSATION(purple_conversations_find_chat(gc, id)); const char *args[2]; if (!convo) { purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n"); return; } args[0] = name; args[1] = purple_conversation_get_name(convo); irc_cmd_invite(irc, "invite", purple_conversation_get_name(convo), args); } static void irc_chat_leave (PurpleConnection *gc, int id) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); PurpleConversation *convo = PURPLE_CONVERSATION(purple_conversations_find_chat(gc, id)); const char *args[2]; if (!convo) return; args[0] = purple_conversation_get_name(convo); args[1] = NULL; irc_cmd_part(irc, "part", purple_conversation_get_name(convo), args); purple_serv_got_chat_left(gc, id); } static int irc_chat_send(PurpleConnection *gc, int id, PurpleMessage *msg) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); PurpleConversation *convo = PURPLE_CONVERSATION(purple_conversations_find_chat(gc, id)); const char *args[2]; char *tmp; if (!convo) { purple_debug(PURPLE_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n"); return -EINVAL; } purple_markup_html_to_xhtml(purple_message_get_contents(msg), NULL, &tmp); args[0] = purple_conversation_get_name(convo); args[1] = tmp; irc_cmd_privmsg(irc, "msg", NULL, args); /* TODO: use msg */ purple_serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), purple_message_get_flags(msg), purple_message_get_contents(msg), time(NULL)); g_free(tmp); return 0; } static guint irc_nick_hash(const char *nick) { char *lc; guint bucket; lc = g_utf8_strdown(nick, -1); bucket = g_str_hash(lc); g_free(lc); return bucket; } static gboolean irc_nick_equal(const char *nick1, const char *nick2) { return (purple_utf8_strcasecmp(nick1, nick2) == 0); } static void irc_buddy_free(struct irc_buddy *ib) { g_free(ib->name); g_free(ib); } static void irc_chat_set_topic(PurpleConnection *gc, int id, const char *topic) { char *buf; const char *name = NULL; struct irc_conn *irc; irc = purple_connection_get_protocol_data(gc); name = purple_conversation_get_name(PURPLE_CONVERSATION( purple_conversations_find_chat(gc, id))); if (name == NULL) return; buf = irc_format(irc, "vt:", "TOPIC", name, topic); irc_send(irc, buf); g_free(buf); } static PurpleRoomlist *irc_roomlist_get_list(PurpleConnection *gc) { struct irc_conn *irc; GList *fields = NULL; PurpleRoomlistField *f; char *buf; irc = purple_connection_get_protocol_data(gc); if (irc->roomlist) g_object_unref(irc->roomlist); irc->roomlist = purple_roomlist_new(purple_connection_get_account(gc)); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "channel", TRUE); fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT, _("Users"), "users", FALSE); fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE); fields = g_list_append(fields, f); purple_roomlist_set_fields(irc->roomlist, fields); buf = irc_format(irc, "v", "LIST"); irc_send(irc, buf); g_free(buf); return irc->roomlist; } static void irc_roomlist_cancel(PurpleRoomlist *list) { PurpleAccount *account = purple_roomlist_get_account(list); PurpleConnection *gc = purple_account_get_connection(account); struct irc_conn *irc; if (gc == NULL) return; irc = purple_connection_get_protocol_data(gc); purple_roomlist_set_in_progress(list, FALSE); if (irc->roomlist == list) { irc->roomlist = NULL; g_object_unref(list); } } static void irc_keepalive(PurpleConnection *gc) { struct irc_conn *irc = purple_connection_get_protocol_data(gc); if ((time(NULL) - irc->recv_time) > PING_TIMEOUT) irc_cmd_ping(irc, NULL, NULL, NULL); } static gssize irc_get_max_message_size(PurpleConversation *conv) { /* TODO: this static value is got from pidgin-otr, but it depends on * some factors, for example IRC channel name. */ return 417; } static void irc_protocol_init(IRCProtocol *self) { PurpleProtocol *protocol = PURPLE_PROTOCOL(self); PurpleAccountUserSplit *split; PurpleAccountOption *option; protocol->id = "prpl-irc"; protocol->name = "IRC"; protocol->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_SLASH_COMMANDS_NATIVE; split = purple_account_user_split_new(_("Server"), IRC_DEFAULT_SERVER, '@'); protocol->user_splits = g_list_append(protocol->user_splits, split); option = purple_account_option_int_new(_("Port"), "port", IRC_DEFAULT_PORT); protocol->account_options = g_list_append(protocol->account_options, option); option = purple_account_option_string_new(_("Encodings"), "encoding", IRC_DEFAULT_CHARSET); protocol->account_options = g_list_append(protocol->account_options, option); option = purple_account_option_bool_new(_("Auto-detect incoming UTF-8"), "autodetect_utf8", IRC_DEFAULT_AUTODETECT); protocol->account_options = g_list_append(protocol->account_options, option); option = purple_account_option_string_new(_("Ident name"), "username", ""); protocol->account_options = g_list_append(protocol->account_options, option); option = purple_account_option_string_new(_("Real name"), "realname", ""); protocol->account_options = g_list_append(protocol->account_options, option); /* option = purple_account_option_string_new(_("Quit message"), "quitmsg", IRC_DEFAULT_QUIT); protocol->account_options = g_list_append(protocol->account_options, option); */ option = purple_account_option_bool_new(_("Use SSL"), "ssl", FALSE); protocol->account_options = g_list_append(protocol->account_options, option); #ifdef HAVE_CYRUS_SASL option = purple_account_option_bool_new(_("Authenticate with SASL"), "sasl", FALSE); protocol->account_options = g_list_append(protocol->account_options, option); option = purple_account_option_bool_new( _("Allow plaintext SASL auth over unencrypted connection"), "auth_plain_in_clear", FALSE); protocol->account_options = g_list_append(protocol->account_options, option); #endif } static void irc_protocol_class_init(IRCProtocolClass *klass) { PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass); protocol_class->login = irc_login; protocol_class->close = irc_close; protocol_class->status_types = irc_status_types; protocol_class->list_icon = irc_blist_icon; } static void irc_protocol_class_finalize(G_GNUC_UNUSED IRCProtocolClass *klass) { } static void irc_protocol_client_iface_init(PurpleProtocolClientInterface *client_iface) { client_iface->get_actions = irc_get_actions; client_iface->normalize = purple_normalize_nocase; client_iface->get_max_message_size = irc_get_max_message_size; } static void irc_protocol_server_iface_init(PurpleProtocolServerInterface *server_iface) { server_iface->set_status = irc_set_status; server_iface->get_info = irc_get_info; server_iface->add_buddy = irc_add_buddy; server_iface->remove_buddy = irc_remove_buddy; server_iface->keepalive = irc_keepalive; server_iface->send_raw = irc_send_raw; } static void irc_protocol_im_iface_init(PurpleProtocolIMInterface *im_iface) { im_iface->send = irc_im_send; } static void irc_protocol_chat_iface_init(PurpleProtocolChatInterface *chat_iface) { chat_iface->info = irc_chat_join_info; chat_iface->info_defaults = irc_chat_info_defaults; chat_iface->join = irc_chat_join; chat_iface->get_name = irc_get_chat_name; chat_iface->invite = irc_chat_invite; chat_iface->leave = irc_chat_leave; chat_iface->send = irc_chat_send; chat_iface->set_topic = irc_chat_set_topic; } static void irc_protocol_roomlist_iface_init(PurpleProtocolRoomlistInterface *roomlist_iface) { roomlist_iface->get_list = irc_roomlist_get_list; roomlist_iface->cancel = irc_roomlist_cancel; } static void irc_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface) { xfer_iface->send_file = irc_dccsend_send_file; xfer_iface->new_xfer = irc_dccsend_new_xfer; } G_DEFINE_DYNAMIC_TYPE_EXTENDED( IRCProtocol, irc_protocol, PURPLE_TYPE_PROTOCOL, 0, G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CLIENT, irc_protocol_client_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_SERVER, irc_protocol_server_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_IM, irc_protocol_im_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CHAT, irc_protocol_chat_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_ROOMLIST, irc_protocol_roomlist_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_XFER, irc_protocol_xfer_iface_init)); static PurplePluginInfo * plugin_query(GError **error) { return purple_plugin_info_new( "id", "prpl-irc", "name", "IRC Protocol", "version", DISPLAY_VERSION, "category", N_("Protocol"), "summary", N_("IRC Protocol Plugin"), "description", N_("The IRC Protocol Plugin that Sucks Less"), "website", PURPLE_WEBSITE, "abi-version", PURPLE_ABI_VERSION, "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL | PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD, NULL ); } static gboolean plugin_load(PurplePlugin *plugin, GError **error) { irc_protocol_register_type(G_TYPE_MODULE(plugin)); irc_xfer_register(G_TYPE_MODULE(plugin)); _irc_protocol = purple_protocols_add(IRC_TYPE_PROTOCOL, error); if (!_irc_protocol) return FALSE; purple_prefs_remove("/plugins/prpl/irc/quitmsg"); purple_prefs_remove("/plugins/prpl/irc"); irc_register_commands(); purple_signal_register(_irc_protocol, "irc-sending-text", purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2, PURPLE_TYPE_CONNECTION, G_TYPE_POINTER); /* pointer to a string */ purple_signal_register(_irc_protocol, "irc-receiving-text", purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2, PURPLE_TYPE_CONNECTION, G_TYPE_POINTER); /* pointer to a string */ purple_signal_connect(purple_get_core(), "uri-handler", plugin, PURPLE_CALLBACK(irc_uri_handler), NULL); return TRUE; } static gboolean plugin_unload(PurplePlugin *plugin, GError **error) { irc_unregister_commands(); purple_signal_disconnect(purple_get_core(), "uri-handler", plugin, PURPLE_CALLBACK(irc_uri_handler)); if (!purple_protocols_remove(_irc_protocol, error)) return FALSE; return TRUE; } PURPLE_PLUGIN_INIT(irc, plugin_query, plugin_load, plugin_unload);