PMFlow/backend/app/services/feishu_integration_service/mixin_receive.py

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,
},
}