danidl / app.py
danicor's picture
Update app.py
f4c0062 verified
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'<script[^>]*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()
)