ForcePilot/backend/package/yuxi/channels/adapters/synologychat/accounts.py
Kris 3ad218e537 feat(synologychat): 新增群晖Chat适配器完整实现
新增群晖Chat渠道适配器的全套实现,包括:
1. 基础适配器与导出接口定义
2. DSM API认证、探测与会话管理
3. 轮询与Webhook两种消息接收方式
4. 消息去重、格式化与规范化处理
5. 多账号支持与权限安全策略
6. 目录用户/群组发现功能
7. 审批配对与流量控制机制
8. 安全审计与配置检查功能
2026-05-12 00:49:16 +08:00

94 lines
2.8 KiB
Python

"""Multi-account support for Synology Chat channel.
Manages multiple DSM connections with independent configurations,
credential management, and default account routing.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
DEFAULT_ACCOUNT_ID = "default"
_BASE_FIELD_KEYS = (
"dsm_url",
"username",
"password",
"password_file",
"verify_ssl",
"name",
)
@dataclass
class AccountConfig:
id: str
dsm_url: str = ""
username: str = ""
password: str = ""
password_file: str = ""
verify_ssl: bool = True
name: str = ""
extra: dict[str, Any] = field(default_factory=dict)
def list_account_ids(config: dict[str, Any]) -> list[str]:
accounts = config.get("accounts", {})
if not isinstance(accounts, dict) or not accounts:
return [DEFAULT_ACCOUNT_ID]
return sorted(accounts.keys())
def resolve_account(config: dict[str, Any], account_id: str = DEFAULT_ACCOUNT_ID) -> AccountConfig:
"""Resolve configuration for a specific account with priority chain:
account > base > env.
For named accounts (non-default), base fields that are NOT explicitly
overridden in the account config are cleared to prevent unwanted
inheritance of sensitive values.
"""
base = {
"dsm_url": config.get("dsm_url", ""),
"username": config.get("username", ""),
"password": config.get("password", ""),
"password_file": config.get("password_file", ""),
"verify_ssl": config.get("verify_ssl", True),
"name": config.get("name", "synologychat"),
}
accounts = config.get("accounts", {})
account_cfg = accounts.get(account_id, {}) if isinstance(accounts, dict) else {}
if account_id != DEFAULT_ACCOUNT_ID:
merged = {}
for k in _BASE_FIELD_KEYS:
if k in account_cfg:
merged[k] = account_cfg[k]
else:
merged[k] = "" if k not in ("verify_ssl", "name") else base[k]
if "name" not in account_cfg:
merged["name"] = merged["name"] or f"synologychat-{account_id}"
else:
merged = {k: account_cfg.get(k, base[k]) for k in base}
return AccountConfig(
id=account_id,
dsm_url=merged["dsm_url"],
username=merged["username"],
password=merged["password"],
password_file=merged["password_file"],
verify_ssl=merged["verify_ssl"],
name=merged["name"],
extra={k: v for k, v in account_cfg.items() if k not in base},
)
def get_default_account_id(config: dict[str, Any]) -> str:
return config.get("default_account", DEFAULT_ACCOUNT_ID)
def has_multi_account(config: dict[str, Any]) -> bool:
accounts = config.get("accounts", {})
return isinstance(accounts, dict) and len(accounts) > 0