214 lines
8.6 KiB
Python
214 lines
8.6 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
class FeishuReceiveMixin:
|
|
async def _handle_message_receive(
|
|
self,
|
|
event: Dict[str, Any]
|
|
) -> Dict[str, Any]:
|
|
"""处理收到的消息事件"""
|
|
state_trace_id = f"feishu-sm-{uuid4().hex[:12]}"
|
|
ingress_source = str(event.get("_ingress_source") or "feishu").strip() or "feishu"
|
|
message = event.get('message', {}) if isinstance(event.get('message', {}), dict) else {}
|
|
chat = event.get('chat', {}) if isinstance(event.get('chat', {}), dict) else {}
|
|
sender = event.get('sender', {}) if isinstance(event.get('sender', {}), dict) else {}
|
|
message_id = (message.get("message_id") or "").strip() or "unknown"
|
|
perf_t0_raw = event.get("_perf_t0")
|
|
perf_t0: Optional[float] = None
|
|
if isinstance(perf_t0_raw, (int, float)):
|
|
perf_t0 = float(perf_t0_raw)
|
|
else:
|
|
try:
|
|
perf_t0 = float(str(perf_t0_raw).strip())
|
|
except Exception:
|
|
perf_t0 = None
|
|
self._emit_state_transition(
|
|
trace_id=state_trace_id,
|
|
state="RECEIVED",
|
|
message_id=message_id,
|
|
message_type=str(message.get("message_type") or "unknown"),
|
|
)
|
|
sender_type = str(sender.get("sender_type") or "").strip().lower()
|
|
if sender_type in {"app", "bot"}:
|
|
return {'code': 0, 'msg': 'Ignore bot/app message'}
|
|
|
|
mentioned_bot_names = self._extract_mentioned_bot_names(message)
|
|
|
|
# 解析消息内容与附件元数据
|
|
message_payload = self._extract_message_payload(event)
|
|
message_content = str(message_payload.get("text") or "").strip() or None
|
|
message_attachments = (
|
|
message_payload.get("attachments")
|
|
if isinstance(message_payload.get("attachments"), list)
|
|
else []
|
|
)
|
|
await self._record_ingress_trace(
|
|
ingress_source=ingress_source,
|
|
trace_id=state_trace_id,
|
|
message_id=message_id,
|
|
event=event,
|
|
message_content=message_content or "",
|
|
)
|
|
if not message_content:
|
|
print(
|
|
f"[Feishu] message.receive_v1 ignored: no text content, "
|
|
f"message_type={message.get('message_type')}, keys={list(message.keys())}"
|
|
)
|
|
chat_id = chat.get('chat_id') or message.get('chat_id') or ''
|
|
if mentioned_bot_names and chat_id:
|
|
await self._safe_reply_text(
|
|
chat_id=chat_id,
|
|
mention_open_id=(sender.get('sender_id', {}) or {}).get('open_id'),
|
|
mention_name=(sender.get('sender_name') or sender.get('name') or '你'),
|
|
text="我已收到你的消息,但目前无法直接解析纯图片内容。请补充 1-2 句文字需求,我会立即给出分派建议。",
|
|
trace_id=state_trace_id,
|
|
source_message_id=message_id,
|
|
perf_t0=perf_t0,
|
|
)
|
|
self._emit_state_transition(
|
|
trace_id=state_trace_id,
|
|
state="REPLIED",
|
|
route="empty_text_prompt",
|
|
)
|
|
return {'code': 0, 'msg': 'No text content'}
|
|
|
|
# 获取发送者信息
|
|
sender_id = sender.get('sender_id', {}) if isinstance(sender.get('sender_id', {}), dict) else {}
|
|
sender_open_id = (sender_id.get('open_id') or "").strip() or None
|
|
sender_name = await self._resolve_sender_display_name(
|
|
sender=sender,
|
|
sender_id=sender_id,
|
|
)
|
|
|
|
# 获取群聊信息
|
|
chat_id = chat.get('chat_id') or message.get('chat_id') or ''
|
|
chat_type = str(chat.get("chat_type") or message.get("chat_type") or "").strip().lower() or "group"
|
|
captured_at = self._extract_message_datetime(message)
|
|
occurred_at_iso = (
|
|
captured_at.isoformat() if isinstance(captured_at, datetime) else None
|
|
)
|
|
message_attachments = self._normalize_ingress_attachment_records(
|
|
message_attachments,
|
|
occurred_at=occurred_at_iso,
|
|
source_message_id=(message.get("message_id") or "").strip() or None,
|
|
source_chat_id=chat_id or None,
|
|
)
|
|
|
|
# 使用信号捕获服务处理
|
|
capture_service = ProactiveSignalCaptureService(self.db)
|
|
|
|
try:
|
|
result = await capture_service.capture_from_feishu(
|
|
group_chat_id=chat_id,
|
|
message_content=message_content,
|
|
sender=sender_name,
|
|
tenant_id=self.tenant_id,
|
|
proposer_open_id=sender_open_id,
|
|
captured_at=captured_at,
|
|
mentioned_bot_names=mentioned_bot_names,
|
|
feishu_message_id=(message.get("message_id") or "").strip() or None,
|
|
chat_type=chat_type,
|
|
)
|
|
await self._persist_signal_attachments_from_ingress(
|
|
signal_id=result.get("signal_id"),
|
|
attachments=message_attachments,
|
|
)
|
|
except Exception as exc:
|
|
self._emit_state_transition(
|
|
trace_id=state_trace_id,
|
|
state="ERROR_REPLY",
|
|
reason="capture_exception",
|
|
error=str(exc),
|
|
)
|
|
if chat_id:
|
|
await self._safe_reply_text(
|
|
chat_id=chat_id,
|
|
mention_open_id=sender_open_id,
|
|
mention_name=sender_name,
|
|
text=self._build_error_reply_text(),
|
|
trace_id=state_trace_id,
|
|
source_message_id=message_id,
|
|
perf_t0=perf_t0,
|
|
)
|
|
self._emit_state_transition(
|
|
trace_id=state_trace_id,
|
|
state="REPLIED",
|
|
route="error_template",
|
|
)
|
|
return {
|
|
"code": 0,
|
|
"msg": "error_handled",
|
|
"data": {
|
|
"status": "error",
|
|
"classification": "error",
|
|
"ai_trace_id": state_trace_id,
|
|
},
|
|
}
|
|
|
|
print(
|
|
"[Feishu] Message processed: "
|
|
f"status={result.get('status')}, "
|
|
f"classification={result.get('classification')}, "
|
|
f"linked_projects={result.get('linked_project_ids', [])}, "
|
|
f"auto_tasks={len(result.get('auto_tasks', []) if isinstance(result.get('auto_tasks', []), list) else [])}, "
|
|
f"ai_trace_id={result.get('ai_trace_id')}"
|
|
)
|
|
scene = self._classify_message_scene(capture_result=result)
|
|
self._emit_state_transition(
|
|
trace_id=state_trace_id,
|
|
state="CLASSIFIED",
|
|
scene=scene,
|
|
status=str(result.get("status") or "unknown"),
|
|
classification=str(result.get("classification") or "unknown"),
|
|
)
|
|
if str(result.get("status") or "").lower() != "error":
|
|
self._emit_state_transition(
|
|
trace_id=state_trace_id,
|
|
state="COMMITTED",
|
|
ai_trace_id=str(result.get("ai_trace_id") or ""),
|
|
)
|
|
|
|
reply_route = await self._route_scene_reply(
|
|
scene=scene,
|
|
result=result,
|
|
message_content=message_content,
|
|
chat_id=chat_id,
|
|
sender_open_id=sender_open_id,
|
|
sender_name=sender_name,
|
|
mentioned_bot_names=mentioned_bot_names,
|
|
trace_id=state_trace_id,
|
|
message_id=message_id,
|
|
perf_t0=perf_t0,
|
|
)
|
|
|
|
if reply_route != "none":
|
|
self._emit_state_transition(
|
|
trace_id=state_trace_id,
|
|
state="REPLIED",
|
|
route=reply_route,
|
|
)
|
|
await self._record_message_trace(
|
|
trace_id=state_trace_id,
|
|
message_id=message_id,
|
|
chat_type=chat_type,
|
|
message_content=message_content,
|
|
capture_result=result,
|
|
reply_source=reply_route,
|
|
)
|
|
|
|
return {
|
|
'code': 0,
|
|
'msg': 'success',
|
|
'data': {
|
|
'status': result.get('status'),
|
|
'classification': result.get('classification'),
|
|
'linked_project_ids': result.get('linked_project_ids', []),
|
|
'auto_tasks_count': len(result.get('auto_tasks', []) if isinstance(result.get('auto_tasks', []), list) else []),
|
|
'ai_trace_id': result.get('ai_trace_id'),
|
|
'state_trace_id': state_trace_id,
|
|
'scene': scene,
|
|
},
|
|
}
|
|
|