geopromini commited on
Commit
bcb6bfc
·
verified ·
1 Parent(s): f565b32

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +73 -72
  2. requirements.txt +4 -3
app.py CHANGED
@@ -1,6 +1,6 @@
1
- # app.py (Cleaned & Adapted for Download/Mute via Telegram/Worker)
2
- # import nest_asyncio # Removed as per previous error
3
- # nest_asyncio.apply() # Removed as per previous error
4
 
5
  import os
6
  import shutil
@@ -11,6 +11,7 @@ import time
11
  from pathlib import Path
12
  import json
13
  import sys
 
14
 
15
  # Web Server & Bot
16
  import httpx
@@ -27,103 +28,97 @@ except ImportError:
27
  sys.exit(1)
28
  import yt_dlp
29
 
30
- # === DNS FIX (RE-ADDED FROM YOUR REFERENCE) ===
31
  import socket
32
- import aiodns # Make sure 'aiodns' is in requirements.txt
33
  original_getaddrinfo = socket.getaddrinfo
34
  async def custom_getaddrinfo_async(host, port, family=0, type=0, proto=0, flags=0):
35
- # Use Google's public DNS servers
36
  resolver = aiodns.DNSResolver(nameservers=['8.8.8.8', '8.8.4.4'])
37
  try:
38
- # Query for A records (IPv4 addresses)
39
  result = await resolver.query(host, 'A'); addrlist = []
40
- for record in result:
41
- # Format the result similar to socket.getaddrinfo
42
- addrlist.append((socket.AF_INET, socket.SOCK_STREAM, 6, '', (record.host, port)))
43
- # If successful, return the list of addresses
44
- if addrlist: # Check if list is not empty
45
  return addrlist
46
- else: # If empty, fall back to original (should ideally not happen with A record query)
47
  print(f"WARNING: aiodns query for {host} returned empty list. Falling back.")
48
- raise aiodns.error.DNSError() # Trigger fallback
49
  except aiodns.error.DNSError as e:
50
- # If aiodns fails (e.g., domain doesn't exist, timeout), use the original method
51
  print(f"aiodns lookup failed for {host}: {e}. Falling back to original getaddrinfo.")
52
  loop = asyncio.get_running_loop()
53
- # Run the original synchronous socket.getaddrinfo in a separate thread
54
  return await loop.run_in_executor(None, original_getaddrinfo, host, port, family, type, proto, flags)
55
  except Exception as e_generic:
56
- # Catch any other unexpected errors during DNS resolution
57
  print(f"Unexpected error during custom DNS resolution for {host}: {e_generic}. Falling back.")
58
  loop = asyncio.get_running_loop()
59
  return await loop.run_in_executor(None, original_getaddrinfo, host, port, family, type, proto, flags)
60
 
61
  def custom_getaddrinfo_sync(*args, **kwargs):
62
- # This function wraps the async version to make it compatible with synchronous code
63
- try:
64
- # Get the currently running event loop if one exists
65
- loop = asyncio.get_running_loop()
66
- except RuntimeError:
67
- # If no loop is running (e.g., in some threading scenarios), create a new one
68
- loop = asyncio.new_event_loop()
69
- asyncio.set_event_loop(loop)
70
- # Run the async DNS lookup until it completes and return the result
71
  return loop.run_until_complete(custom_getaddrinfo_async(*args, **kwargs))
72
 
73
- # --- MONKEY-PATCHING ---
74
- # Replace the standard socket.getaddrinfo with our custom synchronous wrapper
75
  socket.getaddrinfo = custom_getaddrinfo_sync
76
  print("✅ DNS Resolver Patched using aiodns.")
77
- # === END OF DNS FIX ===
78
 
79
  # --- CONFIGURATION ---
80
- WORKER_URL = os.environ.get("WORKER_URL") # Get Worker URL from HF Secrets
81
- LOCAL_TEMP_FOLDER = "/tmp/downloader_temp"
82
- os.makedirs(LOCAL_TEMP_FOLDER, exist_ok=True) # Đảm bảo thư mục tồn tạ
83
-
84
- # User session storage
85
  USER_SESSIONS = {}
86
 
87
- # --- TELEGRAM UTILS ---
88
- # !! IMPORTANT !! Ensure these functions correctly POST to your WORKER_URL
 
89
  async def send_telegram_message(chat_id, text, reply_markup=None):
90
- if not WORKER_URL: print("ERROR: WORKER_URL not set!"); return
91
  url = f"{WORKER_URL}"
92
- # Use MarkdownV2 for better formatting control if your worker supports it
93
  payload = {'action': 'sendMessage', 'chat_id': str(chat_id), 'text': text, 'parse_mode': 'Markdown'}
94
  if reply_markup: payload['reply_markup'] = json.dumps(reply_markup)
95
  try:
96
  async with httpx.AsyncClient() as client:
97
  response = await client.post(url, json=payload, timeout=60)
98
  response.raise_for_status()
 
99
  except Exception as e:
100
  print(f"ERROR sending message to {chat_id} via worker: {e}")
 
101
 
102
  async def send_telegram_video(chat_id, video_path, caption):
 
 
 
 
103
  if not WORKER_URL: print("ERROR: WORKER_URL not set!"); return False
104
- url = f"{WORKER_URL}"
105
  if not os.path.exists(video_path):
106
  print(f"ERROR: Video file not found: {video_path}")
107
- await send_telegram_message(chat_id, f"❌ Lỗi Server: Không tìm thấy file `{os.path.basename(video_path)}` để gửi.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  return False
109
- # Limit file size check (e.g., Telegram's 50MB limit for bots via API might apply depending on worker setup)
110
- file_size_mb = os.path.getsize(video_path) / (1024 * 1024)
111
- if file_size_mb > 49: # Example limit
112
- await send_telegram_message(chat_id, f"⚠️ File `{os.path.basename(video_path)}` ({file_size_mb:.1f}MB) quá lớn để gửi qua bot. Bạn cần tải thủ công nếu muốn.")
113
- return False
114
-
115
- with open(video_path, 'rb') as video_file:
116
- files = { 'video': (os.path.basename(video_path), video_file, 'video/mp4') }
117
- data = { 'action': 'sendVideo', 'chat_id': str(chat_id), 'caption': caption }
118
- try:
119
- async with httpx.AsyncClient() as client:
120
- response = await client.post(url, data=data, files=files, timeout=600)
121
- response.raise_for_status()
122
- return True
123
- except Exception as e:
124
- print(f"ERROR sending video {video_path} to {chat_id} via worker: {e}")
125
- await send_telegram_message(chat_id, f"❌ Lỗi khi gửi file `{os.path.basename(video_path)}`: {e}")
126
- return False
127
 
128
  async def answer_telegram_callback_query(callback_query_id, text="OK"):
129
  if not WORKER_URL: print("ERROR: WORKER_URL not set!"); return
@@ -151,7 +146,7 @@ def get_script_dir():
151
  return os.getcwd()
152
 
153
  async def mute_video_with_moviepy_hf(original_video_path, chat_id):
154
- """ Mutes video using MoviePy, sends status messages via Telegram. """
155
  await send_telegram_message(chat_id, f" 🔇 Bắt đầu tắt tiếng: `{os.path.basename(original_video_path)}`")
156
  original_path = Path(original_video_path)
157
  muted_filename = original_path.parent / f"{original_path.stem}_MUTED.mp4"
@@ -192,7 +187,7 @@ async def mute_video_with_moviepy_hf(original_video_path, chat_id):
192
  return (True, str(muted_filename))
193
 
194
  async def download_single_video_hf(url, output_path, chat_id):
195
- """ Downloads a single video using yt-dlp, using COOKIE_STRING secret. """
196
  await send_telegram_message(chat_id, f"⬇️ Bắt đầu tải: `{url}`")
197
  original_filename = None
198
  loop = asyncio.get_running_loop()
@@ -239,7 +234,6 @@ async def download_single_video_hf(url, output_path, chat_id):
239
  await loop.run_in_executor(None, sync_download)
240
 
241
  if not downloaded_file_holder:
242
- # Try getting filename if download happened but hook missed (e.g., file existed)
243
  try:
244
  temp_ydl_opts = ydl_opts.copy(); temp_ydl_opts['skip_download'] = True
245
  with yt_dlp.YoutubeDL(temp_ydl_opts) as ydl_info:
@@ -268,7 +262,7 @@ async def download_single_video_hf(url, output_path, chat_id):
268
 
269
  # --- Background Task for Processing ---
270
  async def process_links_task(chat_id, urls, is_muting):
271
- """ Background task to download, optionally mute, and send videos. """
272
  success_count = 0; fail_count = 0; mute_fail_count = 0; send_errors = 0
273
  total_links = len(urls)
274
  processed_files_info = []
@@ -297,9 +291,9 @@ async def process_links_task(chat_id, urls, is_muting):
297
  fail_count += 1
298
  await send_telegram_message(chat_id, f"❌ Lỗi hệ thống khi xử lý link {i+1}: `{e}`")
299
  traceback.print_exc()
300
- await asyncio.sleep(1) # Delay
301
 
302
- # --- Sending results ---
303
  await send_telegram_message(chat_id, f"--- Hoàn tất tải {success_count}/{total_links} video gốc ---")
304
  if processed_files_info:
305
  await send_telegram_message(chat_id, f"--- Bắt đầu gửi {len(processed_files_info)} video ---")
@@ -315,19 +309,22 @@ async def process_links_task(chat_id, urls, is_muting):
315
 
316
  if file_to_send and os.path.exists(file_to_send):
317
  await send_telegram_message(chat_id, f" 📤 Đang gửi video {i+1}/{len(processed_files_info)}...")
318
- if await send_telegram_video(chat_id, file_to_send, caption): sent_count += 1
319
- else: send_errors += 1
 
 
 
320
  else: await send_telegram_message(chat_id, f" ⚠️ Không tìm thấy file đã xử lý cho video {i+1} để gửi.")
321
- await asyncio.sleep(0.5) # Small delay between sending files
322
 
323
- # --- Final Summary ---
324
  summary_message = f"🏁 Hoàn thành!\n- Tải thành công: {success_count}/{total_links}"
325
  if fail_count > 0: summary_message += f"\n- Tải thất bại: {fail_count}"
326
  if is_muting and mute_fail_count > 0: summary_message += f"\n- Lỗi tắt tiếng: {mute_fail_count}"
327
  if send_errors > 0: summary_message += f"\n- Lỗi gửi file: {send_errors}"
328
  await send_telegram_message(chat_id, summary_message)
329
 
330
- # --- Cleanup ---
331
  if chat_id in USER_SESSIONS: del USER_SESSIONS[chat_id]
332
  try:
333
  shutil.rmtree(task_temp_folder)
@@ -356,7 +353,6 @@ class TelegramUpdate(BaseModel):
356
  @app.post("/webhook")
357
  async def handle_webhook(update: TelegramUpdate, background_tasks: BackgroundTasks):
358
  if update.callback_query:
359
- # --- Handle Button Clicks ---
360
  cq = update.callback_query
361
  chat_id, msg_id, data, cb_id = cq["message"]["chat"]["id"], cq["message"]["message_id"], cq["data"], cq["id"]
362
  session = USER_SESSIONS.get(chat_id)
@@ -371,7 +367,6 @@ async def handle_webhook(update: TelegramUpdate, background_tasks: BackgroundTas
371
  return {"status": "ok, processing started"}
372
 
373
  elif update.message:
374
- # --- Handle Incoming Messages ---
375
  msg = update.message
376
  chat_id, text = msg["chat"]["id"], msg.get("text", "").strip()
377
 
@@ -398,4 +393,10 @@ async def handle_webhook(update: TelegramUpdate, background_tasks: BackgroundTas
398
  return {"status": "ok, ignored"}
399
 
400
  @app.get("/")
401
- def read_root(): return {"message": "Downloader Bot is running."}
 
 
 
 
 
 
 
1
+ # app.py (ĐÃ SỬA: Kết hợp DNS Fix và logic Worker từ file tham khảo)
2
+ import nest_asyncio
3
+ nest_asyncio.apply()
4
 
5
  import os
6
  import shutil
 
11
  from pathlib import Path
12
  import json
13
  import sys
14
+ import base64 # <-- RẤT QUAN TRỌNG, dùng để gửi video qua worker
15
 
16
  # Web Server & Bot
17
  import httpx
 
28
  sys.exit(1)
29
  import yt_dlp
30
 
31
+ # === DNS FIX (LẤY TỪ FILE THAM KHẢO CỦA BẠN) ===
32
  import socket
33
+ import aiodns
34
  original_getaddrinfo = socket.getaddrinfo
35
  async def custom_getaddrinfo_async(host, port, family=0, type=0, proto=0, flags=0):
 
36
  resolver = aiodns.DNSResolver(nameservers=['8.8.8.8', '8.8.4.4'])
37
  try:
 
38
  result = await resolver.query(host, 'A'); addrlist = []
39
+ for record in result: addrlist.append((socket.AF_INET, socket.SOCK_STREAM, 6, '', (record.host, port)))
40
+ if addrlist:
 
 
 
41
  return addrlist
42
+ else:
43
  print(f"WARNING: aiodns query for {host} returned empty list. Falling back.")
44
+ raise aiodns.error.DNSError()
45
  except aiodns.error.DNSError as e:
 
46
  print(f"aiodns lookup failed for {host}: {e}. Falling back to original getaddrinfo.")
47
  loop = asyncio.get_running_loop()
 
48
  return await loop.run_in_executor(None, original_getaddrinfo, host, port, family, type, proto, flags)
49
  except Exception as e_generic:
 
50
  print(f"Unexpected error during custom DNS resolution for {host}: {e_generic}. Falling back.")
51
  loop = asyncio.get_running_loop()
52
  return await loop.run_in_executor(None, original_getaddrinfo, host, port, family, type, proto, flags)
53
 
54
  def custom_getaddrinfo_sync(*args, **kwargs):
55
+ try: loop = asyncio.get_running_loop()
56
+ except RuntimeError: loop = asyncio.new_event_loop(); asyncio.set_event_loop(loop)
 
 
 
 
 
 
 
57
  return loop.run_until_complete(custom_getaddrinfo_async(*args, **kwargs))
58
 
 
 
59
  socket.getaddrinfo = custom_getaddrinfo_sync
60
  print("✅ DNS Resolver Patched using aiodns.")
61
+ # === KẾT THÚC DNS FIX ===
62
 
63
  # --- CONFIGURATION ---
64
+ # Sửa lại WORKER_URL (file tham khảo của bạn dùng GOOGLE_APP_SCRIPT_URL,
65
+ # nhưng chúng ta dùng WORKER_URL cho nhất quán với các tin nhắn trước)
66
+ WORKER_URL = os.environ.get("WORKER_URL")
67
+ LOCAL_TEMP_FOLDER = "/tmp/downloader_temp" # Sửa lỗi Permission Denied
68
+ os.makedirs(LOCAL_TEMP_FOLDER, exist_ok=True)
69
  USER_SESSIONS = {}
70
 
71
+ # --- TELEGRAM UTILS (DỰA TRÊN LOGIC FILE THAM KHẢO CỦA BẠN) ---
72
+ # Các hàm này sẽ POST đến WORKER_URL (thay GOOGLE_APP_SCRIPT_URL)
73
+
74
  async def send_telegram_message(chat_id, text, reply_markup=None):
75
+ if not WORKER_URL: print("ERROR: WORKER_URL not set!"); return False
76
  url = f"{WORKER_URL}"
 
77
  payload = {'action': 'sendMessage', 'chat_id': str(chat_id), 'text': text, 'parse_mode': 'Markdown'}
78
  if reply_markup: payload['reply_markup'] = json.dumps(reply_markup)
79
  try:
80
  async with httpx.AsyncClient() as client:
81
  response = await client.post(url, json=payload, timeout=60)
82
  response.raise_for_status()
83
+ return True
84
  except Exception as e:
85
  print(f"ERROR sending message to {chat_id} via worker: {e}")
86
+ return False
87
 
88
  async def send_telegram_video(chat_id, video_path, caption):
89
+ """
90
+ Gửi video qua Worker, SỬ DỤNG LOGIC BASE64
91
+ để khớp với file worker.js bạn đã cung cấp.
92
+ """
93
  if not WORKER_URL: print("ERROR: WORKER_URL not set!"); return False
 
94
  if not os.path.exists(video_path):
95
  print(f"ERROR: Video file not found: {video_path}")
96
+ await send_telegram_message(chat_id, f"❌ Lỗi Server: Không tìm thấy file `{os.path.basename(video_path)}`.")
97
+ return False
98
+
99
+ try:
100
+ # Đọc file và encode Base64 (GIỐNG HỆT file tham khảo của bạn)
101
+ with open(video_path, 'rb') as vf:
102
+ video_b64 = base64.b64encode(vf.read()).decode('utf-8')
103
+
104
+ # Tạo payload JSON (GIỐNG HỆT file tham khảo của bạn)
105
+ payload = {
106
+ "action": "sendVideo",
107
+ "chat_id": str(chat_id),
108
+ "caption": caption,
109
+ "video_b64": video_b64,
110
+ "filename": os.path.basename(video_path)
111
+ }
112
+
113
+ async with httpx.AsyncClient() as client:
114
+ # Gửi JSON, tăng timeout vì Base64 rất lớn
115
+ response = await client.post(url, json=payload, timeout=600.0)
116
+ response.raise_for_status()
117
+ return True
118
+ except Exception as e:
119
+ print(f"ERROR sending video (Base64) {video_path} to {chat_id} via worker: {e}")
120
+ await send_telegram_message(chat_id, f"❌ Lỗi khi gửi file (Base64) `{os.path.basename(video_path)}`: {e}")
121
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
  async def answer_telegram_callback_query(callback_query_id, text="OK"):
124
  if not WORKER_URL: print("ERROR: WORKER_URL not set!"); return
 
146
  return os.getcwd()
147
 
148
  async def mute_video_with_moviepy_hf(original_video_path, chat_id):
149
+ """ Tắt tiếng video bằng MoviePy, gửi thông báo qua Telegram. """
150
  await send_telegram_message(chat_id, f" 🔇 Bắt đầu tắt tiếng: `{os.path.basename(original_video_path)}`")
151
  original_path = Path(original_video_path)
152
  muted_filename = original_path.parent / f"{original_path.stem}_MUTED.mp4"
 
187
  return (True, str(muted_filename))
188
 
189
  async def download_single_video_hf(url, output_path, chat_id):
190
+ """ Tải video dùng yt-dlp, sử dụng COOKIE_STRING secret. """
191
  await send_telegram_message(chat_id, f"⬇️ Bắt đầu tải: `{url}`")
192
  original_filename = None
193
  loop = asyncio.get_running_loop()
 
234
  await loop.run_in_executor(None, sync_download)
235
 
236
  if not downloaded_file_holder:
 
237
  try:
238
  temp_ydl_opts = ydl_opts.copy(); temp_ydl_opts['skip_download'] = True
239
  with yt_dlp.YoutubeDL(temp_ydl_opts) as ydl_info:
 
262
 
263
  # --- Background Task for Processing ---
264
  async def process_links_task(chat_id, urls, is_muting):
265
+ """ Tác vụ nền để tải, tắt tiếng gửi video. """
266
  success_count = 0; fail_count = 0; mute_fail_count = 0; send_errors = 0
267
  total_links = len(urls)
268
  processed_files_info = []
 
291
  fail_count += 1
292
  await send_telegram_message(chat_id, f"❌ Lỗi hệ thống khi xử lý link {i+1}: `{e}`")
293
  traceback.print_exc()
294
+ await asyncio.sleep(1)
295
 
296
+ # --- Gửi kết quả ---
297
  await send_telegram_message(chat_id, f"--- Hoàn tất tải {success_count}/{total_links} video gốc ---")
298
  if processed_files_info:
299
  await send_telegram_message(chat_id, f"--- Bắt đầu gửi {len(processed_files_info)} video ---")
 
309
 
310
  if file_to_send and os.path.exists(file_to_send):
311
  await send_telegram_message(chat_id, f" 📤 Đang gửi video {i+1}/{len(processed_files_info)}...")
312
+ # SỬ DỤNG HÀM send_telegram_video (base64)
313
+ if await send_telegram_video(chat_id, file_to_send, caption):
314
+ sent_count += 1
315
+ else:
316
+ send_errors += 1
317
  else: await send_telegram_message(chat_id, f" ⚠️ Không tìm thấy file đã xử lý cho video {i+1} để gửi.")
318
+ await asyncio.sleep(0.5)
319
 
320
+ # --- Tổng kết ---
321
  summary_message = f"🏁 Hoàn thành!\n- Tải thành công: {success_count}/{total_links}"
322
  if fail_count > 0: summary_message += f"\n- Tải thất bại: {fail_count}"
323
  if is_muting and mute_fail_count > 0: summary_message += f"\n- Lỗi tắt tiếng: {mute_fail_count}"
324
  if send_errors > 0: summary_message += f"\n- Lỗi gửi file: {send_errors}"
325
  await send_telegram_message(chat_id, summary_message)
326
 
327
+ # --- Dọn dẹp ---
328
  if chat_id in USER_SESSIONS: del USER_SESSIONS[chat_id]
329
  try:
330
  shutil.rmtree(task_temp_folder)
 
353
  @app.post("/webhook")
354
  async def handle_webhook(update: TelegramUpdate, background_tasks: BackgroundTasks):
355
  if update.callback_query:
 
356
  cq = update.callback_query
357
  chat_id, msg_id, data, cb_id = cq["message"]["chat"]["id"], cq["message"]["message_id"], cq["data"], cq["id"]
358
  session = USER_SESSIONS.get(chat_id)
 
367
  return {"status": "ok, processing started"}
368
 
369
  elif update.message:
 
370
  msg = update.message
371
  chat_id, text = msg["chat"]["id"], msg.get("text", "").strip()
372
 
 
393
  return {"status": "ok, ignored"}
394
 
395
  @app.get("/")
396
+ def read_root(): return {"message": "Downloader Bot is running."}
397
+
398
+ # --- To run locally (optional) ---
399
+ # if __name__ == "__main__":
400
+ # # Make sure to set environment variables locally if you run this
401
+ # # Example: $env:WORKER_URL="your_worker_url"; $env:COOKIE_STRING="your_cookie_content"; python app.py
402
+ # uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
requirements.txt CHANGED
@@ -1,7 +1,8 @@
1
  fastapi
2
  uvicorn[standard]
 
 
 
3
  yt-dlp>=2023.10.13
4
  moviepy==1.0.3
5
- aiodns
6
- numpy
7
- httpx
 
1
  fastapi
2
  uvicorn[standard]
3
+ httpx # <-- Thư viện bị thiếu lần trước
4
+ nest_asyncio # <-- Thư viện từ file tham khảo của bạn
5
+ aiodns # <-- Thư viện DNS fix từ file tham khảo của bạn
6
  yt-dlp>=2023.10.13
7
  moviepy==1.0.3
8
+ numpy