diff --git a/config.ini.example b/config.ini.example index 28a10b6..6779fa6 100644 --- a/config.ini.example +++ b/config.ini.example @@ -1288,6 +1288,8 @@ enabled = true [Cmd_Command] enabled = true +# Optional: override `cmd` output with a link to your full docs page +# cmd_reference_url = https://example.com/command-reference # channels = [Channels_Command] diff --git a/docs/configuration.md b/docs/configuration.md index 9999b14..c0dd954 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -101,6 +101,7 @@ Examples of sections that configure specific commands or features: - **`[Path_Command]`** – Path decoding and repeater selection. See [Path Command](path-command-config.md) for all options. - **`[Prefix_Command]`** – Prefix lookup, prefix best, range limits. +- **`[Cmd_Command]`** – `cmd` behavior. Set `cmd_reference_url` to return `Full command reference: ` instead of the generated compact command list. - **`[Weather]`** – Used by the `wx` / `gwx` commands and the Weather Service plugin (see [Weather Service](weather-service.md)). - **`[Airplanes_Command]`** – Aircraft/ADS-B command (API URL, radius, limits). - **`[Aurora_Command]`** – Aurora command (default coordinates). diff --git a/modules/commands/cmd_command.py b/modules/commands/cmd_command.py index ca15d74..b45afe5 100644 --- a/modules/commands/cmd_command.py +++ b/modules/commands/cmd_command.py @@ -32,6 +32,9 @@ class CmdCommand(BaseCommand): """ super().__init__(bot) self.cmd_enabled = self.get_config_value('Cmd_Command', 'enabled', fallback=True, value_type='bool') + self.cmd_reference_url = self.get_config_value( + 'Cmd_Command', 'cmd_reference_url', fallback='', value_type='str' + ).strip() def can_execute(self, message: MeshMessage, skip_channel_check: bool = False) -> bool: """Check if this command can be executed with the given message. @@ -171,6 +174,9 @@ class CmdCommand(BaseCommand): bool: True if executed successfully, False otherwise. """ try: + if self.cmd_reference_url: + return await self.send_response(message, f"Full command reference: {self.cmd_reference_url}") + # Check if user has defined a custom cmd keyword response in config # Use the already-loaded keywords dict (quotes are already stripped) cmd_keyword = self.bot.command_manager.keywords.get('cmd') diff --git a/tests/commands/test_cmd_command.py b/tests/commands/test_cmd_command.py index e507829..2a4db95 100644 --- a/tests/commands/test_cmd_command.py +++ b/tests/commands/test_cmd_command.py @@ -41,6 +41,40 @@ class TestCmdCommand: response = call_args[0][1] assert "ping" in response or "help" in response or "cmd" in response + @pytest.mark.asyncio + async def test_execute_returns_reference_url_when_configured(self, command_mock_bot): + command_mock_bot.config.add_section("Cmd_Command") + command_mock_bot.config.set("Cmd_Command", "enabled", "true") + command_mock_bot.config.set("Cmd_Command", "cmd_reference_url", "https://example.com/commands") + command_mock_bot.command_manager.keywords = {} + command_mock_bot.command_manager.commands = {"ping": type("MockCmd", (), {"keywords": ["ping"]})()} + cmd = CmdCommand(command_mock_bot) + msg = mock_message(content="cmd", is_dm=True) + + result = await cmd.execute(msg) + + assert result is True + call_args = command_mock_bot.command_manager.send_response.call_args + assert call_args is not None + assert call_args[0][1] == "Full command reference: https://example.com/commands" + + @pytest.mark.asyncio + async def test_execute_reference_url_takes_precedence_over_custom_keyword(self, command_mock_bot): + command_mock_bot.config.add_section("Cmd_Command") + command_mock_bot.config.set("Cmd_Command", "enabled", "true") + command_mock_bot.config.set("Cmd_Command", "cmd_reference_url", "https://example.com/commands") + command_mock_bot.command_manager.keywords = {"cmd": "Custom cmd output"} + command_mock_bot.command_manager.commands = {"ping": type("MockCmd", (), {"keywords": ["ping"]})()} + cmd = CmdCommand(command_mock_bot) + msg = mock_message(content="cmd", is_dm=True) + + result = await cmd.execute(msg) + + assert result is True + call_args = command_mock_bot.command_manager.send_response.call_args + assert call_args is not None + assert call_args[0][1] == "Full command reference: https://example.com/commands" + def test_get_commands_list_truncation(self, command_mock_bot): """Test that _get_commands_list truncates long lists with '(N more)' suffix.""" import re