ForcePilot/backend/package/yuxi/channel/extensions/clickup/config.py

160 lines
6.9 KiB
Python
Raw Normal View History

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 Tokenpk_ 前缀)。在 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", ""))