Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.git/
.github/
.venv/
__pycache__/
**/__pycache__/
*.py[cod]
*$py.class
*.so
*.egg-info/
*.egg
build/
dist/
downloads/
eggs/
.eggs/
config.env
.env
*.session
*.session-journal
*.session-shm
*.session-wal
tests/
*.md
LICENSE
.vscode/
log.text
Thunder/logs/
tdlib_data/
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: uv sync
- run: uv run ruff check Thunder/
- run: uv run ruff format --check Thunder/

test:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +11 to +22
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: uv sync
- run: uv run pytest tests/ -v --tb=short

typecheck:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +23 to +33
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: uv sync
- run: uv run pyright Thunder/ --level basic

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +34 to +42
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ var/
wheels/
*.egg-info/
.installed.cfg
*.egg
*.egg
Thunder/logs/
.pytest_cache/
tdlib_data/
36 changes: 27 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AGENTS.md — Thunder File-to-Link Bot

Python 3.13+ Telegram bot converting files to direct HTTP links. Uses Pyrofork, aiohttp, MongoDB, uvloop.
Python 3.13+ Telegram bot converting files to direct HTTP links. Uses Pytdbot (TDLib), aiohttp, MongoDB, uvloop.

## Run

Expand All @@ -11,9 +11,12 @@ bash thunder.sh # Runs python3 update.py && python3 -m Thunder

## Dependencies

Managed with [uv](https://docs.astral.sh/uv/) (lockfile: `uv.lock`).

```bash
pip install -r requirements.txt
# aiohttp, cloudscraper, Jinja2, pyrofork, pymongo, psutil, python-dotenv, speedtest-cli, tgcrypto, uvloop==0.21.0
uv sync # Install from lockfile (recommended)
uv run python -m Thunder # Run in uv-managed environment
pip install -r requirements.txt # Alternative: legacy pip install
```

## Project Structure
Expand All @@ -35,7 +38,8 @@ Thunder/
│ ├── __init__.py # web_server() — creates aiohttp app with routes
│ ├── stream_routes.py # HTTP streaming endpoints
│ └── exceptions.py # Custom HTTP exceptions
├── utils/ # 20 modules — see imports below
├── utils/ # 27 modules — see imports below
│ └── database/ # 8 files — mixin-based repo decomposition
└── template/ # dl.html, req.html (Jinja2)
```

Expand All @@ -47,17 +51,30 @@ from Thunder.utils.database import db # AsyncMongoClient singleton
from Thunder.utils.rate_limiter import rate_limiter, request_executor, handle_rate_limited_request
from Thunder.utils.bot_utils import is_admin # async def is_admin(cli, chat_id_val) -> bool — checks bot membership, NOT a decorator
from Thunder.utils.decorators import owner_only # async guard function, not a decorator
from Thunder.utils.compat import Filters # Pyrogram-style filters on top of Pytdbot
from Thunder.vars import Var # All env config
```

## Telegram Client: Pytdbot (TDLib)

This project uses [Pytdbot](https://github.com/pytdbot/client) — a TDLib wrapper for Python. Key differences from Pyrogram:

- **Types**: `from pytdbot import types` — `types.Message`, `types.User`, `types.Chat`, `types.Error`, etc.
- **Client**: `from pytdbot import Client` — `Client(token=..., api_id=..., api_hash=...)`
- **Handlers**: `@client.on_message()` for new messages, `@client.on_updateNewCallbackQuery()` for callbacks
- **Filters**: Custom filters via `pytdbot.filters.create(func)` and `Thunder.utils.compat.Filters`
- **Error handling**: Methods return `types.Error | SuccessType` (no exceptions for API errors)
- **File streaming**: `downloadFile()` downloads to disk, then read from `file.local.path`
- **Message bound methods**: `message.reply_text()`, `message.editTextMessage()`, `message.delete()`, `message.copy()`

## Code Conventions

- PEP 8, 4-space indent, 120-char lines
- Imports: stdlib → third-party → local
- All I/O is async; use `asyncio.sleep()` not `time.sleep()`
- Catch `FloodWait` from Telegram API with `await asyncio.sleep(e.value)`
- Check `isinstance(result, types.Error)` after every Pytdbot API call
- Log with `logger.error(..., exc_info=True)` for exceptions
- Admin access: `filters.user(Var.OWNER_ID)` on Pyrogram handlers (not `is_admin()`)
- Admin access: `Filters.user(Var.OWNER_ID)` on handlers (not `is_admin()`)
- Naming: PascalCase classes, snake_case functions/vars, UPPER_SNAKE_CASE constants

## Rate Limiting
Expand All @@ -74,7 +91,8 @@ Copy `config_sample.env` → `config.env`. Required vars: `API_ID`, `API_HASH`,

## Debugging

- Logs: `Thunder/logs/bot.txt`
- Logs: `Thunder/logs/bot.txt` (gitignored)
- Health check: admin `/status` command
- No linting/formatting tools configured — follow conventions manually
- No formal test suite — verify via bot interaction and link streaming
- Linting: `uv run ruff check Thunder/` and `uv run ruff format --check Thunder/`
- Type checking: `uv run pyright Thunder/ --level basic`
- Tests: `uv run pytest tests/ -v` (22 tests, requires pytdbot installed)
146 changes: 146 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# API Documentation — Thunder FileToLink

## HTTP Endpoints

### `GET /`
Redirects to the GitHub repository.

### `GET /health`
Health check endpoint for load balancers.

**Response:** `200 OK`
```json
{"status": "ok"}
```

### `GET /metrics`
Prometheus-compatible metrics endpoint (requires `ADMIN_TOKEN` header if configured).

**Response:** `200 OK` (text/plain)
```
# HELP thunder_uptime_seconds Time since bot started
thunder_uptime_seconds 8130
# HELP thunder_active_streams Currently active file streams
thunder_active_streams 3
# HELP thunder_bytes_served_total Total bytes served via streaming
thunder_bytes_served_total 1073741824
# HELP thunder_requests_total Total HTTP requests by path
thunder_requests_total{path="/f/hash123/file.mp4"} 42
# HELP thunder_errors_total Total HTTP errors by status code
thunder_errors_total{status="404"} 5
```

### `GET /status`
Returns bot status and resource usage (requires `ADMIN_TOKEN` header if configured).

**Response:** `200 OK`
```json
{
"status": "operational",
"version": "2.1.0",
"uptime": "2h 15m 30s",
"active_clients": 1,
"total_workload": 3
}
```

### `GET /watch/f/{public_hash}/{filename}`
Renders an HTML streaming page with video player and download button.

**Parameters:**
- `secure_hash` — 6-character file unique ID prefix
- `message_id` — Telegram message ID
- `filename` — Original filename (for display)

**Response:** `200 OK` (text/html) — Cinema streaming player

### `GET /watch/f/{public_hash}/{filename}`
Renders an HTML streaming page for canonical (deduplicated) files.

**Parameters:**
- `public_hash` — 20-character SHA256 hash of file unique ID
- `filename` — Original filename

**Response:** `200 OK` (text/html) — Cinema streaming player

### `GET /f/{secure_hash}{message_id}/{filename}`
Downloads or streams a file directly.

**Parameters:**
- `secure_hash` — 6-character file unique ID prefix
- `message_id` — Telegram message ID
- `filename` — Original filename

**Query Parameters:**
- `disposition` — `inline` or `attachment` (default: `attachment`)

**Headers:**
- `Range` — Byte range for partial content (e.g., `bytes=0-1023`)

**Response:**
- `200 OK` — Full file
- `206 Partial Content` — Range request
- `404 Not Found` — File not found

### `GET /f/{public_hash}/{filename}`
Downloads or streams a canonical (deduplicated) file.

**Parameters:**
- `public_hash` — 20-character SHA256 hash
- `filename` — Original filename

**Response:** Same as above.

### `HEAD` on any endpoint
Returns headers without body (same as GET but no content).

### `OPTIONS` on any endpoint
Returns CORS preflight headers.

---

## Error Responses

| Status | Meaning |
|--------|---------|
| `200` | Success |
| `206` | Partial content (range request) |
| `302` | Redirect |
| `400` | Bad request (invalid parameters) |
| `404` | Resource not found |
| `416` | Range not satisfiable |
| `500` | Internal server error (with error ID) |
| `503` | All clients at capacity |

---

## Telegram Bot Commands

### User Commands

| Command | Description |
|---------|-------------|
| `/start` | Welcome message and token activation |
| `/help` | Usage instructions |
| `/about` | Bot information |
| `/link [n]` | Generate links (reply to file in groups, `n` for batch) |
| `/dc` | Data center info for user or file |
| `/ping` | Check bot responsiveness |

### Admin Commands

| Command | Description |
|---------|-------------|
| `/status` | Bot status and workload |
| `/stats` | System resource usage |
| `/users` | Total user count |
| `/broadcast [mode]` | Send message to all users |
| `/ban <id> [reason]` | Ban a user or channel |
| `/unban <id>` | Unban a user or channel |
| `/authorize <id>` | Grant permanent access |
| `/deauthorize <id>` | Revoke permanent access |
| `/listauth` | List authorized users |
| `/log` | Send bot log file |
| `/restart` | Restart the bot |
| `/shell <cmd>` | Execute shell command (60s timeout) |
| `/speedtest` | Network speed test |
Loading
Loading