From 7bbfa9b3450637b7261324ca3eb0920a14d88765 Mon Sep 17 00:00:00 2001 From: user Date: Sun, 15 Feb 2026 04:16:41 +0100 Subject: [PATCH] feat: add per-channel plugin filtering Channels with a [channels."#name"] section and `plugins` list only run those plugins. Unconfigured channels run everything. Core is always active. PMs are unrestricted. Denied commands are silently ignored. --- src/derp/bot.py | 21 +++++++++++++++++++++ src/derp/config.py | 2 ++ 2 files changed, 23 insertions(+) diff --git a/src/derp/bot.py b/src/derp/bot.py index d8e6641..40ad43a 100644 --- a/src/derp/bot.py +++ b/src/derp/bot.py @@ -281,8 +281,11 @@ class Bot: return # Dispatch to event handlers (fire-and-forget) + channel = msg.target if msg.is_channel else None event_type = msg.command for handler in self.registry.events.get(event_type, []): + if not self._plugin_allowed(handler.plugin, channel): + continue self._spawn(handler.callback(self, msg), name=f"event:{handler.name}") # Dispatch to command handlers (PRIVMSG only) @@ -312,6 +315,20 @@ class Bot: await self.conn.send(format_msg("NOTICE", msg.nick, reply)) log.debug("CTCP %s reply to %s", ctcp_cmd, msg.nick) + def _plugin_allowed(self, plugin_name: str, channel: str | None) -> bool: + """Check if a plugin is allowed in a channel.""" + if plugin_name == "core": + return True + if not channel or not channel.startswith(("#", "&")): + return True # PMs are unrestricted + chan_cfg = self.config.get("channels", {}).get(channel) + if not chan_cfg: + return True # unconfigured channels run everything + allowed = chan_cfg.get("plugins") + if allowed is None: + return True + return plugin_name in allowed + def _is_admin(self, msg: Message) -> bool: """Check if the message sender is a bot admin. @@ -345,6 +362,10 @@ class Bot: name=f"cmd:{cmd_name}:ambiguous") return + channel = msg.target if msg.is_channel else None + if not self._plugin_allowed(handler.plugin, channel): + return + if handler.admin and not self._is_admin(msg): deny = f"Permission denied: {self.prefix}{cmd_name} requires admin" self._spawn(self.reply(msg, deny), name=f"cmd:{cmd_name}:denied") diff --git a/src/derp/config.py b/src/derp/config.py index fb81c75..d302fb2 100644 --- a/src/derp/config.py +++ b/src/derp/config.py @@ -29,8 +29,10 @@ DEFAULTS: dict = { "rate_burst": 5, "admins": [], }, + "channels": {}, "logging": { "level": "info", + "format": "text", }, }