from __future__ import annotations import os import sys from typing import Any def is_mattermost_configured(account: dict) -> bool: server_url = account.get("server_url", "") bot_token = account.get("bot_token", "") return bool(server_url and bot_token) def normalize_mattermost_base_url(url: str) -> str | None: if not url: return None url = url.strip().rstrip("/") parsed = url.split("/api/v4")[0] if "/api/v4" in url else url if parsed.startswith("http://") or parsed.startswith("https://"): return parsed return None def resolve_configured(account: dict) -> bool: return is_mattermost_configured(account) def get_configured_score(account: dict) -> int: score = 0 if account.get("server_url"): score += 1 if account.get("bot_token"): score += 1 return score def check_env_shortcut_available(config: dict) -> bool: token = os.getenv("MATTERMOST_BOT_TOKEN", "") url = os.getenv("MATTERMOST_SERVER_URL", "") or os.getenv("MATTERMOST_URL", "") has_config_token = bool(config.get("bot_token", "")) has_config_url = bool(config.get("server_url", "")) return bool(token and url) and not (has_config_token and has_config_url) def get_intro_note() -> str: return ( "要连接 Mattermost,你需要:\n" "1. 一个 Mattermost 服务器实例\n" "2. 一个 Bot Account 的 Personal Access Token\n\n" "创建 Bot Account 步骤:\n" "- 进入 System Console → Integrations → Bot Accounts\n" "- 创建 Bot Account 并获取 Token" ) def should_show_intro_note(config: dict) -> bool: return not is_mattermost_configured(config) def build_status_info(account: dict) -> dict: configured = is_mattermost_configured(account) return { "channelLabel": "Mattermost", "configured": configured, "configuredLabel": "configured" if configured else None, "unconfiguredLabel": "needs token + url" if not configured else None, "score": get_configured_score(account), } def inspect_credentials(config: dict) -> dict: return { "accountConfigured": is_mattermost_configured(config), "hasConfiguredValue": bool(config.get("bot_token", "")), "hasEnvValue": bool(os.getenv("MATTERMOST_BOT_TOKEN", "")), } def get_keep_prompt() -> str: return "Mattermost bot token already configured. Keep it?" def get_env_prompt() -> str: return "Mattermost credentials found in environment variables. Use them?" def disable_mattermost(config: dict) -> dict: return {**config, "mattermost": {"enabled": False}} def interactive_setup() -> dict[str, Any]: """CLI 交互式 Mattermost 配置向导。 返回配置 dict,可存入 config。 """ import os print("\n=== Mattermost Bot 配置向导 ===\n") env_url = os.getenv("MATTERMOST_SERVER_URL", "") or os.getenv("MATTERMOST_URL", "") env_token = os.getenv("MATTERMOST_BOT_TOKEN", "") if env_token or env_url: print("[检测到环境变量配置]") print(f" MATTERMOST_SERVER_URL: {'已设置' if env_url else '未设置'}") print(f" MATTERMOST_BOT_TOKEN: {'已设置' if env_token else '未设置'}") use_env = _prompt_yn("是否使用环境变量配置?") if use_env: print("\n✅ 已从环境变量加载配置") return { "server_url": env_url, "bot_token": env_token, "token_source": "env", } print(get_intro_note() + "\n") server_url = _prompt( "Mattermost 服务器 URL", default=env_url or "https://mattermost.example.com", validate_fn=normalize_mattermost_base_url, normalize_fn=normalize_mattermost_base_url, ) bot_token = _prompt( "Bot Token (Personal Access Token)", default=env_token or "", secret=True, ) dm_policy = _prompt("DM 安全策略", default="open", choices=["open", "allowlist", "pairing", "disabled"]) group_policy = _prompt("群组安全策略", default="open", choices=["open", "allowlist", "disabled"]) print("\n--- 配置摘要 ---") print(f" Server URL: {server_url}") print(f" Bot Token: {'[已设置]' if bot_token else '[未设置]'}") print(f" DM Policy: {dm_policy}") print(f" Group Policy: {group_policy}") print() confirm = _prompt_yn("确认配置?") if not confirm: print("已取消配置") return {} return { "server_url": server_url, "bot_token": bot_token, "dm_policy": dm_policy, "group_policy": group_policy, } def _prompt( label: str, default: str = "", choices: list[str] | None = None, secret: bool = False, validate_fn: Any = None, normalize_fn: Any = None, ) -> str: if choices: hint = f" [{', '.join(choices)}]" default_hint = f" (默认: {default})" if default else "" prompt = f"{label}{hint}{default_hint}: " else: default_hint = f" (默认: {default})" if default else "" prompt = f"{label}{default_hint}: " try: user_input = input(prompt).strip() except (KeyboardInterrupt, EOFError): print() sys.exit(0) if not user_input: user_input = default if normalize_fn: normalized = normalize_fn(user_input) if normalized is not None: user_input = normalized if validate_fn: validated = validate_fn(user_input) if validated is None: print(f" 输入无效: {user_input},请重试") return _prompt(label, default, choices, secret, validate_fn, normalize_fn) user_input = validated return user_input def _prompt_yn(question: str) -> bool: try: answer = input(f"{question} (y/n): ").strip().lower() return answer in ("y", "yes", "是") except (KeyboardInterrupt, EOFError): print() return False def validate_config(config: dict) -> list[str]: """验证配置是否完整并有效。返回错误信息列表。""" errors = [] server_url = config.get("server_url", "") if not server_url: errors.append("server_url is required") elif normalize_mattermost_base_url(server_url) is None: errors.append(f"Invalid server_url format: {server_url}") bot_token = config.get("bot_token", "") if not bot_token: errors.append("bot_token is required") dm_policy = config.get("dm_policy", "") if dm_policy and dm_policy not in ("open", "allowlist", "pairing", "disabled"): errors.append(f"Invalid dm_policy: {dm_policy}") group_policy = config.get("group_policy", "") if group_policy and group_policy not in ("open", "allowlist", "disabled"): errors.append(f"Invalid group_policy: {group_policy}") return errors