Fri, 31 May 2024 01:02:19 -0500
IRCv3: Make sure to set the title property when creating conversations
Testing Done:
Manually joined a channel and verified the title was displayed properly.
Reviewed at https://reviews.imfreedom.org/r/3207/
/* * 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 <birb.h> #include "purpleircv3connection.h" #include "purpleircv3core.h" #include "purpleircv3messagehandlers.h" enum { PROP_0, PROP_CLIENT, N_PROPERTIES, }; static GParamSpec *properties[N_PROPERTIES] = {NULL, }; typedef struct { IbisClient *client; gchar *server_name; PurpleConversation *status_conversation; } PurpleIRCv3ConnectionPrivate; G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleIRCv3Connection, purple_ircv3_connection, PURPLE_TYPE_CONNECTION, 0, G_ADD_PRIVATE_DYNAMIC(PurpleIRCv3Connection)) /****************************************************************************** * Helpers *****************************************************************************/ static void purple_ircv3_connection_add_message_handlers(PurpleIRCv3Connection *connection, IbisClient *client) { g_signal_connect_object(client, "message::" IBIS_MSG_TOPIC, G_CALLBACK(purple_ircv3_message_handler_topic), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_NOTOPIC, G_CALLBACK(purple_ircv3_message_handler_topic), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_TOPIC, G_CALLBACK(purple_ircv3_message_handler_topic), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_PRIVMSG, G_CALLBACK(purple_ircv3_message_handler_privmsg), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_NOTICE, G_CALLBACK(purple_ircv3_message_handler_privmsg), connection, G_CONNECT_DEFAULT); g_warning("added all the signal"); } static void purple_ircv3_connection_rejoin_channels(PurpleIRCv3Connection *connection) { PurpleIRCv3ConnectionPrivate *priv = NULL; PurpleAccount *account = NULL; PurpleConversationManager *manager = NULL; GList *conversations = NULL; priv = purple_ircv3_connection_get_instance_private(connection); account = purple_connection_get_account(PURPLE_CONNECTION(connection)); manager = purple_conversation_manager_get_default(); conversations = purple_conversation_manager_get_all(manager); while(conversations != NULL) { PurpleConversation *conversation = conversations->data; PurpleAccount *conv_account = NULL; conv_account = purple_conversation_get_account(conversation); if(conv_account == account) { IbisMessage *message = NULL; const char *id = purple_conversation_get_id(conversation); message = ibis_message_new(IBIS_MSG_JOIN); ibis_message_set_params(message, id, NULL); ibis_client_write(priv->client, message); } conversations = g_list_delete_link(conversations, conversations); } } /****************************************************************************** * Callbacks *****************************************************************************/ static void purple_ircv3_connection_connect_cb(GObject *source, G_GNUC_UNUSED GParamSpec *pspec, gpointer data) { PurpleConnection *connection = data; IbisClient *client = IBIS_CLIENT(source); if(ibis_client_get_connected(client)) { purple_connection_set_state(connection, PURPLE_CONNECTION_STATE_CONNECTED); /* Once reconnected, we need to rejoin any channels that the * conversation manager has for us. */ purple_ircv3_connection_rejoin_channels(PURPLE_IRCV3_CONNECTION(connection)); } else { purple_connection_set_state(connection, PURPLE_CONNECTION_STATE_DISCONNECTED); } } static void purple_ircv3_connection_error_cb(GObject *source, G_GNUC_UNUSED GParamSpec *pspec, gpointer data) { IbisClient *client = IBIS_CLIENT(source); PurpleConnection *connection = data; GError *error = NULL; error = ibis_client_get_error(client); if(error != NULL) { purple_connection_g_error(connection, error); } } /****************************************************************************** * PurpleConnection Implementation *****************************************************************************/ static gboolean purple_ircv3_connection_connect(PurpleConnection *purple_connection, GError **error) { PurpleIRCv3Connection *connection = NULL; PurpleIRCv3ConnectionPrivate *priv = NULL; PurpleAccount *account = NULL; GError *local_error = NULL; GProxyResolver *resolver = NULL; const char *password = NULL; const char *value = NULL; gint default_port = PURPLE_IRCV3_DEFAULT_TLS_PORT; gint port = 0; gboolean tls = TRUE; g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(purple_connection), FALSE); connection = PURPLE_IRCV3_CONNECTION(purple_connection); priv = purple_ircv3_connection_get_instance_private(connection); account = purple_connection_get_account(purple_connection); priv->client = ibis_client_new(); g_signal_connect_object(priv->client, "notify::connected", G_CALLBACK(purple_ircv3_connection_connect_cb), connection, G_CONNECT_DEFAULT); g_signal_connect_object(priv->client, "notify::error", G_CALLBACK(purple_ircv3_connection_error_cb), connection, G_CONNECT_DEFAULT); purple_ircv3_connection_add_message_handlers(connection, priv->client); ibis_client_set_nick(priv->client, purple_connection_get_display_name(purple_connection)); value = purple_account_get_string(account, "ident", NULL); ibis_client_set_username(priv->client, value); value = purple_account_get_string(account, "real-name", NULL); ibis_client_set_realname(priv->client, value); password = purple_account_get_string(account, "server-password", NULL); /* Turn on TLS if requested. */ tls = purple_account_get_bool(account, "use-tls", TRUE); /* If TLS is not being used, set the default port to the plain port. */ if(!tls) { default_port = PURPLE_IRCV3_DEFAULT_PLAIN_PORT; } port = purple_account_get_int(account, "port", default_port); resolver = purple_proxy_get_proxy_resolver(account, &local_error); if(local_error != NULL) { g_propagate_error(error, local_error); g_clear_object(&resolver); g_clear_object(&priv->client); return FALSE; } ibis_client_connect(priv->client, priv->server_name, port, password, tls, resolver); return TRUE; } static gboolean purple_ircv3_connection_disconnect(PurpleConnection *purple_connection, GError **error) { PurpleIRCv3Connection *connection = NULL; PurpleIRCv3ConnectionPrivate *priv = NULL; PurpleConnectionClass *parent_class = NULL; g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(purple_connection), FALSE); /* Chain up to our parent's disconnect to do initial clean up. */ parent_class = PURPLE_CONNECTION_CLASS(purple_ircv3_connection_parent_class); parent_class->disconnect(purple_connection, error); connection = PURPLE_IRCV3_CONNECTION(purple_connection); priv = purple_ircv3_connection_get_instance_private(connection); /* TODO: send QUIT command. */ g_clear_object(&priv->client); return TRUE; } /****************************************************************************** * GObject Implementation *****************************************************************************/ static void purple_ircv3_connection_get_property(GObject *obj, guint param_id, GValue *value, GParamSpec *pspec) { PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj); switch(param_id) { case PROP_CLIENT: g_value_set_object(value, purple_ircv3_connection_get_client(connection)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); break; } } static void purple_ircv3_connection_dispose(GObject *obj) { PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj); PurpleIRCv3ConnectionPrivate *priv = NULL; priv = purple_ircv3_connection_get_instance_private(connection); g_clear_object(&priv->client); g_clear_object(&priv->status_conversation); G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->dispose(obj); } static void purple_ircv3_connection_finalize(GObject *obj) { PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj); PurpleIRCv3ConnectionPrivate *priv = NULL; priv = purple_ircv3_connection_get_instance_private(connection); g_clear_pointer(&priv->server_name, g_free); G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->finalize(obj); } static void purple_ircv3_connection_constructed(GObject *obj) { PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj); PurpleIRCv3ConnectionPrivate *priv = NULL; PurpleAccount *account = NULL; PurpleContactInfo *info = NULL; PurpleConversationManager *conversation_manager = NULL; char **userparts = NULL; const char *username = NULL; char *title = NULL; G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->constructed(obj); priv = purple_ircv3_connection_get_instance_private(connection); account = purple_connection_get_account(PURPLE_CONNECTION(connection)); info = PURPLE_CONTACT_INFO(account); title = g_strdup_printf(_("status for %s"), purple_contact_info_get_username(info)); /* Split the username into nick and server and store the values. */ username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account)); userparts = g_strsplit(username, "@", 2); purple_connection_set_display_name(PURPLE_CONNECTION(connection), userparts[0]); priv->server_name = g_strdup(userparts[1]); /* Free the userparts vector. */ g_strfreev(userparts); /* Check if we have an existing status conversation. */ conversation_manager = purple_conversation_manager_get_default(); priv->status_conversation = purple_conversation_manager_find_with_id(conversation_manager, account, priv->server_name); if(!PURPLE_IS_CONVERSATION(priv->status_conversation)) { /* Create our status conversation. */ priv->status_conversation = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account, "id", priv->server_name, "name", priv->server_name, "title", title, NULL); purple_conversation_manager_register(conversation_manager, priv->status_conversation); } else { /* The conversation existed, so add a reference to it. */ g_object_ref(priv->status_conversation); } g_clear_pointer(&title, g_free); } static void purple_ircv3_connection_init(G_GNUC_UNUSED PurpleIRCv3Connection *connection) { } static void purple_ircv3_connection_class_finalize(G_GNUC_UNUSED PurpleIRCv3ConnectionClass *klass) { } static void purple_ircv3_connection_class_init(PurpleIRCv3ConnectionClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); PurpleConnectionClass *connection_class = PURPLE_CONNECTION_CLASS(klass); obj_class->get_property = purple_ircv3_connection_get_property; obj_class->constructed = purple_ircv3_connection_constructed; obj_class->dispose = purple_ircv3_connection_dispose; obj_class->finalize = purple_ircv3_connection_finalize; connection_class->connect = purple_ircv3_connection_connect; connection_class->disconnect = purple_ircv3_connection_disconnect; /** * PurpleIRCv3Connection:client: * * The [class@Ibis.Client] that this connection is using. * * Since: 3.0 */ properties[PROP_CLIENT] = g_param_spec_object( "client", NULL, NULL, IBIS_TYPE_CLIENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(obj_class, N_PROPERTIES, properties); } /****************************************************************************** * Internal API *****************************************************************************/ void purple_ircv3_connection_register(GPluginNativePlugin *plugin) { GObjectClass *hack = NULL; purple_ircv3_connection_register_type(G_TYPE_MODULE(plugin)); /* Without this hack we get some warnings about no reference on this type * when generating the gir file. However, there are no changes to the * generated gir file with or without this hack, so this is purely to get * rid of the warnings. * * - GK 2023-08-21 */ hack = g_type_class_ref(purple_ircv3_connection_get_type()); g_type_class_unref(hack); } /****************************************************************************** * Public API *****************************************************************************/ IbisClient * purple_ircv3_connection_get_client(PurpleIRCv3Connection *connection) { PurpleIRCv3ConnectionPrivate *priv = NULL; g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL); priv = purple_ircv3_connection_get_instance_private(connection); return priv->client; } void purple_ircv3_connection_add_status_message(PurpleIRCv3Connection *connection, IbisMessage *ibis_message) { PurpleIRCv3ConnectionPrivate *priv = NULL; PurpleMessage *message = NULL; GString *str = NULL; GStrv params = NULL; char *stripped = NULL; const char *command = NULL; g_return_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection)); g_return_if_fail(IBIS_IS_MESSAGE(ibis_message)); priv = purple_ircv3_connection_get_instance_private(connection); command = ibis_message_get_command(ibis_message); str = g_string_new(command); params = ibis_message_get_params(ibis_message); if(params != NULL && params[0] != NULL) { char *joined = g_strjoinv(" ", params); g_string_append_printf(str, " %s", joined); g_free(joined); } stripped = ibis_formatting_strip(str->str); g_string_free(str, TRUE); message = g_object_new( PURPLE_TYPE_MESSAGE, "author", ibis_message_get_source(ibis_message), "contents", stripped, NULL); g_free(stripped); purple_conversation_write_message(priv->status_conversation, message); g_clear_object(&message); } gboolean purple_ircv3_connection_is_channel(PurpleIRCv3Connection *connection, const char *id) { g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE); g_return_val_if_fail(id != NULL, FALSE); return (id[0] == '#'); } PurpleConversation * purple_ircv3_connection_find_or_create_conversation(PurpleIRCv3Connection *connection, const char *id) { PurpleAccount *account = NULL; PurpleConversation *conversation = NULL; PurpleConversationManager *manager = NULL; g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL); g_return_val_if_fail(id != NULL, NULL); account = purple_connection_get_account(PURPLE_CONNECTION(connection)); manager = purple_conversation_manager_get_default(); conversation = purple_conversation_manager_find_with_id(manager, account, id); if(!PURPLE_IS_CONVERSATION(conversation)) { PurpleConversationType type = PURPLE_CONVERSATION_TYPE_DM; if(purple_ircv3_connection_is_channel(connection, id)) { type = PURPLE_CONVERSATION_TYPE_CHANNEL; } conversation = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account, "id", id, "name", id, "title", id, "type", type, NULL); purple_conversation_manager_register(manager, conversation); /* The manager creates its own reference on our new conversation, so we * borrow it like we do above if it already exists. */ g_object_unref(conversation); } return conversation; } PurpleContact * purple_ircv3_connection_find_or_create_contact(PurpleIRCv3Connection *connection, const char *nick) { PurpleAccount *account = NULL; PurpleContact *contact = NULL; PurpleContactManager *manager = NULL; account = purple_connection_get_account(PURPLE_CONNECTION(connection)); manager = purple_contact_manager_get_default(); contact = purple_contact_manager_find_with_id(manager, account, nick); if(!PURPLE_IS_CONTACT(contact)) { contact = purple_contact_new(account, nick); purple_contact_info_set_username(PURPLE_CONTACT_INFO(contact), nick); purple_contact_manager_add(manager, contact); } return contact; }