ForcePilot/backend/package/yuxi/channels/adapters/nextcloudtalk/accounts.py
Kris b018ad25da feat(backend): add Nextcloud Talk channel adapter implementation
实现了完整的Nextcloud Talk聊天适配器,包含以下核心功能:
1. 基础客户端通信与认证
2. 会话路由与聊天类型解析
3. 消息分块与格式转换
4. 用户配对与权限校验
5. 轮询式消息监听
6. 配置验证与诊断工具
7. 安全策略与去重缓存
8. 指标统计与状态快照

新增配套的配对用户缓存文件与模块导出入口。
2026-05-12 00:47:06 +08:00

83 lines
2.5 KiB
Python

from __future__ import annotations
import copy
import logging
from typing import Any
logger = logging.getLogger(__name__)
DEFAULT_ACCOUNT_ID = "default"
class NcAccountConfig:
def __init__(self, account_id: str, config: dict[str, Any] | None = None):
self.account_id = account_id
self.config = config or {}
@property
def server_url(self) -> str:
return self.config.get("server_url", self.config.get("baseUrl", ""))
@property
def bot_user(self) -> str:
return self.config.get("bot_user", self.config.get("botUser", ""))
@property
def app_password(self) -> str:
return self.config.get("app_password", self.config.get("appPassword", ""))
@property
def display_name(self) -> str:
return self.config.get("display_name", self.config.get("displayName", "ForcePilot Bot"))
@property
def dm_policy(self) -> str:
return self.config.get("dm_policy", self.config.get("dmPolicy", "pairing"))
@property
def group_policy(self) -> str:
return self.config.get("group_policy", self.config.get("groupPolicy", "allowlist"))
def resolve_nc_account(account_id: str, channel_config: dict[str, Any]) -> NcAccountConfig | None:
accounts = channel_config.get("accounts", {})
if account_id == DEFAULT_ACCOUNT_ID and not accounts:
return NcAccountConfig(DEFAULT_ACCOUNT_ID, channel_config)
account_cfg = accounts.get(account_id)
if not account_cfg:
return None
merged = resolve_merged_config(account_id, channel_config)
return NcAccountConfig(account_id, merged)
def resolve_merged_config(account_id: str, channel_config: dict[str, Any]) -> dict[str, Any]:
base = {k: v for k, v in channel_config.items() if k not in ("accounts",)}
accounts = channel_config.get("accounts", {})
account_cfg = accounts.get(account_id, {})
merged = copy.deepcopy(base)
_deep_merge(merged, account_cfg)
return merged
def _deep_merge(base: dict, override: dict) -> None:
for key, value in override.items():
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
_deep_merge(base[key], value)
else:
base[key] = value
def list_nc_accounts(channel_config: dict[str, Any]) -> list[str]:
accounts = channel_config.get("accounts", {})
if not accounts:
return [DEFAULT_ACCOUNT_ID]
return [DEFAULT_ACCOUNT_ID] + [k for k in accounts if k != DEFAULT_ACCOUNT_ID]
def resolve_default_account_id(channel_config: dict[str, Any]) -> str:
return DEFAULT_ACCOUNT_ID