from __future__ import annotations import logging from typing import Any logger = logging.getLogger(__name__) WIZARD_STEPS = [ { "step": 0, "title": "Bot Installation Guide", "description": ( "Before configuring the adapter, install a Talk bot on your Nextcloud server.\n\n" "Run this command as an administrator on your Nextcloud server:\n\n" "```bash\n" "occ talk:bot:install \n" "```\n\n" "Example:\n```bash\n" 'occ talk:bot:install "ForcePilot Bot" my_secret_key https://your-server.example.com\n' "```\n\n" "After installation, use the bot username and the secret as app_password in Step 2." ), "field": None, "required": False, }, { "step": 1, "title": "Nextcloud Instance URL", "description": "Enter the base URL of your Nextcloud server (e.g., https://cloud.example.com).", "field": "server_url", "required": True, }, { "step": 2, "title": "Bot Credentials", "description": ( "Enter the bot username and application password. " "Create an app password in Nextcloud at Settings > Security.\n\n" "Environment variables (alternative to config):\n" "- `NEXTCLOUD_TALK_BOT_USER` for bot_user\n" "- `NEXTCLOUD_TALK_APP_PASSWORD` or `NEXTCLOUD_TALK_API_PASSWORD` for app_password" ), "fields": ["bot_user", "app_password"], "required": True, "preferredEnvVar": { "app_password": "NEXTCLOUD_TALK_API_PASSWORD", }, }, { "step": 3, "title": "API Credentials (Optional)", "description": "Optionally provide separate API credentials for room/peer queries. Leave blank to reuse bot credentials.", "fields": ["api_user", "api_password"], "required": False, }, { "step": 4, "title": "DM Policy", "description": "Choose how the bot handles direct messages: open (anyone), pairing (require admin approval), allowlist (specified users only), or disabled.", "field": "dm_policy", "required": True, "options": [ {"value": "open", "label": "Open - anyone can message the bot"}, {"value": "pairing", "label": "Pairing - users must be approved by admin"}, {"value": "allowlist", "label": "Allowlist - only specified users"}, {"value": "disabled", "label": "Disabled - no DMs allowed"}, ], }, { "step": 5, "title": "Allow From", "description": "Configure allowed user IDs and room tokens. Format: nc:{user_id} for users.", "field": "allow_from", "required": False, }, ] def generate_wizard_steps() -> list[dict[str, Any]]: return WIZARD_STEPS def validate_step(step_num: int, data: dict[str, Any]) -> dict[str, Any]: step = next((s for s in WIZARD_STEPS if s["step"] == step_num), None) if not step: return {"valid": False, "error": f"Unknown step: {step_num}"} errors: list[str] = [] if step_num == 1: server_url = data.get("server_url", "").strip() if step["required"] and not server_url: errors.append("Server URL is required") elif server_url and not (server_url.startswith("https://") or server_url.startswith("http://")): errors.append("Server URL must start with https:// or http://") elif step_num == 2: bot_user = data.get("bot_user", "").strip() app_password = data.get("app_password", "").strip() if not bot_user: errors.append("Bot username is required") if not app_password: errors.append("App password is required") elif step_num == 3: pass elif step_num == 4: dm_policy = data.get("dm_policy", "").strip() valid_policies = {"open", "pairing", "allowlist", "disabled"} if dm_policy and dm_policy not in valid_policies: errors.append(f"DM policy must be one of: {', '.join(sorted(valid_policies))}") elif step_num == 5: allow_from = data.get("allow_from", []) if isinstance(allow_from, list): for entry in allow_from: if not isinstance(entry, str) or not entry.startswith("nc:"): errors.append(f"Each allow_from entry must start with 'nc:', got: {entry}") if errors: return {"valid": False, "errors": errors} return {"valid": True} def build_config_from_wizard(wizard_data: dict[str, Any]) -> dict[str, Any]: config: dict[str, Any] = { "server_url": wizard_data.get("server_url", "").rstrip("/"), "bot_user": wizard_data.get("bot_user", ""), "app_password": wizard_data.get("app_password", ""), "dm_policy": wizard_data.get("dm_policy", "pairing"), "try_websocket": True, } api_user = wizard_data.get("api_user", "") if api_user: config["api_user"] = api_user api_password = wizard_data.get("api_password", "") if api_password: config["api_password"] = api_password allow_from = wizard_data.get("allow_from", []) if allow_from: config["allow_from"] = allow_from return config