from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Literal Mode = Literal["wecom", "mp", "personal"] DmPolicy = Literal["open", "pairing", "allowlist", "disabled"] GroupPolicy = Literal["open", "allowlist", "disabled"] @dataclass class WeChatAccount: account_id: str = "default" mode: Mode = "personal" enabled: bool = True corp_id: str | None = None corp_secret: str | None = None agent_id: str | None = None app_id: str | None = None app_secret: str | None = None bridge_url: str | None = None auto_login: bool = False proxy: str | None = None dm_policy: DmPolicy = "pairing" group_policy: GroupPolicy = "allowlist" allow_from: list[str] = field(default_factory=list) group_allow_from: list[str] = field(default_factory=list) groups: dict[str, dict[str, Any]] = field(default_factory=dict) agent_name: str = "" class WeChatAccountManager: def resolve_account(self, cfg: dict[str, Any], account_id: str = "default") -> WeChatAccount: accounts = cfg.get("accounts", {}) if isinstance(accounts, dict) and account_id in accounts: return self._parse_account(account_id, accounts[account_id]) return self._parse_account(account_id, cfg) @staticmethod def _parse_account(account_id: str, cfg: dict[str, Any]) -> WeChatAccount: mode: Mode = "personal" if all(cfg.get(k) for k in ("corp_id", "corp_secret", "agent_id")): mode = "wecom" elif all(cfg.get(k) for k in ("app_id", "app_secret")): mode = "mp" elif cfg.get("bridge_url"): mode = "personal" return WeChatAccount( account_id=account_id, mode=mode, enabled=cfg.get("enabled", True), corp_id=cfg.get("corp_id"), corp_secret=cfg.get("corp_secret"), agent_id=cfg.get("agent_id"), app_id=cfg.get("app_id"), app_secret=cfg.get("app_secret"), bridge_url=cfg.get("bridge_url"), auto_login=cfg.get("auto_login", False), proxy=cfg.get("proxy"), dm_policy=cfg.get("dm_policy", "pairing"), group_policy=cfg.get("group_policy", "allowlist"), allow_from=cfg.get("allow_from", []), group_allow_from=cfg.get("group_allow_from", []), groups=cfg.get("groups", {}), agent_name=cfg.get("agent_name", ""), ) def is_configured(self, cfg: dict[str, Any], account_id: str = "default") -> bool: account = self.resolve_account(cfg, account_id) if account.mode == "wecom": return all([account.corp_id, account.corp_secret, account.agent_id]) elif account.mode == "mp": return all([account.app_id, account.app_secret]) elif account.mode == "personal": return bool(account.bridge_url) return False @staticmethod def default_account_id(cfg: dict[str, Any]) -> str: return "default" def list_account_ids(self, cfg: dict[str, Any]) -> list[str]: accounts = cfg.get("accounts", {}) if isinstance(accounts, dict) and accounts: return list(accounts.keys()) return ["default"] @staticmethod def get_dm_scope(cfg: dict[str, Any]) -> str: return cfg.get("dm_scope", "per-account-channel-peer") @staticmethod def build_session_key( mode: str, agent_id: str, chat_type: str, peer_id: str, room_id: str = "", thread_id: str = "", ) -> str: if chat_type == "group": key = f"agent:{agent_id}:wechat:{mode}:group:{room_id}" else: key = f"agent:{agent_id}:wechat:{mode}:direct:{peer_id}" if thread_id: key = f"{key}:thread:{thread_id}" return key