diff --git a/modules/command_manager.py b/modules/command_manager.py index 81b030e..ff74d22 100644 --- a/modules/command_manager.py +++ b/modules/command_manager.py @@ -738,8 +738,16 @@ class CommandManager: if not self.bot.config.getboolean('Channels', 'respond_to_dms', fallback=True): break # DMs disabled, skip help keyword else: - # For channel messages, check if channel is in monitor_channels - if message.channel not in self.monitor_channels: + # For channel messages, honor the help command's channel access: + # its per-command `channels` override when set, otherwise the + # global monitor_channels. Without this the special path would + # ignore [Help_Command] channels = ... (it bypasses the plugin + # loop where is_channel_allowed is normally enforced). Fall back + # to a bare monitor_channels check when no help command is loaded. + if help_command is not None and hasattr(help_command, 'is_channel_allowed'): + if not help_command.is_channel_allowed(message): + break # Not allowed in this channel, skip help keyword + elif message.channel not in self.monitor_channels: break # Channel not monitored, skip help keyword # When channel_keywords is set, only allow listed triggers in channel if not self._is_channel_trigger_allowed('help', message): diff --git a/tests/test_command_manager.py b/tests/test_command_manager.py index 7a212b1..440fb18 100644 --- a/tests/test_command_manager.py +++ b/tests/test_command_manager.py @@ -264,6 +264,30 @@ class TestCheckKeywords: matches = manager.check_keywords(msg) assert any(trigger == "help" for trigger, _ in matches) + def test_help_channel_override_blocks_disallowed_channel(self, cm_bot): + """[Help_Command] channels override must gate the special help path too. + + The path bypasses the plugin loop (where is_channel_allowed is enforced), so + it has to consult the help command's channel access directly. + """ + cm_bot.config.set("Keywords", "help", "Help: ping, test") + mock_help = MagicMock() + mock_help.help_enabled = True + mock_help.keywords = ["help"] + # Disallow the channel the message arrives on. + mock_help.is_channel_allowed = Mock(return_value=False) + mock_help.should_execute = Mock(return_value=False) + manager = make_manager(cm_bot, commands={"help": mock_help}) + + msg = mock_message(content="help", channel="general", is_dm=False) + matches = manager.check_keywords(msg) + assert not any(trigger == "help" for trigger, _ in matches) + + # Allowed channel still responds. + mock_help.is_channel_allowed = Mock(return_value=True) + matches = manager.check_keywords(mock_message(content="help", channel="general", is_dm=False)) + assert any(trigger == "help" for trigger, _ in matches) + class TestGetHelpForCommand: """Tests for command-specific help."""