Reooiq commited on
Commit
4f882cc
·
verified ·
1 Parent(s): f35b0a4

Update bot.py

Browse files
Files changed (1) hide show
  1. bot.py +124 -60
bot.py CHANGED
@@ -47,7 +47,7 @@ CHANNELS_TO_SCRAPE = os.environ.get("CHANNELS_TO_SCRAPE", "")
47
 
48
  DATA_DIR = "/data" if os.path.exists("/data") else "/tmp/data"
49
  DOWNLOAD_DIR = os.path.join(DATA_DIR, "downloads")
50
- DB_PATH = os.path.join(DATA_DIR, "queue_v2.db")
51
  os.makedirs(DOWNLOAD_DIR, exist_ok=True)
52
 
53
  # ============ إنشاء العميل ============
@@ -102,31 +102,80 @@ class RateLimiter:
102
 
103
  rate_limiter = RateLimiter(delay=2.0)
104
 
105
- # ============ قاعدة البيانات ============
 
 
 
 
 
 
 
 
 
 
 
106
  async def init_db():
107
- async with aiosqlite.connect(DB_PATH) as db:
108
- await db.execute("""
109
- CREATE TABLE IF NOT EXISTS jobs (
110
- id INTEGER PRIMARY KEY AUTOINCREMENT,
111
- chat_id INTEGER NOT NULL,
112
- message_id INTEGER NOT NULL,
113
- media_group_id TEXT,
114
- media_type TEXT,
115
- caption TEXT,
116
- source_name TEXT,
117
- album_total INTEGER DEFAULT 1,
118
- album_index INTEGER DEFAULT 1,
119
- status TEXT DEFAULT 'pending',
120
- attempts INTEGER DEFAULT 0,
121
- error TEXT,
122
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
123
- UNIQUE(chat_id, message_id)
124
- )
125
- """)
126
- await db.execute("CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status)")
127
- await db.execute("CREATE INDEX IF NOT EXISTS idx_jobs_media_group ON jobs(media_group_id)")
128
- await db.commit()
129
- logger.info("Database initialized successfully.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  async def add_message_to_queue(message, album_info=None):
132
  if not message.media:
@@ -162,8 +211,8 @@ async def add_message_to_queue(message, album_info=None):
162
  album_total = album_info.get("total", 1)
163
  album_index = album_info.get("index", 1)
164
 
165
- async with aiosqlite.connect(DB_PATH) as db:
166
- try:
167
  await db.execute("""
168
  INSERT OR IGNORE INTO jobs
169
  (chat_id, message_id, media_group_id, media_type, caption, source_name, album_total, album_index, status)
@@ -172,9 +221,11 @@ async def add_message_to_queue(message, album_info=None):
172
  await db.commit()
173
  stats["total_queued"] += 1
174
  return True
175
- except Exception as e:
176
- logger.error(f"Error adding message to DB queue: {e}")
177
- return False
 
 
178
 
179
  # ============ إعداد الكابشن ============
180
  def format_caption(raw_cap, source_name, album_index, album_total):
@@ -202,7 +253,7 @@ async def process_single_job(job):
202
 
203
  await rate_limiter.wait()
204
 
205
- # محاولة النسخ المباشر
206
  try:
207
  await app.copy_message(
208
  chat_id=TARGET_CHAT,
@@ -224,7 +275,7 @@ async def process_single_job(job):
224
  except Exception as e:
225
  logger.warning(f"Direct copy failed for {msg_id} ({e}), falling back to download & upload...")
226
 
227
- # التحميل والرفع في حال الحماية
228
  try:
229
  msg = await app.get_messages(chat_id, msg_id)
230
  if not msg or not msg.media:
@@ -276,39 +327,52 @@ async def queue_worker():
276
  await asyncio.sleep(5)
277
  continue
278
 
279
- async with aiosqlite.connect(DB_PATH) as db:
280
- db.row_factory = aiosqlite.Row
281
- cursor = await db.execute("""
282
- SELECT * FROM jobs
283
- WHERE status = 'pending' AND attempts < 5
284
- ORDER BY id ASC LIMIT 1
285
- """)
286
- job_row = await cursor.fetchone()
287
-
288
- if not job_row:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  await asyncio.sleep(2)
290
  continue
291
-
292
- job = dict(job_row)
293
- job_id = job["id"]
294
-
295
- await db.execute("UPDATE jobs SET status='processing' WHERE id=?", (job_id,))
296
- await db.commit()
297
 
298
  result = await process_single_job(job)
299
 
300
- async with aiosqlite.connect(DB_PATH) as db:
301
- if result is True:
302
- await db.execute("UPDATE jobs SET status='completed' WHERE id=?", (job_id,))
303
- stats["total_transferred"] += 1
304
- elif result == "DELETED":
305
- await db.execute("UPDATE jobs SET status='failed', error='Message Deleted/Invalid' WHERE id=?", (job_id,))
306
- stats["total_failed"] += 1
307
- else:
308
- await db.execute("""
309
- UPDATE jobs SET status='pending', attempts=attempts+1, error='Retry later' WHERE id=?
310
- """, (job_id,))
311
- await db.commit()
 
 
 
312
 
313
  except (FloodWait, FloodPremiumWait) as e:
314
  wait_val = getattr(e, 'value', 30)
 
47
 
48
  DATA_DIR = "/data" if os.path.exists("/data") else "/tmp/data"
49
  DOWNLOAD_DIR = os.path.join(DATA_DIR, "downloads")
50
+ DB_PATH = os.path.join(DATA_DIR, "archiver_queue.db")
51
  os.makedirs(DOWNLOAD_DIR, exist_ok=True)
52
 
53
  # ============ إنشاء العميل ============
 
102
 
103
  rate_limiter = RateLimiter(delay=2.0)
104
 
105
+ # ============ المعالجة التلقائية لقاعدة البيانات (Self-Healing DB) ============
106
+ def cleanup_corrupt_db():
107
+ logger.warning("Cleaning up corrupted database files...")
108
+ for ext in ["", "-wal", "-shm", "-journal"]:
109
+ file_to_del = f"{DB_PATH}{ext}"
110
+ if os.path.exists(file_to_del):
111
+ try:
112
+ os.remove(file_to_del)
113
+ logger.info(f"Deleted: {file_to_del}")
114
+ except Exception as e:
115
+ logger.error(f"Failed to delete {file_to_del}: {e}")
116
+
117
  async def init_db():
118
+ try:
119
+ # فحص سلامة الملف إذا كان موجوداً
120
+ if os.path.exists(DB_PATH):
121
+ try:
122
+ async with aiosqlite.connect(DB_PATH) as db:
123
+ cursor = await db.execute("PRAGMA integrity_check;")
124
+ res = await cursor.fetchone()
125
+ if not res or res[0] != "ok":
126
+ raise Exception("Integrity check failed")
127
+ except Exception as e:
128
+ logger.error(f"Detected corrupt database ({e}), resetting...")
129
+ cleanup_corrupt_db()
130
+
131
+ async with aiosqlite.connect(DB_PATH) as db:
132
+ await db.execute("PRAGMA journal_mode=WAL;")
133
+ await db.execute("PRAGMA synchronous=NORMAL;")
134
+ await db.execute("""
135
+ CREATE TABLE IF NOT EXISTS jobs (
136
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
137
+ chat_id INTEGER NOT NULL,
138
+ message_id INTEGER NOT NULL,
139
+ media_group_id TEXT,
140
+ media_type TEXT,
141
+ caption TEXT,
142
+ source_name TEXT,
143
+ album_total INTEGER DEFAULT 1,
144
+ album_index INTEGER DEFAULT 1,
145
+ status TEXT DEFAULT 'pending',
146
+ attempts INTEGER DEFAULT 0,
147
+ error TEXT,
148
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
149
+ UNIQUE(chat_id, message_id)
150
+ )
151
+ """)
152
+ await db.execute("CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status)")
153
+ await db.execute("CREATE INDEX IF NOT EXISTS idx_jobs_media_group ON jobs(media_group_id)")
154
+ await db.commit()
155
+ logger.info("Database initialized successfully.")
156
+ except Exception as e:
157
+ logger.error(f"Critical error initializing DB: {e}, recreating fresh DB...")
158
+ cleanup_corrupt_db()
159
+ async with aiosqlite.connect(DB_PATH) as db:
160
+ await db.execute("""
161
+ CREATE TABLE IF NOT EXISTS jobs (
162
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
163
+ chat_id INTEGER NOT NULL,
164
+ message_id INTEGER NOT NULL,
165
+ media_group_id TEXT,
166
+ media_type TEXT,
167
+ caption TEXT,
168
+ source_name TEXT,
169
+ album_total INTEGER DEFAULT 1,
170
+ album_index INTEGER DEFAULT 1,
171
+ status TEXT DEFAULT 'pending',
172
+ attempts INTEGER DEFAULT 0,
173
+ error TEXT,
174
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
175
+ UNIQUE(chat_id, message_id)
176
+ )
177
+ """)
178
+ await db.commit()
179
 
180
  async def add_message_to_queue(message, album_info=None):
181
  if not message.media:
 
211
  album_total = album_info.get("total", 1)
212
  album_index = album_info.get("index", 1)
213
 
214
+ try:
215
+ async with aiosqlite.connect(DB_PATH) as db:
216
  await db.execute("""
217
  INSERT OR IGNORE INTO jobs
218
  (chat_id, message_id, media_group_id, media_type, caption, source_name, album_total, album_index, status)
 
221
  await db.commit()
222
  stats["total_queued"] += 1
223
  return True
224
+ except Exception as e:
225
+ logger.error(f"Error adding message to DB queue: {e}")
226
+ if "malformed" in str(e).lower():
227
+ await init_db()
228
+ return False
229
 
230
  # ============ إعداد الكابشن ============
231
  def format_caption(raw_cap, source_name, album_index, album_total):
 
253
 
254
  await rate_limiter.wait()
255
 
256
+ # 1. محاولة النسخ المباشر من السيرفر
257
  try:
258
  await app.copy_message(
259
  chat_id=TARGET_CHAT,
 
275
  except Exception as e:
276
  logger.warning(f"Direct copy failed for {msg_id} ({e}), falling back to download & upload...")
277
 
278
+ # 2. التحميل والرفع في حال الحماية
279
  try:
280
  msg = await app.get_messages(chat_id, msg_id)
281
  if not msg or not msg.media:
 
327
  await asyncio.sleep(5)
328
  continue
329
 
330
+ job = None
331
+ try:
332
+ async with aiosqlite.connect(DB_PATH) as db:
333
+ db.row_factory = aiosqlite.Row
334
+ cursor = await db.execute("""
335
+ SELECT * FROM jobs
336
+ WHERE status = 'pending' AND attempts < 5
337
+ ORDER BY id ASC LIMIT 1
338
+ """)
339
+ job_row = await cursor.fetchone()
340
+
341
+ if not job_row:
342
+ await asyncio.sleep(2)
343
+ continue
344
+
345
+ job = dict(job_row)
346
+ job_id = job["id"]
347
+
348
+ await db.execute("UPDATE jobs SET status='processing' WHERE id=?", (job_id,))
349
+ await db.commit()
350
+ except Exception as db_err:
351
+ if "malformed" in str(db_err).lower():
352
+ logger.error("Database malformed in worker, resetting database...")
353
+ await init_db()
354
  await asyncio.sleep(2)
355
  continue
356
+ else:
357
+ raise db_err
 
 
 
 
358
 
359
  result = await process_single_job(job)
360
 
361
+ try:
362
+ async with aiosqlite.connect(DB_PATH) as db:
363
+ if result is True:
364
+ await db.execute("UPDATE jobs SET status='completed' WHERE id=?", (job["id"],))
365
+ stats["total_transferred"] += 1
366
+ elif result == "DELETED":
367
+ await db.execute("UPDATE jobs SET status='failed', error='Message Deleted/Invalid' WHERE id=?", (job["id"],))
368
+ stats["total_failed"] += 1
369
+ else:
370
+ await db.execute("""
371
+ UPDATE jobs SET status='pending', attempts=attempts+1, error='Retry later' WHERE id=?
372
+ """, (job["id"],))
373
+ await db.commit()
374
+ except Exception as db_err:
375
+ logger.error(f"Failed to update job status: {db_err}")
376
 
377
  except (FloodWait, FloodPremiumWait) as e:
378
  wait_val = getattr(e, 'value', 30)