Improve monitor channel handling by adding support for quoted values

- Introduced `strip_optional_quotes` function to handle monitor channel values with optional surrounding quotes.
- Updated `load_monitor_channels` method in `CommandManager` to utilize the new function, ensuring compatibility with quoted and unquoted channel configurations.
- Modified `generate_samples` function to apply the same logic for consistency.
- Added tests for `strip_optional_quotes` to validate behavior with various input cases.
This commit is contained in:
agessaman
2026-02-15 21:02:48 -08:00
parent 669f02c96e
commit 04bd004f36
5 changed files with 58 additions and 6 deletions
+5 -4
View File
@@ -23,6 +23,7 @@ try:
from modules.plugin_loader import PluginLoader
from modules.db_manager import DBManager
from modules.utils import resolve_path
from modules.config_validation import strip_optional_quotes
except ImportError as e:
print("Error: Missing required dependencies.")
print(f"Details: {e}")
@@ -2449,8 +2450,8 @@ def generate_samples(config_file):
title = get_website_title(config)
introduction = get_website_intro(config)
# Get monitor channels
monitor_channels_str = config.get('Channels', 'monitor_channels', fallback='')
# Get monitor channels (quoted or unquoted)
monitor_channels_str = strip_optional_quotes(config.get('Channels', 'monitor_channels', fallback=''))
monitor_channels = [ch.strip() for ch in monitor_channels_str.split(',') if ch.strip()]
# Load channels from config
@@ -2678,8 +2679,8 @@ def main():
introduction = get_website_intro(config)
title = get_website_title(config)
# Get monitor channels for channel restriction display
monitor_channels_str = config.get('Channels', 'monitor_channels', fallback='')
# Get monitor channels for channel restriction display (quoted or unquoted)
monitor_channels_str = strip_optional_quotes(config.get('Channels', 'monitor_channels', fallback=''))
monitor_channels = [ch.strip() for ch in monitor_channels_str.split(',') if ch.strip()]
# Load channels from Channels_List section
+6 -2
View File
@@ -17,6 +17,7 @@ from .models import MeshMessage
from .plugin_loader import PluginLoader
from .commands.base_command import BaseCommand
from .utils import check_internet_connectivity_async, decode_escape_sequences, format_keyword_response_with_placeholders
from .config_validation import strip_optional_quotes
@dataclass
@@ -423,8 +424,11 @@ class CommandManager:
return any(sender_id.startswith(entry) for entry in self.banned_users)
def load_monitor_channels(self) -> List[str]:
"""Load monitored channels from config"""
channels = self.bot.config.get('Channels', 'monitor_channels', fallback='')
"""Load monitored channels from config.
Values may be quoted, e.g. \"#bot,#bot-everett,#bots\" or unquoted.
"""
raw = self.bot.config.get('Channels', 'monitor_channels', fallback='')
channels = strip_optional_quotes(raw)
return [channel.strip() for channel in channels.split(',') if channel.strip()]
def load_channel_keywords(self) -> Optional[List[str]]:
+15
View File
@@ -56,6 +56,21 @@ ADMIN_ACL_SECTION = "Admin_ACL" # admin commands disabled
BANNED_USERS_SECTION = "Banned_Users" # empty banned list
LOCALIZATION_SECTION = "Localization" # language=en, translation_path=translations/
def strip_optional_quotes(s: str) -> str:
"""Strip one layer of surrounding double or single quotes if present.
Allows config values like monitor_channels to be written as
"#bot,#bot-everett,#bots" so the list does not look like comments.
Backward compatible: unquoted values are returned unchanged.
"""
if not isinstance(s, str):
return s
s = s.strip()
if len(s) >= 2 and s[0] == s[-1] and s[0] in '"\'':
return s[1:-1]
return s
# Non-standard section name -> suggested canonical name (exact match)
SECTION_TYPO_MAP = {
"WebViewer": "Web_Viewer",
+6
View File
@@ -163,6 +163,12 @@ class TestLoadMonitorChannels:
manager = make_manager(cm_bot)
assert manager.monitor_channels == []
def test_load_monitor_channels_quoted(self, cm_bot):
"""Quoted monitor_channels (e.g. \"#bot,#bot-everett,#bots\") is supported."""
cm_bot.config.set("Channels", "monitor_channels", '"#bot,#bot-everett,#bots"')
manager = make_manager(cm_bot)
assert manager.monitor_channels == ["#bot", "#bot-everett", "#bots"]
class TestLoadChannelKeywords:
"""Tests for channel keyword whitelist loading."""
+26
View File
@@ -7,6 +7,7 @@ from modules.config_validation import (
SEVERITY_ERROR,
SEVERITY_INFO,
SEVERITY_WARNING,
strip_optional_quotes,
validate_config,
_resolve_path,
_check_path_writable,
@@ -15,6 +16,31 @@ from modules.config_validation import (
)
class TestStripOptionalQuotes:
"""Tests for strip_optional_quotes (monitor_channels and similar config values)."""
def test_unquoted_unchanged(self):
assert strip_optional_quotes("#bot,#bot-everett,#bots") == "#bot,#bot-everett,#bots"
assert strip_optional_quotes("general,test") == "general,test"
def test_double_quoted_stripped(self):
assert strip_optional_quotes('"#bot,#bot-everett,#bots"') == "#bot,#bot-everett,#bots"
def test_single_quoted_stripped(self):
assert strip_optional_quotes("'#bot,#bot-everett,#bots'") == "#bot,#bot-everett,#bots"
def test_empty_and_whitespace(self):
assert strip_optional_quotes("") == ""
assert strip_optional_quotes(" ") == ""
def test_mismatched_quotes_not_stripped(self):
assert strip_optional_quotes('"#bot,#bots\'') == '"#bot,#bots\''
assert strip_optional_quotes('\'#bot,#bots"') == '\'#bot,#bots"'
def test_single_char_quoted_stripped(self):
assert strip_optional_quotes('"a"') == "a"
class TestValidateConfig:
"""Tests for validate_config()."""