from __future__ import annotations import secrets import time import logging logger = logging.getLogger(__name__) PAIRING_CODE_LENGTH = 8 PAIRING_CODE_EXPIRY_SECONDS = 600 PAIRING_CODE_CHARS = "ABCDEFGHJKMNPQRSTUVWXYZ23456789" # 无歧义: 排除 0/O/1/I PAIRING_APPROVED_MESSAGE = ( "Your access has been approved. " "You can now interact with this iMessage bot." ) class IMessagePairingAdapter: def __init__(self): self._pending: dict[str, dict] = {} self._approved: set[str] = set() def generate_code(self, peer_id: str) -> str: self._cleanup_expired() code = "".join(secrets.choice(PAIRING_CODE_CHARS) for _ in range(PAIRING_CODE_LENGTH)) self._pending[peer_id] = { "code": code, "created_at": time.monotonic(), "status": "pending", } return code def verify_code(self, peer_id: str, code: str) -> bool: entry = self._pending.get(peer_id) if not entry: return False if time.monotonic() - entry["created_at"] > PAIRING_CODE_EXPIRY_SECONDS: del self._pending[peer_id] return False if secrets.compare_digest(entry["code"], code.upper().strip()): entry["status"] = "approved" self._approved.add(peer_id) return True return False def is_approved(self, peer_id: str) -> bool: return peer_id in self._approved def revoke(self, peer_id: str) -> None: self._approved.discard(peer_id) self._pending.pop(peer_id, None) def _cleanup_expired(self): now = time.monotonic() expired = [ pid for pid, entry in self._pending.items() if (now - entry["created_at"]) > PAIRING_CODE_EXPIRY_SECONDS ] for pid in expired: del self._pending[pid] def pending_count(self) -> int: self._cleanup_expired() return sum(1 for v in self._pending.values() if v["status"] == "pending")