Spaces:
Runtime error
Runtime error
Upload 2 files
Browse files- app.py +73 -72
- requirements.txt +4 -3
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
# app.py (
|
| 2 |
-
|
| 3 |
-
|
| 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 (
|
| 31 |
import socket
|
| 32 |
-
import aiodns
|
| 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 |
-
|
| 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:
|
| 47 |
print(f"WARNING: aiodns query for {host} returned empty list. Falling back.")
|
| 48 |
-
raise aiodns.error.DNSError()
|
| 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 |
-
|
| 63 |
-
|
| 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 |
-
# ===
|
| 78 |
|
| 79 |
# --- CONFIGURATION ---
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
os.
|
| 83 |
-
|
| 84 |
-
|
| 85 |
USER_SESSIONS = {}
|
| 86 |
|
| 87 |
-
# --- TELEGRAM UTILS ---
|
| 88 |
-
#
|
|
|
|
| 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)}`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"""
|
| 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 |
-
"""
|
| 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 |
-
"""
|
| 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)
|
| 301 |
|
| 302 |
-
# ---
|
| 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 |
-
|
| 319 |
-
|
|
|
|
|
|
|
|
|
|
| 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)
|
| 322 |
|
| 323 |
-
# ---
|
| 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 |
-
# ---
|
| 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 vì 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 và 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 |
-
|
| 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
|
|
|
|
|
|