ForcePilot/backend/package/yuxi/channels/adapters/mattermost/monitor.py
Kris 1f78c44b03 refactor: 整理并清理项目中的冗余代码与格式问题
这是一个批量整理提交,包含以下主要改动:
1.  删除多处冗余的空行和未使用的导入
2.  修复文件末尾缺少换行符的问题
3.  调整部分模块的导入顺序与代码排版
4.  修复部分配置默认值与策略逻辑
5.  新增多个功能模块与辅助工具
6.  完善异常处理与日志记录
7.  修复速率限制、消息缓存、权限校验等逻辑bug
8.  废弃部分旧有API与配置项并添加警告提示
2026-05-12 14:51:53 +08:00

144 lines
4.7 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
ChatMode = str
MODE_ONCALL: ChatMode = "oncall"
MODE_ONMESSAGE: ChatMode = "onmessage"
MODE_ONCHAR: ChatMode = "onchar"
CHAT_MODES = frozenset({MODE_ONCALL, MODE_ONMESSAGE, MODE_ONCHAR})
DEFAULT_ONCHAR_PREFIXES = frozenset({">", "!"})
@dataclass
class MentionGateConfig:
chat_mode: ChatMode = MODE_ONMESSAGE
require_mention: bool = True
onchar_prefixes: set[str] = field(default_factory=lambda: set(DEFAULT_ONCHAR_PREFIXES))
@classmethod
def from_config(cls, config: dict) -> MentionGateConfig:
chat_mode = config.get("chatmode", MODE_ONMESSAGE)
if chat_mode not in CHAT_MODES:
chat_mode = MODE_ONMESSAGE
prefixes_raw = config.get("onchar_prefixes", [])
if not prefixes_raw:
prefixes_raw = list(DEFAULT_ONCHAR_PREFIXES)
prefixes = {str(p).strip() for p in prefixes_raw if str(p).strip()}
if not prefixes:
prefixes = set(DEFAULT_ONCHAR_PREFIXES)
require_mention = bool(config.get("require_mention", True))
return cls(
chat_mode=chat_mode,
require_mention=require_mention,
onchar_prefixes=frozenset(prefixes),
)
@dataclass
class MentionGateResult:
should_respond: bool
reason: str = ""
class MentionGate:
"""消息响应门控 — 决定是否响应入站消息。
支持三种模式:
- oncall: 仅响应明确呼叫(不自动触发)
- onmessage: 响应所有消息(结合 requireMention 过滤)
- onchar: 仅响应以特定前缀开头的消息
"""
def __init__(self, config: dict | None = None):
self._config = MentionGateConfig.from_config(config or {})
@property
def chat_mode(self) -> str:
return self._config.chat_mode
@property
def require_mention(self) -> bool:
return self._config.require_mention
def reload_config(self, key: str, value) -> None:
from dataclasses import replace
if key == "chatmode" and value in CHAT_MODES:
self._config = replace(self._config, chat_mode=value)
elif key == "require_mention":
self._config = replace(self._config, require_mention=bool(value))
elif key == "onchar_prefixes":
prefixes = {str(p).strip() for p in (value if isinstance(value, list) else []) if str(p).strip()}
if prefixes:
self._config = replace(self._config, onchar_prefixes=frozenset(prefixes))
def check(
self,
chat_type: str,
text: str,
bot_mentioned: bool,
) -> MentionGateResult:
mode = self._config.chat_mode
if chat_type == "direct":
return self._check_dm(text, mode)
return self._check_group_or_channel(text, bot_mentioned, mode)
def _check_dm(self, text: str, mode: ChatMode) -> MentionGateResult:
if mode == MODE_ONCALL:
return MentionGateResult(True)
if mode == MODE_ONCHAR:
if text and any(text.lstrip().startswith(p) for p in self._config.onchar_prefixes):
return MentionGateResult(True)
return MentionGateResult(False, "DM message does not match onchar prefix")
return MentionGateResult(True)
def _check_group_or_channel(self, text: str, bot_mentioned: bool, mode: ChatMode) -> MentionGateResult:
if mode == MODE_ONCALL:
return MentionGateResult(False, "oncall mode does not respond in groups without explicit mention")
if mode == MODE_ONMESSAGE:
if self._config.require_mention and not bot_mentioned:
return MentionGateResult(False, "Bot not mentioned and requireMention is enabled")
return MentionGateResult(True)
if mode == MODE_ONCHAR:
if text and any(text.lstrip().startswith(p) for p in self._config.onchar_prefixes):
return MentionGateResult(True)
return MentionGateResult(False, "Message does not match onchar prefix")
return MentionGateResult(False, f"Unknown chat mode: {mode}")
CONTROL_COMMAND_MARKERS = frozenset({"/restart", "/sudo", "/exec", "/delete", "/config", "/approve", "/deny"})
def is_control_command(text: str) -> bool:
text_stripped = text.strip()
return any(text_stripped.startswith(cmd) for cmd in CONTROL_COMMAND_MARKERS)
def resolve_control_command_gate(
text: str,
chat_type: str,
bot_mentioned: bool = False,
) -> bool:
"""控制命令门控 — 控制命令总是通过(仅 DM/mention 场景),
由后续的 authorize_command_invocation 做进一步授权检查。
"""
if not is_control_command(text):
return False
if chat_type == "direct":
return True
return bot_mentioned