mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-08-29 07:08:45 +00:00
Update admin commands to include channelpause functionality
- Added `channelpause` and `channelresume` commands to the admin commands list in configuration files, allowing admins to pause or resume bot responses on public channels via DM. - Updated documentation to reflect the new command functionality and its implications for channel responses. - Modified validation and message handling to incorporate the new channel response control feature.
This commit is contained in:
+2
-1
@@ -211,7 +211,8 @@ admin_pubkeys =
|
||||
# Commands that require admin access (comma-separated)
|
||||
# These commands will only work for users in the admin_pubkeys list
|
||||
# reload: Reload configuration without restarting (radio settings cannot be changed)
|
||||
admin_commands = repeater,webviewer,reload
|
||||
# channelpause: DM-only; channelpause / channelresume — pause or resume bot responses on channels (not persisted)
|
||||
admin_commands = repeater,webviewer,reload,channelpause
|
||||
|
||||
[Plugin_Overrides]
|
||||
# Plugin Overrides - Use alternative plugin implementations
|
||||
|
||||
@@ -180,7 +180,7 @@ admin_pubkeys =
|
||||
# Commands that require admin access (comma-separated)
|
||||
# These commands will only work for users in the admin_pubkeys list
|
||||
# reload: Reload configuration without restarting (radio settings cannot be changed)
|
||||
admin_commands = repeater,webviewer,reload
|
||||
admin_commands = repeater,webviewer,reload,channelpause
|
||||
|
||||
[Keywords]
|
||||
# Available placeholders (message-based):
|
||||
|
||||
@@ -25,7 +25,7 @@ banned_users =
|
||||
[Admin_ACL]
|
||||
# 64-char hex public keys (comma-separated). Leave blank to disable admin commands.
|
||||
admin_pubkeys =
|
||||
admin_commands = repeater,webviewer,reload
|
||||
admin_commands = repeater,webviewer,reload,channelpause
|
||||
|
||||
[Keywords]
|
||||
test = "ack @[{sender}]{phrase_part} | {connection_info} | Received at: {timestamp}"
|
||||
|
||||
@@ -46,7 +46,7 @@ The bot will not start without these sections. The validator reports them as **e
|
||||
|
||||
If these are absent, the validator reports **info** (no error):
|
||||
|
||||
- **`[Admin_ACL]`** – Absent means admin commands (repeater, webviewer, reload) are disabled.
|
||||
- **`[Admin_ACL]`** – Absent means admin commands (repeater, webviewer, reload, channelpause) are disabled.
|
||||
- **`[Banned_Users]`** – Absent means no users are banned.
|
||||
- **`[Localization]`** – Absent means defaults (e.g. `language=en`, `translation_path=translations/`) are used.
|
||||
|
||||
|
||||
@@ -93,3 +93,7 @@ Before starting the bot, you can validate section names and path writability. Se
|
||||
## Reloading configuration
|
||||
|
||||
Some configuration can be reloaded without restarting the bot using the **`reload`** command (admin only). Radio/connection settings are not changed by reload; restart the bot for those.
|
||||
|
||||
## Pausing channel responses (remote)
|
||||
|
||||
Admins can DM **`channelpause`** or **`channelresume`** (see `[Admin_ACL]` in `config.ini`) to stop or resume bot reactions on **public channels** only—greeter, keywords, and commands on channels are skipped; DMs still work. The setting is **in memory only** (back to responding on channels after restart). Scheduled channel posts from the scheduler are **not** blocked by this toggle.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Channel pause command
|
||||
DM-only admin: pause or resume bot responses on public channels (in-memory only).
|
||||
"""
|
||||
|
||||
from .base_command import BaseCommand
|
||||
from ..models import MeshMessage
|
||||
|
||||
|
||||
class ChannelPauseCommand(BaseCommand):
|
||||
"""Pause or resume channel-triggered bot responses (greeter, keywords, commands)."""
|
||||
|
||||
name = "channelpause"
|
||||
keywords = ["channelpause", "channelresume"]
|
||||
description = "Pause or resume bot responses on channels (DM only, admin only)"
|
||||
requires_dm = True
|
||||
cooldown_seconds = 2
|
||||
category = "admin"
|
||||
|
||||
def __init__(self, bot):
|
||||
super().__init__(bot)
|
||||
|
||||
def can_execute(self, message: MeshMessage) -> bool:
|
||||
if not self.requires_admin_access():
|
||||
return False
|
||||
return super().can_execute(message)
|
||||
|
||||
def requires_admin_access(self) -> bool:
|
||||
return True
|
||||
|
||||
def get_help_text(self) -> str:
|
||||
return (
|
||||
"Controls whether the bot responds to public channel messages.\n"
|
||||
"DMs always work (including this command).\n"
|
||||
"Not saved across restarts.\n"
|
||||
"Usage: channelpause — stop channel responses\n"
|
||||
" channelresume — resume channel responses"
|
||||
)
|
||||
|
||||
def _stripped_content_lower(self, message: MeshMessage) -> str:
|
||||
content = message.content.strip()
|
||||
if self._command_prefix:
|
||||
if not content.startswith(self._command_prefix):
|
||||
return ""
|
||||
content = content[len(self._command_prefix) :].strip()
|
||||
elif content.startswith("!"):
|
||||
content = content[1:].strip()
|
||||
content = self._strip_mentions(content)
|
||||
return content.lower()
|
||||
|
||||
async def execute(self, message: MeshMessage) -> bool:
|
||||
text = self._stripped_content_lower(message)
|
||||
resume_kw = self.keywords[1].lower()
|
||||
pause_kw = self.keywords[0].lower()
|
||||
|
||||
if text == resume_kw or text.startswith(resume_kw + " "):
|
||||
self.bot.channel_responses_enabled = True
|
||||
reply = "Channel responses: ON. The bot will respond on public channels again."
|
||||
elif text == pause_kw or text.startswith(pause_kw + " "):
|
||||
self.bot.channel_responses_enabled = False
|
||||
reply = (
|
||||
"Channel responses: OFF. No greeter, keywords, or commands on channels; "
|
||||
"DMs still work. Not persisted after restart."
|
||||
)
|
||||
else:
|
||||
reply = "Use channelpause or channelresume."
|
||||
|
||||
await self.send_response(message, reply)
|
||||
return True
|
||||
@@ -1201,6 +1201,15 @@ class GreeterCommand(BaseCommand):
|
||||
self.logger.debug(f"Waiting {self.dead_air_delay_seconds} seconds before greeting {message.sender_id} on {message.channel}")
|
||||
await asyncio.sleep(self.dead_air_delay_seconds)
|
||||
|
||||
if not getattr(self.bot, "channel_responses_enabled", True):
|
||||
self.logger.info(
|
||||
f"Skipping delayed greeting for {message.sender_id} on {message.channel} "
|
||||
"(channel responses paused)"
|
||||
)
|
||||
if key in self.pending_greetings:
|
||||
del self.pending_greetings[key]
|
||||
return
|
||||
|
||||
# Check if greeting was cancelled (user was already greeted or human responded)
|
||||
if key not in self.pending_greetings:
|
||||
self.logger.debug(f"Greeting for {message.sender_id} on {message.channel} was cancelled")
|
||||
|
||||
@@ -177,7 +177,7 @@ def validate_config(config_path: str) -> list[tuple[str, str]]:
|
||||
if ADMIN_ACL_SECTION not in sections_present:
|
||||
results.append((
|
||||
SEVERITY_INFO,
|
||||
f"Section [{ADMIN_ACL_SECTION}] absent; admin commands (repeater, webviewer, reload) disabled.",
|
||||
f"Section [{ADMIN_ACL_SECTION}] absent; admin commands (repeater, webviewer, reload, channelpause) disabled.",
|
||||
))
|
||||
if BANNED_USERS_SECTION not in sections_present:
|
||||
results.append((
|
||||
|
||||
@@ -81,6 +81,9 @@ class MeshCoreBot:
|
||||
self.connected = False
|
||||
self.connection_time = None # Track when connection was established to skip old cached messages
|
||||
|
||||
# Volatile: DM-only admin command (channelpause) toggles this; not persisted across restarts.
|
||||
self.channel_responses_enabled = True
|
||||
|
||||
# Bot start time for uptime tracking
|
||||
self.start_time = time.time()
|
||||
|
||||
|
||||
@@ -2628,7 +2628,7 @@ class MessageHandler:
|
||||
|
||||
# Check greeter command for public channel messages (BEFORE general message filtering)
|
||||
# This allows greeter to work on its own configured channels even if not in monitor_channels
|
||||
if 'greeter' in self.bot.command_manager.commands:
|
||||
if self._channel_responses_allowed(message) and 'greeter' in self.bot.command_manager.commands:
|
||||
greeter_command = self.bot.command_manager.commands['greeter']
|
||||
# First, check if this message should cancel a pending greeting (human greeting detection)
|
||||
if greeter_command:
|
||||
@@ -2779,6 +2779,11 @@ class MessageHandler:
|
||||
self.logger.debug(f"Ignoring message from banned user: {message.sender_id}")
|
||||
return False
|
||||
|
||||
# Channel-only pause (DM-only admin command); DMs still processed
|
||||
if not message.is_dm and not getattr(self.bot, "channel_responses_enabled", True):
|
||||
self.logger.debug("Ignoring non-DM message: channel responses paused")
|
||||
return False
|
||||
|
||||
# Check if channel is monitored (with command override support)
|
||||
if not message.is_dm and message.channel:
|
||||
# Check if channel is in global monitor_channels
|
||||
@@ -2804,6 +2809,12 @@ class MessageHandler:
|
||||
|
||||
return True
|
||||
|
||||
def _channel_responses_allowed(self, message: MeshMessage) -> bool:
|
||||
"""True if channel-driven bot responses are allowed for this message (DMs always True here)."""
|
||||
if message.is_dm:
|
||||
return True
|
||||
return getattr(self.bot, "channel_responses_enabled", True)
|
||||
|
||||
async def handle_new_contact(self, event, metadata=None):
|
||||
"""Handle NEW_CONTACT events for automatic contact management"""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user