~aleteoryx/sexchat

7bcf0afcdb0263caa2fc6588284bb8dbfb198373 — Aleteoryx 4 months ago a1d5bb4
basic batch support

Implements: https://todo.amehut.dev/~aleteoryx/sexchat/2
A src/common/batch.c => src/common/batch.c +125 -0
@@ 0,0 1,125 @@
/* sexchat
 * Copyright (C) 2025 Aleteoryx.
 *
 * This program 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 program 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 program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/* IRCv3 batch CAP */

#include <string.h>

#include "hexchat.h"
#include "batch.h"
#include "proto-irc.h"

void
batch_new (ircconn *serv, char *reftag, char *type, char **args)
{
	struct batch *bat;

	if (batch_lookup (serv, reftag))
		return;
	
	if (strcmp (type, "soju.im/bouncer-networks") == 0)
	{
		bat = (struct batch *)g_new0 (struct batch_sojuim_bouncer_networks, 1);
		bat->type = BATCH_SOJUIM_BOUNCER_NETWORKS;
	} else
	{
		bat = (struct batch *)g_new0 (struct batch_replay, 1);
		bat->type = BATCH_REPLAY;
	}
	
	bat->reftag = g_strdup (reftag);
	
	serv->batches = g_slist_prepend (serv->batches, bat);
}

void
batch_finalize (ircconn *serv, char *reftag)
{
	struct batch *bat;
	
	bat = batch_lookup (serv, reftag);
	if (bat == NULL)
		return;
	
	serv->batches = g_slist_remove (serv->batches, bat);
	
	switch (bat->type)
	{
	case BATCH_SOJUIM_BOUNCER_NETWORKS:
		break;

	case BATCH_REPLAY:
		{
			struct batch_replay *bat2 = (struct batch_replay *)bat;
			GSList *el;
			char *line;
			
			bat2->messages = g_slist_reverse (bat2->messages);
			for (el = bat2->messages; el; el = el->next)
			{
				line = (char *)el->data;
				irc_inline (serv, line, strlen (line));
				g_free (line);
			}
			
			g_slist_free (bat2->messages);
		}
	}
	
	g_free (bat);
}

gboolean
batch_event (ircconn *serv, char *reftag, char *raw, char **word, char **word_eol)
{
	struct batch *bat;

	bat = batch_lookup (serv, reftag);
	if (bat == NULL)
		return FALSE;

	switch (bat->type)
	{
	case BATCH_SOJUIM_BOUNCER_NETWORKS:
		break;
	case BATCH_REPLAY:
		{
			struct batch_replay *bat2 = (struct batch_replay *)bat;
			bat2->messages = g_slist_prepend (bat2->messages, g_strdup (raw));
		}
	}

	return TRUE;
}


struct batch *
batch_lookup (ircconn *serv, char *reftag)
{
	GSList *el;
	struct batch *bat;
	
	for (el = serv->batches; el; el = el->next)
	{
		bat = (struct batch *)el->data;
		if (strcmp (bat->reftag, reftag) == 0)
			return bat;
	}

	return NULL;
}

A src/common/batch.h => src/common/batch.h +52 -0
@@ 0,0 1,52 @@
/* sexchat
 * Copyright (C) 2025 Aleteoryx.
 *
 * This program 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 program 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 program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "hexchat.h"

#ifndef HEXCHAT_BATCH_H
#define HEXCHAT_BATCH_H

enum {
	BATCH_SOJUIM_BOUNCER_NETWORKS, // soju.im/bouncer-networks
	BATCH_REPLAY, // fallback, no special handling other than being deferred till batch end
};

struct batch {
	int type;
	char *reftag;
};

struct batch_sojuim_bouncer_networks {
	int type; // BATCH_SOJUIM_BOUNCER_NETWORKS
	char *reftag;
	GSList *networks;
};

struct batch_replay {
	int type; // BATCH_REPLAY
	char *reftag;
	GSList *messages;
};


void batch_new (ircconn *serv, char *reftag, char *type, char **args);
gboolean batch_event (ircconn *serv, char *reftag, char *raw, char **word, char **word_eol);
void batch_finalize (ircconn *serv, char *reftag);
struct batch *batch_lookup (ircconn *serv, char *reftag);

#endif

M src/common/hexchat.h => src/common/hexchat.h +11 -10
@@ 530,24 530,25 @@ typedef struct server
	struct session *front_session;	/* front-most window/tab */
	struct session *server_session;	/* server window/tab */

	struct server_gui *gui;		  /* initialized by fe_new_server */
	struct server_gui *gui;		/* initialized by fe_new_server */

	unsigned int ctcp_counter;	  /*flood */
	unsigned int ctcp_counter;	/*flood */
	time_t ctcp_last_time;

	unsigned int msg_counter;	  /*counts the msg tab opened in a certain time */
	unsigned int msg_counter;	/*counts the msg tab opened in a certain time */
	time_t msg_last_time;

	/*time_t connect_time;*/				/* when did it connect? */
	unsigned long lag_sent;   /* we are still waiting for this ping response*/
	time_t ping_recv;					/* when we last got a ping reply */
	time_t away_time;					/* when we were marked away */
	unsigned long lag_sent;		/* we are still waiting for this ping response*/
	time_t ping_recv;		/* when we last got a ping reply */
	time_t away_time;		/* when we were marked away */

	char *encoding;
	GIConv read_converter;  /* iconv converter for converting from server encoding to UTF-8. */
	GIConv write_converter; /* iconv converter for converting from UTF-8 to server encoding. */
	GIConv read_converter;  	/* iconv converter for converting from server encoding to UTF-8. */
	GIConv write_converter; 	/* iconv converter for converting from UTF-8 to server encoding. */

	GSList *favlist;			/* list of channels & keys to join */
	GSList *favlist;		/* list of channels & keys to join */

	GSList *batches;		/* list of batches */

	unsigned int motd_skipped:1;
	unsigned int connected:1;

M src/common/inbound.c => src/common/inbound.c +1 -0
@@ 1716,6 1716,7 @@ static const char * const supported_caps[] = {
	"extended-monitor",
	"message-tags",
	"echo-message",
	"batch",

	/* ZNC */
	"znc.in/server-time-iso",

M src/common/meson.build => src/common/meson.build +2 -1
@@ 1,4 1,5 @@
common_sources = [
  'batch.c',
  'cfgfiles.c',
  'chanopt.c',
  'ctcp.c',


@@ 18,7 19,7 @@ common_sources = [
  'scram.c',
  'server.c',
  'servlist.c',
	'text.c',
  'text.c',
  'tree.c',
  'url.c',
  'userlist.c',

M src/common/outbound.c => src/common/outbound.c +2 -2
@@ 151,8 151,8 @@ server_sendquit (session * sess)

void
process_data_init (char *buf, char *cmd, char *word[],
						 char *word_eol[], gboolean handle_quotes,
						 gboolean allow_escape_quotes)
		   char *word_eol[], gboolean handle_quotes,
		   gboolean allow_escape_quotes)
{
	int wordcount = 2;
	int space = FALSE;

M src/common/proto-irc.c => src/common/proto-irc.c +36 -14
@@ 44,6 44,7 @@
#include "hexchatc.h"
#include "url.h"
#include "servlist.h"
#include "batch.h"

static void
irc_login (server *serv, char *user, char *realname)


@@ 1103,14 1104,14 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
			}
			return;

		case WORDL('P', 'I', 'N', 'G'):
		case WORDL('P','I','N','G'):
			tcp_sendf (sess->server, "PONG %s\r\n", word_eol[3]);
			return;

		case WORDL('P','O','N','G'):
			inbound_ping_reply (serv->server_session,
									  (word[4][0] == ':') ? word[4] + 1 : word[4],
									  word[3], tags_data);
					    (word[4][0] == ':') ? word[4] + 1 : word[4],
					    word[3], tags_data);
			return;

		case WORDL('Q','U','I','T'):


@@ 1175,16 1176,16 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
			inbound_account (serv, nick, STRIP_COLON(word, word_eol, 3), tags_data);
			return;

		case WORDL('A', 'U', 'T', 'H'):
		case WORDL('A','U','T','H'):
			inbound_sasl_authenticate (sess->server, word_eol[3]);
			return;

		case WORDL('C', 'H', 'G', 'H'):
		case WORDL('C','H','G','H'):
			inbound_user_info (sess, NULL, word[3], STRIP_COLON(word, word_eol, 4), NULL, nick, NULL,
							   NULL, 0xff, tags_data);
			return;

		case WORDL('S', 'E', 'T', 'N'):
		case WORDL('S','E','T','N'):
			inbound_user_info (sess, NULL, NULL, NULL, NULL, nick, STRIP_COLON(word, word_eol, 3),
							   NULL, 0xff, tags_data);
			return;


@@ 1310,8 1311,8 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],

		case WORDL('T','O','P','I'):
			inbound_topicnew (serv, nick, word[3],
									(word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4],
									tags_data);
					  (word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4],
					  tags_data);
			return;

		case WORDL('W','A','L','L'):


@@ 1320,6 1321,13 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
				text++;
			EMIT_SIGNAL_TAGS (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0, tags_data);
			return;
		
		case WORDL('B','A','T','C'):
			if (word[3][0] == '-')
				batch_finalize (serv, word[3]+1);
			else
				batch_new (serv, word[3]+1, word[4], word+5);
			return;
		}
	}



@@ 1372,8 1380,8 @@ garbage:
/* handle named messages that DON'T start with a ':' */

static void
process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[],
								 const message_tags_data *tags_data)
process_named_servermsg (session *sess, char *buf, char *rawname, char *word[], char *word_eol[],
			 const message_tags_data *tags_data)
{
	sess = sess->server->server_session;



@@ 1400,6 1408,14 @@ process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol
		inbound_sasl_authenticate (sess->server, word_eol[2]);
		return;
	}
	if (!strncmp (buf, "BATCH ", 6))
	{
		if (word[3][0] == '-')
			batch_finalize (sess->server, word[3]+1);
		else
			batch_new (sess->server, word[3]+1, word[4], word+5);
		return;
	}

	EMIT_SIGNAL_TAGS (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0, tags_data);
}


@@ 1503,7 1519,7 @@ handle_message_tag_time (const char *time, message_tags_data *tags_data)
 */
static void
handle_message_tags (server *serv, const char *tags_str,
							message_tags_data *tags_data)
		     message_tags_data *tags_data)
{
	char **tags;
	int i;


@@ 1527,8 1543,11 @@ handle_message_tags (server *serv, const char *tags_str,
		if (serv->have_account_tag && !strcmp (key, "account"))
			tags_data->account = g_strdup (value);
		
		if (serv->have_message_tags && !strcmp (key, "msgid"))
		if (!strcmp (key, "msgid"))
			tags_data->msgid = g_strdup (value);
		
		if (!strcmp (key, "batch"))
			tags_data->batchref = g_strdup (value);

		if (serv->have_idmsg && strcmp (key, "solanum.chat/identified"))
			tags_data->identified = TRUE;


@@ 1577,6 1596,9 @@ irc_inline (server *serv, char *buf, int len)

	/* split line into words and words_to_end_of_line */
	process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
	
	if (tags_data.batchref && batch_event (serv, tags_data.batchref, buf, word, word_eol))
		goto xit;

	if (buf[0] == ':')
	{


@@ 1595,7 1617,7 @@ irc_inline (server *serv, char *buf, int len)
		word_eol[1] = buf;	/* keep the ":" for plugins */

		if (plugin_emit_server (sess, type, word, word_eol,
								tags_data.timestamp))
					tags_data.timestamp))
			goto xit;

		word[1]++;


@@ 1612,7 1634,7 @@ irc_inline (server *serv, char *buf, int len)

	if (buf[0] != ':')
	{
		process_named_servermsg (sess, buf, word[0], word_eol, &tags_data);
		process_named_servermsg (sess, buf, word[0], word, word_eol, &tags_data);
		goto xit;
	}


M src/common/proto-irc.h => src/common/proto-irc.h +2 -0
@@ 27,6 27,7 @@
	{					\
		NULL, /* account name */	\
		NULL, /* message id */		\
		NULL, /* batch ref */		\
		FALSE, /* identified to nick */ \
		(time_t)0, /* timestamp */	\
	}


@@ 41,6 42,7 @@ typedef struct
{
	char *account;
	char *msgid;
	char *batchref;
	gboolean identified;
	time_t timestamp;
} message_tags_data;