Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tgdl — Telegram restricted-video downloader (macOS)

CI license platform python runs with uv deps

Two single-file uv scripts + a bash CLI. No virtualenv, no pip install, credentials in the macOS Keychain.

Download videos from Telegram groups/channels you belong to, including chats that have "restrict saving content" enabled, straight from a Mac terminal.

The save restriction is enforced only by the official client's UI. Over the MTProto API, an authorized user session still receives the media, so a normal download works — this tool is essentially yt-dlp for the Telegram groups you are already a member of.

Responsible use. Only download content you are allowed to keep. Respect copyright and each group's rules. You are responsible for how you use this.


What's in here

File Type Role
tgdl bash CLI Front-end you move to your bin. Locates + runs the uv scripts.
tg_setup.py uv script One-time login. Stores credentials in the macOS Keychain.
tg_download.py uv script Resolves links and downloads video media with a progress bar.
downloads/ dir Unified output directory (gitignored).
SECURITY.md doc Threat model and credential-handling details.

Both Python files are PEP 723 uv scripts: dependencies are declared inline, so uv run builds an ephemeral environment on the fly. You never create or activate a virtualenv.


Requirements

  • macOS (uses the login Keychain for secret storage).
  • uvcurl -LsSf https://astral.sh/uv/install.sh | sh
  • A Telegram account that is a member of the group(s) you want to pull from.
  • Your own api_id + api_hash from https://my.telegram.orgAPI development tools. These identify your app, not the group; they're free.

Security model (short version)

  • Secrets live in the macOS Keychain (service tgdl), never in a plaintext file or in git. The Keychain encrypts them at rest and gates access to your login.
  • The stored session string is equivalent to full access to your Telegram account — treat it like a password. Anyone who steals it can act as you.
  • .gitignore also blocks *.session, .env, and downloads/ as a safety net.
  • Revoke access any time with tgdl logout (revokes the session on Telegram's servers, then clears the Keychain). Full details in SECURITY.md.

Install & first-time setup

# 1. From inside this repo, make the entry points executable.
chmod +x tgdl tg_setup.py tg_download.py

# 2. Log in ONCE while the CLI is still co-located with the scripts.
#    This both authenticates AND records the repo path to
#    ~/.config/tgdl/home, so the CLI keeps working after you move it.
./tgdl setup
#   -> enter api_id, api_hash (hidden), phone
#   -> enter the login code Telegram sends you (and 2FA password if enabled)

# 3. Confirm it works.
./tgdl check          # prints "Connected as <you>"

# 4. Move the CLI onto your PATH (you do this yourself).
mv tgdl ~/bin/        # or wherever your unified bin lives

If you prefer not to rely on the auto-recorded path, point at the repo explicitly:

export TGDL_HOME="/Users/you/Downloads/pros/codebase/demos/tele-vid-download"

Add that line to ~/.zshrc to make it permanent.


Workflow

Get a message link in the Telegram desktop app: right-click the video → Copy Message Link. Private-group links look like https://t.me/c/1234567890/42; public ones like https://t.me/name/42.

# Single video
tgdl get https://t.me/c/1234567890/42

# Several at once
tgdl get https://t.me/c/1234567890/42 https://t.me/c/1234567890/57

# A video posted in a channel's comments (note the ?comment=NNN)
tgdl get "https://t.me/somechannel/352?comment=955"

# A whole range of message ids from one chat (inclusive)
tgdl get --chat https://t.me/c/1234567890 --range 100 180

# Send output somewhere else for one run
tgdl get https://t.me/c/1234567890/42 --out ~/Movies/tg

# Not sure of the chat id? List your dialogs:
tgdl list                 # prints "id<TAB>name" for every chat

# Grab non-video media too (photos, docs)
tgdl get https://t.me/c/1234567890/42 --all

# Preview a range without downloading (shows names + sizes)
tgdl get --chat https://t.me/c/1234567890 --range 100 180 --dry-run

Files are written to downloads/ by default, named <chatid>_<msgid>_<orig> so runs never collide. Re-running skips files already fully downloaded. Downloads default to videos only; pass --all for any media, or --dry-run to see what would be fetched first.

Interrupted downloads resume. A download in progress is written to a <name>.part file and only renamed to its final name once complete, so a partial file never looks finished. If it's interrupted (Ctrl-C, dropped connection, FloodWait), just re-run the same command — it continues from where the .part left off instead of starting over. .part files are gitignored. Transient network errors are retried automatically (up to 5 times, with backoff), each retry resuming from the .part.

Override the output directory globally with TGDL_OUT:

export TGDL_OUT="$HOME/Movies/telegram"

Testing

The pure logic (link parsing, filename sanitizing, video detection, sizing) is covered by unit tests that need no account; everything account-bound is verified by hand. Tiers, cheapest first:

1. Unit tests + static checks (no account needed) — this is what CI runs:

python3 -m py_compile tg_setup.py tg_download.py   # syntax
bash -n tgdl                                        # bash parse
uvx ruff check tg_download.py tg_setup.py           # lint
uv run --with pytest --with telethon --with keyring --with tqdm pytest -q tests/

Every push and PR runs the same steps on Python 3.10 and 3.12 via GitHub Actions.

2. Dependency resolution (downloads packages, no login):

uv run --script tg_download.py --help              # builds env, prints usage

3. Connectivity smoke test (needs tgdl setup first):

tgdl check        # -> "Connected as <you> id=..."  proves session is valid
tgdl list         # -> lists your chats; proves entity access works

4. End-to-end (one real, small video):

tgdl get https://t.me/c/<id>/<msg>
ls -lh downloads/
# Verify the file plays and its size matches the message.

Pick a small clip from a save-restricted group for step 4 — that is the exact capability being validated. See CONTRIBUTING.md to add tests or report an unrecognized link format.


Debugging

Symptom Cause / fix
No stored credentials. Run: tgdl setup Keychain is empty. Run tgdl setup.
Session invalid or expired. Run: tgdl setup Session was revoked/logged out. Re-run setup.
tgdl: uv is not installed Install uv, or ensure it's on PATH.
tgdl: cannot locate the scripts You moved tgdl off the repo without recording home. export TGDL_HOME=/path/to/repo.
Unrecognized Telegram message link Use Copy Message Link (a t.me/... URL), not "Copy Link".
Comment link seems ignored / no such file Quote URLs containing ?comment= — zsh treats ? and & as globbing/job chars: tgdl get "https://t.me/ch/352?comment=955".
channel has no linked discussion group The ?comment= link points at a channel without comments enabled; re-copy the actual message link.
Cannot find any entity corresponding to "PeerChannel..." Fresh session hasn't cached the chat. Run tgdl list once, then retry.
[wait] rate limited ... sleeping Ns Telegram FloodWait. The tool auto-sleeps and resumes; just let it run.
[retry N/5] ...; resuming in Ns Transient network error. Auto-retried with backoff, resuming from the .part.
Keychain prompt "tgdl wants to use ..." Expected on first access after a reboot. Click Always Allow to stop repeats.
Slow downloads cryptg should be installed automatically (it accelerates MTProto AES). Confirm it appears in uv's resolution when running the script.

Verbose logging for deeper issues — no need to edit anything:

tgdl get <link> -v     # info-level
tgdl get <link> -vv    # debug-level (full Telethon trace)

Log out or fully reset:

tgdl logout           # revoke session server-side + drop the local session
                      #   (keeps api_id/api_hash, so re-login is just phone + code)
tgdl reset            # wipe ALL local creds incl. api_id/api_hash; no server-side revoke

Prefer tgdl logout if a session may have leaked — it invalidates the string everywhere, and logging back in only needs tgdl setup → reuse api creds → phone + code. Use tgdl reset only to remove the api credentials too (e.g. full uninstall) or when the server is unreachable.


How it works

  1. tg_setup.py runs an interactive MTProto login and saves a Telethon StringSession (plus api_id/api_hash) into the Keychain.
  2. tg_download.py rebuilds the client from that session, parses the t.me link into (chat, message-id), fetches the message, and calls download_media().
  3. Because the request comes from an authorized user session, the server sends the file regardless of the chat's noforwards ("restrict saving") flag.

The tgdl wrapper only handles command dispatch and locating the scripts, so the security-sensitive logic stays in the auditable Python files.


Alternatives & how this differs

There are more capable projects if you need bulk/bot/cross-platform workflows:

Project Lang Scope
iyear/tdl Go Huge toolkit: download/upload, forward, export, restricted content. Config files.
Dineshkarthik/telegram_media_downloader Python YAML-configured bulk media download by type, with resume.
David256/rcdtool Python Closest analog: Telethon, downloads restricted content from a link via .ini/.env.
victorjalonzo/Telegram-Restricted-Content-Downloader Python Pyrogram, restricted content from private groups you belong to.

tgdl deliberately trades features for zero-setup and safer secrets:

  • Credentials in the macOS Keychain, not a plaintext .env/.ini/.yaml or a .session file on disk — the differentiator versus the tools above.
  • No environment to manage. PEP 723 uv scripts install their own deps on first run; no requirements.txt, no venv, no Docker.
  • Small and auditable. Two short Python files + a bash dispatcher, focused on "give me the video at this link (or id range)".

Trade-offs: macOS-only (Keychain), no bot mode, no type-filtered bulk scraping, single-stream downloads. Reach for tdl or telegram_media_downloader when you need those.

Uninstall

tgdl logout                             # revoke session server-side
tgdl reset                              # remove the remaining api_id/api_hash
rm ~/bin/tgdl                           # remove the CLI you moved
rm -f ~/.config/tgdl/home               # remove the recorded path
# delete this repo directory when done

About

Download videos from save-restricted Telegram groups you belong to — zero-setup uv scripts, credentials in the macOS Keychain.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages