Update main.py
Browse files
main.py
CHANGED
|
@@ -1,15 +1,9 @@
|
|
| 1 |
# Link Generator Bot
|
| 2 |
# Webhook-only | HuggingFace Spaces compatible
|
| 3 |
-
#
|
| 4 |
-
# Flow:
|
| 5 |
-
# User sends any file/photo/video/audio to the bot
|
| 6 |
-
# β file_id extracted from raw webhook update
|
| 7 |
-
# β Downloaded via Pyrogram MTProto (bot.download_media)
|
| 8 |
-
# β Saved to HF Space local storage
|
| 9 |
-
# β Public link returned to user
|
| 10 |
|
| 11 |
import os
|
| 12 |
import sys
|
|
|
|
| 13 |
import logging
|
| 14 |
import asyncio
|
| 15 |
import traceback
|
|
@@ -22,7 +16,6 @@ from fastapi.responses import FileResponse, JSONResponse
|
|
| 22 |
from pyrogram import Client
|
| 23 |
from pyrogram.enums import ParseMode
|
| 24 |
from pyrogram.errors import FloodWait
|
| 25 |
-
from pyrogram.types import Message
|
| 26 |
|
| 27 |
from fileManager import FileManager
|
| 28 |
|
|
@@ -111,6 +104,51 @@ def track_task(coro):
|
|
| 111 |
task.add_done_callback(RUNNING_TASKS.discard)
|
| 112 |
return task
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
# ============================================================
|
| 115 |
# Webhook response helpers
|
| 116 |
# ============================================================
|
|
@@ -153,110 +191,167 @@ def handle_help(chat_id: int) -> dict:
|
|
| 153 |
|
| 154 |
def handle_stats(chat_id: int) -> dict:
|
| 155 |
import shutil, psutil
|
|
|
|
| 156 |
total, used, free = shutil.disk_usage(".")
|
| 157 |
proc = psutil.Process(os.getpid())
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
b /= 1024
|
| 162 |
-
return f"{b:.1f} TB"
|
| 163 |
-
fl_count = len(os.listdir("fl")) if os.path.isdir("fl") else 0
|
| 164 |
return _msg(chat_id,
|
| 165 |
-
"
|
| 166 |
-
|
| 167 |
-
f"**
|
| 168 |
-
f"**
|
| 169 |
-
f"**
|
| 170 |
-
f"**
|
|
|
|
|
|
|
| 171 |
|
| 172 |
# ============================================================
|
| 173 |
-
# Extract file_id + filename from raw webhook message
|
| 174 |
# ============================================================
|
| 175 |
MEDIA_TYPES = [
|
| 176 |
"document", "video", "audio", "voice",
|
| 177 |
"photo", "animation", "sticker", "video_note"
|
| 178 |
]
|
| 179 |
-
|
| 180 |
EXT_MAP = {
|
| 181 |
"video": "mp4", "audio": "mp3", "voice": "ogg",
|
| 182 |
"animation": "gif", "sticker": "webp", "video_note": "mp4"
|
| 183 |
}
|
| 184 |
|
| 185 |
def get_file_info(msg: dict) -> tuple:
|
| 186 |
-
"""Returns (file_id, filename) or (None, None)."""
|
| 187 |
for mtype in MEDIA_TYPES:
|
| 188 |
if mtype not in msg:
|
| 189 |
continue
|
| 190 |
if mtype == "photo":
|
| 191 |
-
# list of sizes β pick the largest
|
| 192 |
photos = msg["photo"]
|
| 193 |
obj = photos[-1] if isinstance(photos, list) else photos
|
| 194 |
-
return obj["file_id"], f"{obj.get('file_unique_id', 'photo')}.jpg"
|
| 195 |
-
|
| 196 |
obj = msg[mtype]
|
| 197 |
fid = obj["file_id"]
|
| 198 |
fname = obj.get("file_name") or obj.get("file_unique_id", mtype)
|
| 199 |
if "." not in fname.split("/")[-1]:
|
| 200 |
fname = f"{fname}.{EXT_MAP.get(mtype, 'bin')}"
|
| 201 |
-
return fid, fname
|
| 202 |
-
|
| 203 |
-
return None, None
|
| 204 |
|
| 205 |
# ============================================================
|
| 206 |
-
# Async media handler
|
| 207 |
# ============================================================
|
| 208 |
-
async def handle_media(chat_id: int, reply_to_id: int,
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
for attempt in range(2):
|
| 217 |
try:
|
| 218 |
-
downloaded = await bot.download_media(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
break
|
| 220 |
except FloodWait as e:
|
| 221 |
wait_s = int(getattr(e, "value", 0) or 0)
|
| 222 |
LOGGER(__name__).warning(f"FloodWait: {wait_s}s")
|
| 223 |
if attempt == 0 and wait_s > 0:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
await asyncio.sleep(wait_s + 1)
|
|
|
|
| 225 |
continue
|
| 226 |
raise
|
| 227 |
|
| 228 |
-
if not downloaded or not os.path.exists(downloaded)
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
try: os.remove(tmp_path)
|
| 231 |
except: pass
|
| 232 |
return
|
| 233 |
|
| 234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
with open(downloaded, "rb") as f:
|
| 236 |
file_data = f.read()
|
| 237 |
os.remove(downloaded)
|
| 238 |
|
| 239 |
-
entry
|
| 240 |
-
|
| 241 |
-
link = f"{SPACE_URL}/download/{entry['id']}"
|
| 242 |
-
file_size = len(file_data)
|
| 243 |
-
size_str = (f"{file_size/1024/1024:.2f} MB"
|
| 244 |
-
if file_size > 1024 * 1024
|
| 245 |
-
else f"{file_size/1024:.1f} KB")
|
| 246 |
|
| 247 |
-
|
| 248 |
-
|
|
|
|
|
|
|
|
|
|
| 249 |
f"π **Link:**\n`{link}`\n\n"
|
| 250 |
f"π **File:** `{entry['filename']}`\n"
|
| 251 |
-
f"π¦ **Size:** `{
|
| 252 |
-
f"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
)
|
| 254 |
-
LOGGER(__name__).info(f"Generated link for {filename}: {link}")
|
| 255 |
|
| 256 |
except Exception as e:
|
| 257 |
LOGGER(__name__).error(traceback.format_exc())
|
| 258 |
try:
|
| 259 |
-
await
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
except Exception:
|
| 261 |
pass
|
| 262 |
|
|
@@ -267,7 +362,8 @@ async def handle_media(chat_id: int, reply_to_id: int, file_id: str, filename: s
|
|
| 267 |
async def lifespan(app: FastAPI):
|
| 268 |
LOGGER(__name__).info("Starting bot clientβ¦")
|
| 269 |
await bot.start()
|
| 270 |
-
|
|
|
|
| 271 |
yield
|
| 272 |
LOGGER(__name__).info("Shutting downβ¦")
|
| 273 |
await bot.stop()
|
|
@@ -281,10 +377,8 @@ app = FastAPI(lifespan=lifespan)
|
|
| 281 |
async def root():
|
| 282 |
return {"status": "ok", "service": "Link Generator Bot"}
|
| 283 |
|
| 284 |
-
|
| 285 |
@app.get("/download/{file_id}")
|
| 286 |
async def download_file(file_id: str):
|
| 287 |
-
"""Serve a stored file by its UUID."""
|
| 288 |
entry = fm.get_entry_by_id(file_id)
|
| 289 |
if not entry:
|
| 290 |
return JSONResponse({"error": "File not found or expired"}, status_code=404)
|
|
@@ -313,7 +407,6 @@ async def telegram_webhook(request: Request):
|
|
| 313 |
text = msg.get("text", "").strip()
|
| 314 |
msg_id = msg["message_id"]
|
| 315 |
|
| 316 |
-
# ββ commands ββββββββββββββββββββββββββββββββββββββββββ
|
| 317 |
if text.startswith("/start"):
|
| 318 |
return JSONResponse(handle_start(chat_id))
|
| 319 |
if text.startswith("/help"):
|
|
@@ -321,13 +414,11 @@ async def telegram_webhook(request: Request):
|
|
| 321 |
if text.startswith("/stats"):
|
| 322 |
return JSONResponse(handle_stats(chat_id))
|
| 323 |
|
| 324 |
-
|
| 325 |
-
file_id, filename = get_file_info(msg)
|
| 326 |
if file_id:
|
| 327 |
-
track_task(handle_media(chat_id, msg_id, file_id, filename))
|
| 328 |
return JSONResponse({"status": "ok"})
|
| 329 |
|
| 330 |
-
# ββ unknown text ββββββββββββββββββββββββββββββββββββββ
|
| 331 |
if text and not text.startswith("/"):
|
| 332 |
return JSONResponse(_msg(chat_id,
|
| 333 |
"π **Please send a file, photo, video or audio.**\n"
|
|
|
|
| 1 |
# Link Generator Bot
|
| 2 |
# Webhook-only | HuggingFace Spaces compatible
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
import os
|
| 5 |
import sys
|
| 6 |
+
import time
|
| 7 |
import logging
|
| 8 |
import asyncio
|
| 9 |
import traceback
|
|
|
|
| 16 |
from pyrogram import Client
|
| 17 |
from pyrogram.enums import ParseMode
|
| 18 |
from pyrogram.errors import FloodWait
|
|
|
|
| 19 |
|
| 20 |
from fileManager import FileManager
|
| 21 |
|
|
|
|
| 104 |
task.add_done_callback(RUNNING_TASKS.discard)
|
| 105 |
return task
|
| 106 |
|
| 107 |
+
# ============================================================
|
| 108 |
+
# UX helpers
|
| 109 |
+
# ============================================================
|
| 110 |
+
def fmt_size(b: float) -> str:
|
| 111 |
+
for u in ["B", "KB", "MB", "GB"]:
|
| 112 |
+
if b < 1024:
|
| 113 |
+
return f"{b:.1f} {u}"
|
| 114 |
+
b /= 1024
|
| 115 |
+
return f"{b:.1f} TB"
|
| 116 |
+
|
| 117 |
+
def fmt_time(seconds: float) -> str:
|
| 118 |
+
seconds = max(0, int(seconds))
|
| 119 |
+
if seconds < 60:
|
| 120 |
+
return f"{seconds}s"
|
| 121 |
+
m, s = divmod(seconds, 60)
|
| 122 |
+
if m < 60:
|
| 123 |
+
return f"{m}m {s}s"
|
| 124 |
+
h, m = divmod(m, 60)
|
| 125 |
+
return f"{h}h {m}m"
|
| 126 |
+
|
| 127 |
+
def progress_bar(current: int, total: int, length: int = 10) -> str:
|
| 128 |
+
if total <= 0:
|
| 129 |
+
return "β" * length
|
| 130 |
+
filled = int(length * current / total)
|
| 131 |
+
empty = length - filled
|
| 132 |
+
return "β" * filled + "β" * empty
|
| 133 |
+
|
| 134 |
+
def build_progress_text(phase: str, current: int, total: int,
|
| 135 |
+
speed: float, elapsed: float, filename: str) -> str:
|
| 136 |
+
pct = min(100, int(current * 100 / total)) if total > 0 else 0
|
| 137 |
+
bar = progress_bar(current, total)
|
| 138 |
+
eta = fmt_time((total - current) / speed) if speed > 0 else "β"
|
| 139 |
+
icons = {"download": "π₯", "upload": "π€"}
|
| 140 |
+
icon = icons.get(phase, "βοΈ")
|
| 141 |
+
label = "Downloading" if phase == "download" else "Saving"
|
| 142 |
+
|
| 143 |
+
return (
|
| 144 |
+
f"{icon} **{label}:** `{filename}`\n\n"
|
| 145 |
+
f"`{bar}` **{pct}%**\n\n"
|
| 146 |
+
f"**Done:** `{fmt_size(current)}` / `{fmt_size(total)}`\n"
|
| 147 |
+
f"**Speed:** `{fmt_size(speed)}/s`\n"
|
| 148 |
+
f"**ETA:** `{eta}`\n"
|
| 149 |
+
f"**Elapsed:** `{fmt_time(elapsed)}`"
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
# ============================================================
|
| 153 |
# Webhook response helpers
|
| 154 |
# ============================================================
|
|
|
|
| 191 |
|
| 192 |
def handle_stats(chat_id: int) -> dict:
|
| 193 |
import shutil, psutil
|
| 194 |
+
from time import time as _time
|
| 195 |
total, used, free = shutil.disk_usage(".")
|
| 196 |
proc = psutil.Process(os.getpid())
|
| 197 |
+
fl_count = len([f for f in os.listdir("fl")
|
| 198 |
+
if not f.startswith("tmp_")]) if os.path.isdir("fl") else 0
|
| 199 |
+
active = len([t for t in RUNNING_TASKS if not t.done()])
|
|
|
|
|
|
|
|
|
|
| 200 |
return _msg(chat_id,
|
| 201 |
+
"π **Bot Statistics**\n"
|
| 202 |
+
"βββββββββββββββββββ\n"
|
| 203 |
+
f"πΎ **Disk Used:** `{fmt_size(used)}` / `{fmt_size(total)}`\n"
|
| 204 |
+
f"π **Disk Free:** `{fmt_size(free)}`\n"
|
| 205 |
+
f"π§ **Memory:** `{round(proc.memory_info()[0]/1024**2)} MiB`\n"
|
| 206 |
+
f"β‘ **CPU:** `{psutil.cpu_percent(interval=0.2)}%`\n"
|
| 207 |
+
f"π **Stored Files:** `{fl_count}`\n"
|
| 208 |
+
f"βοΈ **Active Jobs:** `{active}`")
|
| 209 |
|
| 210 |
# ============================================================
|
| 211 |
+
# Extract file_id + filename + size from raw webhook message
|
| 212 |
# ============================================================
|
| 213 |
MEDIA_TYPES = [
|
| 214 |
"document", "video", "audio", "voice",
|
| 215 |
"photo", "animation", "sticker", "video_note"
|
| 216 |
]
|
|
|
|
| 217 |
EXT_MAP = {
|
| 218 |
"video": "mp4", "audio": "mp3", "voice": "ogg",
|
| 219 |
"animation": "gif", "sticker": "webp", "video_note": "mp4"
|
| 220 |
}
|
| 221 |
|
| 222 |
def get_file_info(msg: dict) -> tuple:
|
| 223 |
+
"""Returns (file_id, filename, file_size) or (None, None, 0)."""
|
| 224 |
for mtype in MEDIA_TYPES:
|
| 225 |
if mtype not in msg:
|
| 226 |
continue
|
| 227 |
if mtype == "photo":
|
|
|
|
| 228 |
photos = msg["photo"]
|
| 229 |
obj = photos[-1] if isinstance(photos, list) else photos
|
| 230 |
+
return obj["file_id"], f"{obj.get('file_unique_id', 'photo')}.jpg", obj.get("file_size", 0)
|
|
|
|
| 231 |
obj = msg[mtype]
|
| 232 |
fid = obj["file_id"]
|
| 233 |
fname = obj.get("file_name") or obj.get("file_unique_id", mtype)
|
| 234 |
if "." not in fname.split("/")[-1]:
|
| 235 |
fname = f"{fname}.{EXT_MAP.get(mtype, 'bin')}"
|
| 236 |
+
return fid, fname, obj.get("file_size", 0)
|
| 237 |
+
return None, None, 0
|
|
|
|
| 238 |
|
| 239 |
# ============================================================
|
| 240 |
+
# Async media handler with live progress
|
| 241 |
# ============================================================
|
| 242 |
+
async def handle_media(chat_id: int, reply_to_id: int,
|
| 243 |
+
file_id: str, filename: str, file_size: int):
|
| 244 |
+
|
| 245 |
+
short_name = filename if len(filename) <= 30 else filename[:27] + "..."
|
| 246 |
+
|
| 247 |
+
# ββ Phase 0: initial status ββββββββββββββββββββββββββββββ
|
| 248 |
+
status_msg = await bot.send_message(
|
| 249 |
+
chat_id,
|
| 250 |
+
f"π **Preparing to process your fileβ¦**\n\n"
|
| 251 |
+
f"π `{short_name}`\n"
|
| 252 |
+
f"π¦ Size: `{fmt_size(file_size) if file_size else 'unknown'}`",
|
| 253 |
+
reply_to_message_id=reply_to_id,
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
dl_start = time.time()
|
| 257 |
+
last_edit = [0.0] # mutable for closure
|
| 258 |
+
tmp_path = f"fl/tmp_{reply_to_id}_{filename}"
|
| 259 |
|
| 260 |
+
# ββ Progress callback for Pyrogram download ββββββββββββββ
|
| 261 |
+
async def on_progress(current: int, total: int):
|
| 262 |
+
now = time.time()
|
| 263 |
+
if now - last_edit[0] < 2.5: # throttle edits to every 2.5s
|
| 264 |
+
return
|
| 265 |
+
last_edit[0] = now
|
| 266 |
+
elapsed = now - dl_start
|
| 267 |
+
speed = current / elapsed if elapsed > 0 else 0
|
| 268 |
+
try:
|
| 269 |
+
await status_msg.edit(
|
| 270 |
+
build_progress_text("download", current, total,
|
| 271 |
+
speed, elapsed, short_name)
|
| 272 |
+
)
|
| 273 |
+
except Exception:
|
| 274 |
+
pass
|
| 275 |
+
|
| 276 |
+
try:
|
| 277 |
+
# ββ Phase 1: Download ββββββββββββββββββββββββββββββββ
|
| 278 |
+
downloaded = None
|
| 279 |
for attempt in range(2):
|
| 280 |
try:
|
| 281 |
+
downloaded = await bot.download_media(
|
| 282 |
+
file_id,
|
| 283 |
+
file_name=tmp_path,
|
| 284 |
+
progress=on_progress,
|
| 285 |
+
)
|
| 286 |
break
|
| 287 |
except FloodWait as e:
|
| 288 |
wait_s = int(getattr(e, "value", 0) or 0)
|
| 289 |
LOGGER(__name__).warning(f"FloodWait: {wait_s}s")
|
| 290 |
if attempt == 0 and wait_s > 0:
|
| 291 |
+
await status_msg.edit(
|
| 292 |
+
f"β³ **FloodWait β retrying in {wait_s}sβ¦**\n\n"
|
| 293 |
+
f"π `{short_name}`"
|
| 294 |
+
)
|
| 295 |
await asyncio.sleep(wait_s + 1)
|
| 296 |
+
dl_start = time.time()
|
| 297 |
continue
|
| 298 |
raise
|
| 299 |
|
| 300 |
+
if not downloaded or not os.path.exists(downloaded) \
|
| 301 |
+
or os.path.getsize(downloaded) == 0:
|
| 302 |
+
await status_msg.edit(
|
| 303 |
+
"β **Download Failed**\n\n"
|
| 304 |
+
"Could not retrieve the file from Telegram.\n"
|
| 305 |
+
"Please try again."
|
| 306 |
+
)
|
| 307 |
try: os.remove(tmp_path)
|
| 308 |
except: pass
|
| 309 |
return
|
| 310 |
|
| 311 |
+
actual_size = os.path.getsize(downloaded)
|
| 312 |
+
dl_elapsed = time.time() - dl_start
|
| 313 |
+
dl_speed = actual_size / dl_elapsed if dl_elapsed > 0 else 0
|
| 314 |
+
|
| 315 |
+
# ββ Phase 2: Saving ββββββββββββββββββββββββββββββββββ
|
| 316 |
+
await status_msg.edit(
|
| 317 |
+
f"π€ **Saving to serverβ¦**\n\n"
|
| 318 |
+
f"π `{short_name}`\n"
|
| 319 |
+
f"π¦ `{fmt_size(actual_size)}`\n\n"
|
| 320 |
+
f"β¬οΈ Downloaded in `{fmt_time(dl_elapsed)}` "
|
| 321 |
+
f"at `{fmt_size(dl_speed)}/s`"
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
with open(downloaded, "rb") as f:
|
| 325 |
file_data = f.read()
|
| 326 |
os.remove(downloaded)
|
| 327 |
|
| 328 |
+
entry = fm.save_file(file_data, filename)
|
| 329 |
+
link = f"{SPACE_URL}/download/{entry['id']}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
|
| 331 |
+
# ββ Phase 3: Done ββββββββββββββββββββββββββββββββββββ
|
| 332 |
+
total_elapsed = time.time() - dl_start
|
| 333 |
+
await status_msg.edit(
|
| 334 |
+
f"β
**Link Generated Successfully!**\n"
|
| 335 |
+
f"βββββββββββββββββββ\n"
|
| 336 |
f"π **Link:**\n`{link}`\n\n"
|
| 337 |
f"π **File:** `{entry['filename']}`\n"
|
| 338 |
+
f"π¦ **Size:** `{fmt_size(actual_size)}`\n"
|
| 339 |
+
f"β‘ **Speed:** `{fmt_size(dl_speed)}/s`\n"
|
| 340 |
+
f"β± **Time:** `{fmt_time(total_elapsed)}`\n"
|
| 341 |
+
f"βββββββββββββββββββ\n"
|
| 342 |
+
f"_Tap the link to copy β’ Accessible by anyone_",
|
| 343 |
+
disable_web_page_preview=True,
|
| 344 |
)
|
| 345 |
+
LOGGER(__name__).info(f"Generated link for {filename} ({fmt_size(actual_size)}): {link}")
|
| 346 |
|
| 347 |
except Exception as e:
|
| 348 |
LOGGER(__name__).error(traceback.format_exc())
|
| 349 |
try:
|
| 350 |
+
await status_msg.edit(
|
| 351 |
+
f"β **Something went wrong**\n\n"
|
| 352 |
+
f"`{str(e)[:200]}`\n\n"
|
| 353 |
+
f"Please try again or contact support."
|
| 354 |
+
)
|
| 355 |
except Exception:
|
| 356 |
pass
|
| 357 |
|
|
|
|
| 362 |
async def lifespan(app: FastAPI):
|
| 363 |
LOGGER(__name__).info("Starting bot clientβ¦")
|
| 364 |
await bot.start()
|
| 365 |
+
me = await bot.get_me()
|
| 366 |
+
LOGGER(__name__).info(f"Bot ready: @{me.username} β waiting for webhook updates.")
|
| 367 |
yield
|
| 368 |
LOGGER(__name__).info("Shutting downβ¦")
|
| 369 |
await bot.stop()
|
|
|
|
| 377 |
async def root():
|
| 378 |
return {"status": "ok", "service": "Link Generator Bot"}
|
| 379 |
|
|
|
|
| 380 |
@app.get("/download/{file_id}")
|
| 381 |
async def download_file(file_id: str):
|
|
|
|
| 382 |
entry = fm.get_entry_by_id(file_id)
|
| 383 |
if not entry:
|
| 384 |
return JSONResponse({"error": "File not found or expired"}, status_code=404)
|
|
|
|
| 407 |
text = msg.get("text", "").strip()
|
| 408 |
msg_id = msg["message_id"]
|
| 409 |
|
|
|
|
| 410 |
if text.startswith("/start"):
|
| 411 |
return JSONResponse(handle_start(chat_id))
|
| 412 |
if text.startswith("/help"):
|
|
|
|
| 414 |
if text.startswith("/stats"):
|
| 415 |
return JSONResponse(handle_stats(chat_id))
|
| 416 |
|
| 417 |
+
file_id, filename, file_size = get_file_info(msg)
|
|
|
|
| 418 |
if file_id:
|
| 419 |
+
track_task(handle_media(chat_id, msg_id, file_id, filename, file_size))
|
| 420 |
return JSONResponse({"status": "ok"})
|
| 421 |
|
|
|
|
| 422 |
if text and not text.startswith("/"):
|
| 423 |
return JSONResponse(_msg(chat_id,
|
| 424 |
"π **Please send a file, photo, video or audio.**\n"
|