2026-05-12 00:46:12 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
|
|
|
|
|
|
ReplyToMode = str
|
|
|
|
|
|
REPLY_OFF: ReplyToMode = "off"
|
|
|
|
|
|
REPLY_FIRST: ReplyToMode = "first"
|
|
|
|
|
|
REPLY_ALL: ReplyToMode = "all"
|
|
|
|
|
|
REPLY_BATCHED: ReplyToMode = "batched"
|
|
|
|
|
|
REPLY_MODES = frozenset({REPLY_OFF, REPLY_FIRST, REPLY_ALL, REPLY_BATCHED})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class ReplyConfig:
|
|
|
|
|
|
mode: ReplyToMode = REPLY_OFF
|
|
|
|
|
|
thread_only: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def from_config(cls, config: dict) -> ReplyConfig:
|
|
|
|
|
|
mode = config.get("reply_to_mode", REPLY_OFF)
|
|
|
|
|
|
if mode not in REPLY_MODES:
|
|
|
|
|
|
mode = REPLY_OFF
|
|
|
|
|
|
thread_only = bool(config.get("reply_thread_only", False))
|
|
|
|
|
|
return cls(mode=mode, thread_only=thread_only)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReplyManager:
|
|
|
|
|
|
def __init__(self, config: dict | None = None):
|
|
|
|
|
|
self._config = ReplyConfig.from_config(config or {})
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def mode(self) -> ReplyToMode:
|
|
|
|
|
|
return self._config.mode
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def thread_only(self) -> bool:
|
|
|
|
|
|
return self._config.thread_only
|
|
|
|
|
|
|
2026-05-12 14:51:53 +08:00
|
|
|
|
def reload_config(self, key: str, value) -> None:
|
|
|
|
|
|
from dataclasses import replace
|
|
|
|
|
|
|
|
|
|
|
|
if key == "reply_to_mode" and value in REPLY_MODES:
|
|
|
|
|
|
self._config = replace(self._config, mode=value)
|
|
|
|
|
|
|
2026-05-12 00:46:12 +08:00
|
|
|
|
def should_reply(self, chat_type: str, has_thread: bool = False) -> bool:
|
|
|
|
|
|
if chat_type == "direct":
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
mode = self._config.mode
|
|
|
|
|
|
|
|
|
|
|
|
if mode == REPLY_OFF:
|
|
|
|
|
|
return False
|
|
|
|
|
|
if mode == REPLY_FIRST and chat_type == "direct":
|
|
|
|
|
|
return True
|
|
|
|
|
|
if mode == REPLY_ALL:
|
|
|
|
|
|
return True
|
|
|
|
|
|
if mode == REPLY_BATCHED:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_reply_target(self, chat_id: str, root_id: str | None) -> str | None:
|
|
|
|
|
|
"""返回应该回复到的消息 ID(用作 root_id),None 表示不绑定线程。"""
|
|
|
|
|
|
if self._config.mode == REPLY_OFF:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
if self._config.thread_only and root_id:
|
|
|
|
|
|
return root_id
|
|
|
|
|
|
|
|
|
|
|
|
if self._config.mode in (REPLY_ALL, REPLY_BATCHED) and root_id:
|
|
|
|
|
|
return root_id
|
|
|
|
|
|
|
|
|
|
|
|
return None
|