protocols/ircv3/purpleircv3connection.c

Tue, 23 Jul 2024 00:57:40 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Tue, 23 Jul 2024 00:57:40 -0500
changeset 42815
95bb2ae83de7
parent 42803
984f8dfabb47
child 42867
e16b8726d9b5
permissions
-rw-r--r--

IRCv3: Use Ibis.Message in PurpleIRCv3.Connection.find_or_create_contact

Testing Done:
Connected to a local ergo and sent some channel messages and privmsgs from both sides and called in the turtles.

Reviewed at https://reviews.imfreedom.org/r/3292/

/*
 * 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 <hasl.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))

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) {
	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);
	}
}

static inline void
purple_ircv3_connection_setup_sasl(PurpleIRCv3Connection *connection,
                                   PurpleAccount *account)
{
	PurpleIRCv3ConnectionPrivate *priv = NULL;
	HaslContext *hasl_context = NULL;
	const char *value = NULL;
	gboolean clear_text = FALSE;

	priv = purple_ircv3_connection_get_instance_private(connection);

	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(priv->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(priv->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);
	}

	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(priv->client, hasl_context);
	g_clear_object(&hasl_context);
}

/******************************************************************************
 * Message Handlers
 *****************************************************************************/
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_handler(G_GNUC_UNUSED IbisClient *client,
                                    G_GNUC_UNUSED const char *command,
                                    IbisMessage *message,
                                    gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleIRCv3ConnectionPrivate *priv = NULL;
	PurpleMessage *purple_message = NULL;
	GStrv params = NULL;
	char *body = NULL;
	const char *source = NULL;

	priv = purple_ircv3_connection_get_instance_private(connection);

	/* The first parameter is supposed to be our nick which we don't want to
	 * echo so we skip past it.
	 */
	params = ibis_message_get_params(message);
	body = g_strjoinv(" ", params + 1);

	source = ibis_message_get_source(message);

	purple_message = g_object_new(
		PURPLE_TYPE_MESSAGE,
		"author", source,
		"contents", body,
		NULL);

	g_free(body);

	purple_conversation_write_message(priv->status_conversation,
	                                  purple_message);
	g_clear_object(&purple_message);

	return TRUE;
}

static gboolean
purple_ircv3_server_no_motd_handler(G_GNUC_UNUSED IbisClient *client,
                                    G_GNUC_UNUSED const char *command,
                                    IbisMessage *message,
                                    gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleIRCv3ConnectionPrivate *priv = NULL;
	PurpleMessage *purple_message = NULL;
	const char *source = NULL;

	priv = purple_ircv3_connection_get_instance_private(connection);

	source = ibis_message_get_source(message);

	purple_message = g_object_new(
		PURPLE_TYPE_MESSAGE,
		"author", source,
		"contents", _("no message of the day found"),
		NULL);


	purple_conversation_write_message(priv->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, "message::" IBIS_RPL_WELCOME,
	                        G_CALLBACK(purple_ircv3_server_message_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_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_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_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_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",
	                        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 *message,
                                           gpointer data)
{
	PurpleIRCv3Connection *connection = data;
	PurpleIRCv3ConnectionPrivate *priv = NULL;
	PurpleMessage *purple_message = NULL;
	char *contents = NULL;

	priv = purple_ircv3_connection_get_instance_private(connection);

	contents = g_strdup_printf(_("unhandled message: '%s'"),
	                           ibis_message_get_raw_message(message));

	purple_message = g_object_new(
		PURPLE_TYPE_MESSAGE,
		"author", ibis_message_get_source(message),
		"contents", contents,
		NULL);

	purple_conversation_write_message(priv->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)) {
		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);
	}
}

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);
}

/******************************************************************************
 * PurpleConnection Implementation
 *****************************************************************************/
static gboolean
purple_ircv3_connection_connect(PurpleConnection *purple_connection,
                                GError **error)
{
	PurpleIRCv3Connection *connection = NULL;
	PurpleIRCv3ConnectionPrivate *priv = NULL;
	PurpleAccount *account = NULL;
	GCancellable *cancellable = NULL;
	IbisCapabilities *capabilities = 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;
	gboolean require_password = FALSE;

	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);

	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(priv->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(&priv->client);

		return FALSE;
	}

	ibis_client_connect(priv->client, priv->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;
	PurpleIRCv3ConnectionPrivate *priv = NULL;

	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);

	/* 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;
	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));

	title = g_strdup_printf(_("status for %s"),
	                        purple_account_get_username(account));

	/* Split the username into nick and server and store the values. */
	username = purple_account_get_username(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,
                                               IbisMessage *message)
{
	PurpleAccount *account = NULL;
	PurpleContact *contact = NULL;
	PurpleContactManager *manager = NULL;
	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);
	}

	purple_contact_info_set_sid(PURPLE_CONTACT_INFO(contact), source);

	g_free(nick);

	return contact;
}

mercurial