Note
ferogram is still in development but already covers major use cases for production. Check CHANGELOG before upgrading.
ferogram is an MTProto client library for Rust. It works for both user accounts and bots, and talks to Telegram directly over MTProto with no Bot API HTTP proxy in between.
The goal is to eventually support multiple languages from the same Rust core, so you can write your bot in whatever language you prefer. Python is already live as a working example of that via ferogram-py on PyPI.
[dependencies]
ferogram = "0.4.0"
tokio = { version = "1", features = ["full"] }Get api_id and api_hash from my.telegram.org. For optional features see the ferogram crate.
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(())
}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(())
}Most common use cases are already covered. See FEATURES.md for the full list. Working examples are in ferogram/examples/.
If something's missing, open a feature request or send a PR. Just make sure to read the contributing guidelines before you do.
If the high-level API doesn't cover what you need, you can always fall through to the raw API with client.invoke().
If you need to call something that isn't wrapped yet, 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?;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 include 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.
Session is stored as a binary file by default. Switch to SQLite or libSQL with a feature flag, or use a base64 string for serverless setups where you can't write to disk. You can also bring your own backend by implementing SessionBackend. See ferogram-session for full details.
let s = client.export_session_string().await?;
let (client, _) = Client::builder().session_string(s).connect().await?;Python support is live via ferogram-py. Install it with pip and you're good to go, no Rust toolchain required, wheels are pre-built for major platforms.
pip install ferogramMore language targets are planned.
Most users only need the ferogram crate. If you need something lower-level like just the MTProto layer, crypto primitives, or the TL type generator on its own, see the workspace crates overview.
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.
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 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.
Big shoutout to Lonami 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 and TDLib.
MIT OR Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.
Usage must comply with Telegram's API Terms of Service.