机器人显示名与站点标题可配;多段联运列表投影;协同群监听对账;工单附件落存;模板与工单后台补强

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-10-19 09:41:59 +08:00
co-authored by Cursor
parent 277cdc1c28
commit 2b528758b4
119 changed files with 6760 additions and 3516 deletions
+63 -8
View File
@@ -22,12 +22,14 @@ _TTL_SEC = 3 * 60
def _norm_text(text: str) -> str:
body = (text or "").replace("@询价小助手", "").replace("询价小助手", "")
from agent.policy.bot_name import strip_bot_mention
body = strip_bot_mention(text or "")
return "".join(body.split())
def text_fingerprint(chat_id: str, sender_id: str, text: str) -> str:
"""同一群同一人同一句(去掉 @询价小助手)。"""
"""同一群同一人同一句(去掉 @机器人显示名,含旧名)。"""
return f"{(chat_id or '').strip()}|{(sender_id or '').strip()}|{_norm_text(text)}"
@@ -56,6 +58,19 @@ def _redis_mark(fp: str) -> None:
logger.exception("空运群入站去重写 Redis 失败")
def _redis_claim(fp: str) -> Optional[bool]:
"""
Redis SET NX 抢占。True=抢到;False=已被占;None=Redis 不可用。
"""
try:
client = get_redis_runtime().client
ok = client.raw.set(client.key(AIR_INBOUND_TEXT, fp), "1", nx=True, ex=_TTL_SEC)
return bool(ok)
except Exception:
logger.exception("空运群入站去重抢占 Redis 失败")
return None
def _file_fresh(fp: str) -> bool:
path = _file_path(fp)
try:
@@ -74,6 +89,27 @@ def _file_mark(fp: str) -> None:
logger.exception("空运群入站去重写文件失败")
def _file_claim(fp: str) -> bool:
"""文件独占创建;已存在且未过期则失败。"""
path = _file_path(fp)
try:
if path.exists():
if (time.time() - path.stat().st_mtime) < _TTL_SEC:
return False
try:
path.unlink()
except OSError:
pass
with path.open("x", encoding="ascii") as fd:
fd.write("1")
return True
except FileExistsError:
return False
except OSError:
logger.exception("空运群入站去重文件抢占失败")
return True
def already_handled_air_text(*, chat_id: str, sender_id: str, text: str) -> bool:
"""这句是否刚被 BOT 办过。给存档补听用。"""
fp = text_fingerprint(chat_id, sender_id, text)
@@ -91,6 +127,26 @@ def mark_air_text(*, chat_id: str, sender_id: str, text: str) -> None:
_file_mark(fp)
def try_claim_air_text(*, chat_id: str, sender_id: str, text: str) -> bool:
"""
同一群同一人同一句只办一遍(含 BOT 桥重复推送)。
返回 True=本路应处理;False=已有一路在办/办过,直接跳过。
运行证据:多段报价同一句回了两份空运单。
"""
fp = text_fingerprint(chat_id, sender_id, text)
if not _norm_text(text):
return True
redis_ok = _redis_claim(fp)
if redis_ok is True:
_file_mark(fp)
return True
if redis_ok is False:
return False
# Redis 不可用时退回文件抢占。
return _file_claim(fp)
def is_archive_source(raw: Optional[dict[str, Any]]) -> bool:
src = str((raw or {}).get("source") or "").strip().lower()
return src in {"wshoto_archive", "wshoto", "archive"}
@@ -116,13 +172,12 @@ def should_skip_air_text(
raw: Optional[dict[str, Any]] = None,
) -> bool:
"""
存档补听到的 @ 句,若 BOT 已经回过就跳过。
同一句去重:BOT 桥重复推、存档补听都跳过。
BOT 自己再 @ 同一工单号仍要再发一版摘要,不在这里拦。
用 try_claim:第一路处理,后续同文案在 TTL 内不再回。
"""
if not is_archive_source(raw):
return False
if already_handled_air_text(chat_id=chat_id, sender_id=sender_id, text=text):
logger.info("空运群存档补听跳过,BOT 已办 chat=%s", chat_id)
_ = raw
if not try_claim_air_text(chat_id=chat_id, sender_id=sender_id, text=text):
logger.info("空运群入站去重跳过 chat=%s", chat_id)
return True
return False