ForcePilot/backend/package/yuxi/channel/extensions/douyin/config.py
Kris 4552bde837 feat(douyin): 新增抖音渠道插件,支持私信收发与相关能力
该提交实现了完整的抖音开放平台IM渠道插件,包含:
1. 基础配置与账号管理能力
2. 消息去重、安全策略校验
3. 流式回复、多媒体消息发送
4. Webhook回调处理与事件解析
5. 配对认证与流量限流机制
2026-05-21 10:45:37 +08:00

135 lines
5.2 KiB
Python

import os
from yuxi.channel.extensions.douyin.types import DouyinAccount
class DouyinConfig:
ENV_MAP = {
"client_key": "DOUYIN_CLIENT_KEY",
"client_secret": "DOUYIN_CLIENT_SECRET",
"dm_policy": "DOUYIN_DM_POLICY",
"streaming": "DOUYIN_STREAMING",
"remove_markdown": "DOUYIN_REMOVE_MD",
"welcome_text": "DOUYIN_WELCOME_TEXT",
}
def __init__(self, config: dict | None = None):
self._config = config or {}
def list_account_ids(self, config: dict | None = None) -> list[str]:
cfg = config or self._config
accounts = cfg.get("accounts", [])
if accounts:
return [a.get("account_id", "default") for a in accounts]
if self._resolve_client_key(cfg):
return ["default"]
return []
def resolve_account(self, account_id: str = "default", config: dict | None = None) -> DouyinAccount:
cfg = config or self._config
accounts = cfg.get("accounts", [])
account_data = {}
for a in accounts:
if a.get("account_id") == account_id:
account_data = a
break
return DouyinAccount(
account_id=account_id,
client_key=account_data.get("client_key") or self._resolve_client_key(cfg) or "",
client_secret=account_data.get("client_secret") or self._env_or_config("client_secret", cfg) or "",
dm_policy=account_data.get("dm_policy") or self._env_or_config("dm_policy", cfg) or "open",
streaming=self._resolve_bool(account_data, "streaming", cfg, True),
remove_markdown=self._resolve_bool(account_data, "remove_markdown", cfg, True),
welcome_text=account_data.get("welcome_text")
or self._env_or_config("welcome_text", cfg)
or "你好!有什么可以帮助你的?",
)
def is_configured(self, account: dict | None = None) -> bool:
if account:
return bool(account.get("client_key") and account.get("client_secret"))
return bool(self._resolve_client_key(self._config) and self._env_or_config("client_secret", self._config))
def is_enabled(self, account: dict | None = None, config: dict | None = None) -> bool:
cfg = config or self._config
if account:
return account.get("enabled", True)
return cfg.get("enabled", True)
def disabled_reason(self, account: dict | None = None) -> str:
if account and account.get("enabled") is False:
return "Account explicitly disabled"
return ""
def describe_account(self, account: dict | None = None) -> dict:
if account:
return {"account_id": account.get("account_id", "default")}
return {"account_id": "default"}
def _resolve_client_key(self, config: dict | None = None) -> str | None:
return self._env_or_config("client_key", config)
def _env_or_config(self, key: str, config: dict | None = None) -> str | None:
cfg = config or self._config
if key in cfg:
val = cfg[key]
if val:
return str(val)
env_key = self.ENV_MAP.get(key, "")
if env_key:
val = os.getenv(env_key)
if val:
return val
return None
def _resolve_bool(self, account_data: dict, key: str, config: dict | None, default: bool) -> bool:
if key in account_data:
val = account_data[key]
if isinstance(val, bool):
return val
if isinstance(val, str):
return val.lower() != "false"
env_val = self._env_or_config(key, config)
if env_val is not None:
return env_val.lower() != "false"
return default
def config_schema(self) -> dict:
return {
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"title": "抖音渠道配置",
"properties": {
"client_key": {
"type": "string",
"title": "Client Key",
"description": "抖音开放平台小程序应用的 Client Key",
},
"client_secret": {
"type": "string",
"title": "Client Secret",
"x-ui-password": True,
"description": "抖音开放平台小程序应用的 Client Secret",
},
"dm_policy": {
"type": "string",
"enum": ["open", "pairing", "allowlist", "disabled"],
"default": "open",
"title": "DM 策略",
},
"streaming": {
"type": "boolean",
"default": True,
"title": "启用流式输出",
},
"remove_markdown": {
"type": "boolean",
"default": True,
"title": "移除 Markdown 格式",
"description": "自动移除 AI 回复中的 Markdown 符号和外部 URL 链接",
},
},
"required": ["client_key", "client_secret"],
}