ForcePilot/backend/package/yuxi/channels/adapters/mattermost/setup.py
Kris 002d601d1b feat(mattermost): 实现完整的 Mattermost 适配器模块
新增 Mattermost 渠道完整实现,包含适配器核心、消息处理、交互回调、命令支持、安全校验、多账号管理等功能,支持机器人消息发送、交互按钮、命令注册、投票功能以及配置动态修改等特性。
2026-05-12 00:46:12 +08:00

227 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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