落地海运协同群:测服靠会话存档听群话,报价单能带上协同字段。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
微盛群消息 → InboundMessage。
|
||||
|
||||
本文件职责:从管家 records 抽出 sender_id / chat_id / 正文 / 附件名。
|
||||
禁止:把存档席「询价机器人」当成发言人入业务;禁止再向企微换 ID。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, Mapping, Optional, Sequence
|
||||
|
||||
from agent.channel.wecom.models import InboundMessage
|
||||
|
||||
# 存档席在群里的显示名;from 偶发不是 userid 时也要丢掉
|
||||
SEAT_DISPLAY_NAMES = frozenset({"询价机器人"})
|
||||
_SKIP_TYPES = frozenset({"revoke", "recall", "event", "unknown"})
|
||||
_FILE_TYPES = frozenset({"file", "image", "video", "emotion", "voice"})
|
||||
|
||||
|
||||
def _as_mapping(raw: Any) -> dict[str, Any]:
|
||||
if isinstance(raw, Mapping):
|
||||
return dict(raw)
|
||||
if isinstance(raw, str) and raw.strip().startswith("{"):
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
return dict(parsed) if isinstance(parsed, Mapping) else {}
|
||||
return {}
|
||||
|
||||
|
||||
def _create_time(msgtime: Any) -> int:
|
||||
"""微盛 msgtime 常是毫秒;InboundMessage.create_time 用秒。"""
|
||||
try:
|
||||
n = int(msgtime or 0)
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
if n > 10**12:
|
||||
return n // 1000
|
||||
return n
|
||||
|
||||
|
||||
def _filename(msg_type: str, content: str, payload: Mapping[str, Any]) -> str:
|
||||
for key in ("filename", "file_name", "FileName", "title", "name"):
|
||||
val = str(payload.get(key) or "").strip()
|
||||
if val:
|
||||
return val
|
||||
if msg_type in _FILE_TYPES:
|
||||
return str(content or "").strip()
|
||||
return ""
|
||||
|
||||
|
||||
def archive_record_to_inbound(
|
||||
record: Mapping[str, Any],
|
||||
*,
|
||||
seat_userids: Sequence[str] = (),
|
||||
) -> Optional[InboundMessage]:
|
||||
"""
|
||||
一条管家记录变成群入站。
|
||||
|
||||
返回 None:不是群、没有发言人、发言人是存档席、或撤回类消息。
|
||||
message_id 带 wshoto: 前缀,避免和回调 MsgId 撞车。
|
||||
"""
|
||||
room_id = str(record.get("room_id") or record.get("roomid") or "").strip()
|
||||
sender_id = str(record.get("from") or record.get("from_id") or "").strip()
|
||||
if not room_id or not sender_id:
|
||||
return None
|
||||
seats = {str(x).strip() for x in seat_userids if str(x).strip()}
|
||||
if sender_id in seats or sender_id in SEAT_DISPLAY_NAMES:
|
||||
return None
|
||||
|
||||
msg_type = str(record.get("msgtype") or record.get("msg_type") or "text").strip().lower()
|
||||
if msg_type in _SKIP_TYPES:
|
||||
return None
|
||||
|
||||
payload = _as_mapping(record.get("msg_content") or record.get("msgcontent"))
|
||||
content = str(record.get("content") or payload.get("content") or "").strip()
|
||||
msgid = str(record.get("msgid") or record.get("msg_id") or "").strip()
|
||||
seq = record.get("seq")
|
||||
if not msgid:
|
||||
msgid = f"{room_id}:{seq or 0}:{sender_id}"
|
||||
|
||||
media: dict[str, Any] = {}
|
||||
inbound_type = "text"
|
||||
if msg_type in _FILE_TYPES:
|
||||
inbound_type = "file" if msg_type == "file" else msg_type
|
||||
media = {
|
||||
"filename": _filename(msg_type, content, payload),
|
||||
"media_id": str(payload.get("sdkfileid") or payload.get("sdk_file_id") or ""),
|
||||
"file_ext": str(payload.get("fileext") or payload.get("file_ext") or ""),
|
||||
"content_type": "",
|
||||
"filesize": payload.get("filesize") or payload.get("file_size") or 0,
|
||||
}
|
||||
if not content:
|
||||
content = str(media.get("filename") or "")
|
||||
|
||||
return InboundMessage(
|
||||
sender_id=sender_id,
|
||||
message_id=f"wshoto:{msgid}",
|
||||
content=content,
|
||||
msg_type=inbound_type,
|
||||
chat_id=room_id,
|
||||
chat_type="group",
|
||||
create_time=_create_time(record.get("msgtime") or record.get("msg_time")),
|
||||
media=media,
|
||||
raw={
|
||||
"source": "wshoto_archive",
|
||||
"seq": seq,
|
||||
"msgtype": msg_type,
|
||||
"direction": record.get("direction") or "",
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user