from typing import Any from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session from app.core.constants import ActorValue from app.modules.audit.constants import AuditSource from app.modules.audit.schemas import AuditLogCreate from app.modules.feishu.commands import FeishuCommandService from app.modules.feishu.constants import ( FEISHU_LONG_CONNECTION_EVENT_ACTION, FEISHU_WEBHOOK_EVENT_ACTION, FeishuCommandKey, FeishuPayloadKey, ) from app.modules.feishu.models import FeishuEventReceipt from app.modules.feishu.service import FeishuService FEISHU_EVENT_ACTIONS = { "webhook": FEISHU_WEBHOOK_EVENT_ACTION, "long_connection": FEISHU_LONG_CONNECTION_EVENT_ACTION, } class FeishuEventService: """Handle Feishu message events from webhook or long connection.""" def __init__(self, db: Session): self.db = db self.feishu = FeishuService(db) self.commands = FeishuCommandService(db) def handle_event( self, payload: dict[str, Any], source: str, auto_reply: bool = True, ) -> dict[str, Any]: self.feishu.verify_event(payload) event_identity = _event_identity(payload, source) if event_identity and not self._register_event(event_identity): return {"ok": True, "handled": False, "duplicate": True} self.feishu.audit.log( AuditLogCreate( actor=ActorValue.FEISHU, source=AuditSource.FEISHU, action=FEISHU_EVENT_ACTIONS.get(source, FEISHU_WEBHOOK_EVENT_ACTION), target_type=source, target_id=event_identity.get("event_key") if event_identity else None, request_payload=payload, response_payload={"accepted": True}, ) ) command = self.commands.extract_event_command(payload) if not command: return {"ok": True, "handled": False} result = self.commands.handle_text( command[FeishuCommandKey.TEXT], chat_id=command[FeishuCommandKey.CHAT_ID], actor=command[FeishuCommandKey.ACTOR], auto_reply=auto_reply, ) return {"ok": True, "handled": True, "result": result} def _register_event(self, event_identity: dict[str, str | None]) -> bool: receipt = FeishuEventReceipt( event_key=str(event_identity["event_key"]), source=str(event_identity["source"]), event_id=event_identity.get("event_id"), message_id=event_identity.get("message_id"), ) self.db.add(receipt) try: self.db.flush() except IntegrityError: self.db.rollback() return False return True def _event_identity(payload: dict[str, Any], source: str) -> dict[str, str | None] | None: header = payload.get(FeishuPayloadKey.HEADER) or {} event = payload.get(FeishuPayloadKey.EVENT) or {} message = event.get(FeishuPayloadKey.MESSAGE) or {} event_id = header.get(FeishuPayloadKey.EVENT_ID) message_id = message.get(FeishuPayloadKey.MESSAGE_ID) stable_id = event_id or message_id if not stable_id: return None event_type = header.get(FeishuPayloadKey.EVENT_TYPE) event_key = ":".join( str(part) for part in (source, event_type or FeishuPayloadKey.EVENT, stable_id) ) return { "event_key": event_key, "source": source, "event_id": str(event_id) if event_id else None, "message_id": str(message_id) if message_id else None, }