home · contact · privacy
Enable collection of multi-param items, to integrate 005 into new system.
authorChristian Heller <c.heller@plomlompom.de>
Wed, 20 Aug 2025 21:38:54 +0000 (23:38 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Wed, 20 Aug 2025 21:38:54 +0000 (23:38 +0200)
ircplom/client.py

index cd10e6a1aedb88be7ca65ddae25bda1c235ea09c..7d180f8e50863166832557685f623c0a83e21429 100644 (file)
@@ -58,143 +58,150 @@ class _MsgParseExpectation(NamedTuple):
     params: tuple[_MsgTokGuide, ...] = tuple()
     len_min_params: int = 0
     len_max_params: int = 0
+    idx_into_list: int = -1
 
 
 _EXPECTATIONS: tuple[_MsgParseExpectation, ...] = (
-   _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',
-                        (_MsgTok.NICKNAME,
-                         (_MsgTok.ANY, 'host_maybe_w_user'),
-                         _MsgTok.ANY)),
-
-   _MsgParseExpectation(_MsgTok.SERVER,
-                        '401',
-                        (_MsgTok.NICKNAME,
-                         (_MsgTok.NICKNAME, 'target'),
-                         _MsgTok.ANY)),
-
-   _MsgParseExpectation(_MsgTok.SERVER,
-                        '432',
-                        ('*',
-                         _MsgTok.NICKNAME,
-                         _MsgTok.ANY)),
-   _MsgParseExpectation(_MsgTok.SERVER,
-                        '432',
-                        ((_MsgTok.NICKNAME, 'fallback'),
-                         _MsgTok.NICKNAME,
-                         _MsgTok.ANY)),
-
-   _MsgParseExpectation(_MsgTok.SERVER,
-                        '433',
-                        (_MsgTok.NICKNAME,
-                         _MsgTok.NICKNAME,
-                         _MsgTok.ANY)),
-
-   _MsgParseExpectation(_MsgTok.SERVER,
-                        '900',
-                        (_MsgTok.NICKNAME,
-                         (_MsgTok.USER_ADDRESS, 'full_address'),
-                         (_MsgTok.ANY, 'account'),
-                         _MsgTok.ANY)),
-   _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'),)),
+    # _MsgParseExpectation(_MsgTok.SERVER, '005', tuple(), 3, 15),
+    _MsgParseExpectation(_MsgTok.SERVER,
+                         '005',
+                         (_MsgTok.NICKNAME,
+                          (_MsgTok.ANY, 'isupports'),
+                          _MsgTok.ANY),
+                         idx_into_list=1),
+
+    _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',
+                         (_MsgTok.NICKNAME,
+                          (_MsgTok.ANY, 'host_maybe_w_user'),
+                          _MsgTok.ANY)),
+
+    _MsgParseExpectation(_MsgTok.SERVER,
+                         '401',
+                         (_MsgTok.NICKNAME,
+                          (_MsgTok.NICKNAME, 'target'),
+                          _MsgTok.ANY)),
+
+    _MsgParseExpectation(_MsgTok.SERVER,
+                         '432',
+                         ('*',
+                          _MsgTok.NICKNAME,
+                          _MsgTok.ANY)),
+    _MsgParseExpectation(_MsgTok.SERVER,
+                         '432',
+                         ((_MsgTok.NICKNAME, 'fallback'),
+                          _MsgTok.NICKNAME,
+                          _MsgTok.ANY)),
+
+    _MsgParseExpectation(_MsgTok.SERVER,
+                         '433',
+                         (_MsgTok.NICKNAME,
+                          _MsgTok.NICKNAME,
+                          _MsgTok.ANY)),
+
+    _MsgParseExpectation(_MsgTok.SERVER,
+                         '900',
+                         (_MsgTok.NICKNAME,
+                          (_MsgTok.USER_ADDRESS, 'full_address'),
+                          (_MsgTok.ANY, 'account'),
+                          _MsgTok.ANY)),
+    _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'),)),
 )
 
 
@@ -565,15 +572,6 @@ class _ClientDb(_Db, SharedClientDbFields):
         'Return part of channels dictionary for channels user is currently in.'
         return {k: v for k, v in self._channels.items() if user in v.users}
 
-    def process_isupport(self, params: tuple[str, ...]) -> None:
-        'Process 005 RPL_ISUPPORT params into dictionary updates.'
-        for param in params[1:-1]:
-            toks = param.split('=', maxsplit=1)
-            if toks[0][0] == '-':
-                del self.isupports[toks[0][1:]]
-            else:
-                self.isupports[toks[0]] = toks[1] if len(toks) > 1 else ''
-
     @property
     def nick_incremented(self) -> str:
         'Return .nick_wanted with number suffix incremented, or "0" if none.'
@@ -675,6 +673,38 @@ class Client(ABC, ClientQueueMixin):
 
     def _match_msg(self, msg: IrcMessage, verb: str):
         'Test .source, .verb, .params.'
+        tok_type = str | tuple[str, ...] | dict[str, str | _ChannelDb]
+
+        def param_match(ex_tok: str | _MsgTok, msg_tok: str | list[str]
+                        ) -> Optional[tok_type | tuple[tok_type, ...]]:
+            if isinstance(msg_tok, list):
+                to_return = []
+                for item in msg_tok:
+                    result = param_match(ex_tok, item)
+                    if not isinstance(result, tok_type):
+                        return None
+                    to_return += [result]
+                return tuple(to_return)
+            if ex_tok is _MsgTok.NONE:
+                return msg_tok if msg_tok == '' else None
+            if ex_tok is _MsgTok.SERVER:
+                return msg_tok if ('.' in msg_tok
+                                   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 ex_tok is _MsgTok.NICKNAME:
+                return msg_tok if msg_tok[0] not in '~&@%+# ' else None
+            if ex_tok is _MsgTok.USER_ADDRESS:
+                toks = msg_tok.split('!')
+                if len(toks) != 2:
+                    return None
+                toks = toks[0:1] + toks[1].split('@')
+                return tuple(toks) if len(toks) == 3 else None
+            if ex_tok is _MsgTok.LIST:
+                return tuple(msg_tok.split())
+            return msg_tok
+
         for ex in [ex for ex in _EXPECTATIONS if verb == ex.verb == msg.verb]:
             if not ex.params:
                 len_p = len(msg.params)
@@ -686,40 +716,25 @@ class Client(ABC, ClientQueueMixin):
                     continue
             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))
+            msg_params: list[str | list[str]]
+            if ex.idx_into_list < 0:
+                msg_params = list(msg.params)
+            else:
+                idx_remainders = len(msg.params) + 1 - (len(ex.params)
+                                                        - ex.idx_into_list)
+                msg_params = list(msg.params[:ex.idx_into_list])\
+                    + [list(msg.params[ex.idx_into_list:idx_remainders])]\
+                    + list(msg.params[idx_remainders:])
+            msg_tok_fields = tuple([msg.source] + msg_params)
             if ex.params and len(ex_tok_fields) != len(msg_tok_fields):
                 continue
-            passing = True
+            passing = False
             for idx, ex_tok in enumerate(ex_tok_fields):
-                passing = False
                 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 != '':
+                to_return[key] = param_match(ex_tok, msg_tok_fields[idx])
+                if to_return[key] is None:
                     break
-                if ex_tok is _MsgTok.SERVER\
-                        and ('.' not in msg_tok or set('@!') & set(msg_tok)):
-                    break
-                if ex_tok is _MsgTok.CHANNEL:
-                    if msg_tok[0] != '#':
-                        break
-                    val_to_ret = {'id': msg_tok, 'db': self._db.chan(msg_tok)}
-                elif ex_tok is _MsgTok.NICKNAME:
-                    if msg_tok[0] in '~&@%+# *':
-                        break
-                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
-                    val_to_ret = tuple(toks)
-                elif ex_tok is _MsgTok.LIST:
-                    val_to_ret = tuple(msg_tok.split())
-                if key:
-                    to_return[key] = val_to_ret
                 passing = True
             if passing:
                 return to_return
@@ -732,8 +747,15 @@ class Client(ABC, ClientQueueMixin):
             self.set_nick(msg.params[0], confirmed=True)
         if _NumericsToIgnore.contain(msg.verb):
             return
-        if self._match_msg(msg, '005'):  # RPL_ISUPPORT
-            self._db.process_isupport(msg.params[1:-1])
+
+        if (ret := self._match_msg(msg, '005')):  # RPL_ISUPPORT
+            for item in ret['isupports']:
+                toks = item.split('=', maxsplit=1)
+                if toks[0][0] == '-':
+                    del self._db.isupports[toks[0][1:]]
+                else:
+                    self._db.isupports[toks[0]] = (toks[1] if len(toks) > 1
+                                                   else '')
 
         elif (ret := self._match_msg(msg, '353')):  # RPL_NAMREPLY
             for name in ret['names']: