Skip to content

Repository files navigation

File 2 Link logo

📡 File 2 Link ™

Send a file → get an instant streaming + download link.

Python Pyrogram aiohttp MongoDB Docker


Deploy To Various Platforms :)


Deploy to Heroku Deploy to Koyeb Deploy to Render


✨ Features

  • ⚡ Instant direct-download links for any file
  • 🎬 In-browser video/audio streaming page — full HTTP Range support, resumable, cinema-style UI, native <video controls> (no bundled player library)
  • 🎧 Audio track switching (multi-track AAC/AC3/Opus files) via native browser audioTracks API — honest limits noted in-app (Firefox/Safari can't expose it at all; DTS/TrueHD can't decode in any browser)
  • 💬 Subtitles — load external .srt/.vtt client-side, toggle on/off
  • ℹ️ VLC-style media info panel — resolution, duration, track count, playback rate
  • 🗑️ Revoke button on every generated link — uploader (or admin) can delete the file from LOG_CHANNEL on demand, instantly killing both the stream and download link. Two-step confirm so a stray tap can't nuke a file.
  • 🔒 Force-subscribe gate (optional)
  • 🔗 Shortlink support — is.gd or Shortzy-based (gplinks, mdisk, etc.)
  • 🚀 Multi-client mode for higher throughput with extra bot tokens (MULTI_TOKEN1..N)
  • ⏩ Per-stream parallel chunk prefetch (CONCURRENT_FETCHES) — faster single-stream speed, not just more concurrent viewers
  • ⚖️ Atomic load-balancer reservation — client pick + slot reservation happen with no await gap between them, so a burst of parallel connections (download accelerators like FDM open several at once) can't all pile onto the same client
  • 🩹 Auto-fallback if the load-balanced client can't read LOG_CHANNEL (e.g. missing admin rights) — retries with the main client instead of a random "file not found"
  • ♻️ Auto-retry on transient Telegram RPC errors (-503 Timeout etc.) and FloodWait, instead of killing the stream
  • 🧹 Graceful shutdown on SIGTERM — stops all clients + in-flight streams cleanly within the platform's grace window (fixes Heroku R12 / forced SIGKILL); the 12h auto-restart routes through this same path instead of a dirty execv
  • 🗄️ Compiled-template caching — HTML pages are parsed once per process, not re-read/recompiled on every page view
  • 🛠️ Admin tools — /stats, /broadcast, /restart
  • 💓 Self-ping keepalive for free-tier hosts (Heroku/Koyeb/Render)

🧱 Stack

Runtime Python 3.11
Telegram Pyrogram fork: pyrofork + TgCrypto-pyrofork
Server aiohttp (web server + chunked streaming)
Database MongoDB (motor)
Deploy Docker → Koyeb / Heroku / Render

🚀 Setup

1. Install deps

pip install -r requirements.txt

2. Set environment variables

Required vars hard-fail with a clear error message if missing.

Var Required Description
API_ID from my.telegram.org — shared across ALL bot tokens, including MULTI_TOKEN*
API_HASH from my.telegram.org — same, shared
BOT_TOKEN main bot, from @BotFather
LOG_CHANNEL private channel used as file storage backend — every bot token (main + multi) must be admin here
DATABASE_URI MongoDB connection string
DATABASE_NAME MongoDB db name
URL public base URL of this deployment. Any extra path accidentally pasted in (e.g. a health-check URL) is auto-stripped down to scheme+host — but set it clean anyway: https://yourapp.koyeb.app/
ADMINS space-separated admin user IDs — also who can /broadcast, /restart, and revoke anyone's file link
FSUB_CHANNEL force-subscribe channel id (0 = disabled)
PORT default 8080
SLEEP_THRESHOLD default 60
PING_INTERVAL self-ping interval in seconds, default 1200
SHORTLINK True/False, use Shortzy-based shortener
SHORTLINK_URL / SHORTLINK_API required if SHORTLINK=True
ISGD True/False, use is.gd shortener (no key needed)
MULTI_TOKEN1, MULTI_TOKEN2, ... extra bot tokens for multi-client load balancing — tokens can come from separate Telegram accounts (BotFather caps 20 bots/account), just add each new bot as admin in LOG_CHANNEL

3. Run

python bot.py

Or with Docker:

docker build -t file2link .
docker run -p 8080:8080 --env-file .env file2link

💬 Bot usage

Who Action
Anyone Send any file in DM → get Stream + Download + Revoke buttons back
Anyone /start, /how
Admins (ADMINS) /stats, /broadcast (reply to a message), /restart
Uploader or admin Tap 🗑 Revoke Link on a generated message → confirm → file deleted from LOG_CHANNEL, links dead instantly

⚙️ How it works

User sends file
      │
      ▼
Bot forwards file → LOG_CHANNEL
      │
      ▼
Bot replies with:
  • Stream link   → /watch/{id}/{filename}?hash=X
  • Download link → /{id}/{filename}?hash=X
  • 🗑 Revoke Link button
      │
      ▼
Page resolves id+hash → serves req.html (player) or dl.html (download),
templates compiled once and cached — not re-parsed per request
      │
      ▼
aiohttp pulls file from Telegram on-demand,
1MB chunks, several fetched in parallel per stream (CONCURRENT_FETCHES),
client pick + load-balancer reservation done atomically (no await gap),
per-DC locked sessions, Range-aware → resumable,
auto-retries on FloodWait / transient Telegram errors

📊 Real-world sizing (not theoretical)

  • Each MULTI_TOKEN client ≈ comfortably serves 3-5 concurrent full-speed streams before Telegram FloodWaits it. Rule of thumb: tokens_needed ≈ peak_concurrent_viewers / 4.
  • Each live client ≈ 25-40MB RAM. On a 512MB dyno, 7-8 clients is the realistic ceiling — confirmed in production: 13 clients hit Heroku R14 (memory quota exceeded) before a single viewer connected, 21 clients would be worse. Going past 7-8 needs more RAM (1GB+), not more tuning.
  • CONCURRENT_FETCHES (per-stream parallel chunk fetch) trades speed for FloodWait risk — bumping this too high compounds fast under real concurrent load. Test any change against real traffic before trusting it; don't just guess upward.
  • Bump MULTI_TOKEN count if you're seeing FloodWait storms in logs; bump dyno RAM if you're seeing R14 — they're different problems with different fixes.
  • Live per-client load is visible anytime at the root URL — GET / returns {"status":"alive","total_clients":N,"clients":[{"client_id":0,"active_streams":2},...]}. Same endpoint your self-ping already hits, zero extra cost to check.

📁 Project layout

bot.py                       entrypoint, plugin loader, auto-restart (routes through graceful SIGTERM path), shutdown
info.py                      env var config, auto-sanitizes a misconfigured URL
Script.py                    bot text templates
utils.py                     shortlink helper, temp state
database/                    MongoDB user store
lib/bot/__init__.py          main bot client (workers=16)
lib/bot/clients.py           multi-client manager — start + graceful stop_clients()
lib/util/custom_dl.py        chunked file streaming from Telegram, retry logic, parallel prefetch, load-balancer fallback
lib/util/render_template.py  renders download/stream HTML pages, caches compiled templates
lib/template/                req.html (player page), dl.html (download page)
lib/server/exceptions.py     InvalidHash / FIleNotFound
plugins/route.py             /watch and raw byte-stream routes, atomic client reservation, live status JSON on /
plugins/start.py             file handling, link generation, revoke button + callbacks
plugins/                     other command handlers (broadcast, stats, etc.)

📝 Notes

  • LOG_CHANNEL must be a channel/group every bot token (main + all MULTI_TOKEN*) is admin in — it's permanent file storage, not just a log. If one client loses access, generate_file_properties() now auto-retries with the main client instead of a random-looking "file not found" for whichever viewer got load-balanced to that client.
  • Revoking a file is permanent and immediate — it deletes the message from LOG_CHANNEL itself, not just the link. There's no undo.
  • DTS/DTS-HD/TrueHD audio tracks won't play in-browser regardless of what's in the file — no browser ships a decoder for them, that's a licensing wall, not a bug.
  • workers=16 on the main bot client sizes update-dispatch concurrency (commands/messages through handlers) — it has no effect on streaming speed, that path never touches it.

🩹 Changelog

Fix Detail
Heroku R12 (Exit timeout / SIGKILL) Graceful shutdown on SIGTERM: stops aiohttp server, cancels in-flight stream tasks, stops every Pyrogram client within a bounded 20s window
Auto-restart bypassed shutdown entirely os.execv() replaced the process image directly, skipping every cleanup step above on every 12h restart. Now sends itself SIGTERM and lets the same graceful path handle it
Load balancer pile-up under download accelerators Client pick + reservation had an await gap between them — parallel connections (FDM, IDM) could all read stale load data and land on the same client. Now atomic, no gap
Silent permanent load-balancer drift An exception during session setup (before the main try/finally) skipped the load counter's release, permanently inflating it. Fixed with an explicit release-on-failure path
Random "file not found" under multi-client A client without real LOG_CHANNEL access returns nothing even though the file exists — now falls back to the main client before giving up
Stream died instantly on [-503 Timeout] / transient Telegram RPC errors Now retried with backoff (7 attempts), same as FloodWait
dict(clients) crash on any failed multi-client token Failed tokens are skipped instead of crashing the whole multi-client pool
GeneratorExit / "coroutine ignored" spam on client disconnect Prefetch producer stops cooperatively instead of being hard-cancelled mid-fetch; strong-ref set prevents early GC of pending tasks
Slow single-stream speed Chunks fetched several-at-a-time per stream (CONCURRENT_FETCHES) instead of strictly sequential
Downloaded filename sometimes corrupted (+ vs space) URL path now uses quote() not quote_plus(); download= attribute also forces the exact real filename client-side
requirements.txt had a typo'd package name wzgram[fast] (doesn't exist on PyPI) → pyrofork[fast]
Page re-parsed/recompiled the Jinja template on every single view Compiled once per process, cached
Bundled a full video-player library duplicating a toolbar already built manually Removed; native <video controls> + manual keyboard shortcuts (Space/F/M/arrows) replace it
workers=50 on the main client Trimmed to 16 — that setting only sizes command-handler concurrency, unrelated to streaming, and was wasting RAM for no real benefit at this bot's scale
MULTI_CLIENT flag never reached the route handler Route now checks live client count directly
info.py crashed with a bare ValueError if required vars missing Now exits with a clear error message
Every plugin handler ran twice (double plugin load) Removed auto-load, kept only the manual loader

👤 Maintainer

Goutham@GouthamSER

About

A Superfast File 2 link repo Tg files converts to url to downlaod faster

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages