home · contact · privacy
Move more message handling into MSG_EXPECTATIONS/out of Client.handle_msg.
authorChristian Heller <c.heller@plomlompom.de>
Sat, 22 Nov 2025 06:26:42 +0000 (07:26 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Sat, 22 Nov 2025 06:26:42 +0000 (07:26 +0100)
src/ircplom/client.py
src/ircplom/msg_parse_expectations.py
src/tests/channels.test

index 873b1f1839343210fd1d4d52829e44fe29989c51..a273230741d8307426cf7f3587fa355434b7124b 100644 (file)
@@ -216,11 +216,13 @@ class _Channel(Channel):
             if prefix:
                 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.'
-        user_id = self._userid_for_nickuserhost(user, create_if_none=True,
+    def join_user(self, user: '_User') -> None:
+        'Register non-"me" user joining channel.'
+        user_id = self._userid_for_nickuserhost(nickuserhost=user,
+                                                create_if_none=True,
                                                 updating=True)
-        self.user_ids.completable_add(user_id, on_complete=True)
+        if user_id != 'me':  # own JOIN would have added name via RPL_NAMREPLY
+            self.user_ids.completable_add(user_id, on_complete=True)
 
     def remove_user(self, user: '_User', msg: str) -> None:
         'From .user_ids remove .nickname, keep .user_ids declared complete.'
@@ -503,6 +505,15 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields):
             attr._preset_init_kwargs = {}
         return attr
 
+    def set_isupport_from_rpl(self, rpl: tuple[str, ...]) -> None:
+        'Parse rpl for additions/deletions to .isupport dict.'
+        for item in rpl:
+            if item[0] == '-':
+                del self.isupport[item[1:]]
+            else:
+                key, data = _tuple_key_val_from_eq_str(item)
+                self.isupport[key] = data
+
     def messaging(self, src: str | NickUserHost) -> ChatMessage:
         'Start input chain for chat message data.'
         return _ChatMessage(sender=src, db=self)
@@ -795,6 +806,35 @@ class Client(ABC, ClientQueueMixin):
         self.conn.send(msg)
         return msg
 
+    def autojoin(self) -> None:
+        'Join all channels marked for auto-join, clear same auto-join list.'
+        for name in self._autojoins:
+            self.send('JOIN', name)
+        self._autojoins.clear()
+
+    def send_authentication(self) -> None:
+        'Send AUTHENTICATE with b64-encoded authentication data.'
+        auth = b64encode((self.db.nick_wanted + '\0'
+                          + self.db.nick_wanted + '\0'
+                          + self.db.password
+                          ).encode('utf-8')
+                         ).decode('utf-8')
+        self.send('AUTHENTICATE', auth)
+
+    def increment_for_nicknameinuse(self, rejected_nick: str) -> None:
+        'Note ERR_NICKNAMEINUSE and try incremented variant of rejected_nick.'
+        self._alert('nickname already in use, trying increment')
+        self.send('NICK', _NickUserHost(nick=rejected_nick).incremented)
+
+    def check_pong(self, reply: str) -> None:
+        'Check reply matching our PING, zero our PING expectations.'
+        assert self._expected_pong == reply
+        self._expected_pong = ''
+
+    def pong(self, reply: str) -> None:
+        'Reply PING with its expected reply.'
+        self.send('PONG', reply)
+
     def handle_msg(self, msg: IrcMessage) -> None:
         'Log msg.raw, then process incoming msg into appropriate client steps.'
         ret = {}
@@ -837,32 +877,12 @@ class Client(ABC, ClientQueueMixin):
                         node(ret[tok_name])
                     else:
                         node()
-        if ret['_verb'] == '001':   # RPL_WELCOME
-            for name in self._autojoins:
-                self.send('JOIN', name)
-            self._autojoins.clear()
-        elif ret['_verb'] == '005':   # RPL_ISUPPORT
-            for item in ret['isupport']:
-                if item[0] == '-':
-                    del self.db.isupport[item[1:]]
-                else:
-                    key, data = _tuple_key_val_from_eq_str(item)
-                    self.db.isupport[key] = data
-        elif ret['_verb'] == '401':  # ERR_NOSUCHNICK
+        if ret['_verb'] == '401':  # ERR_NOSUCHNICK
             raise TargetUserOffline(ret['missing'])
-        elif ret['_verb'] == '432':  # ERR_ERRONEOUSNICKNAME
+        if ret['_verb'] == '432':  # ERR_ERRONEOUSNICKNAME
             self._alert('nickname refused for bad format, '
                         + ('keeping current one' if 'nick' in ret
                            else 'giving up'))
-        elif ret['_verb'] == '433':  # ERR_NICKNAMEINUSE
-            self._alert('nickname already in use, trying increment')
-            self.send('NICK', _NickUserHost(nick=ret['used']).incremented)
-        elif ret['_verb'] == 'AUTHENTICATE':
-            auth = b64encode((self.db.nick_wanted + '\0'
-                              + self.db.nick_wanted + '\0'
-                              + self.db.password
-                              ).encode('utf-8')).decode('utf-8')
-            self.send('AUTHENTICATE', auth)
         elif ret['_verb'] == 'CAP':
             if (self.caps.process_msg(verb=ret['subverb'],
                                       items=ret['items'],
@@ -875,8 +895,6 @@ class Client(ABC, ClientQueueMixin):
                     self.send('AUTHENTICATE', _SASL_PLAIN)
                 else:
                     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'])
@@ -892,11 +910,6 @@ class Client(ABC, ClientQueueMixin):
             if ret['parter'] is self.db.users['me']:
                 del self.db.channels[ret['channel']]
                 self.db.users.purge()
-        elif ret['_verb'] == 'PING':
-            self.send('PONG', ret['reply'])
-        elif ret['_verb'] == 'PONG':
-            assert self._expected_pong == ret['reply']
-            self._expected_pong = ''
 
 
 ClientsDb = dict[str, Client]
index 0feaa0f10828e49510b67aa4b0e13c2a86779826..26cf2e53d87dd8c62e0fef13d211c2df7ad69ec5 100644 (file)
@@ -145,7 +145,8 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
         '001',  # RPL_WELCOME
         _MsgTok.SERVER,
         ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'),
-         _MsgTok.ANY)),
+         _MsgTok.ANY),
+        bonus_tasks=('do_autojoin:',)),
 
     _MsgParseExpectation(
         '002',  # RPL_YOURHOST
@@ -246,7 +247,7 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
         '005',  # RPL_ISUPPORT
         _MsgTok.SERVER,
         ((_MsgTok.NICKNAME, 'setattr_db.users.me:nick'),
-         (_MsgTok.ANY, ':isupport'),
+         (_MsgTok.ANY, 'do_db.set_isupport_from_rpl:isupport'),
          _MsgTok.ANY),  # comment
         idx_into_list=1),
 
@@ -297,7 +298,8 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'AUTHENTICATE',
         _MsgTok.NONE,
-        ('+',)),
+        ('+',),
+        bonus_tasks=('do_send_authentication:',)),
 
     # capability negotation
 
@@ -415,13 +417,13 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
         '433',  # ERR_NICKNAMEINUSE
         _MsgTok.SERVER,
         ('*',
-         (_MsgTok.NICKNAME, ':used'),
+         (_MsgTok.NICKNAME, 'do_increment_for_nicknameinuse:rejected'),
          _MsgTok.ANY)),  # comment
     _MsgParseExpectation(
         '433',  # ERR_NICKNAMEINUSE
         _MsgTok.SERVER,
         (_MsgTok.NICKNAME,  # we rather go for incrementation
-         (_MsgTok.NICKNAME, ':used'),
+         (_MsgTok.NICKNAME, 'do_increment_for_nicknameinuse:rejected'),
          _MsgTok.ANY)),  # comment
 
     _MsgParseExpectation(
@@ -473,8 +475,8 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
 
     _MsgParseExpectation(
         'JOIN',
-        (_MsgTok.NICK_USER_HOST, ':joiner'),
-        ((_MsgTok.CHANNEL, ':channel'),)),
+        (_MsgTok.NICK_USER_HOST, 'do_db.channels.CHANNEL.join_user:user'),
+        ((_MsgTok.CHANNEL, ':CHANNEL'),)),
 
     _MsgParseExpectation(
         'PART',
@@ -546,13 +548,13 @@ MSG_EXPECTATIONS: list[_MsgParseExpectation] = [
     _MsgParseExpectation(
         'PING',
         _MsgTok.NONE,
-        ((_MsgTok.ANY, ':reply'),)),
+        ((_MsgTok.ANY, 'do_pong:reply'),)),
 
     _MsgParseExpectation(
         'PONG',
         _MsgTok.SERVER,
         (_MsgTok.SERVER,
-         (_MsgTok.ANY, ':reply'),)),
+         (_MsgTok.ANY, 'do_check_pong:reply'),)),
 
     # misc.
 
index 605a91b509cba3863e946278d87a0fb4475bc532..61db464cd59d02e1ca2e60d424501386fd765828 100644 (file)
@@ -35,7 +35,7 @@ insert 001-setting-nick
 log 1 > JOIN :#ch_test0
 insert usermode
 insert servermsglogged : + MSG ::foo!~baz@baz.bar.foo JOIN #ch_test0
-insert join-empty : + CHAN_WIN_ID=3 CHANNEL :#ch_test0
+insert join-empty 2: + CHAN_WIN_ID=3 CHANNEL :#ch_test0
 
 × join-already-in
 > /join TARGET