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.'
'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.'
'Collects .topic, and in .user_ids inhabitant IDs.'
topic: Topic
user_ids: Iterable[str]
- prefixes: Dict[tuple[str, ...]]
exits: Dict[str]
+ modes_listy: Dict[tuple[str, ...]]
+ modes_valued: Dict[str]
+ modes_toggled: str = ''
@dataclass
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
- 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)
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.'
- prefixes = self._get_membership_prefixes().values()
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)
- 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.'
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()
- 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:
@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)
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):
)._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'])
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
- 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):
self.old_value = None
self.force_log = False
self.results = []
+ self.steps: list[_UpdatingNode] = []
@property
def key(self) -> str:
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
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.'
- 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)
+ self._get_membership_modes = update.steps[0].get_membership_modes
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)]
'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.'
'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',
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/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)
+× set-prefix
+insert servermsg-mode [% (ARGS)=(MODESET)%(NICK)]
+insert prefix-log
+
× prefix+
insert set-prefix [(MODESET)=+(CHAR) (VERB)=gains]
× ×-------------------------
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]
-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]
-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]
-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 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]]
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)=]
-# 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]
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/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
-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]
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]
# 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]
-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 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]
-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]
-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 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]]
--- /dev/null
+insert ./lib/servermsglogged
+
+× isupport-set
+insert servermsglogged [% (MSG)=:foo.bar.baz%005%foo%(KEY)=(VALUE)%:]
+log 1 $ isupport:(KEY) set to: [(VALUE)]
# 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