I built ferogram because I kept hitting walls with other MTProto libraries. Things that should have been straightforward weren't, and I kept needing the library to behave slightly differently than it would let me. So I wrote my own.
It talks to Telegram directly over MTProto, no Bot API proxy in between. It works for both bots and user accounts from the same API and the same client builder.
The major use cases are covered: messaging, media, inline keyboards, CDN downloads, FSM for multi-step conversations, FakeTLS and MTProxy for censored networks, and a raw invoke() escape hatch for anything the high-level API doesn't wrap yet.
If you want the Bot API instead, take a look at ferobot.
The longer-term goal is to support multiple languages from the same Rust core. Python is already live as ferogram-py on PyPI, pre-built wheels, no Rust toolchain needed.
Note
ferogram is still in active development. It covers major use cases and runs in production, but the API may still shift. Check CHANGELOG before upgrading.
[dependencies]
ferogram = "0.6.0"
tokio = { version = "1", features = ["full"] }Get api_id and api_hash from my.telegram.org. For optional feature flags (SQLite session, HTML parser, FSM derive macro) see the ferogram crate README.
Development on GitHub moves faster than crates.io. Releases are pushed to crates.io when there's a patch or a proper release, so there may be fixes and features on main that aren't published yet. If you need something from main, you can point directly to a specific commit:
ferogram = { git = "https://github.com/ankit-chaubey/ferogram", rev = "COMMIT_SHA" }Otherwise, stable from crates.io is the safe default.
use ferogram::{Client, update::Update};
const API_ID: i32 = 0;
const API_HASH: &str = "";
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (client, _) = Client::quick_connect("bot.session", API_ID, API_HASH).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(())
}use ferogram::Client;
const API_ID: i32 = 0;
const API_HASH: &str = "";
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (client, _) = Client::quick_connect("my.session", API_ID, API_HASH).await?;
client.send_message("me", "Hello from ferogram!").await?;
Ok(())
}use ferogram::filters::{Dispatcher, command, private, text_contains};
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();
});
while let Some(upd) = stream.next().await {
dp.dispatch(upd).await;
}Filters compose with &, |, !. Built-ins cover command, private, group, channel, text, media, forwarded, reply, album, custom, and more.
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();
});Storage is swappable. Implement StateStorage to use Redis, a database, or anything else.
When the high-level API doesn't cover something, client.invoke() takes any TL function directly:
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?;By default the session is a binary file on disk. Switch to SQLite, LibSQL (Turso), or a base64 string for serverless setups. You can also bring your own by implementing SessionBackend.
let s = client.export_session_string().await?;
let (client, _) = Client::builder().session_string(s).connect().await?;See FEATURES.md for the full list with method signatures. Runnable examples are in ferogram/examples/.
If something is missing, open a feature request or drop by t.me/FerogramChat. If the high-level API isn't enough, the raw API is always there.
Secret chats (end-to-end encrypted) are fully implemented but not published to crates.io yet. The plan is to release once there is enough community demand for it.
Voice and video calls : group audio is fully implemented, stable, and already in production use. Group video is implemented with some codec edge cases still being ironed out. P2P is partially implemented and in active development. All of this will be published as separate crates when it comes out of the workspace.
cargo test --workspace
cargo test --workspace --all-features- Channel (releases, announcements): t.me/Ferogram
- Chat (questions, discussion): t.me/FerogramChat
- Guide: ferogram.ankitchaubey.in
- API docs: docs.rs/ferogram
- Crates.io: crates.io/crates/ferogram
- GitHub: github.com/ankit-chaubey/ferogram
Read CONTRIBUTING.md before opening a PR. Run cargo fmt --all, cargo test --workspace, and cargo clippy --workspace first. Security issues: see SECURITY.md.
Big shoutout to Lonami for grammers. It was one of the most helpful references while building ferogram, and grammers and Telethon are two of my all-time favorites. Love those projects.
Protocol behavior references from Telegram Desktop and TDLib.
MIT OR Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.
Usage must comply with Telegram's API Terms of Service.