#!/usr/bin/env python3 """ Check Fansly attachments for 4K variants and upgrade if available. This script: 1. Finds all non-4K video attachments from Fansly Direct 2. Re-fetches media info from the Fansly API 3. Checks if a higher resolution variant is available 4. Updates the attachment URL and resets for re-download if upgrade found """ import asyncio import sys import os # Add project to path sys.path.insert(0, '/opt/media-downloader') # Bootstrap PostgreSQL adapter before any database imports from modules.db_bootstrap import bootstrap_database bootstrap_database() from modules.paid_content.fansly_direct_client import FanslyDirectClient from modules.paid_content.db_adapter import PaidContentDB async def check_and_upgrade_attachments(): """Check all non-4K Fansly attachments for upgrades.""" db = PaidContentDB('/opt/media-downloader/database/media_downloader.db') # Get Fansly auth token service = db.get_service('fansly_direct') if not service or not service.get('session_cookie'): print("ERROR: No Fansly auth token configured") return auth_token = service['session_cookie'] client = FanslyDirectClient(auth_token) # Find non-4K video attachments from Fansly Direct # 4K is 3840x2160 or 2160x3840 (portrait) query = """ SELECT a.id, a.name, a.width, a.height, a.status, p.post_id, p.id as db_post_id FROM paid_content_attachments a JOIN paid_content_posts p ON a.post_id = p.id JOIN paid_content_creators c ON p.creator_id = c.id WHERE c.service_id = 'fansly_direct' AND a.file_type = 'video' AND a.width IS NOT NULL AND a.height IS NOT NULL AND NOT ( (a.width >= 3840 AND a.height >= 2160) OR (a.width >= 2160 AND a.height >= 3840) ) AND p.post_id NOT LIKE 'manual_%' AND p.post_id NOT LIKE 'import_%' AND p.post_id NOT LIKE '20%-%' ORDER BY a.id """ cursor = db.conn.execute(query) attachments = cursor.fetchall() print(f"Found {len(attachments)} non-4K video attachments to check") print("-" * 80) upgrades_found = 0 errors = 0 already_best = 0 for att in attachments: att_id, name, width, height, status, post_id, db_post_id = att current_res = f"{width}x{height}" print(f"\nChecking: {name} (ID: {att_id}, current: {current_res})") try: # Extract media ID from filename (e.g., "12345.mp4" -> "12345") media_id = name.replace('.mp4', '').replace('.mov', '') # Fetch media info from Fansly API # We need to get the account media for this post async with client: # Get the post to find media info posts, _, media_dict, account_media_dict, bundle_dict = await client._fetch_timeline_page( account_id=None, # We'll search by post ID before=str(int(post_id) + 1), # Get this post account={} ) # Find media in the dictionaries found_4k = False best_width = width best_height = height best_url = None # Check account_media_dict for this media for am_id, am_data in account_media_dict.items(): media = am_data.get('media', {}) if str(media.get('id')) == media_id: # Found the media, check variants variants = media.get('variants', []) print(f" Found media with {len(variants)} variants") for v in variants: v_w = v.get('width', 0) or 0 v_h = v.get('height', 0) or 0 v_locs = v.get('locations', []) # Check if this is a higher resolution if v_w * v_h > best_width * best_height: for loc in v_locs: loc_url = loc.get('location', '') # Prefer streaming formats for 4K if '.m3u8' in loc_url or '.mp4' in loc_url or '.mov' in loc_url: best_width = v_w best_height = v_h # Construct signed URL if metadata present metadata = loc.get('metadata', {}) if metadata: params = [] for key in ['Key-Pair-Id', 'Signature', 'Policy']: if key in metadata: params.append(f"{key}={metadata[key]}") if params: best_url = loc_url + '?' + '&'.join(params) else: best_url = loc_url else: best_url = loc_url if v_w >= 3840 or v_h >= 3840: found_4k = True break break if found_4k and best_url: print(f" ✓ UPGRADE FOUND: {best_width}x{best_height}") upgrades_found += 1 # Update the attachment db.conn.execute(""" UPDATE paid_content_attachments SET download_url = ?, width = ?, height = ?, status = 'pending', download_attempts = 0, error_message = NULL, local_path = NULL, local_filename = NULL WHERE id = ? """, (best_url, best_width, best_height, att_id)) db.conn.commit() print(f" → Updated and queued for re-download") elif best_width > width or best_height > height: print(f" ~ Better quality available: {best_width}x{best_height} (not 4K)") else: print(f" - Already at best available quality") already_best += 1 except Exception as e: print(f" ✗ Error: {e}") errors += 1 # Rate limiting await asyncio.sleep(0.5) print("\n" + "=" * 80) print(f"Summary:") print(f" Upgrades found and queued: {upgrades_found}") print(f" Already at best quality: {already_best}") print(f" Errors: {errors}") print(f" Total checked: {len(attachments)}") if __name__ == '__main__': asyncio.run(check_and_upgrade_attachments())