160 lines
6.9 KiB
Python
160 lines
6.9 KiB
Python
|
|
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", ""))
|