Files
arsam 637f3bc57e Import documentation: CLAUDE.md
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:15:24 +03:30

9.1 KiB
Raw Permalink Blame History

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 via client.download_media with retry logic (3 attempts, 5/15/30s backoff) and live progress updates, then rclone copy to Drive. Cleans up temp files on success.
    • _stream_to_drive (≥ 2 GB) — streams directly through RAM via client.stream_media → rclone rcat stdin, with retry logic and speed tracking. Zero disk usage.
  • handle_url (@app.on_message(filters.text)) — detects URLs in text messages, downloads via rclone 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 to file_name attribute or generates one with appropriate extension.
  • send_links — after upload, queries Drive via rclone lsjson for the file ID, then sends direct-download + wget links.

Auth system

  • AUTHORIZED_USERS set persisted in authorized_users.json. Anyone in this set has unlimited upload access.
  • Quota system in quotas.json schema: {"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 subprocesses
  • psutil — RAM/CPU/disk stats for /status
  • python-dotenv — loads .env

Key constraints

  • All handlers are async def — Pyrogram is fully async.
  • rclone subprocesses use asyncio.create_subprocess_exec — do not replace with subprocess.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.