Skip to content

Commit 9dd84f2

Browse files
committed
Organize the bot as an installable package
Move runtime modules under src and expose the uploadbot command. Load local settings from the working directory and move detailed setup out of the README.
1 parent dd8a6f1 commit 9dd84f2

15 files changed

Lines changed: 160 additions & 159 deletions

File tree

‎Dockerfile‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
66
WORKDIR /app
77
COPY requirements.lock ./
88
RUN pip install --no-cache-dir -r requirements.lock
9-
COPY pyproject.toml README.md LICENSE main.py config.py ./
10-
COPY plugins ./plugins
11-
COPY utils ./utils
9+
COPY pyproject.toml README.md LICENSE ./
10+
COPY src ./src
1211
RUN pip install --no-cache-dir --no-deps . \
1312
&& useradd --create-home --uid 10001 bot \
1413
&& mkdir /app/data \
1514
&& chown bot:bot /app/data
1615

1716
USER bot
18-
CMD ["python", "main.py"]
17+
CMD ["uploadbot"]

‎README.md‎

Lines changed: 45 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<p align="center">
2-
<img src="docs/logo.svg" alt="UploadBot logo" width="144" height="144">
2+
<img src="docs/logo.svg" alt="UploadBot logo" width="128" height="128">
33
</p>
44

55
<h1 align="center">UploadBot</h1>
@@ -11,171 +11,81 @@
1111
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-78dba9" alt="MIT license"></a>
1212
</p>
1313

14-
**Turn Telegram files into shareable links as they stream.** Send a document,
15-
photo, video, or audio file to the bot and receive a public download URL from
14+
Send a file to your Telegram bot and get a public download link from
1615
[File2Share](https://file2share.us). Send a public file URL to receive it on Telegram.
1716

17+
UploadBot passes file chunks straight from Telegram into the upload request.
18+
Uploading starts before downloading finishes, with bounded buffers and no
19+
temporary files on the bot host.
20+
1821
```text
19-
Telegram -> 512 KiB chunks -> UploadBot -> HTTP PUT -> File2Share
20-
│
21-
No temporary file
22+
Telegram -> 512 KiB chunks -> UploadBot -> File2Share -> Download link
2223
```
2324

24-
## Why UploadBot?
25-
26-
- **Start uploading immediately.** Telegram chunks feed directly into an
27-
asynchronous HTTP upload, without waiting for the full download.
28-
- **Keep memory bounded.** Backpressure and a configurable concurrency limit
29-
keep file size from becoming buffer size.
30-
- **Stay responsive.** Shared HTTP connections, optional native cryptography and
31-
uvloop, progress updates, and cancellable transfers.
32-
- **Run it your way.** Environment configuration, an optional user allowlist,
33-
Docker Compose, and support for custom upload hosts.
34-
35-
## File2Share hosting
36-
37-
The default host is `https://file2share.us`. No hosting account or API key is
38-
needed. UploadBot streams each file in a single PUT request and returns the
39-
direct file URL supplied by File2Share.
40-
41-
- Files can be up to **95 MB** (`95000000` bytes).
42-
- File2Share keeps uploads for **up to 14 days**.
43-
- The host allows **40 uploads per hour per IP address**.
44-
- Anyone with the download link can access the file.
45-
- Programs and web pages such as `.exe`, `.apk`, `.html`, and `.svg` are rejected
46-
by the host. Archive those files before sending them.
25+
## What you get
4726

48-
These are the host's limits, documented in the
49-
[File2Share command-line guide](https://www.file2share.us/cli). Larger files need
50-
File2Share's separate chunked upload API, which UploadBot does not implement.
51-
Increasing `MAX_FILE_SIZE` above 95 MB is rejected when File2Share is selected.
52-
53-
Custom hosts can be configured with `TRANSFER_URL`. They must accept raw PUT
54-
uploads at `/<filename>` and return a download URL in the response body or the
55-
`X-Direct-Url` header. Limits and retention depend on the selected host.
27+
- **Streaming uploads** without a separate download-to-disk step.
28+
- **Responsive transfers** with progress updates and `/cancel`.
29+
- **Controlled concurrency** with one transfer per user and a global limit.
30+
- **Simple hosting** with uv, Docker Compose, and an optional user allowlist.
5631

5732
## Quick start
5833

59-
Requires **Python 3.11+**, a Telegram bot token from
60-
[@BotFather](https://t.me/BotFather), and an API ID/hash from
61-
[my.telegram.org/apps](https://my.telegram.org/apps). The API ID/hash are also
62-
required for bot accounts because UploadBot uses Telegram's MTProto interface.
34+
You need [uv](https://docs.astral.sh/uv/getting-started/installation/), Python 3.11+,
35+
a bot token from [BotFather](https://t.me/BotFather), and an API ID/hash from
36+
[my.telegram.org/apps](https://my.telegram.org/apps).
6337

6438
```bash
6539
git clone https://github.com/IlyaKhalafi/UploadBot.git
6640
cd UploadBot
67-
python3 -m venv .venv
68-
source .venv/bin/activate
69-
python -m pip install -r requirements.lock
70-
python -m pip install --no-deps .
7141
cp .env.example .env
7242
# Fill in API_ID, API_HASH, and BOT_TOKEN in .env.
73-
python main.py
43+
uv sync --locked --no-dev --extra speed
44+
uv run --no-sync uploadbot
7445
```
7546

76-
On Windows, activate with `.venv\Scripts\activate`. The lock includes optional
77-
speed packages; uvloop is automatically skipped on Windows. For a minimal
78-
installation without speed extras, use `python -m pip install -r requirements.txt`.
79-
80-
Using [uv](https://docs.astral.sh/uv/)? Install the same locked environment with
81-
`uv sync --locked --extra speed`, then run `uv run --no-sync python main.py`.
82-
Startup is noninteractive and works with a service manager.
47+
No File2Share account or hosting API key is required. For a personal bot, set
48+
`ALLOWED_USER_IDS` in `.env` to your numeric Telegram user ID.
8349

84-
## Use it
50+
## Try it
8551

86-
| Send to the bot | Result |
52+
| Send in a private chat | Result |
8753
| --- | --- |
88-
| `/start` or `/help` | Welcome message, file limit, and commands |
89-
| A document, photo, video, audio, voice message, or animation | Stream to File2Share and return a public download URL |
90-
| A public HTTP(S) file URL | Ask Telegram to fetch and send the file |
54+
| A document, photo, video, or audio file | A public download URL |
55+
| A public HTTP(S) file URL | The file sent back through Telegram |
56+
| `/start` or `/help` | Commands and the configured file limit |
9157
| `/cancel` | Stop your active transfer |
9258

93-
The bot operates in private chats. Each user can run one transfer at a time;
94-
additional requests receive a busy message. Uploaded files are accessible through
95-
their sharing URL. File2Share removes uploads after up to 14 days.
96-
97-
For URL → Telegram, the URL must point to a file Telegram can fetch without login
98-
or browser cookies. Telegram imposes its own type and size limits. Signed query
99-
strings are preserved. The bot host does not download these URLs.
100-
101-
## Configuration
102-
103-
Copy [.env.example](.env.example) to `.env`. Existing environment variables take
104-
precedence. Keep `.env` and Telegram session files private; both are gitignored.
105-
106-
| Variable | Default | Purpose |
107-
| --- | --- | --- |
108-
| `API_ID` | Required | Telegram application API ID |
109-
| `API_HASH` | Required | Telegram application API hash |
110-
| `BOT_TOKEN` | Required | Your bot's BotFather token |
111-
| `TRANSFER_URL` | `https://file2share.us` | Upload host base URL; HTTPS recommended |
112-
| `MAX_FILE_SIZE` | `95000000` | Maximum Telegram to host file size, in bytes (95 MB) |
113-
| `MAX_CONCURRENT_TRANSFERS` | `3` | Total active transfers; one per user |
114-
| `TRANSFER_TIMEOUT` | `1800` | Per-transfer time limit in seconds |
115-
| `ALLOWED_USER_IDS` | Empty | Comma-separated Telegram user IDs; empty permits everyone |
116-
| `SESSION_PATH` | `data/uploadbot` | Persistent Telethon session path |
117-
118-
Use `ALLOWED_USER_IDS` for a personal bot. Increasing concurrency trades more
119-
network connections and bounded per-transfer buffers for more simultaneous work.
120-
`MAX_FILE_SIZE` applies to files received from Telegram. Telegram controls limits
121-
when it fetches a URL directly. For custom hosts, set `MAX_FILE_SIZE` to a limit
122-
that the host supports.
123-
124-
Existing installations should update both settings in `.env`.
125-
126-
```dotenv
127-
TRANSFER_URL=https://file2share.us
128-
MAX_FILE_SIZE=95000000
129-
```
130-
131-
## Docker
59+
Voice messages and animations work too. File URLs must be accessible to Telegram
60+
without browser cookies or a login.
13261

133-
Configure `.env` first, then run the following command.
62+
## Hosting limits
13463

135-
```bash
136-
docker compose up --build -d
137-
docker compose logs -f bot
138-
```
139-
140-
The container runs as a non-root user. A named volume persists the Telegram
141-
session, and no incoming port is required. Stop with `docker compose down`.
64+
File2Share accepts **up to 95 MB per request**, keeps files for **up to 14 days**,
65+
and limits uploads to **40 per hour per IP address**. Anyone with the link can
66+
download the file. Some file types, including programs and web pages, must be
67+
archived first. See the [host's API guide](https://www.file2share.us/cli).
14268

143-
## Performance and reliability
69+
Larger-file uploads through File2Share's separate chunked API are not implemented.
70+
You can also configure another compatible PUT upload host.
14471

145-
The old flow downloaded a file to disk and then uploaded it using a blocking
146-
HTTP call. The new flow removes temporary file I/O and starts the upload while
147-
the Telegram download is still in progress. Memory remains bounded by chunks and
148-
transport buffers rather than file size. Actual throughput depends on Telegram,
149-
your network, and the upload host; no universal speed multiplier is claimed.
72+
## Run with Docker
15073

151-
Uploads use raw PUT requests with a known `Content-Length`. File2Share returns
152-
the direct download URL in its `X-Direct-Url` response header. HTTP failures,
153-
incomplete downloads, invalid response URLs, and timeouts are reported as errors.
154-
Redirects are rejected. Configure the final upload endpoint. Interrupted uploads
155-
are not resumed or automatically retried; resend the file when ready.
156-
157-
See [the architecture guide](docs/architecture.md) for flow control, cleanup, and
158-
known limitations, including [local test downloads](docs/architecture.md#local-test-downloads).
159-
Upgrading an older installation? Read the
160-
[migration notes](CHANGELOG.md#migrating-from-1x).
161-
162-
## Development
74+
Configure `.env`, then start the bot.
16375

16476
```bash
165-
uv sync --locked --extra dev --extra speed
166-
uv run --no-sync ruff check .
167-
uv run --no-sync ruff format --check .
168-
uv run --no-sync pytest -q
77+
docker compose up --build -d
78+
docker compose logs -f bot
16979
```
17080

171-
Tests run offline against a local HTTP server and simulated Telegram messages.
172-
They cover streaming before the download completes, byte integrity, bounded
173-
Python allocations, HTTP errors, size limits, cancellation, and configuration.
174-
CI runs on Python 3.11 through 3.14.
81+
The container runs as a non-root user and persists the Telegram session in a named
82+
volume. No incoming port is needed.
17583

176-
Read [CONTRIBUTING.md](CONTRIBUTING.md) to report a bug or contribute a fix.
177-
If UploadBot is useful to you, a GitHub star helps other people discover it.
84+
## More
17885

179-
## License
86+
- [Setup and configuration](docs/setup.md) for every setting and custom upload hosts.
87+
- [How streaming works](docs/architecture.md) for flow control and failure handling.
88+
- [Contributing](CONTRIBUTING.md) for development commands and tests.
89+
- [Migration notes](CHANGELOG.md#migrating-from-1x) for older installations.
18090

181-
[MIT](LICENSE).
91+
MIT licensed. If UploadBot saves you time, a star helps others find it.

‎docs/setup.md‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Setup and configuration
2+
3+
Use [uv](https://docs.astral.sh/uv/getting-started/installation/) to install the
4+
environment locked in `uv.lock`. `pyproject.toml` defines the package and its
5+
dependencies. Running `uv sync --locked --no-dev --extra speed` installs the bot
6+
with optional native cryptography and uvloop. Skip `--extra speed` for the minimal
7+
installation. uvloop is automatically omitted on Windows.
8+
9+
Run `uv run --no-sync uploadbot` or `uv run --no-sync python -m uploadbot`.
10+
Neither command needs virtual environment activation. Run from the project
11+
directory to load its `.env`, or provide configuration through environment
12+
variables when running an installed package elsewhere.
13+
14+
## Credentials
15+
16+
Create a bot with [BotFather](https://t.me/BotFather). Get the application API ID
17+
and hash from [my.telegram.org/apps](https://my.telegram.org/apps). All three
18+
credentials are needed because UploadBot uses Telegram's MTProto interface.
19+
20+
Copy `.env.example` to `.env` and fill in the credentials. Environment variables
21+
take precedence over `.env`. Keep the environment file and Telegram session
22+
private. Both are excluded from Git and Docker build contexts.
23+
24+
## Settings
25+
26+
| Variable | Default | Purpose |
27+
| --- | --- | --- |
28+
| `API_ID` | Required | Telegram application API ID |
29+
| `API_HASH` | Required | Telegram application API hash |
30+
| `BOT_TOKEN` | Required | Your bot's BotFather token |
31+
| `TRANSFER_URL` | `https://file2share.us` | Upload host base URL |
32+
| `MAX_FILE_SIZE` | `95000000` | Maximum Telegram to host file size, in bytes |
33+
| `MAX_CONCURRENT_TRANSFERS` | `3` | Total active transfers, with one per user |
34+
| `TRANSFER_TIMEOUT` | `1800` | Time limit for one transfer, in seconds |
35+
| `ALLOWED_USER_IDS` | Empty | Comma-separated Telegram user IDs; empty permits everyone |
36+
| `SESSION_PATH` | `data/uploadbot` | Persistent Telethon session path |
37+
38+
For a personal bot, restrict `ALLOWED_USER_IDS` to your own Telegram user ID.
39+
Increasing concurrency allows more simultaneous work and uses more connections
40+
and per-transfer buffers. New requests receive a busy reply when all slots are
41+
occupied. There is no unbounded waiting queue.
42+
43+
`MAX_FILE_SIZE` applies to incoming Telegram files. When a user sends a URL,
44+
Telegram fetches it directly and imposes its own limits.
45+
46+
## Upload hosts
47+
48+
File2Share accepts a raw PUT upload at `/<filename>` and returns the direct file
49+
URL in the `X-Direct-Url` response header. Its response body contains a sharing
50+
page URL. UploadBot prefers the direct URL so it works in download clients.
51+
52+
The default file limit is 95,000,000 bytes. Raising it above that value is rejected
53+
when File2Share is selected. Files are retained for up to 14 days, and the host
54+
allows 40 uploads per hour per IP address. Programs and web pages such as `.exe`,
55+
`.apk`, `.html`, and `.svg` must be archived first. These are host restrictions,
56+
documented in the [File2Share API guide](https://www.file2share.us/cli).
57+
58+
Custom hosts must accept raw PUT uploads at `/<filename>` and return a URL in the
59+
`X-Direct-Url` header or a plain URL in the response body. Set `TRANSFER_URL` and
60+
`MAX_FILE_SIZE` to match the host. HTTPS is recommended. Retention and access
61+
rules depend on the selected host.
62+
63+
Configure the final endpoint because upload redirects are rejected. Files are
64+
not automatically retried or resumed after a failure. A local test receiver can
65+
return loopback URLs that only work on the bot's machine. See
66+
[local test downloads](architecture.md#local-test-downloads).
67+
68+
## Docker Compose
69+
70+
Compose reads `.env`, runs as a non-root user, and stores the Telegram session in
71+
a named volume. Uploaded file bytes are not stored in that volume. No incoming
72+
port is needed for Telegram or File2Share.
73+
74+
```bash
75+
docker compose up --build -d
76+
docker compose logs -f bot
77+
docker compose down
78+
```
79+
80+
If you change `SESSION_PATH`, keep it under `/app/data` in the container so it
81+
remains writable and persists in the volume.

‎plugins/__init__.py‎

Whitespace-only changes.

‎pyproject.toml‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,15 @@ dev = [
2828
]
2929

3030
[project.scripts]
31-
uploadbot = "main:main"
31+
uploadbot = "uploadbot.app:main"
3232

3333
[project.urls]
3434
Repository = "https://github.com/IlyaKhalafi/UploadBot"
3535
Issues = "https://github.com/IlyaKhalafi/UploadBot/issues"
3636

37-
[tool.setuptools]
38-
py-modules = ["main", "config"]
39-
4037
[tool.setuptools.packages.find]
41-
include = ["plugins*", "utils*"]
38+
where = ["src"]
39+
include = ["uploadbot*"]
4240

4341
[tool.pytest.ini_options]
4442
asyncio_mode = "auto"

‎src/uploadbot/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Stream files between Telegram and public upload hosts."""

‎src/uploadbot/__main__.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Support python -m uploadbot."""
2+
3+
from uploadbot.app import main
4+
5+
if __name__ == "__main__":
6+
main()

main.py renamed to src/uploadbot/app.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
"""Run UploadBot with python main.py."""
1+
"""Application startup and Telegram client lifecycle."""
22

33
import asyncio
44
import logging
55

66
import aiohttp
77
from telethon import TelegramClient, events
88

9-
from config import Settings
10-
from plugins.bot import UploadBot
11-
from utils.transfer import TransferClient
9+
from uploadbot.bot import UploadBot
10+
from uploadbot.config import Settings
11+
from uploadbot.transfer import TransferClient
1212

1313

1414
async def run(settings: Settings) -> None:

plugins/bot.py renamed to src/uploadbot/bot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
from telethon import errors, types
1111

12-
from config import Settings
13-
from utils.transfer import TransferClient, TransferError, stream_telegram, valid_link
12+
from uploadbot.config import Settings
13+
from uploadbot.transfer import TransferClient, TransferError, stream_telegram, valid_link
1414

1515
logger = logging.getLogger(__name__)
1616

config.py renamed to src/uploadbot/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Settings:
3636

3737
@classmethod
3838
def from_env(cls) -> "Settings":
39-
load_dotenv(Path(__file__).with_name(".env"))
39+
load_dotenv(Path.cwd() / ".env")
4040
api_id = positive_int("API_ID", "0")
4141
api_hash = os.environ.get("API_HASH", "").strip()
4242
bot_token = os.environ.get("BOT_TOKEN", "").strip()

0 commit comments

Comments
 (0)