import logging import os from yuxi.channel.extensions.clickup.types import ClickUpAccount logger = logging.getLogger(__name__) ENV_MAP = { "api_token": "CLICKUP_API_TOKEN", "workspace_id": "CLICKUP_WORKSPACE_ID", "webhook_secret": "CLICKUP_WEBHOOK_SECRET", "auth_mode": "CLICKUP_AUTH_MODE", "oauth_client_id": "CLICKUP_OAUTH_CLIENT_ID", "oauth_client_secret": "CLICKUP_OAUTH_CLIENT_SECRET", "dm_policy": "CLICKUP_DM_POLICY", "allow_from": "CLICKUP_ALLOW_FROM", "channel_policy": "CLICKUP_CHANNEL_POLICY", "poll_fallback_enabled": "CLICKUP_POLL_FALLBACK_ENABLED", "poll_interval": "CLICKUP_POLL_INTERVAL", } def _env_or_config(key: str) -> str | None: env_key = ENV_MAP.get(key, "") if env_key: return os.getenv(env_key) return None class ClickUpConfigAdapter: def list_account_ids(self, config: dict) -> list[str]: accounts = config.get("accounts", {}) if not accounts: return ["default"] if self._env_token_exists("default") else [] return list(accounts.keys()) async def resolve_account(self, account_id: str) -> dict: account = self._build_account(account_id, {}) return { "account_id": account.account_id, "api_token": account.api_token, "workspace_id": account.workspace_id, "webhook_secret": account.webhook_secret, "auth_mode": account.auth_mode, "oauth_client_id": account.oauth_client_id, "oauth_client_secret": account.oauth_client_secret, "name": account.name, "dm_policy": account.dm_policy, "allow_from": account.allow_from, "channel_policy": account.channel_policy, "channel_allowlist": account.channel_allowlist, "poll_fallback_enabled": account.poll_fallback_enabled, "poll_interval": account.poll_interval, "enabled": True, } def is_configured(self, account: dict) -> bool: return bool(account.get("api_token") and account.get("workspace_id")) def is_enabled(self, account: dict) -> bool: return account.get("enabled", True) def describe_account(self, account: dict) -> dict: return { "account_id": account.get("account_id", ""), "workspace_id": account.get("workspace_id", ""), "dm_policy": account.get("dm_policy", "open"), "channel_policy": account.get("channel_policy", "activate_on_mention"), "configured": self.is_configured(account), } def default_account_id(self, config: dict) -> str: return config.get("default_account", "default") def resolve_allow_from(self, config: dict, account_id: str | None = None) -> list[str]: aid = account_id or self.default_account_id(config) accounts = config.get("accounts", {}) account = accounts.get(aid, {}) return account.get("allow_from", []) def config_schema(self) -> dict: return { "$schema": "https://json-schema.org/draft-07/schema#", "type": "object", "title": "ClickUp Chat 渠道配置", "properties": { "api_token": { "type": "string", "title": "Personal API Token", "description": "ClickUp Personal API Token(pk_ 前缀)。在 ClickUp → Settings → Apps 中生成。", "x-ui-password": True, }, "workspace_id": { "type": "string", "title": "Workspace ID", "description": "ClickUp Workspace/Team ID(数字)。可通过 GET /api/v2/team 获取。", }, "webhook_secret": { "type": "string", "title": "Webhook Secret", "description": "用于验证 Automation Webhook 的自定义密钥(可选但强烈推荐)。", "x-ui-password": True, }, "dm_policy": { "type": "string", "title": "DM 安全策略", "enum": ["open", "pairing", "allowlist", "disabled"], "default": "open", }, "allow_from": { "type": "string", "title": "DM 白名单", "description": "逗号分隔的 ClickUp user_id 列表。仅 dm_policy=allowlist 时生效。", }, "channel_policy": { "type": "string", "title": "Channel 响应策略", "enum": ["always", "activate_on_mention", "disabled"], "default": "activate_on_mention", }, "poll_fallback_enabled": { "type": "boolean", "title": "启用轮询回退", "description": "Automation Webhook 不可用时,通过拉取消息列表作为回退方案(P2)。", "default": False, }, }, } def _build_account(self, account_id: str, raw: dict) -> ClickUpAccount: allow_from_str = _env_or_config("allow_from") or raw.get("allow_from", "") if isinstance(allow_from_str, list): allow_from_list = allow_from_str else: allow_from_list = [u.strip() for u in allow_from_str.split(",") if u.strip()] poll_enabled_str = _env_or_config("poll_fallback_enabled") or str(raw.get("poll_fallback_enabled", False)) return ClickUpAccount( account_id=account_id, api_token=_env_or_config("api_token") or raw.get("api_token", ""), workspace_id=_env_or_config("workspace_id") or raw.get("workspace_id", ""), webhook_secret=_env_or_config("webhook_secret") or raw.get("webhook_secret", ""), auth_mode=_env_or_config("auth_mode") or raw.get("auth_mode", "api_token"), oauth_client_id=_env_or_config("oauth_client_id") or raw.get("oauth_client_id", ""), oauth_client_secret=_env_or_config("oauth_client_secret") or raw.get("oauth_client_secret", ""), name=raw.get("name", account_id), dm_policy=_env_or_config("dm_policy") or raw.get("dm_policy", "open"), allow_from=allow_from_list, channel_policy=_env_or_config("channel_policy") or raw.get("channel_policy", "activate_on_mention"), channel_allowlist=raw.get("channel_allowlist", []), poll_fallback_enabled=poll_enabled_str.lower() == "true", poll_interval=float(_env_or_config("poll_interval") or raw.get("poll_interval", "15.0")), ) @staticmethod def _env_token_exists(account_id: str) -> bool: key = f"CLICKUP_API_TOKEN_{account_id.upper()}" return bool(os.environ.get(key, "") or os.environ.get("CLICKUP_API_TOKEN", ""))