degesch: add ability to hook IRC and user input
We're going to make this available to the Lua API soon.
This commit is contained in:
parent
fbfe0ba18a
commit
c912726f49
206
degesch.c
206
degesch.c
|
@ -1382,6 +1382,42 @@ plugin_destroy (struct plugin *self)
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
struct input_hook
|
||||||
|
{
|
||||||
|
LIST_HEADER (struct input_hook)
|
||||||
|
|
||||||
|
struct input_hook_vtable *vtable; ///< Methods
|
||||||
|
int priority; ///< The lesser the sooner
|
||||||
|
};
|
||||||
|
|
||||||
|
struct input_hook_vtable
|
||||||
|
{
|
||||||
|
/// Takes over the ownership of "input", returns either NULL if input
|
||||||
|
/// was thrown away, or a possibly modified version of it
|
||||||
|
char *(*filter) (struct input_hook *self,
|
||||||
|
struct buffer *buffer, char *input);
|
||||||
|
};
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
struct irc_hook
|
||||||
|
{
|
||||||
|
LIST_HEADER (struct irc_hook)
|
||||||
|
|
||||||
|
struct irc_hook_vtable *vtable; ///< Methods
|
||||||
|
int priority; ///< The lesser the sooner
|
||||||
|
};
|
||||||
|
|
||||||
|
struct irc_hook_vtable
|
||||||
|
{
|
||||||
|
/// Takes over the ownership of "message", returns either NULL if message
|
||||||
|
/// was thrown away, or a possibly modified version of it
|
||||||
|
char *(*filter) (struct irc_hook *self,
|
||||||
|
struct server *server, char *message);
|
||||||
|
};
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
struct app_context
|
struct app_context
|
||||||
{
|
{
|
||||||
bool no_colors; ///< Disable attribute printing
|
bool no_colors; ///< Disable attribute printing
|
||||||
|
@ -1443,6 +1479,8 @@ struct app_context
|
||||||
int terminal_suspended; ///< Terminal suspension level
|
int terminal_suspended; ///< Terminal suspension level
|
||||||
|
|
||||||
struct plugin *plugins; ///< Loaded plugins
|
struct plugin *plugins; ///< Loaded plugins
|
||||||
|
struct input_hook *input_hooks; ///< Input hooks
|
||||||
|
struct irc_hook *irc_hooks; ///< IRC hooks
|
||||||
}
|
}
|
||||||
*g_ctx;
|
*g_ctx;
|
||||||
|
|
||||||
|
@ -4066,8 +4104,54 @@ on_irc_autojoin_timeout (void *user_data)
|
||||||
|
|
||||||
// --- Server I/O --------------------------------------------------------------
|
// --- Server I/O --------------------------------------------------------------
|
||||||
|
|
||||||
|
static char *
|
||||||
|
irc_process_hooks (struct server *s, char *input)
|
||||||
|
{
|
||||||
|
log_server_debug (s, "#a>> \"#S\"#r", ATTR_JOIN, input);
|
||||||
|
LIST_FOR_EACH (struct irc_hook, hook, s->ctx->irc_hooks)
|
||||||
|
{
|
||||||
|
char *processed = hook->vtable->filter (hook, s, input);
|
||||||
|
if (input == processed)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (processed == NULL)
|
||||||
|
{
|
||||||
|
log_server_debug (s, "#a>= #s#r", ATTR_JOIN, "thrown away by hook");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_server_debug (s, "#a>= \"#S\"#r", ATTR_JOIN, (input = processed));
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
static void irc_process_message
|
static void irc_process_message
|
||||||
(const struct irc_message *msg, const char *raw, void *user_data);
|
(const struct irc_message *msg, struct server *s);
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_process_buffer_custom (struct server *s, struct str *buf)
|
||||||
|
{
|
||||||
|
const char *start = buf->str, *end = start + buf->len;
|
||||||
|
for (const char *p = start; p + 1 < end; p++)
|
||||||
|
{
|
||||||
|
// Split the input on newlines
|
||||||
|
if (p[0] != '\r' || p[1] != '\n')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
char *processed = irc_process_hooks (s, xstrndup (start, p - start));
|
||||||
|
start = p + 2;
|
||||||
|
if (!processed)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
struct irc_message msg;
|
||||||
|
irc_parse_message (&msg, processed);
|
||||||
|
irc_process_message (&msg, s);
|
||||||
|
irc_free_message (&msg);
|
||||||
|
|
||||||
|
free (processed);
|
||||||
|
}
|
||||||
|
str_remove_slice (buf, 0, start - buf->str);
|
||||||
|
}
|
||||||
|
|
||||||
static enum transport_io_result
|
static enum transport_io_result
|
||||||
irc_try_read (struct server *s)
|
irc_try_read (struct server *s)
|
||||||
|
@ -4081,7 +4165,7 @@ irc_try_read (struct server *s)
|
||||||
return TRANSPORT_IO_ERROR;
|
return TRANSPORT_IO_ERROR;
|
||||||
}
|
}
|
||||||
if (s->read_buffer.len)
|
if (s->read_buffer.len)
|
||||||
irc_process_buffer (&s->read_buffer, irc_process_message, s);
|
irc_process_buffer_custom (s, &s->read_buffer);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5969,7 +6053,7 @@ irc_try_parse_welcome_for_userhost (struct server *s, const char *m)
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool process_input_utf8
|
static bool process_input_utf8
|
||||||
(struct app_context *, struct buffer *, char *, int);
|
(struct app_context *, struct buffer *, const char *, int);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
irc_on_registered (struct server *s, const char *nickname)
|
irc_on_registered (struct server *s, const char *nickname)
|
||||||
|
@ -5988,10 +6072,7 @@ irc_on_registered (struct server *s, const char *nickname)
|
||||||
if (command)
|
if (command)
|
||||||
{
|
{
|
||||||
log_server_debug (s, "Executing \"#s\"", command);
|
log_server_debug (s, "Executing \"#s\"", command);
|
||||||
|
process_input_utf8 (s->ctx, s->buffer, command, 0);
|
||||||
char *copy = xstrdup (command);
|
|
||||||
process_input_utf8 (s->ctx, s->buffer, copy, 0);
|
|
||||||
free (copy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t command_delay = get_config_integer (s->config, "command_delay");
|
int64_t command_delay = get_config_integer (s->config, "command_delay");
|
||||||
|
@ -6609,13 +6690,8 @@ irc_process_numeric (struct server *s,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
irc_process_message (const struct irc_message *msg,
|
irc_process_message (const struct irc_message *msg, struct server *s)
|
||||||
const char *raw, void *user_data)
|
|
||||||
{
|
{
|
||||||
struct server *s = user_data;
|
|
||||||
|
|
||||||
log_server_debug (s, "#a>> \"#S\"#r", ATTR_JOIN, raw);
|
|
||||||
|
|
||||||
struct irc_handler key = { .name = msg->command };
|
struct irc_handler key = { .name = msg->command };
|
||||||
struct irc_handler *handler = bsearch (&key, g_irc_handlers,
|
struct irc_handler *handler = bsearch (&key, g_irc_handlers,
|
||||||
N_ELEMENTS (g_irc_handlers), sizeof key, irc_handler_cmp_by_name);
|
N_ELEMENTS (g_irc_handlers), sizeof key, irc_handler_cmp_by_name);
|
||||||
|
@ -7135,6 +7211,73 @@ server_rename (struct app_context *ctx, struct server *s, const char *new_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Ordered linked lists ----------------------------------------------------
|
||||||
|
|
||||||
|
// This is a bit ugly since there's no way to force a guarantee that the list
|
||||||
|
// members are going to be the first ones in the structure, plus insertion is
|
||||||
|
// O(n), however I don't currently posses any better ordered data structure
|
||||||
|
|
||||||
|
struct list_header
|
||||||
|
{
|
||||||
|
LIST_HEADER (struct list_header)
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct list_header *
|
||||||
|
list_insert_ordered (struct list_header *list, struct list_header *item,
|
||||||
|
bool (*less) (const void *, const void *))
|
||||||
|
{
|
||||||
|
// Corner cases: list is empty or we precede everything
|
||||||
|
if (!list || less (item, list))
|
||||||
|
{
|
||||||
|
LIST_PREPEND (list, item);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise fast-worward to the last entry that precedes us
|
||||||
|
struct list_header *before = list;
|
||||||
|
while (before->next && less (before->next, item))
|
||||||
|
before = before->next;
|
||||||
|
|
||||||
|
// And link ourselves in between it and its successor
|
||||||
|
if ((item->next = before->next))
|
||||||
|
item->next->prev = item;
|
||||||
|
before->next = item;
|
||||||
|
item->prev = before;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Hooks -------------------------------------------------------------------
|
||||||
|
|
||||||
|
static bool
|
||||||
|
input_hook_less (const void *a, const void *b)
|
||||||
|
{
|
||||||
|
return ((const struct input_hook *) a)->priority
|
||||||
|
< ((const struct input_hook *) b)->priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
input_hook_insert (struct app_context *ctx, struct input_hook *hook)
|
||||||
|
{
|
||||||
|
ctx->input_hooks = (struct input_hook *) list_insert_ordered (
|
||||||
|
(struct list_header *) ctx->input_hooks,
|
||||||
|
(struct list_header *) hook, input_hook_less);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
irc_hook_less (const void *a, const void *b)
|
||||||
|
{
|
||||||
|
return ((const struct input_hook *) a)->priority
|
||||||
|
< ((const struct input_hook *) b)->priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_hook_insert (struct app_context *ctx, struct irc_hook *hook)
|
||||||
|
{
|
||||||
|
ctx->irc_hooks = (struct irc_hook *) list_insert_ordered (
|
||||||
|
(struct list_header *) ctx->irc_hooks,
|
||||||
|
(struct list_header *) hook, irc_hook_less);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Lua ---------------------------------------------------------------------
|
// --- Lua ---------------------------------------------------------------------
|
||||||
|
|
||||||
#ifdef HAVE_LUA
|
#ifdef HAVE_LUA
|
||||||
|
@ -8892,7 +9035,7 @@ process_alias (struct app_context *ctx, struct buffer *buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
process_input_utf8 (struct app_context *ctx, struct buffer *buffer,
|
process_input_utf8_posthook (struct app_context *ctx, struct buffer *buffer,
|
||||||
char *input, int alias_level)
|
char *input, int alias_level)
|
||||||
{
|
{
|
||||||
if (*input != '/' || *++input == '/')
|
if (*input != '/' || *++input == '/')
|
||||||
|
@ -8920,6 +9063,41 @@ process_input_utf8 (struct app_context *ctx, struct buffer *buffer,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
process_input_hooks (struct app_context *ctx, struct buffer *buffer,
|
||||||
|
char *input)
|
||||||
|
{
|
||||||
|
LIST_FOR_EACH (struct input_hook, hook, ctx->input_hooks)
|
||||||
|
{
|
||||||
|
char *processed = hook->vtable->filter (hook, buffer, input);
|
||||||
|
if (input == processed)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (processed == NULL)
|
||||||
|
{
|
||||||
|
log_global_debug (ctx, "Input thrown away by hook");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_global_debug (ctx,
|
||||||
|
"Input transformed to \"#s\"#r", (input = processed));
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
process_input_utf8 (struct app_context *ctx, struct buffer *buffer,
|
||||||
|
const char *input, int alias_level)
|
||||||
|
{
|
||||||
|
// Note that this also gets called on expanded aliases,
|
||||||
|
// which might or might not be desirable (we can forward "alias_level")
|
||||||
|
char *processed = process_input_hooks (ctx, buffer, xstrdup (input));
|
||||||
|
bool result = !processed
|
||||||
|
|| process_input_utf8_posthook (ctx, buffer, processed, alias_level);
|
||||||
|
free (processed);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_input (struct app_context *ctx, char *user_input)
|
process_input (struct app_context *ctx, char *user_input)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue