ForcePilot/backend/package/yuxi/channel/extensions/bluebubbles/pairing.py

67 lines
2.0 KiB
Python
Raw Normal View History

import random
import time
import logging
logger = logging.getLogger(__name__)
PAIRING_CODE_LENGTH = 6
PAIRING_CODE_EXPIRY_SECONDS = 3600
MAX_PENDING_PER_CHANNEL = 3
PAIRING_APPROVED_MESSAGE = "BlueBubbles DM pairing approved. You can now send iMessages to this channel."
class PairingManager:
def __init__(self):
self._pending: dict[str, dict] = {}
self._approved: set[str] = set()
def generate_code(self, peer_id: str) -> str:
self._cleanup_expired()
active = sum(1 for v in self._pending.values() if v["status"] == "pending")
if active >= MAX_PENDING_PER_CHANNEL:
raise RuntimeError(f"Maximum pending pairings ({MAX_PENDING_PER_CHANNEL}) reached")
code = "".join(str(random.randint(0, 9)) for _ in range(PAIRING_CODE_LENGTH))
self._pending[peer_id] = {
"code": code,
"created_at": time.time(),
"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.time() - entry["created_at"] > PAIRING_CODE_EXPIRY_SECONDS:
del self._pending[peer_id]
return False
if entry["code"] == code:
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):
self._approved.discard(peer_id)
self._pending.pop(peer_id, None)
def _cleanup_expired(self):
now = time.time()
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")