Tue, 15 Jul 2025 00:49:09 -0500
Add Purple.ConversationManager.get_all_for_account
This method gets a list of all conversations belonging to an account which is
a pretty common use case and we had a number of places where we were doing
this build/check manually and this just helps avoid that.
This also removed Purple.ConversationManager.get_all as
Purple.ConversationManager implements Gio.ListModel so it wasn't necessary.
Testing Done:
Opened a conversation the echo user and then deleted my demo account and verified that the window was closed.
Also called in the turtles.
Reviewed at https://reviews.imfreedom.org/r/4065/
/* * Purple - Internet Messaging Library * Copyright (C) Pidgin Developers <devel@pidgin.im> * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This library 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 library 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 library; if not, see <https://www.gnu.org/licenses/>. */ #include <glib/gi18n-lib.h> #include <pango/pango.h> #include "purpleircv3messagehandlers.h" #include "purpleircv3connection.h" #include "purpleircv3core.h" #include "purpleircv3ctcphandlers.h" /****************************************************************************** * Helpers *****************************************************************************/ static PurpleConversationMember * purple_ircv3_add_contact_to_conversation(PurpleContact *contact, PurpleConversation *conversation, gboolean announce) { PurpleContactInfo *info = PURPLE_CONTACT_INFO(contact); PurpleConversationMember *member = NULL; PurpleConversationMembers *members = NULL; members = purple_conversation_get_members(conversation); member = purple_conversation_members_find_member(members, info); if(!PURPLE_IS_CONVERSATION_MEMBER(member)) { char *message = NULL; if(announce) { message = g_strdup_printf(_("%s has joined %s"), purple_contact_info_get_sid(info), purple_conversation_get_title_for_display(conversation)); } member = purple_conversation_members_add_member(members, info, announce, message); g_clear_pointer(&message, g_free); } return member; } static PurpleBadge * purple_ircv3_badge_for_prefix(const char prefix) { PurpleBadge *badge = NULL; PurpleBadgeManager *manager = NULL; const char *description = NULL; const char *id = NULL; const char *mnemonic = NULL; int priority = 0; manager = purple_badge_manager_get_default(); switch(prefix) { case '~': description = _("Founder"); id = "ircv3-badge-founder"; priority = 500; mnemonic = "~"; break; case '&': description = _("Protected"); id = "ircv3-badge-protected"; priority = 400; mnemonic = "&"; break; case '@': description = _("Operator"); id = "ircv3-badge-operator"; priority = 300; mnemonic = "@"; break; case '%': description = _("Half Operator"); id = "ircv3-badge-halfop"; priority = 200; mnemonic = "%%"; break; case '+': description = _("Voice"); id = "ircv3-badge-voice"; priority = 100; mnemonic = "+"; break; } if(id == NULL) { return NULL; } badge = purple_badge_manager_find(manager, id); if(!PURPLE_IS_BADGE(badge)) { badge = purple_badge_new(id, priority, id, mnemonic); if(!purple_strempty(description)) { purple_badge_set_description(badge, description); } purple_badge_manager_add(manager, badge); /* This is transfer none and the manager has a reference, so we unref * our reference to the badge. */ g_object_unref(badge); } return badge; } static void purple_ircv3_add_badge_to_member(PurpleConversationMember *member, IbisClient *client, const char prefix) { PurpleBadge *badge = NULL; g_return_if_fail(PURPLE_IS_CONVERSATION_MEMBER(member)); g_return_if_fail(IBIS_IS_CLIENT(client)); badge = purple_ircv3_badge_for_prefix(prefix); if(PURPLE_IS_BADGE(badge)) { PurpleBadges *badges = NULL; badges = purple_conversation_member_get_badges(member); purple_badges_add_badge(badges, badge); } } static void purple_ircv3_remove_badge_from_member(PurpleConversationMember *member, IbisClient *client, const char prefix) { PurpleBadge *badge = NULL; g_return_if_fail(PURPLE_IS_CONVERSATION_MEMBER(member)); g_return_if_fail(IBIS_IS_CLIENT(client)); badge = purple_ircv3_badge_for_prefix(prefix); if(PURPLE_IS_BADGE(badge)) { PurpleBadges *badges = purple_conversation_member_get_badges(member); /* I know this is gross, but we need to rework badges here and I wanted * to get this done sooner rather than later. * -- gk 2025-03-14 */ purple_badges_remove_badge(badges, purple_badge_get_id(badge)); } } static void purple_ircv3_add_badges_to_member(PurpleConversationMember *member, IbisClient *client, const char *nick) { char *prefixes = NULL; g_return_if_fail(PURPLE_IS_CONVERSATION_MEMBER(member)); g_return_if_fail(IBIS_IS_CLIENT(client)); prefixes = ibis_client_get_source_prefix(client, nick); if(purple_strempty(prefixes)) { return; } for(guint i = 0; prefixes[i] != '\0'; i++) { purple_ircv3_add_badge_to_member(member, client, prefixes[i]); } g_free(prefixes); } /****************************************************************************** * General Commands *****************************************************************************/ gboolean purple_ircv3_message_handler_away(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleContact *contact = NULL; PurplePresence *presence = NULL; GStrv params = NULL; char *away_message = NULL; contact = purple_ircv3_connection_find_or_create_contact(connection, message); presence = purple_contact_info_get_presence(PURPLE_CONTACT_INFO(contact)); /* Figure out if we have a message. */ params = ibis_message_get_params(message); if(params != NULL) { away_message = g_strjoinv(" ", params); } /* We have a message so we need to set it and possibly set the presence to * away. */ if(!purple_strempty(away_message)) { purple_presence_set_message(presence, away_message); purple_presence_set_primitive(presence, PURPLE_PRESENCE_PRIMITIVE_AWAY); } else { purple_presence_set_message(presence, NULL); purple_presence_set_primitive(presence, PURPLE_PRESENCE_PRIMITIVE_AVAILABLE); } g_clear_pointer(&away_message, g_free); return TRUE; } gboolean purple_ircv3_message_handler_join(IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleContact *contact = NULL; PurpleConversation *conversation = NULL; IbisMessage *who_message = NULL; GStrv params = NULL; const char *conversation_name = NULL; contact = purple_ircv3_connection_find_or_create_contact(connection, message); params = ibis_message_get_params(message); /* A normal join command has the channel as the only parameter. */ if(g_strv_length(params) == 1) { conversation_name = params[0]; } else { /* TODO: write this to join to the status window saying we didn't know * how to parse it. */ return TRUE; } conversation = purple_ircv3_connection_find_or_create_conversation(connection, conversation_name); purple_ircv3_add_contact_to_conversation(contact, conversation, TRUE); /* Now fire off a who message to sync the conversation. */ who_message = ibis_message_new(IBIS_MSG_WHO); ibis_message_set_params(who_message, conversation_name, NULL); ibis_client_write(client, who_message); return TRUE; } gboolean purple_ircv3_message_handler_part(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleAccount *account = NULL; PurpleContact *contact = NULL; PurpleConversation *conversation = NULL; PurpleConversationManager *manager = NULL; PurpleConversationMembers *members = NULL; GStrv params = NULL; guint n_params = 0; char *reason = NULL; const char *conversation_name = NULL; params = ibis_message_get_params(message); n_params = g_strv_length(params); if(n_params == 0) { /* TODO: mention unparsable message in the status window. */ return TRUE; } /* TODO: The spec says servers _SHOULD NOT_ send a comma separated list of * channels, but we should support that at some point just in case. */ conversation_name = params[0]; account = purple_connection_get_account(PURPLE_CONNECTION(connection)); manager = purple_conversation_manager_get_default(); conversation = purple_conversation_manager_find_with_id(manager, account, conversation_name); if(!PURPLE_IS_CONVERSATION(conversation)) { /* TODO: write status message unknown channel. */ return TRUE; } members = purple_conversation_get_members(conversation); /* We do want to find or create the contact, even on a part, because we * could have connected to a BNC and we weren't told about the contact yet. */ contact = purple_ircv3_connection_find_or_create_contact(connection, message); /* If a part message was given, join the remaining parameters with a space. */ if(n_params > 1) { char *part_message = NULL; part_message = g_strjoinv(" ", params + 1); reason = g_strdup_printf(_("%s has left %s (%s)"), purple_contact_info_get_sid(PURPLE_CONTACT_INFO(contact)), purple_conversation_get_title_for_display(conversation), part_message); } else { reason = g_strdup_printf(_("%s has left %s"), purple_contact_info_get_sid(PURPLE_CONTACT_INFO(contact)), purple_conversation_get_title_for_display(conversation)); } purple_conversation_members_remove_member(members, PURPLE_CONTACT_INFO(contact), TRUE, reason); g_clear_pointer(&reason, g_free); return TRUE; } gboolean purple_ircv3_message_handler_namreply(IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleConversation *conversation = NULL; GStrv params = NULL; GStrv nicks = NULL; const char *target = NULL; params = ibis_message_get_params(message); if(params == NULL) { g_warning("namreply received with no parameters"); return FALSE; } if(g_strv_length(params) != 4) { char *body = g_strjoinv(" ", params); g_warning("unknown namreply format: '%s'", body); g_free(body); return FALSE; } /* params[0] holds nick of the user and params[1] holds the channel type * (public/private) but we don't care about either of these. */ target = params[2]; if(!ibis_client_is_channel(client, target)) { g_warning("received namreply for '%s' which is not a channel.", target); return FALSE; } conversation = purple_ircv3_connection_find_or_create_conversation(connection, target); /* Split the last parameter on space to get a list of all the nicks. */ nicks = g_strsplit(params[3], " ", -1); if(nicks != NULL) { PurpleAccount *account = NULL; PurpleConnection *purple_connection = NULL; PurpleContactManager *manager = purple_contact_manager_get_default(); PurpleConversationMembers *existing_members = NULL; PurpleConversationMembers *new_members = NULL; const char *active_nick = NULL; purple_connection = PURPLE_CONNECTION(connection); account = purple_connection_get_account(purple_connection); existing_members = purple_conversation_get_members(conversation); new_members = purple_conversation_members_new(); /* We don't want to add ourselves and we're already in the list. */ active_nick = ibis_client_get_active_nick(client); for(guint i = 0; i < g_strv_length(nicks); i++) { PurpleContact *contact = NULL; PurpleConversationMember *member = NULL; const char *nick = nicks[i]; char *stripped = NULL; stripped = ibis_client_strip_source_prefix(client, nick); if(purple_strequal(stripped, active_nick)) { g_free(stripped); continue; } contact = purple_contact_manager_find_with_id(manager, account, stripped); if(!PURPLE_IS_CONTACT(contact)) { contact = purple_contact_new(account, stripped); purple_contact_info_set_username(PURPLE_CONTACT_INFO(contact), stripped); purple_contact_manager_add(manager, contact); } /* Check if the member is already in the existing members list. * This can happen if the user sends a NAMES command and surely * other ways are possible. */ member = purple_conversation_members_find_member(existing_members, PURPLE_CONTACT_INFO(contact)); /* If the member doesn't exist, add them. */ if(!PURPLE_IS_CONVERSATION_MEMBER(member)) { member = purple_conversation_members_add_member(new_members, PURPLE_CONTACT_INFO(contact), FALSE, NULL); } purple_ircv3_add_badges_to_member(member, client, nick); g_free(stripped); g_clear_object(&contact); } purple_conversation_members_extend(existing_members, new_members); } g_strfreev(nicks); return TRUE; } gboolean purple_ircv3_message_handler_tagmsg(IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleContact *contact = NULL; PurpleConversation *conversation = NULL; GStrv params = NULL; IbisTags *tags = NULL; const char *target = NULL; const char *value = NULL; params = ibis_message_get_params(ibis_message); tags = ibis_message_get_tags(ibis_message); if(params == NULL) { g_warning("tagmsg received with no parameters"); return FALSE; } if(g_strv_length(params) != 1) { char *body = g_strjoinv(" ", params); g_warning("unknown tagmsg message format: '%s'", body); g_free(body); return FALSE; } /* Find or create the contact. */ contact = purple_ircv3_connection_find_or_create_contact(connection, ibis_message); /* Find or create the conversation. */ target = params[0]; if(!ibis_client_is_channel(client, target)) { target = purple_contact_info_get_id(PURPLE_CONTACT_INFO(contact)); } conversation = purple_ircv3_connection_find_or_create_conversation(connection, target); purple_ircv3_add_contact_to_conversation(contact, conversation, FALSE); /* Handle typing notifications. */ value = ibis_tags_lookup(tags, IBIS_TAG_TYPING); if(!purple_strempty(value)) { PurpleConversationMember *member = NULL; PurpleConversationMembers *members = NULL; PurpleTypingState state = PURPLE_TYPING_STATE_NONE; guint timeout = 1; members = purple_conversation_get_members(conversation); member = purple_conversation_members_find_member(members, PURPLE_CONTACT_INFO(contact)); if(purple_strequal(value, IBIS_TYPING_ACTIVE)) { state = PURPLE_TYPING_STATE_TYPING; timeout = 6; } else if(purple_strequal(value, IBIS_TYPING_PAUSED)) { state = PURPLE_TYPING_STATE_PAUSED; timeout = 30; } purple_conversation_member_set_typing_state(member, state, timeout); } return TRUE; } gboolean purple_ircv3_message_handler_privmsg(IbisClient *client, const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleContact *contact = NULL; PurpleConversation *conversation = NULL; PurpleConversationMember *member = NULL; PurpleMessage *message = NULL; GDateTime *dt = NULL; IbisCTCPMessage *ctcp_message = NULL; IbisTags *tags = NULL; GStrv params = NULL; const char *target = NULL; gboolean announce = TRUE; params = ibis_message_get_params(ibis_message); ctcp_message = ibis_message_get_ctcp_message(ibis_message); tags = ibis_message_get_tags(ibis_message); if(params == NULL) { g_warning("privmsg received with no parameters"); return FALSE; } if(g_strv_length(params) != 2) { char *body = g_strjoinv(" ", params); g_warning("unknown privmsg message format: '%s'", body); g_free(body); return FALSE; } /* Find or create the contact. */ contact = purple_ircv3_connection_find_or_create_contact(connection, ibis_message); target = params[0]; if(!ibis_client_is_channel(client, target)) { target = purple_contact_info_get_id(PURPLE_CONTACT_INFO(contact)); } if(!ibis_client_get_registered(client)) { conversation = purple_ircv3_connection_get_status_conversation(connection); announce = FALSE; } if(IBIS_IS_CTCP_MESSAGE(ctcp_message)) { if(ibis_ctcp_message_is_command(ctcp_message, IBIS_CTCP_ACTION)) { GStrv ctcp_params = NULL; char *ctcp_body = NULL; char *stripped = NULL; if(!PURPLE_IS_CONVERSATION(conversation)) { conversation = purple_ircv3_connection_find_or_create_conversation(connection, target); } member = purple_ircv3_add_contact_to_conversation(contact, conversation, announce); ctcp_params = ibis_ctcp_message_get_params(ctcp_message); ctcp_body = g_strjoinv(" ", ctcp_params); stripped = ibis_formatting_strip(ctcp_body); g_free(ctcp_body); message = purple_message_new(member, stripped); g_free(stripped); purple_message_set_action(message, TRUE); } else { char *body = NULL; announce = FALSE; if(!PURPLE_IS_CONVERSATION(conversation)) { conversation = purple_ircv3_connection_get_status_conversation(connection); } member = purple_ircv3_add_contact_to_conversation(contact, conversation, announce); body = g_strdup_printf(_("requested CTCP '%s' (to %s) from %s"), ibis_ctcp_message_get_command(ctcp_message), params[0], purple_contact_info_get_id(PURPLE_CONTACT_INFO(contact))); message = purple_message_new(member, body); purple_message_set_event(message, TRUE); g_free(body); } } else { PangoAttrList *attrs = NULL; char *stripped = NULL; /* If we received this message before registration has completed, * conversation will be set to the status conversation. */ if(!PURPLE_IS_CONVERSATION(conversation)) { conversation = purple_ircv3_connection_find_or_create_conversation(connection, target); } member = purple_ircv3_add_contact_to_conversation(contact, conversation, announce); stripped = ibis_formatting_parse(params[1], &attrs); message = purple_message_new(member, stripped); purple_message_set_attributes(message, attrs); g_clear_pointer(&stripped, g_free); g_clear_pointer(&attrs, pango_attr_list_unref); } if(purple_strequal(command, IBIS_MSG_NOTICE)) { purple_message_set_notice(message, TRUE); } if(IBIS_IS_TAGS(tags)) { const char *raw_tag = NULL; /* Grab the msgid if one was provided. */ raw_tag = ibis_tags_lookup(tags, "msgid"); if(!purple_strempty(raw_tag)) { purple_message_set_id(message, raw_tag); } /* Determine the timestamp of the message. */ raw_tag = ibis_tags_lookup(tags, "time"); if(!purple_strempty(raw_tag)) { GTimeZone *tz = g_time_zone_new_utc(); dt = g_date_time_new_from_iso8601(raw_tag, tz); g_time_zone_unref(tz); purple_message_set_timestamp(message, dt); g_date_time_unref(dt); } } /* If the server didn't provide a time, use the current local time. */ if(dt == NULL) { purple_message_set_timestamp_now(message); } purple_conversation_write_message(conversation, message); g_clear_object(&message); /* If the message contained a CTCP message and was a PRIVMSG, we then need * to attempt to handle it. */ if(IBIS_IS_CTCP_MESSAGE(ctcp_message) && purple_strequal(command, IBIS_MSG_PRIVMSG)) { purple_ircv3_ctcp_handler(connection, client, ibis_message); } return TRUE; } gboolean purple_ircv3_message_handler_topic(G_GNUC_UNUSED IbisClient *client, const char *command, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleConversation *conversation = NULL; GStrv params = NULL; const char *channel = NULL; const char *topic = NULL; guint n_params = 0; params = ibis_message_get_params(message); n_params = g_strv_length(params); if(purple_strequal(command, IBIS_MSG_TOPIC)) { if(n_params != 2) { g_message("received TOPIC with %u parameters, expected 2", n_params); return FALSE; } channel = params[0]; topic = params[1]; } else if(purple_strequal(command, IBIS_RPL_NOTOPIC)) { if(n_params != 3) { g_message("received RPL_NOTOPIC with %u parameters, expected 3", n_params); return FALSE; } channel = params[1]; topic = ""; } else if(purple_strequal(command, IBIS_RPL_TOPIC)) { if(n_params != 3) { g_message("received RPL_TOPIC with %u parameters, expected 3", n_params); return FALSE; } channel = params[1]; topic = params[2]; } else { g_message("unexpected command %s", command); return FALSE; } conversation = purple_ircv3_connection_find_or_create_conversation(connection, channel); if(!PURPLE_IS_CONVERSATION(conversation)) { g_message("failed to find or create channel '%s'", channel); return FALSE; } purple_conversation_set_topic(conversation, topic); return TRUE; } gboolean purple_ircv3_message_handler_whotopic(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleConversation *conversation = NULL; PurpleContact *contact = NULL; GDateTime *timestamp = NULL; GObject *obj = NULL; GStrv params = NULL; char *nick = NULL; guint n_params = 0; params = ibis_message_get_params(message); n_params = g_strv_length(params); /* The 333 whotopic message has parameters of `client channel source * timestamp`. */ if(n_params != 4) { g_message("received 333 with %u parameters, expected 4", n_params); return FALSE; } conversation = purple_ircv3_connection_find_or_create_conversation(connection, params[1]); if(!PURPLE_IS_CONVERSATION(conversation)) { g_message("failed to find or create channel '%s'", params[1]); return FALSE; } ibis_source_parse(params[2], &nick, NULL, NULL); contact = purple_ircv3_connection_find_or_create_contact_from_nick(connection, nick); g_free(nick); timestamp = g_date_time_new_from_unix_utc(atoi(params[3])); obj = G_OBJECT(conversation); g_object_freeze_notify(obj); purple_conversation_set_topic_author(conversation, PURPLE_CONTACT_INFO(contact)); purple_conversation_set_topic_updated(conversation, timestamp); g_object_thaw_notify(obj); g_date_time_unref(timestamp); return TRUE; } gboolean purple_ircv3_message_handler_channel_url(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleConversation *conversation = NULL; GStrv params = NULL; guint n_params = 0; params = ibis_message_get_params(message); n_params = g_strv_length(params); /* The 328 numeric has parameters of `client channel :url`. */ if(n_params != 3) { g_message("received 328 with %u parameters, expected 3", n_params); return FALSE; } conversation = purple_ircv3_connection_find_or_create_conversation(connection, params[1]); if(!PURPLE_IS_CONVERSATION(conversation)) { g_message("failed to find or create channel '%s'", params[1]); return FALSE; } purple_conversation_set_url(conversation, params[2]); return TRUE; } gboolean purple_ircv3_message_handler_quit(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *v3_connection = data; PurpleAccount *account = NULL; PurpleConnection *connection = data; PurpleContact *contact = NULL; PurpleContactInfo *info = NULL; PurpleConversationManager *manager = NULL; PurplePresence *presence = NULL; GListModel *conversations = NULL; GStrv params = NULL; guint n_params = 0; char *message = NULL; char *reason = NULL; guint n_items = 0; params = ibis_message_get_params(ibis_message); n_params = g_strv_length(params); contact = purple_ircv3_connection_find_or_create_contact(v3_connection, ibis_message); info = PURPLE_CONTACT_INFO(contact); if(n_params > 0) { reason = g_strjoinv(" ", params); message = g_strdup_printf("%s has quit (%s)", purple_contact_info_get_sid(info), reason); } else { message = g_strdup_printf("%s has quit", purple_contact_info_get_sid(info)); } /* Update the presence to offline and if they provided a quit message, set * it as the presence message. */ presence = purple_contact_info_get_presence(PURPLE_CONTACT_INFO(contact)); purple_presence_set_message(presence, reason); purple_presence_set_primitive(presence, PURPLE_PRESENCE_PRIMITIVE_OFFLINE); manager = purple_conversation_manager_get_default(); account = purple_connection_get_account(connection); conversations = purple_conversation_manager_get_all_for_account(manager, account); n_items = g_list_model_get_n_items(G_LIST_MODEL(conversations)); for(guint i = 0; i < n_items; i++) { PurpleConversation *conversation = NULL; PurpleConversationMembers *members = NULL; conversation = g_list_model_get_item(G_LIST_MODEL(conversations), i); members = purple_conversation_get_members(conversation); purple_conversation_members_remove_member(members, info, TRUE, message); g_clear_object(&conversation); } g_clear_object(&conversations); g_clear_pointer(&message, g_free); g_clear_pointer(&reason, g_free); return TRUE; } gboolean purple_ircv3_message_handler_nick(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleContact *contact = NULL; PurpleContactInfo *info = NULL; IbisTags *tags = NULL; GStrv params = NULL; guint n_params = 0; char *new_source = NULL; char *user = NULL; char *host = NULL; const char *source = NULL; const char *nick = NULL; params = ibis_message_get_params(ibis_message); n_params = g_strv_length(params); if(n_params != 1) { g_message("received NICK with %d params, expected 1", n_params); return FALSE; } nick = params[0]; source = ibis_message_get_source(ibis_message); ibis_source_parse(source, NULL, &user, &host); new_source = ibis_source_serialize(nick, user, host); g_clear_pointer(&user, g_free); g_clear_pointer(&host, g_free); contact = purple_ircv3_connection_find_or_create_contact(connection, ibis_message); info = PURPLE_CONTACT_INFO(contact); /* If the account tag doesn't exist, we need to update the id property of * the contact. */ tags = ibis_message_get_tags(ibis_message); if(!ibis_tags_exists(tags, IBIS_TAG_ACCOUNT)) { purple_contact_info_set_id(info, nick); } purple_contact_info_set_display_name(info, nick); purple_contact_info_set_sid(info, new_source); g_clear_pointer(&new_source, g_free); return TRUE; } gboolean purple_ircv3_message_handler_error(IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *v3_connection = data; GError *error = NULL; GStrv params = NULL; guint n_params = 0; char *reason = NULL; params = ibis_message_get_params(ibis_message); n_params = g_strv_length(params); if(n_params > 0) { reason = g_strjoinv(" ", params); } else { reason = g_strdup(_("unknown error")); } purple_ircv3_connection_write_status_message(v3_connection, ibis_message, TRUE, FALSE); error = g_error_new_literal(PURPLE_IRCV3_DOMAIN, 0, reason); g_clear_pointer(&reason, g_free); ibis_client_stop(client, error); return TRUE; } gboolean purple_ircv3_message_handler_wallops(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *v3_connection = data; PurpleAccount *account = NULL; PurpleConnection *connection = data; PurpleContact *contact = NULL; PurpleContactInfo *info = NULL; PurpleNotification *notification = NULL; PurpleNotificationManager *manager = NULL; GStrv params = NULL; char *wallops_title = NULL; guint n_params = 0; params = ibis_message_get_params(ibis_message); n_params = g_strv_length(params); if(n_params != 1) { g_message("received WALLOPS with %u params, expected 1", n_params); return FALSE; } contact = purple_ircv3_connection_find_or_create_contact(v3_connection, ibis_message); info = PURPLE_CONTACT_INFO(contact); wallops_title = g_strdup_printf(_("WALLOPS from %s"), purple_contact_info_get_name_for_display(info)); notification = purple_notification_new(NULL, wallops_title); g_free(wallops_title); account = purple_connection_get_account(connection); purple_notification_set_account(notification, account); purple_notification_set_subtitle(notification, params[0]); purple_notification_set_icon_name(notification, PURPLE_IRCV3_ICON_NAME); manager = purple_notification_manager_get_default(); purple_notification_manager_add(manager, notification); g_clear_object(¬ification); return TRUE; } gboolean purple_ircv3_message_handler_kick(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *v3_connection = data; PurpleAccount *account = NULL; PurpleContact *kicker = NULL; PurpleContact *kickee = NULL; PurpleContactInfo *me = NULL; PurpleConversation *conversation = NULL; GStrv params = NULL; char *reason = NULL; const char *kickee_id = NULL; const char *me_id = NULL; guint n_params = 0; params = ibis_message_get_params(ibis_message); n_params = g_strv_length(params); if(n_params < 2) { g_message("received KICK with %u params, need at least 2", n_params); return FALSE; } account = purple_connection_get_account(PURPLE_CONNECTION(v3_connection)); me = purple_account_get_contact_info(account); me_id = purple_contact_info_get_id(me); kicker = purple_ircv3_connection_find_or_create_contact(v3_connection, ibis_message); conversation = purple_ircv3_connection_find_or_create_conversation(v3_connection, params[0]); kickee = purple_ircv3_connection_find_or_create_contact_from_nick(v3_connection, params[1]); kickee_id = purple_contact_info_get_id(PURPLE_CONTACT_INFO(kickee)); if(n_params > 2) { reason = g_strjoinv(" ", params + 2); } else { reason = g_strdup(_("no reason")); } if(purple_strequal(kickee_id, me_id)) { GError *error = NULL; error = g_error_new(PURPLE_IRCV3_DOMAIN, 0, _("You were kicked from %s by %s: %s"), params[0], purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(kicker)), reason); purple_conversation_set_error(conversation, error); g_clear_error(&error); purple_conversation_set_online(conversation, FALSE); } else { PurpleConversationMembers *members = NULL; char *contents = NULL; contents = g_strdup_printf(_("%s kicked %s from %s: %s"), purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(kicker)), purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(kickee)), params[0], reason); members = purple_conversation_get_members(conversation); purple_conversation_members_remove_member(members, PURPLE_CONTACT_INFO(kickee), TRUE, contents); g_free(contents); } g_free(reason); return TRUE; } gboolean purple_ircv3_message_handler_mode(IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *v3_connection = data; PurpleContact *contact = NULL; PurpleConversation *conversation = NULL; PurpleConversationMember *author = NULL; PurpleMessage *message = NULL; IbisModeChange *mode_changes = NULL; GError *error = NULL; GStrv params = NULL; char *contents = NULL; char *parts = NULL; guint n_params = 0; guint n_mode_changes = 0; const char *target = NULL; params = ibis_message_get_params(ibis_message); n_params = g_strv_length(params); if(n_params < 2) { g_message("received MODE with %u params, need at least 2", n_params); return FALSE; } target = params[0]; if(!ibis_client_is_channel(client, target)) { return FALSE; } conversation = purple_ircv3_connection_find_or_create_conversation(v3_connection, target); contact = purple_ircv3_connection_find_or_create_contact(v3_connection, ibis_message); author = purple_conversation_find_or_add_member(conversation, PURPLE_CONTACT_INFO(contact), FALSE, NULL); mode_changes = ibis_client_parse_mode_string(client, params[1], params + 2, &n_mode_changes, &error); if(error != NULL) { g_warning("failed to parse mode string: %s", error->message); g_clear_error(&error); return FALSE; } for(guint i = 0; i < n_mode_changes; i++) { PurpleContact *subject = NULL; PurpleConversationMember *member = NULL; IbisModeChange change = mode_changes[i]; char prefix = '\0'; prefix = ibis_client_get_prefix_for_mode(client, change.mode); if(prefix == '\0') { continue; } subject = purple_ircv3_connection_find_or_create_contact_from_nick(v3_connection, change.parameter); member = purple_conversation_find_or_add_member(conversation, PURPLE_CONTACT_INFO(subject), FALSE, NULL); if(change.add) { purple_ircv3_add_badge_to_member(member, client, prefix); } else { purple_ircv3_remove_badge_from_member(member, client, prefix); } } parts = g_strjoinv(" ", params + 1); contents = g_strdup_printf(_("mode %s"), parts); g_free(parts); message = g_object_new( PURPLE_TYPE_MESSAGE, "author", author, "contents", contents, "event", TRUE, NULL); g_free(contents); purple_conversation_write_message(conversation, message); g_clear_object(&message); return TRUE; } gboolean purple_ircv3_message_handler_whoreply(IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *v3_connection = data; PurpleContact *contact = NULL; PurplePresence *presence = NULL; GStrv params = NULL; char *sid = NULL; guint n_params = 0; const char *flags = NULL; params = ibis_message_get_params(ibis_message); n_params = g_strv_length(params); /* A standard RPL_WHOREPLY has 7 parameters: * * <client> <channel> <username> <host> <server> <nick> <flags> :<hopcount> <realname> * * We ignore hopcount and realname for now as we don't have a good use case * to use it. */ if(n_params < 7) { g_message("received RPL_WHOREPLY with %u params, need at least 7", n_params); return FALSE; } /* Find the contact and update everything for it. */ contact = purple_ircv3_connection_find_or_create_contact_from_nick(v3_connection, params[5]); sid = g_strdup_printf("%s!%s@%s", params[5], params[2], params[3]); purple_contact_info_set_sid(PURPLE_CONTACT_INFO(contact), sid); /* Process the flags starting with presence. */ flags = params[6]; presence = purple_contact_info_get_presence(PURPLE_CONTACT_INFO(contact)); if(flags[0] == 'G') { purple_presence_set_primitive(presence, PURPLE_PRESENCE_PRIMITIVE_AWAY); } else { purple_presence_set_primitive(presence, PURPLE_PRESENCE_PRIMITIVE_AVAILABLE); } /* Increment flags to the next value. */ flags += 1; /* Check if a server admin. */ if(flags[0] == '*') { /* We'll need to create badge for this at some point */ flags += 1; } /* If we got a channel update the member's prefix (badges). */ if(!purple_strequal(params[1], "*")) { PurpleConversation *conversation = NULL; PurpleConversationMember *member = NULL; conversation = purple_ircv3_connection_find_or_create_conversation(v3_connection, params[1]); member = purple_conversation_find_or_add_member(conversation, PURPLE_CONTACT_INFO(contact), FALSE, NULL); /* Now run through the remaining flags looking for channel member * prefixes and adding badges for the ones we find. */ for(guint i = 0; flags[i] != '\0'; i++) { /* We abuse the fact that if a badge can't be found it is ignored * to avoid having to check the prefix before adding the badge. */ purple_ircv3_add_badge_to_member(member, client, flags[i]); } } return TRUE; } gboolean purple_ircv3_message_handler_nowaway(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleContact *contact = NULL; PurplePresence *presence = NULL; GStrv params = NULL; guint n_params = 0; params = ibis_message_get_params(ibis_message); n_params = g_strv_length(params); if(n_params < 1) { g_message("received RPL_NOWAWAY with %u params, need at least 1", n_params); return FALSE; } contact = purple_ircv3_connection_find_or_create_contact_from_nick(connection, params[0]); presence = purple_contact_info_get_presence(PURPLE_CONTACT_INFO(contact)); purple_presence_set_primitive(presence, PURPLE_PRESENCE_PRIMITIVE_AWAY); return TRUE; } gboolean purple_ircv3_message_handler_unaway(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleContact *contact = NULL; PurplePresence *presence = NULL; GStrv params = NULL; guint n_params = 0; params = ibis_message_get_params(ibis_message); n_params = g_strv_length(params); if(n_params < 1) { g_message("received RPL_UNAWAY with %u params, need at least 1", n_params); return FALSE; } contact = purple_ircv3_connection_find_or_create_contact_from_nick(connection, params[0]); presence = purple_contact_info_get_presence(PURPLE_CONTACT_INFO(contact)); purple_presence_set_primitive(presence, PURPLE_PRESENCE_PRIMITIVE_AVAILABLE); return TRUE; }