mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-08-28 05:14:12 +00:00
refactor: Consolidate escape sequence decoding across modules
- Removed the custom `_decode_escape_sequences` method from multiple classes and replaced it with a centralized `decode_escape_sequences` function in the utils module. - Updated all relevant modules (CommandManager, MessageScheduler, GreeterCommand) to utilize the new function for decoding escape sequences in configuration strings, improving code maintainability and consistency. - Enhanced the config example to clarify the usage of escape sequences in messages.
This commit is contained in:
@@ -311,6 +311,9 @@ help = "Bot Help: test (or t), ping, help, hello, cmd, advert, @string, wx, aqi,
|
||||
# Time format: HHMM (24-hour, no colon)
|
||||
# Bot will send these messages at the specified times daily
|
||||
# Example: 0800 = general:Good morning! Weather update coming soon.
|
||||
#
|
||||
# Newlines: use \n in the message for a line break (e.g. general:Line one\nLine two).
|
||||
# Literal backslash: use \\n for backslash+n; \\t for tab.
|
||||
#
|
||||
# Available placeholders for mesh network information:
|
||||
#
|
||||
|
||||
@@ -16,7 +16,7 @@ from meshcore import EventType
|
||||
from .models import MeshMessage
|
||||
from .plugin_loader import PluginLoader
|
||||
from .commands.base_command import BaseCommand
|
||||
from .utils import check_internet_connectivity_async, format_keyword_response_with_placeholders
|
||||
from .utils import check_internet_connectivity_async, decode_escape_sequences, format_keyword_response_with_placeholders
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -342,32 +342,6 @@ class CommandManager:
|
||||
self.bot.bot_tx_rate_limiter.record_tx()
|
||||
return True
|
||||
|
||||
def _decode_escape_sequences(self, text: str) -> str:
|
||||
"""Decode escape sequences in config strings.
|
||||
|
||||
Processes common escape sequences like \\n (newline), \\t (tab), \\\\ (backslash).
|
||||
This allows users to add newlines in keyword responses using \n (single backslash).
|
||||
|
||||
Behavior:
|
||||
- \n in config file → newline character
|
||||
- \\n in config file → literal backslash + n
|
||||
|
||||
Args:
|
||||
text: The text string to process.
|
||||
|
||||
Returns:
|
||||
str: The text with escape sequences decoded.
|
||||
"""
|
||||
# Replace escape sequences
|
||||
# Order matters: \\ must be processed first to avoid double-processing
|
||||
# This preserves literal backslashes (\\n becomes \n, not a newline)
|
||||
text = text.replace('\\\\', '\x00') # Temporary placeholder for backslash
|
||||
text = text.replace('\\n', '\n') # Newline
|
||||
text = text.replace('\\t', '\t') # Tab
|
||||
text = text.replace('\\r', '\r') # Carriage return
|
||||
text = text.replace('\x00', '\\') # Restore backslash
|
||||
return text
|
||||
|
||||
def load_keywords(self) -> Dict[str, str]:
|
||||
"""Load keywords from config.
|
||||
|
||||
@@ -380,8 +354,8 @@ class CommandManager:
|
||||
# Strip quotes from the response if present
|
||||
if response.startswith('"') and response.endswith('"'):
|
||||
response = response[1:-1]
|
||||
# Decode escape sequences (e.g., \\n for newlines)
|
||||
response = self._decode_escape_sequences(response)
|
||||
# Decode escape sequences (e.g., \n for newlines)
|
||||
response = decode_escape_sequences(response)
|
||||
keywords[keyword.lower()] = response
|
||||
return keywords
|
||||
|
||||
@@ -393,8 +367,8 @@ class CommandManager:
|
||||
# Strip quotes from the response format if present
|
||||
if response_format.startswith('"') and response_format.endswith('"'):
|
||||
response_format = response_format[1:-1]
|
||||
# Decode escape sequences (e.g., \\n for newlines)
|
||||
response_format = self._decode_escape_sequences(response_format)
|
||||
# Decode escape sequences (e.g., \n for newlines)
|
||||
response_format = decode_escape_sequences(response_format)
|
||||
syntax_patterns[pattern] = response_format
|
||||
return syntax_patterns
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any, List, Tuple
|
||||
from .base_command import BaseCommand
|
||||
from ..models import MeshMessage
|
||||
from ..utils import decode_escape_sequences
|
||||
|
||||
|
||||
class GreeterCommand(BaseCommand):
|
||||
@@ -119,39 +120,13 @@ class GreeterCommand(BaseCommand):
|
||||
import traceback
|
||||
self.logger.error(traceback.format_exc())
|
||||
|
||||
def _decode_escape_sequences(self, text: str) -> str:
|
||||
"""Decode escape sequences in config strings.
|
||||
|
||||
Processes common escape sequences like \\n (newline), \\t (tab), \\\\ (backslash).
|
||||
This allows users to add newlines in greeting messages using \n (single backslash).
|
||||
|
||||
Behavior:
|
||||
- \n in config file → newline character
|
||||
- \\n in config file → literal backslash + n
|
||||
|
||||
Args:
|
||||
text: The text string to process.
|
||||
|
||||
Returns:
|
||||
str: The text with escape sequences decoded.
|
||||
"""
|
||||
# Replace escape sequences
|
||||
# Order matters: \\ must be processed first to avoid double-processing
|
||||
# This preserves literal backslashes (\\n becomes \n, not a newline)
|
||||
text = text.replace('\\\\', '\x00') # Temporary placeholder for backslash
|
||||
text = text.replace('\\n', '\n') # Newline
|
||||
text = text.replace('\\t', '\t') # Tab
|
||||
text = text.replace('\\r', '\r') # Carriage return
|
||||
text = text.replace('\x00', '\\') # Restore backslash
|
||||
return text
|
||||
|
||||
def _load_config(self) -> None:
|
||||
"""Load configuration for greeter command."""
|
||||
self.enabled = self.get_config_value('Greeter_Command', 'enabled', fallback=False, value_type='bool')
|
||||
self.greeting_message = self.get_config_value('Greeter_Command', 'greeting_message',
|
||||
fallback='Welcome to the mesh, {sender}!')
|
||||
# Decode escape sequences (e.g., \\n for newlines)
|
||||
self.greeting_message = self._decode_escape_sequences(self.greeting_message)
|
||||
# Decode escape sequences (e.g., \n for newlines)
|
||||
self.greeting_message = decode_escape_sequences(self.greeting_message)
|
||||
|
||||
self.rollout_days = self.get_config_value('Greeter_Command', 'rollout_days', fallback=7, value_type='int')
|
||||
self.include_mesh_info = self.get_config_value('Greeter_Command', 'include_mesh_info',
|
||||
@@ -159,7 +134,7 @@ class GreeterCommand(BaseCommand):
|
||||
self.mesh_info_format = self.get_config_value('Greeter_Command', 'mesh_info_format',
|
||||
fallback='\n\nMesh Info: {total_contacts} contacts, {repeaters} repeaters')
|
||||
# Decode escape sequences (e.g., \n for newlines)
|
||||
self.mesh_info_format = self._decode_escape_sequences(self.mesh_info_format)
|
||||
self.mesh_info_format = decode_escape_sequences(self.mesh_info_format)
|
||||
|
||||
# Log configuration for debugging
|
||||
self.logger.debug(f"Greeter config loaded: include_mesh_info={self.include_mesh_info}, "
|
||||
@@ -199,8 +174,8 @@ class GreeterCommand(BaseCommand):
|
||||
channel_name, greeting = entry.split(':', 1)
|
||||
channel_name = channel_name.strip()
|
||||
greeting = greeting.strip()
|
||||
# Decode escape sequences (e.g., \\n for newlines)
|
||||
greeting = self._decode_escape_sequences(greeting)
|
||||
# Decode escape sequences (e.g., \n for newlines)
|
||||
greeting = decode_escape_sequences(greeting)
|
||||
# Store both original and lowercase channel name for case-insensitive matching
|
||||
self.channel_greetings[channel_name.lower()] = {
|
||||
'channel': channel_name,
|
||||
|
||||
@@ -14,7 +14,7 @@ import json
|
||||
import os
|
||||
from typing import Dict, Tuple, Any
|
||||
from pathlib import Path
|
||||
from .utils import format_keyword_response_with_placeholders
|
||||
from .utils import decode_escape_sequences, format_keyword_response_with_placeholders
|
||||
|
||||
|
||||
class MessageScheduler:
|
||||
@@ -57,15 +57,17 @@ class MessageScheduler:
|
||||
continue
|
||||
|
||||
channel, message = message_info.split(':', 1)
|
||||
channel = channel.strip()
|
||||
message = decode_escape_sequences(message.strip())
|
||||
# Convert HHMM to HH:MM for scheduler
|
||||
hour = int(time_str[:2])
|
||||
minute = int(time_str[2:])
|
||||
schedule_time = f"{hour:02d}:{minute:02d}"
|
||||
|
||||
schedule.every().day.at(schedule_time).do(
|
||||
self.send_scheduled_message, channel.strip(), message.strip()
|
||||
self.send_scheduled_message, channel, message
|
||||
)
|
||||
self.scheduled_messages[time_str] = (channel.strip(), message.strip())
|
||||
self.scheduled_messages[time_str] = (channel, message)
|
||||
self.logger.info(f"Scheduled message: {schedule_time} -> {channel}: {message}")
|
||||
except ValueError:
|
||||
self.logger.warning(f"Invalid scheduled message format: {message_info}")
|
||||
|
||||
@@ -133,6 +133,28 @@ def truncate_string(text: str, max_length: int, ellipsis: str = '...') -> str:
|
||||
return text[:max_length - len(ellipsis)] + ellipsis
|
||||
|
||||
|
||||
def decode_escape_sequences(text: str) -> str:
|
||||
"""Decode escape sequences in config strings (e.g. Keywords, Scheduled_Messages).
|
||||
|
||||
Processes \\n (newline), \\t (tab), \\r (carriage return), \\\\ (literal backslash).
|
||||
Use a single backslash in config: \\n for newline; \\\\n for literal backslash + n.
|
||||
|
||||
Args:
|
||||
text: The text string to process.
|
||||
|
||||
Returns:
|
||||
str: The text with escape sequences decoded.
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
text = text.replace('\\\\', '\x00') # Temporary placeholder for backslash
|
||||
text = text.replace('\\n', '\n') # Newline
|
||||
text = text.replace('\\t', '\t') # Tab
|
||||
text = text.replace('\\r', '\r') # Carriage return
|
||||
text = text.replace('\x00', '\\') # Restore backslash
|
||||
return text
|
||||
|
||||
|
||||
def format_location_for_display(city: Optional[str], state: Optional[str] = None,
|
||||
country: Optional[str] = None, max_length: int = 20) -> Optional[str]:
|
||||
"""Format location data for display with intelligent abbreviation.
|
||||
|
||||
Reference in New Issue
Block a user