ferogram 0.3.7

Production-grade async Telegram MTProto client: updates, bots, flood-wait, dialogs, messages
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
<div align="center">

# ferogram

Async Rust client for the Telegram MTProto API.

[![Crates.io](https://img.shields.io/crates/v/ferogram?style=flat-square&color=fc8d62)](https://crates.io/crates/ferogram)
[![docs.rs](https://img.shields.io/badge/docs.rs-ferogram-5865F2?style=flat-square)](https://docs.rs/ferogram)
[![License](https://img.shields.io/badge/license-MIT%20%7C%20Apache--2.0-blue?style=flat-square)](../LICENSE-MIT)
[![TL Layer](https://img.shields.io/badge/TL%20Layer-224-8b5cf6?style=flat-square)](https://core.telegram.org/schema)
[![Telegram Channel](https://img.shields.io/badge/channel-%40Ferogram-2CA5E0?style=flat-square&logo=telegram)](https://t.me/Ferogram)
[![Telegram Chat](https://img.shields.io/badge/chat-%40FerogramChat-2CA5E0?style=flat-square&logo=telegram)](https://t.me/FerogramChat)

Built by **[Ankit Chaubey](https://github.com/ankit-chaubey)**

</div>

> [!NOTE]
> ferogram is still in development but already covers major use cases for production. Check [CHANGELOG]../CHANGELOG.md before upgrading.

## What it is

This is the high-level client crate in the ferogram workspace. It talks to Telegram directly over MTProto, no Bot API proxy in between. Works for user accounts and bots.

For the rest of the workspace (crypto, session, TL types, transport layer, etc.) see the [repository root](https://github.com/ankit-chaubey/ferogram) or the [crates table](#crates) below.

---

## Installation

```toml
[dependencies]
ferogram = "0.3.7"
tokio    = { version = "1", features = ["full"] }
```

Get `api_id` and `api_hash` from [my.telegram.org](https://my.telegram.org).

Optional feature flags:

```toml
ferogram = { version = "0.3.7", features = [
    "sqlite-session",  # SqliteBackend via rusqlite
    "libsql-session",  # LibSqlBackend via libsql-client (Turso)
    "html",            # parse_html / generate_html (built-in parser)
    "html5ever",       # parse_html via spec-compliant html5ever
    "derive",          # #[derive(FsmState)]
    "serde",           # serde support on session types
] }
```

---

## Quick start: bot

```rust
use ferogram::{Client, update::Update};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let (client, _) = Client::builder()
        .api_id(std::env::var("API_ID")?.parse()?)
        .api_hash(std::env::var("API_HASH")?)
        .session("bot.session")
        .connect().await?;

    client.bot_sign_in(&std::env::var("BOT_TOKEN")?).await?;

    let mut stream = client.stream_updates();
    while let Some(upd) = stream.next().await {
        if let Update::NewMessage(msg) = upd {
            if !msg.outgoing() {
                msg.reply(msg.text().unwrap_or_default()).await.ok();
            }
        }
    }
    Ok(())
}
```

## Quick start: user account

```rust
use ferogram::{Client, SignInError};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let (client, _) = Client::builder()
        .api_id(std::env::var("API_ID")?.parse()?)
        .api_hash(std::env::var("API_HASH")?)
        .session("my.session")
        .connect().await?;

    if !client.is_authorized().await? {
        let token = client.request_login_code("+1234567890").await?;
        match client.sign_in(&token, &read_line()).await {
            Ok(_) => {}
            Err(SignInError::PasswordRequired(t)) => {
                client.check_password(*t, &read_line()).await?;
            }
            Err(e) => return Err(e.into()),
        }
        client.save_session().await?;
    }

    client.send_message("me", "Hello from ferogram!").await?;
    Ok(())
}
```

---

## Connecting

```rust
use ferogram::Client;

let (client, _shutdown) = Client::builder()
    .api_id(12345)
    .api_hash("your_api_hash")
    .session("my.session")
    .catch_up(true)
    .connect()
    .await?;
```

Some builder options worth knowing:

- `.session(path)` / `.in_memory()` / `.session_string(s)` / `.session_backend(Arc<...>)` for session storage
- `.socks5("127.0.0.1:1080")` / `.proxy_link("t.me/proxy?...")` for proxies
- `.transport(TransportKind::Obfuscated)` or `.transport(TransportKind::FakeTls)` for DPI bypass
- `.probe_transport(true)` to race transports and use the first that connects
- `.resilient_connect(true)` to fall back through DoH and Telegram's special-config if TCP fails
- `.catch_up(true)` to replay missed updates after a reconnect
- `.retry_policy(...)` / `.restart_policy(...)` to customize retry and reconnect behavior

Full list and usage at [docs.rs/ferogram](https://docs.rs/ferogram).

---

## Dispatcher and filters

```rust
use ferogram::filters::{Dispatcher, command, private, text_contains, group, media};

let mut dp = Dispatcher::new();

dp.on_message(command("start"), |msg| async move {
    msg.reply("Hello!").await.ok();
});

dp.on_message(private() & text_contains("help"), |msg| async move {
    msg.reply("Type /start to begin.").await.ok();
});

dp.on_message(group() & media(), |msg| async move {
    // handle media in groups
});

while let Some(upd) = stream.next().await {
    dp.dispatch(upd).await;
}
```

Filters compose with `&`, `|`, `!`. Built-ins include `command`, `private`, `group`, `channel`, `text`, `text_contains`, `media`, `photo`, `document`, `forwarded`, `reply`, `from_user`, `album`, `custom`, and more.

---

## Middleware

```rust
dp.middleware(|upd, next| async move {
    tracing::info!("incoming update");
    let result = next.run(upd).await;
    tracing::info!("handler done");
    result
});
```

Runs in registration order. Call `next.run(upd)` to pass control forward, or return early to stop the chain.

---

## FSM

```rust
use ferogram::{FsmState, fsm::MemoryStorage};
use std::sync::Arc;

#[derive(FsmState, Clone, Debug, PartialEq)]
enum Form { Name, Age }

dp.with_state_storage(Arc::new(MemoryStorage::new()));

dp.on_message_fsm(text(), Form::Name, |msg, state| async move {
    state.set_data("name", msg.text().unwrap()).await.ok();
    state.transition(Form::Age).await.ok();
    msg.reply("How old are you?").await.ok();
});
```

`MemoryStorage` is built in. To persist state, implement `StateStorage` for Redis, a database, or anything else. State keys scope per-user, per-chat, or per-user-in-chat via `StateKeyStrategy`. See [`ferogram-fsm`](../ferogram-fsm/) for details.

---

## Session backends

```rust
use ferogram_session::{SqliteBackend, LibSqlBackend};
use std::sync::Arc;

Client::builder().session("bot.session")                                     // binary file (default)
Client::builder().in_memory()                                                // no persistence
Client::builder().session_string(env::var("SESSION")?)                       // base64 string
Client::builder().session_backend(Arc::new(SqliteBackend::open("s.db")?))   // sqlite
Client::builder().session_backend(Arc::new(LibSqlBackend::remote(url, token).await?))  // turso
```

Custom: implement `SessionBackend` from [`ferogram-session`](../ferogram-session/). The base64 string backend is handy for serverless or containers where you can't write to disk.

---

## Transport and proxy

```rust
use ferogram::TransportKind;

Client::builder().transport(TransportKind::Abridged)    // default
Client::builder().transport(TransportKind::Obfuscated)  // DPI bypass, plain MTProxy secrets
Client::builder().transport(TransportKind::FakeTls)     // TLS camouflage, 0xee secrets

// MTProxy from a t.me link
Client::builder().proxy_link("https://t.me/proxy?server=HOST&port=PORT&secret=SECRET")

// SOCKS5
Client::builder().socks5("127.0.0.1:1080")

// Race transports, use first to connect
Client::builder().probe_transport(true)

// Fall back through DoH + Telegram special-config if TCP fails
Client::builder().resilient_connect(true)
```

See [`ferogram-connect`](../ferogram-connect/) for the framing layer underneath.

---

## Raw API

If the high-level API doesn't cover something yet, `client.invoke()` takes any Layer 224 TL function directly:

```rust
use ferogram::tl;

let req = tl::functions::bots::SetBotCommands {
    scope: tl::enums::BotCommandScope::Default(tl::types::BotCommandScopeDefault {}),
    lang_code: "en".into(),
    commands: vec![tl::enums::BotCommand::BotCommand(tl::types::BotCommand {
        command: "start".into(),
        description: "Start the bot".into(),
    })],
};
client.invoke(&req).await?;
client.invoke_on_dc(2, &req).await?;  // target a specific DC
```

See [`ferogram-tl-types`](../ferogram-tl-types/) for all 2,329 generated types.

---

## Error handling

```rust
use ferogram::{InvocationError, RpcError};

match client.send_message("@peer", "Hi").await {
    Ok(()) => {}
    Err(InvocationError::Rpc(RpcError { code, message, .. })) => {
        eprintln!("Telegram error {code}: {message}");
    }
    Err(InvocationError::Io(e)) => eprintln!("I/O: {e}"),
    Err(e) => eprintln!("{e}"),
}
```

`FLOOD_WAIT` is handled automatically. To disable it:

```rust
use ferogram::retry::NoRetries;
Client::builder().retry_policy(Arc::new(NoRetries))
```

---

## Shutdown

```rust
let (client, shutdown) = Client::builder()...connect().await?;

shutdown.cancel();   // graceful
client.disconnect(); // immediate
```

---

## Features

Most of what you'd expect is already covered. Check **[FEATURES.md](../FEATURES.md)** for the complete list with all method signatures. A few things worth pointing out:

- [x] **Bot + user account** in the same API, same client builder
- [x] **Dispatcher with composable filters** (`&`, `|`, `!`) and middleware pipeline
- [x] **FSM with pluggable storage** for multi-step conversations
- [x] **FakeTLS and Obfuscated2 transport** for censored regions, full MTProxy support
- [x] **Resilient connect** - DoH resolver + Telegram special-config fallback when TCP is blocked
- [x] **Transport probing** - races multiple transports, uses whichever connects first
- [x] **Update gap recovery** - PTS/QTS tracking, fetches missed updates via `getDifference` on reconnect
- [x] **QR code login** for user accounts
- [x] **Concurrent upload and download** with per-part retry and CDN redirect handling
- [x] **Turso/LibSQL session backend** for serverless and distributed setups
- [x] **Forum topics** (supergroups with topics enabled) - create, edit, delete
- [x] **Inline button click from code** (`msg.click_button`, `msg.click_button_where`)
- [x] **Scheduled messages** - send, list, delete, send immediately
- [x] **Paid reactions** and custom emoji reactions
- [x] **Python bindings** via [ferogram-py]https://github.com/ankit-chaubey/ferogram-py, pre-built wheels, no Rust toolchain needed
- [ ] Secret chats (end-to-end encrypted) - not yet

---

## Crates

Most people only need `ferogram`. But each crate is independently publishable if you need just one layer.

| Crate | What it does |
|---|---|
| [`ferogram`]. | High-level client. Auth, messaging, media, dispatcher, FSM, middleware. |
| [`ferogram-session`]../ferogram-session/ | Session types and pluggable storage backends (file, memory, SQLite, LibSQL, base64). |
| [`ferogram-fsm`]../ferogram-fsm/ | FSM state storage and context. `StateStorage` trait, `MemoryStorage`, `StateContext`. |
| [`ferogram-parsers`]../ferogram-parsers/ | Telegram Markdown and HTML entity parsers. |
| [`ferogram-derive`]../ferogram-derive/ | `#[derive(FsmState)]` proc macro. |
| [`ferogram-mtsender`]../ferogram-mtsender/ | DC connection pool and retry policy. `AutoSleep`, `NoRetries`, `CircuitBreaker`. |
| [`ferogram-connect`]../ferogram-connect/ | Raw TCP, MTProto framing, obfuscation, SOCKS5, MTProxy, gzip. |
| [`ferogram-mtproto`]../ferogram-mtproto/ | MTProto 2.0 session, DH key exchange, message framing, PFS key binding. |
| [`ferogram-crypto`]../ferogram-crypto/ | AES-IGE, RSA, SHA, Diffie-Hellman, PQ factorization, auth key derivation. |
| [`ferogram-tl-types`]../ferogram-tl-types/ | Auto-generated TL types, functions, and enums for Layer 224. |
| [`ferogram-tl-gen`]../ferogram-tl-gen/ | Build-time code generator from TL AST to Rust source. |
| [`ferogram-tl-parser`]../ferogram-tl-parser/ | Parses `.tl` schema text into a Definition AST. |

The rough dependency chain:

```
ferogram
└ ferogram-mtsender
  └ ferogram-connect
    ├ ferogram-mtproto
    │ ├ ferogram-tl-types
    │ │ └ (build) ferogram-tl-gen
    │ │   └ (build) ferogram-tl-parser
    │ └ ferogram-crypto
    └ ferogram-crypto
```

---

## Testing

```bash
cargo test --workspace
cargo test --workspace --all-features
```

## Community

- **Channel** (releases, announcements): [t.me/Ferogram]https://t.me/Ferogram
- **Chat** (questions, discussion): [t.me/FerogramChat]https://t.me/FerogramChat
- **Guide**: [ferogram.ankitchaubey.in]https://ferogram.ankitchaubey.in/
- **API docs**: [docs.rs/ferogram]https://docs.rs/ferogram
- **Crates.io**: [crates.io/crates/ferogram]https://crates.io/crates/ferogram
- **GitHub**: [github.com/ankit-chaubey/ferogram]https://github.com/ankit-chaubey/ferogram

## Contributing

Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a PR. Run `cargo fmt --all`, `cargo test --workspace` and `cargo clippy --workspace` first. Security issues: see [SECURITY.md](SECURITY.md).

## Author

[Ankit Chaubey](https://github.com/ankit-chaubey)

I built ferogram because I was already using other MTProto libraries but kept running into cases where I needed things to work a bit differently than they allowed. So I wrote my own.

It covers the major use cases and that was the primary goal. If something's missing for you, feel free to drop by [t.me/FerogramChat](https://t.me/FerogramChat) and say hi. I genuinely like hearing what people are building with it. Just keeping it real though, every new feature is more to maintain, so I'm a bit selective. But I still love to hear from you.

If ferogram has been useful, a star or fork means a lot. And if you want to contribute, even better.

## Acknowledgments

Big shoutout to [Lonami](https://codeberg.org/Lonami/grammers) for grammers. It was genuinely one of the most helpful references while building ferogram, and honestly grammers and Telethon are two of my all-time favorites that I've been using for years. Love those projects.

Protocol behavior references from [Telegram Desktop](https://github.com/telegramdesktop/tdesktop) and [TDLib](https://github.com/tdlib/td).

## License

MIT OR Apache-2.0. See [LICENSE-MIT](LICENSE-MIT) and [LICENSE-APACHE](LICENSE-APACHE).

Usage must comply with [Telegram's API Terms of Service](https://core.telegram.org/api/terms).