新增 RocketChat 渠道扩展,支持在 Yuxi 平台中集成 RocketChat 团队协作平台。 包含以下功能模块: - client: RocketChat API 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - websocket: WebSocket 实时连接 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - dedup: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - session: 会话管理 - gating: 门控管理 - threading: 线程管理 - reactions: 表情反应 - types: 类型定义
191 lines
7.2 KiB
Python
191 lines
7.2 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class RocketChatConfigAdapter:
|
|
def __init__(self):
|
|
self._config: dict = {}
|
|
|
|
def list_account_ids(self, config: dict) -> list[str]:
|
|
self._config = config
|
|
accounts = config.get("accounts", {})
|
|
if not accounts:
|
|
return ["default"] if self._env_token_exists() else []
|
|
return list(accounts.keys())
|
|
|
|
def default_account_id(self, config: dict) -> str:
|
|
self._config = config
|
|
return config.get("default_account", "default")
|
|
|
|
async def resolve_account(self, account_id: str) -> dict:
|
|
raw = self._load_config(account_id)
|
|
return self._build_account(account_id, raw)
|
|
|
|
def _load_config(self, account_id: str) -> dict:
|
|
accounts = self._config.get("accounts", {})
|
|
return accounts.get(account_id, {})
|
|
|
|
def _build_account(self, account_id: str, raw: dict) -> dict:
|
|
env_token = os.environ.get("ROCKETCHAT_AUTH_TOKEN", "")
|
|
env_user_id = os.environ.get("ROCKETCHAT_USER_ID", "")
|
|
env_url = os.environ.get("ROCKETCHAT_URL", "")
|
|
|
|
account = {
|
|
"account_id": account_id,
|
|
"name": raw.get("name", account_id),
|
|
"enabled": raw.get("enabled", True),
|
|
"auth_token": env_token or raw.get("auth_token", ""),
|
|
"user_id": env_user_id or raw.get("user_id", ""),
|
|
"server_url": env_url or raw.get("server_url", ""),
|
|
"webhook_secret": raw.get("webhook_secret", ""),
|
|
"chatmode": raw.get("chatmode", "oncall"),
|
|
"onchar_prefixes": raw.get("onchar_prefixes", [">", "!"]),
|
|
"require_mention": raw.get("require_mention", True),
|
|
"dm_policy": raw.get("dm_policy", "pairing"),
|
|
"allow_from": raw.get("allow_from", []),
|
|
"group_policy": raw.get("group_policy", "allowlist"),
|
|
"group_allow_from": raw.get("group_allow_from", []),
|
|
"channel_policy": raw.get("channel_policy", "allowlist"),
|
|
"channel_allow_from": raw.get("channel_allow_from", []),
|
|
"text_chunk_limit": raw.get("text_chunk_limit", 4000),
|
|
"reply_to_mode": raw.get("reply_to_mode", "off"),
|
|
"block_streaming": raw.get("block_streaming", True),
|
|
"block_streaming_coalesce": raw.get(
|
|
"block_streaming_coalesce",
|
|
{"min_chars": 1500, "idle_ms": 1000},
|
|
),
|
|
}
|
|
|
|
coalesce = account["block_streaming_coalesce"]
|
|
account["block_streaming_coalesce_min_chars"] = coalesce.get("min_chars", 1500)
|
|
account["block_streaming_coalesce_idle_ms"] = coalesce.get("idle_ms", 1000)
|
|
|
|
return account
|
|
|
|
def is_configured(self, account: dict) -> bool:
|
|
return bool(account.get("auth_token") and account.get("user_id") and account.get("server_url"))
|
|
|
|
def is_enabled(self, account: dict) -> bool:
|
|
return account.get("enabled", True)
|
|
|
|
def disabled_reason(self, account: dict) -> str:
|
|
if not account.get("enabled", True):
|
|
return "Account disabled"
|
|
return ""
|
|
|
|
def describe_account(self, account: dict) -> dict:
|
|
return {
|
|
"account_id": account.get("account_id", ""),
|
|
"name": account.get("name", ""),
|
|
"server_url": account.get("server_url", ""),
|
|
"dm_policy": account.get("dm_policy", "pairing"),
|
|
"configured": self.is_configured(account),
|
|
"enabled": self.is_enabled(account),
|
|
}
|
|
|
|
def resolve_allow_from(self, config: dict, account_id: str | None = None) -> list[str] | None:
|
|
self._config = config
|
|
aid = account_id or "default"
|
|
accounts = config.get("accounts", {})
|
|
acct = accounts.get(aid, {}) if accounts else {}
|
|
allow = acct.get("allow_from", config.get("allow_from", []))
|
|
if not allow:
|
|
return None
|
|
return [str(e).strip() for e in allow]
|
|
|
|
def config_schema(self) -> dict:
|
|
return {
|
|
"$schema": "https://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"title": "Rocket.Chat 渠道配置",
|
|
"properties": {
|
|
"server_url": {
|
|
"type": "string",
|
|
"title": "Server URL",
|
|
"description": "Rocket.Chat 服务器 URL (如 https://chat.example.com)",
|
|
},
|
|
"auth_token": {
|
|
"type": "string",
|
|
"title": "Auth Token",
|
|
"description": "Personal Access Token (X-Auth-Token)",
|
|
"x-ui-password": True,
|
|
},
|
|
"user_id": {
|
|
"type": "string",
|
|
"title": "User ID",
|
|
"description": "Bot 用户 ID (X-User-Id)",
|
|
},
|
|
"dm_policy": {
|
|
"type": "string",
|
|
"title": "DM 策略",
|
|
"enum": ["pairing", "allowlist", "open", "disabled"],
|
|
"default": "pairing",
|
|
},
|
|
"group_policy": {
|
|
"type": "string",
|
|
"title": "群组策略",
|
|
"enum": ["open", "allowlist", "disabled"],
|
|
"default": "allowlist",
|
|
},
|
|
"channel_policy": {
|
|
"type": "string",
|
|
"title": "公开频道策略",
|
|
"enum": ["open", "allowlist", "disabled"],
|
|
"default": "allowlist",
|
|
},
|
|
"chatmode": {
|
|
"type": "string",
|
|
"title": "聊天模式",
|
|
"enum": ["oncall", "onmessage", "onchar"],
|
|
"default": "oncall",
|
|
},
|
|
"require_mention": {
|
|
"type": "boolean",
|
|
"title": "需要 @提及",
|
|
"default": True,
|
|
},
|
|
"text_chunk_limit": {
|
|
"type": "integer",
|
|
"title": "文本分块上限",
|
|
"default": 4000,
|
|
},
|
|
"block_streaming": {
|
|
"type": "boolean",
|
|
"title": "启用块流式",
|
|
"default": True,
|
|
},
|
|
"webhook_secret": {
|
|
"type": "string",
|
|
"title": "Webhook Secret",
|
|
"description": "Incoming Webhook 签名验证密钥",
|
|
"x-ui-password": True,
|
|
},
|
|
},
|
|
}
|
|
|
|
def _env_token_exists(self) -> bool:
|
|
return bool(os.environ.get("ROCKETCHAT_AUTH_TOKEN") and os.environ.get("ROCKETCHAT_USER_ID"))
|
|
|
|
|
|
def normalize_rocketchat_server_url(url: str) -> str:
|
|
url = url.rstrip("/")
|
|
if url.endswith("/api/v1"):
|
|
url = url[:-7]
|
|
return url
|
|
|
|
|
|
def validate_rocketchat_config(account: dict) -> list[str]:
|
|
errors: list[str] = []
|
|
|
|
dm_policy = account.get("dm_policy", "pairing")
|
|
if dm_policy == "open":
|
|
allow_from = account.get("allow_from", [])
|
|
if "*" not in [str(e).strip() for e in allow_from]:
|
|
errors.append("dm_policy='open' requires allow_from to include '*' (open to all senders)")
|
|
|
|
return errors
|