tgbotrs 0.1.3

A fully-featured, auto-generated Telegram Bot API library for Rust. All 285 types and 165 methods β€” strongly typed, fully async.
Documentation

Crates.io docs.rs CI Auto-Regen

Telegram Bot API Rust License: MIT Crates.io Downloads

Types Methods Coverage Async Serde

All 285 types and 165 methods of the Telegram Bot API β€” strongly typed, fully async, automatically kept up-to-date.

πŸ“¦ Install β€’ πŸš€ Quick Start β€’ πŸ“– Examples β€’ πŸ”§ API Reference β€’ πŸ”„ Auto-Codegen β€’ 🀝 Contributing


✨ 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

  • Generated from the official spec
  • Daily automated check for API updates
  • PR auto-opened on every new API version
  • Zero manual work to stay up-to-date

πŸ¦€ Idiomatic Rust

  • Fully async/await with Tokio
  • Into<ChatId> β€” accepts i64 or "@username"
  • Into<String> on all text params
  • Option<T> for all optional fields
  • Box<T> to break recursive type cycles

πŸ›‘οΈ 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 all API calls

πŸ“‘ Flexible HTTP Layer

  • Custom API server support (local Bot API)
  • Multipart file uploads
  • 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:

[dependencies]
tgbotrs = "0.1"
tokio   = { version = "1", features = ["full"] }

Requirements: Rust 1.75+ Β· Tokio async runtime


πŸš€ Quick Start

use tgbotrs::Bot;

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

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

    // Send a message β€” chat_id accepts i64 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

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

use tgbotrs::gen_methods::SendMessageParams;

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

bot.send_message(
    "@mychannel",
    "<b>Bold</b>, <i>italic</i>, <code>code</code>",
    Some(params),
).await?;

🎹 Inline Keyboards

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

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".into(), url: Some("https://example.com".into()), ..Default::default() },
        ],
    ],
};

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

bot.send_message(chat_id, "Choose an option:", Some(params)).await?;

πŸ“Έ Send Photos

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

let params = SendPhotoParams::new().caption("Nice photo! πŸ“·".to_string());

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

// By URL
bot.send_photo(chat_id, "https://example.com/photo.jpg", Some(params.clone())).await?;

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

🎬 Media Groups

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?;

⌨️ Reply Keyboards

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

let keyboard = ReplyKeyboardMarkup {
    keyboard: vec![
        vec![
            KeyboardButton { text: "πŸ“ Location".into(), request_location: Some(true), ..Default::default() },
            KeyboardButton { text: "πŸ“± Contact".into(),  request_contact: Some(true),  ..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?;

πŸ“Š Polls

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?;

⚑ Callback Queries

use tgbotrs::gen_methods::AnswerCallbackQueryParams;
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("");

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

        // Edit original message
        if let Some(MaybeInaccessibleMessage::Message(m)) = cq.message {
            let _ = bot.edit_message_text(
                m.chat.id, m.message_id,
                format!("βœ… Selected: <b>{}</b>", data),
                Some(tgbotrs::gen_methods::EditMessageTextParams::new()
                    .parse_mode("HTML".to_string())),
            ).await;
        }
    })
});

πŸͺ Inline Queries

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

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

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

πŸ›’ Payments

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

use tgbotrs::gen_methods::SetWebhookParams;

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?;

🌐 Local Bot API Server

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

πŸ› οΈ Error Handling

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 the 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!("❌ Error: {}", e),
}

πŸ”§ API Reference

Bot β€” Core Struct

pub struct Bot {
    pub token:   String,  // Bot token from @BotFather
    pub me:      User,    // Populated via getMe on creation
    pub api_url: String,  // API base URL (default: 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
Method Description
bot.call_api(method, body) Raw JSON POST API call
bot.call_api_multipart(method, form) Multipart POST (for file uploads)
bot.endpoint(method) Returns full URL for a method

ChatId β€” Flexible Chat Identifier

// All of these work wherever ChatId is expected:
bot.send_message(123456789i64,    "by integer id", None).await?;
bot.send_message(-100123456789i64, "group/channel", None).await?;
bot.send_message("@channelname",  "by username",   None).await?;
bot.send_message(ChatId::Id(123), "explicit",      None).await?;

InputFile β€” File Sending

// Reference an already-uploaded file by file_id
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

// 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
ReplyMarkup::ForceReply(ForceReply { force_reply: true, .. })

Poller β€” Long Polling Dispatcher

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

BotError β€” Error Handling

pub enum BotError {
    Http(reqwest::Error),   // Network or HTTP transport error
    Json(serde_json::Error),// Serialization/deserialization error
    Api {
        code: i64,                      // Telegram error code (e.g. 400, 403, 429)
        description: String,            // Human-readable error message
        retry_after: Option<i64>,       // Seconds to wait (flood-wait, code 429)
        migrate_to_chat_id: Option<i64>,// New chat ID (migration error, code 400)
    },
    InvalidToken,           // Token does not contain ':'
    Other(String),          // Catch-all
}

// Helper methods:
error.is_api_error_code(429)  // β†’ bool
error.flood_wait_seconds()    // β†’ Option<i64>

Builder Pattern for Optional Params

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

// Pattern: MethodNameParams::new().field(value).field(value)
let params = SendMessageParams::new()
    .parse_mode("MarkdownV2".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, ..Default::default()
    }));

πŸ“Š 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 βœ…
Params structs 100 βœ…
Lines generated ~11,258 β€”

πŸ”„ Auto-Codegen

tgbotrs is the only Rust Telegram library that automatically stays in sync with the official API spec via GitHub Actions.

Architecture

Every Day at 08:00 UTC
        β”‚
        β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Fetch latest   β”‚  ←── github.com/ankit-chaubey/api-spec
  β”‚  api.json spec  β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Compare with   │── No change? ──► Stop βœ…
  β”‚  pinned version β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ Changed!
           β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  diff_spec.py   β”‚  ←── Full semantic diff
  β”‚                 β”‚       β€’ Added/removed types
  β”‚                 β”‚       β€’ Added/removed methods
  β”‚                 β”‚       β€’ Per-field changes
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  codegen.py     β”‚  ←── Pure Python, zero dependencies
  β”‚                 β”‚       Generates:
  β”‚                 β”‚       β€’ gen_types.rs  (5,821 lines)
  β”‚                 β”‚       β€’ gen_methods.rs (5,437 lines)
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  validate.py    β”‚  ←── Verify 100% coverage
  β”‚                 β”‚       All types & methods present
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Open PR with   β”‚  ←── Rich description:
  β”‚  full report    β”‚       β€’ Summary table
  β”‚                 β”‚       β€’ New/removed items
  β”‚                 β”‚       β€’ Per-field diff (collapsible)
  β”‚                 β”‚       β€’ Checklist
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  GitHub Issue   β”‚  ←── Notification with full changelog
  β”‚  notification   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  On PR merge:   β”‚
  β”‚  β€’ Bump semver  β”‚
  β”‚  β€’ Git tag      β”‚
  β”‚  β€’ GitHub Releaseβ”‚
  β”‚  β€’ crates.io    β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Regenerate Manually

# 1. Download latest spec
curl -o api.json \
  https://raw.githubusercontent.com/ankit-chaubey/api-spec/main/api.json

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

# 3. Rebuild your project
cargo build

GitHub Actions Workflows

Workflow Trigger Purpose
auto-regenerate.yml ⏰ Daily 08:00 UTC + manual Fetch spec β†’ diff β†’ codegen β†’ PR
ci.yml Every push/PR Build, test, lint, validate sync
release.yml PR merged β†’ main Version bump + crates.io publish
notify.yml After regen GitHub Issue with change summary

Setting Up in Your Fork

Add these secrets in Settings β†’ Secrets β†’ Actions:

Secret Purpose
CRATES_IO_TOKEN API token from crates.io/settings/tokens

Enable PR creation in Settings β†’ Actions β†’ General β†’ Workflow permissions.


πŸ—οΈ Project Structure

tgbotrs/
β”‚
β”œβ”€β”€ πŸ“„ README.md                 ← You are here
β”œβ”€β”€ πŸ“„ CHANGELOG.md              ← Auto-updated on each release
β”œβ”€β”€ πŸ“„ LICENSE                   ← MIT β€” Ankit Chaubey 2024-present
β”œβ”€β”€ πŸ“„ api.json                  ← Pinned Telegram Bot API spec
β”œβ”€β”€ πŸ“„ spec_commit               ← Pinned spec commit SHA
β”œβ”€β”€ πŸ“„ Cargo.toml                ← Workspace root
β”‚
β”œβ”€β”€ πŸ—‚οΈ  .github/
β”‚   β”œβ”€β”€ workflows/
β”‚   β”‚   β”œβ”€β”€ auto-regenerate.yml  ← Daily spec sync + codegen + PR opener
β”‚   β”‚   β”œβ”€β”€ ci.yml               ← Build/test on 3 OSes Γ— 2 Rust versions
β”‚   β”‚   β”œβ”€β”€ release.yml          ← Semver bump + tag + publish
β”‚   β”‚   └── notify.yml           ← Issue creation on API updates
β”‚   └── scripts/
β”‚       β”œβ”€β”€ diff_spec.py         ← Semantic diff: added/removed/changed
β”‚       β”œβ”€β”€ validate_generated.py← Verifies 100% type + method coverage
β”‚       β”œβ”€β”€ build_pr_body.py     ← Generates rich PR descriptions
β”‚       β”œβ”€β”€ coverage_report.py   ← Markdown coverage table for CI
β”‚       └── update_changelog.py  ← Auto-prepends entries to CHANGELOG.md
β”‚
β”œβ”€β”€ πŸ—‚οΈ  codegen/
β”‚   β”œβ”€β”€ Cargo.toml
β”‚   β”œβ”€β”€ codegen.py               ← Main codegen: Python, zero deps
β”‚   └── src/main.rs              ← Rust codegen binary (alternative)
β”‚
└── πŸ—‚οΈ  tgbotrs/                 ← The library crate
    β”œβ”€β”€ Cargo.toml
    β”œβ”€β”€ examples/
    β”‚   β”œβ”€β”€ echo_bot.rs          ← Basic echo bot
    β”‚   └── advanced_bot.rs      ← Keyboards, photos, callbacks
    └── src/
        β”œβ”€β”€ lib.rs               ← Crate root + public API + re-exports
        β”œβ”€β”€ bot.rs               ← Bot struct + HTTP + JSON API layer
        β”œβ”€β”€ error.rs             ← BotError with all error variants
        β”œβ”€β”€ chat_id.rs           ← ChatId (i64 | @username)
        β”œβ”€β”€ input_file.rs        ← InputFile + InputFileOrString
        β”œβ”€β”€ reply_markup.rs      ← ReplyMarkup (4-variant enum)
        β”œβ”€β”€ polling.rs           ← Poller (long-polling dispatcher)
        β”œβ”€β”€ types.rs             ← Re-exports gen_types
        β”œβ”€β”€ gen_types.rs         ← ⚑ AUTO-GENERATED β€” 5,821 lines
        └── gen_methods.rs       ← ⚑ AUTO-GENERATED β€” 5,437 lines

🀝 Contributing

Contributions are very welcome!

Report Issues

Development

# Clone the repo
git clone https://github.com/ankit-chaubey/tgbotrs
cd tgbotrs

# Build everything
cargo build --workspace

# Run tests
cargo test --workspace

# Regenerate from latest spec
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

# Lint
cargo clippy --workspace --all-targets -- -D warnings

# Format
cargo fmt --all

PR Guidelines

  • One concern per PR
  • Run cargo fmt and cargo clippy before submitting
  • Add examples for new helpers
  • Keep generated files (gen_*.rs) untouched β€” edit codegen.py instead

πŸ“œ Changelog

See CHANGELOG.md for the full release history.


πŸ™ Credits

Telegram The official Bot API this library implements
PaulSonOfLars / gotgbot Design inspiration for the auto-generation approach and code structure
ankit-chaubey / api-spec Machine-readable Telegram Bot API spec used as the codegen source

πŸ“¬ Contact

πŸ“§ Email ankitchaubey.dev@gmail.com
πŸ’¬ Telegram @ankify
πŸ™ GitHub github.com/ankit-chaubey
πŸ“¦ crates.io crates.io/crates/tgbotrs
πŸ“– docs.rs docs.rs/tgbotrs

πŸ“„ License

MIT License

Copyright (c) 2024-present Ankit Chaubey

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Created and developed by Ankit Chaubey

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

GitHub stars GitHub forks Telegram