1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-12 14:14:48 +02:00
Files
weechat/src/plugins/irc/irc-batch.c
T
2023-08-09 14:55:50 +02:00

587 lines
18 KiB
C

/*
* irc-batch.c - functions for managing batched events
*
* Copyright (C) 2023 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat 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 3 of the License, or
* (at your option) any later version.
*
* WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "../weechat-plugin.h"
#include "irc.h"
#include "irc-batch.h"
#include "irc-message.h"
#include "irc-protocol.h"
#include "irc-raw.h"
#include "irc-server.h"
#include "irc-tag.h"
/*
* Searches a batch reference.
*
* Returns pointer to batch, NULL if not found.
*/
struct t_irc_batch *
irc_batch_search (struct t_irc_server *server, const char *reference)
{
struct t_irc_batch *ptr_batch;
if (!server || !reference)
return NULL;
for (ptr_batch = server->batches; ptr_batch;
ptr_batch = ptr_batch->next_batch)
{
if (strcmp (ptr_batch->reference, reference) == 0)
return ptr_batch;
}
/* batch not found */
return NULL;
}
/*
* Generates a random batch reference with `size` chars (the next one is the
* final '\0', so the string must be at least size + 1 bytes long).
*/
void
irc_batch_generate_random_ref (char *string, int size)
{
const char *chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789";
int i, length_chars;
if (!string || (size < 0))
return;
length_chars = strlen (chars);
for (i = 0; i < size; i++)
{
string[i] = chars[rand() % length_chars];
}
string[size] = '\0';
}
/*
* Adds a batch to list of batched events.
*/
void
irc_batch_add_to_list (struct t_irc_server *server, struct t_irc_batch *batch)
{
if (server->last_batch)
server->last_batch->next_batch = batch;
else
server->batches = batch;
batch->prev_batch = server->last_batch;
batch->next_batch = NULL;
server->last_batch = batch;
}
/*
* Starts a batch.
*
* Returns pointer to new batch, NULL if error.
*/
struct t_irc_batch *
irc_batch_start_batch (struct t_irc_server *server, const char *reference,
const char *parent_ref, const char *type,
const char *parameters, struct t_hashtable *tags)
{
struct t_irc_batch *ptr_batch;
if (!server || !reference || !type)
return NULL;
/* check if reference already exists */
ptr_batch = irc_batch_search (server, reference);
if (ptr_batch)
return NULL;
ptr_batch = malloc (sizeof (*ptr_batch));
if (!ptr_batch)
return NULL;
ptr_batch->reference = strdup (reference);
ptr_batch->parent_ref = (parent_ref) ? strdup (parent_ref) : NULL;
ptr_batch->type = strdup (type);
ptr_batch->parameters = (parameters) ? strdup (parameters) : NULL;
ptr_batch->tags = (tags) ? weechat_hashtable_dup (tags) : NULL;
ptr_batch->start_time = time (NULL);
ptr_batch->messages = NULL;
ptr_batch->end_received = 0;
ptr_batch->messages_processed = 0;
irc_batch_add_to_list (server, ptr_batch);
return ptr_batch;
}
/*
* Adds an IRC message to a batch reference.
*
* Returns:
* 1: OK, message added
* 0: error, message not added
*/
int
irc_batch_add_message (struct t_irc_server *server, const char *reference,
const char *irc_message)
{
struct t_irc_batch *ptr_batch;
if (!server || !reference || !irc_message)
return 0;
ptr_batch = irc_batch_search (server, reference);
if (!ptr_batch)
return 0;
if (!ptr_batch->messages)
ptr_batch->messages = weechat_string_dyn_alloc (256);
if (!ptr_batch->messages)
return 0;
if ((*(ptr_batch->messages))[0])
weechat_string_dyn_concat (ptr_batch->messages, "\n", -1);
weechat_string_dyn_concat (ptr_batch->messages, irc_message, -1);
return 1;
}
/*
* Frees a batch.
*/
void
irc_batch_free (struct t_irc_server *server, struct t_irc_batch *batch)
{
if (batch->reference)
free (batch->reference);
if (batch->parent_ref)
free (batch->parent_ref);
if (batch->type)
free (batch->type);
if (batch->parameters)
free (batch->parameters);
if (batch->tags)
weechat_hashtable_free (batch->tags);
if (batch->messages)
weechat_string_dyn_free (batch->messages, 1);
/* remove batch from list */
if (batch->prev_batch)
(batch->prev_batch)->next_batch = batch->next_batch;
if (batch->next_batch)
(batch->next_batch)->prev_batch = batch->prev_batch;
if (server->batches == batch)
server->batches = batch->next_batch;
if (server->last_batch == batch)
server->last_batch = batch->prev_batch;
free (batch);
}
/*
* Frees all batches from server.
*/
void
irc_batch_free_all (struct t_irc_server *server)
{
while (server->batches)
{
irc_batch_free (server, server->batches);
}
}
/*
* Processes messages in a batch.
*/
void
irc_batch_process_messages (struct t_irc_server *server,
struct t_irc_batch *batch)
{
char **list_messages, *command, *channel, modifier_data[1024], *new_messages;
char *message, *message2;
int i, count_messages;
if (!batch || !batch->messages)
return;
snprintf (modifier_data, sizeof (modifier_data),
"%s,%s,%s",
server->name,
batch->type,
batch->parameters);
new_messages = weechat_hook_modifier_exec ("irc_batch", modifier_data,
*(batch->messages));
/* no changes in new messages */
if (new_messages && (strcmp (*(batch->messages), new_messages) == 0))
{
free (new_messages);
new_messages = NULL;
}
/* messages not dropped? */
if (!new_messages || new_messages[0])
{
list_messages = weechat_string_split (
(new_messages) ? new_messages : *(batch->messages),
"\n", NULL, 0, 0, &count_messages);
if (list_messages)
{
for (i = 0; i < count_messages; i++)
{
message = weechat_string_replace (list_messages[i], "\r", "\n");
if (!message)
continue;
message2 = irc_tag_add_tags_to_message (message,
batch->tags);
if (!message2)
continue;
irc_message_parse (server,
message2,
NULL, /* tags */
NULL, /* message_without_tags */
NULL, /* nick */
NULL, /* user */
NULL, /* host */
&command,
&channel,
NULL, /* arguments */
NULL, /* text */
NULL, /* params */
NULL, /* num_params */
NULL, /* pos_command */
NULL, /* pos_arguments */
NULL, /* pos_channel */
NULL); /* pos_text */
/* add raw message */
irc_raw_print (server, IRC_RAW_FLAG_RECV, message2);
/* call receive callback, ignoring batch tags */
irc_protocol_recv_command (server, message2, command, channel, 1);
free (message);
free (message2);
if (command)
free (command);
if (channel)
free (channel);
}
weechat_string_free_split (list_messages);
}
}
if (new_messages)
free (new_messages);
}
/*
* Ends a batch reference.
*/
void
irc_batch_end_batch (struct t_irc_server *server, const char *reference)
{
struct t_irc_batch *ptr_batch, *ptr_next_batch, *ptr_parent_batch;
int num_processed;
if (!server || !reference)
return;
ptr_batch = irc_batch_search (server, reference);
if (!ptr_batch)
return;
ptr_batch->end_received = 1;
/*
* process messages in all batches, if these conditions are met:
* - end_received = 1
* - no parent or the parent has messages_processed = 1
*/
while (1)
{
num_processed = 0;
for (ptr_batch = server->batches; ptr_batch;
ptr_batch = ptr_batch->next_batch)
{
if (!ptr_batch->end_received || ptr_batch->messages_processed)
continue;
ptr_parent_batch = irc_batch_search (server, ptr_batch->parent_ref);
if (!ptr_parent_batch || ptr_parent_batch->messages_processed)
{
irc_batch_process_messages (server, ptr_batch);
ptr_batch->messages_processed = 1;
num_processed++;
}
}
if (num_processed == 0)
break;
}
/* remove all batches that are processed */
ptr_batch = server->batches;
while (ptr_batch)
{
ptr_next_batch = ptr_batch->next_batch;
if (ptr_batch->messages_processed)
irc_batch_free (server, ptr_batch);
ptr_batch = ptr_next_batch;
}
}
/*
* Processes multiline batch: convert multiple messages into a single one,
* that can include newline chars ("\r" that are converted later to "\n").
*
* Parameter "target" is the batch target (channel or nick name).
*
* Note: result must be freed after use.
*/
char *
irc_batch_process_multiline (struct t_irc_server *server,
const char *messages, const char *target)
{
char **result, **list_messages;
char *tags, *host, *command, *channel, *text;
int i, count_messages;
struct t_hashtable *hash_tags;
result = weechat_string_dyn_alloc (256);
hash_tags = NULL;
list_messages = weechat_string_split (messages, "\n", NULL, 0, 0,
&count_messages);
if (!list_messages)
goto end;
hash_tags = weechat_hashtable_new (32,
WEECHAT_HASHTABLE_STRING,
WEECHAT_HASHTABLE_STRING,
NULL, NULL);
for (i = 0; i < count_messages; i++)
{
irc_message_parse (server,
list_messages[i],
&tags,
NULL, /* message_without_tags */
NULL, /* nick */
NULL, /* user */
&host,
&command,
&channel,
NULL, /* arguments */
&text,
NULL, /* params */
NULL, /* num_params */
NULL, /* pos_command */
NULL, /* pos_arguments */
NULL, /* pos_channel */
NULL); /* pos_text */
if (host
&& command
&& ((strcmp (command, "PRIVMSG") == 0)
|| (strcmp (command, "NOTICE") == 0))
&& channel
&& (strcmp (channel, target) == 0))
{
if (hash_tags)
{
weechat_hashtable_remove_all (hash_tags);
if (tags && tags[0])
irc_tag_parse (tags, hash_tags, NULL);
}
if (*result[0])
{
if (!hash_tags
|| !weechat_hashtable_has_key (hash_tags,
"draft/multiline-concat"))
{
weechat_string_dyn_concat (result, "\r", -1);
}
}
else
{
if (tags && tags[0])
{
weechat_string_dyn_concat (result, "@", -1);
weechat_string_dyn_concat (result, tags, -1);
weechat_string_dyn_concat (result, " ", -1);
}
weechat_string_dyn_concat (result, ":", -1);
weechat_string_dyn_concat (result, host, -1);
weechat_string_dyn_concat (result, " ", -1);
weechat_string_dyn_concat (result, command, -1);
weechat_string_dyn_concat (result, " ", -1);
weechat_string_dyn_concat (result, target, -1);
weechat_string_dyn_concat (result, " :", -1);
}
if (text)
weechat_string_dyn_concat (result, text, -1);
}
if (tags)
free (tags);
if (host)
free (host);
if (command)
free (command);
if (channel)
free (channel);
if (text)
free (text);
}
end:
if (hash_tags)
weechat_hashtable_free (hash_tags);
if (list_messages)
weechat_string_free_split (list_messages);
return weechat_string_dyn_free (result, 0);
}
/*
* Callback for modifier "irc_batch".
*/
char *
irc_batch_modifier_cb (const void *pointer, void *data,
const char *modifier, const char *modifier_data,
const char *string)
{
struct t_irc_server *ptr_server;
char **items, *result;
int num_items;
/* make C compiler happy */
(void) pointer;
(void) data;
(void) modifier;
result = NULL;
if (!modifier_data)
return NULL;
items = weechat_string_split (modifier_data, ",", NULL, 0, 3, &num_items);
if (!items)
return NULL;
if (items && (num_items > 1))
{
ptr_server = irc_server_search (items[0]);
if (ptr_server
&& (num_items > 2)
&& (strcmp (items[1], "draft/multiline") == 0)
&& weechat_hashtable_has_key (ptr_server->cap_list, "draft/multiline"))
{
result = irc_batch_process_multiline (ptr_server, string, items[2]);
}
}
if (items)
weechat_string_free_split (items);
return (result) ? result : strdup (string);
}
/*
* Returns hdata for batch.
*/
struct t_hdata *
irc_batch_hdata_batch_cb (const void *pointer, void *data,
const char *hdata_name)
{
struct t_hdata *hdata;
/* make C compiler happy */
(void) pointer;
(void) data;
hdata = weechat_hdata_new (hdata_name, "prev_batch", "next_batch",
0, 0, NULL, NULL);
if (hdata)
{
WEECHAT_HDATA_VAR(struct t_irc_batch, reference, STRING, 0, NULL, NULL);
WEECHAT_HDATA_VAR(struct t_irc_batch, parent_ref, STRING, 0, NULL, NULL);
WEECHAT_HDATA_VAR(struct t_irc_batch, type, STRING, 0, NULL, NULL);
WEECHAT_HDATA_VAR(struct t_irc_batch, parameters, STRING, 0, NULL, NULL);
WEECHAT_HDATA_VAR(struct t_irc_batch, start_time, TIME, 0, NULL, NULL);
WEECHAT_HDATA_VAR(struct t_irc_batch, messages, POINTER, 0, NULL, NULL);
WEECHAT_HDATA_VAR(struct t_irc_batch, end_received, INTEGER, 0, NULL, NULL);
WEECHAT_HDATA_VAR(struct t_irc_batch, messages_processed, INTEGER, 0, NULL, NULL);
WEECHAT_HDATA_VAR(struct t_irc_batch, prev_batch, POINTER, 0, NULL, hdata_name);
WEECHAT_HDATA_VAR(struct t_irc_batch, next_batch, POINTER, 0, NULL, hdata_name);
}
return hdata;
}
/*
* Prints batch infos in WeeChat log file (usually for crash dump).
*/
void
irc_batch_print_log (struct t_irc_server *server)
{
struct t_irc_batch *ptr_batch;
for (ptr_batch = server->batches; ptr_batch;
ptr_batch = ptr_batch->next_batch)
{
weechat_log_printf ("");
weechat_log_printf (" => batch (addr:0x%lx):", ptr_batch);
weechat_log_printf (" reference . . . . . : '%s'", ptr_batch->reference);
weechat_log_printf (" parent_ref. . . . . : '%s'", ptr_batch->parent_ref);
weechat_log_printf (" type. . . . . . . . : '%s'", ptr_batch->type);
weechat_log_printf (" parameters. . . . . : '%s'", ptr_batch->parameters);
weechat_log_printf (" tags. . . . . . . . : 0x%lx (hashtable: '%s')",
ptr_batch->tags,
weechat_hashtable_get_string (ptr_batch->tags,
"keys_values"));
weechat_log_printf (" start_time. . . . . : %lld", (long long)ptr_batch->start_time);
weechat_log_printf (" message . . . . . . : 0x%lx ('%s')",
ptr_batch->messages,
(ptr_batch->messages) ? *(ptr_batch->messages) : NULL);
weechat_log_printf (" end_received. . . . : %d", ptr_batch->end_received);
weechat_log_printf (" messages_processed. : %d", ptr_batch->messages_processed);
weechat_log_printf (" prev_batch. . . . . : 0x%lx", ptr_batch->prev_batch);
weechat_log_printf (" next_batch. . . . . : 0x%lx", ptr_batch->next_batch);
}
}