Update bot.py
Browse files
bot.py
CHANGED
|
@@ -14,8 +14,9 @@ from hydrogram.types import (
|
|
| 14 |
from fastapi import FastAPI
|
| 15 |
|
| 16 |
# ============ CRITICAL FIX: Monkey-patch FloodPremiumWait ============
|
| 17 |
-
|
| 18 |
import hydrogram.errors.rpc_error as _rpc_error
|
|
|
|
| 19 |
if "FLOOD_PREMIUM_WAIT" not in [k for d in _rpc_error.exceptions.values() for k in d.keys()]:
|
| 20 |
class FloodPremiumWait(FloodWait):
|
| 21 |
CODE = 420
|
|
@@ -61,7 +62,6 @@ TARGET_CHAT = None
|
|
| 61 |
|
| 62 |
# ============ Global Flood Control ============
|
| 63 |
class GlobalFloodControl:
|
| 64 |
-
"""Pause ALL workers when ANY worker hits a FloodWait."""
|
| 65 |
def __init__(self):
|
| 66 |
self.until = 0
|
| 67 |
self._lock = asyncio.Lock()
|
|
@@ -84,7 +84,7 @@ flood_control = GlobalFloodControl()
|
|
| 84 |
|
| 85 |
# ============ Throttle Controller ============
|
| 86 |
class ThrottleController:
|
| 87 |
-
def __init__(self, rate=3.0):
|
| 88 |
self.rate = rate
|
| 89 |
self.last_send = {}
|
| 90 |
self.lock = asyncio.Lock()
|
|
@@ -102,7 +102,7 @@ throttle = ThrottleController(rate=3.0)
|
|
| 102 |
|
| 103 |
# ============ Locks ============
|
| 104 |
download_lock = asyncio.Lock()
|
| 105 |
-
send_sem = asyncio.Semaphore(1)
|
| 106 |
|
| 107 |
# ============ قاعدة البيانات ============
|
| 108 |
async def init_db():
|
|
@@ -167,7 +167,6 @@ async def add_job(message):
|
|
| 167 |
file_id = message.animation.file_id
|
| 168 |
file_size = message.animation.file_size or 0
|
| 169 |
|
| 170 |
-
# CRITICAL FIX: Explicit typing to prevent "unsupported type" SQLite errors
|
| 171 |
chat_id = int(message.chat.id)
|
| 172 |
message_id = int(message.id)
|
| 173 |
file_size = int(file_size) if file_size else 0
|
|
@@ -194,7 +193,6 @@ async def add_job(message):
|
|
| 194 |
logger.error(f"Add job error: {e}")
|
| 195 |
|
| 196 |
async def update_job_status(chat_id, message_id, status, file_path=None, error=None):
|
| 197 |
-
# CRITICAL FIX: Explicit int typing
|
| 198 |
chat_id = int(chat_id)
|
| 199 |
message_id = int(message_id)
|
| 200 |
|
|
@@ -208,7 +206,7 @@ async def update_job_status(chat_id, message_id, status, file_path=None, error=N
|
|
| 208 |
await db.execute("""
|
| 209 |
UPDATE jobs SET status=?, error=?, attempts=attempts+1, updated_at=CURRENT_TIMESTAMP
|
| 210 |
WHERE chat_id=? AND message_id=?
|
| 211 |
-
""", (status, str(error)[:500], chat_id, message_id))
|
| 212 |
else:
|
| 213 |
await db.execute("""
|
| 214 |
UPDATE jobs SET status=?, updated_at=CURRENT_TIMESTAMP
|
|
@@ -306,7 +304,6 @@ async def verify_download(file_path, expected_size):
|
|
| 306 |
return True
|
| 307 |
|
| 308 |
async def download_with_message_object(chat_id, message_id, file_path):
|
| 309 |
-
"""إعادة جلب الرسالة وتحميلها (الأكثر موثوقية)"""
|
| 310 |
try:
|
| 311 |
logger.info(f"Re-fetching message {chat_id}/{message_id}")
|
| 312 |
message = await app.get_messages(int(chat_id), int(message_id))
|
|
@@ -323,8 +320,6 @@ async def download_with_message_object(chat_id, message_id, file_path):
|
|
| 323 |
return None
|
| 324 |
|
| 325 |
async def download_job(job):
|
| 326 |
-
"""3 محاولات: file_id → message object → final retry"""
|
| 327 |
-
# CRITICAL FIX: Explicit int typing to prevent SQLite binding errors
|
| 328 |
chat_id = int(job['chat_id'])
|
| 329 |
message_id = int(job['message_id'])
|
| 330 |
expected_size = int(job['file_size'] or 0)
|
|
@@ -344,14 +339,12 @@ async def download_job(job):
|
|
| 344 |
except:
|
| 345 |
pass
|
| 346 |
|
| 347 |
-
# Respect global flood waits before starting
|
| 348 |
await flood_control.wait()
|
| 349 |
|
| 350 |
try:
|
| 351 |
await update_job_status(chat_id, message_id, 'downloading')
|
| 352 |
downloaded = None
|
| 353 |
|
| 354 |
-
# المحاولة 1: file_id
|
| 355 |
if file_id:
|
| 356 |
logger.info(f"Attempt 1 (file_id): {message_id}")
|
| 357 |
try:
|
|
@@ -362,12 +355,10 @@ async def download_job(job):
|
|
| 362 |
except Exception as e:
|
| 363 |
logger.warning(f"file_id failed: {e}")
|
| 364 |
|
| 365 |
-
# المحاولة 2: message object
|
| 366 |
if not await verify_download(downloaded, expected_size):
|
| 367 |
logger.info(f"Attempt 2 (message): {message_id}")
|
| 368 |
downloaded = await download_with_message_object(chat_id, message_id, file_path)
|
| 369 |
|
| 370 |
-
# المحاولة 3: إعادة أخيرة
|
| 371 |
if not await verify_download(downloaded, expected_size):
|
| 372 |
logger.info(f"Attempt 3 (retry): {message_id}")
|
| 373 |
await asyncio.sleep(5)
|
|
@@ -402,7 +393,6 @@ async def download_job(job):
|
|
| 402 |
|
| 403 |
# ============ الإرسال ============
|
| 404 |
async def safe_send(send_func):
|
| 405 |
-
"""Manual retry that RESPECTS FloodWait from Telegram."""
|
| 406 |
for attempt in range(5):
|
| 407 |
try:
|
| 408 |
return await send_func()
|
|
@@ -432,7 +422,6 @@ async def send_job(job):
|
|
| 432 |
await update_job_status(chat_id, message_id, 'pending', error="File not found")
|
| 433 |
return False
|
| 434 |
|
| 435 |
-
# Respect global flood waits
|
| 436 |
await flood_control.wait()
|
| 437 |
|
| 438 |
try:
|
|
@@ -685,7 +674,6 @@ def run_gradio():
|
|
| 685 |
|
| 686 |
# ============ Main ============
|
| 687 |
async def main():
|
| 688 |
-
# 🔑 تأكد من عدم وجود اتصال سابق
|
| 689 |
try:
|
| 690 |
if app.is_connected:
|
| 691 |
await app.stop()
|
|
|
|
| 14 |
from fastapi import FastAPI
|
| 15 |
|
| 16 |
# ============ CRITICAL FIX: Monkey-patch FloodPremiumWait ============
|
| 17 |
+
import hydrogram
|
| 18 |
import hydrogram.errors.rpc_error as _rpc_error
|
| 19 |
+
|
| 20 |
if "FLOOD_PREMIUM_WAIT" not in [k for d in _rpc_error.exceptions.values() for k in d.keys()]:
|
| 21 |
class FloodPremiumWait(FloodWait):
|
| 22 |
CODE = 420
|
|
|
|
| 62 |
|
| 63 |
# ============ Global Flood Control ============
|
| 64 |
class GlobalFloodControl:
|
|
|
|
| 65 |
def __init__(self):
|
| 66 |
self.until = 0
|
| 67 |
self._lock = asyncio.Lock()
|
|
|
|
| 84 |
|
| 85 |
# ============ Throttle Controller ============
|
| 86 |
class ThrottleController:
|
| 87 |
+
def __init__(self, rate=3.0):
|
| 88 |
self.rate = rate
|
| 89 |
self.last_send = {}
|
| 90 |
self.lock = asyncio.Lock()
|
|
|
|
| 102 |
|
| 103 |
# ============ Locks ============
|
| 104 |
download_lock = asyncio.Lock()
|
| 105 |
+
send_sem = asyncio.Semaphore(1)
|
| 106 |
|
| 107 |
# ============ قاعدة البيانات ============
|
| 108 |
async def init_db():
|
|
|
|
| 167 |
file_id = message.animation.file_id
|
| 168 |
file_size = message.animation.file_size or 0
|
| 169 |
|
|
|
|
| 170 |
chat_id = int(message.chat.id)
|
| 171 |
message_id = int(message.id)
|
| 172 |
file_size = int(file_size) if file_size else 0
|
|
|
|
| 193 |
logger.error(f"Add job error: {e}")
|
| 194 |
|
| 195 |
async def update_job_status(chat_id, message_id, status, file_path=None, error=None):
|
|
|
|
| 196 |
chat_id = int(chat_id)
|
| 197 |
message_id = int(message_id)
|
| 198 |
|
|
|
|
| 206 |
await db.execute("""
|
| 207 |
UPDATE jobs SET status=?, error=?, attempts=attempts+1, updated_at=CURRENT_TIMESTAMP
|
| 208 |
WHERE chat_id=? AND message_id=?
|
| 209 |
+
""", (status, str(error)[:500], chat_id, message_id))
|
| 210 |
else:
|
| 211 |
await db.execute("""
|
| 212 |
UPDATE jobs SET status=?, updated_at=CURRENT_TIMESTAMP
|
|
|
|
| 304 |
return True
|
| 305 |
|
| 306 |
async def download_with_message_object(chat_id, message_id, file_path):
|
|
|
|
| 307 |
try:
|
| 308 |
logger.info(f"Re-fetching message {chat_id}/{message_id}")
|
| 309 |
message = await app.get_messages(int(chat_id), int(message_id))
|
|
|
|
| 320 |
return None
|
| 321 |
|
| 322 |
async def download_job(job):
|
|
|
|
|
|
|
| 323 |
chat_id = int(job['chat_id'])
|
| 324 |
message_id = int(job['message_id'])
|
| 325 |
expected_size = int(job['file_size'] or 0)
|
|
|
|
| 339 |
except:
|
| 340 |
pass
|
| 341 |
|
|
|
|
| 342 |
await flood_control.wait()
|
| 343 |
|
| 344 |
try:
|
| 345 |
await update_job_status(chat_id, message_id, 'downloading')
|
| 346 |
downloaded = None
|
| 347 |
|
|
|
|
| 348 |
if file_id:
|
| 349 |
logger.info(f"Attempt 1 (file_id): {message_id}")
|
| 350 |
try:
|
|
|
|
| 355 |
except Exception as e:
|
| 356 |
logger.warning(f"file_id failed: {e}")
|
| 357 |
|
|
|
|
| 358 |
if not await verify_download(downloaded, expected_size):
|
| 359 |
logger.info(f"Attempt 2 (message): {message_id}")
|
| 360 |
downloaded = await download_with_message_object(chat_id, message_id, file_path)
|
| 361 |
|
|
|
|
| 362 |
if not await verify_download(downloaded, expected_size):
|
| 363 |
logger.info(f"Attempt 3 (retry): {message_id}")
|
| 364 |
await asyncio.sleep(5)
|
|
|
|
| 393 |
|
| 394 |
# ============ الإرسال ============
|
| 395 |
async def safe_send(send_func):
|
|
|
|
| 396 |
for attempt in range(5):
|
| 397 |
try:
|
| 398 |
return await send_func()
|
|
|
|
| 422 |
await update_job_status(chat_id, message_id, 'pending', error="File not found")
|
| 423 |
return False
|
| 424 |
|
|
|
|
| 425 |
await flood_control.wait()
|
| 426 |
|
| 427 |
try:
|
|
|
|
| 674 |
|
| 675 |
# ============ Main ============
|
| 676 |
async def main():
|
|
|
|
| 677 |
try:
|
| 678 |
if app.is_connected:
|
| 679 |
await app.stop()
|