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 | ||||
| { | ||||
| 	bool no_colors;                     ///< Disable attribute printing
 | ||||
| @ -1443,6 +1479,8 @@ struct app_context | ||||
| 	int terminal_suspended;             ///< Terminal suspension level
 | ||||
| 
 | ||||
| 	struct plugin *plugins;             ///< Loaded plugins
 | ||||
| 	struct input_hook *input_hooks;     ///< Input hooks
 | ||||
| 	struct irc_hook *irc_hooks;         ///< IRC hooks
 | ||||
| } | ||||
| *g_ctx; | ||||
| 
 | ||||
| @ -4066,8 +4104,54 @@ on_irc_autojoin_timeout (void *user_data) | ||||
| 
 | ||||
| // --- 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 | ||||
| 	(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 | ||||
| irc_try_read (struct server *s) | ||||
| @ -4081,7 +4165,7 @@ irc_try_read (struct server *s) | ||||
| 		return TRANSPORT_IO_ERROR; | ||||
| 	} | ||||
| 	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; | ||||
| } | ||||
| 
 | ||||
| @ -5969,7 +6053,7 @@ irc_try_parse_welcome_for_userhost (struct server *s, const char *m) | ||||
| } | ||||
| 
 | ||||
| static bool process_input_utf8 | ||||
| 	(struct app_context *, struct buffer *, char *, int); | ||||
| 	(struct app_context *, struct buffer *, const char *, int); | ||||
| 
 | ||||
| static void | ||||
| irc_on_registered (struct server *s, const char *nickname) | ||||
| @ -5988,10 +6072,7 @@ irc_on_registered (struct server *s, const char *nickname) | ||||
| 	if (command) | ||||
| 	{ | ||||
| 		log_server_debug (s, "Executing \"#s\"", command); | ||||
| 
 | ||||
| 		char *copy = xstrdup (command); | ||||
| 		process_input_utf8 (s->ctx, s->buffer, copy, 0); | ||||
| 		free (copy); | ||||
| 		process_input_utf8 (s->ctx, s->buffer, command, 0); | ||||
| 	} | ||||
| 
 | ||||
| 	int64_t command_delay = get_config_integer (s->config, "command_delay"); | ||||
| @ -6609,13 +6690,8 @@ irc_process_numeric (struct server *s, | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| irc_process_message (const struct irc_message *msg, | ||||
| 	const char *raw, void *user_data) | ||||
| irc_process_message (const struct irc_message *msg, struct server *s) | ||||
| { | ||||
| 	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 *handler = bsearch (&key, g_irc_handlers, | ||||
| 		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 ---------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifdef HAVE_LUA | ||||
| @ -8892,7 +9035,7 @@ process_alias (struct app_context *ctx, struct buffer *buffer, | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
| { | ||||
| 	if (*input != '/' || *++input == '/') | ||||
| @ -8920,6 +9063,41 @@ process_input_utf8 (struct app_context *ctx, struct buffer *buffer, | ||||
| 	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 | ||||
| process_input (struct app_context *ctx, char *user_input) | ||||
| { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user