这是一个批量整理提交,包含以下主要改动: 1. 删除多处冗余的空行和未使用的导入 2. 修复文件末尾缺少换行符的问题 3. 调整部分模块的导入顺序与代码排版 4. 修复部分配置默认值与策略逻辑 5. 新增多个功能模块与辅助工具 6. 完善异常处理与日志记录 7. 修复速率限制、消息缓存、权限校验等逻辑bug 8. 废弃部分旧有API与配置项并添加警告提示
161 lines
5.3 KiB
Python
161 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import os
|
|
from typing import Any
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
def verify_interaction_hmac(body: bytes, signature: str, secret: str) -> bool:
|
|
if not secret or not signature:
|
|
return False
|
|
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
|
|
return hmac.compare_digest(expected, signature)
|
|
|
|
|
|
def is_allowed_interaction_source(
|
|
remote_ip: str,
|
|
allowed_ips: list[str] | None = None,
|
|
trusted_proxies: list[str] | None = None,
|
|
) -> bool:
|
|
if not allowed_ips:
|
|
return True
|
|
return remote_ip in allowed_ips
|
|
|
|
|
|
async def handle_interaction(
|
|
body: bytes,
|
|
headers: dict,
|
|
remote_ip: str,
|
|
adapter: Any,
|
|
) -> dict:
|
|
signing_secret = os.getenv("MATTERMOST_SIGNING_SECRET", "")
|
|
signature = headers.get("X-Mattermost-Signature", "") or headers.get("x-mattermost-signature", "")
|
|
|
|
if not verify_interaction_hmac(body, signature, signing_secret):
|
|
logger.warning("[Mattermost] Interaction HMAC verification failed")
|
|
return {"text": "签名验证失败", "status": 401}
|
|
|
|
config = (adapter.config or {}) if adapter else {}
|
|
interactions_cfg = config.get("interactions", {})
|
|
allowed_ips = interactions_cfg.get("allowedSourceIps", interactions_cfg.get("allowed_source_ips", []))
|
|
|
|
if allowed_ips and not is_allowed_interaction_source(remote_ip, allowed_ips):
|
|
logger.warning(f"[Mattermost] Interaction from unauthorized IP: {remote_ip}")
|
|
return {"text": "来源 IP 未被允许", "status": 403}
|
|
|
|
try:
|
|
data = json.loads(body)
|
|
except json.JSONDecodeError:
|
|
return {"text": "无效的请求数据", "status": 400}
|
|
|
|
action_type = data.get("type", "")
|
|
context = data.get("context", {})
|
|
if isinstance(context, str):
|
|
try:
|
|
context = json.loads(context)
|
|
except json.JSONDecodeError:
|
|
context = {}
|
|
|
|
user_id = data.get("user_id", "")
|
|
channel_id = data.get("channel_id", "")
|
|
post_id = data.get("post_id", "")
|
|
team_id = data.get("team_id", "")
|
|
|
|
logger.info(
|
|
f"[Mattermost] Interaction received: type={action_type} user={user_id} channel={channel_id} team={team_id}"
|
|
)
|
|
|
|
actions_list = data.get("data", {}).get("actions", []) if isinstance(data.get("data"), dict) else []
|
|
|
|
for action in actions_list:
|
|
action_name = action.get("name", "")
|
|
action_value = action.get("value", "")
|
|
action_context = action.get("integration", {}).get("context", action.get("context", {}))
|
|
if isinstance(action_context, str):
|
|
try:
|
|
action_context = json.loads(action_context)
|
|
except json.JSONDecodeError:
|
|
action_context = {}
|
|
|
|
merged_context = {**context, **action_context}
|
|
|
|
return await _dispatch_action(
|
|
adapter, action_name, action_value, user_id, channel_id, post_id, team_id, merged_context
|
|
)
|
|
|
|
return {"update": {"message": "操作已处理", "props": {}}}
|
|
|
|
|
|
async def _dispatch_action(
|
|
adapter: Any,
|
|
action_name: str,
|
|
action_value: str,
|
|
user_id: str,
|
|
channel_id: str,
|
|
post_id: str,
|
|
team_id: str,
|
|
context: dict,
|
|
) -> dict:
|
|
if action_name in ("poll_vote", "poll"):
|
|
return await _handle_poll_action(adapter, action_name, action_value, user_id, channel_id, post_id, context)
|
|
|
|
if action_name.startswith("model_select_") or action_name in (
|
|
"model_provider_select",
|
|
"model_select",
|
|
"model_confirm",
|
|
):
|
|
from .model_picker_interaction import handle_model_picker_interaction
|
|
|
|
return await handle_model_picker_interaction(
|
|
adapter, user_id, channel_id, post_id, action_name, action_value, adapter.config
|
|
)
|
|
|
|
if action_name == "approve_exec":
|
|
mgr = getattr(adapter, "_approval_manager", None)
|
|
if mgr is None:
|
|
return {"update": {"message": "审批功能未配置", "props": {}}}
|
|
success = mgr.approve(action_value, user_id)
|
|
if not success:
|
|
return {"update": {"message": "审批请求不存在或已过期", "props": {}}}
|
|
return {"update": {"message": "执行已批准 ✅", "props": {}}}
|
|
|
|
if action_name == "deny_exec":
|
|
mgr = getattr(adapter, "_approval_manager", None)
|
|
if mgr is None:
|
|
return {"update": {"message": "审批功能未配置", "props": {}}}
|
|
success = mgr.deny(action_value, user_id)
|
|
if not success:
|
|
return {"update": {"message": "审批请求不存在或已过期", "props": {}}}
|
|
return {"update": {"message": "执行已拒绝 ❌", "props": {}}}
|
|
|
|
return {"update": {"message": f"操作已处理: {action_name}", "props": {}}}
|
|
|
|
|
|
async def _handle_poll_action(
|
|
adapter: Any,
|
|
action_name: str,
|
|
action_value: str,
|
|
user_id: str,
|
|
channel_id: str,
|
|
post_id: str,
|
|
context: dict,
|
|
) -> dict:
|
|
poll_id = context.get("poll_id", "")
|
|
vote = context.get("vote", action_value)
|
|
|
|
if adapter and hasattr(adapter, "handle_poll_vote"):
|
|
result = await adapter.handle_poll_vote(poll_id, vote or action_value, user_id)
|
|
if result.get("error"):
|
|
return {"update": {"message": f"投票处理失败: {result['error']}", "props": {}}}
|
|
|
|
return {
|
|
"update": {
|
|
"message": f"投票已记录: {vote}",
|
|
"props": {},
|
|
}
|
|
}
|