import gradio as gr import requests import json import time import re import base64 import os import tempfile from pathlib import Path from urllib.parse import quote_plus import html as html_lib # ==================== استخراج API از سایت ==================== def extract_api_config_from_website(): """ استخراج خودکار API key و endpoint از سایت downloaderto """ try: print("\n" + "=" * 60) print("🔍 استخراج تنظیمات API از سایت downloaderto...") print("=" * 60) # دریافت صفحه اصلی سایت site_url = "https://downloaderto.com/enHF/" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', } response = requests.get(site_url, headers=headers, timeout=15) if response.status_code != 200: print(f"❌ خطا در دریافت صفحه: {response.status_code}") return None html_content = response.text # ذخیره HTML برای دیباگ debug_dir = Path(tempfile.gettempdir()) / "debug" debug_dir.mkdir(exist_ok=True) debug_file = debug_dir / "downloaderto_main_page.html" with open(debug_file, 'w', encoding='utf-8') as f: f.write(html_content) print(f"📄 HTML ذخیره شد: {debug_file}") # الگوهای مختلف برای یافتن API key api_key_patterns = [ r'api["\']?\s*[:=]\s*["\']([a-f0-9]{32})["\']', r'apiKey["\']?\s*[:=]\s*["\']([a-f0-9]{32})["\']', r'API_KEY["\']?\s*[:=]\s*["\']([a-f0-9]{32})["\']', r'key["\']?\s*[:=]\s*["\']([a-f0-9]{32})["\']', r'["\']([a-f0-9]{32})["\']', # هر رشته 32 کاراکتری hex ] api_key = None for pattern in api_key_patterns: matches = re.findall(pattern, html_content, re.IGNORECASE) for match in matches: # بررسی اینکه واقعاً API key است if len(match) == 32 and all(c in '0123456789abcdef' for c in match): api_key = match print(f"✅ API Key پیدا شد: {api_key}") break if api_key: break # الگوهای مختلف برای یافتن API endpoint api_url_patterns = [ r'(https?://[a-z0-9\.-]+/ajax/download\.php)', r'(https?://[a-z0-9\.-]+/api/download\.php)', r'apiUrl["\']?\s*[:=]\s*["\']([^"\']+)["\']', r'API_URL["\']?\s*[:=]\s*["\']([^"\']+)["\']', r'downloadUrl["\']?\s*[:=]\s*["\']([^"\']+)["\']', ] api_url = None for pattern in api_url_patterns: matches = re.findall(pattern, html_content, re.IGNORECASE) for match in matches: if 'download' in match.lower() and match.startswith('http'): api_url = match print(f"✅ API URL پیدا شد: {api_url}") break if api_url: break # جستجو در فایل‌های JavaScript js_files = re.findall(r']*src=["\']([^"\']+\.js[^"\']*)["\']', html_content) if not api_key or not api_url: print(f"🔍 جستجو در {len(js_files)} فایل JavaScript...") for js_file in js_files[:5]: # فقط 5 فایل اول try: if not js_file.startswith('http'): if js_file.startswith('/'): js_url = f"https://downloaderto.com{js_file}" else: js_url = f"https://downloaderto.com/{js_file}" else: js_url = js_file print(f"📥 دریافت: {js_url[:60]}...") js_response = requests.get(js_url, headers=headers, timeout=10) if js_response.status_code == 200: js_content = js_response.text # جستجوی API key if not api_key: for pattern in api_key_patterns: matches = re.findall(pattern, js_content, re.IGNORECASE) for match in matches: if len(match) == 32 and all(c in '0123456789abcdef' for c in match): api_key = match print(f"✅ API Key در JS پیدا شد: {api_key}") break if api_key: break # جستجوی API URL if not api_url: for pattern in api_url_patterns: matches = re.findall(pattern, js_content, re.IGNORECASE) for match in matches: if 'download' in match.lower() and match.startswith('http'): api_url = match print(f"✅ API URL در JS پیدا شد: {api_url}") break if api_url: break if api_key and api_url: break except Exception as e: print(f"⚠️ خطا در پردازش {js_file[:30]}: {str(e)[:30]}") continue if api_key and api_url: print("\n✅ تنظیمات API با موفقیت استخراج شد!") return { 'api_key': api_key, 'api_url': api_url, 'timestamp': time.time() } else: print(f"\n⚠️ استخراج ناقص:") print(f" API Key: {'✅' if api_key else '❌'}") print(f" API URL: {'✅' if api_url else '❌'}") return None except Exception as e: print(f"❌ خطا در استخراج API: {str(e)}") return None def get_api_config(force_refresh=False): """ دریافت تنظیمات API - از cache یا استخراج جدید """ cache_file = Path(tempfile.gettempdir()) / "downloaderto_api_cache.json" # اگر force_refresh نیست، ابتدا cache را بررسی کن if not force_refresh and cache_file.exists(): try: with open(cache_file, 'r') as f: cached_config = json.load(f) # بررسی اینکه cache قدیمی نباشد (کمتر از 24 ساعت) cache_age = time.time() - cached_config.get('timestamp', 0) if cache_age < 24 * 3600: # 24 ساعت print(f"📦 استفاده از API cache (سن: {cache_age/3600:.1f} ساعت)") return cached_config except: pass # استخراج جدید از سایت config = extract_api_config_from_website() if config: # ذخیره در cache try: with open(cache_file, 'w') as f: json.dump(config, f) print(f"💾 تنظیمات API در cache ذخیره شد") except: pass return config # اگر استخراج ناموفق بود، از مقادیر پیش‌فرض استفاده کن print("⚠️ استفاده از API پیش‌فرض") return { 'api_key': '2e716c3914a4f931fdad91ad9e14c6b1', 'api_url': 'https://p.lbserver.xyz/ajax/download.php', 'timestamp': time.time() } # ==================== استخراج عنوان ویدیو ==================== def extract_youtube_video_id(url): """استخراج Video ID از URL یوتیوب""" patterns = [ r'(?:v=|/)([0-9A-Za-z_-]{11}).*', r'(?:embed/)([0-9A-Za-z_-]{11})', r'(?:watch\?v=)([0-9A-Za-z_-]{11})', r'youtu\.be/([0-9A-Za-z_-]{11})', r'shorts/([0-9A-Za-z_-]{11})', ] for pattern in patterns: match = re.search(pattern, url) if match: return match.group(1) return None def get_title_from_youtube_oembed(video_id): """دریافت عنوان از YouTube oEmbed API""" try: url = f"https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v={video_id}&format=json" print(f"🔍 درخواست به YouTube oEmbed API...") response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() title = data.get('title', '') if title: title = html_lib.unescape(title) print(f"✅ عنوان دریافت شد: {title}") return title else: print(f"⚠️ YouTube oEmbed خطا: {response.status_code}") except Exception as e: print(f"❌ خطا در YouTube oEmbed: {str(e)}") return None def get_title_from_noembed(video_id): """دریافت عنوان از noembed.com""" try: url = f"https://noembed.com/embed?url=https://www.youtube.com/watch?v={video_id}" print(f"🔍 درخواست به noembed.com...") response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() title = data.get('title', '') if title: title = html_lib.unescape(title) print(f"✅ عنوان از noembed: {title}") return title except Exception as e: print(f"❌ خطا در noembed: {str(e)}") return None def get_video_title(youtube_url): """دریافت عنوان ویدیو""" print("=" * 60) print("🎬 شروع استخراج عنوان ویدیو") print("=" * 60) video_id = extract_youtube_video_id(youtube_url) if not video_id: print("❌ نتوانستم Video ID را استخراج کنم") return f"Video_{int(time.time())}" print(f"📝 Video ID: {video_id}") # روش 1: YouTube oEmbed title = get_title_from_youtube_oembed(video_id) if title and len(title) > 5: return sanitize_title(title) # روش 2: noembed.com title = get_title_from_noembed(video_id) if title and len(title) > 5: return sanitize_title(title) # عنوان پیش‌فرض fallback = f"YouTube_{video_id}" print(f"⚠️ استفاده از عنوان پیش‌فرض: {fallback}") return fallback def sanitize_title(title): """تمیز کردن عنوان""" title = html_lib.unescape(title) title = re.sub(r'\s*-\s*YouTube\s*$', '', title, flags=re.IGNORECASE) invalid_chars = '<>:"/\\|?*\n\r\t' for char in invalid_chars: title = title.replace(char, ' ') title = re.sub(r'\s+', ' ', title).strip() if len(title) > 100: title = title[:97] + "..." print(f"🎬 عنوان نهایی: {title}") return title # ==================== دانلود با Polling ==================== def wait_for_download_link(download_id, max_attempts=25, wait_time=2): """صبر کردن تا لینک دانلود آماده شود""" possible_urls = [ f"https://p.savenow.to/download/{download_id}", f"https://p.savenow.to/api/download/{download_id}", f"https://p.lbserver.xyz/download/{download_id}", ] headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'video/mp4,video/*,*/*;q=0.8', 'Referer': 'https://downloaderto.com/', } print(f"⏳ شروع polling برای Download ID: {download_id}") print(f"📊 حداکثر {max_attempts} تلاش، هر {wait_time} ثانیه یک بار") for attempt in range(1, max_attempts + 1): print(f"\n🔄 تلاش {attempt}/{max_attempts}...") for url in possible_urls: try: response = requests.head(url, headers=headers, timeout=8, allow_redirects=True) if response.status_code in [200, 302, 307]: final_url = response.url if response.history else url file_size = response.headers.get('content-length', 0) if file_size: size_bytes = int(file_size) if size_bytes > 500 * 1024: if size_bytes < 1024*1024: size_info = f"{size_bytes/1024:.1f} KB" elif size_bytes < 1024*1024*1024: size_info = f"{size_bytes/(1024*1024):.1f} MB" else: size_info = f"{size_bytes/(1024*1024*1024):.2f} GB" print(f"✅ لینک آماده شد! حجم: {size_info}") return { 'url': final_url, 'size': size_info, 'size_bytes': size_bytes, 'attempts': attempt } except Exception as e: continue if attempt < max_attempts: time.sleep(wait_time) print(f"❌ پس از {max_attempts} تلاش، لینک پیدا نشد") return None def download_file(download_url, filename, output_dir): """دانلود فایل""" try: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://downloaderto.com/', } print(f"📥 شروع دانلود: {filename}") response = requests.get(download_url, headers=headers, stream=True, timeout=60) response.raise_for_status() filepath = output_dir / filename total_size = int(response.headers.get('content-length', 0)) with open(filepath, 'wb') as f: downloaded = 0 for chunk in response.iter_content(chunk_size=8192): if chunk: f.write(chunk) downloaded += len(chunk) if total_size: percent = (downloaded / total_size) * 100 print(f"📥 {percent:.1f}%", end='\r') print(f"\n✅ دانلود کامل: {filepath.name}") actual_size = filepath.stat().st_size if actual_size < 1024*1024: size_display = f"{actual_size/1024:.1f} KB" elif actual_size < 1024*1024*1024: size_display = f"{actual_size/(1024*1024):.1f} MB" else: size_display = f"{actual_size/(1024*1024*1024):.2f} GB" return { 'success': True, 'filepath': str(filepath), 'filename': filepath.name, 'size': size_display } except Exception as e: print(f"❌ خطا در دانلود: {str(e)}") return { 'success': False, 'error': str(e) } def cleanup_old_files(download_dir, max_files=5): """پاک‌سازی فایل‌های قدیمی""" try: files = list(download_dir.glob("*.mp4")) files.sort(key=lambda x: x.stat().st_ctime, reverse=True) deleted = 0 for filepath in files[max_files:]: try: filepath.unlink() deleted += 1 except: pass if deleted > 0: print(f"🧹 {deleted} فایل قدیمی حذف شد") except: pass # ==================== تابع اصلی ==================== def download_youtube_video(youtube_url, quality, enable_preview): """دانلود ویدیو یوتیوب با polling و استخراج خودکار API""" quality_map = { "360p": "360", "480p": "480", "720p": "720", "1080p": "1080", "بهترین": "best" } format_code = quality_map.get(quality, "720") # مرحله 0: دریافت تنظیمات API api_config = get_api_config(force_refresh=False) api_key = api_config['api_key'] api_url = api_config['api_url'] print(f"\n🔑 API Key: {api_key[:16]}...") print(f"🌐 API URL: {api_url}") # مرحله 1: عنوان ویدیو video_title = get_video_title(youtube_url) # مرحله 2: API print("\n" + "=" * 60) print("📤 درخواست به API downloaderto") print("=" * 60) params = { 'copyright': '0', 'format': format_code, 'url': youtube_url, 'api': api_key } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json', 'Referer': 'https://downloaderto.com/', } try: response = requests.get(api_url, params=params, headers=headers, timeout=30) if response.status_code != 200: print(f"❌ API خطا: {response.status_code}") print("🔄 تلاش برای به‌روزرسانی API از سایت...") # تلاش برای استخراج API جدید new_config = get_api_config(force_refresh=True) if new_config and (new_config['api_key'] != api_key or new_config['api_url'] != api_url): print("✅ API جدید پیدا شد! تلاش مجدد...") params['api'] = new_config['api_key'] response = requests.get(new_config['api_url'], params=params, headers=headers, timeout=30) if response.status_code != 200: return None, f"❌ خطای API حتی با تنظیمات جدید: {response.status_code}", None else: return None, f"❌ خطای API: {response.status_code}", None data = response.json() if not data.get('success'): error_msg = data.get('error', 'نامشخص') print(f"❌ API خطا: {error_msg}") # اگر خطا مربوط به API key باشد، تلاش برای استخراج جدید if 'api' in error_msg.lower() or 'key' in error_msg.lower() or 'auth' in error_msg.lower(): print("🔄 به‌روزرسانی API از سایت...") new_config = get_api_config(force_refresh=True) if new_config: params['api'] = new_config['api_key'] response = requests.get(new_config['api_url'], params=params, headers=headers, timeout=30) data = response.json() if not data.get('success'): return None, f"❌ API خطا حتی با key جدید: {data.get('error', 'نامشخص')}", None else: return None, f"❌ API خطا: {error_msg}", None else: return None, f"❌ API خطا: {error_msg}", None download_id = data.get('id') print(f"✅ Download ID: {download_id}") # مرحله 3: Polling print("\n" + "=" * 60) print("⏳ در حال آماده‌سازی فایل روی سرور...") print("=" * 60) link_info = wait_for_download_link(download_id, max_attempts=25, wait_time=2) if not link_info: manual_url = f"https://downloaderto.com/download/{download_id}" return { 'id': download_id, 'title': video_title, }, f"""⏱️ زمان انتظار تمام شد 📝 عنوان: {video_title} ⚠️ فایل هنوز آماده نشده است 💡 راه‌حل: 1. چند لحظه صبر کنید و دوباره تلاش کنید 2. یا مستقیماً به این آدرس بروید: {manual_url}""", None print(f"\n✅ فایل آماده شد بعد از {link_info['attempts']} تلاش") # مرحله 4: دانلود print("\n" + "=" * 60) print("💾 دانلود فایل") print("=" * 60) download_dir = Path(tempfile.gettempdir()) / "youtube_downloads" download_dir.mkdir(exist_ok=True) cleanup_old_files(download_dir, max_files=3) safe_title = re.sub(r'[<>:"/\\|?*]', '_', video_title) filename = f"{safe_title}_{quality}.mp4" result = download_file(link_info['url'], filename, download_dir) preview_video = None if enable_preview: if result.get('success') and result.get('filepath'): if os.path.exists(result['filepath']): preview_video = result['filepath'] else: preview_video = link_info['url'] else: preview_video = link_info['url'] if result.get('success'): info_text = f"""✅ دانلود موفق! 📝 عنوان: {video_title} 📊 کیفیت: {quality} 💾 حجم: {result['size']} 📂 نام فایل: {result['filename']} ⏱️ آماده شد بعد از: {link_info['attempts']} تلاش 🔗 لینک اصلی: {link_info['url']}""" return { 'title': video_title, 'file': result['filepath'], 'filename': result['filename'], 'size': result['size'], 'url': link_info['url'] }, info_text, preview_video else: info_text = f"""⚠️ دانلود خودکار ناموفق بود 📝 عنوان: {video_title} 📊 کیفیت: {quality} 💾 حجم: {link_info['size']} ⏱️ آماده شد بعد از: {link_info['attempts']} تلاش 🔗 لینک مستقیم: {link_info['url']} 💡 لینک را کپی کرده و در مرورگر باز کنید""" return { 'title': video_title, 'size': link_info['size'], 'url': link_info['url'] }, info_text, preview_video except Exception as e: return None, f"❌ خطا: {str(e)}", None # ==================== رابط کاربری ==================== with gr.Blocks(title="YouTube Downloader") as demo: gr.Markdown("# 🎬 YouTube Video Downloader") gr.Markdown("### دانلود هوشمند با استخراج خودکار API") with gr.Row(): with gr.Column(scale=1): url_input = gr.Textbox( label="🔗 لینک یوتیوب", placeholder="https://www.youtube.com/watch?v=...", lines=2 ) quality_select = gr.Dropdown( label="📊 کیفیت", choices=["360p", "480p", "720p", "1080p", "بهترین"], value="720p" ) preview_checkbox = gr.Checkbox( label="🎥 نمایش پیش‌نمایش ویدیو", value=False, info="فعال کردن نمایش ویدیو (ممکن است کند شود)" ) download_btn = gr.Button("⬇️ دانلود", variant="primary", size="lg") refresh_api_btn = gr.Button("🔄 به‌روزرسانی API از سایت", variant="secondary") gr.Markdown(""" ### ✨ ویژگی‌های جدید: - 🤖 **استخراج خودکار API** - 🔄 **به‌روزرسانی هوشمند** - ⏳ **Polling تا آماده شدن** - 🎬 **عنوان خودکار** - 💾 **Cache تنظیمات** ### 🔧 نحوه کار: 1. برنامه API را از سایت استخراج می‌کند 2. اگر API خطا داد، خودکار به‌روزرسانی می‌کند 3. تنظیمات را 24 ساعت cache می‌کند """) with gr.Column(scale=2): info_output = gr.Textbox( label="📋 اطلاعات و پیشرفت", lines=15, interactive=False ) with gr.Row(): size_output = gr.Textbox(label="💾 حجم", scale=1) status_output = gr.Textbox(label="✅ وضعیت", scale=1) link_output = gr.Textbox( label="🔗 لینک دانلود مستقیم", interactive=True ) file_output = gr.File(label="📁 فایل دانلود شده") video_output = gr.Video( label="🎥 پیش‌نمایش", height=400, visible=True ) # تست سریع gr.Markdown("### 🧪 تست سریع") with gr.Row(): test1_btn = gr.Button("تست ویدیو کوتاه", variant="secondary") test2_btn = gr.Button("تست YouTube Short", variant="secondary") @download_btn.click( inputs=[url_input, quality_select, preview_checkbox], outputs=[info_output, size_output, status_output, link_output, file_output, video_output] ) def handle_download(url, quality, enable_preview): if not url: return "❌ لطفاً لینک یوتیوب را وارد کنید", "", "❌ خطا", "", None, None yield "🚀 شروع پردازش...\n⏳ لطفاً صبر کنید...", "", "⏳ در حال پردازش", "", None, None result, info, preview = download_youtube_video(url, quality, enable_preview) if not result: yield info, "", "❌ خطا", "", None, None return size = result.get('size', '') link = result.get('url', '') file = result.get('file') if file and os.path.exists(file): status = "✅ موفق" else: status = "⚠️ فقط لینک" yield info, size, status, link, file, preview @refresh_api_btn.click(outputs=[info_output]) def refresh_api(): result = "🔄 شروع به‌روزرسانی API...\n\n" config = get_api_config(force_refresh=True) if config: result += f"✅ API با موفقیت به‌روزرسانی شد!\n\n" result += f"🔑 API Key: {config['api_key'][:16]}...\n" result += f"🌐 API URL: {config['api_url']}\n" result += f"⏰ زمان: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(config['timestamp']))}" else: result += "❌ خطا در به‌روزرسانی API" return result @test1_btn.click(outputs=[url_input, quality_select, preview_checkbox]) def test1(): return "https://www.youtube.com/watch?v=jNQXAC9IVRw", "480p", False @test2_btn.click(outputs=[url_input, quality_select, preview_checkbox]) def test2(): return "https://www.youtube.com/shorts/cPuS6WPZjWI", "720p", False # راهنما with gr.Accordion("📖 راهنمای استفاده", open=False): gr.Markdown(""" ### استخراج خودکار API: - ✅ برنامه خودش API را از سایت downloaderto می‌خواند - ✅ اگر API عوض شد، خودکار آن را پیدا می‌کند - ✅ تنظیمات را 24 ساعت نگه می‌دارد - ✅ در صورت خطا، به‌روزرسانی می‌کند ### دکمه "به‌روزرسانی API": اگر مشکلی پیش آمد، روی این دکمه کلیک کنید تا: - API جدید را از سایت بخواند - Cache قدیمی را پاک کند - تنظیمات جدید را نمایش دهد ### نکات: - برنامه هوشمند است و خودش مشکلات را حل می‌کند - نیازی به تنظیم دستی نیست - همه چیز خودکار است! """) if __name__ == "__main__": demo.launch( server_name="0.0.0.0", server_port=7860, show_error=True, theme=gr.themes.Soft() )