IRCv3: Do a who after joining a channel

Mon, 21 Apr 2025 23:41:59 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Mon, 21 Apr 2025 23:41:59 -0500
changeset 43236
ce4987105a4e
parent 43235
42e7b89033fe
child 43237
c641f6bea7dd

IRCv3: Do a who after joining a channel

This is necessary to synchronize away states and some other stuff. This also
allows us to ignore the implicit namreply when joining a channel since the who
messages contain a superset of that information.

Testing Done:
Connected to my local ergo and look at the packet capture and verified that no namreply was sent and that the conversation member list looked correct.

Bugs closed: PIDGIN-17936

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

protocols/ircv3/purpleircv3connection.c file | annotate | diff | comparison | revisions
protocols/ircv3/purpleircv3messagehandlers.c file | annotate | diff | comparison | revisions
protocols/ircv3/purpleircv3messagehandlers.h file | annotate | diff | comparison | revisions
--- a/protocols/ircv3/purpleircv3connection.c	Mon Apr 21 23:09:08 2025 -0500
+++ b/protocols/ircv3/purpleircv3connection.c	Mon Apr 21 23:41:59 2025 -0500
@@ -483,6 +483,13 @@
 	                        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);
@@ -590,6 +597,14 @@
 	/* 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
--- a/protocols/ircv3/purpleircv3messagehandlers.c	Mon Apr 21 23:09:08 2025 -0500
+++ b/protocols/ircv3/purpleircv3messagehandlers.c	Mon Apr 21 23:41:59 2025 -0500
@@ -235,7 +235,7 @@
 }
 
 gboolean
-purple_ircv3_message_handler_join(G_GNUC_UNUSED IbisClient *client,
+purple_ircv3_message_handler_join(IbisClient *client,
                                   G_GNUC_UNUSED const char *command,
                                   IbisMessage *message,
                                   gpointer data)
@@ -243,6 +243,7 @@
 	PurpleIRCv3Connection *connection = data;
 	PurpleContact *contact = NULL;
 	PurpleConversation *conversation = NULL;
+	IbisMessage *who_message = NULL;
 	GStrv params = NULL;
 	const char *conversation_name = NULL;
 
@@ -266,6 +267,11 @@
 	                                                                   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;
 }
 
@@ -1103,3 +1109,88 @@
 
 	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;
+}
--- a/protocols/ircv3/purpleircv3messagehandlers.h	Mon Apr 21 23:09:08 2025 -0500
+++ b/protocols/ircv3/purpleircv3messagehandlers.h	Mon Apr 21 23:41:59 2025 -0500
@@ -51,6 +51,7 @@
 G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_wallops(IbisClient *client, const char *command, IbisMessage *message, gpointer data);
 G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_kick(IbisClient *client, const char *command, IbisMessage *message, gpointer data);
 G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_mode(IbisClient *client, const char *command, IbisMessage *message, gpointer data);
+G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_whoreply(IbisClient *client, const char *command, IbisMessage *message, gpointer data);
 
 G_END_DECLS
 

mercurial