"""Manual collector decisions and short-lived, request-bound selection receipts.""" from datetime import datetime, timedelta, timezone import hashlib import jwt from fastapi import HTTPException from ..config import settings def can_override(user): return user.get('role') == 'admin' or (user.get('features') or {}).get('ignore_profile_limits') is True def decision(item): reasons = [str(r) for r in (item.get('rejections') or [])] accepted = (item.get('approved') is True and not reasons and not item.get('rejected') and not item.get('temporarilyRejected') and item.get('downloadAllowed') is not False) # Unknown/operational rejections remain blocked. This permission only relaxes profile limits. profile_only = bool(reasons) and all(any(term in reason.lower() for term in ( 'quality profile', 'not wanted in profile', 'custom format', 'minimum score', 'quality is not', 'quality for', 'language', 'maximum size', 'minimum size', 'larger than', 'smaller than', 'size limit', 'release profile', )) for reason in reasons) override = not accepted and profile_only and item.get('downloadAllowed') is not False and not item.get('temporarilyRejected') return accepted, override, reasons def source_id(url): return hashlib.sha256(str(url).rstrip('/').encode()).hexdigest() def issue_selection(release, request_id, user, source, item_id): return jwt.encode({'aud': 'manual-release', 'sub': user['username'], 'request': str(request_id), 'source': source_id(source), 'item': item_id, 'guid': release['guid'], 'indexer': release['indexerId'], 'title': release.get('title'), 'override': release['requiresOverride'], 'rejections': release['rejections'], 'exp': datetime.now(timezone.utc) + timedelta(minutes=10)}, settings.jwt_secret, algorithm='HS256') def verify_selection(payload, request_id, user, source, item_id): try: receipt = jwt.decode(payload.get('selectionToken', ''), settings.jwt_secret, algorithms=['HS256'], audience='manual-release') except jwt.InvalidTokenError as exc: raise HTTPException(409, 'This release selection expired or is invalid. Search again before downloading.') from exc if (receipt.get('sub') != user.get('username') or receipt.get('request') != str(request_id) or receipt.get('source') != source_id(source) or receipt.get('item') != item_id or receipt.get('guid') != payload.get('guid') or receipt.get('indexer') != payload.get('indexerId')): raise HTTPException(409, 'This release does not belong to this account and request. Search again.') if receipt.get('override'): if not can_override(user): raise HTTPException(403, 'Ignore profile limits is disabled for your account.') if payload.get('ignoreProfileLimits') is not True: raise HTTPException(400, 'Explicitly confirm ignoring the profile limits for this release.') return receipt