新增 Mattermost 渠道完整实现,包含适配器核心、消息处理、交互回调、命令支持、安全校验、多账号管理等功能,支持机器人消息发送、交互按钮、命令注册、投票功能以及配置动态修改等特性。
227 lines
6.8 KiB
Python
227 lines
6.8 KiB
Python
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
|