From: Christian Heller Date: Wed, 20 Aug 2025 19:42:15 +0000 (+0200) Subject: In _MsgParseExpectation explicitly label under what key a value to return by. X-Git-Url: https://plomlompom.com/repos/blog?a=commitdiff_plain;h=aa8f35e2d0eeb1aa0c280ef951aa082cc0d4a8a5;p=ircplom In _MsgParseExpectation explicitly label under what key a value to return by. --- diff --git a/ircplom/client.py b/ircplom/client.py index db72800..dd448f1 100644 --- a/ircplom/client.py +++ b/ircplom/client.py @@ -44,74 +44,129 @@ class _MsgTok(Enum): CHANNEL = auto() LIST = auto() NICKNAME = auto() - NICKNAME_ME = auto() NONE = auto() SERVER = auto() USER_ADDRESS = auto() - USER_ADDRESS_ME = auto() + + +_MsgTokGuide = str | _MsgTok | tuple[_MsgTok, str] class _MsgParseExpectation(NamedTuple): - source: _MsgTok + source: _MsgTokGuide verb: str + params: tuple[_MsgTokGuide, ...] = tuple() len_min_params: int = 0 len_max_params: int = 0 - params: tuple[str | _MsgTok, ...] = tuple() _EXPECTATIONS: tuple[_MsgParseExpectation, ...] = ( - _MsgParseExpectation(_MsgTok.SERVER, '005', 3, 15), - _MsgParseExpectation(_MsgTok.SERVER, '353', - params=(_MsgTok.NICKNAME_ME, '=', _MsgTok.CHANNEL, - _MsgTok.LIST,)), - _MsgParseExpectation(_MsgTok.SERVER, '366', - params=(_MsgTok.NICKNAME_ME, _MsgTok.CHANNEL, - _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.SERVER, '372', - params=(_MsgTok.NICKNAME_ME, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.SERVER, '376', - params=(_MsgTok.NICKNAME_ME, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.SERVER, '396', 3), - _MsgParseExpectation(_MsgTok.SERVER, '401', 3), - _MsgParseExpectation(_MsgTok.SERVER, '432', 3), - _MsgParseExpectation(_MsgTok.SERVER, '433', 3), - _MsgParseExpectation(_MsgTok.SERVER, '900', 4), - _MsgParseExpectation(_MsgTok.SERVER, '903', - params=(_MsgTok.NICKNAME_ME, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.SERVER, '904', - params=(_MsgTok.NICKNAME_ME, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.NONE, 'AUTHENTICATE', params=('+',)), - _MsgParseExpectation(_MsgTok.SERVER, 'CAP', 3, 15), - _MsgParseExpectation(_MsgTok.NONE, 'ERROR', params=(_MsgTok.ANY,)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS_ME, 'JOIN', - params=(_MsgTok.CHANNEL,)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS, 'JOIN', - params=(_MsgTok.CHANNEL,)), - _MsgParseExpectation(_MsgTok.NICKNAME_ME, 'MODE', - params=(_MsgTok.NICKNAME_ME, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS_ME, 'MODE', - params=(_MsgTok.NICKNAME_ME, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS_ME, 'NICK', - params=(_MsgTok.NICKNAME,)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS, 'NICK', - params=(_MsgTok.NICKNAME,)), - _MsgParseExpectation(_MsgTok.SERVER, 'NOTICE', params=('*', _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.SERVER, 'NOTICE', - params=(_MsgTok.NICKNAME, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS, 'NOTICE', - params=(_MsgTok.NICKNAME_ME, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS_ME, 'PART', - params=(_MsgTok.CHANNEL,)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS, 'PART', - params=(_MsgTok.CHANNEL,)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS, 'PART', - params=(_MsgTok.CHANNEL, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.NONE, 'PING', params=(_MsgTok.ANY,)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS, 'PRIVMSG', - params=(_MsgTok.NICKNAME_ME, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS, 'PRIVMSG', - params=(_MsgTok.CHANNEL, _MsgTok.ANY)), - _MsgParseExpectation(_MsgTok.USER_ADDRESS, 'QUIT', params=(_MsgTok.ANY,)), + _MsgParseExpectation(_MsgTok.SERVER, '005', tuple(), 3, 15), + + _MsgParseExpectation(_MsgTok.SERVER, + '353', + (_MsgTok.NICKNAME, + '=', + (_MsgTok.CHANNEL, 'channel'), + (_MsgTok.LIST, 'names'))), + _MsgParseExpectation(_MsgTok.SERVER, + '366', + (_MsgTok.NICKNAME, + (_MsgTok.CHANNEL, 'channel'), + _MsgTok.ANY)), + + _MsgParseExpectation(_MsgTok.SERVER, + '372', + (_MsgTok.NICKNAME, + (_MsgTok.ANY, 'line'))), + _MsgParseExpectation(_MsgTok.SERVER, + '376', + (_MsgTok.NICKNAME, + _MsgTok.ANY)), + + _MsgParseExpectation(_MsgTok.SERVER, '396', tuple(), 3), + _MsgParseExpectation(_MsgTok.SERVER, '401', tuple(), 3), + _MsgParseExpectation(_MsgTok.SERVER, '432', tuple(), 3), + _MsgParseExpectation(_MsgTok.SERVER, '433', tuple(), 3), + _MsgParseExpectation(_MsgTok.SERVER, '900', tuple(), 4), + + _MsgParseExpectation(_MsgTok.SERVER, + '903', + (_MsgTok.NICKNAME, + (_MsgTok.ANY, 'result'))), + _MsgParseExpectation(_MsgTok.SERVER, + '904', + (_MsgTok.NICKNAME, + (_MsgTok.ANY, 'result'))), + + _MsgParseExpectation(_MsgTok.NONE, + 'AUTHENTICATE', + ('+',)), + + _MsgParseExpectation(_MsgTok.SERVER, 'CAP', tuple(), 3, 15), + + _MsgParseExpectation(_MsgTok.NONE, + 'ERROR', + ((_MsgTok.ANY, 'reason'),)), + + _MsgParseExpectation((_MsgTok.USER_ADDRESS, 'joiner'), + 'JOIN', + ((_MsgTok.CHANNEL, 'channel'),)), + + _MsgParseExpectation(_MsgTok.NICKNAME, + 'MODE', + (_MsgTok.NICKNAME, + (_MsgTok.ANY, 'mode'))), + _MsgParseExpectation(_MsgTok.USER_ADDRESS, + 'MODE', + (_MsgTok.NICKNAME, + (_MsgTok.ANY, 'mode'))), + + _MsgParseExpectation((_MsgTok.USER_ADDRESS, 'named'), + 'NICK', + ((_MsgTok.NICKNAME, 'nickname'),)), + + _MsgParseExpectation(_MsgTok.SERVER, + 'NOTICE', + ('*', + (_MsgTok.ANY, 'message'))), + _MsgParseExpectation(_MsgTok.SERVER, + 'NOTICE', + ((_MsgTok.NICKNAME, 'nickname'), + (_MsgTok.ANY, 'message'))), + _MsgParseExpectation((_MsgTok.USER_ADDRESS, 'sender'), + 'NOTICE', + ((_MsgTok.NICKNAME, 'nickname'), + (_MsgTok.ANY, 'message'))), + _MsgParseExpectation((_MsgTok.USER_ADDRESS, 'sender'), + 'NOTICE', + ((_MsgTok.CHANNEL, 'channel'), + (_MsgTok.ANY, 'message'))), + + _MsgParseExpectation((_MsgTok.USER_ADDRESS, 'parter'), + 'PART', + ((_MsgTok.CHANNEL, 'channel'),)), + _MsgParseExpectation((_MsgTok.USER_ADDRESS, 'parter'), + 'PART', + ((_MsgTok.CHANNEL, 'channel'), + (_MsgTok.ANY, 'reason'))), + + _MsgParseExpectation(_MsgTok.NONE, + 'PING', + ((_MsgTok.ANY, 'reply'),)), + + _MsgParseExpectation((_MsgTok.USER_ADDRESS, 'sender'), + 'PRIVMSG', + ((_MsgTok.NICKNAME, 'nickname'), + (_MsgTok.ANY, 'message'))), + _MsgParseExpectation((_MsgTok.USER_ADDRESS, 'sender'), + 'PRIVMSG', + ((_MsgTok.CHANNEL, 'channel'), + (_MsgTok.ANY, 'message'))), + + _MsgParseExpectation((_MsgTok.USER_ADDRESS, 'quitter'), + 'QUIT', + ((_MsgTok.ANY, 'message'),)), ) @@ -601,7 +656,7 @@ class Client(ABC, ClientQueueMixin): continue if (not ex.len_max_params) and len_p != ex.len_min_params: continue - to_return: dict[str, Any] = {'': ''} + to_return: dict[str, Any] = {'': ''} # non-emtpy so boolish True ex_tok_fields = tuple([ex.source] + list(ex.params)) msg_tok_fields = tuple([msg.source] + list(msg.params)) if ex.params and len(ex_tok_fields) != len(msg_tok_fields): @@ -609,38 +664,34 @@ class Client(ABC, ClientQueueMixin): passing = True for idx, ex_tok in enumerate(ex_tok_fields): passing = False - msg_tok = msg_tok_fields[idx] + ex_tok, key = ((ex_tok[0], ex_tok[1]) + if isinstance(ex_tok, tuple) else (ex_tok, '')) + val_to_ret: str | tuple[str, ...] | dict[str, str | _ChannelDb] + val_to_ret = msg_tok = msg_tok_fields[idx] if ex_tok is _MsgTok.NONE and msg_tok != '': break if ex_tok is _MsgTok.SERVER\ and ('.' not in msg_tok or set('@!') & set(msg_tok)): break - key_nick = 'sender' if not idx else 'nickname' if ex_tok is _MsgTok.CHANNEL: if msg_tok[0] != '#': break - to_return |= {'ch_name': msg_tok, - 'channel': self._db.chan(msg_tok)} - elif ex_tok in {_MsgTok.NICKNAME, _MsgTok.NICKNAME_ME}: + val_to_ret = {'id': msg_tok, 'db': self._db.chan(msg_tok)} + elif ex_tok is _MsgTok.NICKNAME: if msg_tok[0] in '~&@%+# *': break - to_return[key_nick] = msg_tok - elif ex_tok in {_MsgTok.USER_ADDRESS, _MsgTok.USER_ADDRESS_ME}: + elif ex_tok is _MsgTok.USER_ADDRESS: toks = msg_tok.split('!') if len(toks) != 2: break toks = toks[0:1] + toks[1].split('@') if len(toks) != 3: break - to_return[key_nick] = toks[0] - elif ex_tok is _MsgTok.ANY: - to_return['any'] = msg_tok + val_to_ret = tuple(toks) elif ex_tok is _MsgTok.LIST: - to_return['list'] = msg_tok.split() - if ex_tok in {_MsgTok.NICKNAME_ME, _MsgTok.USER_ADDRESS_ME}: - if to_return[key_nick] != self._db.nickname: - break - to_return[f'{key_nick}_me'] = to_return[key_nick] + val_to_ret = tuple(msg_tok.split()) + if key: + to_return[key] = val_to_ret passing = True if passing: return to_return @@ -655,15 +706,19 @@ class Client(ABC, ClientQueueMixin): return if self._match_msg(msg, '005'): # RPL_ISUPPORT self._db.process_isupport(msg.params[1:-1]) + elif (ret := self._match_msg(msg, '353')): # RPL_NAMREPLY - for usr in ret['list']: - ret['channel'].append_completable('users', usr.lstrip('~&@%+')) - elif self._match_msg(msg, '366'): # RPL_ENDOFNAMES - self._db.chan(msg.params[1]).declare_complete('users') + for name in ret['names']: + ret['channel']['db'].append_completable('users', + name.lstrip('~&@%+')) + elif (ret := self._match_msg(msg, '366')): # RPL_ENDOFNAMES + ret['channel']['db'].declare_complete('users') + elif (ret := self._match_msg(msg, '372')): # RPL_MOTD - self._db.append_completable('motd', ret['any']) + self._db.append_completable('motd', ret['line']) elif self._match_msg(msg, '376'): # RPL_ENDOFMOTD self._db.declare_complete('motd') + elif self._match_msg(msg, '396'): # RPL_VISIBLEHOST # '@'-split because # claims: " can also be in the form " @@ -686,16 +741,19 @@ class Client(ABC, ClientQueueMixin): self._db.nickname, remainder = msg.params[1].split('!', maxsplit=1) self._db.username, self._db.client_host = remainder.split('@') self._db.sasl_account = msg.params[2] + elif ((ret := self._match_msg(msg, '903')) # RPL_SASLSUCCESS or (ret := self._match_msg(msg, '904'))): # ERR_SASLFAIL - self._db.sasl_auth_state = ret['any'] + self._db.sasl_auth_state = ret['result'] self._caps.end_negotiation() + elif self._match_msg(msg, 'AUTHENTICATE'): auth = b64encode((self._db.nick_wanted + '\0' + self._db.nick_wanted + '\0' + self._db.password ).encode('utf-8')).decode('utf-8') self.send(IrcMessage('AUTHENTICATE', (auth,))) + elif self._match_msg(msg, 'CAP'): if (self._caps.process_msg(msg.params[1:]) and self._db.caps.has('sasl') @@ -705,51 +763,64 @@ class Client(ABC, ClientQueueMixin): self.send(IrcMessage('AUTHENTICATE', ('PLAIN',))) else: self._caps.end_negotiation() + elif (ret := self._match_msg(msg, 'ERROR')): - self._db.connection_state = ret['any'] + self._db.connection_state = ret['reason'] self.close() + elif (ret := self._match_msg(msg, 'JOIN')): - log_msg = f'{ret["sender"]} {msg.verb.lower()}s {ret["ch_name"]}' - self._log(log_msg, scope=LogScope.CHAT, target=ret['ch_name']) - if 'sender_me' not in ret: - ret['channel'].append_completable('users', ret['sender'], - stay_complete=True) + self._log(f'{ret["joiner"][0]} {msg.verb.lower()}s ' + + f'{ret["channel"]["id"]}', + scope=LogScope.CHAT, target=ret['channel']['id']) + if ret['joiner'][0] != self._db.nickname: + ret['channel']['db'].append_completable('users', + ret['joiner'][0], True) + elif (ret := self._match_msg(msg, 'MODE')): - self._db.user_modes = ret['any'] + self._db.user_modes = ret['mode'] + elif (ret := self._match_msg(msg, 'NICK')): - if 'sender_me' in ret: + if ret['named'][0] == self._db.nickname: self.set_nick(ret['nickname'], confirmed=True) else: - for nom, chan in self._db.chans_of_user(ret['sender']).items(): - chan.remove_completable('users', ret['sender'], True) - chan.append_completable('users', ret['nickname'], True) - self._log(f'{ret["sender"]} becomes {ret["nickname"]}', - scope=LogScope.CHAT, target=nom) + for id_, ch in self._db.chans_of_user(ret['named'][0]).items(): + ch.remove_completable('users', ret['named'][0], True) + ch.append_completable('users', ret['nickname'], True) + self._log(f'{ret["named"][0]} becomes {ret["nickname"]}', + scope=LogScope.CHAT, target=id_) + elif (ret := self._match_msg(msg, 'NOTICE'))\ or (ret := self._match_msg(msg, 'PRIVMSG')): - kw = {'sender': ret['sender'], 'scope': LogScope.CHAT, - 'target': ret.get('ch_name', ret['sender']), - } if 'sender' in ret else {} - if msg.verb == 'NOTICE': - if 'nickname' in ret and 'nickname_me' not in ret: - self.set_nick(ret['nickname'], confirmed=True) - kw |= {'as_notice': True} - self._log(ret['any'], out=False, **kw) + if 'nickname' in ret and ret['nickname'] != self._db.nickname: + self.set_nick(ret['nickname'], confirmed=True) + kw: dict[str, bool | str | LogScope] = { + 'as_notice': msg.verb == 'NOTICE'} + if 'sender' in ret: # not just server message + kw |= {'sender': ret['sender'][0], 'scope': LogScope.CHAT, + 'target': (ret['sender'][0] if 'nickname' in ret + else ret['channel']['id'])} + self._log(ret['message'], out=False, **kw) + elif (ret := self._match_msg(msg, 'PART')): - log_msg = f'{ret["sender"]} {msg.verb.lower()}s {ret["ch_name"]}' - log_msg += f': {ret["any"]}' if 'any' in ret else '' - self._log(log_msg, scope=LogScope.CHAT, target=ret['ch_name']) - if 'sender_me' in ret: + reason = f': ret["reason"]' if 'reason' in ret else '' + self._log(f'{ret["parter"][0]} {msg.verb.lower()}s ' + + f'{ret["channel"]["id"]}{reason}', + scope=LogScope.CHAT, target=ret['channel']['id']) + if ret['parter'][0] == self._db.nickname: self._db.del_chan(ret['ch_name']) else: - ret['channel'].remove_completable('users', ret['sender'], True) + ret['channel']['db'].remove_completable('users', + ret['parter'][0], True) + elif (ret := self._match_msg(msg, 'PING')): - self.send(IrcMessage(verb='PONG', params=(ret['any'],))) + self.send(IrcMessage(verb='PONG', params=(ret['reply'],))) + elif (ret := self._match_msg(msg, 'QUIT')): - for nom, chan in self._db.chans_of_user(ret['sender']).items(): - chan.remove_completable('users', ret['sender'], True) - self._log(f'{ret["sender"]} quits: {ret["any"]}', - LogScope.CHAT, target=nom) + for id_, chan in self._db.chans_of_user(ret['quitter'][0]).items(): + chan.remove_completable('users', ret['quitter'][0], True) + self._log(f'{ret["quitter"][0]} quits: {ret["message"]}', + LogScope.CHAT, target=id_) + else: self._log(f'PLEASE IMPLEMENT HANDLER FOR: {msg.raw}')