Thu, 03 Jul 2025 01:56:45 -0500
Update to ibis-0.14.0
This fixes a build error on OpenBSD among other things.
Testing Done:
Called in the turtles.
Reviewed at https://reviews.imfreedom.org/r/4043/
/* * 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 <birb.h> #include <hasl.h> #include "purpleircv3connection.h" #include "purpleircv3core.h" #include "purpleircv3messagehandlers.h" #define IRCV3_STATUS_CONVERSATION_ID "@ircv3-status@" enum { PROP_0, PROP_CLIENT, N_PROPERTIES, }; static GParamSpec *properties[N_PROPERTIES] = {NULL, }; struct _PurpleIRCv3Connection { PurpleConnection parent; IbisClient *client; char *server_name; PurpleConversation *status_conversation; }; G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleIRCv3Connection, purple_ircv3_connection, PURPLE_TYPE_CONNECTION, G_TYPE_FLAG_FINAL, {}) static gboolean purple_ircv3_connection_unknown_message_cb(IbisClient *client, const char *command, IbisMessage *message, gpointer data); static gboolean purple_ircv3_connection_saslsuccess(IbisClient *client, const char *command, IbisMessage *message, gpointer data); /****************************************************************************** * Helpers *****************************************************************************/ static void purple_ircv3_connection_rejoin_channels(PurpleIRCv3Connection *connection) { PurpleAccount *account = NULL; PurpleConversationManager *manager = NULL; GList *conversations = NULL; 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); /* We set the online status and clear the error on the conversation * so that we can get any updated value if the join fails. */ purple_conversation_set_online(conversation, TRUE); purple_conversation_set_error(conversation, NULL); /* If this is not the status conversation we need to rejoin it. */ if(conversation != connection->status_conversation) { message = ibis_message_new(IBIS_MSG_JOIN); ibis_message_set_params(message, id, NULL); ibis_client_write(connection->client, message); } } conversations = g_list_delete_link(conversations, conversations); } } static inline void purple_ircv3_connection_setup_sasl(PurpleIRCv3Connection *connection, PurpleAccount *account) { HaslContext *hasl_context = NULL; const char *value = NULL; gboolean clear_text = FALSE; gboolean tls = FALSE; hasl_context = hasl_context_new(); value = purple_account_get_string(account, "sasl-login-name", NULL); if(!purple_strempty(value)) { hasl_context_set_username(hasl_context, value); } else { hasl_context_set_username(hasl_context, ibis_client_get_nick(connection->client)); /* Since the user doesn't have a SASL login name set, we'll listen for * IBIS_RPL_SASLSUCCESS and use that to set the login name to the * username in HASL which worked if the signal handler gets called. */ g_signal_connect_object(connection->client, "message::" IBIS_RPL_SASLSUCCESS, G_CALLBACK(purple_ircv3_connection_saslsuccess), connection, G_CONNECT_DEFAULT); } value = purple_connection_get_password(PURPLE_CONNECTION(connection)); hasl_context_set_password(hasl_context, value); value = purple_account_get_string(account, "sasl-mechanisms", NULL); if(!purple_strempty(value)) { hasl_context_set_allowed_mechanisms(hasl_context, value); } tls = purple_account_get_bool(account, "use-tls", TRUE); hasl_context_set_tls(hasl_context, tls); clear_text = purple_account_get_bool(account, "plain-sasl-in-clear", FALSE); hasl_context_set_allow_clear_text(hasl_context, clear_text); ibis_client_set_hasl_context(connection->client, hasl_context); g_clear_object(&hasl_context); } /** * purple_ircv3_write_server_status_message: (skip) * @connection: The instance. * @message: The message to write to the status window. * @show_command: Whether or not to display the command. * * Like purple_ircv3_connection_write_status_message() but removes the first * parameter which is the user's nick for server messages. */ static void purple_ircv3_write_server_status_message(PurpleIRCv3Connection *connection, IbisMessage *message, gboolean show_command) { GStrv original = NULL; GStrv extra_crispy = NULL; original = ibis_message_get_params(message); extra_crispy = g_strdupv(original + 1); ibis_message_set_paramsv(message, extra_crispy); g_strfreev(extra_crispy); purple_ircv3_connection_write_status_message(connection, message, show_command, FALSE); } /****************************************************************************** * Message Handlers *****************************************************************************/ static void purple_ircv3_wrote_message_echo_cb(G_GNUC_UNUSED IbisClient *client, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleAccount *account = NULL; PurpleContactInfo *info = NULL; PurpleConversationMember *author = NULL; PurpleMessage *purple_message = NULL; GStrv params = NULL; char *body = NULL; const char *command = NULL; account = purple_connection_get_account(PURPLE_CONNECTION(connection)); info = purple_account_get_contact_info(account); command = ibis_message_get_command(message); params = ibis_message_get_params(message); if(params != NULL) { char *paramsv = g_strjoinv(" ", params); body = g_strdup_printf("%s %s", command, paramsv); g_free(paramsv); } else { body = g_strdup(command); } author = purple_conversation_find_or_add_member(connection->status_conversation, info, FALSE, NULL); purple_message = purple_message_new(author, body); purple_message_set_event(purple_message, TRUE); g_clear_pointer(&body, g_free); purple_conversation_write_message(connection->status_conversation, purple_message); g_clear_object(&purple_message); } static gboolean purple_ircv3_message_handler_ignore(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, G_GNUC_UNUSED IbisMessage *message, G_GNUC_UNUSED gpointer data) { return TRUE; } static gboolean purple_ircv3_connection_saslsuccess(IbisClient *client, G_GNUC_UNUSED const char *command, G_GNUC_UNUSED IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleAccount *account = NULL; const char *value = NULL; account = purple_connection_get_account(PURPLE_CONNECTION(connection)); value = purple_account_get_string(account, "sasl-login-name", NULL); /* If the sasl-login-name is empty, we set it to the current username in * our hasl context that was used to login. */ if(purple_strempty(value)) { HaslContext *hasl_context = NULL; hasl_context = ibis_client_get_hasl_context(client); if(HASL_IS_CONTEXT(hasl_context)) { purple_account_set_string(account, "sasl-login-name", hasl_context_get_username(hasl_context)); } } /* We don't actually handle SASLSUCCESS, but we just needed to know if it * was sent. */ return FALSE; } static gboolean purple_ircv3_server_message_echo(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { purple_ircv3_connection_write_status_message(data, message, TRUE, TRUE); return FALSE; } static gboolean purple_ircv3_server_message_handler(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { GStrv original = NULL; GStrv extra_crispy = NULL; original = ibis_message_get_params(message); extra_crispy = g_strdupv(original + 1); ibis_message_set_paramsv(message, extra_crispy); g_strfreev(extra_crispy); purple_ircv3_connection_write_status_message(data, message, FALSE, FALSE); return TRUE; } static gboolean purple_ircv3_rpl_welcome_handler(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleAccount *account = NULL; PurpleContactInfo *info = NULL; GStrv params = NULL; account = purple_connection_get_account(PURPLE_CONNECTION(connection)); info = purple_account_get_contact_info(account); params = ibis_message_get_params(message); if(params != NULL) { /* Per https://modern.ircdocs.horse/#connection-registration the first * parameter of RPL_WELCOME is the nick that the server assigned us. */ purple_contact_info_set_id(info, params[0]); purple_contact_info_set_username(info, params[0]); } purple_ircv3_write_server_status_message(connection, message, FALSE); return TRUE; } static gboolean purple_ircv3_server_rpl_isupport(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *message, gpointer data) { PurpleIRCv3Connection *connection = data; purple_ircv3_write_server_status_message(connection, message, FALSE); /* We want the default handler to run which will populate the features * object on the client, so we return false here. */ return FALSE; } static gboolean purple_ircv3_server_no_motd_handler(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleContact *contact = NULL; PurpleConversationMember *author = NULL; PurpleMessage *purple_message = NULL; contact = purple_ircv3_connection_find_or_create_contact(connection, ibis_message); author = purple_conversation_find_or_add_member(connection->status_conversation, PURPLE_CONTACT_INFO(contact), FALSE, NULL); purple_message = purple_message_new(author, _("no message of the day found")); purple_conversation_write_message(connection->status_conversation, purple_message); g_clear_object(&purple_message); return TRUE; } static void purple_ircv3_connection_add_message_handlers(PurpleIRCv3Connection *connection, IbisClient *client) { g_signal_connect_object(client, "wrote-message::" IBIS_MSG_PING, G_CALLBACK(purple_ircv3_wrote_message_echo_cb), connection, 0); g_signal_connect_object(client, "wrote-message::" IBIS_MSG_PONG, G_CALLBACK(purple_ircv3_wrote_message_echo_cb), connection, 0); g_signal_connect_object(client, "message::" IBIS_MSG_PING, G_CALLBACK(purple_ircv3_server_message_echo), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_PONG, G_CALLBACK(purple_ircv3_server_message_echo), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_WELCOME, G_CALLBACK(purple_ircv3_rpl_welcome_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_YOURHOST, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_CREATED, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_MYINFO, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_ISUPPORT, G_CALLBACK(purple_ircv3_server_rpl_isupport), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_LUSERCLIENT, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_LUSEROP, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_LUSERUNKNOWN, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_LUSERCHANNELS, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_LUSERME, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_LOCALUSERS, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_GLOBALUSERS, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_MOTD, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_MOTDSTART, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_UMODEIS, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_ERR_YOUREBANNEDCREEP, G_CALLBACK(purple_ircv3_server_message_handler), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_ERR_NOMOTD, G_CALLBACK(purple_ircv3_server_no_motd_handler), connection, G_CONNECT_DEFAULT); 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_RPL_TOPICWHOTIME, G_CALLBACK(purple_ircv3_message_handler_whotopic), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_CHANNEL_URL, G_CALLBACK(purple_ircv3_message_handler_channel_url), 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_signal_connect_object(client, "message::" IBIS_MSG_TAGMSG, G_CALLBACK(purple_ircv3_message_handler_tagmsg), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_AWAY, G_CALLBACK(purple_ircv3_message_handler_away), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_NOWAWAY, G_CALLBACK(purple_ircv3_message_handler_nowaway), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_UNAWAY, G_CALLBACK(purple_ircv3_message_handler_unaway), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_JOIN, G_CALLBACK(purple_ircv3_message_handler_join), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_PART, G_CALLBACK(purple_ircv3_message_handler_part), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_NICK, G_CALLBACK(purple_ircv3_message_handler_nick), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_QUIT, G_CALLBACK(purple_ircv3_message_handler_quit), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_ERROR, G_CALLBACK(purple_ircv3_message_handler_error), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_NAMREPLY, G_CALLBACK(purple_ircv3_message_handler_namreply), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_ENDOFNAMES, G_CALLBACK(purple_ircv3_message_handler_ignore), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_WALLOPS, G_CALLBACK(purple_ircv3_message_handler_wallops), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_KICK, G_CALLBACK(purple_ircv3_message_handler_kick), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_MSG_MODE, G_CALLBACK(purple_ircv3_message_handler_mode), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_WHOREPLY, G_CALLBACK(purple_ircv3_message_handler_whoreply), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message::" IBIS_RPL_ENDOFWHO, G_CALLBACK(purple_ircv3_message_handler_ignore), connection, G_CONNECT_DEFAULT); g_signal_connect_object(client, "message", G_CALLBACK(purple_ircv3_connection_unknown_message_cb), connection, G_CONNECT_AFTER); } /****************************************************************************** * Callbacks *****************************************************************************/ static gboolean purple_ircv3_connection_unknown_message_cb(G_GNUC_UNUSED IbisClient *client, G_GNUC_UNUSED const char *command, IbisMessage *ibis_message, gpointer data) { PurpleIRCv3Connection *connection = data; PurpleContact *contact = NULL; PurpleConversationMember *author = NULL; PurpleMessage *purple_message = NULL; char *contents = NULL; contact = purple_ircv3_connection_find_or_create_contact(connection, ibis_message); author = purple_conversation_find_or_add_member(connection->status_conversation, PURPLE_CONTACT_INFO(contact), FALSE, NULL); contents = g_strdup_printf(_("unhandled message: '%s'"), ibis_message_get_raw_message(ibis_message)); purple_message = purple_message_new(author, contents); purple_conversation_write_message(connection->status_conversation, purple_message); g_clear_object(&purple_message); g_free(contents); return TRUE; } 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)) { PurpleAccount *account = purple_connection_get_account(connection); purple_account_disconnect(account); } } static void purple_ircv3_connection_registered_cb(GObject *source, G_GNUC_UNUSED GParamSpec *pspec, gpointer data) { PurpleConnection *connection = data; IbisClient *client = IBIS_CLIENT(source); if(ibis_client_get_registered(client)) { PurpleAccount *account = NULL; account = purple_connection_get_account(connection); if(!purple_account_is_connected(account)) { purple_account_connected(account); } /* Once reconnected, we need to rejoin any channels that the * conversation manager has for us. */ purple_ircv3_connection_rejoin_channels(PURPLE_IRCV3_CONNECTION(connection)); } } 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) { PurpleAccount *account = purple_connection_get_account(connection); purple_account_set_error(account, g_error_copy(error)); } } static void purple_ircv3_connection_capabilities_ready_cb(IbisCapabilities *capabilities, G_GNUC_UNUSED gpointer data) { /* account-tag just adds an account tag to everything if it's available. * The account-tag is the user's username for authentication for all users * not just the one using libpurple. */ ibis_capabilities_lookup_and_request(capabilities, IBIS_CAPABILITY_ACCOUNT_TAG); /* away-notify tells us when users in a channel go away or come back. */ ibis_capabilities_lookup_and_request(capabilities, IBIS_CAPABILITY_AWAY_NOTIFY); /* no-implicit-names tells the server to not send us the namreply message * when joining a channel. These messages are not useful to use since we * immediately send a WHO command on the channel when we join which has a * super set of the information in namreply. */ ibis_capabilities_lookup_and_request(capabilities, IBIS_CAPABILITY_NO_IMPLICIT_NAMES); } static void purple_ircv3_connection_update_status_title_cb(G_GNUC_UNUSED GObject *obj, G_GNUC_UNUSED GParamSpec *pspec, gpointer data) { PurpleIRCv3Connection *connection = data; char *title = NULL; const char *nick = NULL; const char *network = NULL; nick = ibis_client_get_active_nick(connection->client); network = ibis_client_get_network(connection->client); if(purple_strempty(network)) { network = connection->server_name; } title = g_strdup_printf(_("status %s on %s"), nick, network); purple_conversation_set_title(connection->status_conversation, title); g_free(title); } /****************************************************************************** * PurpleConnection Implementation *****************************************************************************/ static gboolean purple_ircv3_connection_connect(PurpleConnection *purple_connection, GError **error) { PurpleIRCv3Connection *connection = NULL; PurpleAccount *account = NULL; GCancellable *cancellable = NULL; IbisCapabilities *capabilities = NULL; GError *local_error = NULL; GProxyResolver *resolver = NULL; GStrv userparts = NULL; const char *password = NULL; const char *username = NULL; const char *value = NULL; int default_port = PURPLE_IRCV3_DEFAULT_TLS_PORT; int port = 0; gboolean tls = TRUE; gboolean require_password = FALSE; g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(purple_connection), FALSE); connection = PURPLE_IRCV3_CONNECTION(purple_connection); account = purple_connection_get_account(purple_connection); connection->client = ibis_client_new(); g_signal_connect_object(connection->client, "notify::connected", G_CALLBACK(purple_ircv3_connection_connect_cb), connection, G_CONNECT_DEFAULT); g_signal_connect_object(connection->client, "notify::registered", G_CALLBACK(purple_ircv3_connection_registered_cb), connection, G_CONNECT_DEFAULT); g_signal_connect_object(connection->client, "notify::error", G_CALLBACK(purple_ircv3_connection_error_cb), connection, G_CONNECT_DEFAULT); g_signal_connect_object(connection->client, "notify::active-nick", G_CALLBACK(purple_ircv3_connection_update_status_title_cb), connection, G_CONNECT_DEFAULT); g_signal_connect_object(connection->client, "notify::network", G_CALLBACK(purple_ircv3_connection_update_status_title_cb), connection, G_CONNECT_DEFAULT); purple_ircv3_connection_add_message_handlers(connection, connection->client); /* We need to split the username to get the nick. */ username = purple_account_get_username(account); userparts = g_strsplit(username, "@", 2); ibis_client_set_nick(connection->client, userparts[0]); g_strfreev(userparts); value = purple_account_get_string(account, "ident", NULL); ibis_client_set_username(connection->client, value); value = purple_account_get_string(account, "real-name", NULL); ibis_client_set_realname(connection->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); require_password = purple_account_get_require_password(account); if(require_password) { purple_ircv3_connection_setup_sasl(connection, account); } cancellable = purple_connection_get_cancellable(purple_connection); /* Connect to the ready signal of capabilities. */ capabilities = ibis_client_get_capabilities(connection->client); g_signal_connect_object(capabilities, "ready", G_CALLBACK(purple_ircv3_connection_capabilities_ready_cb), connection, G_CONNECT_DEFAULT); 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(&connection->client); return FALSE; } ibis_client_connect(connection->client, connection->server_name, port, password, tls, cancellable, resolver); return TRUE; } static gboolean purple_ircv3_connection_disconnect(PurpleConnection *purple_connection, G_GNUC_UNUSED GError **error) { PurpleIRCv3Connection *connection = NULL; g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(purple_connection), FALSE); connection = PURPLE_IRCV3_CONNECTION(purple_connection); /* TODO: send QUIT command. */ g_clear_object(&connection->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); g_clear_object(&connection->client); g_clear_object(&connection->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); g_clear_pointer(&connection->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); PurpleAccount *account = NULL; PurpleConversationManager *conversation_manager = NULL; char **userparts = NULL; const char *username = NULL; G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->constructed(obj); account = purple_connection_get_account(PURPLE_CONNECTION(connection)); /* Split the username into nick and server and store the values. */ username = purple_account_get_username(account); userparts = g_strsplit(username, "@", 2); connection->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(); connection->status_conversation = purple_conversation_manager_find_with_id(conversation_manager, account, IRCV3_STATUS_CONVERSATION_ID); if(!PURPLE_IS_CONVERSATION(connection->status_conversation)) { /* Create our status conversation. */ connection->status_conversation = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account, "id", IRCV3_STATUS_CONVERSATION_ID, "online", TRUE, NULL); purple_conversation_manager_add(conversation_manager, connection->status_conversation); } else { /* The conversation existed, so add a reference to it. */ g_object_ref(connection->status_conversation); purple_conversation_set_online(connection->status_conversation, TRUE); purple_conversation_set_error(connection->status_conversation, NULL); } } 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) { purple_ircv3_connection_register_type(G_TYPE_MODULE(plugin)); } /****************************************************************************** * Public API *****************************************************************************/ IbisClient * purple_ircv3_connection_get_client(PurpleIRCv3Connection *connection) { g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL); return connection->client; } void purple_ircv3_connection_add_status_message(PurpleIRCv3Connection *connection, IbisMessage *ibis_message) { PurpleContact *contact = NULL; PurpleConversationMember *author = NULL; PurpleMessage *purple_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)); 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); contact = purple_ircv3_connection_find_or_create_contact(connection, ibis_message); author = purple_conversation_find_or_add_member(connection->status_conversation, PURPLE_CONTACT_INFO(contact), FALSE, NULL); purple_message = purple_message_new(author, stripped); g_free(stripped); purple_conversation_write_message(connection->status_conversation, purple_message); g_clear_object(&purple_message); } PurpleConversation * purple_ircv3_connection_find_or_create_conversation(PurpleIRCv3Connection *connection, const char *id) { PurpleAccount *account = NULL; PurpleConversation *conversation = NULL; PurpleConversationManager *manager = NULL; char *normalized_id = 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(); normalized_id = ibis_client_normalize(connection->client, id); conversation = purple_conversation_manager_find_with_id(manager, account, normalized_id); if(!PURPLE_IS_CONVERSATION(conversation)) { PurpleConversationType type = PURPLE_CONVERSATION_TYPE_DM; if(ibis_client_is_channel(connection->client, id)) { type = PURPLE_CONVERSATION_TYPE_CHANNEL; } conversation = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account, "id", normalized_id, "title", id, "type", type, "online", TRUE, NULL); purple_conversation_manager_add(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); } g_free(normalized_id); return conversation; } PurpleContact * purple_ircv3_connection_find_or_create_contact(PurpleIRCv3Connection *connection, IbisMessage *message) { PurpleAccount *account = NULL; PurpleContact *contact = NULL; PurpleContactManager *manager = NULL; PurplePresence *presence = NULL; PurplePresencePrimitive cur_primitive = PURPLE_PRESENCE_PRIMITIVE_OFFLINE; IbisTags *tags = NULL; const char *source = NULL; char *nick = NULL; account = purple_connection_get_account(PURPLE_CONNECTION(connection)); manager = purple_contact_manager_get_default(); tags = ibis_message_get_tags(message); if(IBIS_IS_TAGS(tags)) { const char *account_tag = NULL; account_tag = ibis_tags_lookup(tags, IBIS_TAG_ACCOUNT); if(!purple_strempty(account_tag)) { contact = purple_contact_manager_find_with_id(manager, account, account_tag); } } source = ibis_message_get_source(message); ibis_source_parse(source, &nick, NULL, NULL); /* If we don't have a contact yet, use the source (Luke) to search next. */ if(!PURPLE_IS_CONTACT(contact)) { contact = purple_contact_manager_find_with_id(manager, account, nick); } /* If we _still_ don't have a contact, create it. */ 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); /* We don't return a reference and the manager add its own. */ g_object_unref(G_OBJECT(contact)); } purple_contact_info_set_sid(PURPLE_CONTACT_INFO(contact), source); purple_contact_info_set_display_name(PURPLE_CONTACT_INFO(contact), nick); /* * Grab the presence and set it as online if we were offline. It's possible * that we're already AWAY due to an RPL_NOWAWAY message. */ presence = purple_contact_info_get_presence(PURPLE_CONTACT_INFO(contact)); cur_primitive = purple_presence_get_primitive(presence); if (cur_primitive == PURPLE_PRESENCE_PRIMITIVE_OFFLINE) { purple_presence_set_primitive(presence, PURPLE_PRESENCE_PRIMITIVE_AVAILABLE); } g_free(nick); return contact; } PurpleContact * purple_ircv3_connection_find_or_create_contact_from_nick(PurpleIRCv3Connection *connection, const char *nick) { PurpleAccount *account = NULL; PurpleContact *contact = NULL; PurpleContactManager *manager = NULL; g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL); g_return_val_if_fail(!purple_strempty(nick), 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_manager_find_with_username(manager, account, nick); } if(!PURPLE_IS_CONTACT(contact)) { PurpleAccount *account = NULL; account = purple_connection_get_account(PURPLE_CONNECTION(connection)); contact = purple_contact_new(account, nick); purple_contact_info_set_username(PURPLE_CONTACT_INFO(contact), nick); purple_contact_info_set_display_name(PURPLE_CONTACT_INFO(contact), nick); purple_contact_manager_add(manager, contact); g_object_unref(G_OBJECT(contact)); } return contact; } PurpleConversation * purple_ircv3_connection_get_status_conversation(PurpleIRCv3Connection *connection) { g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL); return connection->status_conversation; } void purple_ircv3_connection_write_status_message(PurpleIRCv3Connection *connection, IbisMessage *ibis_message, gboolean show_command, gboolean event) { PurpleContact *contact = NULL; PurpleConversationMember *author = NULL; PurpleMessage *purple_message = NULL; PangoAttrList *attrs = NULL; GString *str = NULL; GStrv params = NULL; char *body = NULL; char *stripped = NULL; str = g_string_new(""); if(show_command) { const char *command = NULL; command = ibis_message_get_command(ibis_message); if(!purple_strempty(command)) { g_string_append_printf(str, "%s ", command); } } params = ibis_message_get_params(ibis_message); body = g_strjoinv(" ", params); /* Grab the attributes and offset them for however many bytes are already * in the string. */ stripped = ibis_formatting_parse(body, &attrs); pango_attr_list_update(attrs, 0, 0, str->len); g_free(body); g_string_append(str, stripped); g_free(stripped); contact = purple_ircv3_connection_find_or_create_contact(connection, ibis_message); author = purple_conversation_find_or_add_member(connection->status_conversation, PURPLE_CONTACT_INFO(contact), FALSE, NULL); purple_message = purple_message_new(author, str->str); g_string_free(str, TRUE); purple_message_set_attributes(purple_message, attrs); pango_attr_list_unref(attrs); purple_message_set_event(purple_message, event); purple_conversation_write_message(connection->status_conversation, purple_message); g_clear_object(&purple_message); }