fix: post-rebase compatibility fixes for #138 (radio reliability)

Two small incompatibilities surfaced after rebasing dev-kg7qin-changes
onto upstream/dev in code introduced by #138:

- message_handler.py: replace two remaining Optional[str] annotations
  with str | None; the Optional import was removed during typing
  modernization but two annotations in this module were missed
- command_manager.py: restore # noqa: F401 guard on PUBLIC_CHANNEL_KEY_HEX
  re-export so ruff auto-fix does not silently remove it (imported by core.py)

Do not merge until #138 is merged.
This commit is contained in:
Stacy Olivas
2026-04-14 10:02:48 -07:00
committed by agessaman
parent 973d1fc947
commit a75ea16609
2 changed files with 45 additions and 45 deletions
+26 -26
View File
@@ -9,7 +9,7 @@ import random
import time
from dataclasses import dataclass
from hashlib import sha256
from typing import Any, Optional
from typing import Any
from meshcore import EventType
@@ -37,7 +37,7 @@ class InternetStatusCache:
"""
has_internet: bool
timestamp: float
_lock: Optional[asyncio.Lock] = None
_lock: asyncio.Lock | None = None
def _get_lock(self) -> asyncio.Lock:
"""Lazily initialize the async lock.
@@ -110,7 +110,7 @@ class CommandManager:
# Command queue for near-expiring global cooldowns
# Key: (command_name, user_id) tuple, Value: QueuedCommand
self._command_queue: dict[tuple[str, str], QueuedCommand] = {}
self._queue_processor_task: Optional[asyncio.Task] = None
self._queue_processor_task: asyncio.Task | None = None
# Multi-scope reply: map of normalized scope name → 16-byte HMAC key.
# flood_scope_allow_global is True when '*' (or equivalent) appears in
@@ -306,11 +306,11 @@ class CommandManager:
self.logger.debug(f"Applying {self.bot.tx_delay_ms}ms transmission delay")
await asyncio.sleep(self.bot.tx_delay_ms / 1000.0)
def get_rate_limit_key(self, message: MeshMessage) -> Optional[str]:
def get_rate_limit_key(self, message: MeshMessage) -> str | None:
"""Return the key used for per-user rate limiting (pubkey when available, else sender name)."""
return message.sender_pubkey or message.sender_id or None
def get_rate_limit_wait_seconds(self, rate_limit_key: Optional[str] = None) -> float:
def get_rate_limit_wait_seconds(self, rate_limit_key: str | None = None) -> float:
"""Return seconds to wait until we could pass rate limits (for reply retry)."""
wait = 0.0
if not self.bot.rate_limiter.can_send():
@@ -322,8 +322,8 @@ class CommandManager:
return wait
async def _check_rate_limits(
self, skip_user_rate_limit: bool = False, rate_limit_key: Optional[str] = None,
channel: Optional[str] = None,
self, skip_user_rate_limit: bool = False, rate_limit_key: str | None = None,
channel: str | None = None,
) -> tuple[bool, str]:
"""Check all rate limits before sending.
@@ -389,7 +389,7 @@ class CommandManager:
operation_name: str,
target: str,
used_retry_method: bool = False,
rate_limit_key: Optional[str] = None,
rate_limit_key: str | None = None,
) -> bool:
"""Handle result from message send operations.
@@ -497,7 +497,7 @@ class CommandManager:
banned = self.bot.config.get('Banned_Users', 'banned_users', fallback='')
return [user.strip() for user in banned.split(',') if user.strip()]
def is_user_banned(self, sender_id: Optional[str]) -> bool:
def is_user_banned(self, sender_id: str | None) -> bool:
"""Check if sender is banned using prefix (starts-with) matching.
A banned entry "Awful Username" matches "Awful Username" and "Awful Username 🍆".
@@ -526,7 +526,7 @@ class CommandManager:
return channel_list
def load_channel_keywords(self) -> Optional[list[str]]:
def load_channel_keywords(self) -> list[str] | None:
"""Load channel keyword whitelist from config.
When set, only these triggers (command/keyword names) are answered in channels;
@@ -615,7 +615,7 @@ class CommandManager:
Returns:
List[tuple]: List of (trigger, response) tuples for matched keywords.
"""
matches: list[tuple[str, Optional[str]]] = []
matches: list[tuple[str, str | None]] = []
content = message.content.strip()
# Check for command prefix if configured
@@ -778,7 +778,7 @@ class CommandManager:
# case-insensitive + ignore extra spaces
return " ".join(text.lower().split())
def match_randomline(self, message: MeshMessage) -> Optional[tuple[str, str]]:
def match_randomline(self, message: MeshMessage) -> tuple[str, str] | None:
"""
Exact-match message content against RandomLine triggers.
Returns (key, response) or None.
@@ -919,9 +919,9 @@ class CommandManager:
self,
recipient_id: str,
content: str,
command_id: Optional[str] = None,
command_id: str | None = None,
skip_user_rate_limit: bool = False,
rate_limit_key: Optional[str] = None,
rate_limit_key: str | None = None,
) -> bool:
"""Send a direct message using meshcore-cli command.
@@ -1027,10 +1027,10 @@ class CommandManager:
self,
channel: str,
content: str,
command_id: Optional[str] = None,
command_id: str | None = None,
skip_user_rate_limit: bool = False,
rate_limit_key: Optional[str] = None,
scope: Optional[str] = None,
rate_limit_key: str | None = None,
scope: str | None = None,
) -> bool:
"""Send a channel message using meshcore_py (optional flood scope).
@@ -1147,10 +1147,10 @@ class CommandManager:
channel: str,
chunks: list[str],
*,
command_id: Optional[str] = None,
command_id: str | None = None,
skip_user_rate_limit: bool = True,
rate_limit_key: Optional[str] = None,
scope: Optional[str] = None,
rate_limit_key: str | None = None,
scope: str | None = None,
) -> bool:
"""Send multiple channel messages with rate-limit spacing between chunks.
@@ -1195,7 +1195,7 @@ class CommandManager:
return False
return True
def get_help_for_command(self, command_name: str, message: Optional[MeshMessage] = None) -> str:
def get_help_for_command(self, command_name: str, message: MeshMessage | None = None) -> str:
"""Get help text for a specific command (LoRa-friendly compact format).
Args:
@@ -1228,7 +1228,7 @@ class CommandManager:
return f"Help {command_name}: {help_text}"
# Next, consult plugin_loader keyword mappings (if available)
mapped_name: Optional[str] = None
mapped_name: str | None = None
if hasattr(self, 'plugin_loader') and hasattr(self.plugin_loader, 'keyword_mappings'):
mapped_name = self.plugin_loader.keyword_mappings.get(normalized_name)
if mapped_name:
@@ -1285,7 +1285,7 @@ class CommandManager:
_HELP_PREFIX = "Bot Help: "
_HELP_SUFFIX = " | More: 'help <command>'"
def get_general_help(self, message: Optional[MeshMessage] = None) -> str:
def get_general_help(self, message: MeshMessage | None = None) -> str:
"""Get general help text from config (LoRa-friendly compact format).
When message is provided, only lists commands valid for the message's channel.
@@ -1773,11 +1773,11 @@ class CommandManager:
return has_internet
def get_plugin_by_keyword(self, keyword: str) -> Optional[BaseCommand]:
def get_plugin_by_keyword(self, keyword: str) -> BaseCommand | None:
"""Get a plugin by keyword"""
return self.plugin_loader.get_plugin_by_keyword(keyword)
def get_plugin_by_name(self, name: str) -> Optional[BaseCommand]:
def get_plugin_by_name(self, name: str) -> BaseCommand | None:
"""Get a plugin by name"""
return self.plugin_loader.get_plugin_by_name(name)
@@ -1785,6 +1785,6 @@ class CommandManager:
"""Reload a specific plugin"""
return self.plugin_loader.reload_plugin(plugin_name)
def get_plugin_metadata(self, plugin_name: Optional[str] = None) -> dict[str, Any]:
def get_plugin_metadata(self, plugin_name: str | None = None) -> dict[str, Any]:
"""Get plugin metadata"""
return self.plugin_loader.get_plugin_metadata(plugin_name)