Update bot.py
Browse files
bot.py
CHANGED
|
@@ -4,14 +4,14 @@ import logging
|
|
| 4 |
import time
|
| 5 |
import threading
|
| 6 |
import shutil
|
| 7 |
-
from typing import Optional, List, Dict, Any
|
| 8 |
import aiosqlite
|
| 9 |
import gradio as gr
|
| 10 |
-
from hydrogram import Client, filters
|
| 11 |
from hydrogram.errors import FloodWait, RPCError
|
| 12 |
from hydrogram.types import (
|
| 13 |
InputMediaPhoto, InputMediaVideo, InputMediaDocument,
|
| 14 |
-
InputMediaAudio, Message
|
| 15 |
)
|
| 16 |
from fastapi import FastAPI
|
| 17 |
import uvicorn
|
|
@@ -44,7 +44,7 @@ logging.basicConfig(
|
|
| 44 |
format='%(asctime)s | %(levelname)-7s | %(message)s',
|
| 45 |
datefmt='%Y-%m-%d %H:%M:%S'
|
| 46 |
)
|
| 47 |
-
logger = logging.getLogger("
|
| 48 |
|
| 49 |
# ============ الإعدادات والمتغيرات ============
|
| 50 |
API_ID = int(os.environ.get("API_ID", 0))
|
|
@@ -55,12 +55,12 @@ AUTO_SCAN_ALL = os.environ.get("AUTO_SCAN_ALL", "1") == "1"
|
|
| 55 |
|
| 56 |
DATA_DIR = "/data" if os.path.exists("/data") else "/tmp/data"
|
| 57 |
DOWNLOAD_DIR = os.path.join(DATA_DIR, "downloads")
|
| 58 |
-
DB_PATH = os.path.join(DATA_DIR, "
|
| 59 |
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
|
| 60 |
|
| 61 |
# ============ إنشاء العميل ============
|
| 62 |
app = Client(
|
| 63 |
-
"
|
| 64 |
api_id=API_ID,
|
| 65 |
api_hash=API_HASH,
|
| 66 |
session_string=SESSION_STRING,
|
|
@@ -69,7 +69,7 @@ app = Client(
|
|
| 69 |
max_concurrent_transmissions=4,
|
| 70 |
device_model="Universal Archiver Server",
|
| 71 |
system_version="Linux 64bit",
|
| 72 |
-
app_version="
|
| 73 |
lang_code="ar"
|
| 74 |
)
|
| 75 |
|
|
@@ -88,9 +88,9 @@ stats = {
|
|
| 88 |
"status": "Starting..."
|
| 89 |
}
|
| 90 |
|
| 91 |
-
# ============ إدارة التحكم بالسرعة و
|
| 92 |
class SmartFloodManager:
|
| 93 |
-
def __init__(self, normal_delay: float = 1.
|
| 94 |
self.normal_delay = normal_delay
|
| 95 |
self.last_call = 0.0
|
| 96 |
self.lock = asyncio.Lock()
|
|
@@ -113,9 +113,9 @@ class SmartFloodManager:
|
|
| 113 |
def handle_flood(self, seconds: int):
|
| 114 |
wait_seconds = max(seconds, 5) + 2
|
| 115 |
self.flood_until = time.time() + wait_seconds
|
| 116 |
-
logger.warning(f"🚨 استلام FloodWait
|
| 117 |
|
| 118 |
-
flood_manager = SmartFloodManager(normal_delay=1.
|
| 119 |
|
| 120 |
# ============ قاعدة البيانات ذاتية الإصلاح والتحديث ============
|
| 121 |
def cleanup_corrupted_files():
|
|
@@ -175,7 +175,7 @@ async def init_db():
|
|
| 175 |
chat_type TEXT,
|
| 176 |
last_scraped_id INTEGER DEFAULT 0,
|
| 177 |
total_queued INTEGER DEFAULT 0,
|
| 178 |
-
status TEXT DEFAULT 'idle',
|
| 179 |
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 180 |
)
|
| 181 |
""")
|
|
@@ -381,27 +381,38 @@ async def queue_worker_loop():
|
|
| 381 |
logger.error(f"خطأ في محرك الإرسال: {e}")
|
| 382 |
await asyncio.sleep(4)
|
| 383 |
|
| 384 |
-
# ============ تسخين ذاكرة المحادثات
|
| 385 |
async def warm_up_dialogs_cache() -> List[Dict[str, Any]]:
|
| 386 |
logger.info("🔄 جاري فحص وتسخين ذاكرة القنوات والمجموعات المنضم إليها الحساب...")
|
| 387 |
discovered = []
|
| 388 |
try:
|
| 389 |
async for dialog in app.get_dialogs():
|
| 390 |
chat = dialog.chat
|
|
|
|
|
|
|
|
|
|
| 391 |
if TARGET_CHAT_ID and chat.id == TARGET_CHAT_ID:
|
| 392 |
continue
|
| 393 |
-
if chat.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
chat_info = {
|
| 395 |
"id": chat.id,
|
| 396 |
-
"title":
|
| 397 |
"username": chat.username or "",
|
| 398 |
-
"type":
|
| 399 |
}
|
| 400 |
KNOWN_CHATS[chat.id] = chat_info
|
| 401 |
discovered.append(chat_info)
|
|
|
|
| 402 |
|
| 403 |
stats["discovered_chats"] = len(discovered)
|
| 404 |
-
logger.info(f"✅ تم ا
|
| 405 |
return discovered
|
| 406 |
except Exception as e:
|
| 407 |
logger.error(f"خطأ أثناء جلب المحادثات: {e}")
|
|
@@ -483,7 +494,7 @@ async def scrape_all_joined_chats(limit_per_chat: int = 0) -> str:
|
|
| 483 |
try:
|
| 484 |
chats = await warm_up_dialogs_cache()
|
| 485 |
if not chats:
|
| 486 |
-
return "لم يتم العثور على قنوات أو مجموعات في الحساب."
|
| 487 |
|
| 488 |
logger.info(f"🚀 بدء عملية الفحص الشامل لـ {len(chats)} قناة ومجموعة...")
|
| 489 |
total_all = 0
|
|
@@ -514,12 +525,13 @@ async def realtime_all_chats_handler(client, message: Message):
|
|
| 514 |
if str(message.chat.id) == str(TARGET_CHAT):
|
| 515 |
return
|
| 516 |
|
| 517 |
-
|
|
|
|
| 518 |
KNOWN_CHATS[message.chat.id] = {
|
| 519 |
"id": message.chat.id,
|
| 520 |
"title": message.chat.title or f"Chat_{message.chat.id}",
|
| 521 |
"username": message.chat.username or "",
|
| 522 |
-
"type":
|
| 523 |
}
|
| 524 |
|
| 525 |
if message.media_group_id:
|
|
@@ -570,7 +582,7 @@ def create_dashboard():
|
|
| 570 |
gr.Markdown("""
|
| 571 |
# 🌐 Universal Telegram Archiver Pro (النسخة الشاملة التلقائية)
|
| 572 |
**يسحب من جميع القنوات والكروبات المنضم إليها تلقائياً بدون استثناء وبدون ضياع ملف واحد:**
|
| 573 |
-
* ✅ حل مشكلة `PEER_ID_INVALID` بشكل جذري
|
| 574 |
* ✅ فحص أرشيف كل القنوات والمجموعات المنضم إليها دفعة واحدة تلقائياً.
|
| 575 |
* ✅ رصد فوري لحظي لأي ملف أو ألبوم ينزل في أي قناة أو كروب.
|
| 576 |
* ✅ طابور ذكي دائم مع كتابة المصدر ورقم المقطع ونظام ضد الحظر.
|
|
@@ -591,6 +603,13 @@ def create_dashboard():
|
|
| 591 |
|
| 592 |
scrape_status = gr.Textbox(label="حالة الفحص والعمليات", interactive=False)
|
| 593 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
def trigger_scan_all(lim):
|
| 595 |
if MAIN_LOOP and MAIN_LOOP.is_running():
|
| 596 |
asyncio.run_coroutine_threadsafe(
|
|
@@ -600,20 +619,40 @@ def create_dashboard():
|
|
| 600 |
return "🚀 تم بدء عملية الفحص الشامل لجميع القنوات والكروبات في الخلفية بنجاح! راقب السجلات والطابور."
|
| 601 |
return "❌ تعذر إطلاق المهمة، تأكد من اتصال البوت."
|
| 602 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
scan_all_btn.click(fn=trigger_scan_all, inputs=[limit_input], outputs=[scrape_status])
|
|
|
|
| 604 |
|
| 605 |
return demo
|
| 606 |
|
| 607 |
# ============ تشغيل الويب ============
|
| 608 |
def run_web_server():
|
| 609 |
fastapi_app = FastAPI()
|
| 610 |
-
demo = create_dashboard()
|
| 611 |
-
gr.mount_gradio_app(fastapi_app, demo, path="/")
|
| 612 |
|
| 613 |
@fastapi_app.get("/health")
|
| 614 |
async def health():
|
| 615 |
return {"status": "ok", "stats": stats}
|
| 616 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
uvicorn.run(fastapi_app, host="0.0.0.0", port=7860)
|
| 618 |
|
| 619 |
# ============ نقطة الدخول ============
|
|
|
|
| 4 |
import time
|
| 5 |
import threading
|
| 6 |
import shutil
|
| 7 |
+
from typing import Optional, List, Dict, Any
|
| 8 |
import aiosqlite
|
| 9 |
import gradio as gr
|
| 10 |
+
from hydrogram import Client, filters, enums
|
| 11 |
from hydrogram.errors import FloodWait, RPCError
|
| 12 |
from hydrogram.types import (
|
| 13 |
InputMediaPhoto, InputMediaVideo, InputMediaDocument,
|
| 14 |
+
InputMediaAudio, Message
|
| 15 |
)
|
| 16 |
from fastapi import FastAPI
|
| 17 |
import uvicorn
|
|
|
|
| 44 |
format='%(asctime)s | %(levelname)-7s | %(message)s',
|
| 45 |
datefmt='%Y-%m-%d %H:%M:%S'
|
| 46 |
)
|
| 47 |
+
logger = logging.getLogger("UniversalArchiver")
|
| 48 |
|
| 49 |
# ============ الإعدادات والمتغيرات ============
|
| 50 |
API_ID = int(os.environ.get("API_ID", 0))
|
|
|
|
| 55 |
|
| 56 |
DATA_DIR = "/data" if os.path.exists("/data") else "/tmp/data"
|
| 57 |
DOWNLOAD_DIR = os.path.join(DATA_DIR, "downloads")
|
| 58 |
+
DB_PATH = os.path.join(DATA_DIR, "ultra_archiver_v5.db")
|
| 59 |
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
|
| 60 |
|
| 61 |
# ============ إنشاء العميل ============
|
| 62 |
app = Client(
|
| 63 |
+
"universal_archiver",
|
| 64 |
api_id=API_ID,
|
| 65 |
api_hash=API_HASH,
|
| 66 |
session_string=SESSION_STRING,
|
|
|
|
| 69 |
max_concurrent_transmissions=4,
|
| 70 |
device_model="Universal Archiver Server",
|
| 71 |
system_version="Linux 64bit",
|
| 72 |
+
app_version="5.0.0",
|
| 73 |
lang_code="ar"
|
| 74 |
)
|
| 75 |
|
|
|
|
| 88 |
"status": "Starting..."
|
| 89 |
}
|
| 90 |
|
| 91 |
+
# ============ إدارة التحكم بالسرعة ومكافحة الحظر ============
|
| 92 |
class SmartFloodManager:
|
| 93 |
+
def __init__(self, normal_delay: float = 1.5):
|
| 94 |
self.normal_delay = normal_delay
|
| 95 |
self.last_call = 0.0
|
| 96 |
self.lock = asyncio.Lock()
|
|
|
|
| 113 |
def handle_flood(self, seconds: int):
|
| 114 |
wait_seconds = max(seconds, 5) + 2
|
| 115 |
self.flood_until = time.time() + wait_seconds
|
| 116 |
+
logger.warning(f"🚨 استلام FloodWait: {seconds} ثانية. تهدئة العمليات حتى {wait_seconds} ثانية.")
|
| 117 |
|
| 118 |
+
flood_manager = SmartFloodManager(normal_delay=1.6)
|
| 119 |
|
| 120 |
# ============ قاعدة البيانات ذاتية الإصلاح والتحديث ============
|
| 121 |
def cleanup_corrupted_files():
|
|
|
|
| 175 |
chat_type TEXT,
|
| 176 |
last_scraped_id INTEGER DEFAULT 0,
|
| 177 |
total_queued INTEGER DEFAULT 0,
|
| 178 |
+
status TEXT DEFAULT 'idle',
|
| 179 |
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 180 |
)
|
| 181 |
""")
|
|
|
|
| 381 |
logger.error(f"خطأ في محرك الإرسال: {e}")
|
| 382 |
await asyncio.sleep(4)
|
| 383 |
|
| 384 |
+
# ============ فحص وتسخين الذاكرة لجميع المحادثات ============
|
| 385 |
async def warm_up_dialogs_cache() -> List[Dict[str, Any]]:
|
| 386 |
logger.info("🔄 جاري فحص وتسخين ذاكرة القنوات والمجموعات المنضم إليها الحساب...")
|
| 387 |
discovered = []
|
| 388 |
try:
|
| 389 |
async for dialog in app.get_dialogs():
|
| 390 |
chat = dialog.chat
|
| 391 |
+
if not chat:
|
| 392 |
+
continue
|
| 393 |
+
|
| 394 |
if TARGET_CHAT_ID and chat.id == TARGET_CHAT_ID:
|
| 395 |
continue
|
| 396 |
+
if str(chat.id) == str(TARGET_CHAT):
|
| 397 |
+
continue
|
| 398 |
+
|
| 399 |
+
type_str = str(chat.type).lower()
|
| 400 |
+
is_group_or_channel = any(k in type_str for k in ["channel", "supergroup", "group"])
|
| 401 |
+
|
| 402 |
+
if is_group_or_channel:
|
| 403 |
+
title = chat.title or chat.first_name or f"Chat_{chat.id}"
|
| 404 |
chat_info = {
|
| 405 |
"id": chat.id,
|
| 406 |
+
"title": title,
|
| 407 |
"username": chat.username or "",
|
| 408 |
+
"type": type_str
|
| 409 |
}
|
| 410 |
KNOWN_CHATS[chat.id] = chat_info
|
| 411 |
discovered.append(chat_info)
|
| 412 |
+
logger.info(f"📌 تم اكتشاف: [{title}] | الآيدي: ({chat.id}) | النوع: ({type_str})")
|
| 413 |
|
| 414 |
stats["discovered_chats"] = len(discovered)
|
| 415 |
+
logger.info(f"✅ اكتمل الفحص: تم تأمين وتسجيل {len(discovered)} قناة ومجموعة بنجاح!")
|
| 416 |
return discovered
|
| 417 |
except Exception as e:
|
| 418 |
logger.error(f"خطأ أثناء جلب المحادثات: {e}")
|
|
|
|
| 494 |
try:
|
| 495 |
chats = await warm_up_dialogs_cache()
|
| 496 |
if not chats:
|
| 497 |
+
return "لم يتم العثور على قنوات أو مجموعات في الحساب. تأكد من أن الحساب منضم إلى قنوات."
|
| 498 |
|
| 499 |
logger.info(f"🚀 بدء عملية الفحص الشامل لـ {len(chats)} قناة ومجموعة...")
|
| 500 |
total_all = 0
|
|
|
|
| 525 |
if str(message.chat.id) == str(TARGET_CHAT):
|
| 526 |
return
|
| 527 |
|
| 528 |
+
type_str = str(message.chat.type).lower()
|
| 529 |
+
if message.chat.id not in KNOWN_CHATS and any(k in type_str for k in ["channel", "supergroup", "group"]):
|
| 530 |
KNOWN_CHATS[message.chat.id] = {
|
| 531 |
"id": message.chat.id,
|
| 532 |
"title": message.chat.title or f"Chat_{message.chat.id}",
|
| 533 |
"username": message.chat.username or "",
|
| 534 |
+
"type": type_str
|
| 535 |
}
|
| 536 |
|
| 537 |
if message.media_group_id:
|
|
|
|
| 582 |
gr.Markdown("""
|
| 583 |
# 🌐 Universal Telegram Archiver Pro (النسخة الشاملة التلقائية)
|
| 584 |
**يسحب من جميع القنوات والكروبات المنضم إليها تلقائياً بدون استثناء وبدون ضياع ملف واحد:**
|
| 585 |
+
* ✅ حل مشكلة `PEER_ID_INVALID` بشكل جذري.
|
| 586 |
* ✅ فحص أرشيف كل القنوات والمجموعات المنضم إليها دفعة واحدة تلقائياً.
|
| 587 |
* ✅ رصد فوري لحظي لأي ملف أو ألبوم ينزل في أي قناة أو كروب.
|
| 588 |
* ✅ طابور ذكي دائم مع كتابة المصدر ورقم المقطع ونظام ضد الحظر.
|
|
|
|
| 603 |
|
| 604 |
scrape_status = gr.Textbox(label="حالة الفحص والعمليات", interactive=False)
|
| 605 |
|
| 606 |
+
gr.Markdown("---")
|
| 607 |
+
gr.Markdown("### 🎯 فحص قناة أو كروب محدد (اختياري):")
|
| 608 |
+
with gr.Row():
|
| 609 |
+
single_ch_input = gr.Textbox(label="معرف أو رابط القناة", placeholder="@my_channel أو https://t.me/...")
|
| 610 |
+
single_lim_input = gr.Number(label="الحد الأقصى (0 = الكل)", value=0)
|
| 611 |
+
single_scan_btn = gr.Button("فحص هذه القناة فقط 📥")
|
| 612 |
+
|
| 613 |
def trigger_scan_all(lim):
|
| 614 |
if MAIN_LOOP and MAIN_LOOP.is_running():
|
| 615 |
asyncio.run_coroutine_threadsafe(
|
|
|
|
| 619 |
return "🚀 تم بدء عملية الفحص الشامل لجميع القنوات والكروبات في الخلفية بنجاح! راقب السجلات والطابور."
|
| 620 |
return "❌ تعذر إطلاق المهمة، تأكد من اتصال البوت."
|
| 621 |
|
| 622 |
+
def trigger_single_scan(ch, lim):
|
| 623 |
+
if not ch or not ch.strip():
|
| 624 |
+
return "⚠️ يرجى إدخال معرف القناة أولاً."
|
| 625 |
+
if MAIN_LOOP and MAIN_LOOP.is_running():
|
| 626 |
+
async def do_single():
|
| 627 |
+
try:
|
| 628 |
+
c = await app.get_chat(ch.strip())
|
| 629 |
+
await scrape_single_chat(c.id, c.title or ch, int(lim))
|
| 630 |
+
except Exception as e:
|
| 631 |
+
logger.error(f"Single scan error: {e}")
|
| 632 |
+
asyncio.run_coroutine_threadsafe(do_single(), MAIN_LOOP)
|
| 633 |
+
return f"🚀 تم بدء فحص القناة ({ch}) بنجاح!"
|
| 634 |
+
return "❌ تعذر إطلاق المهمة."
|
| 635 |
+
|
| 636 |
scan_all_btn.click(fn=trigger_scan_all, inputs=[limit_input], outputs=[scrape_status])
|
| 637 |
+
single_scan_btn.click(fn=trigger_single_scan, inputs=[single_ch_input, single_lim_input], outputs=[scrape_status])
|
| 638 |
|
| 639 |
return demo
|
| 640 |
|
| 641 |
# ============ تشغيل الويب ============
|
| 642 |
def run_web_server():
|
| 643 |
fastapi_app = FastAPI()
|
|
|
|
|
|
|
| 644 |
|
| 645 |
@fastapi_app.get("/health")
|
| 646 |
async def health():
|
| 647 |
return {"status": "ok", "stats": stats}
|
| 648 |
|
| 649 |
+
@fastapi_app.get("/ping")
|
| 650 |
+
async def ping():
|
| 651 |
+
return {"status": "alive", "stats": stats}
|
| 652 |
+
|
| 653 |
+
demo = create_dashboard()
|
| 654 |
+
gr.mount_gradio_app(fastapi_app, demo, path="/")
|
| 655 |
+
|
| 656 |
uvicorn.run(fastapi_app, host="0.0.0.0", port=7860)
|
| 657 |
|
| 658 |
# ============ نقطة الدخول ============
|