An open API service indexing awesome lists of open source software.

https://github.com/ankit-chaubey/tgbotrs

A strongly-typed, async Rust client for the Telegram Bot API, generated from the official spec.
https://github.com/ankit-chaubey/tgbotrs

botapi-rust tgbotapi tgbotrs

Last synced: 4 months ago
JSON representation

A strongly-typed, async Rust client for the Telegram Bot API, generated from the official spec.

Awesome Lists containing this project

README

          

Ferris the Crab

tgbotrs

A fully-featured, auto-generated Telegram Bot API library for Rust πŸ¦€

[![Crates.io](https://img.shields.io/crates/v/tgbotrs?style=for-the-badge&logo=rust&color=f74c00&labelColor=1a1a2e)](https://crates.io/crates/tgbotrs)
[![docs.rs](https://img.shields.io/docsrs/tgbotrs?style=for-the-badge&logo=docs.rs&color=4a90d9&labelColor=1a1a2e)](https://docs.rs/tgbotrs)
[![CI](https://img.shields.io/github/actions/workflow/status/ankit-chaubey/tgbotrs/ci.yml?branch=main&style=for-the-badge&logo=github-actions&label=CI&color=2ea44f&labelColor=1a1a2e)](https://github.com/ankit-chaubey/tgbotrs/actions/workflows/ci.yml)
[![API Sync](https://img.shields.io/github/actions/workflow/status/ankit-chaubey/tgbotrs/auto-regenerate.yml?style=for-the-badge&logo=telegram&label=API+SYNC&color=0088cc&labelColor=1a1a2e)](https://github.com/ankit-chaubey/tgbotrs/actions/workflows/auto-regenerate.yml)

[![Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-9.4-0088cc?style=flat-square&logo=telegram&logoColor=white)](https://core.telegram.org/bots/api)
[![Rust](https://img.shields.io/badge/Rust-1.75%2B-f74c00?style=flat-square&logo=rust)](https://www.rust-lang.org)
[![Types](https://img.shields.io/badge/Types-285-7c3aed?style=flat-square)](https://docs.rs/tgbotrs)
[![Methods](https://img.shields.io/badge/Methods-165-16a34a?style=flat-square)](https://docs.rs/tgbotrs)
[![Coverage](https://img.shields.io/badge/API%20Coverage-100%25-22c55e?style=flat-square)](https://github.com/ankit-chaubey/tgbotrs/actions)
[![Downloads](https://img.shields.io/crates/d/tgbotrs?style=flat-square&color=f97316&label=Downloads)](https://crates.io/crates/tgbotrs)
[![License](https://img.shields.io/badge/License-MIT-eab308?style=flat-square)](LICENSE)


> All **285 types** and **165 methods** of the Telegram Bot API β€”
> strongly typed, fully async, automatically kept in sync with every official release.


[πŸ“¦ Install](#-installation) Β· [πŸš€ Quick Start](#-quick-start) Β· [πŸ“– Examples](#-examples) Β· [πŸ”§ API Reference](#-api-reference) Β· [πŸ”„ Auto-Codegen](#-auto-codegen) Β· [πŸ“š docs.rs](https://docs.rs/tgbotrs)

---

## ✨ Features

**πŸ€– Complete API Coverage**
- All **285 types** β€” structs, enums, markers
- All **165 methods** β€” fully async
- All **21 union types** as Rust enums
- **100 optional params structs** with builder pattern

**πŸ”„ Auto-Generated & Always Fresh**
- Spec sourced from [tgapis/x](https://github.com/tgapis/x/tree/data) β€” auto-updated every 6 hours
- Pipeline dispatches regeneration on every new API version
- PR auto-opened with full semantic diff on every change
- Zero manual work to stay up-to-date

**πŸ¦€ Idiomatic Rust**
- Fully `async/await` with **Tokio**
- `Into` β€” accepts `i64` or `"@username"`
- `Into` on all text params
- `Option` for all optional fields
- `Box` to break recursive type cycles

**πŸ›‘οΈ Fully Type-Safe**
- `ChatId` β€” integer or username, no stringly typing
- `InputFile` β€” file_id / URL / raw bytes
- `ReplyMarkup` β€” unified enum for all 4 keyboard types
- `InputMedia` β€” typed enum for media groups
- Compile-time guarantees on every API call

**πŸ“‘ Flexible HTTP Layer**
- Custom API server support (local Bot API)
- Multipart file uploads built-in
- Configurable timeout
- Flood-wait aware error handling
- `reqwest` backend

**πŸ“¬ Built-in Polling**
- Long-polling dispatcher included
- Spawns a Tokio task per update
- Configurable timeout, limit, allowed\_updates
- Clean concurrent update processing

---

## πŸ“¦ Installation

Add to your `Cargo.toml`:

```toml
[package]
name = "mybot"
version = "0.1.0"
edition = "2021"

[dependencies]
tgbotrs = ">=0.1.5"
tokio = { version = "1", features = ["full"] }
```

> **Requirements:** Rust `1.75+` Β· Tokio async runtime

---

## πŸš€ Quick Start

```rust
use tgbotrs::Bot;

#[tokio::main]
async fn main() -> Result<(), Box> {
let bot = Bot::new("YOUR_BOT_TOKEN").await?;

println!("βœ… Running as @{}", bot.me.username.as_deref().unwrap_or("unknown"));
println!(" ID: {}", bot.me.id);

// chat_id accepts i64, negative group IDs, or "@username"
let msg = bot.send_message(123456789i64, "Hello from tgbotrs! πŸ¦€", None).await?;
println!("πŸ“¨ Sent message #{}", msg.message_id);

Ok(())
}
```

---

## πŸ“– Examples

### πŸ” Echo Bot β€” Long Polling

The simplest possible bot. Receives every message and echoes it back.

```rust
use tgbotrs::{Bot, Poller, UpdateHandler};

#[tokio::main]
async fn main() {
let bot = Bot::new(std::env::var("BOT_TOKEN").unwrap())
.await
.expect("Invalid token");

println!("πŸ€– @{} is running...", bot.me.username.as_deref().unwrap_or(""));

let handler: UpdateHandler = Box::new(|bot, update| {
Box::pin(async move {
let Some(msg) = update.message else { return };
let Some(text) = msg.text else { return };
let _ = bot.send_message(msg.chat.id, text, None).await;
})
});

Poller::new(bot, handler)
.timeout(30)
.limit(100)
.start()
.await
.unwrap();
}
```

---

### πŸ’¬ Formatted Messages

Send HTML or MarkdownV2 formatted messages with optional settings.

```rust
use tgbotrs::gen_methods::SendMessageParams;

let params = SendMessageParams::new()
.parse_mode("HTML".to_string())
.disable_notification(true);

bot.send_message(
"@mychannel",
"Bold Β· Italic Β· code Β· Link",
Some(params),
).await?;
```

---

### 🎹 Inline Keyboards

Buttons embedded inside messages. Perfect for interactive menus.

```rust
use tgbotrs::{ReplyMarkup, gen_methods::SendMessageParams};
use tgbotrs::types::{InlineKeyboardButton, InlineKeyboardMarkup};

let keyboard = InlineKeyboardMarkup {
inline_keyboard: vec![
vec![
InlineKeyboardButton {
text: "βœ… Accept".into(),
callback_data: Some("accept".into()),
..Default::default()
},
InlineKeyboardButton {
text: "❌ Decline".into(),
callback_data: Some("decline".into()),
..Default::default()
},
],
vec![
InlineKeyboardButton {
text: "🌐 Visit Website".into(),
url: Some("https://ankitchaubey.in".into()),
..Default::default()
},
],
],
};

let params = SendMessageParams::new()
.parse_mode("HTML".to_string())
.reply_markup(ReplyMarkup::InlineKeyboard(keyboard));

bot.send_message(chat_id, "Make a choice:", Some(params)).await?;
```

---

### ⚑ Callback Queries

Handle button taps from inline keyboards. Always acknowledge with `answer_callback_query`.

```rust
use tgbotrs::gen_methods::{AnswerCallbackQueryParams, EditMessageTextParams};
use tgbotrs::types::MaybeInaccessibleMessage;

let handler: UpdateHandler = Box::new(|bot, update| {
Box::pin(async move {
let Some(cq) = update.callback_query else { return };
let data = cq.data.as_deref().unwrap_or("");

// Always acknowledge β€” dismisses the loading spinner
let _ = bot
.answer_callback_query(
cq.id.clone(),
Some(
AnswerCallbackQueryParams::new()
.text(format!("You chose: {}", data))
.show_alert(false),
),
)
.await;

// Edit the original message in-place
if let Some(msg) = &cq.message {
if let MaybeInaccessibleMessage::Message(m) = msg.as_ref() {
let edit_params = EditMessageTextParams::new()
.chat_id(m.chat.id)
.message_id(m.message_id)
.parse_mode("HTML".to_string());

let _ = bot
.edit_message_text(
format!("βœ… You selected: {}", data),
Some(edit_params),
)
.await;
}
}
})
});
```

---

### ⌨️ Reply Keyboards

Custom keyboard shown at the bottom of the screen. Great for persistent menu buttons.

```rust
use tgbotrs::{ReplyMarkup, gen_methods::SendMessageParams};
use tgbotrs::types::{KeyboardButton, ReplyKeyboardMarkup};

let keyboard = ReplyKeyboardMarkup {
keyboard: vec![
vec![
KeyboardButton {
text: "πŸ“ Share Location".into(),
request_location: Some(true),
..Default::default()
},
KeyboardButton {
text: "πŸ“± Share Contact".into(),
request_contact: Some(true),
..Default::default()
},
],
vec![
KeyboardButton { text: "🏠 Home".into(), ..Default::default() },
KeyboardButton { text: "βš™οΈ Settings".into(), ..Default::default() },
],
],
resize_keyboard: Some(true),
one_time_keyboard: Some(true),
..Default::default()
};

let params = SendMessageParams::new()
.reply_markup(ReplyMarkup::ReplyKeyboard(keyboard));

bot.send_message(chat_id, "Use the keyboard below πŸ‘‡", Some(params)).await?;
```

---

### πŸ“Έ Send Photos & Files

Send files by file\_id, URL, or raw bytes from disk.

```rust
use tgbotrs::{InputFile, gen_methods::SendPhotoParams};

let params = SendPhotoParams::new()
.caption("Look at this! πŸ“·".to_string())
.parse_mode("HTML".to_string());

// Fastest β€” already on Telegram's servers
bot.send_photo(chat_id, "AgACAgIAAxkBAAI...", Some(params.clone())).await?;

// Let Telegram download from a URL
bot.send_photo(chat_id, "https://example.com/photo.jpg", Some(params.clone())).await?;

// Upload raw bytes from disk
let data = tokio::fs::read("photo.jpg").await?;
bot.send_photo(chat_id, InputFile::memory("photo.jpg", data), Some(params)).await?;
```

---

### 🎬 Media Groups

Send multiple photos or videos as an album in a single message.

```rust
use tgbotrs::InputMedia;
use tgbotrs::types::{InputMediaPhoto, InputMediaVideo};

let media = vec![
InputMedia::Photo(InputMediaPhoto {
r#type: "photo".into(),
media: "AgACAgIAAxkBAAI...".into(),
caption: Some("First photo πŸ“Έ".into()),
..Default::default()
}),
InputMedia::Video(InputMediaVideo {
r#type: "video".into(),
media: "BAACAgIAAxkBAAI...".into(),
caption: Some("A video 🎬".into()),
..Default::default()
}),
];

bot.send_media_group(chat_id, media, None).await?;
```

---

### πŸ“Š Polls

Send polls β€” regular or quiz style.

```rust
use tgbotrs::gen_methods::SendPollParams;
use tgbotrs::types::InputPollOption;

let options = vec![
InputPollOption { text: "πŸ¦€ Rust".into(), ..Default::default() },
InputPollOption { text: "🐹 Go".into(), ..Default::default() },
InputPollOption { text: "🐍 Python".into(), ..Default::default() },
];

let params = SendPollParams::new().is_anonymous(false);

bot.send_poll(chat_id, "Best language for bots?", options, Some(params)).await?;
```

---

### πŸͺ Inline Queries

Handle `@yourbot query` inline mode from any chat.

```rust
use tgbotrs::types::{
InlineQueryResult, InlineQueryResultArticle,
InputMessageContent, InputTextMessageContent,
};

let results = vec![
InlineQueryResult::InlineQueryResultArticle(InlineQueryResultArticle {
r#type: "article".into(),
id: "1".into(),
title: "Hello World".into(),
input_message_content: InputMessageContent::InputTextMessageContent(InputTextMessageContent {
message_text: "Hello from inline mode! πŸ‘‹".into(),
..Default::default()
}),
description: Some("Send a greeting".into()),
reply_markup: None,
url: None,
thumbnail_url: None,
thumbnail_width: None,
thumbnail_height: None,
}),
];

bot.answer_inline_query(query.id.clone(), results, None).await?;
```

---

### πŸ›’ Payments & Telegram Stars

Send invoices using Telegram Stars (`XTR`) or payment providers.

```rust
use tgbotrs::gen_methods::SendInvoiceParams;
use tgbotrs::types::LabeledPrice;

let prices = vec![
LabeledPrice { label: "Premium Plan".into(), amount: 999 },
];

bot.send_invoice(
chat_id,
"Premium Access",
"30 days of unlimited features",
"payload_premium_30d",
"XTR", // Telegram Stars
prices,
None,
).await?;
```

---

### πŸ”” Webhooks

Register a webhook URL so Telegram pushes updates to your server instead of you polling. Read detailed [webhook bot examples!](https://github.com/ankit-chaubey/tgbotrs/tree/main/examples/webhook)

```rust
use tgbotrs::gen_methods::SetWebhookParams;

// Register webhook
let params = SetWebhookParams::new()
.max_connections(100i64)
.allowed_updates(vec!["message".into(), "callback_query".into()])
.secret_token("my_secret_token".to_string());

bot.set_webhook("https://mybot.example.com/webhook", Some(params)).await?;
```

**Full webhook server with [axum](https://github.com/tokio-rs/axum):**

```toml
# Cargo.toml
[dev-dependencies]
axum = "0.7"
```

```rust
use axum::{extract::State, http::StatusCode, routing::post, Json, Router};
use std::sync::Arc;
use tgbotrs::{gen_methods::SetWebhookParams, types::Update, Bot};

struct AppState { bot: Bot }

#[tokio::main]
async fn main() {
let bot = Bot::new("YOUR_BOT_TOKEN").await.unwrap();

bot.set_webhook(
"https://yourdomain.com/webhook",
Some(SetWebhookParams::new()),
)
.await
.unwrap();

let app = Router::new()
.route("/webhook", post(handle_update))
.with_state(Arc::new(AppState { bot }));

let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}

async fn handle_update(
State(state): State>,
Json(update): Json,
) -> StatusCode {
let bot = state.bot.clone();
// Spawn immediately β€” return 200 fast or Telegram will retry
tokio::spawn(async move {
if let Some(msg) = update.message {
let _ = bot
.send_message(msg.chat.id, "Received via webhook! πŸš€", None)
.await;
}
});
StatusCode::OK
}
```

> For local testing: `ngrok http 8080` β†’ use the ngrok URL as your webhook

---

### 🌐 Local Bot API Server

Point the bot at a self-hosted [Telegram Bot API server](https://github.com/tdlib/telegram-bot-api) for higher file size limits and faster speeds.

```rust
let bot = Bot::with_api_url("YOUR_TOKEN", "http://localhost:8081").await?;
```

---

### πŸ› οΈ Error Handling

Structured errors with helpers for flood-wait and common API errors.

```rust
use tgbotrs::BotError;

match bot.send_message(chat_id, "Hello!", None).await {
Ok(msg) => println!("βœ… Sent: #{}", msg.message_id),

Err(BotError::Api { code: 403, .. }) => {
eprintln!("🚫 Bot was blocked by user");
}
Err(BotError::Api { code: 400, description, .. }) => {
eprintln!("⚠️ Bad request: {}", description);
}
Err(e) if e.is_api_error_code(429) => {
if let Some(secs) = e.flood_wait_seconds() {
println!("⏳ Flood wait: {} seconds", secs);
tokio::time::sleep(std::time::Duration::from_secs(secs as u64)).await;
}
}
Err(e) => eprintln!("❌ Unexpected error: {}", e),
}
```

---

## πŸ”§ API Reference

### `Bot` β€” Core Struct

```rust
pub struct Bot {
pub token: String, // Bot token from @BotFather
pub me: User, // Populated via getMe on creation
pub api_url: String, // Default: https://api.telegram.org
}
```

| Constructor | Description |
|---|---|
| `Bot::new(token)` | Create bot, calls `getMe`, verifies token |
| `Bot::with_api_url(token, url)` | Create with a custom/local API server |
| `Bot::new_unverified(token)` | Create without calling `getMe` |

---

### `ChatId` β€” Flexible Chat Identifier

Anywhere `ChatId` is expected, you can pass any of these:

```rust
bot.send_message(123456789i64, "user by numeric id", None).await?;
bot.send_message(-100123456789i64, "group or channel", None).await?;
bot.send_message("@channelname", "by username", None).await?;
bot.send_message(ChatId::Id(123), "explicit wrapper", None).await?;
```

---

### `InputFile` β€” File Sending

```rust
// Reference a file already on Telegram's servers (fastest)
InputFile::file_id("AgACAgIAAxkBAAI...")

// Let Telegram download from a URL
InputFile::url("https://example.com/image.png")

// Upload raw bytes directly
let data = tokio::fs::read("photo.jpg").await?;
InputFile::memory("photo.jpg", data)
```

---

### `ReplyMarkup` β€” All Keyboard Types

```rust
// Inline keyboard β€” buttons inside messages
ReplyMarkup::InlineKeyboard(InlineKeyboardMarkup { .. })

// Reply keyboard β€” custom keyboard at bottom of screen
ReplyMarkup::ReplyKeyboard(ReplyKeyboardMarkup { .. })

// Remove the reply keyboard
ReplyMarkup::ReplyKeyboardRemove(ReplyKeyboardRemove { remove_keyboard: true, .. })

// Force the user to reply to a message
ReplyMarkup::ForceReply(ForceReply { force_reply: true, .. })
```

---

### `Poller` β€” Long Polling Dispatcher

```rust
Poller::new(bot, handler)
.timeout(30) // Seconds to long-poll (0 = short poll)
.limit(100) // Max updates per request (1–100)
.allowed_updates(vec![ // Filter which update types to receive
"message".into(),
"callback_query".into(),
"inline_query".into(),
])
.start()
.await?;
```

---

### `BotError` β€” Error Variants

```rust
pub enum BotError {
Http(reqwest::Error), // Network / HTTP transport error
Json(serde_json::Error), // Serialization error
Api {
code: i64, // Telegram error code (400, 403, 429…)
description: String, // Human-readable message
retry_after: Option, // Flood-wait seconds (code 429)
migrate_to_chat_id: Option, // Migration target (code 400)
},
InvalidToken, // Token missing ':'
Other(String), // Catch-all
}

// Helper methods
error.is_api_error_code(429) // β†’ bool
error.flood_wait_seconds() // β†’ Option
```

---

### Builder Pattern for Optional Params

Every method with optional parameters has a `*Params` struct with a fluent builder API:

```rust
// Pattern: MethodNameParams::new().field(value).field(value)
let params = SendMessageParams::new()
.parse_mode("HTML".to_string())
.disable_notification(true)
.protect_content(false)
.message_thread_id(123i64)
.reply_parameters(ReplyParameters { message_id: 42, ..Default::default() })
.reply_markup(ReplyMarkup::ForceReply(ForceReply {
force_reply: true,
input_field_placeholder: None,
selective: None,
}));
```

---

## πŸ“Š Coverage Statistics

| Category | Count | Status |
|:---|:---:|:---:|
| **Total Types** | **285** | βœ… 100% |
| ↳ Struct types | 257 | βœ… |
| ↳ Union / Enum types | 21 | βœ… |
| ↳ Marker types | 7 | βœ… |
| **Total Methods** | **165** | βœ… 100% |
| ↳ `set*` methods | 30 | βœ… |
| ↳ `get*` methods | 29 | βœ… |
| ↳ `send*` methods | 22 | βœ… |
| ↳ `edit*` methods | 12 | βœ… |
| ↳ `delete*` methods | 11 | βœ… |
| ↳ Other methods | 61 | βœ… |
| **Optional params structs** | 100 | βœ… |
| **Lines auto-generated** | ~11,258 | β€” |

---

## πŸ”„ Auto-Codegen

tgbotrs is the only Rust Telegram library that **automatically stays in sync** with the official API β€” no manual updates, no lag.

The spec is sourced from **[tgapis/x](https://github.com/tgapis/x/tree/data)** (`data` branch, `botapi.json`), which scrapes and parses the official Telegram Bot API page and auto-updates every **6 hours**. When a new API version is detected, the `tgapis/x` pipeline dispatches a trigger to this repo β€” regeneration kicks off immediately, no cron required.

### How It Works

```
tgapis/x scrapes Telegram Bot API
every 6 hours β†’ pushes botapi.json
to the data branch
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ tgapis/x pipeline │── No change? ──► Stop βœ…
β”‚ detects new versionβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ Changed!
β”‚ repository_dispatch β†’
β”‚ event: x-data-updated
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Fetch botapi.json β”‚ ← raw.githubusercontent.com/tgapis/x/data/botapi.json
β”‚ (always latest) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Compare with │── No change? ──► Stop βœ…
β”‚ pinned api.json β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ Changed!
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ diff_spec.py β”‚ ← Semantic diff (added/removed types & methods)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ codegen.py β”‚ ← Pure Python, zero pip dependencies
β”‚ β”‚ Generates gen_types.rs + gen_methods.rs
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ validate.py β”‚ ← Verify 100% coverage
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Open PR with β”‚ ← Rich report: summary table, per-field diff
β”‚ full report β”‚ New/removed items, checklist
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ On PR merge: β”‚
β”‚ β€’ Bump semver β”‚
β”‚ β€’ Git tag β”‚
β”‚ β€’ GitHub Release β”‚
β”‚ β€’ crates.io β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

### Regenerate Manually

```sh
# 1. Pull latest spec from tgapis/x data branch into repo root
curl -sSf https://raw.githubusercontent.com/tgapis/x/data/botapi.json -o api.json

# 2. Run codegen (no pip installs needed)
python3 codegen/codegen.py api.json tgbotrs/src/

# 3. Rebuild
cargo build
```

### GitHub Actions Workflows

| Workflow | Trigger | Purpose |
|:---|:---|:---|
| `auto-regenerate.yml` | πŸ“‘ Pipeline dispatch from `tgapis/x` + manual | Spec sync β†’ diff β†’ codegen β†’ PR |
| `ci.yml` | Every push / PR | Build, test, lint on 3 OS Γ— 2 Rust versions (spec fetched live from `tgapis/x`) |
| `release.yml` | PR merged β†’ main | Semver bump β†’ tag β†’ crates.io publish |
| `notify.yml` | After regen | GitHub Issue with full change summary |

---

## 🀝 Contributing

Contributions are welcome!

**Report issues:**
- πŸ› Bug β†’ [open a bug report](https://github.com/ankit-chaubey/tgbotrs/issues/new?template=bug_report.md)
- πŸ’‘ Feature β†’ [open a feature request](https://github.com/ankit-chaubey/tgbotrs/issues/new?template=feature_request.md)
- πŸ”’ Security β†’ email [ankitchaubey.dev@gmail.com](mailto:ankitchaubey.dev@gmail.com) directly

**Development workflow:**

```sh
git clone https://github.com/ankit-chaubey/tgbotrs && cd tgbotrs

cargo build --workspace # Build everything
cargo test --workspace # Run tests
cargo clippy --workspace -- -D warnings # Lint
cargo fmt --all # Format

# Pull the latest spec from tgapis/x into repo root and regenerate
curl -sSf https://raw.githubusercontent.com/tgapis/x/data/botapi.json -o api.json
python3 codegen/codegen.py api.json tgbotrs/src/

# Validate 100% coverage
python3 .github/scripts/validate_generated.py \
api.json tgbotrs/src/gen_types.rs tgbotrs/src/gen_methods.rs
```

**PR guidelines:**
- One concern per PR
- Always run `cargo fmt` and `cargo clippy` before submitting
- Never edit `gen_types.rs` or `gen_methods.rs` directly β€” edit `codegen.py` instead
- Add examples for any new helpers

---

## πŸ“œ Changelog

See [CHANGELOG.md](CHANGELOG.md) for the full release history.

---

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

**tgbotrs** started as a personal tool.
I was constantly running into limitations, missing features, and unsupported things,
so in 2024 I decided to build my own solution.

After using **tgbotrs** for a long time (2024-26) and refining it along the way,
I felt it could be useful for others too β€” so I made it public.

If this helps you in any way, feel free to ⭐ star it or 🍴 fork it 😁

Developed and maintained by Ankit Chaubey [(@ankit-chaubey)](https://github.com/ankit-chaubey)



Rust engineer Β· Open-source builder Β· Telegram & systems enthusiast




























---

## πŸ™ Thanks & Credits

Special thanks to **[Paul / PaulSonOfLars](https://github.com/PaulSonOfLars)** β€” the auto-generation approach at the heart of this library was directly inspired by his excellent Go library **[gotgbot](https://github.com/PaulSonOfLars/gotgbot)** and api-spec gen. Seeing how clean and maintainable a fully-generated, strongly-typed Telegram library can be was the spark for building tgbotrs.

| | |
|:---|:---|
| [**Telegram**](https://core.telegram.org/bots/api) | The Bot API this library implements |
| [**PaulSonOfLars / gotgbot**](https://github.com/PaulSonOfLars/gotgbot) | Inspiration for the codegen-first approach |
| [**tgapis/x**](https://github.com/tgapis/x/tree/data) | Machine-readable spec source β€” auto-updated every 6 hours |

---

## πŸ“„ License

MIT License Β© 2026 [Ankit Chaubey](https://github.com/ankit-chaubey)

---

*If tgbotrs saved you time, a ⭐ on GitHub means a lot!*