Update app.py
Browse files
app.py
CHANGED
|
@@ -3,58 +3,155 @@ from flask import Flask, render_template_string
|
|
| 3 |
from playwright.sync_api import sync_playwright
|
| 4 |
from PIL import Image
|
| 5 |
import img2pdf
|
|
|
|
| 6 |
|
| 7 |
-
# --- الإعدادات ---
|
| 8 |
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
|
|
|
| 9 |
bot = telebot.TeleBot(BOT_TOKEN, threaded=False)
|
| 10 |
app = Flask(__name__)
|
| 11 |
|
|
|
|
|
|
|
|
|
|
| 12 |
@app.route('/')
|
| 13 |
-
def home(): return "<h1>Manga Engine
|
| 14 |
|
| 15 |
-
|
|
|
|
| 16 |
with sync_playwright() as p:
|
| 17 |
-
# تشغيل متصفح مخفي (Headless)
|
| 18 |
browser = p.chromium.launch(headless=True)
|
| 19 |
context = browser.new_context(user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
| 20 |
page = context.new_page()
|
| 21 |
-
|
| 22 |
try:
|
| 23 |
-
# الذهاب للرابط والانتظار حتى يتم تحميل الشبكة بالكامل (مثل HakuNeko)
|
| 24 |
page.goto(url, wait_until="networkidle", timeout=60000)
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
page.mouse.wheel(0, 1000)
|
| 29 |
time.sleep(1)
|
| 30 |
|
| 31 |
-
# استخراج روابط الصور التي تنتهي بامتدادات صور معروفة
|
| 32 |
images = page.query_selector_all("img")
|
| 33 |
-
|
| 34 |
-
|
| 35 |
for img in images:
|
| 36 |
src = img.get_attribute("src") or img.get_attribute("data-src")
|
| 37 |
if src and any(ext in src.lower() for ext in ['.jpg', '.jpeg', '.png', '.webp']):
|
| 38 |
-
# تحويل الصورة إلى Bytes
|
| 39 |
res = page.request.get(src)
|
| 40 |
-
if res.status == 200 and len(res.body()) >
|
| 41 |
-
|
| 42 |
-
|
| 43 |
browser.close()
|
| 44 |
-
return
|
| 45 |
-
except
|
| 46 |
browser.close()
|
| 47 |
return []
|
| 48 |
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
base_part = re.sub(r'/(?:chapter-)?\d+$', '', sample_url.strip().rstrip('/'))
|
| 51 |
is_azora = "azoramoon" in sample_url
|
| 52 |
pdf_files = []
|
| 53 |
|
| 54 |
for i in range(int(start), int(end) + 1):
|
| 55 |
target_url = f"{base_part}/chapter-{i}" if is_azora else f"{base_part}/{i}"
|
| 56 |
-
imgs =
|
| 57 |
-
|
| 58 |
if imgs:
|
| 59 |
fname = f"Chapter_{i}.pdf"
|
| 60 |
imgs[0].save(fname, save_all=True, append_images=imgs[1:], format='PDF')
|
|
@@ -62,34 +159,14 @@ def process_engine(sample_url, start, end):
|
|
| 62 |
|
| 63 |
if not pdf_files: return None
|
| 64 |
|
| 65 |
-
zip_name = f"
|
| 66 |
with zipfile.ZipFile(zip_name, 'w') as zipf:
|
| 67 |
for f in pdf_files:
|
| 68 |
zipf.write(f); os.remove(f)
|
| 69 |
return zip_name
|
| 70 |
|
| 71 |
-
|
| 72 |
-
def handle_msg(message):
|
| 73 |
-
try:
|
| 74 |
-
url, range_part = message.text.strip().split(' ')
|
| 75 |
-
start, end = range_part.split('-')
|
| 76 |
-
|
| 77 |
-
status = bot.reply_to(message, "🚀 جاري تشغيل محرك المحاكاة... قد يستغرق وقتاً أطول لتجاوز الحماية.")
|
| 78 |
-
zip_path = process_engine(url, start, end)
|
| 79 |
-
|
| 80 |
-
if zip_path:
|
| 81 |
-
with open(zip_path, 'rb') as f:
|
| 82 |
-
bot.send_document(message.chat.id, f, caption=f"✅ المحرك نجح في سحب الفصول {start}-{end}")
|
| 83 |
-
os.remove(zip_path)
|
| 84 |
-
bot.delete_message(message.chat.id, status.message_id)
|
| 85 |
-
else:
|
| 86 |
-
bot.edit_message_text("❌ المحرك لم يجد صوراً. قد تكون الحماية متطورة جداً.", message.chat.id, status.message_id)
|
| 87 |
-
except:
|
| 88 |
-
bot.reply_to(message, "⚠️ التنسيق: الرابط مسافة 1-5")
|
| 89 |
-
|
| 90 |
-
# تشغيل البوت والسيرفر
|
| 91 |
if __name__ == "__main__":
|
| 92 |
-
# تثبيت المتصفحات المطلوبة لـ Playwright عند التشغيل الأول
|
| 93 |
os.system("playwright install chromium")
|
| 94 |
threading.Thread(target=lambda: bot.infinity_polling(), daemon=True).start()
|
| 95 |
app.run(host="0.0.0.0", port=7860)
|
|
|
|
| 3 |
from playwright.sync_api import sync_playwright
|
| 4 |
from PIL import Image
|
| 5 |
import img2pdf
|
| 6 |
+
from telebot import types
|
| 7 |
|
| 8 |
+
# --- الإعدادات العامة ---
|
| 9 |
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
| 10 |
+
ADMIN_HANDLE = "@svipfast"
|
| 11 |
bot = telebot.TeleBot(BOT_TOKEN, threaded=False)
|
| 12 |
app = Flask(__name__)
|
| 13 |
|
| 14 |
+
# تخزين مؤقت لبيانات المستخدمين
|
| 15 |
+
user_data = {}
|
| 16 |
+
|
| 17 |
@app.route('/')
|
| 18 |
+
def home(): return "<h1>Manga Engine Professional is Running</h1>"
|
| 19 |
|
| 20 |
+
# --- محرك السحب (Playwright) ---
|
| 21 |
+
def fetch_images(url):
|
| 22 |
with sync_playwright() as p:
|
|
|
|
| 23 |
browser = p.chromium.launch(headless=True)
|
| 24 |
context = browser.new_context(user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
| 25 |
page = context.new_page()
|
|
|
|
| 26 |
try:
|
|
|
|
| 27 |
page.goto(url, wait_until="networkidle", timeout=60000)
|
| 28 |
+
# التمرير لضمان تحميل الصور (Lazy Load)
|
| 29 |
+
for _ in range(4):
|
| 30 |
+
page.mouse.wheel(0, 1500)
|
|
|
|
| 31 |
time.sleep(1)
|
| 32 |
|
|
|
|
| 33 |
images = page.query_selector_all("img")
|
| 34 |
+
img_list = []
|
|
|
|
| 35 |
for img in images:
|
| 36 |
src = img.get_attribute("src") or img.get_attribute("data-src")
|
| 37 |
if src and any(ext in src.lower() for ext in ['.jpg', '.jpeg', '.png', '.webp']):
|
|
|
|
| 38 |
res = page.request.get(src)
|
| 39 |
+
if res.status == 200 and len(res.body()) > 25000:
|
| 40 |
+
img_list.append(Image.open(io.BytesIO(res.body())).convert('RGB'))
|
|
|
|
| 41 |
browser.close()
|
| 42 |
+
return img_list
|
| 43 |
+
except:
|
| 44 |
browser.close()
|
| 45 |
return []
|
| 46 |
|
| 47 |
+
# --- واجهة البوت ---
|
| 48 |
+
|
| 49 |
+
@bot.message_handler(commands=['start'])
|
| 50 |
+
def send_welcome(message):
|
| 51 |
+
markup = types.InlineKeyboardMarkup(row_width=1)
|
| 52 |
+
btn1 = types.InlineKeyboardButton("📥 ابدأ تحميل مانجا", callback_data="start_download")
|
| 53 |
+
btn2 = types.InlineKeyboardButton("ℹ️ شرح طريقة الروابط", callback_data="how_to_url")
|
| 54 |
+
btn3 = types.InlineKeyboardButton("👨💻 تواصل مع الإدارة", url=f"https://t.me/{ADMIN_HANDLE.replace('@','')}")
|
| 55 |
+
markup.add(btn1, btn2, btn3)
|
| 56 |
+
|
| 57 |
+
welcome_text = (
|
| 58 |
+
"<b>مرحباً بك في نظام سحب المانجا الإحترافي 🚀</b>\n\n"
|
| 59 |
+
"هذا النظام مدعوم بمحرك محاكاة متطور لتجاوز الحماية وسحب الفصول بأعلى جودة ممكنة.\n\n"
|
| 60 |
+
f"للدعم الفني: {ADMIN_HANDLE}"
|
| 61 |
+
)
|
| 62 |
+
bot.send_message(message.chat.id, welcome_text, parse_mode="HTML", reply_markup=markup)
|
| 63 |
+
|
| 64 |
+
@bot.callback_query_handler(func=lambda call: True)
|
| 65 |
+
def callback_query(call):
|
| 66 |
+
if call.data == "start_download":
|
| 67 |
+
bot.answer_callback_query(call.id)
|
| 68 |
+
msg = bot.send_message(call.message.chat.id, "<b>الرجاء إرسال رابط المانجا المراد تحميلها:</b>", parse_mode="HTML")
|
| 69 |
+
bot.register_next_step_handler(msg, get_manga_url)
|
| 70 |
+
|
| 71 |
+
elif call.data == "how_to_url":
|
| 72 |
+
help_text = (
|
| 73 |
+
"<b>💡 كيف تحصل على الرابط الصحيح؟</b>\n\n"
|
| 74 |
+
"يجب أن ينتهي الرابط برقم الفصل ليعمل النظام تلقائياً:\n\n"
|
| 75 |
+
"✅ <b>أزورا:</b>\n<code>https://azoramoon.com/series/manga-name/chapter-1</code>\n\n"
|
| 76 |
+
"✅ <b>أوليمبوس:</b>\n<code>https://olympustaff.com/series/manga-name/1</code>\n\n"
|
| 77 |
+
"⚠️ تأكد من نسخ الرابط من المتصفح مباشرة."
|
| 78 |
+
)
|
| 79 |
+
bot.send_message(call.message.chat.id, help_text, parse_mode="HTML")
|
| 80 |
+
|
| 81 |
+
def get_manga_url(message):
|
| 82 |
+
url = message.text.strip()
|
| 83 |
+
if not url.startswith("http"):
|
| 84 |
+
bot.reply_to(message, "❌ الرابط غير صالح. يرجى البدء بـ http")
|
| 85 |
+
return
|
| 86 |
+
|
| 87 |
+
user_data[message.chat.id] = {'url': url}
|
| 88 |
+
|
| 89 |
+
markup = types.InlineKeyboardMarkup()
|
| 90 |
+
markup.add(types.InlineKeyboardButton("🎯 فصل واحد فقط", callback_data="type_single"),
|
| 91 |
+
types.InlineKeyboardButton("📦 حزمة فصول (مدى)", callback_data="type_range"))
|
| 92 |
+
markup.add(types.InlineKeyboardButton("⏩ تحميل 10 فصول قادمة", callback_data="type_auto_10"))
|
| 93 |
+
|
| 94 |
+
bot.send_message(message.chat.id, "<b>اختر طريقة التحميل المفضلة:</b>", parse_mode="HTML", reply_markup=markup)
|
| 95 |
+
|
| 96 |
+
@bot.callback_query_handler(func=lambda call: call.data.startswith("type_"))
|
| 97 |
+
def handle_type_selection(call):
|
| 98 |
+
chat_id = call.message.chat.id
|
| 99 |
+
if chat_id not in user_data: return
|
| 100 |
+
|
| 101 |
+
if call.data == "type_single":
|
| 102 |
+
msg = bot.send_message(chat_id, "🔢 أرسل رقم الفصل المراد تحميله:")
|
| 103 |
+
bot.register_next_step_handler(msg, lambda m: start_process(m, "single"))
|
| 104 |
+
|
| 105 |
+
elif call.data == "type_range":
|
| 106 |
+
msg = bot.send_message(chat_id, "🔢 أرسل النطاق المطلوب (مثال: 1-10):")
|
| 107 |
+
bot.register_next_step_handler(msg, lambda m: start_process(m, "range"))
|
| 108 |
+
|
| 109 |
+
elif call.data == "type_auto_10":
|
| 110 |
+
msg = bot.send_message(chat_id, "🔢 أرسل رقم الفصل الذي تريد البدء منه:")
|
| 111 |
+
bot.register_next_step_handler(msg, lambda m: start_process(m, "auto10"))
|
| 112 |
+
|
| 113 |
+
def start_process(message, mode):
|
| 114 |
+
chat_id = message.chat.id
|
| 115 |
+
url = user_data[chat_id]['url']
|
| 116 |
+
|
| 117 |
+
try:
|
| 118 |
+
if mode == "single":
|
| 119 |
+
start, end = message.text, message.text
|
| 120 |
+
elif mode == "range":
|
| 121 |
+
start, end = message.text.split('-')
|
| 122 |
+
elif mode == "auto10":
|
| 123 |
+
start = int(message.text)
|
| 124 |
+
end = start + 9
|
| 125 |
+
|
| 126 |
+
status_msg = bot.send_message(chat_id, f"<b>⏳ جاري تشغيل المحرك...</b>\n🔄 معالجة الفصول من {start} إلى {end}", parse_mode="HTML")
|
| 127 |
+
|
| 128 |
+
# تنفيذ السحب
|
| 129 |
+
zip_path = run_engine_logic(url, start, end)
|
| 130 |
+
|
| 131 |
+
if zip_path:
|
| 132 |
+
with open(zip_path, 'rb') as f:
|
| 133 |
+
caption = (
|
| 134 |
+
"<b>✅ تمت عملية السحب بنجاح</b>\n\n"
|
| 135 |
+
f"📂 <b>الحزمة:</b> {start} - {end}\n"
|
| 136 |
+
f"👤 <b>بواسطة:</b> {ADMIN_HANDLE}"
|
| 137 |
+
)
|
| 138 |
+
bot.send_document(chat_id, f, caption=caption, parse_mode="HTML")
|
| 139 |
+
os.remove(zip_path)
|
| 140 |
+
bot.delete_message(chat_id, status_msg.message_id)
|
| 141 |
+
else:
|
| 142 |
+
bot.edit_message_text(f"❌ فشل المحرك في إيجاد الصور. تواصل مع {ADMIN_HANDLE}", chat_id, status_msg.message_id)
|
| 143 |
+
|
| 144 |
+
except Exception as e:
|
| 145 |
+
bot.send_message(chat_id, "⚠️ حدث خطأ في إدخال البيانات. يرجى المحاولة مرة أخرى.")
|
| 146 |
+
|
| 147 |
+
def run_engine_logic(sample_url, start, end):
|
| 148 |
base_part = re.sub(r'/(?:chapter-)?\d+$', '', sample_url.strip().rstrip('/'))
|
| 149 |
is_azora = "azoramoon" in sample_url
|
| 150 |
pdf_files = []
|
| 151 |
|
| 152 |
for i in range(int(start), int(end) + 1):
|
| 153 |
target_url = f"{base_part}/chapter-{i}" if is_azora else f"{base_part}/{i}"
|
| 154 |
+
imgs = fetch_images(target_url)
|
|
|
|
| 155 |
if imgs:
|
| 156 |
fname = f"Chapter_{i}.pdf"
|
| 157 |
imgs[0].save(fname, save_all=True, append_images=imgs[1:], format='PDF')
|
|
|
|
| 159 |
|
| 160 |
if not pdf_files: return None
|
| 161 |
|
| 162 |
+
zip_name = f"Manga_Pack_{int(time.time())}.zip"
|
| 163 |
with zipfile.ZipFile(zip_name, 'w') as zipf:
|
| 164 |
for f in pdf_files:
|
| 165 |
zipf.write(f); os.remove(f)
|
| 166 |
return zip_name
|
| 167 |
|
| 168 |
+
# --- تشغيل التطبيق ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
if __name__ == "__main__":
|
|
|
|
| 170 |
os.system("playwright install chromium")
|
| 171 |
threading.Thread(target=lambda: bot.infinity_polling(), daemon=True).start()
|
| 172 |
app.run(host="0.0.0.0", port=7860)
|