Update app.py
Browse files
app.py
CHANGED
|
@@ -1,187 +1,105 @@
|
|
| 1 |
import os, time, threading, zipfile, io, re, telebot, requests
|
| 2 |
-
from flask import Flask
|
| 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 |
-
|
|
|
|
| 16 |
|
| 17 |
@app.route('/')
|
| 18 |
-
def home(): return "<h1>
|
| 19 |
|
| 20 |
-
# --- محرك الس
|
| 21 |
-
def
|
| 22 |
with sync_playwright() as p:
|
| 23 |
browser = p.chromium.launch(headless=True)
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
for _ in range(5):
|
| 30 |
-
page.mouse.wheel(0, 1200)
|
| 31 |
-
time.sleep(0.8)
|
| 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") or img.get_attribute("data-lazy-src")
|
| 37 |
-
if src and any(ext in src.lower() for ext in ['.jpg', '.jpeg', '.png', '.webp', '.avif']):
|
| 38 |
-
if "logo" in src.lower() or "banner" in src.lower(): continue
|
| 39 |
-
try:
|
| 40 |
-
res = page.request.get(src)
|
| 41 |
-
if res.status == 200 and len(res.body()) > 25000:
|
| 42 |
-
img_list.append(Image.open(io.BytesIO(res.body())).convert('RGB'))
|
| 43 |
-
except: continue
|
| 44 |
-
browser.close()
|
| 45 |
-
return img_list
|
| 46 |
-
except:
|
| 47 |
-
browser.close()
|
| 48 |
-
return []
|
| 49 |
-
|
| 50 |
-
# --- لوحة التحكم والترحيب ---
|
| 51 |
-
@bot.message_handler(commands=['start'])
|
| 52 |
-
def welcome(message):
|
| 53 |
-
user_states[message.chat.id] = {} # تصفير حالة المستخدم
|
| 54 |
-
markup = types.InlineKeyboardMarkup(row_width=1)
|
| 55 |
-
btn_start = types.InlineKeyboardButton("📥 إرسال رابط جديد", callback_data="new_order")
|
| 56 |
-
btn_help = types.InlineKeyboardButton("💡 كيف أحصل على الرابط؟", callback_data="help_link")
|
| 57 |
-
btn_admin = types.InlineKeyboardButton("👨💻 المطور", url=f"https://t.me/{ADMIN_HANDLE.replace('@','')}")
|
| 58 |
-
markup.add(btn_start, btn_help, btn_admin)
|
| 59 |
-
|
| 60 |
-
bot.send_message(message.chat.id,
|
| 61 |
-
f"<b>مرحباً بك في بوت سحب المانجا الإحترافي 🚀</b>\n\n"
|
| 62 |
-
f"النظام يعمل بمحرك محاكاة لتجاوز الحمايات.\n"
|
| 63 |
-
f"المطور المسؤول: {ADMIN_HANDLE}",
|
| 64 |
-
parse_mode="HTML", reply_markup=markup)
|
| 65 |
-
|
| 66 |
-
# --- معالجة الضغط على الأزرار ---
|
| 67 |
-
@bot.callback_query_handler(func=lambda call: True)
|
| 68 |
-
def handle_query(call):
|
| 69 |
-
chat_id = call.message.chat.id
|
| 70 |
-
|
| 71 |
-
if call.data == "new_order":
|
| 72 |
-
msg = bot.send_message(chat_id, "<b>الآن، أرسل رابط المانجا (رابط أي فصل):</b>", parse_mode="HTML")
|
| 73 |
-
bot.register_next_step_handler(msg, process_url_step)
|
| 74 |
-
|
| 75 |
-
elif call.data == "help_link":
|
| 76 |
-
help_txt = (
|
| 77 |
-
"<b>طريقة إرسال الرابط الصحيحة:</b>\n\n"
|
| 78 |
-
"انسخ رابط الفصل من الموقع مباشرة، مثال:\n"
|
| 79 |
-
"<code>https://olympustaff.com/series/manga-name/1</code>"
|
| 80 |
-
)
|
| 81 |
-
bot.send_message(chat_id, help_txt, parse_mode="HTML")
|
| 82 |
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
elif mode == "range":
|
| 91 |
-
msg = bot.send_message(chat_id, "🔢 أرسل <b>المدى</b> (مثال: 1-10):")
|
| 92 |
-
elif mode == "auto10":
|
| 93 |
-
msg = bot.send_message(chat_id, "🔢 أرسل <b>رقم الفصل</b> الذي سنبدأ التحميل منه:")
|
| 94 |
-
|
| 95 |
-
bot.register_next_step_handler(msg, final_execution_step)
|
| 96 |
-
|
| 97 |
-
bot.answer_callback_query(call.id)
|
| 98 |
|
| 99 |
-
# --- ال
|
| 100 |
-
def
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
bot.send_message(message.chat.id, "❌ الرابط غير صحيح، أرسل رابط يبدأ بـ http")
|
| 104 |
-
return
|
| 105 |
-
|
| 106 |
-
user_states[message.chat.id] = {'url': url}
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
markup.add(
|
| 111 |
-
types.InlineKeyboardButton("🎯 فصل واحد", callback_data="mode_single"),
|
| 112 |
-
types.InlineKeyboardButton("📦 حزمة فصول", callback_data="mode_range"),
|
| 113 |
-
types.InlineKeyboardButton("⏩ تحميل 10 فصول", callback_data="mode_auto10")
|
| 114 |
-
)
|
| 115 |
-
bot.send_message(message.chat.id, "<b>ممتاز! اختر الآن طريقة التحميل:</b>", parse_mode="HTML", reply_markup=markup)
|
| 116 |
|
| 117 |
-
#
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
bot.send_message(chat_id, "⚠️ حدث خطأ، يرجى البدء من جديد عبر /start")
|
| 123 |
-
return
|
| 124 |
-
|
| 125 |
-
text = message.text.strip()
|
| 126 |
-
mode = state['mode']
|
| 127 |
-
url = state['url']
|
| 128 |
-
|
| 129 |
-
try:
|
| 130 |
-
if mode == "single":
|
| 131 |
-
start, end = int(text), int(text)
|
| 132 |
-
elif mode == "range":
|
| 133 |
-
start, end = map(int, text.split('-'))
|
| 134 |
-
elif mode == "auto10":
|
| 135 |
-
start = int(text)
|
| 136 |
-
end = start + 9
|
| 137 |
-
|
| 138 |
-
status_msg = bot.send_message(chat_id, f"<b>🔄 جاري تشغيل المحرك الذكي...</b>\n📦 سحب الفصول: {start} ⬅️ {end}", parse_mode="HTML")
|
| 139 |
-
|
| 140 |
-
# تشغيل السحب
|
| 141 |
-
zip_path = run_manga_engine(url, start, end)
|
| 142 |
-
|
| 143 |
-
if zip_path:
|
| 144 |
-
with open(zip_path, 'rb') as f:
|
| 145 |
-
caption = (
|
| 146 |
-
f"<b>✅ اكتملت عملية السحب بنجاح</b>\n\n"
|
| 147 |
-
f"📂 <b>النطاق:</b> {start} إلى {end}\n"
|
| 148 |
-
f"👨💻 <b>بواسطة:</b> {ADMIN_HANDLE}"
|
| 149 |
-
)
|
| 150 |
-
bot.send_document(chat_id, f, caption=caption, parse_mode="HTML")
|
| 151 |
-
os.remove(zip_path)
|
| 152 |
-
bot.delete_message(chat_id, status_msg.message_id)
|
| 153 |
else:
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
except Exception as e:
|
| 157 |
-
bot.send_message(chat_id, "⚠️ خطأ في إدخال الأرقام. يرجى التأكد من كتابتها بشكل صحيح (مثال: 1-5).")
|
| 158 |
|
| 159 |
-
#
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
if not pdf_files: return None
|
| 174 |
-
|
| 175 |
-
zip_name = f"Manga_Pack_{int(time.time())}.zip"
|
| 176 |
-
with zipfile.ZipFile(zip_name, 'w') as zipf:
|
| 177 |
-
for f in pdf_files:
|
| 178 |
-
zipf.write(f)
|
| 179 |
-
os.remove(f)
|
| 180 |
-
return zip_name
|
| 181 |
|
| 182 |
-
# --- ا
|
| 183 |
-
|
| 184 |
-
# تنصيب متصفح Playwright
|
| 185 |
-
os.system("playwright install chromium")
|
| 186 |
-
threading.Thread(target=lambda: bot.infinity_polling(), daemon=True).start()
|
| 187 |
-
app.run(host="0.0.0.0", port=7860)
|
|
|
|
| 1 |
import os, time, threading, zipfile, io, re, telebot, requests
|
| 2 |
+
from flask import Flask
|
| 3 |
from playwright.sync_api import sync_playwright
|
| 4 |
from PIL import Image
|
|
|
|
| 5 |
from telebot import types
|
| 6 |
|
| 7 |
# --- الإعدادات ---
|
| 8 |
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
| 9 |
+
# معرف القناة التي سيتم تخزين الملفات فيها (الأرشيف)
|
| 10 |
+
ARCHIVE_CH = os.getenv("ARCHIVE_CHANNEL_ID")
|
| 11 |
ADMIN_HANDLE = "@svipfast"
|
| 12 |
+
|
| 13 |
bot = telebot.TeleBot(BOT_TOKEN, threaded=False)
|
| 14 |
app = Flask(__name__)
|
| 15 |
|
| 16 |
+
# قاموس لتخزين "معرفات الملفات" (File IDs) لتجنب إعادة الرفع
|
| 17 |
+
# في الإنتاج الحقيقي، يفضل استخدام قاعدة بيانات بسيطة مثل SQLite
|
| 18 |
+
archive_db = {}
|
| 19 |
|
| 20 |
@app.route('/')
|
| 21 |
+
def home(): return "<h1>Archive Engine Pro is Running</h1>"
|
| 22 |
|
| 23 |
+
# --- وظيفة السحب (نفس المحرك السابق لكن محسن) ---
|
| 24 |
+
def fetch_and_pack(url, start, end):
|
| 25 |
with sync_playwright() as p:
|
| 26 |
browser = p.chromium.launch(headless=True)
|
| 27 |
+
page = browser.new_page()
|
| 28 |
+
downloaded_files = []
|
| 29 |
+
|
| 30 |
+
base_part = re.sub(r'/(?:chapter-)?\d+$', '', url.strip().rstrip('/'))
|
| 31 |
+
is_azora = "azoramoon" in url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
+
for i in range(start, end + 1):
|
| 34 |
+
target_url = f"{base_part}/chapter-{i}" if is_azora else f"{base_part}/{i}"
|
| 35 |
+
try:
|
| 36 |
+
page.goto(target_url, wait_until="networkidle", timeout=60000)
|
| 37 |
+
# تمرير ذكي
|
| 38 |
+
page.evaluate("window.scrollTo(0, document.body.scrollHeight/2)")
|
| 39 |
+
time.sleep(1)
|
| 40 |
+
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
|
| 41 |
+
|
| 42 |
+
images = page.query_selector_all("img")
|
| 43 |
+
img_data = []
|
| 44 |
+
for img in images:
|
| 45 |
+
src = img.get_attribute("src") or img.get_attribute("data-src")
|
| 46 |
+
if src and any(x in src.lower() for x in ['.jpg', '.jpeg', '.png', '.webp']):
|
| 47 |
+
if "logo" in src.lower() or "banner" in src.lower(): continue
|
| 48 |
+
res = requests.get(src, timeout=10)
|
| 49 |
+
if res.status_code == 200 and len(res.content) > 25000:
|
| 50 |
+
img_data.append(res.content)
|
| 51 |
+
|
| 52 |
+
if img_data:
|
| 53 |
+
# ضغط الصور في ZIP للفصل الواحد
|
| 54 |
+
ch_zip = f"CH_{i}.zip"
|
| 55 |
+
with zipfile.ZipFile(ch_zip, 'w') as z:
|
| 56 |
+
for idx, data in enumerate(img_data):
|
| 57 |
+
z.writestr(f"{idx:03d}.jpg", data)
|
| 58 |
+
downloaded_files.append((i, ch_zip))
|
| 59 |
+
except: continue
|
| 60 |
|
| 61 |
+
browser.close()
|
| 62 |
+
return downloaded_files
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
# --- معالجة الطلب بنظام الأرشيف ---
|
| 65 |
+
def smart_process(chat_id, url, start, end, status_msg):
|
| 66 |
+
# توليد مفتاح فريد للمانجا بناءً على الرابط
|
| 67 |
+
manga_id = re.sub(r'\W+', '', url.split('/')[-2])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
+
needed_chapters = []
|
| 70 |
+
found_files = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
+
# 1. البحث في الأرشيف أولاً
|
| 73 |
+
for i in range(start, end + 1):
|
| 74 |
+
key = f"{manga_id}_{i}"
|
| 75 |
+
if key in archive_db:
|
| 76 |
+
found_files.append(archive_db[key])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
else:
|
| 78 |
+
needed_chapters.append(i)
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
+
# 2. إذا كانت هناك فصول ناقصة، نسحبها
|
| 81 |
+
if needed_chapters:
|
| 82 |
+
bot.edit_message_text(f"🔍 الفصل/الفصول {needed_chapters} غير موجودة في الأرشيف.. جاري السحب الآن.", chat_id, status_msg.message_id)
|
| 83 |
+
new_files = fetch_and_pack(url, min(needed_chapters), max(needed_chapters))
|
| 84 |
+
|
| 85 |
+
for ch_num, file_path in new_files:
|
| 86 |
+
# رفع الملف للقناة الخاصة (الأرشيف) للحفظ الدائم
|
| 87 |
+
with open(file_path, 'rb') as f:
|
| 88 |
+
archived_msg = bot.send_document(ARCHIVE_CH, f, caption=f"Archive: {manga_id} | Ch: {ch_num}")
|
| 89 |
+
# حفظ الـ File ID لاستخدامه لاحقاً دون إعادة رفع
|
| 90 |
+
file_id = archived_msg.document.file_id
|
| 91 |
+
archive_db[f"{manga_id}_{ch_num}"] = file_id
|
| 92 |
+
found_files.append(file_id)
|
| 93 |
+
os.remove(file_path)
|
| 94 |
|
| 95 |
+
# 3. إرسال جميع الملفات للمستخدم (بسرعة البرق من سيرفرات تليجرام)
|
| 96 |
+
if found_files:
|
| 97 |
+
bot.send_message(chat_id, f"✅ تم تجهيز الحزمة لك من الأرشيف الذكي:")
|
| 98 |
+
for fid in found_files:
|
| 99 |
+
bot.send_document(chat_id, fid)
|
| 100 |
+
bot.delete_message(chat_id, status_msg.message_id)
|
| 101 |
+
else:
|
| 102 |
+
bot.edit_message_text("❌ فشل السحب بالكامل.", chat_id, status_msg.message_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
+
# --- استكمال الأزرار والـ Start كما في النسخ السابقة ---
|
| 105 |
+
# ملاحظة: في execute() استدعي smart_process() بدلاً من المحرك القديم.
|
|
|
|
|
|
|
|
|
|
|
|