Files
meshcore-bot/modules/command_prefix.py
T
agessaman 95b0601980 feat(command_prefix, config): enhance command prefix handling and documentation
- Updated `config.ini.example` to support single, multiple, and decorative command prefixes, improving flexibility in command invocation.
- Refactored `CommandManager` and `BaseCommand` to utilize new command prefix parsing logic, allowing for better handling of multiple prefixes and optional bare commands.
- Enhanced documentation to clarify command prefix configuration options and behavior, ensuring users understand how to set and use prefixes effectively.
- Added unit tests to verify the correct functionality of command prefix handling, including support for permissive and strict prefix modes.
2026-07-07 09:37:28 -07:00

79 lines
2.6 KiB
Python

"""Command prefix parsing and message normalization."""
from __future__ import annotations
from configparser import ConfigParser
# Decorative single-character prefixes; used when parsing concatenated lists like "!~."
DECORATIVE_PREFIX_CHARS = frozenset('!.,/~')
def parse_command_prefixes(raw: str) -> list[str]:
"""Parse ``command_prefix`` config into an ordered prefix list.
Rules:
- Empty / unset → no prefixes (legacy bare + ``!`` mode).
- Comma-separated → explicit list (e.g. ``!, ~, .`` or ``abc, xyz``).
- All decorative chars and length > 1 → split per char (e.g. ``!~.``).
- Otherwise → single prefix string (e.g. ``!``, ``abc``).
"""
text = (raw or '').strip()
if not text:
return []
if ',' in text:
return [part.strip() for part in text.split(',') if part.strip()]
if len(text) > 1 and all(char in DECORATIVE_PREFIX_CHARS for char in text):
return list(text)
return [text]
def load_command_prefix_settings(config: ConfigParser) -> tuple[list[str], bool]:
"""Load ``(prefixes, require_prefix)`` from ``[Bot]`` config."""
raw = ''
if config.has_section('Bot'):
raw = (config.get('Bot', 'command_prefix', fallback='') or '').strip()
prefixes = parse_command_prefixes(raw)
if config.has_section('Bot') and config.has_option('Bot', 'require_command_prefix'):
require_prefix = config.getboolean('Bot', 'require_command_prefix')
else:
require_prefix = True
return prefixes, require_prefix
def find_matching_prefix(content: str, prefixes: list[str]) -> str | None:
"""Return the longest configured prefix that ``content`` starts with."""
if not prefixes or not content:
return None
matched: str | None = None
for prefix in sorted(prefixes, key=len, reverse=True):
if content.startswith(prefix):
if matched is None or len(prefix) > len(matched):
matched = prefix
return matched
def normalize_command_content(
content: str,
prefixes: list[str],
*,
require_prefix: bool,
) -> str | None:
"""Strip command prefix from content, or reject / pass through per ``require_prefix``.
Returns:
Stripped content, or ``None`` when the message should be ignored.
"""
text = content.strip()
if not prefixes:
if text.startswith('!'):
return text[1:].strip()
return text
matched = find_matching_prefix(text, prefixes)
if matched is not None:
return text[len(matched):].strip()
if require_prefix:
return None
return text