Compare commits

..
Author SHA1 Message Date
deepend 111b1fa0ad negotiate explicitly registered IRC capabilities
(cherry picked from commit c630730a1d)
2026-11-08 07:12:05 -06:00
deepend 2355ffc5d0 reserve additive API slots and load optional Python modules
(cherry picked from commit 1cdee8ea6b)
2026-11-08 06:00:01 -06:00
13 changed files with 305 additions and 11 deletions

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
# Script-supported capabilities
`register_capability(name, userdata=None, connection_id=None)` returns a normal
hook handle. Register before connecting/reconnecting. Core requests the name only
if advertised in CAP LS or CAP NEW. Existing built-in requests remain enabled;
SASL and STS registration is rejected so scripts cannot change authentication or
transport policy through this API. None/-1 means any connection (zero is a valid
connection ID). Names are lowercase ASCII alphanumerics plus - . / _, 1-200 bytes.
Duplicate registrations do not duplicate requests for an advertised token.
`unhook(handle)` or script/plugin unload removes future request ownership. It
intentionally does not disable a capability already acknowledged on a live
connection, because another plugin/core may depend on it. Registering after CAP
LS does not retrospectively negotiate an already-advertised capability: reconnect
or wait for CAP NEW. Core retains ownership of CAP END; this API does not extend
SASL waiting or delay registration awaiting plugin-only CAP ACKs.
`hook_capability(name, callback, userdata=None, priority=PRI_NORM)` independently
observes negotiation through existing raw CAP hooks. Callback receives
`(CapabilityEvent, userdata)` with name, subcommand, value, enabled, context and
connection_id. LS/NEW mean advertised (`enabled=None`), ACK/LIST mean enabled
(unless a negative ACK), NAK means rejected and DEL means removed. Callback
returns are ignored so this observer cannot eat core negotiation. Existing raw
hooks may still eat CAP before this hook according to their normal priorities.
C: `zoitechat_register_capability(ph, name, flags, connection_id, userdata)`;
flags=0, ID=-1 for any. Check get_info("api_plugin_caps") for "1" before using
the appended Windows slot. Use existing server CAP hooks for notifications.
Requests are split at complete token boundaries within the existing wire limit.
```python
registration = zoitechat.register_capability('draft/example')
zoitechat.hook_capability('draft/example', lambda event, data:
event.context.prnt('{}: {}'.format(event.subcommand, event.enabled)))
```
@@ -0,0 +1,24 @@
# Optional plugin API additions (discussion #144)
Existing hooks, callbacks, return values, context lookup, strings and menu behaviour
remain unchanged. The C plugin function table gains four fixed append-only slots;
unused slots are NULL. Older binaries use the unchanged prefix. A plugin must check
the documented get_info() feature key before accessing a new slot on an older host.
New Python modules are installed by Meson and the existing Windows copy/installer
pipeline. Only modules present in the installation are exported; imports of
zoitechat, hexchat and xchat retain their compatibility aliases.
All APIs run on the client main thread unless explicitly documented otherwise.
New reserved flags must be zero. These changes do not make old API calls thread-safe.
Nested hook dispatch defers deleted-hook reclamation until its outermost return,
so new observers can safely emit events and unhook themselves or other hooks.
The optional asyncio module requires Python 3.7 or newer; older supported
interpreters skip that module and retain the existing scripting API.
Python reload drops the script/API modules while retaining the embedding module
so callback and lib.ph pointer types keep the same CFFI identity.
Server-hook insertion matches both raw and attrs hooks so documented priorities
are honoured for mixed registrations, including new filtered attrs callbacks.
+21
View File
@@ -383,3 +383,24 @@ def find_context(server=None, channel=None):
return None return None
return Context(ctx) return Context(ctx)
# Optional additive APIs live in separate modules so builds can select features.
def __load_additive_features():
import importlib
import os
for suffix in ('fd', 'events', 'async', 'names', 'contexts',
'post', 'filters', 'caps'):
if suffix == 'async' and sys.version_info < (3, 7):
continue
name = '_zoitechat_' + suffix
if not os.path.isfile(os.path.join(os.path.dirname(__file__), name + '.py')):
continue
module = importlib.import_module(name)
for symbol in module.__all__:
globals()[symbol] = getattr(module, symbol)
__all__.append(symbol)
__load_additive_features()
+64
View File
@@ -0,0 +1,64 @@
"""Script-owned capability requests and structured negotiation observations."""
from collections import namedtuple
import operator
import _zoitechat as api
from _zoitechat_embedded import ffi, lib
__all__ = ['CapabilityEvent', 'register_capability', 'hook_capability']
CapabilityEvent = namedtuple('CapabilityEvent',
'name subcommand value enabled context connection_id')
def register_capability(name, userdata=None, connection_id=None):
"""Request an advertised capability on next negotiation; unhook releases ownership."""
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(None, userdata)
handle = lib.zoitechat_register_capability(lib.ph, name.encode(), 0,
connection_id, hook.handle)
if handle == ffi.NULL:
hook.is_unload = True
plugin.remove_hook(id(hook))
raise ValueError('invalid capability name, reserved capability or connection ID')
hook.zoitechat_hook = handle
return id(hook)
def hook_capability(name, callback, userdata=None, priority=api.PRI_NORM):
"""Observe LS/NEW/ACK/NAK/DEL/LIST; this hook does not itself request the capability."""
if not callable(callback):
raise TypeError('callback must be callable')
def dispatch(word, word_eol, data):
if not word_eol:
return api.EAT_NONE
line = word_eol[0]
if line.startswith(':'):
unused, separator, line = line.partition(' ')
parts = line.split(' ', 3)
if len(parts) != 4 or parts[0].upper() != 'CAP':
return api.EAT_NONE
subcommand, tokens = parts[2].upper(), parts[3]
if subcommand not in ('LS', 'NEW', 'ACK', 'NAK', 'DEL', 'LIST'):
return api.EAT_NONE
if tokens.startswith('* '):
tokens = tokens[2:]
tokens = tokens.lstrip(':')
for token in tokens.split():
negative = token.startswith('-')
cap, equals, value = token.lstrip('-').partition('=')
if cap != name:
continue
enabled = None
if subcommand in ('ACK', 'LIST'):
enabled = not negative
elif subcommand in ('DEL', 'NAK'):
enabled = False
event = CapabilityEvent(cap, subcommand, value if equals else None,
enabled, api.get_context(), api.get_prefs('id'))
callback(event, data)
return api.EAT_NONE # observing CAP must not suppress core negotiation
return api.hook_server('CAP', dispatch, userdata, priority)
+5 -1
View File
@@ -27,7 +27,11 @@ python3_source = custom_target('python-bindings',
command: [find_program('generate_plugin.py'), '@INPUT@', '@OUTPUT@'] command: [find_program('generate_plugin.py'), '@INPUT@', '@OUTPUT@']
) )
install_data(['_zoitechat.py', 'zoitechat.py', 'xchat.py', 'hexchat.py'], # Include any selected additive API modules alongside the compatibility modules.
python_api_files = run_command(python3, '-c',
'import glob,sys; print("\\n".join(sorted(glob.glob(sys.argv[1] + "/_zoitechat*.py"))))',
meson.current_source_dir(), check: true).stdout().strip().split('\n')
install_data(python_api_files + ['zoitechat.py', 'xchat.py', 'hexchat.py'],
install_dir: join_paths(get_option('libdir'), 'zoitechat/python') install_dir: join_paths(get_option('libdir'), 'zoitechat/python')
) )
+6 -1
View File
@@ -590,7 +590,12 @@ def _on_plugin_deinit():
sys.stderr = sys.__stderr__ sys.stderr = sys.__stderr__
pydoc.help = pydoc.Helper() pydoc.help = pydoc.Helper()
for mod in ('_zoitechat', 'zoitechat', 'xchat', '_zoitechat_embedded'): # CFFI callbacks keep the embedding module's ffi/lib identity for the
# process lifetime; deleting it creates incompatible types on reload.
for mod in ('_zoitechat', 'zoitechat', 'hexchat', 'xchat',
'_zoitechat_fd', '_zoitechat_events', '_zoitechat_async',
'_zoitechat_names', '_zoitechat_contexts', '_zoitechat_post',
'_zoitechat_filters', '_zoitechat_caps'):
try: try:
del sys.modules[mod] del sys.modules[mod]
+1
View File
@@ -52,6 +52,7 @@
<None Include="xchat.py" /> <None Include="xchat.py" />
<None Include="hexchat.py" /> <None Include="hexchat.py" />
<None Include="_zoitechat.py" /> <None Include="_zoitechat.py" />
<None Include="_zoitechat_*.py" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="$(IntDir)python.c" /> <ClCompile Include="$(IntDir)python.c" />
+1
View File
@@ -52,6 +52,7 @@
<None Include="xchat.py" /> <None Include="xchat.py" />
<None Include="hexchat.py" /> <None Include="hexchat.py" />
<None Include="_zoitechat.py" /> <None Include="_zoitechat.py" />
<None Include="_zoitechat_*.py" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="$(IntDir)python.c" /> <ClCompile Include="$(IntDir)python.c" />
+72 -6
View File
@@ -41,6 +41,8 @@
#include "network.h" #include "network.h"
#include "notify.h" #include "notify.h"
#include "outbound.h" #include "outbound.h"
#include <gmodule.h>
#include "plugin.h"
#include "inbound.h" #include "inbound.h"
#include "server.h" #include "server.h"
#include "servlist.h" #include "servlist.h"
@@ -1980,6 +1982,22 @@ inbound_cap_ack (server *serv, char *nick, char *extensions,
inbound_toggle_caps (serv, extensions, TRUE); inbound_toggle_caps (serv, extensions, TRUE);
} }
/* Keep CAP REQ tokens intact and below the existing 500-byte buffer. */
static void
inbound_cap_request_append (server *serv, char *buffer, gsize size,
const char *name, time_t timestamp)
{
if (strlen (buffer) + strlen (name) + 1 >= size)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session,
buffer + 9, NULL, NULL, NULL, 0, timestamp);
tcp_sendf (serv, "%s\r\n", g_strchomp (buffer));
g_strlcpy (buffer, "CAP REQ :", size);
}
g_strlcat (buffer, name, size);
g_strlcat (buffer, " ", size);
}
static void inbound_request_new_caps (server *serv, const char *extensions_str, static void inbound_request_new_caps (server *serv, const char *extensions_str,
const message_tags_data *tags_data); const message_tags_data *tags_data);
@@ -2094,6 +2112,32 @@ inbound_request_new_caps (server *serv, const char *extensions_str,
break; break;
} }
} }
}
/* Also request plugin-owned capabilities, without repeating core requests. */
for (i = 0; extensions[i]; i++)
{
gsize previous;
gboolean builtin = FALSE;
for (x = 0; x < G_N_ELEMENTS (supported_caps); x++)
{
if (!strcmp (extensions[i], supported_caps[x]))
{
builtin = TRUE;
break;
}
}
if (builtin || !plugin_requests_capability (serv, extensions[i]))
continue;
for (previous = 0; previous < i; previous++)
{
if (!strcmp (extensions[previous], extensions[i]))
break;
}
if (previous != i)
continue;
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session,
extensions[i], NULL, NULL, NULL, 0, tags_data->timestamp);
tcp_sendf (serv, "CAP REQ :%s\r\n", extensions[i]);
} }
g_strfreev (extensions); g_strfreev (extensions);
} }
@@ -2161,6 +2205,8 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
gboolean want_cap = FALSE; /* format the CAP REQ string based on previous capabilities being requested or not */ gboolean want_cap = FALSE; /* format the CAP REQ string based on previous capabilities being requested or not */
gboolean sts_upgrade_triggered = FALSE; gboolean sts_upgrade_triggered = FALSE;
char **extensions; char **extensions;
GString *requests = g_string_new (NULL);
GHashTable *seen = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
int i; int i;
if (g_str_has_prefix (extensions_str, "* ")) if (g_str_has_prefix (extensions_str, "* "))
@@ -2226,16 +2272,23 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
} }
want_cap = TRUE; want_cap = TRUE;
serv->waiting_on_sasl = TRUE; serv->waiting_on_sasl = TRUE;
g_strlcat (buffer, "sasl ", sizeof(buffer)); if (g_hash_table_add (seen, g_strdup ("sasl")))
g_string_append (requests, "sasl ");
continue; continue;
} }
for (x = 0; x < G_N_ELEMENTS (supported_caps); ++x) for (x = 0; x < G_N_ELEMENTS (supported_caps); ++x)
{ {
if (!g_strcmp0 (extension, supported_caps[x])) if (!g_strcmp0 (extension, supported_caps[x]))
break;
}
if (x < G_N_ELEMENTS (supported_caps) ||
plugin_requests_capability (serv, extension))
{ {
g_strlcat (buffer, extension, sizeof(buffer)); if (g_hash_table_add (seen, g_strdup (extension)))
g_strlcat (buffer, " ", sizeof(buffer)); {
g_string_append (requests, extension);
g_string_append_c (requests, ' ');
want_cap = TRUE; want_cap = TRUE;
} }
} }
@@ -2245,17 +2298,30 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
if (sts_upgrade_triggered) if (sts_upgrade_triggered)
{ {
g_hash_table_destroy (seen);
g_string_free (requests, TRUE);
return; return;
} }
if (want_cap) if (want_cap)
{ {
/* buffer + 9 = emit buffer without "CAP REQ :" */ char **tokens = g_strsplit (requests->str, " ", 0);
for (i = 0; tokens[i]; i++)
{
if (tokens[i][0])
inbound_cap_request_append (serv, buffer, sizeof (buffer),
tokens[i], tags_data->timestamp);
}
g_strfreev (tokens);
if (buffer[9])
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session, EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session,
buffer + 9, NULL, NULL, NULL, 0, buffer + 9, NULL, NULL, NULL, 0, tags_data->timestamp);
tags_data->timestamp);
tcp_sendf (serv, "%s\r\n", g_strchomp (buffer)); tcp_sendf (serv, "%s\r\n", g_strchomp (buffer));
} }
}
g_hash_table_destroy (seen);
g_string_free (requests, TRUE);
if (!serv->waiting_on_sasl && !serv->waiting_on_cap) if (!serv->waiting_on_sasl && !serv->waiting_on_cap)
{ {
/* if we use SASL, CAP END is dealt via raw numerics */ /* if we use SASL, CAP END is dealt via raw numerics */
+50 -3
View File
@@ -99,6 +99,7 @@ struct _zoitechat_hook
int tag; /* for timers & FDs only */ int tag; /* for timers & FDs only */
int type; /* HOOK_* */ int type; /* HOOK_* */
int pri; /* fd */ /* priority / fd for HOOK_FD only */ int pri; /* fd */ /* priority / fd for HOOK_FD only */
int cap_connection_id;
}; };
struct _zoitechat_list struct _zoitechat_list
@@ -143,7 +144,8 @@ enum
HOOK_PRINT_ATTRS = 1 << 4, /* same as above, with attributes */ HOOK_PRINT_ATTRS = 1 << 4, /* same as above, with attributes */
HOOK_TIMER = 1 << 5, /* timeouts */ HOOK_TIMER = 1 << 5, /* timeouts */
HOOK_FD = 1 << 6, /* sockets & fds */ HOOK_FD = 1 << 6, /* sockets & fds */
HOOK_DELETED = 1 << 7 /* marked for deletion */ HOOK_DELETED = 1 << 7, /* marked for deletion */
HOOK_CAPABILITY = 1 << 9 /* script-owned CAP requests */
}; };
enum enum
@@ -176,6 +178,7 @@ enum
GSList *plugin_list = NULL; /* export for plugingui.c */ GSList *plugin_list = NULL; /* export for plugingui.c */
static GSList *hook_list = NULL; static GSList *hook_list = NULL;
static unsigned int plugin_dispatch_depth;
extern const struct prefs vars[]; /* cfgfiles.c */ extern const struct prefs vars[]; /* cfgfiles.c */
@@ -243,7 +246,7 @@ plugin_list_add (zoitechat_context *ctx, char *filename, const char *name,
{ {
zoitechat_plugin *pl; zoitechat_plugin *pl;
pl = g_new (zoitechat_plugin, 1); pl = g_new0 (zoitechat_plugin, 1);
pl->handle = handle; pl->handle = handle;
pl->filename = filename; pl->filename = filename;
pl->context = ctx; pl->context = ctx;
@@ -350,6 +353,7 @@ plugin_add (session *sess, char *filename, void *handle, void *init_func,
pl->zoitechat_emit_print_attrs = zoitechat_emit_print_attrs; pl->zoitechat_emit_print_attrs = zoitechat_emit_print_attrs;
pl->zoitechat_event_attrs_create = zoitechat_event_attrs_create; pl->zoitechat_event_attrs_create = zoitechat_event_attrs_create;
pl->zoitechat_event_attrs_free = zoitechat_event_attrs_free; pl->zoitechat_event_attrs_free = zoitechat_event_attrs_free;
pl->zoitechat_register_capability = zoitechat_register_capability;
/* run zoitechat_plugin_init, if it returns 0, close the plugin */ /* run zoitechat_plugin_init, if it returns 0, close the plugin */
if (((zoitechat_init_func *)init_func) (pl, &pl->name, &pl->desc, &pl->version, arg) == 0) if (((zoitechat_init_func *)init_func) (pl, &pl->name, &pl->desc, &pl->version, arg) == 0)
@@ -610,6 +614,7 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
zoitechat_hook *hook; zoitechat_hook *hook;
int ret, eat = 0; int ret, eat = 0;
plugin_dispatch_depth++;
list = hook_list; list = hook_list;
while (1) while (1)
{ {
@@ -655,6 +660,10 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
} }
xit: xit:
/* Nested callbacks can unhook entries still used by an outer dispatch.
* Keep both hook objects and list links alive until the outermost return. */
if (--plugin_dispatch_depth != 0)
return eat;
/* really remove deleted hooks now */ /* really remove deleted hooks now */
list = hook_list; list = hook_list;
while (list) while (list)
@@ -806,7 +815,7 @@ plugin_insert_hook (zoitechat_hook *new_hook)
break; break;
case HOOK_SERVER: case HOOK_SERVER:
case HOOK_SERVER_ATTRS: case HOOK_SERVER_ATTRS:
new_hook_type = HOOK_SERVER | HOOK_PRINT_ATTRS; new_hook_type = HOOK_SERVER | HOOK_SERVER_ATTRS;
break; break;
default: default:
new_hook_type = new_hook->type; new_hook_type = new_hook->type;
@@ -1022,6 +1031,41 @@ zoitechat_unhook (zoitechat_plugin *ph, zoitechat_hook *hook)
return hook->userdata; return hook->userdata;
} }
zoitechat_hook *
zoitechat_register_capability (zoitechat_plugin *ph, const char *name, int flags,
int connection_id, void *userdata)
{
const unsigned char *p;
zoitechat_hook *hook;
if (flags != 0 || connection_id < -1 || !name || !name[0] || strlen (name) > 200 ||
strcmp (name, "sasl") == 0 || strcmp (name, "sts") == 0)
return NULL;
for (p = (const unsigned char *)name; *p; p++)
{
if (!g_ascii_islower (*p) && !g_ascii_isdigit (*p) &&
*p != '-' && *p != '.' && *p != '/' && *p != '_')
return NULL;
}
hook = plugin_add_hook (ph, HOOK_CAPABILITY, 0, name, NULL, NULL, 0, userdata);
hook->cap_connection_id = connection_id;
return hook;
}
gboolean
plugin_requests_capability (server *serv, const char *name)
{
GSList *entry;
for (entry = hook_list; entry; entry = entry->next)
{
zoitechat_hook *hook = entry->data;
if (hook && hook->type == HOOK_CAPABILITY &&
(hook->cap_connection_id == -1 || hook->cap_connection_id == serv->id) &&
strcmp (hook->name, name) == 0)
return TRUE;
}
return FALSE;
}
zoitechat_hook * zoitechat_hook *
zoitechat_hook_command (zoitechat_plugin *ph, const char *name, int pri, zoitechat_hook_command (zoitechat_plugin *ph, const char *name, int pri,
zoitechat_cmd_cb *callb, const char *help_text, void *userdata) zoitechat_cmd_cb *callb, const char *help_text, void *userdata)
@@ -1181,6 +1225,9 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id)
return text_find_format_string (e); return text_find_format_string (e);
} }
if (strcmp (id, "api_plugin_caps") == 0)
return "1";
hash = str_hash (id); hash = str_hash (id);
/* do the session independant ones first */ /* do the session independant ones first */
switch (hash) switch (hash)
+10
View File
@@ -150,6 +150,15 @@ struct _zoitechat_plugin
void (*zoitechat_event_attrs_free) (zoitechat_plugin *ph, void (*zoitechat_event_attrs_free) (zoitechat_plugin *ph,
zoitechat_event_attrs *attrs); zoitechat_event_attrs *attrs);
/* Additive API slots: append only; old binary offsets are unchanged. */
zoitechat_hook *(*zoitechat_hook_print_after) (zoitechat_plugin *, const char *, int,
int, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_hook_server_filtered) (zoitechat_plugin *, const char *, int,
int, int, const char *, int (*)(char *[], char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_hook_print_filtered) (zoitechat_plugin *, const char *, int,
int, int, const char *, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_register_capability) (zoitechat_plugin *, const char *, int, int, void *);
/* PRIVATE FIELDS! */ /* PRIVATE FIELDS! */
void *handle; /* from dlopen */ void *handle; /* from dlopen */
char *filename; /* loaded from */ char *filename; /* loaded from */
@@ -174,6 +183,7 @@ int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol
int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[], int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[],
time_t server_time); time_t server_time);
int plugin_emit_print (session *sess, char *word[], time_t server_time); int plugin_emit_print (session *sess, char *word[], time_t server_time);
gboolean plugin_requests_capability (server *serv, const char *name);
int plugin_emit_dummy_print (session *sess, char *name); int plugin_emit_dummy_print (session *sess, char *name);
int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gunichar key); int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gunichar key);
GList* plugin_command_list(GList *tmp_list); GList* plugin_command_list(GList *tmp_list);
+14
View File
@@ -193,10 +193,23 @@ struct _zoitechat_plugin
zoitechat_event_attrs *(*zoitechat_event_attrs_create) (zoitechat_plugin *ph); zoitechat_event_attrs *(*zoitechat_event_attrs_create) (zoitechat_plugin *ph);
void (*zoitechat_event_attrs_free) (zoitechat_plugin *ph, void (*zoitechat_event_attrs_free) (zoitechat_plugin *ph,
zoitechat_event_attrs *attrs); zoitechat_event_attrs *attrs);
/* Additive API slots: append only; old binary offsets are unchanged. */
zoitechat_hook *(*zoitechat_hook_print_after) (zoitechat_plugin *, const char *, int,
int, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_hook_server_filtered) (zoitechat_plugin *, const char *, int,
int, int, const char *, int (*)(char *[], char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_hook_print_filtered) (zoitechat_plugin *, const char *, int,
int, int, const char *, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_register_capability) (zoitechat_plugin *, const char *, int, int, void *);
}; };
#endif #endif
/* api_plugin_caps=1: register before CAP LS; flags=0, connection_id=-1 for any. */
zoitechat_hook *zoitechat_register_capability (zoitechat_plugin *ph,
const char *name, int flags, int connection_id, void *userdata);
zoitechat_hook * zoitechat_hook *
zoitechat_hook_command (zoitechat_plugin *ph, zoitechat_hook_command (zoitechat_plugin *ph,
const char *name, const char *name,
@@ -416,6 +429,7 @@ zoitechat_pluginpref_list (zoitechat_plugin *ph,
#define zoitechat_hook_server ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server) #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_server_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server_attrs)
#define zoitechat_hook_print ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print) #define zoitechat_hook_print ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print)
#define zoitechat_register_capability ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_register_capability)
#define zoitechat_hook_print_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_attrs) #define zoitechat_hook_print_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_attrs)
#define zoitechat_hook_timer ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_timer) #define zoitechat_hook_timer ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_timer)
#define zoitechat_hook_fd ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_fd) #define zoitechat_hook_fd ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_fd)
+2
View File
@@ -90,6 +90,7 @@
<MSWindowsTheme Include="$(DepsRoot)\share\themes\MS-Windows\**\*" /> <MSWindowsTheme Include="$(DepsRoot)\share\themes\MS-Windows\**\*" />
<HicolorIcons Include="$(DepsRoot)\share\icons\hicolor\index.theme" /> <HicolorIcons Include="$(DepsRoot)\share\icons\hicolor\index.theme" />
<HicolorIcons Include="$(DepsRoot)\share\icons\hicolor\**\*.*" /> <HicolorIcons Include="$(DepsRoot)\share\icons\hicolor\**\*.*" />
<PythonAdditiveApis Include="..\..\plugins\python\_zoitechat_*.py" />
</ItemGroup> </ItemGroup>
<Copy SourceFiles="@(None)" DestinationFolder="$(ZoiteChatRel)" /> <Copy SourceFiles="@(None)" DestinationFolder="$(ZoiteChatRel)" />
@@ -115,6 +116,7 @@
<Copy SourceFiles="..\..\plugins\python\hexchat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" /> <Copy SourceFiles="..\..\plugins\python\hexchat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
<Copy SourceFiles="..\..\plugins\python\zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" /> <Copy SourceFiles="..\..\plugins\python\zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
<Copy SourceFiles="..\..\plugins\python\_zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" /> <Copy SourceFiles="..\..\plugins\python\_zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
<Copy SourceFiles="@(PythonAdditiveApis)" DestinationFolder="$(ZoiteChatRel)\python" Condition="('$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true') and '@(PythonAdditiveApis)' != ''" />
<WriteLinesToFile File="$(ZoiteChatRel)portable-mode" Lines="2" Overwrite="true" /> <WriteLinesToFile File="$(ZoiteChatRel)portable-mode" Lines="2" Overwrite="true" />