From: Christian Heller <c.heller@plomlompom.de> Date: Tue, 26 Aug 2025 01:25:52 +0000 (+0200) Subject: Parse isupports to derive proper legal/illegal first chars for nicknames, channel... X-Git-Url: https://plomlompom.com/repos/%22https:/validator.w3.org/blog?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=ircplom Parse isupports to derive proper legal/illegal first chars for nicknames, channel names. --- diff --git a/ircplom/client.py b/ircplom/client.py index 9a99aa2..46b21cc 100644 --- a/ircplom/client.py +++ b/ircplom/client.py @@ -11,8 +11,9 @@ from uuid import uuid4 # ourselves from ircplom.events import ( AffectiveEvent, CrashingException, ExceptionEvent, QueueMixin) -from ircplom.irc_conn import (BaseIrcConnection, IrcConnAbortException, - IrcMessage, PORT_SSL) +from ircplom.irc_conn import ( + BaseIrcConnection, IrcConnAbortException, IrcMessage, ILLEGAL_NICK_CHARS, + ILLEGAL_NICK_FIRSTCHARS, ISUPPORT_DEFAULTS, PORT_SSL) ClientsDb = dict[str, 'Client'] @@ -130,7 +131,6 @@ class ClientQueueMixin(QueueMixin, _ClientIdMixin): _NAMES_DESIRED_SERVER_CAPS = ('sasl',) -_ILLEGAL_NICK_FIRSTCHARS = '~&@+# ' class _MsgTok(Enum): @@ -487,7 +487,7 @@ class _UpdatingDict: self._dict: dict[str, Any] = {} def __getitem__(self, key: str): - return self._dict[key] + return self._dict[key] if key in self._dict else None def __setitem__(self, key: str, val: Any) -> None: if isinstance(val, _NickUserHost): @@ -697,6 +697,26 @@ class _ClientDb(_Db, SharedClientDbFields): return not isinstance(getattr(self, key), (bool, int, str, tuple, _CompletableStringsList)) + @property + def illegal_nick_firstchars(self) -> str: + 'Calculated from hardcoded constants and .isupports.' + return (ILLEGAL_NICK_CHARS + ILLEGAL_NICK_FIRSTCHARS + + self.chan_prefixes + self.membership_prefixes) + + @property + def chan_prefixes(self) -> str: + 'Registered possible channel name prefixes.' + return self.isupports['CHANTYPES'] or ISUPPORT_DEFAULTS['CHANTYPES'] + + @property + def membership_prefixes(self) -> str: + 'Registered possible membership nickname prefixes.' + prefix = self.isupports['PREFIX'] or ISUPPORT_DEFAULTS['PREFIX'] + toks = prefix.split(')', maxsplit=1) + assert len(toks) == 2 + assert toks[0][0] == '(' + return toks[1] + def user_id(self, query: str | _NickUserHost) -> str: 'Return user_id for nickname of entire NickUserHost, create if none.' nick = query if isinstance(query, str) else query.nick @@ -852,9 +872,10 @@ class Client(ABC, ClientQueueMixin): and not set('@!') & set(msg_tok)) else None if ex_tok is _MsgTok.CHANNEL: return {'id': msg_tok, 'db': self._db.chan(msg_tok) - } if msg_tok[0] == '#' else None + } if msg_tok[0] in self._db.chan_prefixes else None if ex_tok is _MsgTok.NICKNAME: - return (msg_tok if msg_tok[0] not in _ILLEGAL_NICK_FIRSTCHARS + return (msg_tok + if msg_tok[0] not in self._db.illegal_nick_firstchars else None) if ex_tok is _MsgTok.NICK_USER_HOST: try: @@ -919,7 +940,7 @@ class Client(ABC, ClientQueueMixin): self._db.isupports.set_from_eq_str(str(item)) elif ret['verb'] == '353': # RPL_NAMREPLY for user_id in [ - self._db.user_id(name.lstrip(_ILLEGAL_NICK_FIRSTCHARS)) + self._db.user_id(name.lstrip(self._db.membership_prefixes)) for name in ret['names']]: ret['channel']['db'].add_user(user_id) elif ret['verb'] == '372': # RPL_MOTD diff --git a/ircplom/client_tui.py b/ircplom/client_tui.py index 3792fa6..5664df2 100644 --- a/ircplom/client_tui.py +++ b/ircplom/client_tui.py @@ -6,7 +6,7 @@ from typing import Callable, Optional, Sequence # ourselves from ircplom.tui_base import (BaseTui, PromptWidget, TuiEvent, Window, CMD_SHORTCUTS) -from ircplom.irc_conn import IrcMessage +from ircplom.irc_conn import IrcMessage, ISUPPORT_DEFAULTS from ircplom.client import ( Client, ClientQueueMixin, Db, IrcConnSetup, LogScope, NewClientEvent, NickUserHost, ServerCapability, SharedChannelDbFields, @@ -130,11 +130,6 @@ class _Update: if not self.display: self.display = str(self.value) - @property - def is_chan(self) -> bool: - 'Return if .path points to a _ChannelDb.' - return self.path[0] == '#' - class _Db(Db): @@ -185,7 +180,7 @@ class _TuiClientDb(_Db, SharedClientDbFields): def set_and_check_for_change(self, update: _Update ) -> bool | dict[str, tuple[str, ...]]: result: bool | dict[str, tuple[str, ...]] = False - if update.is_chan: + if self.is_chan_name(update.path): chan_name = update.path if update.value is None and not update.arg: del self._channels[chan_name] @@ -204,6 +199,11 @@ class _TuiClientDb(_Db, SharedClientDbFields): result = super().set_and_check_for_change(update) return result + def is_chan_name(self, name: str) -> bool: + 'Tests name to match CHANTYPES prefixes.' + return name[0] in self.isupports.get('CHANTYPES', + ISUPPORT_DEFAULTS['CHANTYPES']) + def chan(self, name: str) -> _ChannelDb: 'Produce DB for channel of name â pre-existing, or newly created.' if name not in self._channels: @@ -261,12 +261,13 @@ class _ClientWindowsManager: def update_db(self, update: _Update) -> bool: 'Apply update to .db, and if changing anything, log and trigger.' - scope = (LogScope.CHAT if update.is_chan + is_chan_update = self.db.is_chan_name(update.path) + scope = (LogScope.CHAT if is_chan_update else (LogScope.ALL if update.path == 'connection_state' else LogScope.SERVER)) verb = 'cleared' if update.value is None else 'changed to:' what = f'{update.path}:{update.arg}' if update.arg else update.path - log_kwargs = {'target': update.path} if update.is_chan else {} + log_kwargs = {'target': update.path} if is_chan_update else {} result = self.db.set_and_check_for_change(update) if result is False: return False @@ -388,7 +389,7 @@ class _ClientKnowingTui(Client): def _on_update(self, path: str, arg: str = '') -> None: value: Optional[_DbType] = None - is_chan = path[0] == '#' + is_chan = path[0] in self._db.chan_prefixes display = '' if arg: if is_chan and path in self._db.chan_names: diff --git a/ircplom/irc_conn.py b/ircplom/irc_conn.py index 5e09c1a..add211d 100644 --- a/ircplom/irc_conn.py +++ b/ircplom/irc_conn.py @@ -13,6 +13,12 @@ _TIMEOUT_RECV_LOOP = 0.1 _TIMEOUT_CONNECT = 5 _CONN_RECV_BUFSIZE = 1024 +ILLEGAL_NICK_CHARS = ' ,*?!@' +ILLEGAL_NICK_FIRSTCHARS = ':$' +ISUPPORT_DEFAULTS = { + 'CHANTYPES': '#&', + 'PREFIX': '(ov)@+' +} _IRCSPEC_LINE_SEPARATOR = b'\r\n' _IRCSPEC_TAG_ESCAPES = ((r'\:', ';'), (r'\s', ' '),