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.
This commit is contained in:
@@ -281,8 +281,11 @@ class Bot:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Dispatch to event handlers (fire-and-forget)
|
# Dispatch to event handlers (fire-and-forget)
|
||||||
|
channel = msg.target if msg.is_channel else None
|
||||||
event_type = msg.command
|
event_type = msg.command
|
||||||
for handler in self.registry.events.get(event_type, []):
|
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}")
|
self._spawn(handler.callback(self, msg), name=f"event:{handler.name}")
|
||||||
|
|
||||||
# Dispatch to command handlers (PRIVMSG only)
|
# Dispatch to command handlers (PRIVMSG only)
|
||||||
@@ -312,6 +315,20 @@ class Bot:
|
|||||||
await self.conn.send(format_msg("NOTICE", msg.nick, reply))
|
await self.conn.send(format_msg("NOTICE", msg.nick, reply))
|
||||||
log.debug("CTCP %s reply to %s", ctcp_cmd, msg.nick)
|
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:
|
def _is_admin(self, msg: Message) -> bool:
|
||||||
"""Check if the message sender is a bot admin.
|
"""Check if the message sender is a bot admin.
|
||||||
|
|
||||||
@@ -345,6 +362,10 @@ class Bot:
|
|||||||
name=f"cmd:{cmd_name}:ambiguous")
|
name=f"cmd:{cmd_name}:ambiguous")
|
||||||
return
|
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):
|
if handler.admin and not self._is_admin(msg):
|
||||||
deny = f"Permission denied: {self.prefix}{cmd_name} requires admin"
|
deny = f"Permission denied: {self.prefix}{cmd_name} requires admin"
|
||||||
self._spawn(self.reply(msg, deny), name=f"cmd:{cmd_name}:denied")
|
self._spawn(self.reply(msg, deny), name=f"cmd:{cmd_name}:denied")
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ DEFAULTS: dict = {
|
|||||||
"rate_burst": 5,
|
"rate_burst": 5,
|
||||||
"admins": [],
|
"admins": [],
|
||||||
},
|
},
|
||||||
|
"channels": {},
|
||||||
"logging": {
|
"logging": {
|
||||||
"level": "info",
|
"level": "info",
|
||||||
|
"format": "text",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user