home · contact · privacy
Process _all_ CHANMODES settings by server, together with PREFIX ones. master
authorChristian Heller <c.heller@plomlompom.de>
Mon, 15 Dec 2025 21:45:10 +0000 (22:45 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 15 Dec 2025 21:45:10 +0000 (22:45 +0100)
src/ircplom/client.py
src/ircplom/client_tui.py
src/ircplom/db_primitives.py
src/ircplom/msg_parse_expectations.py
src/tests/channel_modes.test
src/tests/isupports.test
src/tests/lib/isupport-set [new file with mode: 0644]
src/tests/test.test

index 15a29ca37643c88114b9f50a4ae76cea14e5f64b..b5988ebbea34e7f049a21880a59963420100d5b9 100644 (file)
@@ -37,12 +37,6 @@ def _tuple_key_val_from_eq_str(eq_str: str) -> tuple[str, str]:
     return toks[0], ('' if len(toks) == 1 else toks[1])
 
 
     return toks[0], ('' if len(toks) == 1 else toks[1])
 
 
-def _operation_chars_from_modeset_str(modeset_str: str) -> tuple[str, str]:
-    operation, chars = modeset_str[:1], modeset_str[1:]
-    assert chars and operation in '+-'
-    return operation, chars
-
-
 class SendFail(Exception):
     'When Client.send fails.'
 
 class SendFail(Exception):
     'When Client.send fails.'
 
@@ -95,6 +89,16 @@ class SharedClientDbFields(IrcConnSetup):
         'Tests name to match CHANTYPES prefixes.'
         return name[0] in self.isupport['CHANTYPES']
 
         'Tests name to match CHANTYPES prefixes.'
         return name[0] in self.isupport['CHANTYPES']
 
+    def get_membership_modes(self) -> dict[str, str]:
+        'Return currently registered membership nickname prefix possibilities.'
+        toks = self.isupport.get('PREFIX', '()').split(')', maxsplit=1)
+        assert len(toks) == 2
+        assert toks[0][0] == '('
+        toks[0] = toks[0][1:]
+        assert len(toks[0]) == len(toks[1])
+        prefixes = {toks[0][idx]: toks[1][idx] for idx in range(len(toks[0]))}
+        return prefixes
+
 
 class User(NickUserHost):
     'Adds to NickUserHost non-naming-specific attributes.'
 
 class User(NickUserHost):
     'Adds to NickUserHost non-naming-specific attributes.'
@@ -120,8 +124,10 @@ class Channel:
     'Collects .topic, and in .user_ids inhabitant IDs.'
     topic: Topic
     user_ids: Iterable[str]
     'Collects .topic, and in .user_ids inhabitant IDs.'
     topic: Topic
     user_ids: Iterable[str]
-    prefixes: Dict[tuple[str, ...]]
     exits: Dict[str]
     exits: Dict[str]
+    modes_listy: Dict[tuple[str, ...]]
+    modes_valued: Dict[str]
+    modes_toggled: str = ''
 
 
 @dataclass
 
 
 @dataclass
@@ -174,12 +180,14 @@ class _Channel(Channel):
 
     def __init__(self,
                  userid_for_nickuserhost: Callable,
 
     def __init__(self,
                  userid_for_nickuserhost: Callable,
-                 get_membership_prefixes: Callable,
+                 get_membership_modes: Callable[[], dict[str, str]],
+                 get_chanmodes: Callable[[], dict[str, str]],
                  purge_users: Callable,
                  **kwargs
                  ) -> None:
         self._userid_for_nickuserhost = userid_for_nickuserhost
                  purge_users: Callable,
                  **kwargs
                  ) -> None:
         self._userid_for_nickuserhost = userid_for_nickuserhost
-        self._get_membership_prefixes = get_membership_prefixes
+        self._get_membership_modes = get_membership_modes
+        self._get_chanmodes = get_chanmodes
         self.purge_users = purge_users
         super().__init__(**kwargs)
 
         self.purge_users = purge_users
         super().__init__(**kwargs)
 
@@ -187,26 +195,20 @@ class _Channel(Channel):
         return self._userid_for_nickuserhost(NickUserHost(nick),
                                              create_if_none=create_if_none)
 
         return self._userid_for_nickuserhost(NickUserHost(nick),
                                              create_if_none=create_if_none)
 
-    def _add_membership_prefix(self, user_id: str, prefix: str) -> None:
-        if prefix in self.prefixes.keys():
-            self.prefixes[prefix] = tuple(sorted(self.prefixes[prefix]
-                                                 + (user_id,)))
-        else:
-            self.prefixes[prefix] = (user_id,)
-
-    def _remove_membership_prefix(self, user_id: str, prefix: str) -> None:
-        self.prefixes[prefix] = tuple(id_ for id_ in self.prefixes[prefix]
-                                      if id_ != user_id)
-
     def add_from_namreply(self, items: tuple[str, ...]) -> None:
         'Add to .user_ids items assumed as nicknames with membership prefixes.'
     def add_from_namreply(self, items: tuple[str, ...]) -> None:
         'Add to .user_ids items assumed as nicknames with membership prefixes.'
-        prefixes = self._get_membership_prefixes().values()
         for item in items:
         for item in items:
-            prefix = ''.join([pfx for pfx in prefixes if item.startswith(pfx)])
-            user_id = self._id_from_nick(item.lstrip(prefix), True)
+            mode = ''
+            for mode, prefix in [
+                    (c, pfx) for c, pfx in self._get_membership_modes().items()
+                    if item.startswith(pfx)]:
+                item = item.lstrip(prefix)
+            user_id = self._id_from_nick(item, True)
             self.user_ids.completable_add(user_id, on_complete=False)
             self.user_ids.completable_add(user_id, on_complete=False)
-            if prefix:
-                self._add_membership_prefix(user_id, prefix)
+            if mode:
+                self.modes_listy[mode]\
+                    = tuple(sorted(list(self.modes_listy.get(mode, tuple()))
+                                   + [user_id]))
 
     def join_user(self, user: '_User') -> None:
         'Register non-"me" user joining channel.'
 
     def join_user(self, user: '_User') -> None:
         'Register non-"me" user joining channel.'
@@ -221,23 +223,58 @@ class _Channel(Channel):
         self.exits[user.id_] = msg
         self.user_ids.completable_remove(user.id_, on_complete=True)
         del self.exits[user.id_]
         self.exits[user.id_] = msg
         self.user_ids.completable_remove(user.id_, on_complete=True)
         del self.exits[user.id_]
-        for prefix in self.prefixes.keys():
-            self._remove_membership_prefix(user.id_, prefix)
+        for c in [c for c in self._get_membership_modes().keys()
+                  if user.id_ in self.modes_listy.get(c, tuple())]:
+            self.modes_listy[c] = tuple(uid for uid in self.modes_listy[c]
+                                        if uid != user.id_)
         self.purge_users()
 
         self.purge_users()
 
-    def mode_on_nick(self, nick: str, modeset: str) -> None:
-        'Set channel mode towards user of nick.'
-        user_id = self._id_from_nick(nick, False)
-        operation, prefix_char = _operation_chars_from_modeset_str(modeset)
-        prefixes = self._get_membership_prefixes()
-        if prefix_char not in prefixes.keys():
-            raise ImplementationFail(
-                    f'for nickname setting channel mode: {modeset}')
-        prefix = prefixes[prefix_char]
-        if operation == '+':
-            self._add_membership_prefix(user_id, prefix)
-        else:
-            self._remove_membership_prefix(user_id, prefix)
+    def set_modes(self, modeset: str, args_str='') -> None:
+        'Apply MODE settings on channels.'
+        prefix_modes = ''.join(self._get_membership_modes().keys())
+        modes = {c: '' for c in 'ABCD'} | self._get_chanmodes()
+        todos: list[tuple[bool, str, str]] = []
+        args = args_str.split()
+        idx_args = 0
+        do_add = False
+        try:
+            assert modeset[0] in '+-'
+            for c in modeset:
+                arg = ''
+                if c in '+-':
+                    do_add = c == '+'
+                    continue
+                if c in prefix_modes + modes['A'] + modes['B']\
+                        or (do_add and c in modes['C']):
+                    assert idx_args < len(args)
+                    arg = args[idx_args]
+                    if c in prefix_modes:
+                        arg = self._id_from_nick(arg, False)
+                    idx_args += 1
+                elif c not in modes['C'] + modes['D']:
+                    assert False
+                todos += [(do_add, c, arg)]
+            assert idx_args == len(args)
+        except AssertionError:
+            raise ImplementationFail(  # pylint: disable=raise-missing-from
+                    f'channel mode setting {modeset} on args: {args_str}')
+        for do_add, char, arg in todos:
+            if char in modes['A'] + prefix_modes:
+                char_modes = list(self.modes_listy.get(char, tuple()))
+                self.modes_listy[char]\
+                    = tuple(sorted((char_modes + [arg]) if do_add
+                                   else [item for item in char_modes
+                                         if item != arg]))
+            elif char in modes['B'] + modes['C']:
+                if do_add:
+                    self.modes_valued[char] = arg
+                elif char in self.modes_valued.keys():
+                    del self.modes_valued[char]
+            else:  # char in chanmodes['D']
+                self.modes_toggled\
+                    = ''.join(sorted(set((self.modes_toggled + char) if do_add
+                                         else (c for c in self.modes_toggled
+                                               if c != char))))
 
 
 class _SetNickuserhostMixin:
 
 
 class _SetNickuserhostMixin:
@@ -300,7 +337,8 @@ class _User(_SetNickuserhostMixin, User):
 
     @modes.setter
     def modes(self, modeset: str) -> None:
 
     @modes.setter
     def modes(self, modeset: str) -> None:
-        operation, chars = _operation_chars_from_modeset_str(modeset)
+        operation, chars = modeset[:1], modeset[1:]
+        assert chars and operation in '+-'
         for char in chars:
             if operation == '+':
                 self._modes.add(char)
         for char in chars:
             if operation == '+':
                 self._modes.add(char)
@@ -320,7 +358,8 @@ class _UpdatingChannel(UpdatingAttrsMixin, _Channel):
     user_ids: UpdatingCompletableStringsSet
     topic: _UpdatingCompletableTopic
     exits: UpdatingDict[str]
     user_ids: UpdatingCompletableStringsSet
     topic: _UpdatingCompletableTopic
     exits: UpdatingDict[str]
-    prefixes: UpdatingDict[tuple[str, ...]]
+    modes_listy: UpdatingDict[tuple[str, ...]]
+    modes_valued: UpdatingDict[str]
 
 
 class _UpdatingUser(UpdatingAttrsMixin, _User):
 
 
 class _UpdatingUser(UpdatingAttrsMixin, _User):
@@ -440,7 +479,8 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields):
                                              )._preset_init_kwargs is not None:
             attr._preset_init_kwargs = {
                     'userid_for_nickuserhost': self.users.id_for_nickuserhost,
                                              )._preset_init_kwargs is not None:
             attr._preset_init_kwargs = {
                     'userid_for_nickuserhost': self.users.id_for_nickuserhost,
-                    'get_membership_prefixes': self._get_membership_prefixes,
+                    'get_membership_modes': self.get_membership_modes,
+                    'get_chanmodes': self._get_chanmodes,
                     'purge_users': self.users.purge}
         elif key == 'users':
             attr.userlen = int(self.isupport['USERLEN'])
                     'purge_users': self.users.purge}
         elif key == 'users':
             attr.userlen = int(self.isupport['USERLEN'])
@@ -581,21 +621,23 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields):
             return False
         if nick[0] in (ILLEGAL_NICK_FIRSTCHARS
                        + self.isupport['CHANTYPES']
             return False
         if nick[0] in (ILLEGAL_NICK_FIRSTCHARS
                        + self.isupport['CHANTYPES']
-                       + ''.join(self._get_membership_prefixes().values())):
+                       + ''.join(self.get_membership_modes().values())):
             return False
         for c in [c for c in nick if c in ILLEGAL_NICK_CHARS]:
             return False
         return True
 
             return False
         for c in [c for c in nick if c in ILLEGAL_NICK_CHARS]:
             return False
         return True
 
-    def _get_membership_prefixes(self) -> dict[str, str]:
-        'Registered membership nickname prefix possibilities.'
-        toks = self.isupport['PREFIX'].split(')', maxsplit=1)
-        assert len(toks) == 2
-        assert toks[0][0] == '('
-        toks[0] = toks[0][1:]
-        assert len(toks[0]) == len(toks[1])
-        prefixes = {toks[0][idx]: toks[1][idx] for idx in range(len(toks[0]))}
-        return prefixes
+    def _get_chanmodes(self) -> dict[str, str]:
+        'Parse CHANMODES into dict of mode-type char sequences.'
+        d = {}
+        idx_mode_types = 0
+        for chars in self.isupport.get('CHANMODES', '').split(','):
+            mode_type = chr(ord('A') + idx_mode_types)
+            d[mode_type] = chars
+            if mode_type == 'Z':  # in practice wouldn't expect much more than
+                break             # 4 mode types, even allowing 26 is gracious
+            idx_mode_types += 1
+        return d
 
 
 class _CapsManager(Clearable):
 
 
 class _CapsManager(Clearable):
index 18c9be6e811f1f73bde05d42e739ad9d5143fb29..b78ebd191ca4ee4737e4e2f50cd8a6a2c83bfad1 100644 (file)
@@ -195,6 +195,7 @@ class _Update:
         self.old_value = None
         self.force_log = False
         self.results = []
         self.old_value = None
         self.force_log = False
         self.results = []
+        self.steps: list[_UpdatingNode] = []
 
     @property
     def key(self) -> str:
 
     @property
     def key(self) -> str:
@@ -217,6 +218,7 @@ class _UpdatingNode(AutoAttrMixin):
         node = self._get(update.key)
         if len(update.rel_path) > 1:
             update.decrement_path()
         node = self._get(update.key)
         if len(update.rel_path) > 1:
             update.decrement_path()
+            update.steps += [self]
             node.recursive_set_and_report_change(update)
             return
         update.old_value = node
             node.recursive_set_and_report_change(update)
             return
         update.old_value = node
@@ -284,20 +286,26 @@ class _UpdatingDict(Dict[DictItem], _UpdatingNode):
 class _UpdatingChannel(_UpdatingNode, Channel):
     user_ids: set[str]
     exits: _UpdatingDict[str]
 class _UpdatingChannel(_UpdatingNode, Channel):
     user_ids: set[str]
     exits: _UpdatingDict[str]
-    prefixes: _UpdatingDict[tuple[str, ...]]
+    modes_listy: _UpdatingDict[tuple[str, ...]]
+    modes_valued: _UpdatingDict[str]
 
     def prefixtok_for(self, user_id: str) -> str:
         'Make ":{prefixes}" log token for user of user_id.'
 
     def prefixtok_for(self, user_id: str) -> str:
         'Make ":{prefixes}" log token for user of user_id.'
-        return ':' + ''.join(prefix for prefix, ids in self.prefixes.items()
-                             if user_id in ids)
+        d_prefixes = self._get_membership_modes()
+        return ':' + ''.join(d_prefixes[c]
+                             for c, ids in self.modes_listy.items()
+                             if c in d_prefixes and user_id in ids)
 
     def recursive_set_and_report_change(self, update: _Update) -> None:
         def diff_in(base: tuple[str, ...], excluder: tuple[str, ...]
                     ) -> tuple[str, ...]:
             return tuple(id_ for id_ in base if id_ not in excluder)
 
 
     def recursive_set_and_report_change(self, update: _Update) -> None:
         def diff_in(base: tuple[str, ...], excluder: tuple[str, ...]
                     ) -> tuple[str, ...]:
             return tuple(id_ for id_ in base if id_ not in excluder)
 
+        self._get_membership_modes = update.steps[0].get_membership_modes
         super().recursive_set_and_report_change(update)
         super().recursive_set_and_report_change(update)
-        if update.full_path[2] == 'prefixes' and self.user_ids:
+        if update.full_path[2] == 'modes_listy'\
+                and update.key in self._get_membership_modes().keys()\
+                and self.user_ids:
             update.results += [
                     (_LogScope.CHAT, [f'NICK:{id_}', f': gains {update.key}'])
                     for id_ in diff_in(update.value, update.old_value)]
             update.results += [
                     (_LogScope.CHAT, [f'NICK:{id_}', f': gains {update.key}'])
                     for id_ in diff_in(update.value, update.old_value)]
index 38b1939761459d938f3f517ab1129bbd50f49f04..52020503d1cd32c8ad643d3d8837aecc1ed63c47 100644 (file)
@@ -87,6 +87,10 @@ class Dict(Clearable, Generic[DictItem]):
         'Wrapper around dict.values().'
         return tuple((val for val in self._dict.values()))
 
         'Wrapper around dict.values().'
         return tuple((val for val in self._dict.values()))
 
+    def get(self, key: str, default: DictItem) -> DictItem:
+        'Wrapper around dict.get(key, default), excluding None returns.'
+        return self._dict.get(key, default)
+
 
 class Completable(ABC):
     'Data whose collection can be declared complete/incomplete.'
 
 class Completable(ABC):
     'Data whose collection can be declared complete/incomplete.'
index cdda2fa8b7c81b2d057e5c33a383b59c39e43e24..302044c60cf477643a8a80949825bc9a0dea726c 100644 (file)
@@ -600,9 +600,14 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
         'MODE',
         _MsgToken.SERVER,
         ((_MsgToken.CHANNEL, ':CHANNEL'),
         'MODE',
         _MsgToken.SERVER,
         ((_MsgToken.CHANNEL, ':CHANNEL'),
-         (_MsgToken.ANY, ':mode'),
-         (_MsgToken.NICKNAME, ':nick')),
-        bonus_tasks=('do_db.channels.CHANNEL.mode_on_nick:nick,mode',)),
+         (_MsgToken.ANY, 'do_db.channels.CHANNEL.set_modes:modeset'))),
+    _MsgParseExpectation(
+        'MODE',
+        _MsgToken.SERVER,
+        ((_MsgToken.CHANNEL, ':CHANNEL'),
+         (_MsgToken.ANY, ':modeset'),
+         (_MsgToken.ANY, ':args')),
+        bonus_tasks=('do_db.channels.CHANNEL.set_modes:modeset,args',)),
 
     _MsgParseExpectation(
         'TOPIC',
 
     _MsgParseExpectation(
         'TOPIC',
index deaf6356f5ff1b39107e3201d91d11f8bce9227f..114fe2a02c90da41b0abab9d48150f0f7528d5b5 100644 (file)
@@ -1,4 +1,5 @@
 insert ./lib/connect-to-usermode
 insert ./lib/connect-to-usermode
+insert ./lib/isupport-set
 insert ./lib/join-channel
 # for: join-channel-0-cmd-to-list-residents, join-channel-1-end-of-names
 insert ./lib/no-handler
 insert ./lib/join-channel
 # for: join-channel-0-cmd-to-list-residents, join-channel-1-end-of-names
 insert ./lib/no-handler
@@ -7,11 +8,17 @@ insert ./lib/privmsg-win
 insert ./lib/servermsglogged
 insert ./lib/user-set-to
 
 insert ./lib/servermsglogged
 insert ./lib/user-set-to
 
-× set-prefix
-insert servermsglogged [% (MSG)=:foo.bar.baz%MODE%#ch_win3%(MODESET)%(NICK)]
-log 1 $ channels:#ch_win3:prefixes:(PREFIX) (NEW_IDS)
+× servermsg-mode
+insert servermsglogged [% (MSG)=:foo.bar.baz%MODE%#ch_win3%(ARGS)]
+
+× prefix-log
+log 1 $ channels:#ch_win3:modes_listy:(PREFIX) (NEW_IDS)
 log 3 $ (NICK) (VERB) (PREFIX)
 
 log 3 $ (NICK) (VERB) (PREFIX)
 
+× set-prefix
+insert servermsg-mode [% (ARGS)=(MODESET)%(NICK)]
+insert prefix-log
+
 × prefix+
 insert set-prefix [(MODESET)=+(CHAR) (VERB)=gains]
 
 × prefix+
 insert set-prefix [(MODESET)=+(CHAR) (VERB)=gains]
 
@@ -30,16 +37,17 @@ insert msg-prefixed-long range=-1:
 × ×-------------------------
 
 insert connect-to-usermode
 × ×-------------------------
 
 insert connect-to-usermode
+insert isupport-set [(KEY)=CHANMODES (VALUE)=lmn,k,xy,abcABC]
 
 # check /join into channel with many other users, with multi-line 353, and membership prefixes
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=3 (RESIDENT_NAMES)=foo%@baz%+oof]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=baz]
 
 # check /join into channel with many other users, with multi-line 353, and membership prefixes
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=3 (RESIDENT_NAMES)=foo%@baz%+oof]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=baz]
-log 1 $ channels:#ch_win3:prefixes:@ set to: [1]
+log 1 $ channels:#ch_win3:modes_listy:o set to: [1]
 insert user-set-to range=:1 [(USER_ID)=2 (USER_NICK)=oof]
 insert user-set-to range=:1 [(USER_ID)=2 (USER_NICK)=oof]
-log 1 $ channels:#ch_win3:prefixes:+ set to: [2]
+log 1 $ channels:#ch_win3:modes_listy:v set to: [2]
 insert servermsglogged [% (MSG)=:foo.bar.baz%353%foo%=%#ch_win3%:+rab%zab]
 insert user-set-to range=:1 [(USER_ID)=3 (USER_NICK)=rab]
 insert servermsglogged [% (MSG)=:foo.bar.baz%353%foo%=%#ch_win3%:+rab%zab]
 insert user-set-to range=:1 [(USER_ID)=3 (USER_NICK)=rab]
-log 1 $ channels:#ch_win3:prefixes:+ set to: [2], [3]
+log 1 $ channels:#ch_win3:modes_listy:v set to: [2], [3]
 insert user-set-to range=:1 [(USER_ID)=4 (USER_NICK)=zab]
 insert join-channel-1-end-of-names [% (WIN_ID)=3 (RESIDENT_IDS)=[1],%[2],%[3],%[4],%[me] (RESIDENT_NICKS)=@baz,%+oof,%+rab,%zab,%foo]
 
 insert user-set-to range=:1 [(USER_ID)=4 (USER_NICK)=zab]
 insert join-channel-1-end-of-names [% (WIN_ID)=3 (RESIDENT_IDS)=[1],%[2],%[3],%[4],%[me] (RESIDENT_NICKS)=@baz,%+oof,%+rab,%zab,%foo]
 
@@ -51,13 +59,14 @@ insert msg-prefixed-long [(USER_ID)=3 (NICK)=rab (PREFIX)=+ PRIVMSG=NOTICE [+rab
 insert msg-prefixed-long [(USER_ID)=4 (NICK)=zab (PREFIX)=]
 
 # check server giving and taking membership prefixes
 insert msg-prefixed-long [(USER_ID)=4 (NICK)=zab (PREFIX)=]
 
 # check server giving and taking membership prefixes
-insert prefix- [% (CHAR)=o (PREFIX)=@ (NICK)=baz (NEW_IDS)=emptied]
-insert prefix- [% (CHAR)=v (PREFIX)=+ (NICK)=rab (NEW_IDS)=set%to:%[2]]
-insert prefix- [% (CHAR)=v (PREFIX)=+ (NICK)=oof (NEW_IDS)=emptied]
-insert prefix+ [% (CHAR)=v (PREFIX)=+ (NICK)=oof (NEW_IDS)=set%to:%[2]]
-insert prefix+ [% (CHAR)=o (PREFIX)=@ (NICK)=zab (NEW_IDS)=set%to:%[4]]
-insert prefix+ [% (CHAR)=o (PREFIX)=@ (NICK)=baz (NEW_IDS)=set%to:%[1],%[4]]
-insert prefix+ [% (CHAR)=o (PREFIX)=@ (NICK)=foo (NEW_IDS)=set%to:%[1],%[4],%[me]]
+insert prefix- [(CHAR)=o (PREFIX)=o (NICK)=baz (NEW_IDS)=emptied]
+insert prefix- [% (CHAR)=v (PREFIX)=v (NICK)=rab (NEW_IDS)=set%to:%[2]]
+insert servermsg-mode [% (ARGS)=-v+oo%:oof%zab%baz]
+insert prefix-log [% (PREFIX)=v (VERB)=loses (NICK)=oof (NEW_IDS)=emptied]
+insert prefix-log [% (PREFIX)=o (VERB)=gains (NICK)=zab (NEW_IDS)=set%to:%[4]]
+insert prefix-log [% (PREFIX)=o (VERB)=gains (NICK)=baz (NEW_IDS)=set%to:%[1],%[4]]
+insert prefix+ [% (CHAR)=v (PREFIX)=v (NICK)=oof (NEW_IDS)=set%to:%[2]]
+insert prefix+ [% (CHAR)=o (PREFIX)=o (NICK)=foo (NEW_IDS)=set%to:%[1],%[4],%[me]]
 
 # re-check membership prefix mirorring in message display after re-distribution
 insert privmsg-win [% (WIN_ID)=3 (TXT)=foo%foo%foo [foo]=[@foo]]
 
 # re-check membership prefix mirorring in message display after re-distribution
 insert privmsg-win [% (WIN_ID)=3 (TXT)=foo%foo%foo [foo]=[@foo]]
@@ -70,14 +79,58 @@ insert msg-prefixed-short [(USER_ID)=4 (NICK)=zab (PREFIX)=@]
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%baz]
 insert join-channel-1-end-of-names [% (WIN_ID)=4 (RESIDENT_IDS)=[1],%[me] (RESIDENT_NICKS)=baz,%foo]
 insert part-no-msg-other [% (USER_ID)=1 (NICK)=baz (REMAINING_IDS)=[2],%[3],%[4],%[me]]
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%baz]
 insert join-channel-1-end-of-names [% (WIN_ID)=4 (RESIDENT_IDS)=[1],%[me] (RESIDENT_NICKS)=baz,%foo]
 insert part-no-msg-other [% (USER_ID)=1 (NICK)=baz (REMAINING_IDS)=[2],%[3],%[4],%[me]]
-log 1 $ channels:#ch_win3:prefixes:@ set to: [4], [me]
+log 1 $ channels:#ch_win3:modes_listy:o set to: [4], [me]
 insert servermsglogged [% (MSG)=:baz!~bazbaz@baz.baz%JOIN%:#ch_win3]
 log 1 $ channels:#ch_win3:user_ids set to: [1], [2], [3], [4], [me]
 log 3 $ baz!~bazbaz@baz.baz joins
 insert msg-prefixed-short [(USER_ID)=1 (NICK)=baz (PREFIX)=]
 
 insert servermsglogged [% (MSG)=:baz!~bazbaz@baz.baz%JOIN%:#ch_win3]
 log 1 $ channels:#ch_win3:user_ids set to: [1], [2], [3], [4], [me]
 log 3 $ baz!~bazbaz@baz.baz joins
 insert msg-prefixed-short [(USER_ID)=1 (NICK)=baz (PREFIX)=]
 
-# check server setting unknown modes towards users
-insert servermsglogged [% (MSG)=:foo.bar.baz_MODE%#ch_win3%+a%zab]
-insert no-handler range=-2: [% (ALERT_WIN_IDS)=2,3,4 (MSG)=:foo.bar.baz_MODE%#ch_win3%+a%zab]
-insert servermsglogged [% (MSG)=:foo.bar.baz%MODE%#ch_win3%-b%rab]
-insert no-handler range=-2: [% (ALERT_WIN_IDS)=2,3,4 (MSG)=for%nickname%setting%channel%mode:%-b]
+# check server setting unknown or invalid modes
+insert servermsg-mode [(ARGS)=+f]
+insert no-handler range=-2: [% (ALERT_WIN_IDS)=2,3,4 (MSG)=channel%mode%setting%+f%on%args:%]
+insert servermsg-mode [% (ARGS)=-g%grab]
+insert no-handler range=-2: [% (ALERT_WIN_IDS)=2,3,4 (MSG)=channel%mode%setting%-g%on%args:%grab]
+insert servermsg-mode [(ARGS)=+l]
+insert no-handler range=-2: [% (ALERT_WIN_IDS)=2,3,4 (MSG)=channel%mode%setting%+l%on%args:%]
+insert servermsg-mode [% (ARGS)=+aBc%:foo%bar]
+insert no-handler range=-2: [% (ALERT_WIN_IDS)=2,3,4 (MSG)=channel%mode%setting%+aBc%on%args:%foo%bar]
+insert servermsg-mode [(ARGS)=a]
+insert no-handler range=-2: [% (ALERT_WIN_IDS)=2,3,4 (MSG)=channel%mode%setting%a%on%args:%]
+
+# check server setting type-A modes
+insert servermsg-mode [% (ARGS)=+l%:foo]
+log 1 $ channels:#ch_win3:modes_listy:l set to: [foo]
+insert servermsg-mode [% (ARGS)=+l%:bar]
+log 1 $ channels:#ch_win3:modes_listy:l set to: [bar], [foo]
+insert servermsg-mode [% (ARGS)=+l-l%:baz%foo]
+log 1 $ channels:#ch_win3:modes_listy:l set to: [bar], [baz], [foo]
+log 1 $ channels:#ch_win3:modes_listy:l set to: [bar], [baz]
+
+# check server setting type-B modes
+insert servermsg-mode [% (ARGS)=+k%:password]
+log 1 $ channels:#ch_win3:modes_valued:k set to: [password]
+insert servermsg-mode [% (ARGS)=-k%:*]
+log 1 $ channels:#ch_win3:modes_valued:k deleted
+
+# check server setting type-C modes
+insert servermsg-mode [% (ARGS)=+x%:foo]
+log 1 $ channels:#ch_win3:modes_valued:x set to: [foo]
+insert servermsg-mode [% (ARGS)=+x%:bar]
+log 1 $ channels:#ch_win3:modes_valued:x set to: [bar]
+insert servermsg-mode [% (ARGS)=+y%:zab]
+log 1 $ channels:#ch_win3:modes_valued:y set to: [zab]
+insert servermsg-mode [(ARGS)=-x]
+log 1 $ channels:#ch_win3:modes_valued:x deleted
+
+# check server setting type-D modes
+insert servermsg-mode [(ARGS)=+aBc]
+log 1 $ channels:#ch_win3:modes_toggled set to: [a]
+log 1 $ channels:#ch_win3:modes_toggled set to: [Ba]
+log 1 $ channels:#ch_win3:modes_toggled set to: [Bac]
+insert servermsg-mode [(ARGS)=-cba]
+log 1 $ channels:#ch_win3:modes_toggled set to: [Ba]
+log 1 $ channels:#ch_win3:modes_toggled set to: [B]
+insert servermsg-mode [(ARGS)=-B+cb]
+log 1 $ channels:#ch_win3:modes_toggled set to: []
+log 1 $ channels:#ch_win3:modes_toggled set to: [c]
+log 1 $ channels:#ch_win3:modes_toggled set to: [bc]
index ea5b369c8dfe8f42d0470127d1a3415d53b0bcc2..ea1a5aecb1e386d46353d0627266ae09cb814098 100644 (file)
@@ -6,6 +6,7 @@ insert ./lib/attempting-to-connected
 insert ./lib/connect-to-usermode
 insert ./lib/disconnect
 insert ./lib/isupport-clear
 insert ./lib/connect-to-usermode
 insert ./lib/disconnect
 insert ./lib/isupport-clear
+insert ./lib/isupport-set
 insert ./lib/join-channel
 # for: join-channel-0-cmd-to-list-residents, join-channel-1
 insert ./lib/join-empty
 insert ./lib/join-channel
 # for: join-channel-0-cmd-to-list-residents, join-channel-1
 insert ./lib/join-empty
@@ -13,14 +14,10 @@ insert ./lib/no-handler
 insert ./lib/servermsglogged
 insert ./lib/user-set-to
 
 insert ./lib/servermsglogged
 insert ./lib/user-set-to
 
-× un-default
-insert servermsglogged [% (MSG)=:foo.bar.baz%005%foo%(KEY)=(VALUE)%:]
-log 1 $ isupport:(KEY) set to: [(VALUE)]
-
 × un-defaults
 × un-defaults
-insert un-default [(KEY)=CHANTYPES (VALUE)=123=456]
-insert un-default [(KEY)=PREFIX (VALUE)=(ovE)@+=]
-insert un-default [(KEY)=USERLEN (VALUE)=8]
+insert isupport-set [(KEY)=CHANTYPES (VALUE)=123=456]
+insert isupport-set [(KEY)=PREFIX (VALUE)=(ovE)@+=]
+insert isupport-set [(KEY)=USERLEN (VALUE)=8]
 
 × join-and-hi
 insert servermsglogged [% (MSG)=:(NICK)!(TO_CUT)q@(NICK).(NICK)%JOIN%:#ch_win9]
 
 × join-and-hi
 insert servermsglogged [% (MSG)=:(NICK)!(TO_CUT)q@(NICK).(NICK)%JOIN%:#ch_win9]
@@ -73,7 +70,7 @@ insert 001-to-usermode
 insert join-empty [(WIN_ID)=3]
 insert join-empty [(WIN_ID)=4 #ch_win=&ch_win]
 insert no-handler [% (ALERT_WIN_IDS)=2,3,4 (MSG)=foo!~baz@baz.bar.foo%JOIN%$ch_winA]
 insert join-empty [(WIN_ID)=3]
 insert join-empty [(WIN_ID)=4 #ch_win=&ch_win]
 insert no-handler [% (ALERT_WIN_IDS)=2,3,4 (MSG)=foo!~baz@baz.bar.foo%JOIN%$ch_winA]
-insert un-default [(KEY)=CHANTYPES (VALUE)=#$%]
+insert isupport-set [(KEY)=CHANTYPES (VALUE)=#$%]
 insert join-empty [(WIN_ID)=5]
 insert no-handler [% (ALERT_WIN_IDS)=2,3,4,5 (MSG)=foo!~baz@baz.bar.foo%JOIN%&ch_winB]
 insert join-empty [(WIN_ID)=6 #ch_win=$ch_win]
 insert join-empty [(WIN_ID)=5]
 insert no-handler [% (ALERT_WIN_IDS)=2,3,4,5 (MSG)=foo!~baz@baz.bar.foo%JOIN%&ch_winB]
 insert join-empty [(WIN_ID)=6 #ch_win=$ch_win]
@@ -81,21 +78,21 @@ insert join-empty [(WIN_ID)=6 #ch_win=$ch_win]
 # test effect of PREFIX
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=7 (RESIDENT_NAMES)=foo%@bar%+baz%=quux]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=bar]
 # test effect of PREFIX
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=7 (RESIDENT_NAMES)=foo%@bar%+baz%=quux]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=bar]
-log 1 $ channels:#ch_win7:prefixes:@ set to: [1]
+log 1 $ channels:#ch_win7:modes_listy:o set to: [1]
 insert user-set-to range=:1 [(USER_ID)=2 (USER_NICK)=baz]
 insert user-set-to range=:1 [(USER_ID)=2 (USER_NICK)=baz]
-log 1 $ channels:#ch_win7:prefixes:+ set to: [2]
+log 1 $ channels:#ch_win7:modes_listy:v set to: [2]
 insert user-set-to range=:1 [(USER_ID)=3 (USER_NICK)==quux]
 insert join-channel-1-end-of-names [% (WIN_ID)=7 (RESIDENT_IDS)=[1],%[2],%[3],%[me] (RESIDENT_NICKS)=@bar,%+baz,%=quux,%foo]
 insert user-set-to range=:1 [(USER_ID)=3 (USER_NICK)==quux]
 insert join-channel-1-end-of-names [% (WIN_ID)=7 (RESIDENT_IDS)=[1],%[2],%[3],%[me] (RESIDENT_NICKS)=@bar,%+baz,%=quux,%foo]
-insert un-default [(KEY)=PREFIX (VALUE)=(vE)+=]
+insert isupport-set [(KEY)=PREFIX (VALUE)=(vE)+=]
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=8 (RESIDENT_NAMES)=foo%@bar%+baz%=quux]
 insert user-set-to range=:1 [(USER_ID)=4 (USER_NICK)=@bar]
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=8 (RESIDENT_NAMES)=foo%@bar%+baz%=quux]
 insert user-set-to range=:1 [(USER_ID)=4 (USER_NICK)=@bar]
-log 1 $ channels:#ch_win8:prefixes:+ set to: [2]
+log 1 $ channels:#ch_win8:modes_listy:v set to: [2]
 insert user-set-to range=:1 [(USER_ID)=5 (USER_NICK)=quux]
 insert user-set-to range=:1 [(USER_ID)=5 (USER_NICK)=quux]
-log 1 $ channels:#ch_win8:prefixes:= set to: [5]
+log 1 $ channels:#ch_win8:modes_listy:E set to: [5]
 insert join-channel-1-end-of-names [% (WIN_ID)=8 (RESIDENT_IDS)=[2],%[4],%[5],%[me] (RESIDENT_NICKS)=+baz,%@bar,%=quux,%foo]
 
 # test effect of USERLEN
 insert join-empty [(WIN_ID)=9]
 insert join-and-hi [(NICK)=user0 (USER_ID)=6 (TO_CUT)=foobarbaz (RESIDENT_IDS)=[6]]
 insert join-channel-1-end-of-names [% (WIN_ID)=8 (RESIDENT_IDS)=[2],%[4],%[5],%[me] (RESIDENT_NICKS)=+baz,%@bar,%=quux,%foo]
 
 # test effect of USERLEN
 insert join-empty [(WIN_ID)=9]
 insert join-and-hi [(NICK)=user0 (USER_ID)=6 (TO_CUT)=foobarbaz (RESIDENT_IDS)=[6]]
-insert un-default [(KEY)=USERLEN (VALUE)=8]
+insert isupport-set [(KEY)=USERLEN (VALUE)=8]
 insert join-and-hi [% (NICK)=user1 (USER_ID)=7 (TO_CUT)=foobarb (RESIDENT_IDS)=[6],%[7]]
 insert join-and-hi [% (NICK)=user1 (USER_ID)=7 (TO_CUT)=foobarb (RESIDENT_IDS)=[6],%[7]]
diff --git a/src/tests/lib/isupport-set b/src/tests/lib/isupport-set
new file mode 100644 (file)
index 0000000..3757f02
--- /dev/null
@@ -0,0 +1,5 @@
+insert ./lib/servermsglogged
+
+× isupport-set
+insert servermsglogged [% (MSG)=:foo.bar.baz%005%foo%(KEY)=(VALUE)%:]
+log 1 $ isupport:(KEY) set to: [(VALUE)]
index d04cf5f43ba4a8a2b6d1c38e702e691c7a75537e..897a791eb2f4bd8a06200c025c7bc68efbd746b1 100644 (file)
@@ -115,7 +115,7 @@ log 3 $ foo1!~foofoo@foo.foo renames foo
 # join channel with other user
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%@baz]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=baz]
 # join channel with other user
 insert join-channel-0-cmd-to-list-residents [% (WIN_ID)=4 (RESIDENT_NAMES)=foo%@baz]
 insert user-set-to range=:1 [(USER_ID)=1 (USER_NICK)=baz]
-log 1 $ channels:#ch_win4:prefixes:@ set to: [1]
+log 1 $ channels:#ch_win4:modes_listy:o set to: [1]
 insert join-channel-1-end-of-names [% (WIN_ID)=4 (RESIDENT_IDS)=[1],%[me] (RESIDENT_NICKS)=@baz,%foo]
 
 # process non-self channel JOIN
 insert join-channel-1-end-of-names [% (WIN_ID)=4 (RESIDENT_IDS)=[1],%[me] (RESIDENT_NICKS)=@baz,%foo]
 
 # process non-self channel JOIN