Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
import
|
| 3 |
from bs4 import BeautifulSoup
|
| 4 |
import img2pdf
|
| 5 |
import io
|
|
@@ -12,111 +12,89 @@ def fetch_chapters_range(base_url, start_ch, end_ch):
|
|
| 12 |
start = int(start_ch)
|
| 13 |
end = int(end_ch)
|
| 14 |
|
|
|
|
|
|
|
|
|
|
| 15 |
all_imgs = []
|
| 16 |
log_messages = []
|
| 17 |
-
session = requests.Session()
|
| 18 |
|
| 19 |
-
#
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
| 23 |
-
'Referer': base_url,
|
| 24 |
-
'Accept-Language': 'ar,en-US;q=0.7,en;q=0.3',
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
base_url = base_url.strip().rstrip('/')
|
| 28 |
|
| 29 |
for i in range(start, end + 1):
|
| 30 |
-
# تجربة نمط
|
| 31 |
-
|
| 32 |
-
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
ch_url = f"{base_url}/{i}/"
|
| 39 |
-
response = session.get(ch_url, headers=headers, timeout=20)
|
| 40 |
-
|
| 41 |
-
soup = BeautifulSoup(response.text, 'html.parser')
|
| 42 |
-
|
| 43 |
-
# بحث مكثف عن الصور في كل الأماكن المحتملة
|
| 44 |
-
# أوليمبوس غالباً يضع الصور داخل div باسم 'rd-host' أو داخل برمجية JavaScript
|
| 45 |
-
images = soup.find_all('img')
|
| 46 |
-
|
| 47 |
-
chapter_count = 0
|
| 48 |
-
for img in images:
|
| 49 |
-
# فحص كل السمات الممكنة لرابط الصورة
|
| 50 |
-
img_url = (img.get('src') or
|
| 51 |
-
img.get('data-src') or
|
| 52 |
-
img.get('data-lazy-src') or
|
| 53 |
-
img.get('data-full-url') or
|
| 54 |
-
img.get('srcset')) # بعض المواقع تستخدم srcset
|
| 55 |
|
| 56 |
-
if
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
img_url = "https:" + img_url if img_url.startswith('//') else img_url
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
log_messages.append(f"✅ تم سحب {chapter_count} صورة من الفصل {i}")
|
| 78 |
-
else:
|
| 79 |
-
log_messages.append(f"⚠️ لم نجد صوراً في الفصل {i}، قد يكون المحتوى مشفراً.")
|
| 80 |
-
|
| 81 |
-
time.sleep(2) # زيادة وقت الانتظار لتجنب حماية Cloudflare في أوليمبوس
|
| 82 |
-
|
| 83 |
-
except Exception as e:
|
| 84 |
-
log_messages.append(f"❌ خطأ في الفصل {i}: {str(e)}")
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
with gr.Row():
|
| 92 |
-
url_input = gr.Textbox(
|
| 93 |
-
label="رابط المانهوا الرئيسي",
|
| 94 |
-
placeholder="مثال: https://teamx.org/series/nano-machine",
|
| 95 |
-
scale=3
|
| 96 |
-
)
|
| 97 |
|
| 98 |
with gr.Row():
|
| 99 |
-
start_ch = gr.Number(label="
|
| 100 |
-
end_ch = gr.Number(label="إلى فصل", value=
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
<p style='color: #ff4500; font-size: 18px; font-weight: bold; margin-bottom: 10px;'>🚨 تنبيه السيرفر</p>
|
| 109 |
-
<p style='color: #ffffff; margin-bottom: 15px;'>لضمان عدم توقف التحميل التلقائي، يرجى تفعيل "رابط التحقق" من الراعي الرسمي:</p>
|
| 110 |
-
<a href='ضع_رابط_ADSTERRA_هنا' target='_blank'
|
| 111 |
-
style='background: #ff4500; color: white; padding: 10px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block;'>
|
| 112 |
-
✅ اضغط هنا لتفعيل التحميل السريع
|
| 113 |
-
</a>
|
| 114 |
-
</div>
|
| 115 |
-
""")
|
| 116 |
-
|
| 117 |
-
status_output = gr.Textbox(label="سجل العملية (Log)", interactive=False, lines=8)
|
| 118 |
-
file_output = gr.File(label="تحميل ملف الـ PDF المجمع")
|
| 119 |
|
| 120 |
btn.click(fn=fetch_chapters_range, inputs=[url_input, start_ch, end_ch], outputs=[file_output, status_output])
|
| 121 |
|
| 122 |
-
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
import cloudscraper
|
| 3 |
from bs4 import BeautifulSoup
|
| 4 |
import img2pdf
|
| 5 |
import io
|
|
|
|
| 12 |
start = int(start_ch)
|
| 13 |
end = int(end_ch)
|
| 14 |
|
| 15 |
+
if end < start:
|
| 16 |
+
return None, "❌ خطأ: رقم الفصل النهائي أصغر من البداية!"
|
| 17 |
+
|
| 18 |
all_imgs = []
|
| 19 |
log_messages = []
|
|
|
|
| 20 |
|
| 21 |
+
# استخدام cloudscraper بدلاً من requests العادي لتجاوز حماية أوليمبوس
|
| 22 |
+
scraper = cloudscraper.create_scraper()
|
| 23 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
base_url = base_url.strip().rstrip('/')
|
| 25 |
|
| 26 |
for i in range(start, end + 1):
|
| 27 |
+
# تجربة أنماط روابط مختلفة (أوليمبوس وتيم إكس)
|
| 28 |
+
ch_urls = [f"{base_url}/chapter-{i}/", f"{base_url}/{i}/", f"{base_url}/فصل-{i}/"]
|
| 29 |
+
success = False
|
| 30 |
|
| 31 |
+
for url in ch_urls:
|
| 32 |
+
try:
|
| 33 |
+
log_messages.append(f"⏳ فحص: {url}")
|
| 34 |
+
response = scraper.get(url, timeout=20)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
+
if response.status_code == 200:
|
| 37 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
| 38 |
+
# البحث عن الصور في الأماكن المحتملة (أوليمبوس يستخدم كلاسات متغيرة)
|
| 39 |
+
images = soup.find_all('img')
|
| 40 |
+
chapter_count = 0
|
|
|
|
| 41 |
|
| 42 |
+
for img in images:
|
| 43 |
+
img_url = img.get('src') or img.get('data-src') or img.get('data-lazy-src')
|
| 44 |
+
if img_url:
|
| 45 |
+
img_url = img_url.strip()
|
| 46 |
+
if not img_url.startswith('http'):
|
| 47 |
+
img_url = "https:" + img_url if img_url.startswith('//') else img_url
|
| 48 |
+
|
| 49 |
+
if any(x in img_url.lower() for x in ["logo", "icon", "avatar", "bg", "banner"]):
|
| 50 |
+
continue
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
img_res = scraper.get(img_url, timeout=15)
|
| 54 |
+
if img_res.status_code == 200 and len(img_res.content) > 10000:
|
| 55 |
+
image = Image.open(io.BytesIO(img_res.content)).convert('RGB')
|
| 56 |
+
all_imgs.append(image)
|
| 57 |
+
chapter_count += 1
|
| 58 |
+
except: continue
|
| 59 |
|
| 60 |
+
if chapter_count > 0:
|
| 61 |
+
log_messages.append(f"✅ تم سحب {chapter_count} صورة من الفصل {i}")
|
| 62 |
+
success = True
|
| 63 |
+
break # توقف عن تجربة الأنماط الأخرى لهذا الفصل
|
| 64 |
+
except: continue
|
| 65 |
+
|
| 66 |
+
if not success:
|
| 67 |
+
log_messages.append(f"⚠️ تعذر الوصول للفصل {i}")
|
| 68 |
+
|
| 69 |
+
time.sleep(2) # حماية من الحظر
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
+
if not all_imgs:
|
| 72 |
+
return None, "\n".join(log_messages) + "\n\n❌ لم يتم العثور على صور. تأكد من الرابط!"
|
| 73 |
+
|
| 74 |
+
output_filename = f"manga_collection_{int(time.time())}.pdf"
|
| 75 |
+
all_imgs[0].save(output_filename, save_all=True, append_images=all_imgs[1:], format='PDF')
|
| 76 |
+
|
| 77 |
+
return output_filename, "\n".join(log_messages) + "\n\n✨ تم التجميع بنجاح!"
|
| 78 |
+
|
| 79 |
+
# تصميم الواجهة مع إصلاح خطأ Theme
|
| 80 |
+
with gr.Blocks(title="Manga Downloader") as demo:
|
| 81 |
+
gr.Markdown("# 📚 محمل المانهوا الشامل")
|
| 82 |
|
| 83 |
with gr.Row():
|
| 84 |
+
url_input = gr.Textbox(label="رابط المانهوا الرئيسي", placeholder="https://olympustaff.com/series/demonic-emperor/")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
with gr.Row():
|
| 87 |
+
start_ch = gr.Number(label="من فصل", value=1)
|
| 88 |
+
end_ch = gr.Number(label="إلى فصل", value=2)
|
| 89 |
|
| 90 |
+
btn = gr.Button("🚀 ابدأ التحميل", variant="primary")
|
| 91 |
+
|
| 92 |
+
gr.HTML("<div style='text-align:center'><a href='YOUR_ADSTERRA_LINK'>📥 اضغط هنا لتسريع التحميل (إعلان)</a></div>")
|
| 93 |
+
|
| 94 |
+
status_output = gr.Textbox(label="سجل العملية", lines=5)
|
| 95 |
+
file_output = gr.File(label="الملف الجاهز")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
btn.click(fn=fetch_chapters_range, inputs=[url_input, start_ch, end_ch], outputs=[file_output, status_output])
|
| 98 |
|
| 99 |
+
# نقل الـ theme هنا كما يطلب Gradio 6
|
| 100 |
+
demo.launch(ssr_mode=False)
|