实现了完整的Nextcloud Talk聊天适配器,包含以下核心功能: 1. 基础客户端通信与认证 2. 会话路由与聊天类型解析 3. 消息分块与格式转换 4. 用户配对与权限校验 5. 轮询式消息监听 6. 配置验证与诊断工具 7. 安全策略与去重缓存 8. 指标统计与状态快照 新增配套的配对用户缓存文件与模块导出入口。
188 lines
6.7 KiB
Python
188 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import Any
|
|
|
|
from .probe import probe_capabilities, resolve_api_version
|
|
from .security import collect_security_warnings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def run_diagnostics(
|
|
client,
|
|
config: dict[str, Any],
|
|
adapter=None,
|
|
) -> list[dict[str, Any]]:
|
|
results: list[dict[str, Any]] = []
|
|
|
|
results.append(_check_credentials(config))
|
|
results.append(_check_server_url(config))
|
|
results.append(_check_server_url_ssl(config))
|
|
results.append(_check_environment_variables())
|
|
|
|
if client and client._session:
|
|
results.append(await _check_api_connectivity(client, config))
|
|
|
|
results.extend(_check_security_policies(config))
|
|
results.append(_check_room_configs(config))
|
|
results.append(_check_dm_configs(config))
|
|
results.append(_check_multi_account(config))
|
|
results.append(_check_webhook_config(config))
|
|
|
|
if adapter:
|
|
results.append(_check_runtime_status(adapter))
|
|
|
|
return results
|
|
|
|
|
|
def _check_credentials(config: dict[str, Any]) -> dict[str, Any]:
|
|
bot_user = config.get("bot_user", config.get("botUser", ""))
|
|
app_password = config.get("app_password", config.get("appPassword", ""))
|
|
if not bot_user or not app_password:
|
|
return {"check": "credentials", "status": "fail", "detail": "bot_user or app_password is missing"}
|
|
return {"check": "credentials", "status": "pass", "detail": f"Bot user: {bot_user}"}
|
|
|
|
|
|
def _check_server_url(config: dict[str, Any]) -> dict[str, Any]:
|
|
server_url = config.get("server_url", "").rstrip("/")
|
|
if not server_url:
|
|
return {"check": "server_url", "status": "fail", "detail": "server_url is missing"}
|
|
if not (server_url.startswith("https://") or server_url.startswith("http://")):
|
|
return {"check": "server_url", "status": "fail", "detail": f"Invalid URL format: {server_url}"}
|
|
return {"check": "server_url", "status": "pass", "detail": server_url}
|
|
|
|
|
|
async def _check_api_connectivity(client, config: dict[str, Any]) -> dict[str, Any]:
|
|
try:
|
|
server_url = client.server_url
|
|
caps = await probe_capabilities(client.session, server_url)
|
|
if caps:
|
|
version = caps.get("version", "unknown")
|
|
api_version = resolve_api_version(caps)
|
|
return {
|
|
"check": "api_connectivity",
|
|
"status": "pass",
|
|
"detail": f"Talk version: {version}, API: {api_version}",
|
|
}
|
|
return {"check": "api_connectivity", "status": "fail", "detail": "No Talk capabilities found"}
|
|
except Exception as e:
|
|
return {"check": "api_connectivity", "status": "fail", "detail": str(e)}
|
|
|
|
|
|
def _check_security_policies(config: dict[str, Any]) -> list[dict[str, Any]]:
|
|
warnings = collect_security_warnings(config)
|
|
if warnings:
|
|
return [
|
|
{
|
|
"check": "security_policies",
|
|
"status": "warn",
|
|
"detail": f"{len(warnings)} warning(s)",
|
|
"warnings": warnings,
|
|
}
|
|
]
|
|
return [{"check": "security_policies", "status": "pass", "detail": "No warnings"}]
|
|
|
|
|
|
def _check_room_configs(config: dict[str, Any]) -> dict[str, Any]:
|
|
rooms = config.get("rooms", {})
|
|
if not rooms:
|
|
return {"check": "room_configs", "status": "info", "detail": "No per-room configuration"}
|
|
disabled_rooms = [t for t, c in rooms.items() if not c.get("enabled", c.get("enabled_", True))]
|
|
return {
|
|
"check": "room_configs",
|
|
"status": "info",
|
|
"detail": f"{len(rooms)} room(s) configured, {len(disabled_rooms)} disabled",
|
|
}
|
|
|
|
|
|
def _check_dm_configs(config: dict[str, Any]) -> dict[str, Any]:
|
|
dms = config.get("dms", {})
|
|
if not dms:
|
|
return {"check": "dm_configs", "status": "info", "detail": "No per-DM configuration"}
|
|
disabled_dms = [u for u, c in dms.items() if not c.get("enabled", True)]
|
|
return {
|
|
"check": "dm_configs",
|
|
"status": "info",
|
|
"detail": f"{len(dms)} DM config(s), {len(disabled_dms)} disabled",
|
|
}
|
|
|
|
|
|
def _check_server_url_ssl(config: dict[str, Any]) -> dict[str, Any]:
|
|
server_url = config.get("server_url", "").rstrip("/")
|
|
if server_url.startswith("https://"):
|
|
return {"check": "server_url_ssl", "status": "pass", "detail": "HTTPS enabled"}
|
|
if server_url.startswith("http://"):
|
|
return {"check": "server_url_ssl", "status": "warn", "detail": "HTTP (non-SSL) in use"}
|
|
return {"check": "server_url_ssl", "status": "info", "detail": "No server URL configured"}
|
|
|
|
|
|
def _check_environment_variables() -> dict[str, Any]:
|
|
found: list[str] = []
|
|
missing: list[str] = []
|
|
|
|
for var in (
|
|
"NEXTCLOUD_TALK_BOT_USER",
|
|
"NEXTCLOUD_TALK_BOT_SECRET",
|
|
"NEXTCLOUD_TALK_APP_PASSWORD",
|
|
"NEXTCLOUD_TALK_API_PASSWORD",
|
|
"OPENCLAW_DEBUG_NEXTCLOUD_TALK_ACCOUNTS",
|
|
):
|
|
if os.getenv(var):
|
|
found.append(var)
|
|
else:
|
|
missing.append(var)
|
|
|
|
return {
|
|
"check": "environment_variables",
|
|
"status": "info",
|
|
"detail": f"{len(found)} set, {len(missing)} not set",
|
|
"set": found,
|
|
"not_set": missing,
|
|
}
|
|
|
|
|
|
def _check_multi_account(config: dict[str, Any]) -> dict[str, Any]:
|
|
accounts = config.get("accounts", [])
|
|
if isinstance(accounts, list) and accounts:
|
|
return {
|
|
"check": "multi_account",
|
|
"status": "info",
|
|
"detail": f"{len(accounts)} accounts configured",
|
|
"accounts": [
|
|
{"id": a.get("id", "?"), "label": a.get("label", "?")}
|
|
for a in accounts if isinstance(a, dict)
|
|
],
|
|
}
|
|
return {"check": "multi_account", "status": "info", "detail": "Single account mode (default)"}
|
|
|
|
|
|
def _check_webhook_config(config: dict[str, Any]) -> dict[str, Any]:
|
|
webhook = config.get("webhook", {})
|
|
if not webhook:
|
|
return {"check": "webhook", "status": "info", "detail": "Webhook server not configured"}
|
|
enabled = webhook.get("enabled", False)
|
|
if enabled:
|
|
return {
|
|
"check": "webhook",
|
|
"status": "info",
|
|
"detail": f"Enabled (port={webhook.get('port', '?')}, path={webhook.get('path', '?')})",
|
|
}
|
|
return {"check": "webhook", "status": "info", "detail": "Webhook server disabled"}
|
|
|
|
|
|
def _check_runtime_status(adapter) -> dict[str, Any]:
|
|
try:
|
|
stats = adapter.get_activity_stats()
|
|
metrics = adapter._metrics.snapshot() if hasattr(adapter, "_metrics") else {}
|
|
return {
|
|
"check": "runtime_status",
|
|
"status": "pass",
|
|
"detail": f"Connected: {adapter.status == 'connected'}",
|
|
"activity": stats,
|
|
"metrics": metrics,
|
|
}
|
|
except Exception as e:
|
|
return {"check": "runtime_status", "status": "warn", "detail": str(e)}
|