mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-11-08 17:05:38 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
420adde173 |
No files matched your search
@@ -0,0 +1,24 @@
|
|||||||
|
# Connection and context navigation
|
||||||
|
|
||||||
|
`get_connections()` returns one Connection snapshot per live connection:
|
||||||
|
`id`, `network`, `server`, `context`. Connection IDs distinguish simultaneous
|
||||||
|
connections with identical names. Prefer its server-tab context where present.
|
||||||
|
`get_connection(context=None)` finds the owner of a live context.
|
||||||
|
|
||||||
|
`get_contexts(connection_id=None, types=None)` lists live contexts, optionally
|
||||||
|
filtered by connection ID (or a Connection object) and session types: server=1,
|
||||||
|
channel=2, query=3. `connection.contexts(types=None)` is a convenience method.
|
||||||
|
`find_context_by_id(connection_id, channel=None)` / `connection.find(channel)`
|
||||||
|
resolve using that connection's IRC comparator; no channel selects the server tab.
|
||||||
|
Missing/closed contexts return None. Existing find_context() remains unchanged.
|
||||||
|
|
||||||
|
These helpers use the existing channels list's ID/context fields, so no new C
|
||||||
|
context ABI is needed. Snapshots are not persistent IDs across client restarts;
|
||||||
|
refresh them after closing/recreating connections. Always check Context.set()
|
||||||
|
when explicitly selecting a context that might have closed.
|
||||||
|
|
||||||
|
```python
|
||||||
|
connection = zoitechat.get_connection()
|
||||||
|
for context in connection.contexts(types=(2,)):
|
||||||
|
context.prnt('A channel on this exact connection')
|
||||||
|
```
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# 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)
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"""Navigate live connections by ID instead of ambiguous server/network names."""
|
||||||
|
from collections import namedtuple
|
||||||
|
import operator
|
||||||
|
import _zoitechat as api
|
||||||
|
|
||||||
|
__all__ = ['Connection', 'get_connections', 'get_connection', 'get_contexts',
|
||||||
|
'find_context_by_id']
|
||||||
|
|
||||||
|
|
||||||
|
class Connection(namedtuple('_Connection', 'id network server context')):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def contexts(self, types=None):
|
||||||
|
return get_contexts(self.id, types)
|
||||||
|
|
||||||
|
def find(self, channel=None):
|
||||||
|
return find_context_by_id(self.id, channel)
|
||||||
|
|
||||||
|
|
||||||
|
def _id(connection):
|
||||||
|
return operator.index(connection.id if isinstance(connection, Connection) else connection)
|
||||||
|
|
||||||
|
|
||||||
|
def get_connections():
|
||||||
|
"""Return one snapshot per live connection, including disconnected ones."""
|
||||||
|
entries = api.get_list('channels')
|
||||||
|
by_id = {}
|
||||||
|
order = []
|
||||||
|
for entry in entries:
|
||||||
|
if entry.id not in by_id:
|
||||||
|
order.append(entry.id)
|
||||||
|
if entry.id not in by_id or entry.type == 1:
|
||||||
|
by_id[entry.id] = Connection(entry.id, entry.network, entry.server,
|
||||||
|
entry.context)
|
||||||
|
return [by_id[connection_id] for connection_id in order]
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection(context=None):
|
||||||
|
"""Find the owning connection of a Context; return None when it has closed."""
|
||||||
|
context = context or api.get_context()
|
||||||
|
for entry in api.get_list('channels'):
|
||||||
|
if entry.context == context:
|
||||||
|
for connection in get_connections():
|
||||||
|
if connection.id == entry.id:
|
||||||
|
return connection
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_contexts(connection_id=None, types=None):
|
||||||
|
"""Return contexts, optionally restricted to a connection and session types."""
|
||||||
|
if connection_id is not None:
|
||||||
|
connection_id = _id(connection_id)
|
||||||
|
if types is not None:
|
||||||
|
types = frozenset(types)
|
||||||
|
return [entry.context for entry in api.get_list('channels')
|
||||||
|
if (connection_id is None or entry.id == connection_id)
|
||||||
|
and (types is None or entry.type in types)]
|
||||||
|
|
||||||
|
|
||||||
|
def find_context_by_id(connection_id, channel=None):
|
||||||
|
"""Resolve a live context on exactly one connection; None selects its server tab."""
|
||||||
|
connection_id = _id(connection_id)
|
||||||
|
previous = api.get_context()
|
||||||
|
try:
|
||||||
|
for entry in api.get_list('channels'):
|
||||||
|
if entry.id != connection_id:
|
||||||
|
continue
|
||||||
|
if channel is None:
|
||||||
|
if entry.type == 1:
|
||||||
|
return entry.context
|
||||||
|
elif entry.context.set() and api.nickcmp(entry.channel, channel) == 0:
|
||||||
|
return entry.context
|
||||||
|
finally:
|
||||||
|
previous.set()
|
||||||
|
return None
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
"""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)
|
|
||||||
Reference in new issue
Block a user