from __future__ import annotations import time from dataclasses import dataclass from typing import Any from yuxi.utils.logging_config import logger @dataclass class InteractiveEvent: event_key: str event_type: str channel_user_id: str chat_id: str task_id: str | None = None selections: dict[str, list[str]] | None = None timestamp: float = 0.0 class WeChatInteractiveAdapter: def __init__(self): self._enabled: bool = False self._pending_events: dict[str, InteractiveEvent] = {} self._processed_events: set[str] = set() self._event_handlers: dict[str, list[callable]] = {} def configure(self, config: dict[str, Any]) -> None: self._enabled = config.get("interactive", {}).get("enabled", False) logger.info(f"[WeChat/Interactive] Configured: enabled={self._enabled}") def register_handler(self, event_key: str, handler: callable) -> None: self._event_handlers.setdefault(event_key, []).append(handler) def parse_click_event(self, payload: dict[str, Any]) -> InteractiveEvent | None: if not self._enabled: return None msg_type = payload.get("MsgType", "") event_type = payload.get("Event", "") if msg_type != "event" and event_type not in ("click", "view"): return None event_key = payload.get("EventKey", "") from_user = payload.get("FromUserName", "") chat_id = payload.get("ToUserName", "") if not event_key or not from_user: return None event = InteractiveEvent( event_key=event_key, event_type=event_type, channel_user_id=from_user, chat_id=chat_id, task_id=payload.get("TaskId"), timestamp=time.time(), ) logger.info(f"[WeChat/Interactive] Parsed event: key={event_key}, user={from_user}") return event def parse_submit_event(self, payload: dict[str, Any]) -> InteractiveEvent | None: if not self._enabled: return None msg_type = payload.get("MsgType", "") event = payload.get("Event", "") if msg_type != "event" or event != "template_card_event": return None card_event = payload.get("TemplateCardEvent", {}) event_key = card_event.get("EventKey", "") task_id = card_event.get("TaskId", "") selections_raw = card_event.get("SelectedItems", {}) selections: dict[str, list[str]] = {} if isinstance(selections_raw, dict): for key, value in selections_raw.items(): if isinstance(value, list): selections[key] = value elif isinstance(value, str): selections[key] = [value] interactive_event = InteractiveEvent( event_key=event_key, event_type="submit", channel_user_id=payload.get("FromUserName", ""), chat_id=payload.get("ToUserName", ""), task_id=task_id, selections=selections, timestamp=time.time(), ) logger.info(f"[WeChat/Interactive] Parsed submit event: key={event_key}, task={task_id}") return interactive_event async def dispatch_event(self, event: InteractiveEvent) -> list[dict[str, Any]]: if not self._enabled: return [] event_id = f"{event.event_key}:{event.channel_user_id}:{event.timestamp}" if event_id in self._processed_events: return [] self._processed_events.add(event_id) results: list[dict[str, Any]] = [] handlers = self._event_handlers.get(event.event_key, []) for handler in handlers: try: result = await handler(event) results.append(result) except Exception as e: logger.error(f"[WeChat/Interactive] Handler error for '{event.event_key}': {e}") return results def is_enabled(self) -> bool: return self._enabled def clear_processed(self) -> None: self._processed_events.clear()