新增 Mattermost 渠道完整实现,包含适配器核心、消息处理、交互回调、命令支持、安全校验、多账号管理等功能,支持机器人消息发送、交互按钮、命令注册、投票功能以及配置动态修改等特性。
128 lines
3.4 KiB
Python
128 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import re
|
|
from typing import Any
|
|
|
|
|
|
def sanitize_action_id(action_id: str) -> str:
|
|
return re.sub(r"[-_]", "", action_id)
|
|
|
|
|
|
def build_button_attachment(
|
|
title: str,
|
|
text: str,
|
|
buttons: list[dict[str, str]],
|
|
fallback: str = "",
|
|
color: str = "",
|
|
callback_id: str = "",
|
|
) -> dict[str, Any]:
|
|
"""构建 Mattermost Interactive Button attachment。"""
|
|
actions = []
|
|
for i, btn in enumerate(buttons):
|
|
btn_id = sanitize_action_id(btn.get("id", f"btn_{i}"))
|
|
actions.append(
|
|
{
|
|
"id": btn_id,
|
|
"name": btn.get("name", f"选择 {i + 1}"),
|
|
"integration": {
|
|
"url": btn.get("callback_url", ""),
|
|
"context": {
|
|
"action": btn.get("action", ""),
|
|
"button_id": btn_id,
|
|
"callback_id": sanitize_action_id(callback_id),
|
|
"value": btn.get("value", ""),
|
|
},
|
|
},
|
|
"type": "button",
|
|
"text": btn.get("text", f"Button {i + 1}"),
|
|
"style": btn.get("style", ""),
|
|
}
|
|
)
|
|
|
|
return {
|
|
"fallback": fallback or title,
|
|
"title": title,
|
|
"text": text,
|
|
"color": color or "",
|
|
"callback_id": callback_id,
|
|
"actions": actions,
|
|
}
|
|
|
|
|
|
def build_select_attachment(
|
|
title: str,
|
|
text: str,
|
|
options: list[dict[str, str]],
|
|
select_name: str = "selection",
|
|
callback_id: str = "",
|
|
) -> dict[str, Any]:
|
|
"""构建 Mattermost Select dropdown attachment。"""
|
|
select_options = []
|
|
for opt in options:
|
|
select_options.append(
|
|
{
|
|
"text": opt.get("text", ""),
|
|
"value": opt.get("value", ""),
|
|
}
|
|
)
|
|
|
|
return {
|
|
"fallback": title,
|
|
"title": title,
|
|
"text": text,
|
|
"callback_id": sanitize_action_id(callback_id),
|
|
"actions": [
|
|
{
|
|
"id": sanitize_action_id(select_name),
|
|
"name": select_name,
|
|
"integration": {
|
|
"url": "",
|
|
"context": {"action": select_name, "callback_id": sanitize_action_id(callback_id)},
|
|
},
|
|
"type": "select",
|
|
"options": select_options,
|
|
},
|
|
],
|
|
}
|
|
|
|
|
|
def parse_interaction_context(data: dict) -> dict:
|
|
"""解析交互回调 context。"""
|
|
context = data.get("context", {})
|
|
if isinstance(context, str):
|
|
try:
|
|
context = json.loads(context)
|
|
except json.JSONDecodeError:
|
|
context = {}
|
|
return context
|
|
|
|
|
|
def verify_hmac_signature(payload: bytes, signature: str, secret: str) -> bool:
|
|
"""验证 HMAC 签名。"""
|
|
if not signature or not secret:
|
|
return True
|
|
expected = hmac.new(
|
|
secret.encode(),
|
|
payload,
|
|
hashlib.sha256,
|
|
).hexdigest()
|
|
return hmac.compare_digest(expected, signature)
|
|
|
|
|
|
def build_interactive_post_props(
|
|
channel_id: str,
|
|
message: str,
|
|
attachments: list[dict] | None = None,
|
|
root_id: str = "",
|
|
) -> dict[str, Any]:
|
|
"""构建带 interactive attachments 的 post 选项。"""
|
|
return {
|
|
"channel_id": channel_id,
|
|
"message": message,
|
|
"root_id": root_id,
|
|
"props": {"attachments": attachments or []},
|
|
}
|