新增群晖Chat渠道适配器的全套实现,包括: 1. 基础适配器与导出接口定义 2. DSM API认证、探测与会话管理 3. 轮询与Webhook两种消息接收方式 4. 消息去重、格式化与规范化处理 5. 多账号支持与权限安全策略 6. 目录用户/群组发现功能 7. 审批配对与流量控制机制 8. 安全审计与配置检查功能
94 lines
2.8 KiB
Python
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
|