Reooiq commited on
Commit
210b67f
·
verified ·
1 Parent(s): 099ee13

Update bot.py

Browse files
Files changed (1) hide show
  1. bot.py +238 -293
bot.py CHANGED
@@ -1,336 +1,281 @@
1
  import os
2
- import gc
3
  import asyncio
4
- import aiosqlite
5
- import time
6
  import shutil
7
- import random
8
- import threading
9
- from pyrogram import Client, filters, idle
10
- from pyrogram.errors import FloodWait
11
  from pyrogram.types import InputMediaPhoto, InputMediaVideo, InputMediaDocument, InputMediaAudio
12
  from pyrogram.enums import ChatAction
13
- import gradio as gr
14
 
15
- # ============ الإعدادات ============
16
- DATA_DIR = "./data"
17
- DOWNLOAD_DIR = os.path.join(DATA_DIR, "downloads")
18
- DB_PATH = os.path.join(DATA_DIR, 'bot_archiver.db')
19
- os.makedirs(DOWNLOAD_DIR, exist_ok=True)
20
-
21
- MAX_FILE_SIZE = 2000 * 1024 * 1024 # 2GB
22
- BASE_UPLOAD_DELAY = 4
23
 
 
24
  API_ID = int(os.environ.get("API_ID", 0))
25
  API_HASH = os.environ.get("API_HASH", "")
26
  SESSION_STRING = os.environ.get("SESSION_STRING", "")
27
  TARGET_CHAT = int(os.environ.get("TARGET_CHAT", 0))
 
28
 
29
- download_queue = asyncio.Queue()
30
-
31
- # ============ قاعدة البيانات ============
32
- async def init_db():
33
- async with aiosqlite.connect(DB_PATH) as db:
34
- await db.execute("PRAGMA journal_mode=WAL;")
35
- await db.execute('''
36
- CREATE TABLE IF NOT EXISTS queue (
37
- id INTEGER PRIMARY KEY AUTOINCREMENT,
38
- chat_id INTEGER,
39
- msg_id INTEGER,
40
- file_unique_id TEXT UNIQUE,
41
- media_group_id TEXT,
42
- media_type TEXT,
43
- caption TEXT,
44
- file_path TEXT,
45
- file_size INTEGER DEFAULT 0,
46
- status TEXT DEFAULT 'DOWNLOADED',
47
- timestamp REAL,
48
- UNIQUE(chat_id, msg_id)
49
- )''')
50
- await db.commit()
51
-
52
- def get_media_details(message):
53
- media = message.photo or message.video or message.document or message.audio or message.voice or message.video_note or message.animation
54
- if not media:
55
- return None, None, 0
56
-
57
- m_type = "doc"
58
- if message.photo: m_type = "photo"
59
- elif message.video: m_type = "video"
60
- elif message.audio: m_type = "audio"
61
- elif message.voice: m_type = "voice"
62
- elif message.video_note: m_type = "video_note"
63
- elif message.animation: m_type = "animation"
64
-
65
- unique_id = getattr(media, "file_unique_id", f"{message.chat.id}_{message.id}")
66
- file_size = getattr(media, "file_size", 0)
67
- return unique_id, m_type, file_size
68
-
69
- # ============ محرك التنزيل ============
70
- async def download_worker_task(worker_id):
71
- while True:
72
- message = await download_queue.get()
73
- try:
74
- while shutil.disk_usage(DATA_DIR).free / (1024 * 1024) <= 500:
75
- await asyncio.sleep(15)
76
-
77
- unique_id, m_type, file_size = get_media_details(message)
78
- if not unique_id or file_size > MAX_FILE_SIZE:
79
- continue
80
 
81
- async with aiosqlite.connect(DB_PATH) as db:
82
- async with db.execute("SELECT id FROM queue WHERE file_unique_id=?", (unique_id,)) as cursor:
83
- if await cursor.fetchone():
84
- continue
85
-
86
- file_prefix = f"{DOWNLOAD_DIR}/{message.chat.id}_{message.id}_"
87
-
88
- for attempt in range(3):
89
- try:
90
- file_path = await asyncio.wait_for(message.download(file_name=file_prefix), timeout=300)
91
- if file_path and os.path.exists(file_path) and os.path.getsize(file_path) > 0:
92
- group_id = str(message.media_group_id) if message.media_group_id else None
93
- src_title = message.chat.title or "المصدر"
94
- raw_cap = message.caption or ""
95
- final_caption = f"{raw_cap}\n\n📌 *المصدر:* {src_title}".strip()
96
 
97
- async with aiosqlite.connect(DB_PATH) as db:
98
- await db.execute(
99
- "INSERT OR IGNORE INTO queue (chat_id, msg_id, file_unique_id, media_group_id, media_type, caption, file_path, file_size, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
100
- (message.chat.id, message.id, unique_id, group_id, m_type, final_caption, file_path, file_size, time.time())
101
- )
102
- await db.commit()
103
- print(f"⚡ [Worker-{worker_id}] تم تنزيل: {message.id}")
104
- break
105
- else:
106
- if file_path and os.path.exists(file_path):
107
- os.remove(file_path)
108
- except Exception:
109
- await asyncio.sleep(2)
110
- except Exception as e:
111
- print(f"❌ خطأ في التنزيل: {e}")
112
- finally:
113
- download_queue.task_done()
114
 
115
- # ============ صائد الرسائل ============
116
  app = Client(
117
- "memory_session",
118
  api_id=API_ID,
119
  api_hash=API_HASH,
120
  session_string=SESSION_STRING,
121
  in_memory=True,
122
  device_model="Samsung Galaxy S23 Ultra",
123
- system_version="Android 14.0",
124
  app_version="10.14.1",
125
  lang_code="ar"
126
  )
127
 
128
- @app.on_message(filters.incoming & ~filters.me & (filters.channel | filters.group) & filters.media)
129
- async def catch_incoming(client, message):
130
- if message.chat.id != TARGET_CHAT:
131
- await download_queue.put(message)
132
-
133
- @app.on_message(filters.me & filters.command("status"))
134
- async def status_command(client, message):
135
- async with aiosqlite.connect(DB_PATH) as db:
136
- async with db.execute("SELECT COUNT(*) FROM queue WHERE status='DOWNLOADED'") as c1:
137
- pending = (await c1.fetchone())[0]
138
- async with db.execute("SELECT COUNT(*) FROM queue WHERE status='UPLOADED'") as c2:
139
- uploaded = (await c2.fetchone())[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
- total, used, free = shutil.disk_usage(DATA_DIR)
142
- free_mb = round(free / (1024 * 1024), 2)
 
 
 
143
 
144
- report = (
145
- f"👑 **حالة الأرشفة:**\n\n"
146
- f"📥 جاهز للرفع: `{pending}`\n"
147
- f"📤 تم رفعه: `{uploaded}`\n"
148
- f"🔄 قيد التنزيل: `{download_queue.qsize()}`\n"
149
- f"💾 مساحة متاحة: `{free_mb} MB`"
150
- )
151
- await message.reply_text(report)
152
-
153
- # ============ محرك الرفع ============
154
- async def upload_worker():
155
- global BASE_UPLOAD_DELAY
156
- while True:
 
 
 
157
  try:
158
- async with aiosqlite.connect(DB_PATH) as db:
159
- async with db.execute(
160
- "SELECT id, chat_id, msg_id, media_group_id, media_type, caption, file_path, file_size, timestamp FROM queue WHERE status='DOWNLOADED' ORDER BY id ASC LIMIT 1"
161
- ) as cursor:
162
- row = await cursor.fetchone()
163
-
164
- if not row:
165
- await asyncio.sleep(2)
166
- continue
167
-
168
- q_id, chat_id, msg_id, group_id, m_type, caption, file_path, file_size, ts = row
169
-
170
- if not os.path.exists(file_path):
171
- async with aiosqlite.connect(DB_PATH) as db:
172
- await db.execute("UPDATE queue SET status='FAILED' WHERE id=?", (q_id,))
173
- await db.commit()
174
- continue
175
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  try:
177
  if m_type == "photo":
178
- await app.send_chat_action(TARGET_CHAT, ChatAction.UPLOAD_PHOTO)
179
- elif m_type in ["video", "animation"]:
180
- await app.send_chat_action(TARGET_CHAT, ChatAction.UPLOAD_VIDEO)
181
  elif m_type == "doc":
182
- await app.send_chat_action(TARGET_CHAT, ChatAction.UPLOAD_DOCUMENT)
183
- elif m_type in ["audio", "voice"]:
184
- await app.send_chat_action(TARGET_CHAT, ChatAction.UPLOAD_AUDIO)
185
- await asyncio.sleep(random.uniform(1.0, 2.0))
 
 
 
 
 
 
 
 
186
  except:
187
  pass
188
 
189
- if group_id:
190
- time_passed = time.time() - ts
191
- if time_passed < 10:
192
- await asyncio.sleep(10 - time_passed)
193
-
194
- async with aiosqlite.connect(DB_PATH) as db:
195
- async with db.execute(
196
- "SELECT id, media_type, caption, file_path FROM queue WHERE chat_id=? AND media_group_id=? AND status='DOWNLOADED' ORDER BY msg_id ASC",
197
- (chat_id, group_id)
198
- ) as cursor:
199
- group_rows = await cursor.fetchall()
200
-
201
- if group_rows:
202
- for i in range(0, len(group_rows), 10):
203
- chunk = group_rows[i:i+10]
204
- media_list, ids_to_update, files_to_delete = [], [], []
205
-
206
- for idx, g_item in enumerate(chunk):
207
- g_id, g_type, g_cap, g_path = g_item
208
- item_caption = g_cap if idx == 0 else ""
209
-
210
- if g_type == "photo":
211
- media_list.append(InputMediaPhoto(media=g_path, caption=item_caption))
212
- elif g_type == "video":
213
- media_list.append(InputMediaVideo(media=g_path, caption=item_caption, supports_streaming=True))
214
- elif g_type == "doc":
215
- media_list.append(InputMediaDocument(media=g_path, caption=item_caption))
216
- elif g_type == "audio":
217
- media_list.append(InputMediaAudio(media=g_path, caption=item_caption))
218
-
219
- ids_to_update.append(g_id)
220
- files_to_delete.append(g_path)
221
-
222
- if media_list:
223
- await app.send_media_group(TARGET_CHAT, media=media_list)
224
- async with aiosqlite.connect(DB_PATH) as db:
225
- for item_id in ids_to_update:
226
- await db.execute("UPDATE queue SET status='UPLOADED' WHERE id=?", (item_id,))
227
- await db.commit()
228
-
229
- for f_path in files_to_delete:
230
- if os.path.exists(f_path):
231
- os.remove(f_path)
232
-
233
- gc.collect()
234
- await asyncio.sleep(BASE_UPLOAD_DELAY + random.uniform(2.0, 4.0))
235
- continue
236
-
237
- if m_type == "photo":
238
- await app.send_photo(TARGET_CHAT, photo=file_path, caption=caption)
239
- elif m_type == "video":
240
- await app.send_video(TARGET_CHAT, video=file_path, caption=caption, supports_streaming=True)
241
- elif m_type == "doc":
242
- await app.send_document(TARGET_CHAT, document=file_path, caption=caption)
243
- elif m_type == "audio":
244
- await app.send_audio(TARGET_CHAT, audio=file_path, caption=caption)
245
- elif m_type == "voice":
246
- await app.send_voice(TARGET_CHAT, voice=file_path, caption=caption)
247
- elif m_type == "video_note":
248
- await app.send_video_note(TARGET_CHAT, video_note=file_path)
249
- elif m_type == "animation":
250
- await app.send_animation(TARGET_CHAT, animation=file_path, caption=caption)
251
-
252
- async with aiosqlite.connect(DB_PATH) as db:
253
- await db.execute("UPDATE queue SET status='UPLOADED' WHERE id=?", (q_id,))
254
- await db.commit()
255
-
256
- if os.path.exists(file_path):
257
- os.remove(file_path)
258
-
259
- size_factor = min(file_size / (10 * 1024 * 1024), 10)
260
- safe_sleep = BASE_UPLOAD_DELAY + size_factor + random.uniform(1.0, 3.0)
261
-
262
- gc.collect()
263
- await asyncio.sleep(safe_sleep)
264
-
265
- except FloodWait as e:
266
- print(f"⚠️ FloodWait: {e.value} ثانية")
267
- BASE_UPLOAD_DELAY += 5
268
- await asyncio.sleep(e.value + random.uniform(10, 20))
269
- except Exception as e:
270
- print(f"⚠️ خطأ: {e}")
271
- async with aiosqlite.connect(DB_PATH) as db:
272
- await db.execute("UPDATE queue SET status='FAILED' WHERE id=?", (q_id,))
273
- await db.commit()
274
- if os.path.exists(file_path):
275
- os.remove(file_path)
276
- await asyncio.sleep(5)
277
-
278
- # ============ التشغيل الرئيسي ============
279
- async def main():
280
- await init_db()
281
- await app.start()
282
 
283
- print("🔄 جاري الاتصال...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  try:
285
- me = await app.get_me()
286
- print(f"✅ متصل: {me.first_name}")
287
- async for _ in app.get_dialogs(limit=150):
288
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  except Exception as e:
290
- print(f"⚠️ تحذير: {e}")
 
 
 
 
 
 
291
 
292
- print("🚀 البوت يعمل!")
293
-
294
- for i in range(2):
295
- asyncio.create_task(download_worker_task(i + 1))
296
-
297
- asyncio.create_task(upload_worker())
 
 
 
 
298
 
299
- await idle()
300
- await app.stop()
301
-
302
- def run_bot():
303
- """تشغيل البوت في thread منفصلة"""
304
- loop = asyncio.new_event_loop()
305
- asyncio.set_event_loop(loop)
306
  while True:
307
- try:
308
- loop.run_until_complete(main())
309
- except Exception as e:
310
- print(f"💥 إعادة تشغيل: {e}")
311
- time.sleep(10)
312
-
313
- # ============ Gradio Interface ============
314
- bot_status = {"running": False, "logs": "⏳ جاري التشغيل..."}
315
-
316
- def start_bot():
317
- if not bot_status["running"]:
318
- bot_status["running"] = True
319
- bot_status["logs"] = "🟢 البوت يعمل في الخلفية"
320
- thread = threading.Thread(target=run_bot, daemon=True)
321
- thread.start()
322
- return "✅ تم تشغيل البوت!"
323
- return "⚠️ البوت يعمل بالفعل!"
324
-
325
- # تشغيل البوت تلقائياً
326
- start_bot()
327
 
 
328
  with gr.Blocks(title="Telegram Archiver") as demo:
329
- gr.Markdown("# 📡 Telegram Archiver Bot")
330
- gr.Markdown("البوت يعمل في الخلفية ويقوم بأرشفة الملفات تلقائياً")
331
 
332
- status_box = gr.Textbox(label="الحالة", value="🟢 البوت يعمل", interactive=False)
333
- gr.Markdown("### المتغيرات المطلوبة في Settings:")
334
- gr.Markdown("- `API_ID`\n- `API_HASH`\n- `SESSION_STRING`\n- `TARGET_CHAT`")
335
-
336
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
2
  import asyncio
3
+ import logging
 
4
  import shutil
5
+ import time
6
+ import gradio as gr
7
+ from pyrogram import Client, filters
8
+ from pyrogram.errors import FloodWait, RPCError
9
  from pyrogram.types import InputMediaPhoto, InputMediaVideo, InputMediaDocument, InputMediaAudio
10
  from pyrogram.enums import ChatAction
 
11
 
12
+ logging.basicConfig(
13
+ level=logging.INFO,
14
+ format='%(asctime)s - %(levelname)s - %(message)s'
15
+ )
16
+ logger = logging.getLogger(__name__)
 
 
 
17
 
18
+ # ============ الإعدادات ============
19
  API_ID = int(os.environ.get("API_ID", 0))
20
  API_HASH = os.environ.get("API_HASH", "")
21
  SESSION_STRING = os.environ.get("SESSION_STRING", "")
22
  TARGET_CHAT = int(os.environ.get("TARGET_CHAT", 0))
23
+ SOURCE_CHATS = os.environ.get("SOURCE_CHATS", "")
24
 
25
+ SOURCE_CHAT_IDS = []
26
+ if SOURCE_CHATS:
27
+ try:
28
+ SOURCE_CHAT_IDS = [int(x.strip()) for x in SOURCE_CHATS.split(",") if x.strip()]
29
+ except:
30
+ logger.error("❌ تنسيق SOURCE_CHATS غير صحيح")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ DATA_DIR = "./data"
33
+ DOWNLOAD_DIR = os.path.join(DATA_DIR, "downloads")
34
+ os.makedirs(DOWNLOAD_DIR, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ logger.info(f"🎯 الهدف: {TARGET_CHAT} | المصادر: {SOURCE_CHAT_IDS or 'الكل'}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ # ============ العميل ============
39
  app = Client(
40
+ "archiver",
41
  api_id=API_ID,
42
  api_hash=API_HASH,
43
  session_string=SESSION_STRING,
44
  in_memory=True,
45
  device_model="Samsung Galaxy S23 Ultra",
46
+ system_version="Android 14",
47
  app_version="10.14.1",
48
  lang_code="ar"
49
  )
50
 
51
+ # ============ دوال مساعدة ============
52
+ def get_media_info(message):
53
+ """استخرج معلومات الملف من الرسالة"""
54
+ if message.photo:
55
+ return "photo", message.photo.file_size or 0
56
+ elif message.video:
57
+ return "video", message.video.file_size or 0
58
+ elif message.document:
59
+ return "doc", message.document.file_size or 0
60
+ elif message.audio:
61
+ return "audio", message.audio.file_size or 0
62
+ elif message.voice:
63
+ return "voice", message.voice.file_size or 0
64
+ elif message.video_note:
65
+ return "video_note", message.video_note.file_size or 0
66
+ elif message.animation:
67
+ return "animation", message.animation.file_size or 0
68
+ return None, 0
69
+
70
+ # ============ التعامل مع الألبومات ============
71
+ media_groups = {}
72
+ mg_lock = asyncio.Lock()
73
+
74
+ async def flush_media_group(chat_id, mg_id):
75
+ """انتظر 5 ثواني لجمع كل أجزاء الألبوم ثم أرسلها"""
76
+ await asyncio.sleep(5)
77
 
78
+ async with mg_lock:
79
+ key = (chat_id, mg_id)
80
+ if key not in media_groups:
81
+ return
82
+ messages = media_groups.pop(key, [])
83
 
84
+ if not messages:
85
+ return
86
+
87
+ messages.sort(key=lambda m: m.id)
88
+ logger.info(f"📦 ألبوم {mg_id} يحتوي على {len(messages)} ملفات")
89
+
90
+ downloaded = []
91
+ for i, msg in enumerate(messages):
92
+ m_type, fsize = get_media_info(msg)
93
+ if not m_type:
94
+ continue
95
+
96
+ cap = msg.caption if i == 0 else ""
97
+ src_title = msg.chat.title or "المصدر"
98
+ final_cap = f"{cap}\n\n📌 المصدر: {src_title}".strip() if cap else f"📌 المصدر: {src_title}"
99
+
100
  try:
101
+ file_path = await msg.download(file_name=f"{DOWNLOAD_DIR}/{chat_id}_{msg.id}_")
102
+ if file_path and os.path.exists(file_path) and os.path.getsize(file_path) > 0:
103
+ downloaded.append((m_type, file_path, final_cap))
104
+ logger.info(f"⬇️ تم التنزيل: {msg.id}")
105
+ else:
106
+ logger.warning(f"⚠️ فشل تنزيل: {msg.id}")
107
+ except Exception as e:
108
+ logger.error(f"❌ خطأ تنزيل {msg.id}: {e}")
109
+
110
+ if not downloaded:
111
+ return
112
+
113
+ # إرسال الألبوم
114
+ try:
115
+ media_list = []
116
+ for idx, (m_type, fpath, cap) in enumerate(downloaded):
117
+ item_cap = cap if idx == 0 else ""
118
+
119
+ if m_type == "photo":
120
+ media_list.append(InputMediaPhoto(media=fpath, caption=item_cap))
121
+ elif m_type == "video":
122
+ media_list.append(InputMediaVideo(media=fpath, caption=item_cap, supports_streaming=True))
123
+ elif m_type == "doc":
124
+ media_list.append(InputMediaDocument(media=fpath, caption=item_cap))
125
+ elif m_type == "audio":
126
+ media_list.append(InputMediaAudio(media=fpath, caption=item_cap))
127
+
128
+ if media_list:
129
+ await app.send_media_group(TARGET_CHAT, media=media_list)
130
+ logger.info(f"✅ تم إرسال الألبوم {mg_id}")
131
+ except FloodWait as e:
132
+ logger.warning(f"⏳ FloodWait: {e.value} ثانية")
133
+ await asyncio.sleep(e.value + 5)
134
+ await app.send_media_group(TARGET_CHAT, media=media_list)
135
+ except Exception as e:
136
+ logger.error(f"❌ فشل إرسال الألبوم: {e}")
137
+ # احتياطي: أرسل كل ملف لوحده
138
+ for m_type, fpath, cap in downloaded:
139
  try:
140
  if m_type == "photo":
141
+ await app.send_photo(TARGET_CHAT, photo=fpath, caption=cap)
142
+ elif m_type == "video":
143
+ await app.send_video(TARGET_CHAT, video=fpath, caption=cap, supports_streaming=True)
144
  elif m_type == "doc":
145
+ await app.send_document(TARGET_CHAT, document=fpath, caption=cap)
146
+ elif m_type == "audio":
147
+ await app.send_audio(TARGET_CHAT, audio=fpath, caption=cap)
148
+ await asyncio.sleep(2)
149
+ except Exception as e2:
150
+ logger.error(f"❌ فشل الإرسال الفردي: {e2}")
151
+ finally:
152
+ # حذف الملفات المؤقتة
153
+ for _, fpath, _ in downloaded:
154
+ try:
155
+ if os.path.exists(fpath):
156
+ os.remove(fpath)
157
  except:
158
  pass
159
 
160
+ # ============ صائد الرسائل المنفردة ============
161
+ @app.on_message(filters.media & (filters.channel | filters.group))
162
+ async def on_media(client, message):
163
+ chat_id = message.chat.id
164
+
165
+ if SOURCE_CHAT_IDS and chat_id not in SOURCE_CHAT_IDS:
166
+ return
167
+ if chat_id == TARGET_CHAT:
168
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
+ logger.info(f"📥 جديد من {chat_id} | رسالة {message.id}")
171
+
172
+ # إذا ألبوم — اجمعه
173
+ if message.media_group_id:
174
+ key = (chat_id, message.media_group_id)
175
+ async with mg_lock:
176
+ if key not in media_groups:
177
+ media_groups[key] = []
178
+ asyncio.create_task(flush_media_group(chat_id, message.media_group_id))
179
+ media_groups[key].append(message)
180
+ return
181
+
182
+ # رسالة منفردة — حمل وأرسل
183
+ m_type, fsize = get_media_info(message)
184
+ if not m_type:
185
+ return
186
+
187
+ src_title = message.chat.title or "المصدر"
188
+ raw_cap = message.caption or ""
189
+ final_cap = f"{raw_cap}\n\n📌 المصدر: {src_title}".strip() if raw_cap else f"📌 المصدر: {src_title}"
190
+
191
+ file_path = None
192
  try:
193
+ file_path = await message.download(file_name=f"{DOWNLOAD_DIR}/{chat_id}_{message.id}_")
194
+ if not file_path or not os.path.exists(file_path) or os.path.getsize(file_path) == 0:
195
+ logger.warning(f"⚠️ فشل تنزيل الرسالة {message.id}")
196
+ return
197
+
198
+ # إرسال حسب النوع
199
+ if m_type == "photo":
200
+ await app.send_photo(TARGET_CHAT, photo=file_path, caption=final_cap)
201
+ elif m_type == "video":
202
+ await app.send_video(TARGET_CHAT, video=file_path, caption=final_cap, supports_streaming=True)
203
+ elif m_type == "doc":
204
+ await app.send_document(TARGET_CHAT, document=file_path, caption=final_cap)
205
+ elif m_type == "audio":
206
+ await app.send_audio(TARGET_CHAT, audio=file_path, caption=final_cap)
207
+ elif m_type == "voice":
208
+ await app.send_voice(TARGET_CHAT, voice=file_path, caption=final_cap)
209
+ elif m_type == "video_note":
210
+ await app.send_video_note(TARGET_CHAT, video_note=file_path)
211
+ elif m_type == "animation":
212
+ await app.send_animation(TARGET_CHAT, animation=file_path, caption=final_cap)
213
+
214
+ logger.info(f"✅ تم الإرس��ل: {message.id}")
215
+ await asyncio.sleep(3) # تأخير بسيط لتجنب الحظر
216
+
217
+ except FloodWait as e:
218
+ logger.warning(f"⏳ FloodWait: {e.value} ثانية")
219
+ await asyncio.sleep(e.value + 5)
220
+ # إعادة المحاولة
221
+ if m_type == "photo":
222
+ await app.send_photo(TARGET_CHAT, photo=file_path, caption=final_cap)
223
+ elif m_type == "video":
224
+ await app.send_video(TARGET_CHAT, video=file_path, caption=final_cap)
225
+ elif m_type == "doc":
226
+ await app.send_document(TARGET_CHAT, document=file_path, caption=final_cap)
227
+ except RPCError as e:
228
+ logger.error(f"❌ خطأ Telegram: {e}")
229
  except Exception as e:
230
+ logger.error(f"❌ خطأ: {e}")
231
+ finally:
232
+ if file_path and os.path.exists(file_path):
233
+ try:
234
+ os.remove(file_path)
235
+ except:
236
+ pass
237
 
238
+ # ============ التشغيل ============
239
+ async def startup():
240
+ await app.start()
241
+ me = await app.get_me()
242
+ logger.info("=" * 50)
243
+ logger.info(f"🚀 البوت يعمل!")
244
+ logger.info(f"👤 الحساب: {me.first_name} (@{me.username})")
245
+ logger.info(f"🎯 القناة الهدف: {TARGET_CHAT}")
246
+ logger.info(f"📡 القنوات المصدر: {SOURCE_CHAT_IDS or 'كل القنوات'}")
247
+ logger.info("=" * 50)
248
 
 
 
 
 
 
 
 
249
  while True:
250
+ await asyncio.sleep(3600)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
+ # ============ واجهة Gradio ============
253
  with gr.Blocks(title="Telegram Archiver") as demo:
254
+ gr.Markdown("# 📡 Telegram Archiver")
255
+ gr.Markdown("🟢 **الحالة:** البوت يعمل في الخلفية")
256
 
257
+ gr.Textbox(
258
+ label="الحالة",
259
+ value="✅ نشط - افتح تبويب Logs لرؤية النشاط",
260
+ interactive=False
261
+ )
262
+
263
+ gr.Markdown("""
264
+ ### 🔧 المتغيرات المطلوبة:
265
+
266
+ | المتغير | الوصف |
267
+ |---------|-------|
268
+ | `API_ID` | من my.telegram.org |
269
+ | `API_HASH` | من my.telegram.org |
270
+ | `SESSION_STRING` | سلسلة الجلسة |
271
+ | `TARGET_CHAT` | معرف قناتك الخاصة |
272
+ | `SOURCE_CHATS` | (اختياري) قنوات المصدر |
273
+
274
+ ### ⚠️ ملاحظة مهمة:
275
+ الحساب لازم يكون **مشترك** في القناة المصدرية حتى يقدر يحمل منها.
276
+ """)
277
+
278
+ if __name__ == "__main__":
279
+ loop = asyncio.get_event_loop()
280
+ loop.create_task(startup())
281
+ demo.launch(server_name="0.0.0.0", server_port=7860)