From 9126ce92024230461ea4bdaee374569e69576483 Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Fri, 2 Oct 2026 14:14:54 -0600 Subject: [PATCH] filter hooks by connection and context in core --- docs/plugin-api-additions/filters.md | 24 +++++++++++++ plugins/python/_zoitechat_filters.py | 36 +++++++++++++++++++ src/common/plugin.c | 52 ++++++++++++++++++++++++++++ src/common/zoitechat-plugin.h | 12 +++++++ 4 files changed, 124 insertions(+) create mode 100644 docs/plugin-api-additions/filters.md create mode 100644 plugins/python/_zoitechat_filters.py diff --git a/docs/plugin-api-additions/filters.md b/docs/plugin-api-additions/filters.md new file mode 100644 index 00000000..2c5c47ed --- /dev/null +++ b/docs/plugin-api-additions/filters.md @@ -0,0 +1,24 @@ +# Core-filtered hooks + +Python: `hook_server_filtered(name, callback, userdata=None, priority=PRI_NORM, +connection_id=None, channel=None)` and `hook_print_filtered(...)`. +Callbacks match the existing attrs variants: `(word, word_eol, userdata, attrs)`. +The connection ID distinguishes duplicate names; None/-1 means any connection +(including connection ID zero). A channel restriction uses that connection's +IRC comparator. None means any context; an empty string is an exact restriction. + +C: the corresponding zoitechat_hook_*_filtered functions accept +`(ph, name, pri, flags, connection_id, channel, callback, userdata)`; +flags=0, ID=-1 for any and NULL channel for any. Check +`get_info("api_context_filters")` for "1" before accessing the new Windows slots. + +Filtering takes place before Python/interpreter entry. Existing unrestricted +hooks, priority, EAT semantics and unload/unhook handling are unchanged. +Filters refer to the core-selected context, not an IRC target parsed from the +line: unknown queries/channels can still have a server-tab context, and QUIT +has its usual single server-hook context. Connection IDs last only this process. + +```python +zoitechat.hook_print_filtered('Channel Message', callback, + connection_id=zoitechat.get_prefs('id'), channel='#zoitechat') +``` diff --git a/plugins/python/_zoitechat_filters.py b/plugins/python/_zoitechat_filters.py new file mode 100644 index 00000000..0a7b5aee --- /dev/null +++ b/plugins/python/_zoitechat_filters.py @@ -0,0 +1,36 @@ +"""Context restrictions evaluated by core before entering Python.""" +import operator +import _zoitechat as api +from _zoitechat_embedded import ffi, lib + +__all__ = ['hook_server_filtered', 'hook_print_filtered'] + + +def _register(name, callback, userdata, priority, connection_id, channel, server): + if not callable(callback): + raise TypeError('callback must be callable') + connection_id = -1 if connection_id is None else operator.index(connection_id) + if connection_id < -1 or connection_id > 2147483647: + raise ValueError('connection_id must fit the C API; -1 means any') + plugin = api.__get_current_plugin() + hook = plugin.add_hook(callback, userdata) + register = lib.zoitechat_hook_server_filtered if server else lib.zoitechat_hook_print_filtered + dispatch = lib._on_server_attrs_hook if server else lib._on_print_attrs_hook + handle = register(lib.ph, name.encode(), priority, 0, connection_id, + ffi.NULL if channel is None else channel.encode(), dispatch, hook.handle) + if handle == ffi.NULL: + hook.is_unload = True + plugin.remove_hook(id(hook)) + raise RuntimeError('unable to install filtered hook') + hook.zoitechat_hook = handle + return id(hook) + + +def hook_server_filtered(name, callback, userdata=None, priority=api.PRI_NORM, + connection_id=None, channel=None): + return _register(name, callback, userdata, priority, connection_id, channel, True) + + +def hook_print_filtered(name, callback, userdata=None, priority=api.PRI_NORM, + connection_id=None, channel=None): + return _register(name, callback, userdata, priority, connection_id, channel, False) diff --git a/src/common/plugin.c b/src/common/plugin.c index a681fb8e..ef87e361 100644 --- a/src/common/plugin.c +++ b/src/common/plugin.c @@ -99,6 +99,9 @@ struct _zoitechat_hook int tag; /* for timers & FDs only */ int type; /* HOOK_* */ int pri; /* fd */ /* priority / fd for HOOK_FD only */ + int connection_filter; + char *channel_filter; + gboolean context_filtered; }; struct _zoitechat_list @@ -352,6 +355,8 @@ plugin_add (session *sess, char *filename, void *handle, void *init_func, pl->zoitechat_emit_print_attrs = zoitechat_emit_print_attrs; pl->zoitechat_event_attrs_create = zoitechat_event_attrs_create; pl->zoitechat_event_attrs_free = zoitechat_event_attrs_free; + pl->zoitechat_hook_server_filtered = zoitechat_hook_server_filtered; + pl->zoitechat_hook_print_filtered = zoitechat_hook_print_filtered; pl->zoitechat_hook_print_after = zoitechat_hook_print_after; /* run zoitechat_plugin_init, if it returns 0, close the plugin */ @@ -623,6 +628,15 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[], hook = list->data; next = list->next; + if (hook->context_filtered && + (!is_session (sess) || + (hook->connection_filter != -1 && hook->connection_filter != sess->server->id) || + (hook->channel_filter && sess->server->p_cmp (hook->channel_filter, sess->channel)))) + { + list = next; + continue; /* filter in C before entering an interpreter */ + } + if (type == HOOK_PRINT_AFTER && hook->tag) { list = next; @@ -1052,6 +1066,8 @@ zoitechat_unhook (zoitechat_plugin *ph, zoitechat_hook *hook) g_free (hook->name); /* NULL for timers & fds */ g_free (hook->help_text); /* NULL for non-commands */ + g_free (hook->channel_filter); + hook->channel_filter = NULL; return hook->userdata; } @@ -1065,6 +1081,39 @@ zoitechat_hook_print_after (zoitechat_plugin *ph, const char *name, int pri, return plugin_add_hook (ph, HOOK_PRINT_AFTER, pri, name, NULL, callback, 0, userdata); } +static zoitechat_hook * +plugin_add_filtered_hook (zoitechat_plugin *ph, const char *name, int pri, + int flags, int connection_id, const char *channel, + int type, void *callback, void *userdata) +{ + zoitechat_hook *hook; + if (flags != 0 || connection_id < -1 || !name || !callback) + return NULL; + hook = plugin_add_hook (ph, type, pri, name, NULL, callback, 0, userdata); + hook->context_filtered = TRUE; + hook->connection_filter = connection_id; + hook->channel_filter = g_strdup (channel); + return hook; +} + +zoitechat_hook * +zoitechat_hook_server_filtered (zoitechat_plugin *ph, const char *name, int pri, + int flags, int connection_id, const char *channel, + zoitechat_serv_attrs_cb *callback, void *userdata) +{ + return plugin_add_filtered_hook (ph, name, pri, flags, connection_id, channel, + HOOK_SERVER_ATTRS, callback, userdata); +} + +zoitechat_hook * +zoitechat_hook_print_filtered (zoitechat_plugin *ph, const char *name, int pri, + int flags, int connection_id, const char *channel, + zoitechat_print_attrs_cb *callback, void *userdata) +{ + return plugin_add_filtered_hook (ph, name, pri, flags, connection_id, channel, + HOOK_PRINT_ATTRS, callback, userdata); +} + zoitechat_hook * zoitechat_hook_command (zoitechat_plugin *ph, const char *name, int pri, zoitechat_cmd_cb *callb, const char *help_text, void *userdata) @@ -1237,6 +1286,9 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id) if (strcmp (id, "api_hook_print_after") == 0) return "1"; + if (strcmp (id, "api_context_filters") == 0) + return "1"; + hash = str_hash (id); /* do the session independant ones first */ switch (hash) diff --git a/src/common/zoitechat-plugin.h b/src/common/zoitechat-plugin.h index ba18ab80..92a64078 100644 --- a/src/common/zoitechat-plugin.h +++ b/src/common/zoitechat-plugin.h @@ -212,6 +212,16 @@ zoitechat_hook *zoitechat_hook_print_after (zoitechat_plugin *ph, int (*callback) (char *word[], zoitechat_event_attrs *attrs, void *userdata), void *userdata); +/* api_context_filters=1: connection_id -1 is any; NULL channel is any. */ +zoitechat_hook *zoitechat_hook_server_filtered (zoitechat_plugin *ph, + const char *name, int pri, int flags, int connection_id, const char *channel, + int (*callback) (char *word[], char *word_eol[], zoitechat_event_attrs *attrs, void *userdata), + void *userdata); +zoitechat_hook *zoitechat_hook_print_filtered (zoitechat_plugin *ph, + const char *name, int pri, int flags, int connection_id, const char *channel, + int (*callback) (char *word[], zoitechat_event_attrs *attrs, void *userdata), + void *userdata); + zoitechat_hook * zoitechat_hook_command (zoitechat_plugin *ph, const char *name, @@ -431,6 +441,8 @@ zoitechat_pluginpref_list (zoitechat_plugin *ph, #define zoitechat_hook_server ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server) #define zoitechat_hook_server_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server_attrs) #define zoitechat_hook_print ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print) +#define zoitechat_hook_server_filtered ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server_filtered) +#define zoitechat_hook_print_filtered ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_filtered) #define zoitechat_hook_print_after ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_after) #define zoitechat_hook_print_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_attrs) #define zoitechat_hook_timer ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_timer)