新增 Mattermost 渠道完整实现,包含适配器核心、消息处理、交互回调、命令支持、安全校验、多账号管理等功能,支持机器人消息发送、交互按钮、命令注册、投票功能以及配置动态修改等特性。
100 lines
3.0 KiB
Python
100 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
import secrets
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
|
|
PAIRING_CODE_LENGTH = 6
|
|
PAIRING_REQUEST_TTL_S = 300
|
|
|
|
|
|
@dataclass
|
|
class PairingRequest:
|
|
user_id: str
|
|
code: str
|
|
created_at: float = field(default_factory=time.monotonic)
|
|
approved: bool = False
|
|
denied: bool = False
|
|
|
|
@property
|
|
def expired(self) -> bool:
|
|
return time.monotonic() - self.created_at > PAIRING_REQUEST_TTL_S
|
|
|
|
|
|
@dataclass
|
|
class PairingResult:
|
|
needs_pairing: bool
|
|
code: str = ""
|
|
reason: str = ""
|
|
|
|
|
|
class MattermostPairingManager:
|
|
def __init__(self):
|
|
self._pending: dict[str, PairingRequest] = {}
|
|
|
|
def check_or_request(self, user_id: str) -> PairingResult:
|
|
existing = self._pending.get(user_id)
|
|
if existing and not existing.expired:
|
|
if existing.approved:
|
|
return PairingResult(needs_pairing=False, reason="already approved")
|
|
return PairingResult(
|
|
needs_pairing=True,
|
|
code=existing.code,
|
|
reason="pairing pending",
|
|
)
|
|
|
|
code = self._generate_code()
|
|
self._pending[user_id] = PairingRequest(user_id=user_id, code=code)
|
|
self._cleanup_expired()
|
|
return PairingResult(needs_pairing=True, code=code, reason="new pairing request")
|
|
|
|
def approve(self, user_id: str, code: str) -> bool:
|
|
req = self._pending.get(user_id)
|
|
if req is None or req.expired:
|
|
return False
|
|
if req.code != code:
|
|
return False
|
|
req.approved = True
|
|
return True
|
|
|
|
def deny(self, user_id: str) -> bool:
|
|
req = self._pending.get(user_id)
|
|
if req is None or req.expired:
|
|
return False
|
|
req.denied = True
|
|
return True
|
|
|
|
def is_approved(self, user_id: str) -> bool:
|
|
req = self._pending.get(user_id)
|
|
if req is None:
|
|
return False
|
|
if req.expired:
|
|
del self._pending[user_id]
|
|
return False
|
|
return req.approved
|
|
|
|
def list_pending(self) -> list[PairingRequest]:
|
|
self._cleanup_expired()
|
|
return [r for r in self._pending.values() if not r.approved and not r.denied]
|
|
|
|
def list_approved(self) -> list[PairingRequest]:
|
|
self._cleanup_expired()
|
|
return [r for r in self._pending.values() if r.approved]
|
|
|
|
@staticmethod
|
|
def _generate_code() -> str:
|
|
return secrets.token_hex(PAIRING_CODE_LENGTH // 2)[:PAIRING_CODE_LENGTH].upper()
|
|
|
|
def _cleanup_expired(self) -> None:
|
|
self._pending = {uid: r for uid, r in self._pending.items() if not r.expired}
|
|
|
|
|
|
def build_pairing_notification(user_id: str, pairing_code: str) -> str:
|
|
return (
|
|
f"**ForcePilot DM 配对请求**\n\n"
|
|
f"检测到来自 `{user_id}` 的私聊消息,但该用户未在您的允许列表中。\n\n"
|
|
f"配对码:**`{pairing_code}`**\n\n"
|
|
f"请管理员确认此配对请求以允许该用户与 Bot 对话。\n"
|
|
f"配对码有效期:{PAIRING_REQUEST_TTL_S // 60} 分钟。"
|
|
)
|