ForcePilot/backend/package/yuxi/channel/extensions/twitch/config.py
Kris 0babb1ca8e feat(channel): 添加 Tlon 渠道扩展
新增 Tlon 渠道扩展,支持在 Yuxi 平台中集成 Tlon/Urbit 去中心化通讯平台。

包含以下功能模块:
- tlon_api: Tlon API 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- sse_client: SSE 客户端
- outbound: 外发消息管理
- send: 消息发送
- security: 安全校验
- auth: 认证管理
- monitor: 渠道状态监控
- status: 会话状态管理
- session: 会话管理
- approval: 审批流程
- channel_mgmt: 频道管理
- channel_ops: 频道操作
- contacts: 联系人管理
- discovery: 服务发现
- doctor: 健康诊断
- expose: 服务暴露
- gallery: 图库管理
- history: 历史记录
- hooks: 钩子管理
- media: 媒体资源处理
- notebook: 笔记本功能
- settings_store: 设置存储
- setup: 初始化设置
- story: 故事功能
- targets: 目标管理
- cite_parser: 引用解析
- utils: 工具函数
- types: 类型定义
2026-05-21 11:55:25 +08:00

119 lines
3.7 KiB
Python

from pydantic import BaseModel
from yuxi.channel.extensions.twitch.types import TwitchRole
class TwitchAccountConfig(BaseModel):
username: str = ""
access_token: str = ""
client_id: str | None = None
channel: str = ""
enabled: bool = True
allow_from: list[str] = []
allowed_roles: list[TwitchRole] = []
require_mention: bool = True
response_prefix: str | None = None
client_secret: str | None = None
refresh_token: str | None = None
expires_in: int | None = None
obtainment_timestamp: float | None = None
class TwitchConfig(BaseModel):
enabled: bool = True
name: str | None = None
default_account: str = "default"
username: str | None = None
access_token: str | None = None
client_id: str | None = None
channel: str | None = None
require_mention: bool = True
allow_from: list[str] = []
allowed_roles: list[TwitchRole] = []
response_prefix: str | None = None
client_secret: str | None = None
refresh_token: str | None = None
accounts: dict[str, TwitchAccountConfig] = {}
block_streaming: bool = True
block_streaming_coalesce_max_delay_ms: int = 500
block_streaming_coalesce_min_chars: int = 100
send_chunk_interval_ms: int = 1500
eventsub_enabled: bool = True
_MERGE_FIELDS = (
"username",
"access_token",
"client_id",
"channel",
"require_mention",
"allow_from",
"allowed_roles",
)
def normalize_twitch_channel(channel: str) -> str:
return channel.strip().removeprefix("#").lower()
def is_account_configured(account: TwitchAccountConfig) -> bool:
return bool(account.username and account.access_token and account.channel)
def get_account_config(config: TwitchConfig, account_id: str = "default") -> TwitchAccountConfig:
account = config.accounts.get(account_id)
if account is None:
if account_id == "default":
return TwitchAccountConfig(
username=config.username or "",
access_token=config.access_token or "",
client_id=config.client_id,
channel=config.channel or "",
require_mention=config.require_mention,
allow_from=list(config.allow_from),
allowed_roles=list(config.allowed_roles),
response_prefix=config.response_prefix,
)
return TwitchAccountConfig()
if account_id == "default":
merged = account.model_copy()
for field_name in _MERGE_FIELDS:
base_val = getattr(config, field_name, None)
is_empty = False
if isinstance(base_val, str) and not base_val:
is_empty = True
elif isinstance(base_val, list) and not base_val:
is_empty = True
if base_val is None or is_empty:
continue
setattr(merged, field_name, base_val)
return merged
return account
def list_account_ids(config: TwitchConfig) -> list[str]:
if config.accounts:
return list(config.accounts.keys())
return ["default"]
def has_configured_state(config: TwitchConfig) -> bool:
if config.accounts:
return any(is_account_configured(acct) for acct in config.accounts.values())
default = TwitchAccountConfig(
username=config.username or "",
access_token=config.access_token or "",
channel=config.channel or "",
)
return is_account_configured(default)
def resolve_default_to(config: TwitchConfig, account_id: str | None = None) -> str | None:
aid = account_id or config.default_account
account = get_account_config(config, aid)
if is_account_configured(account):
return normalize_twitch_channel(account.channel)
return None