home · contact · privacy
Add basic infrastructure for collecting channel information.
authorChristian Heller <c.heller@plomlompom.de>
Fri, 15 Aug 2025 04:45:26 +0000 (06:45 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Fri, 15 Aug 2025 04:45:26 +0000 (06:45 +0200)
ircplom/client.py

index 0eff7a46491daac33ab992af4cdffdb5f8fdb375..c985c33c8ab71b622a8e9afe5c0af78badfb9ff6 100644 (file)
@@ -22,6 +22,8 @@ _NUMERICS_TO_CONFIRM_NICKNAME = (
     (1, 5),
     (251, 255),
     (265, 266),
+    353,
+    366,
     372,
     (375, 376),
     396,
@@ -131,7 +133,7 @@ class _CapsManager:
                     self._db.append('caps_LS', param, keep_confirmed=True)
             case 'DEL':
                 for param in params[-1].split():
-                    self._db.caps_LS.remove(param)
+                    self._db.remove('caps_LS', param)
             case 'ACK' | 'NAK':
                 for name in params[-1].split():
                     if params[0] == 'ACK':
@@ -174,13 +176,8 @@ class IrcConnSetup(NamedTuple):
     password: str
 
 
-class ClientDbBase:
+class _Db:
     'For values of variable confirmation, and reading in multi-line lists.'
-    client_host: str
-    isupports: list[str]
-    motd: list[str]
-    nickname: str
-    user_modes: str
 
     def __init__(self) -> None:
         self._dict: dict[str, ClientDbType] = {}
@@ -261,15 +258,6 @@ class ClientDbBase:
             self._typecheck(key, value)
         return (value, key in self._confirmeds)
 
-
-class _ClientDb(ClientDbBase):
-    caps_LS: list[str]
-    caps_LIST: list[str]
-    hostname: str
-    password: str
-    port: int
-    realname: str
-
     def append(self, key: str, value: str, keep_confirmed=False) -> None:
         'To list[str] keyed by key, append value; if non-existant, create it.'
         if not keep_confirmed and key in self._confirmeds:
@@ -283,6 +271,39 @@ class _ClientDb(ClientDbBase):
         else:
             raise CrashingException('called on non-list entry')
 
+    def remove(self, key, value: str) -> None:
+        'From list[str] keyed by key, remove value.'
+        targeted = self._dict.get(key, None)
+        if isinstance(targeted, list):
+            targeted.remove(value)
+        else:
+            raise CrashingException('called on non-list entry')
+
+
+class ClientDbBase(_Db):
+    'DB exposable to TUI.'
+    client_host: str
+    isupports: list[str]
+    motd: list[str]
+    nickname: str
+    user_modes: str
+
+
+class _ChannelDb(_Db):
+    users: list[str]
+    topic: str
+    channel_modes: str
+
+
+class _ClientDb(ClientDbBase):
+    caps_LS: list[str]
+    caps_LIST: list[str]
+    hostname: str
+    password: str
+    port: int
+    realname: str
+    _channels: dict[str, _ChannelDb]
+
     @property
     def conn_setup(self) -> IrcConnSetup:
         'Constructed out of stored entries *including* unconfirmed ones.'
@@ -306,6 +327,18 @@ class _ClientDb(ClientDbBase):
                 d[name] = _ServerCapability(name in self.caps_LIST, data)
         return d
 
+    def del_channel(self, name: str) -> None:
+        'Remove DB for channel of name.'
+        del self._channels[name]
+
+    def channel(self, name: str) -> _ChannelDb:
+        'Produce DB for channel of name – pre-existing, or newly created.'
+        if self._channels is None:
+            self._channels = {}
+        if name not in self._channels:
+            self._channels[name] = _ChannelDb()
+        return self._channels[name]
+
 
 class Client(ABC, ClientQueueMixin):
     'Abstracts socket connection, loop over it, and handling messages from it.'
@@ -414,6 +447,11 @@ class Client(ABC, ClientQueueMixin):
             case '005' if len(msg.params) > 2:
                 for param in msg.params[1:-1]:
                     self._db.append('isupports', param)
+            case '353' if len(msg.params) == 4:
+                for user in msg.params[3].split():
+                    self._db.channel(msg.params[2]).append('users', user)
+            case '366' if len(msg.params) == 3:
+                self._db.channel(msg.params[1]).set('users', None, True)
             case '372' if len(msg.params) == 2:  # RPL_MOTD
                 self._db.append('motd', msg.params[-1])
             case '376' if len(msg.params) == 2:  # RPL_ENDOFMOTD
@@ -442,6 +480,9 @@ class Client(ABC, ClientQueueMixin):
             case 'ERROR' if len(msg.params) == 1:
                 self.close()
             case 'JOIN' if len(msg.params) == 1:
+                if msg.nick_from_source != self._db.nickname:
+                    self._db.channel(channel).append(
+                            'users', msg.nick_from_source, keep_confirmed=True)
                 self._log(log_msg, scope=scope, channel=channel)
             case 'MODE' if (len(msg.params) == 2
                             and msg.params[0] == self._db.nickname):
@@ -458,6 +499,11 @@ class Client(ABC, ClientQueueMixin):
                 if len(msg.params) > 1:
                     log_msg += f': {msg.params[1]}'
                 self._log(log_msg, scope=scope, channel=channel)
+                if msg.nick_from_source == self._db.nickname:
+                    self._db.del_channel(channel)
+                else:
+                    self._db.channel(channel).remove('users',
+                                                     msg.nick_from_source)
             case 'PING' if len(msg.params) == 1:
                 self.send(IrcMessage(verb='PONG', params=(msg.params[0],)))
             case '903' | '904' if len(msg.params) == 1: