新增 Mattermost 渠道扩展,支持在 Yuxi 平台中集成 Mattermost 团队协作平台。 包含以下功能模块: - client: Mattermost API 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - websocket: WebSocket 实时连接 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - dedup: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - session: 会话管理 - interactions: 交互处理 - slash_commands: 斜杠指令 - actions: 动作处理 - approval: 审批流程 - delivery: 消息送达确认 - directory: 目录管理 - threading: 线程管理 - gating: 门控管理 - reconnect: 重连机制 - reactions: 表情反应 - media: 媒体资源处理 - model_picker: 模型选择 - types: 类型定义
85 lines
2.2 KiB
Python
85 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import secrets
|
|
import string
|
|
|
|
from yuxi.channel.extensions.mattermost.security import normalize_allow_entry
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
PAIRING_CODE_LENGTH = 8
|
|
PAIRING_CODE_EXPIRY_SECONDS = 600
|
|
|
|
|
|
class MattermostPairingAdapter:
|
|
|
|
def __init__(self):
|
|
self._pending: dict[str, tuple[str, float]] = {}
|
|
|
|
def generate_code(self, peer_id: str) -> str:
|
|
import time as _time
|
|
|
|
code = _generate_pairing_code()
|
|
self._pending[peer_id] = (code, _time.time())
|
|
|
|
existing = [peer_id]
|
|
for pid, (c, _) in list(self._pending.items()):
|
|
if c == code:
|
|
existing.append(pid)
|
|
|
|
return code
|
|
|
|
def verify_code(self, peer_id: str, code: str) -> bool:
|
|
import time as _time
|
|
|
|
if peer_id not in self._pending:
|
|
return False
|
|
|
|
stored_code, timestamp = self._pending[peer_id]
|
|
now = _time.time()
|
|
|
|
if now - timestamp > PAIRING_CODE_EXPIRY_SECONDS:
|
|
self._pending.pop(peer_id, None)
|
|
return False
|
|
|
|
if stored_code != code.strip():
|
|
return False
|
|
|
|
self._pending.pop(peer_id, None)
|
|
return True
|
|
|
|
def get_pending_code(self, peer_id: str) -> str | None:
|
|
import time as _time
|
|
|
|
if peer_id not in self._pending:
|
|
return None
|
|
|
|
code, timestamp = self._pending[peer_id]
|
|
if _time.time() - timestamp > PAIRING_CODE_EXPIRY_SECONDS:
|
|
self._pending.pop(peer_id, None)
|
|
return None
|
|
|
|
return code
|
|
|
|
def clear_expired(self) -> None:
|
|
import time as _time
|
|
|
|
now = _time.time()
|
|
expired = [
|
|
pid for pid, (_, ts) in self._pending.items()
|
|
if now - ts > PAIRING_CODE_EXPIRY_SECONDS
|
|
]
|
|
for pid in expired:
|
|
self._pending.pop(pid, None)
|
|
|
|
def is_paired(self, peer_id: str, allow_list: list[str]) -> bool:
|
|
normalized = [normalize_allow_entry(e) for e in allow_list]
|
|
return peer_id in normalized
|
|
|
|
|
|
def _generate_pairing_code() -> str:
|
|
chars = string.ascii_uppercase + string.digits
|
|
raw = "".join(secrets.choice(chars) for _ in range(PAIRING_CODE_LENGTH))
|
|
return f"{raw[:4]}-{raw[4:]}"
|