mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-11-08 17:05:38 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c93bb0f0 |
No files matched your search
@@ -0,0 +1,26 @@
|
|||||||
|
# Semantic IRC hooks
|
||||||
|
|
||||||
|
`on_join`, `on_part`, `on_quit`, `on_nick`, `on_kick`, `on_message`, and
|
||||||
|
`on_notice` accept `(callback, userdata=None, priority=PRI_NORM)`. The general
|
||||||
|
form is `hook_event(command, callback, userdata=None, priority=PRI_NORM)`.
|
||||||
|
Callbacks receive `(event, userdata)`, where the immutable IRCEvent has
|
||||||
|
command, prefix, nick, user, host, params, context, connection_id and time.
|
||||||
|
`params` preserves the trailing parameter, including spaces; JOIN parameters
|
||||||
|
also retain extended-join account/realname data when supplied by the server.
|
||||||
|
|
||||||
|
These wrappers use existing server hooks, before core command processing;
|
||||||
|
return the ordinary EAT_* constants. EAT_ZOITECHAT suppresses the entire IRC
|
||||||
|
command (including client state updates), not just its text. Use EAT_NONE for
|
||||||
|
observation. Other plugins' priority and eating rules remain unchanged.
|
||||||
|
The context is the core-selected context, which can be the server tab when a
|
||||||
|
query/channel does not yet exist. QUIT is one event per incoming command,
|
||||||
|
not one callback per affected channel. `event.time` is the existing server-time
|
||||||
|
attribute (zero when unavailable); other message tags are not exposed here.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def joined(event, userdata):
|
||||||
|
event.context.prnt('{} joined {}'.format(event.nick, event.params[0]))
|
||||||
|
return zoitechat.EAT_NONE
|
||||||
|
|
||||||
|
zoitechat.on_join(joined)
|
||||||
|
```
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Explicit IRC comparison keys
|
|
||||||
|
|
||||||
`IRCName(text, context=None, casemapping=None, connection_id=None)` is an
|
|
||||||
immutable helper, not a str subclass. Construct it explicitly for dictionary/set
|
|
||||||
keys; existing get_info/list/event values remain normal strings.
|
|
||||||
Equality and hashing include the connection ID and captured casemapping, so
|
|
||||||
identical-looking names on different connections are different keys. An IRCName
|
|
||||||
never compares equal to a plain str. `str(key)` / `key.text` recover the spelling.
|
|
||||||
|
|
||||||
`irc_casefold(text, casemapping='rfc1459')` supports ascii, strict-rfc1459 and
|
|
||||||
rfc1459. Only ASCII IRC characters fold; Unicode casefold would be incorrect.
|
|
||||||
The new `get_info('casemapping')` reports the comparator actually used by core.
|
|
||||||
Current master handles ASCII and RFC1459; this patch does not change its handling
|
|
||||||
of advertised strict-rfc1459. An explicit mapping is available for scripts that
|
|
||||||
need strict-rfc1459 independently. Keys capture their mapping so hashes stay
|
|
||||||
stable; rebuild a collection if the connection's mapping changes.
|
|
||||||
|
|
||||||
```python
|
|
||||||
seen = {zoitechat.IRCName('[SomeNick]')}
|
|
||||||
assert zoitechat.IRCName('{somenick}') in seen # RFC1459 connection
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
"""Structured, opt-in hooks before the IRC command's core processing."""
|
||||||
|
from collections import namedtuple
|
||||||
|
import _zoitechat as api
|
||||||
|
|
||||||
|
__all__ = ['IRCEvent', 'hook_event', 'on_join', 'on_part', 'on_quit',
|
||||||
|
'on_nick', 'on_kick', 'on_message', 'on_notice']
|
||||||
|
IRCEvent = namedtuple('IRCEvent', 'command prefix nick user host params context connection_id time')
|
||||||
|
_SUPPORTED = frozenset(('JOIN', 'PART', 'QUIT', 'NICK', 'KICK', 'PRIVMSG', 'NOTICE'))
|
||||||
|
|
||||||
|
|
||||||
|
def _parse(line):
|
||||||
|
prefix = ''
|
||||||
|
if line.startswith(':'):
|
||||||
|
prefix, separator, line = line[1:].partition(' ')
|
||||||
|
if not separator:
|
||||||
|
return None
|
||||||
|
command, separator, rest = line.partition(' ')
|
||||||
|
params = []
|
||||||
|
while rest:
|
||||||
|
rest = rest.lstrip(' ')
|
||||||
|
if rest.startswith(':'):
|
||||||
|
params.append(rest[1:])
|
||||||
|
break
|
||||||
|
param, separator, rest = rest.partition(' ')
|
||||||
|
if param:
|
||||||
|
params.append(param)
|
||||||
|
nick, marker, userhost = prefix.partition('!')
|
||||||
|
user, marker, host = userhost.partition('@')
|
||||||
|
return command.upper(), prefix, nick, user, host, tuple(params)
|
||||||
|
|
||||||
|
|
||||||
|
def hook_event(command, callback, userdata=None, priority=api.PRI_NORM):
|
||||||
|
"""callback(event, userdata) returns existing EAT_* constants."""
|
||||||
|
command = command.upper()
|
||||||
|
if command not in _SUPPORTED:
|
||||||
|
raise ValueError('unsupported semantic event: ' + command)
|
||||||
|
if not callable(callback):
|
||||||
|
raise TypeError('callback must be callable')
|
||||||
|
|
||||||
|
def dispatch(word, word_eol, data, attrs):
|
||||||
|
if not word_eol:
|
||||||
|
return api.EAT_NONE
|
||||||
|
parsed = _parse(word_eol[0])
|
||||||
|
if parsed is None or parsed[0] != command:
|
||||||
|
return api.EAT_NONE
|
||||||
|
event = IRCEvent(*(parsed + (api.get_context(), api.get_prefs('id'), attrs.time)))
|
||||||
|
return callback(event, data)
|
||||||
|
|
||||||
|
return api.hook_server_attrs(command, dispatch, userdata, priority)
|
||||||
|
|
||||||
|
|
||||||
|
def on_join(callback, userdata=None, priority=api.PRI_NORM):
|
||||||
|
return hook_event('JOIN', callback, userdata, priority)
|
||||||
|
|
||||||
|
|
||||||
|
def on_part(callback, userdata=None, priority=api.PRI_NORM):
|
||||||
|
return hook_event('PART', callback, userdata, priority)
|
||||||
|
|
||||||
|
|
||||||
|
def on_quit(callback, userdata=None, priority=api.PRI_NORM):
|
||||||
|
return hook_event('QUIT', callback, userdata, priority)
|
||||||
|
|
||||||
|
|
||||||
|
def on_nick(callback, userdata=None, priority=api.PRI_NORM):
|
||||||
|
return hook_event('NICK', callback, userdata, priority)
|
||||||
|
|
||||||
|
|
||||||
|
def on_kick(callback, userdata=None, priority=api.PRI_NORM):
|
||||||
|
return hook_event('KICK', callback, userdata, priority)
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(callback, userdata=None, priority=api.PRI_NORM):
|
||||||
|
return hook_event('PRIVMSG', callback, userdata, priority)
|
||||||
|
|
||||||
|
|
||||||
|
def on_notice(callback, userdata=None, priority=api.PRI_NORM):
|
||||||
|
return hook_event('NOTICE', callback, userdata, priority)
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
"""Explicit immutable IRC names; existing string-returning APIs are unchanged."""
|
|
||||||
import _zoitechat as api
|
|
||||||
|
|
||||||
__all__ = ['IRCName', 'irc_casefold']
|
|
||||||
|
|
||||||
|
|
||||||
def irc_casefold(text, casemapping='rfc1459'):
|
|
||||||
"""Fold ASCII IRC characters only; never apply Unicode lower()/casefold()."""
|
|
||||||
if casemapping not in ('ascii', 'strict-rfc1459', 'rfc1459'):
|
|
||||||
raise ValueError('unsupported IRC casemapping')
|
|
||||||
extra = {'[': '{', ']': '}', '\\': '|'} if casemapping != 'ascii' else {}
|
|
||||||
if casemapping == 'rfc1459':
|
|
||||||
extra['^'] = '~'
|
|
||||||
return ''.join(chr(ord(c) + 32) if 'A' <= c <= 'Z' else extra.get(c, c)
|
|
||||||
for c in text)
|
|
||||||
|
|
||||||
|
|
||||||
class IRCName:
|
|
||||||
"""Equality/hash include connection identity and the captured casemapping."""
|
|
||||||
__slots__ = ('_text', '_casemapping', '_connection_id', '_key')
|
|
||||||
|
|
||||||
def __init__(self, text, context=None, casemapping=None, connection_id=None):
|
|
||||||
context = context or api.get_context()
|
|
||||||
if casemapping is None:
|
|
||||||
casemapping = context.get_info('casemapping')
|
|
||||||
if casemapping is None:
|
|
||||||
raise RuntimeError('host does not expose its comparison casemapping')
|
|
||||||
if connection_id is None:
|
|
||||||
previous = api.get_context()
|
|
||||||
if not context.set():
|
|
||||||
raise ValueError('context has closed')
|
|
||||||
try:
|
|
||||||
connection_id = api.get_prefs('id')
|
|
||||||
finally:
|
|
||||||
previous.set()
|
|
||||||
object.__setattr__(self, '_text', text)
|
|
||||||
object.__setattr__(self, '_casemapping', casemapping)
|
|
||||||
object.__setattr__(self, '_connection_id', connection_id)
|
|
||||||
object.__setattr__(self, '_key', (connection_id, casemapping,
|
|
||||||
irc_casefold(text, casemapping)))
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
raise AttributeError('IRCName is immutable')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self):
|
|
||||||
return self._text
|
|
||||||
|
|
||||||
@property
|
|
||||||
def casemapping(self):
|
|
||||||
return self._casemapping
|
|
||||||
|
|
||||||
@property
|
|
||||||
def connection_id(self):
|
|
||||||
return self._connection_id
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self._text
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'IRCName({!r}, casemapping={!r}, connection_id={!r})'.format(
|
|
||||||
self._text, self._casemapping, self._connection_id)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, IRCName):
|
|
||||||
return NotImplemented
|
|
||||||
return self._key == other._key
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
result = self.__eq__(other)
|
|
||||||
return NotImplemented if result is NotImplemented else not result
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self._key)
|
|
||||||
@@ -1187,16 +1187,6 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id)
|
|||||||
return text_find_format_string (e);
|
return text_find_format_string (e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Report the comparison actually used by this connection, not an
|
|
||||||
* advertised mapping that the current core does not implement. */
|
|
||||||
if (strcmp (id, "casemapping") == 0)
|
|
||||||
{
|
|
||||||
if (!is_session (ph->context))
|
|
||||||
return NULL;
|
|
||||||
return ph->context->server->p_cmp == g_ascii_strcasecmp
|
|
||||||
? "ascii" : "rfc1459";
|
|
||||||
}
|
|
||||||
|
|
||||||
hash = str_hash (id);
|
hash = str_hash (id);
|
||||||
/* do the session independant ones first */
|
/* do the session independant ones first */
|
||||||
switch (hash)
|
switch (hash)
|
||||||
|
|||||||
Reference in new issue
Block a user