From: Christian Heller Date: Sat, 22 Nov 2025 02:32:11 +0000 (+0100) Subject: Add server setting channel membership prefixes. X-Git-Url: https://plomlompom.com/repos/task?a=commitdiff_plain;h=f70a76f808a60841f9c69f71cf2b647a593c639b;p=ircplom Add server setting channel membership prefixes. --- diff --git a/src/ircplom/client.py b/src/ircplom/client.py index 514e410..873b1f1 100644 --- a/src/ircplom/client.py +++ b/src/ircplom/client.py @@ -36,6 +36,12 @@ def _tuple_key_val_from_eq_str(eq_str: str) -> tuple[str, str]: 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.' @@ -186,19 +192,29 @@ class _Channel(Channel): 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.' @@ -209,13 +225,26 @@ class _Channel(Channel): 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): @@ -322,8 +351,7 @@ class _User(_SetNickuserhostMixin, User): @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) @@ -556,18 +584,21 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields): 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): @@ -846,6 +877,9 @@ class Client(ABC, ClientQueueMixin): 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) diff --git a/src/ircplom/msg_parse_expectations.py b/src/ircplom/msg_parse_expectations.py index 89c1d56..0feaa0f 100644 --- a/src/ircplom/msg_parse_expectations.py +++ b/src/ircplom/msg_parse_expectations.py @@ -567,6 +567,13 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [ ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'), (_MsgTok.ANY, 'setattr_db.users.me:modes'))), + _MsgParseExpectation( + 'MODE', + _MsgTok.SERVER, + ((_MsgTok.CHANNEL, ':channel'), + (_MsgTok.ANY, ':mode_on_nick'), + (_MsgTok.NICKNAME, ':nick'))), + _MsgParseExpectation( 'TOPIC', (_MsgTok.NICK_USER_HOST, 'setattr_db.channels.CHAN.topic:who'), diff --git a/src/tests/channels.test b/src/tests/channels.test index 657188f..605a91b 100644 --- a/src/tests/channels.test +++ b/src/tests/channels.test @@ -11,6 +11,7 @@ insert ./lib/disconnect # 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 @@ -29,8 +30,8 @@ log 4 $ baz!~baz@baz.baz set topic: NEWTOPIC × 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 @@ -113,7 +114,7 @@ log 3 $ residents: bar, foo 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] @@ -126,6 +127,18 @@ insert user-set-to :1 + USER_ID=5 USERNICK :zab 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 @@ -160,16 +173,17 @@ log 3 < (*.?.net) msg_test6 msg_test7 # 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