A fast Telegram userbot written in Rust. Uses grammers for the MTProto user client, teloxide for the inline Bot API, and Lua 5.4 as the module scripting language.
All commands use . as the prefix and only respond to your own messages.
| Command | Alias | Description |
|---|---|---|
.ping |
— | Replies with pong to check connectivity |
.eval <expression> |
.e |
Evaluates a Lua expression and shows the result |
.term <command> |
— | Runs a shell command and shows its output |
.install <file-or-url> [name] |
— | Installs a Lua module into modules/ |
| `.note set | get | clear` |
| `.alias set | get | del` |
.help |
— | Shows all available commands |
.restart |
— | Saves state and restarts the process |
.update |
— | Checks for updates (stub, extend via module) |
A paired Telegram bot handles inline queries and button callbacks. Modules can register UUID-keyed inline results with keyboards that trigger Rust/Lua callbacks when pressed.
Modules are plain Lua 5.4 files placed in the modules/ directory. They are loaded at startup and hot-reloaded automatically whenever a file changes — no restart needed.
Each module receives a ctx object with the full Telegram and database API. See moduleBuild.md for the full module authoring guide.
A flat JSON file (database.json) acts as a key-value store accessible from both Rust and Lua. Writes are crash-safe: each flush writes to a .tmp sibling file and renames it over the target, so the database is never left in a truncated state.
Anti-delete history is stored separately in data/antidelete.sqlite so deleted
message archives do not bloat or expose the main JSON key-value store.
On first launch (no saved session), an axum HTTP server starts at http://127.0.0.1:8080 with a minimal login form. After successful sign-in the server shuts down automatically.
The login form accepts an optional SOCKS5 proxy URL:
socks5://127.0.0.1:9050
socks5://user:password@127.0.0.1:9050
Use an IP address for the proxy host if you need to avoid local DNS lookup for the proxy itself. Telegram datacenter targets are connected by IP through the SOCKS5 tunnel.
Use a different session name in /login to authorize another account. The
dashboard starts a separate update loop for the new session immediately and
remembers it in session_files, so all known accounts are connected again on
the next launch.
After the userbot connects, the same local web port becomes a dashboard at
http://127.0.0.1:8080. It shows uptime, connection state, account name,
per-account status, update and command counters, loaded modules, proxy state,
and database encryption state.
The dashboard settings panel can update the stored SOCKS5 proxy URL and change or clear the master password used for local encrypted storage. Proxy changes apply to new MTProto connections after restart.
Set FLY_MASTER_PASSWORD before launch to encrypt local state at rest:
# Linux / macOS
export FLY_MASTER_PASSWORD="change-me"
# Windows (PowerShell)
$env:FLY_MASTER_PASSWORD = "change-me"When enabled, database.json is written as database.json.enc, and
fly-telegram.session is sealed as fly-telegram.session.enc after a normal
shutdown. The clear session file exists while the process is running because
grammers needs a SQLite session file.
Anti-delete storage follows the same master password. With encryption enabled,
data/antidelete.sqlite is sealed as data/antidelete.sqlite.enc at rest.
The notify crate watches the modules/ directory. When a .lua file is saved, the old module is unloaded and the new version is loaded without restarting the process.
Modules that declare "trusted": true in their manifest gain access to the full Lua runtime (no sandbox). Without signing, any file dropped into modules/ with that flag would immediately obtain elevated privileges.
The signing system binds the trusted flag to an operator-held Ed25519 key pair. On load, the runtime verifies the signature against the module source and its security-relevant manifest fields (name, version, permissions, trusted). A missing or invalid signature forces trusted to false with a warning — even after a hot-reload of a tampered file.
./fly-telegram --keygen
# prompts for a signing password
# writes keys/signing.key.enc (encrypted private key)
# keys/signing.pub (raw public key, 32 bytes)./fly-telegram --sign modules/core.lua
# prompts for the signing password
# writes the base64 signature into core.lua.manifest.json
# also refreshes the checksum field (SHA-256 of the source)Repeat for every module that needs "trusted": true. Modules without a valid signature are sandboxed automatically.
{"name":"core","permissions":[...],"source_sha256":"<hex>","trusted":true,"version":"1.0.0"}Keys are sorted lexicographically — the payload is deterministic. Changing the source file, permissions, name, or version invalidates the signature.
If keys/signing.pub is absent, all modules run sandboxed regardless of their manifest.
| Tool | Minimum version | Notes |
|---|---|---|
| Rust + Cargo | 1.75 | Install via rustup.rs |
| C compiler | — | MSVC (Windows) or GCC/Clang (Linux/macOS) — required to compile Lua 5.4 |
| Git | any | Needed to clone the repo |
No system Lua installation is required — Lua 5.4 is compiled and bundled automatically by Cargo (vendored feature).
Go to https://my.telegram.org/auth, sign in, open API development tools, and create an application. Save your API ID (integer) and API hash (string).
git clone <repo-url>
cd rust-fly-telegram
# Windows
compile.bat
# Linux / macOS
chmod +x compile.sh
./compile.shOr build manually:
cargo build --releaseThe binary is placed at target/release/fly-telegram (Linux/macOS) or target\release\fly-telegram.exe (Windows).
# Linux / macOS
./target/release/fly-telegram
# Windows
.\target\release\fly-telegram.exeOn first launch you will be prompted for:
- API ID — integer from my.telegram.org
- API hash — string from my.telegram.org
- Phone number — in international format, e.g.
+79001234567 - Login code — sent to your Telegram account
- 2FA password — only if your account has two-factor authentication enabled
Credentials are saved to database.json. The session key is saved to fly-telegram.session. Neither file should be committed to version control.
Create a bot via @BotFather and set the TELOXIDE_TOKEN environment variable before launching:
# Linux / macOS
export TELOXIDE_TOKEN="123456789:AAxxxxxx"
# Windows (PowerShell)
$env:TELOXIDE_TOKEN = "123456789:AAxxxxxx"Enable inline mode for the bot in BotFather (/setinline).
rust-fly-telegram/
├── src/
│ ├── main.rs Entry point, --keygen / --sign CLI
│ ├── config.rs Compile-time path constants
│ ├── crypto.rs Argon2 + XChaCha20 encryption, Ed25519 signing
│ ├── database.rs JSON key-value store
│ ├── watcher.rs Hot-reload file watcher
│ ├── client/
│ │ ├── mod.rs grammers update loop
│ │ └── auth.rs CLI + web authorization
│ ├── loader/
│ │ ├── mod.rs Lua module loader & dispatcher
│ │ ├── manifest.rs Manifest parsing, signature verification
│ │ └── context.rs ctx UserData exposed to Lua
│ ├── bot/
│ │ └── mod.rs teloxide inline bot
│ └── web/
│ ├── mod.rs axum login server
│ └── login.html Login page
├── modules/ Lua modules (hot-reloaded)
│ ├── core.lua
│ ├── executor.lua
│ ├── help.lua
│ └── updater.lua
├── keys/ Created by --keygen (do not commit)
│ ├── signing.pub Ed25519 verifying key (32 bytes)
│ └── signing.key.enc Ed25519 signing key (encrypted)
├── database.json Created on first run
├── fly-telegram.session Created on first run
├── compile.bat Windows build script
├── compile.sh Linux/macOS build script
├── README.md
└── moduleBuild.md Module authoring guide
| Variable | Required | Description |
|---|---|---|
TELOXIDE_TOKEN |
Optional | Bot token for the inline bot subsystem |
FLY_MASTER_PASSWORD |
Optional | Encrypts database.json and the session file at rest |
RUST_LOG |
Optional | Log level, e.g. fly_telegram=debug |
| Flag | Description |
|---|---|
--no-web |
Skip the web authorization server |
--keygen |
Generate an Ed25519 key pair in keys/ and exit |
--sign <path> |
Sign a module's manifest with the operator key and exit |
MIT
