Files
meshcore-bot/modules/commands/schedule_command.py
Stacy Olivas ad77d7b00d fix: BUG-025/026/027/028/029 implementations and ruff/mypy refinements
BUG-025: send_channel_message retry logic on no_event_received
BUG-026: split_text_into_chunks and chunked dispatch in message_handler
BUG-027: test_weekly_on_wrong_day_does_not_run patch uses fake_now
BUG-028: byte_data = b"" initialised before try in decode_meshcore_packet
BUG-029: app.py db_path via self._config_base; realtime.html socket race
  fixed; base.html forceNew removed; ping_timeout 5 to 20s

Additional: ruff and mypy refinements across all modules; discord bridge,
telegram bridge, rate limiter, and service plugin updates
2026-03-17 18:07:19 -07:00

111 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""
Schedule command for the MeshCore Bot
Lists upcoming scheduled messages and interval advertising settings.
"""
from typing import Any, Optional
from ..models import MeshMessage
from .base_command import BaseCommand
class ScheduleCommand(BaseCommand):
"""Show scheduled messages and advertising interval configured for the bot.
Responds to ``schedule`` or ``schedule list``. The output is kept compact
so it fits within typical MeshCore message limits (~200 chars).
Config section ``[Schedule_Command]``:
enabled = true # enable/disable this command
dm_only = true # restrict to DMs (default true — exposes config)
"""
name = "schedule"
keywords = ["schedule"]
description = "List upcoming scheduled messages and advertising interval."
category = "admin"
short_description = "Show scheduled messages and advertising interval"
usage = "schedule [list]"
examples = ["schedule", "schedule list"]
def __init__(self, bot: Any) -> None:
super().__init__(bot)
self._enabled = self.get_config_value(
"Schedule_Command", "enabled", fallback=True, value_type="bool"
)
self._dm_only = self.get_config_value(
"Schedule_Command", "dm_only", fallback=True, value_type="bool"
)
# ------------------------------------------------------------------
# BaseCommand interface
# ------------------------------------------------------------------
def can_execute(self, message: MeshMessage, skip_channel_check: bool = False) -> bool:
if not self._enabled:
return False
if self._dm_only and not message.is_dm:
return False
return super().can_execute(message)
def get_help_text(self) -> str:
return "schedule [list] — show scheduled messages and advert interval"
async def execute(self, message: MeshMessage) -> bool:
response = self._build_response()
return await self.send_response(message, response)
# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------
def _build_response(self) -> str:
lines: list[str] = []
# --- Scheduled messages ---
scheduled = self._get_scheduled_messages()
if scheduled:
lines.append(f"Scheduled ({len(scheduled)}):")
for time_str, channel, preview in scheduled:
# Format HHMM → HH:MM
hhmm = f"{time_str[:2]}:{time_str[2:]}"
lines.append(f" {hhmm} #{channel}: {preview}")
else:
lines.append("No scheduled messages configured.")
# --- Interval advertising ---
advert_info = self._get_advert_info()
if advert_info:
lines.append(advert_info)
return "\n".join(lines)
def _get_scheduled_messages(self) -> list[tuple]:
"""Return sorted list of (time_str, channel, preview) tuples."""
scheduler = getattr(self.bot, "scheduler", None)
if scheduler is None:
return []
scheduled = getattr(scheduler, "scheduled_messages", {})
results = []
for time_str, payload in sorted(scheduled.items()):
channel, message = payload
# Truncate long messages so response stays compact
preview = message if len(message) <= 40 else message[:37] + "..."
results.append((time_str, channel, preview))
return results
def _get_advert_info(self) -> Optional[str]:
"""Return a one-line advert interval summary, or None if disabled."""
try:
interval_hours = self.bot.config.getint(
"Bot", "advert_interval_hours", fallback=0
)
if interval_hours > 0:
return f"Advert interval: every {interval_hours}h"
except Exception:
pass
return None