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