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)}