ForcePilot/backend/package/yuxi/channel/extensions/ringcentral/config.py
Kris 7a9031ea53 feat(channel): 添加 RingCentral 渠道扩展
新增 RingCentral 渠道扩展,支持在 Yuxi 平台中集成 RingCentral 统一通信平台。

包含以下功能模块:
- sdk: RingCentral SDK 封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- subscription: 事件订阅
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- session: 会话管理
- events: 事件处理
- adaptive_cards: 自适应卡片
- formatting: 格式化
- media: 媒体资源处理
- mentions: @提及
- notes: 笔记功能
- reactions: 表情反应
- tasks: 任务管理
- teams: 团队管理
- types: 类型定义
2026-05-21 11:37:49 +08:00

168 lines
7.2 KiB
Python

from __future__ import annotations
import logging
import os
from yuxi.channel.extensions.ringcentral.types import ResolvedRingCentralAccount
logger = logging.getLogger(__name__)
_DEFAULT_WEBHOOK_PATH = "/api/webhook/ringcentral"
class RingCentralConfigAdapter:
DEFAULT_ACCOUNT_ID = "default"
def list_account_ids(self, config: dict) -> list[str]:
raw = config.get("channels", {}).get("ringcentral", {})
accounts = raw.get("accounts", {})
if accounts:
return list(accounts.keys())
if raw.get("client_id"):
return [self.DEFAULT_ACCOUNT_ID]
return []
async def resolve_account(self, account_id: str) -> dict:
from yuxi.channel.runtime.manager import gateway
config = gateway.global_config if gateway is not None else {}
rc_config = config.get("channels", {}).get("ringcentral", {}) if config else {}
defaults = rc_config.get("accounts", {}).get("default", {})
account_cfg = rc_config.get("accounts", {}).get(account_id, {})
merged = {**rc_config, **defaults, **account_cfg}
resolved = self._build_resolved(account_id, merged)
return {
"account_id": account_id,
"name": resolved.name,
"client_id": resolved.client_id,
"client_secret": resolved.client_secret,
"server_url": resolved.server_url,
"jwt_token": resolved.jwt_token,
"webhook_path": resolved.webhook_path,
"webhook_validation_token": resolved.webhook_validation_token,
"bot_person_id": resolved.bot_person_id,
"subscription_id": resolved.subscription_id,
"dm_policy": resolved.dm_policy,
"dm_enabled": resolved.dm_enabled,
"dm_allow_from": resolved.dm_allow_from,
"group_policy": resolved.group_policy,
"require_mention": resolved.require_mention,
"text_chunk_limit": resolved.text_chunk_limit,
"media_max_mb": resolved.media_max_mb,
"enabled": resolved.enabled,
"config": resolved.config,
"resolved": resolved,
}
def _build_resolved(self, account_id: str, merged: dict) -> ResolvedRingCentralAccount:
client_id = merged.get("client_id", "") or os.getenv("RINGCENTRAL_CLIENT_ID", "")
client_secret = merged.get("client_secret", "") or os.getenv("RINGCENTRAL_CLIENT_SECRET", "")
jwt_token = merged.get("jwt_token", "") or os.getenv("RINGCENTRAL_JWT_TOKEN", "")
return ResolvedRingCentralAccount(
account_id=account_id,
name=merged.get("name", account_id),
client_id=client_id,
client_secret=client_secret,
server_url=merged.get("server_url", "https://platform.ringcentral.com"),
jwt_token=jwt_token,
webhook_path=merged.get("webhook_path", _DEFAULT_WEBHOOK_PATH),
webhook_validation_token=merged.get("webhook_validation_token", ""),
bot_person_id=merged.get("bot_person_id", ""),
subscription_id=merged.get("subscription_id", ""),
dm_policy=merged.get("dm_policy", "pairing"),
dm_enabled=merged.get("dm_enabled", True),
dm_allow_from=merged.get("dm_allow_from", []),
group_policy=merged.get("group_policy", "allowlist"),
require_mention=merged.get("require_mention", True),
text_chunk_limit=merged.get("text_chunk_limit", 1000),
media_max_mb=merged.get("media_max_mb", 20),
enabled=merged.get("enabled", True),
config=merged,
)
def is_configured(self, account: dict) -> bool:
resolved = account.get("resolved")
if isinstance(resolved, ResolvedRingCentralAccount):
return resolved.is_configured
return bool(account.get("client_id") and account.get("jwt_token"))
def is_enabled(self, account: dict) -> bool:
return bool(account.get("enabled", True))
def disabled_reason(self, account: dict) -> str:
if not self.is_configured(account):
return "RingCentral client_id, client_secret and jwt_token are required"
return ""
async def resolve_allow_from(self, config: dict, account_id: str) -> list[str] | None:
account = await self.resolve_account(account_id)
return account.get("dm_allow_from", [])
def describe_account(self, account: dict) -> dict:
return {
"account_id": account.get("account_id", ""),
"name": account.get("name", ""),
"configured": self.is_configured(account),
}
def config_schema(self) -> dict:
return {
"type": "object",
"properties": {
"enabled": {"type": "boolean", "default": True},
"name": {"type": "string"},
"clientId": {"type": "string"},
"clientSecret": {"type": "string"},
"serverUrl": {
"type": "string",
"default": "https://platform.ringcentral.com",
},
"jwtToken": {"type": "string"},
"webhookPath": {"type": "string", "default": _DEFAULT_WEBHOOK_PATH},
"webhookValidationToken": {"type": "string"},
"botPersonId": {"type": "string"},
"subscriptionId": {"type": "string"},
"dmPolicy": {
"type": "string",
"enum": ["open", "pairing", "allowlist", "disabled"],
"default": "pairing",
},
"dmEnabled": {"type": "boolean", "default": True},
"dmAllowFrom": {"type": "array", "items": {"type": "string"}},
"groupPolicy": {
"type": "string",
"enum": ["open", "allowlist", "disabled"],
"default": "allowlist",
},
"requireMention": {"type": "boolean", "default": True},
"textChunkLimit": {"type": "integer", "default": 1000},
"mediaMaxMb": {"type": "integer", "default": 20},
"accounts": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"name": {"type": "string"},
"clientId": {"type": "string"},
"clientSecret": {"type": "string"},
"serverUrl": {"type": "string"},
"jwtToken": {"type": "string"},
"webhookPath": {"type": "string"},
"webhookValidationToken": {"type": "string"},
"botPersonId": {"type": "string"},
"subscriptionId": {"type": "string"},
"dmPolicy": {"type": "string"},
"dmEnabled": {"type": "boolean"},
"dmAllowFrom": {"type": "array", "items": {"type": "string"}},
"groupPolicy": {"type": "string"},
"requireMention": {"type": "boolean"},
},
},
},
},
}