Reooiq commited on
Commit
ec1f4a7
·
verified ·
1 Parent(s): e5f0ebf

Update bot.py

Browse files
Files changed (1) hide show
  1. bot.py +38 -63
bot.py CHANGED
@@ -1,6 +1,5 @@
1
  import os
2
  import gc
3
- import sys
4
  import asyncio
5
  import aiosqlite
6
  import time
@@ -10,18 +9,17 @@ from threading import Thread
10
  from http.server import BaseHTTPRequestHandler, HTTPServer
11
  from pyrogram import Client, filters, idle
12
  from pyrogram.errors import FloodWait
13
- from pyrogram.enums import ChatType
14
  from pyrogram.types import InputMediaPhoto, InputMediaVideo, InputMediaDocument, InputMediaAudio
15
 
16
- # --- 1. الإعدادات والتنسيق الأساسي ---
17
  DATA_DIR = "./data"
18
  DOWNLOAD_DIR = os.path.join(DATA_DIR, "downloads")
19
  DB_PATH = os.path.join(DATA_DIR, 'bot_archiver.db')
20
  os.makedirs(DOWNLOAD_DIR, exist_ok=True)
21
 
22
- HEALTHCHECK_URL = os.environ.get("HEALTHCHECK_URL", "") # رابط اختياري لمراقبة Uptime
23
- MAX_FILE_SIZE = 2000 * 1024 * 1024 # 2GB الحد الأقصى للملفات
24
- DYNAMIC_UPLOAD_DELAY = 5
25
  download_queue = asyncio.Queue()
26
 
27
  # --- 2. سيرفر الحفاظ على التشغيل ---
@@ -29,12 +27,12 @@ class RequestHandler(BaseHTTPRequestHandler):
29
  def do_GET(self):
30
  self.send_response(200)
31
  self.end_headers()
32
- self.wfile.write(b"Enterprise Async Archiver Engine Active!")
33
  def log_message(self, format, *args): pass
34
 
35
  Thread(target=lambda: HTTPServer(('0.0.0.0', 7860), RequestHandler).serve_forever(), daemon=True).start()
36
 
37
- # --- 3. إعدادات حساب Pyrogram ---
38
  API_ID = int(os.environ.get("API_ID", 0))
39
  API_HASH = os.environ.get("API_HASH", "")
40
  SESSION_STRING = os.environ.get("SESSION_STRING", "")
@@ -46,10 +44,13 @@ app = Client(
46
  api_hash=API_HASH,
47
  session_string=SESSION_STRING,
48
  in_memory=True,
49
- max_concurrent_transmissions=3
 
 
 
50
  )
51
 
52
- # --- 4. أدوات فحص النظام والتنظيف ---
53
  def check_disk_space():
54
  total, used, free = shutil.disk_usage(DATA_DIR)
55
  return (free / (1024 * 1024)) > 500
@@ -57,7 +58,6 @@ def check_disk_space():
57
  async def init_db():
58
  async with aiosqlite.connect(DB_PATH) as db:
59
  await db.execute("PRAGMA journal_mode=WAL;")
60
- await db.execute("PRAGMA auto_vacuum = FULL;")
61
  await db.execute('''
62
  CREATE TABLE IF NOT EXISTS queue (
63
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -73,7 +73,6 @@ async def init_db():
73
  timestamp REAL,
74
  UNIQUE(chat_id, msg_id)
75
  )''')
76
- await db.execute('''CREATE TABLE IF NOT EXISTS scan_progress (chat_id INTEGER PRIMARY KEY, last_msg_id INTEGER)''')
77
  await db.commit()
78
 
79
  def get_media_details(message):
@@ -92,7 +91,7 @@ def get_media_details(message):
92
  file_size = getattr(media, "file_size", 0)
93
  return unique_id, m_type, file_size
94
 
95
- # --- 5. محرك التنزيل المتوازي العالي السرعة ---
96
  async def download_worker_task(worker_id):
97
  while True:
98
  message = await download_queue.get()
@@ -102,13 +101,11 @@ async def download_worker_task(worker_id):
102
 
103
  unique_id, m_type, file_size = get_media_details(message)
104
  if not unique_id or file_size > MAX_FILE_SIZE:
105
- download_queue.task_done()
106
  continue
107
 
108
  async with aiosqlite.connect(DB_PATH) as db:
109
  async with db.execute("SELECT id FROM queue WHERE file_unique_id=?", (unique_id,)) as cursor:
110
  if await cursor.fetchone():
111
- download_queue.task_done()
112
  continue
113
 
114
  unique_name = f"{DOWNLOAD_DIR}/{message.chat.id}_{message.id}"
@@ -128,24 +125,24 @@ async def download_worker_task(worker_id):
128
  (message.chat.id, message.id, unique_id, group_id, m_type, final_caption, file_path, file_size, time.time())
129
  )
130
  await db.commit()
131
- print(f"⚡ [Worker-{worker_id}] تم التنزيل: {message.id}")
132
  break
133
  else:
134
  if file_path and os.path.exists(file_path): os.remove(file_path)
135
  except Exception:
136
  await asyncio.sleep(2)
137
  except Exception as e:
138
- print(f"❌ خطأ في عامل التنزيل {worker_id}: {e}")
139
  finally:
140
- download_queue.task_done()
141
 
142
- # --- 6. صائد الرسائل الحية ---
143
  @app.on_message(filters.incoming & ~filters.me & (filters.channel | filters.group) & filters.media)
144
  async def catch_incoming(client, message):
145
  if message.chat.id != TARGET_CHAT:
146
  await download_queue.put(message)
147
 
148
- # --- 7. لوحة التحكم بالأوامر ---
149
  @app.on_message(filters.me & filters.command("status"))
150
  async def status_command(client, message):
151
  async with aiosqlite.connect(DB_PATH) as db:
@@ -158,46 +155,26 @@ async def status_command(client, message):
158
  free_mb = round(free / (1024 * 1024), 2)
159
 
160
  report = (
161
- f"👑 **حالة المحرك المتوازي (Enterprise):**\n\n"
162
- f"📥 بالطابور المعلق: `{pending}`\n"
163
- f"📤 المرفوع بنجاح: `{uploaded}`\n"
164
  f"🔄 قيد التنزيل الآن: `{download_queue.qsize()}`\n"
165
- f"💾 المساحة المتاحة: `{free_mb} MB`\n"
166
- f"⏱️ الفاصل الزمني: `{DYNAMIC_UPLOAD_DELAY}s`"
167
  )
168
  await message.reply_text(report)
169
 
170
- # --- 8. مسح السجلات التاريخية ---
171
- async def scan_history_worker():
172
- async for dialog in app.get_dialogs():
173
- if dialog.chat.type in [ChatType.CHANNEL, ChatType.SUPERGROUP, ChatType.GROUP] and dialog.chat.id != TARGET_CHAT:
174
- try:
175
- async with aiosqlite.connect(DB_PATH) as db:
176
- async with db.execute("SELECT last_msg_id FROM scan_progress WHERE chat_id=?", (dialog.chat.id,)) as cursor:
177
- res = await cursor.fetchone()
178
- last_id = res[0] if res else 0
179
-
180
- async for message in app.get_chat_history(dialog.chat.id):
181
- if message.id <= last_id: break
182
- if message.media: await download_queue.put(message)
183
-
184
- async with aiosqlite.connect(DB_PATH) as db:
185
- await db.execute("INSERT OR REPLACE INTO scan_progress (chat_id, last_msg_id) VALUES (?, ?)", (dialog.chat.id, message.id))
186
- await db.commit()
187
- except Exception: pass
188
- await asyncio.sleep(2)
189
-
190
- # --- 9. محرك الرفع المستقل ---
191
  async def upload_worker():
192
  global DYNAMIC_UPLOAD_DELAY
193
  while True:
194
  try:
195
  async with aiosqlite.connect(DB_PATH) as db:
196
- async with db.execute("SELECT id, chat_id, msg_id, media_group_id, media_type, caption, file_path, timestamp FROM queue WHERE status='DOWNLOADED' ORDER BY file_size ASC, id ASC LIMIT 1") as cursor:
 
197
  row = await cursor.fetchone()
198
 
199
  if not row:
200
- await asyncio.sleep(4)
201
  continue
202
 
203
  q_id, chat_id, msg_id, group_id, m_type, caption, file_path, ts = row
@@ -208,10 +185,10 @@ async def upload_worker():
208
  await db.commit()
209
  continue
210
 
211
- # معالجة الألبومات
212
  if group_id:
213
  time_passed = time.time() - ts
214
- if time_passed < 12: await asyncio.sleep(12 - time_passed)
215
 
216
  async with aiosqlite.connect(DB_PATH) as db:
217
  async with db.execute(
@@ -221,13 +198,13 @@ async def upload_worker():
221
  group_rows = await cursor.fetchall()
222
 
223
  if group_rows:
224
- for i in range(0, len(group_rows), 10):
225
  chunk = group_rows[i:i+10]
226
  media_list, ids_to_update, files_to_delete = [], [], []
227
 
228
  for idx, g_item in enumerate(chunk):
229
  g_id, g_type, g_cap, g_path = g_item
230
- item_caption = g_cap if idx == 0 else ""
231
 
232
  if g_type == "photo": media_list.append(InputMediaPhoto(media=g_path, caption=item_caption))
233
  elif g_type == "video": media_list.append(InputMediaVideo(media=g_path, caption=item_caption, supports_streaming=True))
@@ -265,36 +242,34 @@ async def upload_worker():
265
  await db.commit()
266
 
267
  if os.path.exists(file_path): os.remove(file_path)
268
- if DYNAMIC_UPLOAD_DELAY > 4: DYNAMIC_UPLOAD_DELAY -= 0.1
269
  gc.collect()
270
  await asyncio.sleep(DYNAMIC_UPLOAD_DELAY)
271
 
272
  except FloodWait as e:
273
- DYNAMIC_UPLOAD_DELAY += 4
 
274
  await asyncio.sleep(e.value + 5)
275
- except Exception:
 
276
  await asyncio.sleep(5)
277
 
278
- # --- 10. إرسال نبضات الحفاظ على الخدمة (Healthcheck Ping) ---
279
  async def healthcheck_ping():
280
  while True:
281
- await asyncio.sleep(300) # كل 5 دقائق
282
  if HEALTHCHECK_URL:
283
  try: urllib.request.urlopen(HEALTHCHECK_URL, timeout=10)
284
  except: pass
285
 
286
- # --- 11. التشغيل الرئيسي المتوازي ---
287
  async def main():
288
  await init_db()
289
  await app.start()
290
- print("🚀 تم تشغيل المحرك بالتوازي الكامل (3 Downloader Workers + 1 Uploader)!")
291
 
292
- # تشغيل 3 عمال تنزيل بالتوازي
293
  for i in range(3):
294
  asyncio.create_task(download_worker_task(i + 1))
295
 
296
  asyncio.create_task(upload_worker())
297
- asyncio.create_task(scan_history_worker())
298
  asyncio.create_task(healthcheck_ping())
299
 
300
  await idle()
@@ -305,5 +280,5 @@ if __name__ == "__main__":
305
  try:
306
  app.run(main())
307
  except Exception as crash_error:
308
- print(f"⚠️ إعادة تشغيل ذكية بعد الانهيار: {crash_error}")
309
- time.sleep(10)
 
1
  import os
2
  import gc
 
3
  import asyncio
4
  import aiosqlite
5
  import time
 
9
  from http.server import BaseHTTPRequestHandler, HTTPServer
10
  from pyrogram import Client, filters, idle
11
  from pyrogram.errors import FloodWait
 
12
  from pyrogram.types import InputMediaPhoto, InputMediaVideo, InputMediaDocument, InputMediaAudio
13
 
14
+ # --- 1. الإعدادات الأساسية ---
15
  DATA_DIR = "./data"
16
  DOWNLOAD_DIR = os.path.join(DATA_DIR, "downloads")
17
  DB_PATH = os.path.join(DATA_DIR, 'bot_archiver.db')
18
  os.makedirs(DOWNLOAD_DIR, exist_ok=True)
19
 
20
+ HEALTHCHECK_URL = os.environ.get("HEALTHCHECK_URL", "")
21
+ MAX_FILE_SIZE = 2000 * 1024 * 1024 # 2GB
22
+ DYNAMIC_UPLOAD_DELAY = 3 # تم تقليل وقت الانتظار الافتراضي
23
  download_queue = asyncio.Queue()
24
 
25
  # --- 2. سيرفر الحفاظ على التشغيل ---
 
27
  def do_GET(self):
28
  self.send_response(200)
29
  self.end_headers()
30
+ self.wfile.write(b"Live Radar Archiver Engine Active!")
31
  def log_message(self, format, *args): pass
32
 
33
  Thread(target=lambda: HTTPServer(('0.0.0.0', 7860), RequestHandler).serve_forever(), daemon=True).start()
34
 
35
+ # --- 3. إعدادات الحساب (محاكاة هاتف حقيقي) ---
36
  API_ID = int(os.environ.get("API_ID", 0))
37
  API_HASH = os.environ.get("API_HASH", "")
38
  SESSION_STRING = os.environ.get("SESSION_STRING", "")
 
44
  api_hash=API_HASH,
45
  session_string=SESSION_STRING,
46
  in_memory=True,
47
+ # السر هنا: التخفي كأنه هاتف حقيقي لتجنب الحظر
48
+ device_model="Samsung Galaxy S23 Ultra",
49
+ system_version="Android 14.0",
50
+ app_version="10.14.1"
51
  )
52
 
53
+ # --- 4. أدوات النظام وقاعدة البيانات ---
54
  def check_disk_space():
55
  total, used, free = shutil.disk_usage(DATA_DIR)
56
  return (free / (1024 * 1024)) > 500
 
58
  async def init_db():
59
  async with aiosqlite.connect(DB_PATH) as db:
60
  await db.execute("PRAGMA journal_mode=WAL;")
 
61
  await db.execute('''
62
  CREATE TABLE IF NOT EXISTS queue (
63
  id INTEGER PRIMARY KEY AUTOINCREMENT,
 
73
  timestamp REAL,
74
  UNIQUE(chat_id, msg_id)
75
  )''')
 
76
  await db.commit()
77
 
78
  def get_media_details(message):
 
91
  file_size = getattr(media, "file_size", 0)
92
  return unique_id, m_type, file_size
93
 
94
+ # --- 5. محرك التنزيل المتوازي ---
95
  async def download_worker_task(worker_id):
96
  while True:
97
  message = await download_queue.get()
 
101
 
102
  unique_id, m_type, file_size = get_media_details(message)
103
  if not unique_id or file_size > MAX_FILE_SIZE:
 
104
  continue
105
 
106
  async with aiosqlite.connect(DB_PATH) as db:
107
  async with db.execute("SELECT id FROM queue WHERE file_unique_id=?", (unique_id,)) as cursor:
108
  if await cursor.fetchone():
 
109
  continue
110
 
111
  unique_name = f"{DOWNLOAD_DIR}/{message.chat.id}_{message.id}"
 
125
  (message.chat.id, message.id, unique_id, group_id, m_type, final_caption, file_path, file_size, time.time())
126
  )
127
  await db.commit()
128
+ print(f"⚡ [Worker-{worker_id}] تم تنزيل رسالة: {message.id}")
129
  break
130
  else:
131
  if file_path and os.path.exists(file_path): os.remove(file_path)
132
  except Exception:
133
  await asyncio.sleep(2)
134
  except Exception as e:
135
+ print(f"❌ خطأ في التنزيل: {e}")
136
  finally:
137
+ download_queue.task_done() # يعمل مرة واحدة فقط لتجنب انهيار الطابور
138
 
139
+ # --- 6. صائد الرسائل الحية (الرادار) ---
140
  @app.on_message(filters.incoming & ~filters.me & (filters.channel | filters.group) & filters.media)
141
  async def catch_incoming(client, message):
142
  if message.chat.id != TARGET_CHAT:
143
  await download_queue.put(message)
144
 
145
+ # --- 7. لوحة المراقبة ---
146
  @app.on_message(filters.me & filters.command("status"))
147
  async def status_command(client, message):
148
  async with aiosqlite.connect(DB_PATH) as db:
 
155
  free_mb = round(free / (1024 * 1024), 2)
156
 
157
  report = (
158
+ f"👑 **حالة رادار الأرشفة:**\n\n"
159
+ f"📥 جاهز للرفع (بالترتيب): `{pending}`\n"
160
+ f"📤 تم رفعه: `{uploaded}`\n"
161
  f"🔄 قيد التنزيل الآن: `{download_queue.qsize()}`\n"
162
+ f"💾 المساحة المتاحة: `{free_mb} MB`"
 
163
  )
164
  await message.reply_text(report)
165
 
166
+ # --- 8. محرك الرفع التسلسلي الصارم ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  async def upload_worker():
168
  global DYNAMIC_UPLOAD_DELAY
169
  while True:
170
  try:
171
  async with aiosqlite.connect(DB_PATH) as db:
172
+ # التعديل الأهم: الترتيب حسب id ASC لضمان رفع الملفات بنفس ترتيب وصولها بالضبط
173
+ async with db.execute("SELECT id, chat_id, msg_id, media_group_id, media_type, caption, file_path, timestamp FROM queue WHERE status='DOWNLOADED' ORDER BY id ASC LIMIT 1") as cursor:
174
  row = await cursor.fetchone()
175
 
176
  if not row:
177
+ await asyncio.sleep(2)
178
  continue
179
 
180
  q_id, chat_id, msg_id, group_id, m_type, caption, file_path, ts = row
 
185
  await db.commit()
186
  continue
187
 
188
+ # معالجة الألبومات (بنفس ترتيبها الأصلي)
189
  if group_id:
190
  time_passed = time.time() - ts
191
+ if time_passed < 8: await asyncio.sleep(8 - time_passed) # انتظار قصير لتكتمل أجزاء الألبوم
192
 
193
  async with aiosqlite.connect(DB_PATH) as db:
194
  async with db.execute(
 
198
  group_rows = await cursor.fetchall()
199
 
200
  if group_rows:
201
+ for i in range(0, len(group_rows), 10): # تليجرام يقبل 10 ملفات كحد أقصى في الألبوم
202
  chunk = group_rows[i:i+10]
203
  media_list, ids_to_update, files_to_delete = [], [], []
204
 
205
  for idx, g_item in enumerate(chunk):
206
  g_id, g_type, g_cap, g_path = g_item
207
+ item_caption = g_cap if idx == 0 else "" # النص للصورة الأولى فقط لتجنب التكرار
208
 
209
  if g_type == "photo": media_list.append(InputMediaPhoto(media=g_path, caption=item_caption))
210
  elif g_type == "video": media_list.append(InputMediaVideo(media=g_path, caption=item_caption, supports_streaming=True))
 
242
  await db.commit()
243
 
244
  if os.path.exists(file_path): os.remove(file_path)
245
+ if DYNAMIC_UPLOAD_DELAY > 3: DYNAMIC_UPLOAD_DELAY -= 0.1
246
  gc.collect()
247
  await asyncio.sleep(DYNAMIC_UPLOAD_DELAY)
248
 
249
  except FloodWait as e:
250
+ print(f"⚠️ تليجرام يطلب الانتظار {e.value} ثانية... (نظام الحماية يعمل)")
251
+ DYNAMIC_UPLOAD_DELAY += 2
252
  await asyncio.sleep(e.value + 5)
253
+ except Exception as e:
254
+ print(f"⚠️ خطأ أثناء الرفع: {e}")
255
  await asyncio.sleep(5)
256
 
 
257
  async def healthcheck_ping():
258
  while True:
259
+ await asyncio.sleep(300)
260
  if HEALTHCHECK_URL:
261
  try: urllib.request.urlopen(HEALTHCHECK_URL, timeout=10)
262
  except: pass
263
 
 
264
  async def main():
265
  await init_db()
266
  await app.start()
267
+ print("🚀 تم تشغيل وضع الرادار الحي! (محاكاة هاتف حقيقي فعالة)")
268
 
 
269
  for i in range(3):
270
  asyncio.create_task(download_worker_task(i + 1))
271
 
272
  asyncio.create_task(upload_worker())
 
273
  asyncio.create_task(healthcheck_ping())
274
 
275
  await idle()
 
280
  try:
281
  app.run(main())
282
  except Exception as crash_error:
283
+ print(f"⚠️ إعادة تشغيل ذكية: {crash_error}")
284
+ time.sleep(5)