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

125 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
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:
```bash
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):
```bash
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.