ForcePilot/backend/package/yuxi/channel/extensions/zalo/pairing.py
Kris 5946478772 feat(channel): 添加小红书、XMPP、元宝和 Zalo 渠道扩展
新增小红书、XMPP、元宝、Zalo 四个渠道扩展。

小红书渠道扩展主要模块:config, gateway, webhook, outbound, streaming, pairing, security, dedupe, media, status, window

XMPP 渠道扩展主要模块:plugin, config, gateway, outbound, streaming, pairing, security, dedupe, accounts, commands, muc, rate_limiter, stanza_utils, status, monitor

元宝渠道扩展主要模块:plugin, client, config_schema, gateway, outbound(chunk/queue/transport), inbound(dispatcher), streaming, pairing, security, accounts, actions, commands, codec(biz/conn), session, shared, utils

Zalo 渠道扩展主要模块:api, config, gateway, webhook, outbound, pairing, security, session, polling, monitor, status
2026-05-21 12:04:05 +08:00

78 lines
2.7 KiB
Python

import hmac
import secrets
import time
import logging
logger = logging.getLogger(__name__)
class ZaloPairing:
id_label = "zaloUserId"
def __init__(self):
self._codes: dict[str, tuple[str, float]] = {}
self._code_ttl_seconds = 300
def _cleanup_expired(self) -> None:
now = time.monotonic()
expired = [pid for pid, (_, ts) in self._codes.items() if now - ts > self._code_ttl_seconds]
for pid in expired:
self._codes.pop(pid, None)
async def generate_code(self, peer_id: str) -> str:
code = f"{secrets.randbelow(1_000_000):06d}"
self._codes[peer_id] = (code, time.monotonic())
return code
async def verify_code(self, peer_id: str, code: str) -> bool:
self._cleanup_expired()
stored = self._codes.pop(peer_id, None)
if stored is None:
return False
stored_code, created_at = stored
if time.monotonic() - created_at > self._code_ttl_seconds:
return False
return hmac.compare_digest(stored_code, code)
@staticmethod
def normalize_allow_entry(entry: str) -> str:
stripped = entry.strip()
for prefix in ("zalo:", "zl:"):
if stripped.startswith(prefix):
stripped = stripped[len(prefix) :]
break
return stripped
async def notify_approval(self, config: dict, peer_id: str, account_id: str | None = None) -> None:
from yuxi.channel.extensions.zalo.config import ZaloConfigAdapter
from yuxi.channel.extensions.zalo.api import ZaloBotApi
adapter = ZaloConfigAdapter()
aid = account_id or adapter.default_account_id(config)
account = await adapter.resolve_account(aid)
bot_token = account.get("bot_token", "")
if not bot_token:
logger.warning("Zalo notify_approval: no bot_token for account %s", aid)
return
api = ZaloBotApi(bot_token, proxy=account.get("proxy"))
try:
await api.send_message(peer_id, self.pairing_approved_message())
logger.info("Zalo pairing approved notification sent to peer %s", peer_id)
except Exception:
logger.exception("Zalo notify_approval: failed to send notification to peer %s", peer_id)
finally:
await api.close()
def pairing_prompt_message(self, peer_id: str) -> str:
return (
f"To start using this bot, please ask an admin to approve your pairing request.\n\n"
f"Your Zalo user id: {peer_id}"
)
def pairing_approved_message(self) -> str:
return "Your pairing request has been approved. You can now chat with the bot."