Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
9.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
A single-file Telegram bot (bot.py) that receives files (and URLs) and uploads them to Google Drive. Uses Pyrogram (MTProto, v2+) for direct Telegram communication and rclone (with gdrive: remote) for Drive uploads. All async, all in one file.
Environment setup
source venv/bin/activate
pip install -r requirements.txt
Required .env variables: BOT_TOKEN, API_ID, API_HASH. The rclone remote (gdrive:) must be authenticated separately via rclone config.
Running the bot
Production: runs as a systemd service (/etc/systemd/system/tg-gdrive-bot.service, enabled on boot, Restart=always replaces the old run_bot.sh watchdog). Manage it with:
systemctl status tg-gdrive-bot # state
systemctl restart tg-gdrive-bot # restart (or use the in-bot /restart)
systemctl stop tg-gdrive-bot # stop (do this before any manual run, or the session conflicts)
journalctl -u tg-gdrive-bot -f # follow logs (also appended to bot.log for the in-bot /logs)
The unit uses the venv interpreter (venv/bin/python -u bot.py) and a PATH that includes /usr/local/bin (for deno). stdout/stderr append to bot.log so the in-bot /logs command keeps working. run_bot.sh is the legacy nohup watchdog — superseded by systemd; don't run both.
Manual run (debugging only — stop the service first):
systemctl stop tg-gdrive-bot
source venv/bin/activate
python bot.py
No real tests exist beyond the stub test_local_api.py.
Bot commands
| Command | Auth required | Description |
|---|---|---|
/start |
No | Upload instructions + system info |
/help |
No | Full command reference |
/list |
Yes | Last 10 files in Drive |
/listall |
Yes | All files (paginated) |
/search <q> |
Yes | Search files by name |
/share <name> |
Yes | Make file public + share link |
/status |
Yes | Uptime, RAM, CPU, disk |
/logs |
Yes | Last 50 log lines |
/adduser @user |
Yes | Grant full access |
/removeuser @user |
Yes | Revoke full access |
/setquota @user <size> |
Yes | Set daily upload quota |
/clearquota @user |
Yes | Remove quota |
/quotas |
Yes | Overview all quotas |
/myquota |
No | Check own quota |
/restart |
Yes | Graceful restart via os.execv |
Architecture
All logic in bot.py (~1368 lines). Key components:
handle_file(@app.on_message(filters.media)) — receives any media type (document, photo, video, audio, voice, sticker, video_note, animation). Routes to one of two upload strategies based on file size:_download_then_upload(< 2 GB) — downloads to temp dir viaclient.download_mediawith retry logic (3 attempts, 5/15/30s backoff) and live progress updates, thenrclone copyto Drive. Cleans up temp files on success._stream_to_drive(≥ 2 GB) — streams directly through RAM viaclient.stream_media→rclone rcatstdin, with retry logic and speed tracking. Zero disk usage.
handle_url(@app.on_message(filters.text)) — detects URLs in text messages, downloads viarclone copyurl(RAM-only, no disk), saves to Drive. Pulse animation during transfer.classify_to_folder— routes files into Drive subfolders by extension/MIME (Videos, Music, Photos, Software, Archives, Models, Docs, etc.).resolve_filename— uses caption as filename override if provided; falls back tofile_nameattribute or generates one with appropriate extension.send_links— after upload, queries Drive viarclone lsjsonfor the file ID, then sends direct-download + wget links.
Auth system
AUTHORIZED_USERSset persisted inauthorized_users.json. Anyone in this set has unlimited upload access.- Quota system in
quotas.jsonschema:{"quotas": {"username": bytes}, "usage": {"username": {"YYYY-MM-DD": bytes}}}. Non-auth users can upload if they have a quota with remaining bytes today. Quotas auto-reset at midnight UTC.
Upload methods
| Size | Method | Disk usage | CLI tool |
|---|---|---|---|
| < 2 GB | Download then upload | Temp file on disk | rclone copy |
| ≥ 2 GB | RAM stream | None | rclone rcat |
| URLs (any size) | RAM stream | None | rclone copyurl |
| Media-site URLs | yt-dlp to temp dir, then upload | Temp file on disk | yt-dlp + rclone copy |
The 2 GB threshold is STREAM_THRESHOLD at line 53.
yt-dlp media downloads
URLs whose domain matches YTDLP_DOMAINS (YouTube, Instagram, TikTok, Twitter/X, Facebook, Reddit, Pinterest, Tumblr, LinkedIn, Aparat) or NSFW_DOMAINS (PornHub, XVideos, xHamster, YouPorn, RedTube, SpankBang) are routed by _process_url to _ytdlp_download instead of rclone copyurl. Files are downloaded with yt-dlp (venv package; ffmpeg merges streams) into a temp dir, then uploaded to gdrive:MEDIA-DLS/<category>/ — category from classify_to_folder (Videos, Music, …), NSFW domains always go to MEDIA-DLS/NSFW. Adding the word mp3 or audio after the link extracts audio (-x --audio-format mp3). Playlists are capped at 50 items; runtime capped at 3 h (livestreams). If cookies.txt exists in the bot dir it is passed via --cookies — required for Instagram and for YouTube (this server's IP is bot-flagged by YouTube). curl_cffi is installed for browser impersonation (needed by e.g. SpankBang). Keep yt-dlp updated (venv/bin/pip install -U yt-dlp) — extractors break when sites change.
Inline file-action buttons
Every send_links message carries an inline keyboard (🌍 Share · ℹ️ Info · 📂 Move · 🗑 Delete with confirm step · 📥 Send to Telegram). Buttons reference an 8-hex-char token stored in FILE_ACTIONS (persisted to file_actions.json, capped at 500 entries, survives restarts). handle_buttons (@app.on_callback_query()) dispatches on the action:token[:dest] callback-data format and runs rclone (link, moveto, deletefile, lsjson). Authorized users can act on any file; quota users only on files they uploaded (owner field).
Torrents (aria2c)
Magnet links (detected in handle_url via MAGNET_RE) and .torrent files (detected in handle_file by extension/mime) route to process_torrent. A minimal bencode decoder (_bdecode / torrent_files) reads the file list without sanitizing names (they must match what aria2 writes to disk and are used verbatim as Drive paths). Magnets first fetch metadata only (_aria2_fetch_metadata). Download strategy by total size: < 8 GB downloads the whole torrent then uploads each file; ≥ 8 GB (TORRENT_STREAM_THRESHOLD) uses per-file mode (--select-file=N, download→upload→delete per file) to keep peak disk low. There is no RAM-stream path — BitTorrent is out-of-order, so torrents always hit disk. Work dir is dl_tmp/ on /dev/sda1 (NOT /tmp, which is RAM-backed tmpfs, only 3.9 GB). Pre-checks: free disk (shutil.disk_usage, needs biggest-file or total + 2 GB headroom) and quota. Files upload to gdrive:Torrents/<name>/… preserving structure. aria2c and the seeding-disabled flags live in ARIA2_COMMON; progress parsed by ARIA2_PROGRESS_RE.
Telegram delivery (Drive → Telegram)
/get <query> and the 📥 button both call deliver_to_telegram: look up the file (get_drive_entry / _find_drive_file), refuse if over TG_SEND_LIMIT (~2 GB Telegram bot cap, offers /share instead), rclone-copy to dl_tmp/ with progress (parsed from --stats-one-line), then send_video/send_audio/send_document by extension with an upload-progress callback. This is the only path that sends files out of Drive.
yt-dlp auto-updater
ytdlp_update_loop (started in _main() alongside app.start()/idle()) runs pip install -U yt-dlp in the venv every 24 h and logs version changes. Subprocesses pick up the new version immediately.
Spotify links (SPOTIFY_DOMAINS) are routed to _spotify_download, which runs spotdl (Spotify metadata → audio downloaded from YouTube Music) and uploads results to MEDIA-DLS/Music. Because the audio comes from YouTube, this also requires cookies.txt on this server. Deno is installed at /root/.config/spotdl/deno (symlinked to /usr/local/bin/deno) — yt-dlp needs it for YouTube JS challenges. Both yt-dlp and spotdl uploads share _upload_media_files.
Key tech stack
- Pyrogram v2+ — async MTProto client (
Client),filters.media/filters.command()handlers - rclone — Drive operations:
copy,rcat,copyurl,lsjson,link asyncio.create_subprocess_exec— all rclone calls are non-blocking subprocessespsutil— RAM/CPU/disk stats for/statuspython-dotenv— loads.env
Key constraints
- All handlers are
async def— Pyrogram is fully async. - rclone subprocesses use
asyncio.create_subprocess_exec— do not replace withsubprocess.run. - Temp files only deleted on successful upload (manual retry on failure).
- Filenames cleaned via
sanitize_filename(NFKC normalization, emoji removal, illegal char stripping). - The bot can also handle files sent by non-auth users if they have a daily quota assigned.