from __future__ import annotations from .shared import * class FeishuConfigMixin: def __init__(self, db: AsyncSession): self.db = db # 从数据库获取飞书配置 self.app_id = None self.app_secret = None self.encrypt_key = None self.verification_token = None self.event_mode = "long_connection" self.tenant_id = 1 @staticmethod def _normalize_event_mode(_: Optional[str] = None) -> str: return "long_connection" @staticmethod def _is_postgresql_session(db: AsyncSession) -> bool: try: bind = db.get_bind() except Exception: return False dialect_name = str(getattr(getattr(bind, "dialect", None), "name", "")).lower() return dialect_name.startswith("postgres") @staticmethod async def _notify_outbox_ready(db: AsyncSession, *, outbox_id: Optional[int]) -> None: """仅在 PostgreSQL 下发送 outbox ready 事件。""" if not FeishuConfigMixin._is_postgresql_session(db): return try: payload = str(int(outbox_id))[:64] if outbox_id is not None else "" await db.execute( text("SELECT pg_notify('feishu_outbox_ready', :payload)"), {"payload": payload}, ) except Exception as exc: print(f"[Feishu] outbox notify failed: outbox_id={outbox_id}, error={exc}") async def load_config(self, tenant_id: int = 1): """从数据库加载飞书配置""" self.tenant_id = tenant_id result = await self.db.execute( select(Integration).where( Integration.tenant_id == tenant_id, Integration.type == 'feishu', Integration.is_active.is_(True), ) ) integration = result.scalar_one_or_none() if integration: if isinstance(integration.config, str): config = json.loads(integration.config) if integration.config else {} else: config = integration.config or {} self.app_id = config.get('app_id') self.app_secret = config.get('app_secret') self.encrypt_key = None self.verification_token = None self.event_mode = self._normalize_event_mode(config.get('event_mode')) else: self.app_id = None self.app_secret = None self.encrypt_key = None self.verification_token = None self.event_mode = 'long_connection' def is_configured(self) -> bool: """检查是否已配置""" return bool(self.app_id and self.app_secret) async def save_config( self, app_id: str, app_secret: str, encrypt_key: str, verification_token: str, event_mode: str = "long_connection", tenant_id: int = 1 ): """保存飞书配置到数据库""" mode = self._normalize_event_mode(event_mode) config = { 'app_id': app_id, 'app_secret': app_secret, 'encrypt_key': "", 'verification_token': "", 'event_mode': mode, } # 查找或创建集成记录 result = await self.db.execute( select(Integration).where( Integration.tenant_id == tenant_id, Integration.type == 'feishu' ) ) integration = result.scalar_one_or_none() if integration: integration.config = config integration.is_active = True else: integration = Integration( type='feishu', config=config, is_active=True, tenant_id=tenant_id ) self.db.add(integration) await self.db.commit() # 更新内存配置 self.app_id = app_id self.app_secret = app_secret self.encrypt_key = None self.verification_token = None self.event_mode = mode self.tenant_id = tenant_id async def handle_message_event( self, event_data: Dict[str, Any] ) -> Dict[str, Any]: """ 处理飞书消息事件 支持的事件类型: - message.receive_v1: 收到群聊消息 - im.message.receive_v1: 收到群聊消息(schema 2.0) - card.action.trigger: 卡片按钮回调 """ try: event_type = (event_data.get('type') or '').strip() # 飞书 schema 2.0 会返回 im.message.receive_v1 if event_type in {'message.receive_v1', 'im.message.receive_v1'}: return await self._handle_message_receive(event_data) if event_type in {'card.action.trigger', 'im.card.action.trigger'}: return await self._handle_card_action_trigger(event_data) return { 'code': 0, 'msg': f'Event type {event_type} not handled' } except Exception as e: print(f"[Feishu] Handle event error: {e}") return { 'code': 1, 'msg': str(e) }