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.'
self.purge_users = purge_users
super().__init__(**kwargs)
+ def _id_from_nick(self, nick: str, create_if_none: bool) -> str:
+ 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] += (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()
+ prefixes = self._get_membership_prefixes().values()
for item in items:
prefix = ''.join([pfx for pfx in prefixes if item.startswith(pfx)])
- n_u_h = NickUserHost(item.lstrip(prefix))
- user_id = self._userid_for_nickuserhost(n_u_h, create_if_none=True)
+ user_id = self._id_from_nick(item.lstrip(prefix), True)
self.user_ids.completable_add(user_id, on_complete=False)
if prefix:
- if prefix in self.prefixes.keys():
- self.prefixes[prefix] += (user_id,)
- else:
- self.prefixes[prefix] = (user_id,)
+ self._add_membership_prefix(user_id, prefix)
def add_user(self, user: '_User') -> None:
'To .user_ids add user.nickname, keep .user_ids declared complete.'
def remove_user(self, user: '_User', msg: str) -> None:
'From .user_ids remove .nickname, keep .user_ids declared complete.'
for prefix in self.prefixes.keys():
- self.prefixes[prefix] = tuple(id_ for id_ in self.prefixes[prefix]
- if id_ != user.id_)
+ self._remove_membership_prefix(user.id_, prefix)
self.exits[user.id_] = msg
self.user_ids.completable_remove(user.id_, on_complete=True)
del self.exits[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)
+
class _ChatMessage(ChatMessage):
@modes.setter
def modes(self, modeset: str) -> None:
- operation, chars = modeset[:1], modeset[1:]
- assert chars and operation in '+-'
+ operation, chars = _operation_chars_from_modeset_str(modeset)
for char in chars:
if operation == '+':
self._modes.add(char)
return False
if nick[0] in (ILLEGAL_NICK_FIRSTCHARS
+ self.isupport['CHANTYPES']
- + self._get_membership_prefixes()):
+ + ''.join(self._get_membership_prefixes().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) -> str:
- 'Registered possible membership nickname prefixes.'
+ 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] == '('
- return toks[1]
+ 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 _CapsManager(Clearable):
self.caps.end_negotiation()
elif ret['_verb'] == 'JOIN' and ret['joiner'] != self.db.users['me']:
self.db.channels[ret['channel']].add_user(ret['joiner'])
+ elif ret['_verb'] == 'MODE' and 'mode_on_nick' in ret:
+ self.db.channels[ret['channel']].mode_on_nick(
+ ret['nick'], ret['mode_on_nick'])
elif ret['_verb'] == 'NICK':
user_id = self.db.users.id_for_nickuserhost(ret['named'],
updating=True)
# for: disconnect0, disconnect1
insert ./lib/join-empty
# for: join-channel-0, join-channel-1, join-empty
+insert ./lib/no-handler
insert ./lib/part
# for: exit-channel, part, parts-core, quit
insert ./lib/privmsg
× reconnect
> /reconnect
insert attempting-to-connected : + WIN_IDS :2,3,4
-insert caps-neg-empty : +
-insert 001-setting-nick : +
+insert caps-neg-empty
+insert 001-setting-nick
log 1 > JOIN :#ch_test0
insert usermode
insert servermsglogged : + MSG ::foo!~baz@baz.bar.foo JOIN #ch_test0
insert part : + CHANNEL=#ch_test0 CHAN_WIN_ID=3 USERIDS_CLEAR :set to: [1]
log 1 $ users:1 deleted
-# check /join into channel with many other users, with multi-line 353
+# check /join into channel with many other users, with multi-line 353, and membership prefixes
insert join-channel-0 : + CHANNEL=#ch_test0 RESIDENT_NAMES :foo @baz +oof
insert user-set-to :1 + USER_ID=2 USERNICK :baz
log 1 $ channels:#ch_test0:prefixes:@ set to: [2]
insert join-channel-1 : + CHANNEL=#ch_test0 RESIDENT_IDS :[2], [3], [4], [5], [me]
log 3 $ residents: baz, oof, rab, zab, foo
+# check server giving and taking membership prefixes
+insert servermsglogged : + MSG ::foo.bar.baz MODE #ch_test0 +o zab
+log 1 $ channels:#ch_test0:prefixes:@ set to: [2], [5]
+insert servermsglogged : + MSG ::foo.bar.baz MODE #ch_test0 -v rab
+log 1 $ channels:#ch_test0:prefixes:+ set to: [3]
+
+# check server setting unknown modes towards users
+insert servermsglogged : + MSG ::foo.bar.baz MODE #ch_test0 +a zab
+insert no-handler -2: + ALERT_WIN_IDS=2,3,4 ? :for nickname setting channel mode: +a
+insert servermsglogged : + MSG ::foo.bar.baz MODE #ch_test0 -b rab
+insert no-handler -2: + ALERT_WIN_IDS=2,3,4 ? :for nickname setting channel mode: -b
+
# check /join into channel with topic set
> /window 4
insert part-empty : + CHAN_WIN_ID=4 CHANNEL :#ch_test1
# check part of user visible, and of user NOT visible in other channel
insert part-other-0-no-msg : + NICK :baz
-log 1 $ channels:#ch_test0:prefixes:@ emptied
+log 1 $ channels:#ch_test0:prefixes:@ set to: [5]
insert part-other-1-no-msg : + USER_ID=2 NICK=baz REMAINING_IDS :[3], [4], [5], [me]
insert part-other-0-no-msg : + NICK :oof
-log 1 $ channels:#ch_test0:prefixes:+ set to: [4]
+log 1 $ channels:#ch_test0:prefixes:+ emptied
insert part-other-1-no-msg : + USER_ID=3 NICK=oof REMAINING_IDS :[4], [5], [me]
log 1 $ users:3 deleted
# check other-user part with exit message
insert part-other-0 : + NICK=zab ARGS :#ch_test0 :goodbye
insert user-set-to 1: + USER_ID=5 USERNAME=~zab USERHOST :zab.zab
+log 1 $ channels:#ch_test0:prefixes:@ emptied
insert part-other-1 : + USER_ID=5 NICK=zab exitPREFIX=:§ exitMSG=goodbye REMAINING_IDS=[4],§[me] § :
log 1 $ users:5 deleted