refactor(commands): every bundled get_help_text takes message=None

S4. BaseCommand and the 40 bundled overrides that took no argument now take `message: MeshMessage | None = None`, matching joke and rain, which already use the message. None of the 40 bodies read it, so their help text is unchanged. help and command_manager keep their TypeError retry, since local plugins may still implement get_help_text(self). For bundled commands the retry no longer runs: `help <command>` calls get_help_text(message) once instead of failing and calling it again. A new test pins the signature for every bundled command class. The offline smoke run against the real config produces the same help text for all 46 commands.
This commit is contained in:
agessaman committed 2026-10-03 22:28:45 -07:00
1 parent 596a369204
commit 7aba432b54
41 files changed
+75 -40

No files matched your search

+1 -1
View File
@@ -37,7 +37,7 @@ class AdvertCommand(BaseCommand):
super().__init__(bot)
self.advert_enabled = self.get_config_value('Advert_Command', 'enabled', fallback=True, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the advert command.
Returns:
+1 -1
View File
@@ -103,7 +103,7 @@ class AirplanesCommand(BaseCommand):
self.max_results = self.get_config_value('Airplanes_Command', 'max_results', fallback=3, value_type='int')
self.url_timeout = self.get_config_value('Airplanes_Command', 'url_timeout', fallback=10, value_type='int')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for this command.
Returns:
@@ -141,7 +141,7 @@ class GlobalWxCommand(WeatherCommandMixin, BaseCommand):
"""
return load_open_meteo_model(self.bot.config, self.logger)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the command.
Returns:
+1 -1
View File
@@ -135,7 +135,7 @@ class AqiCommand(BaseCommand):
'comet': "Comets have thin atmospheres of water vapor and dust. AQI: Variable, but you'd freeze in space anyway. ☄️"
}
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
region = self.default_state or self.default_country
return f"Usage: aqi <city|neighborhood|city country|lat,lon|help> - Get AQI for city/neighborhood in {region}, intl cities, coordinates, or help"
+1 -1
View File
@@ -376,7 +376,7 @@ class BaseCommand(ABC):
return False
return True
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for this command.
Returns:
+1 -1
View File
@@ -117,7 +117,7 @@ class CatfactCommand(BaseCommand):
"""
return self.translated_or('commands.catfact.facts', self.cat_facts_fallback)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the catfact command.
Returns:
+1 -1
View File
@@ -23,7 +23,7 @@ class ChannelPauseCommand(BaseCommand):
def __init__(self, bot):
super().__init__(bot)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
return (
"Controls whether the bot responds to public channel messages.\n"
"DMs always work (including this command).\n"
+1 -1
View File
@@ -63,7 +63,7 @@ class ChannelsCommand(BaseCommand):
super().__init__(bot)
self.channels_enabled = self.get_config_value('Channels_Command', 'enabled', fallback=True, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
return self.translate('commands.channels.help')
def matches_keyword(self, message: MeshMessage) -> bool:
+1 -1
View File
@@ -41,7 +41,7 @@ class CmdCommand(BaseCommand):
'Cmd_Command', 'cmd_reference_url', fallback='', value_type='str'
).strip()
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the cmd command.
Returns:
+1 -1
View File
@@ -39,7 +39,7 @@ class ContactCommand(BaseCommand):
super().__init__(bot)
self.enabled = self.get_config_value('Contact_Command', 'enabled', fallback=True, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the contact command.
Returns:
+1 -1
View File
@@ -66,7 +66,7 @@ class DadJokeCommand(BaseCommand):
self.dadjoke_enabled = self.get_config_value('DadJoke_Command', 'dadjoke_enabled', fallback=True, value_type='bool')
self.long_jokes = self.get_config_value('DadJoke_Command', 'long_jokes', fallback=False, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the dadjoke command.
Returns:
+1 -1
View File
@@ -57,7 +57,7 @@ class DiceCommand(BaseCommand):
super().__init__(bot)
self.dice_enabled = self.get_config_value('Dice_Command', 'enabled', fallback=True, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the dice command.
Returns:
+1 -1
View File
@@ -83,7 +83,7 @@ class FeedCommand(BaseCommand):
else:
return await self.send_response(message, self.get_help_text())
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for feed command"""
return """Feed Command Usage:
feed subscribe <rss|api> <url> <channel> [name]
+1 -1
View File
@@ -1514,7 +1514,7 @@ class GreeterCommand(BaseCommand):
# Mark as greeted so we don't greet them later
self.mark_as_greeted(key[0], key[1])
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the greeter command.
Returns:
+1 -1
View File
@@ -429,7 +429,7 @@ class HackerCommand(BaseCommand):
if self.enabled is None:
self.enabled = self.get_config_value('Hacker_Command', 'hacker_enabled', fallback=False, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the hacker command.
Returns:
+1 -1
View File
@@ -285,7 +285,7 @@ class HelloCommand(BaseCommand):
"""
return self.translated_or('commands.hello.emoji_responses', self.emoji_responses_fallback, dict)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the hello command.
Returns:
+1 -1
View File
@@ -47,7 +47,7 @@ class HelpCommand(BaseCommand):
super().__init__(bot)
self.help_enabled = self.get_config_value('Help_Command', 'enabled', fallback=True, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the help command.
Returns:
+1 -1
View File
@@ -61,7 +61,7 @@ class HfcondCommand(BaseCommand):
error_msg = self.translate('commands.hfcond.error', error=str(e))
return await self.send_response(message, error_msg)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for this command.
Returns:
+1 -1
View File
@@ -46,7 +46,7 @@ class Magic8Command(BaseCommand):
super().__init__(bot)
self.magic8_enabled = self.get_config_value('Magic8_Command', 'enabled', fallback=True, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the magic8 command.
Returns:
+1 -1
View File
@@ -166,7 +166,7 @@ class MoonCommand(BaseCommand):
# Fallback to original format if formatting fails
return self.translate('commands.moon.fallback', info=moon_info)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for this command.
Returns:
+1 -1
View File
@@ -530,7 +530,7 @@ class MultitestCommand(BaseCommand):
"""True when any condensed layout is enabled (flat or nested)."""
return self.condense_paths_mode != "off"
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
return self.translate('commands.multitest.help', fallback="Listens for 6 seconds and collects all unique paths from incoming messages")
def extract_path_from_rf_data(self, rf_data: dict) -> Optional[str]:
+1 -1
View File
@@ -60,7 +60,7 @@ class NeighborsCommand(BaseCommand):
# where the scheduler's own trigger is also visible.
self._cycle_task: Optional[asyncio.Task] = None
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the neighbors command.
Returns:
+1 -1
View File
@@ -1097,6 +1097,6 @@ class PathCommand(BaseCommand):
"""Get help text for the path command"""
return self.translate('commands.path.help')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the path command (used by help system)"""
return self.get_help()
+1 -1
View File
@@ -39,7 +39,7 @@ class PingCommand(BaseCommand):
super().__init__(bot)
self.ping_enabled = self.get_config_value('Ping_Command', 'enabled', fallback=True, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the ping command.
Returns:
+1 -1
View File
@@ -157,7 +157,7 @@ class PrefixCommand(BaseCommand):
self.prefix_best_location_radius_km = 50.0
self.prefix_best_do_not_suggest = []
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the prefix command.
Returns:
+1 -1
View File
@@ -29,7 +29,7 @@ class ReloadCommand(BaseCommand):
"""
super().__init__(bot)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the reload command.
Returns:
+1 -1
View File
@@ -45,7 +45,7 @@ class RollCommand(BaseCommand):
super().__init__(bot)
self.roll_enabled = self.get_config_value('Roll_Command', 'enabled', fallback=True, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the roll command.
Returns:
+1 -1
View File
@@ -103,7 +103,7 @@ class SatpassCommand(BaseCommand):
await self.send_response(message, error_msg)
return False
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for this command.
Returns:
+1 -1
View File
@@ -56,7 +56,7 @@ class ScheduleCommand(BaseCommand):
return False
return super().can_execute(message)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
return "schedule [list] — show scheduled messages and advert interval"
async def execute(self, message: MeshMessage) -> bool:
+1 -1
View File
@@ -67,7 +67,7 @@ class SolarCommand(BaseCommand):
await self.send_response(message, error_msg)
return False
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for this command.
Returns:
+1 -1
View File
@@ -106,7 +106,7 @@ class SolarforecastCommand(BaseCommand):
# Get database manager for geocoding cache
self.db_manager = bot.db_manager
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
return self.translate('commands.solarforecast.usage')
def _translate_day_abbreviation(self, day_abbr: str) -> str:
+1 -1
View File
@@ -141,7 +141,7 @@ class SportsCommand(BaseCommand):
return self._cleaned_content_matches(message, _matches)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
return self.translate('commands.sports.help')
+1 -1
View File
@@ -342,7 +342,7 @@ class StatsCommand(BaseCommand):
# If it's already a single node ID or short path, return as-is
return path
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the stats command.
Returns:
+1 -1
View File
@@ -62,7 +62,7 @@ class SunCommand(BaseCommand):
error_msg = self.translate('commands.sun.error', error=str(e))
return await self.send_response(message, error_msg)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for this command.
Returns:
+1 -1
View File
@@ -86,7 +86,7 @@ class TestCommand(BaseCommand):
except Exception as e:
self.logger.warning(f"Error reading bot location from config: {e}")
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the command.
Returns:
+1 -1
View File
@@ -82,7 +82,7 @@ class TraceCommand(BaseCommand):
output_fmt = (self.bot.config.get("Trace_Command", "output_format", fallback="inline") or "inline").strip().lower()
self.output_format = output_fmt if output_fmt in ("inline", "vertical") else "inline"
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
return (
"trace [path] — run trace along path (return may not be heard). No path = round-trip like tracer. "
"tracer [path] — round-trip so bot hears return. Path: comma-separated hex nodes "
+1 -1
View File
@@ -37,7 +37,7 @@ class VersionCommand(BaseCommand):
value_type="bool",
)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
return self.description
async def execute(self, message: MeshMessage) -> bool:
+1 -1
View File
@@ -30,7 +30,7 @@ class WebViewerCommand(BaseCommand):
super().__init__(bot)
self.webviewer_enabled = self.get_config_value('WebViewer_Command', 'enabled', fallback=True, value_type='bool')
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text for the webviewer command.
Returns:
+1 -1
View File
@@ -81,7 +81,7 @@ class WorldCupCommand(BaseCommand):
return self._cleaned_content_matches(message, _matches)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
return self.translate("commands.worldcup.help")
# ------------------------------------------------------------------ helpers
+1 -1
View File
@@ -366,7 +366,7 @@ class WxCommand(WeatherCommandMixin, BaseCommand):
"""Move blocking provider work off-loop without concurrent Session use."""
return await asyncio.to_thread(self._run_sync_provider, operation, *args, **kwargs)
def get_help_text(self) -> str:
def get_help_text(self, message: MeshMessage | None = None) -> str:
"""Get help text, delegating to international command if using Open-Meteo"""
if self.delegate_command:
return self.delegate_command.get_help_text()
+35
View File
@@ -0,0 +1,35 @@
"""Every bundled command takes ``get_help_text(message=None)``.
help and command_manager still retry without the message when a command's
get_help_text takes none (local plugins may be written that way), but bundled
commands use the one signature.
"""
import importlib
import inspect
import pkgutil
import modules.commands
import modules.commands.alternatives
from modules.commands.base_command import BaseCommand
def _bundled_command_classes():
for package in (modules.commands, modules.commands.alternatives):
for info in pkgutil.iter_modules(package.__path__):
module = importlib.import_module(f"{package.__name__}.{info.name}")
for _, cls in inspect.getmembers(module, inspect.isclass):
if issubclass(cls, BaseCommand) and cls.__module__ == module.__name__:
yield cls
def test_every_bundled_get_help_text_takes_an_optional_message():
classes = list(_bundled_command_classes())
assert len(classes) > 40
wrong = []
for cls in classes:
params = inspect.signature(cls.get_help_text).parameters
message = params.get("message")
if message is None or message.default is not None:
wrong.append(cls.__name__)
assert wrong == []