📮 An elegant Telegram bots framework for Rust

Overview

teloxide

A full-featured framework that empowers you to easily build Telegram bots using the async/.await syntax in Rust. It handles all the difficult stuff so you can focus only on your business logic.

Table of contents

Highlights

  • Functional reactive design. teloxide follows functional reactive design, allowing you to declaratively manipulate streams of updates from Telegram using filters, maps, folds, zips, and a lot of other adaptors.
  • Dialogues management subsystem. We have designed our dialogues management subsystem to be easy-to-use, and, furthermore, to be agnostic of how/where dialogues are stored. For example, you can just replace a one line to achieve persistence. Out-of-the-box storages include Redis and Sqlite.
  • Strongly typed bot commands. You can describe bot commands as enumerations, and then they'll be automatically constructed from strings — just like JSON structures in serde-json and command-line arguments in structopt.

Setting up your environment

  1. Download Rust.
  2. Create a new bot using @Botfather to get a token in the format 123456789:blablabla.
  3. Initialise the TELOXIDE_TOKEN environmental variable to your token:
# Unix-like
$ export TELOXIDE_TOKEN=<Your token here>

# Windows
$ set TELOXIDE_TOKEN=<Your token here>
  1. Make sure that your Rust compiler is up to date:
# If you're using stable
$ rustup update stable
$ rustup override set stable

# If you're using nightly
$ rustup update nightly
$ rustup override set nightly
  1. Run cargo new my_bot, enter the directory and put these lines into your Cargo.toml:
[dependencies]
teloxide = "0.3"
teloxide-macros = "0.3"

log = "0.4.8"
pretty_env_logger = "0.4.0"

tokio = { version =  "0.2.11", features = ["rt-threaded", "macros"] }

API overview

The dices bot

This bot replies with a dice throw to each received message:

(Full)

use teloxide::prelude::*;

#[tokio::main]
async fn main() {
    teloxide::enable_logging!();
    log::info!("Starting dices_bot...");

    let bot = Bot::from_env();

    teloxide::repl(bot, |message| async move {
        message.answer_dice().send().await?;
        ResponseResult::<()>::Ok(())
    })
    .await;
}

Commands

Commands are strongly typed and defined declaratively, similar to how we define CLI using structopt and JSON structures in serde-json. The following bot accepts these commands:

  • /username <your username>
  • /usernameandage <your username> <your age>
  • /help

(Full)

use teloxide::{utils::command::BotCommand, prelude::*};

#[derive(BotCommand)]
#[command(rename = "lowercase", description = "These commands are supported:")]
enum Command {
    #[command(description = "display this text.")]
    Help,
    #[command(description = "handle a username.")]
    Username(String),
    #[command(description = "handle a username and an age.", parse_with = "split")]
    UsernameAndAge { username: String, age: u8 },
}

async fn answer(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<()> {
    match command {
        Command::Help => cx.answer(Command::descriptions()).send().await?,
        Command::Username(username) => {
            cx.answer_str(format!("Your username is @{}.", username)).await?
        }
        Command::UsernameAndAge { username, age } => {
            cx.answer_str(format!("Your username is @{} and age is {}.", username, age)).await?
        }
    };

    Ok(())
}

#[tokio::main]
async fn main() {
    teloxide::enable_logging!();
    log::info!("Starting simple_commands_bot...");

    let bot = Bot::from_env();

    let bot_name: String = panic!("Your bot's name here");
    teloxide::commands_repl(bot, bot_name, answer).await;
}

Dialogues management

A dialogue is described by an enumeration where each variant is one of possible dialogue's states. There are also subtransition functions, which turn a dialogue from one state to another, thereby forming a FSM.

Below is a bot that asks you three questions and then sends the answers back to you. First, let's start with an enumeration (a collection of our dialogue's states):

(dialogue_bot/src/dialogue/mod.rs)

// Imports are omitted...

#[derive(Transition, From)]
pub enum Dialogue {
    Start(StartState),
    ReceiveFullName(ReceiveFullNameState),
    ReceiveAge(ReceiveAgeState),
    ReceiveLocation(ReceiveLocationState),
}

impl Default for Dialogue {
    fn default() -> Self {
        Self::Start(StartState)
    }
}

When a user sends a message to our bot and such a dialogue does not exist yet, a Dialogue::default() is invoked, which is a Dialogue::Start in this case. Every time a message is received, an associated dialogue is extracted and then passed to a corresponding subtransition function:

Dialogue::Start

(dialogue_bot/src/dialogue/states/start.rs)

// Imports are omitted...

pub struct StartState;

#[teloxide(subtransition)]
async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> TransitionOut<Dialogue> {
    cx.answer_str("Let's start! What's your full name?").await?;
    next(ReceiveFullNameState)
}
Dialogue::ReceiveFullName

(dialogue_bot/src/dialogue/states/receive_full_name.rs)

// Imports are omitted...

#[derive(Generic)]
pub struct ReceiveFullNameState;

#[teloxide(subtransition)]
async fn receive_full_name(
    state: ReceiveFullNameState,
    cx: TransitionIn,
    ans: String,
) -> TransitionOut<Dialogue> {
    cx.answer_str("How old are you?").await?;
    next(ReceiveAgeState::up(state, ans))
}
Dialogue::ReceiveAge

(dialogue_bot/src/dialogue/states/receive_age.rs)

// Imports are omitted...

#[derive(Generic)]
pub struct ReceiveAgeState {
    pub full_name: String,
}

#[teloxide(subtransition)]
async fn receive_age_state(
    state: ReceiveAgeState,
    cx: TransitionIn,
    ans: String,
) -> TransitionOut<Dialogue> {
    match ans.parse::<u8>() {
        Ok(ans) => {
            cx.answer_str("What's your location?").await?;
            next(ReceiveLocationState::up(state, ans))
        }
        _ => {
            cx.answer_str("Send me a number.").await?;
            next(state)
        }
    }
}
Dialogue::ReceiveLocation

(dialogue_bot/src/dialogue/states/receive_location.rs)

// Imports are omitted...

#[derive(Generic)]
pub struct ReceiveLocationState {
    pub full_name: String,
    pub age: u8,
}

#[teloxide(subtransition)]
async fn receive_location(
    state: ReceiveLocationState,
    cx: TransitionIn,
    ans: String,
) -> TransitionOut<Dialogue> {
    cx.answer_str(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans))
        .await?;
    exit()
}

All these subtransition functions accept a corresponding state (one of the many variants of Dialogue), a context, and a textual message. They return TransitionOut<Dialogue>, e.g. a mapping from <your state type> to Dialogue.

Finally, the main function looks like this:

(dialogue_bot/src/main.rs)

// Imports are omitted...

#[tokio::main]
async fn main() {
    teloxide::enable_logging!();
    log::info!("Starting dialogue_bot...");

    let bot = Bot::from_env();

    teloxide::dialogues_repl(bot, |message, dialogue| async move {
        handle_message(message, dialogue).await.expect("Something wrong with the bot!")
    })
    .await;
}

async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> {
    match cx.update.text_owned() {
        None => {
            cx.answer_str("Send me a text message.").await?;
            next(dialogue)
        }
        Some(ans) => dialogue.react(cx, ans).await,
    }
}

More examples!

Recommendations

  • Use this pattern:
#[tokio::main]
async fn main() {
    run().await;
}

async fn run() {
    // Your logic here...
}

Instead of this:

#[tokio::main]
async fn main() {
    // Your logic here...
}

The second one produces very strange compiler messages due to the #[tokio::main] macro. However, the examples in this README use the second variant for brevity.

Cargo features

  • redis-storage -- enables the Redis support.
  • sqlite-storage -- enables the Sqlite support.
  • cbor-serializer -- enables the CBOR serializer for dialogues.
  • bincode-serializer -- enables the Bincode serializer for dialogues.
  • frunk -- enables teloxide::utils::UpState, which allows mapping from a structure of field1, ..., fieldN to a structure of field1, ..., fieldN, fieldN+1.

FAQ

Q: Where I can ask questions?

A: Issues is a good place for well-formed questions, for example, about:

  • the library design;
  • enhancements;
  • bug reports;
  • ...

If you can't compile your bot due to compilation errors and need quick help, feel free to ask in our official Telegram group.

Q: Do you support the Telegram API for clients?

A: No, only the bots API.

Q: Why Rust?

A: Most programming languages have their own implementations of Telegram bots frameworks, so why not Rust? We think Rust provides a good enough ecosystem and the language for it to be suitable for writing bots.

UPD: The current design relies on wide and deep trait bounds, thereby increasing cognitive complexity. It can be avoided using mux-stream, but currently the stable Rust channel doesn't support necessary features to use mux-stream conveniently. Furthermore, the mux-stream could help to make a library out of teloxide, not a framework, since the design in this case could be defined by just combining streams of updates.

Q: Can I use webhooks?

A: teloxide doesn't provide special API for working with webhooks due to their nature with lots of subtle settings. Instead, you should setup your webhook by yourself, as shown in examples/ngrok_ping_pong_bot and examples/heroku_ping_pong_bot.

Associated links:

Q: Can I use different loggers?

A: Yes. You can setup any logger, for example, fern, e.g. teloxide has no specific requirements as it depends only on log. Remember that enable_logging! and enable_logging_with_filter! are just optional utilities.

Community bots

Feel free to push your own bot into our collection!

Contributing

See CONRIBUTING.md.

Comments
  • `repl_with_listener` breaks when using `Arc<DashMap>`

    `repl_with_listener` breaks when using `Arc`

    I tried this code:

    use teloxide::{
        prelude::{Message, Bot, AutoSend, Requester, RequesterExt, Update},
        dispatching::{stop_token::AsyncStopToken, update_listeners, update_listeners::StatefulListener,},
        respond,
        requests::ResponseResult,
        types::ChatId
    };
    use dashmap::DashMap;
    use tokio::{sync::Mutex, time::Instant, sync::mpsc};
    use reqwest::{StatusCode, Url};
    use std::{convert::Infallible, net::SocketAddr, sync::Arc};
    use tokio_stream::wrappers::UnboundedReceiverStream;
    use warp::Filter;
    
    #[derive(Debug, Clone)]
    struct TextData{
        data: String,
        modified: Instant
    }
    
    #[tokio::main]
    async fn main() {
        dotenv::dotenv().ok();
        pretty_env_logger::init();
        log::info!("Starting bot...");
    
        let db = Arc::new(DashMap::new());
        let bot = Bot::from_env().auto_send();
    
        log::info!("Starting REPL...");
        teloxide::dispatching::repls::repl_with_listener(
            bot.clone(),
            move |msg: Message, bot: AutoSend<Bot>| main_handler(db.clone(), msg, bot),
            webhook(bot).await,
        )
        .await;
    }
    
    async fn main_handler(
        db: Arc<DashMap<ChatId, TextData>>,
        msg: Message,
        bot: AutoSend<Bot>,
    ) -> ResponseResult<()> {
        match (msg.text(), db.get(&msg.chat.id)) {
            (None, None) => {
                bot.send_message(msg.chat.id, "No text here  ●︿●").await?;
                log::info!("Current status is ---- ");
            }
            (Some(text), None) => {
                bot.send_message(msg.chat.id, "Storing your text").await?;
                db.insert(
                    msg.chat.id,
                    TextData {
                        data: text.to_string(),
                        modified: Instant::now(),
                    },
                );
                log::info!("Current status is {:?}", db.get(&msg.chat.id));    // This would look like `Some(Ref { k: 0x1665b9a7760, v: 0x1665b9a7768 })`
            }
            (None, Some(st)) => {
                bot.send_message(msg.chat.id, "No text here  ●︿●").await?;
                log::info!("Current status is {:?}", &st);
            }
            (Some(text), Some(_)) => {
                db.entry(msg.chat.id)
                    .and_modify(|text_data| text_data.change_content_to(text.to_string()));
                bot.send_message(msg.chat.id, "Data changed!").await?;
                log::info!("Current status is {:?}", db.get(&msg.chat.id));
            }
        };
        respond(())
    }
    
    async fn handle_rejection(error: warp::Rejection) -> Result<impl warp::Reply, Infallible> {
        log::error!("Cannot process the request due to: {:?}", error);
        Ok(StatusCode::INTERNAL_SERVER_ERROR)
    }
    
    fn stream_f<S, T>(state: &mut (S, T)) -> &mut S {
        &mut state.0
    }
    
    pub async fn webhook(bot: AutoSend<Bot>) -> impl update_listeners::UpdateListener<Infallible> {
        let url: Url = std::env::vars()
            .find(|v| v.0 == "WEBHOOK_URL")
            .unwrap()
            .1
            .parse()
            .unwrap();
        let addr = "127.0.0.1:88".to_string().parse::<SocketAddr>().unwrap();
    
        if let Err(error) = bot.set_webhook(url).await {
            panic!("[ {:?} ]", error)
        }
    
        let (sender, receiver) = mpsc::unbounded_channel();
    
        let server = warp::post()
            .and(warp::body::json())
            .map(move |update: Update| {
                sender
                    .send(Ok(update))
                    .expect("Cannot send an incoming update from the webhook");
                StatusCode::OK
            })
            .recover(handle_rejection);
    
        let (stop_token, stop_flag) = AsyncStopToken::new_pair();
        let server = warp::serve(server);
        let (_addr, fut) = server.bind_with_graceful_shutdown(addr, stop_flag);
    
        tokio::spawn(fut);
        let stream = UnboundedReceiverStream::new(receiver);
    
        StatefulListener::new(
            (stream, stop_token),
            stream_f,
            |state: &mut (_, AsyncStopToken)| state.1.clone(),
        )
    }
    
    

    I expected to see this happen: I needed to store some data during runtime, and be able to modify it, but repls don't work with borrows, so I used DashMap wrapped in Arcas was suggested by someone in teloxide telegram group.

    Instead, this happened: The bot broke completely.

    1. After any data was recieved and saved, bot stopped processing any messages
    2. Saved data was corrupted and looked like Some(Ref { k: 0x1665b9a7760, v: 0x1665b9a7768 })
    3. After first Ctrl + C bot didn't shutdown dispatcher successfully, after second it exit with exit code 0xc000013a I thought there might be some problems with concurrency, so I tried using Mutex and also tried changing type of stored data, but none of that worked. Running with --release or +nightly didn't change anything either.

    Am I misusing or misunderstanding something?

    Meta

    Dependencies tokio = { version = "1", features = ["full"] } tokio-stream = "0.1.8" warp = "0.3" teloxide = "0.9" reqwest = "0.11" log = "0.4" pretty_env_logger = "0.4" rand = "0.8" dashmap = "5.3.3" dotenv = "0.15.0"

    Windows 10 Home I used ngrok to get updates from telegram.

    question 
    opened by hardskulls 15
  • Add macros with common `derive`s

    Add macros with common `derive`s

    Add macros with common derives:

    • Debug
    • PartialEq
    • Eq (for types without f32),
    • Hash (for types without f32)
    • Clone
    • Deserialize
    • Serialize This (yet unnamed) macro can be used on payloads and types.

    Unresolved questions

    • How should we name the macro?
    • Is it possible to implement this with "macro-by-example"?
    enhancement good first issue 
    opened by WaffleLapkin 14
  • Throttling does not appear to work

    Throttling does not appear to work

    I don't think throttling is working or I am using it incorrectly. I create a bot with:

    let bot = Bot::new(token).throttle(Limits::default());
    

    And then something like this in an loop over different lat and lon:

       let m = bot.send_location(id, lat, lon);
       // Do other non-related stuff.
       m.send().await?;
    

    Note that id does not change, so this is not tracking confusion between names and numeric ids.

    I expected to see this happen:

    Throttling working / no Errors returned by ? and the sending slowing down based on the throttle result from the Telegram API.

    Instead, this happened:

    When I hit api rate limits I see a RetryAfter(i32) error and the Result failure / early return path taken

    Meta

    • I'm not sure how to help debug, but m is confirmed to be a ThrottlingRequest<JsonRequest<SendLocation>>>
    • teloxide version: both 0.5 and the new 0.6
    bug C-core 
    opened by LegNeato 13
  • Is it possible to run teloxide using one single thread

    Is it possible to run teloxide using one single thread

    I run some tests to make a simple server manager bot, but teloxide seems not to work properly on a poor server with one single thread, even when I modify tokio features and the thread flavor. Is there any method make it work properly ? Thank you !

    question 
    opened by hammerfunctor 12
  • `teloxide-core` v0.4.2 causes timeout errors when using polling

    `teloxide-core` v0.4.2 causes timeout errors when using polling

    After update tokio to 1.17 and teloxide to 0.7 in console output next message

    ERROR teloxide::error_handlers > An error from the update listener: Network(reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("api.telegram.org")), port: None, path: "/bot5232090378:AAE4Zb5eoDETkTJaPs5yxvw/GetUpdates", query: None, fragment: None }, source: TimedOut })

    Meta

    • teloxide version:
    bug blocked C-core 
    opened by steevgvik 11
  • bot.get_chat_administrators(chat_id) panic!

    bot.get_chat_administrators(chat_id) panic!

    I tried this code:

    async fn check_sender_is_admin(bot: &AutoSend<Bot>, chat_id: i64) -> bool {
        let chat_admin: Result<ChatMember, RequestError> = bot.get_chat_administrators(chat_id).await;
        let chat_admin = match chat_admin {
            Ok(admin) => admin,
            Err(ref error) => {
                log::error!("AppError::api: API error, details: {:?}", error);
                chat_admin.unwrap()
            }
        };
        println!("{}", chat_admin.user.id);
        return false;
    }
    

    I expected to see this happen: a struct like this JSON output

    {
      "ok": true,
      "result": [
        {
          "user": {
            "id": "XXXXXX",
            "is_bot": false,
            "first_name": "XXXXXX",
            "username": "XXXXXX",
            "language_code": "en"
          },
          "status": "creator",
          "is_anonymous": false
        }
      ]
    }
    

    Instead, this happened:

    thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidJson(Error("data did not match any variant of untagged enum TelegramResponse", line: 0, column: 0))'
    

    Meta

    • teloxide version: 0.4.0
    • rustc version:
        rustc 1.51.0 (2fd73fabe 2021-03-23)
      	binary: rustc
      	commit-hash: 2fd73fabe469357a12c2c974c140f67e7cdd76d0
        commit-date: 2021-03-23
      	host: aarch64-apple-darwin
      	release: 1.51.0
      	LLVM version: 11.0.1
      
    bug 
    opened by Garfield550 11
  • DefaultParseMode didn't derive Clone

    DefaultParseMode didn't derive Clone

    I've changed my code to this after BotBuilder was removed. It seems that Dispatcher requires the bot to derive Clone. BTW AutoSend does derive Clone. So I think it might be a left out?

    let bot = Bot::new(token).parse_mode(teloxide::types::ParseMode::MarkdownV2);
    Dispatcher::new(bot)
        .dispatch()
        .await;
    

    Meta

    • teloxide version: 0.4.0
    • rustc version:
      rustc 1.51.0 (2fd73fabe 2021-03-23)
      binary: rustc
      commit-hash: 2fd73fabe469357a12c2c974c140f67e7cdd76d0
      commit-date: 2021-03-23
      host: x86_64-pc-windows-msvc
      release: 1.51.0
      LLVM version: 11.0.1
      
    bug 
    opened by runapp 11
  • add doc(cfg) attributes

    add doc(cfg) attributes

    Resolves #303 I tried building the docs both with nightly and stable, however in both cases I get errors for underlying crates:

    1. nightly
    RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --open --all-features
    
    error[E0658]: `#[doc(cfg)]` is experimental
      --> /home/elpiel/.cargo/registry/src/github.com-1ecc6299db9ec823/combine-4.3.2/src/stream/mod.rs:51:20
       |
    51 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
       |                    ^^^^^^^^^^^^^^^^^^^^^^^^^
       |
       = note: see issue #43781 <https://github.com/rust-lang/rust/issues/43781> for more information
       = help: add `#![feature(doc_cfg)]` to the crate attributes to enable
    
    error[E0658]: `#[doc(cfg)]` is experimental
      --> /home/elpiel/.cargo/registry/src/github.com-1ecc6299db9ec823/combine-4.3.2/src/stream/decoder.rs:49:20
       |
    49 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    
    .... and more
    
    1. stable
    RUSTDOCFLAGS="--cfg docsrs" cargo doc --open --all-features
    
    
    error[E0554]: `#![feature]` may not be used on the stable release channel
      --> /home/elpiel/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-io-0.3.7/src/lib.rs:29:21
       |
    29 | #![cfg_attr(docsrs, feature(doc_cfg))]
    
    hacktoberfest-approved 
    opened by elpiel 11
  • teloxide-macros 0.1.1 breaks compatibility

    teloxide-macros 0.1.1 breaks compatibility

    [  642s] error[E0049]: method `parse` has 1 type parameter but its trait declaration has 0 type parameters
    [  642s]    --> src/utils/command.rs:165:18
    [  642s]     |
    [  642s] 95  |     fn parse(s: &str) -> Option<(Self, Vec<&str>)>;
    [  642s]     |             - expected 0 type parameters
    [  642s] ...
    [  642s] 165 |         #[derive(BotCommand, Debug, PartialEq)]
    [  642s]     |                  ^^^^^^^^^^ found 1 type parameter
    [  642s] 
    [  642s] error[E0049]: method `parse` has 1 type parameter but its trait declaration has 0 type parameters
    [  642s]    --> src/utils/command.rs:180:18
    [  642s]     |
    [  642s] 95  |     fn parse(s: &str) -> Option<(Self, Vec<&str>)>;
    [  642s]     |             - expected 0 type parameters
    [  642s] ...
    [  642s] 180 |         #[derive(BotCommand, Debug, PartialEq)]
    [  642s]     |                  ^^^^^^^^^^ found 1 type parameter
    [  642s] 
    [  642s] error[E0049]: method `parse` has 1 type parameter but its trait declaration has 0 type parameters
    [  642s]    --> src/utils/command.rs:196:18
    [  642s]     |
    [  642s] 95  |     fn parse(s: &str) -> Option<(Self, Vec<&str>)>;
    [  642s]     |             - expected 0 type parameters
    [  642s] ...
    [  642s] 196 |         #[derive(BotCommand, Debug, PartialEq)]
    [  642s]     |                  ^^^^^^^^^^ found 1 type parameter
    [  642s] 
    [  642s] error[E0049]: method `parse` has 1 type parameter but its trait declaration has 0 type parameters
    [  642s]    --> src/utils/command.rs:220:18
    [  642s]     |
    [  642s] 95  |     fn parse(s: &str) -> Option<(Self, Vec<&str>)>;
    [  642s]     |             - expected 0 type parameters
    [  642s] ...
    [  642s] 220 |         #[derive(BotCommand, Debug, PartialEq)]
    [  642s]     |                  ^^^^^^^^^^ found 1 type parameter
    [  642s] 
    
    opened by ignatenkobrain 11
  • Feature Request: Constructs a `MessageEntityKind::TextMention` with user id only

    Feature Request: Constructs a `MessageEntityKind::TextMention` with user id only

    Introducing an approach allows to construct a MessageEntityKind::TextMention with user id only.

    Pros

    Currently, if we want to mention a user via MessageEntity instead of ParseMode, the way to do it is to use MessageEntityKind::TextLink("tg://user?id=<user_id>").

    However, this causes some problems:

    • Telegram treats the link as a normal URL and not as a user mention URL.
    • In different clients, such "user mention URLs as normal URLs" are handled differently:
      • In tdesktop, clicking on the URL does not give any response.
      • In Telegram iOS, clicking on the URL asks and then jumps to the user panel.

    I have tested, sending MessageEntity::TextMention with user id only is working as expected.

    {
    	"chat_id": <user_id>,
    	"text": "this is a test message",
    	"entities": [
    		{
    			"type": "text_mention",
    			"offset": 0,
    			"length": 4,
    			"user": {
    				"id": <user_id>
    			}
    		}
    	]
    }
    

    Cons

    I am not sure of the best solution to introduce this feature.

    Here's an immature and dirty idea, but it doesn't break the API: We can implement a method for MessageEntityKind that fills the fields other than user id with random values. It looks like this (not compiled, a better function name could be considered):

    impl MessageEntityKind {
        pub fn new_text_mention(user_id: i64) -> Self {
            Self::MessageEntityKind::TextMention {
                user: User {
                    id: user_id,
                    is_bot: true,
                    first_name: "blah",
                    last_name: None,
                    username: None,
                    language_code: None,
                },
            }
        }
    }
    

    After testing, this works as expected and Telegram seems to ignore the obviously wrong fields is_bot and first_name.

    {
    	"chat_id": <user_id>,
    	"text": "this is a test message",
    	"entities": [
    		{
    			"type": "text_mention",
    			"offset": 0,
    			"length": 4,
    			"user": {
    				"id": <user_id>,
    				"is_bot": true,
    				"first_name": "blah"
    			}
    		}
    	]
    }
    

    Other solutions than this may be API breaking.

    feature-request C-core 
    opened by SpriteOvO 10
  • Deadlock when using Throttle

    Deadlock when using Throttle

    I have a fairly complicated bot, with the rest of the app using Tower services.

    I notice occasionally the bot deadlocks. Looking with tokio-console, there are only 3 active tasks during the deadlock, and the only one running is from teloxide:

        1 ▶           256.9929s 241.0000µs  256.9927s 2     tokio::task <cargo>/teloxide-core-0.4.0/src/adaptors/throttle.rs:129:9
        2 ⏸           256.1417s 106.8750ms  256.0348s 775   tokio::task <cargo>/hyper-0.14.16/src/common/exec.rs:49:21
        3 ⏸           256.1403s  69.9860ms  256.0703s 515   tokio::task <cargo>/hyper-0.14.16/src/common/exec.rs:49:21
    

    In Activity Monitor the process is stuck on 100% CPU. I have no code that uses threading, only async/tokio so I am fairly sure the cause is throttle but haven't really had to debug a deadlock in a while.

    I know this isn't the greatest bug report, let me know what I can do to debug.

    bug C-core 
    opened by LegNeato 9
  • Feature Request: Photo with spoiler animation covered

    Feature Request: Photo with spoiler animation covered

    Telegram Bot API introduced has_spoiler field for InputMediaPhoto in Bot API 6.4. It is necessary for bot to send some NSFW content to normal group. But currently teloxide's SendPhotoSetters doesn't implement this feature.

    C-core 
    opened by Avimitin 0
  • Uses full qualified names in macros code

    Uses full qualified names in macros code

    I have this problem, because I usually define a custom Result type.

    https://doc.rust-lang.org/reference/procedural-macros.html#procedural-macro-hygiene

    opened by sanpii 0
  • Overhaul `teloxide_core::types::chat_member` module

    Overhaul `teloxide_core::types::chat_member` module

    Closes #781

    In addition to the changes described in #781, this PR deprecates ChatMemberKind::can_manage_topics method that had been added after the issue was created.

    Note that replacing Option<bool> is a breaking change. Usually, when breaking changes occur we strive to make them all at once. So maybe it makes sense to not only deprecate methods but remove them? I don't know what deprecations retention policy teloxide uses 😅

    opened by Veetaha 0
  • Documentation is broken on docs.rs

    Documentation is broken on docs.rs

    The documentation doesn't pass the build on docs.rs. Here are the build logs:

    [INFO] [stderr] error[E0658]: `#[track_caller]` on closures is currently unstable
    [INFO] [stderr]   --> /opt/rustwide/cargo-home/registry/src/github.com-1ecc6299db9ec823/dptree-0.3.0/src/handler/endpoint.rs:23:9
    [INFO] [stderr]    |
    [INFO] [stderr] 23 | /         async move {
    [INFO] [stderr] 24 | |             let f = f.inject(&x);
    [INFO] [stderr] 25 | |             f().map(ControlFlow::Break).await
    [INFO] [stderr] 26 | |         }
    [INFO] [stderr]    | |_________^
    [INFO] [stderr]    |
    [INFO] [stderr]    = note: see issue #87417 <https://github.com/rust-lang/rust/issues/87417> for more information
    [INFO] [stderr]    = help: add `#![feature(closure_track_caller)]` to the crate attributes to enable
    

    It looks like a bug in the toolchain, because I don't see track_caller on top of a closure there.

    Note, however, that #[track_caller] is useless for code that occurs inside of the closures, because it's not propagated to the closures used inside the function annotated with #[track_caller]. It also doesn't work with async functions at all (because it annotates the future constructor functions, not the future's poll() method). So I recommend just to get rid of that annotation.

    Meta

    Versions of teloxide from 0.11.0 to 0.11.3 (latest as for now): image

    bug 
    opened by Veetaha 1
  • Add support for unix file system sockets.

    Add support for unix file system sockets.

    Adds support for unix file system sockets next to tcp sockets.

    These can be used to leverage file system permissions for access control and they do not add tcp overhead to communication.

    C-main 
    opened by schaschko 1
  • API Unknown Error - Request Entity Too Large

    API Unknown Error - Request Entity Too Large

    Using the latest teloxide, it returns an Unknown error when uploading a file that was 101MB, Telegram lays out how to upload properly but it said to open an Issue in the docs for an unknown error.

    ERROR teloxide::error_handlers > Error: Api(Unknown("Request Entity Too Large"))

    bug good first issue FIXME Unknown API error 
    opened by csfore 2
Releases(v0.11.3)
A Rust web framework

cargonauts - a Rust web framework Documentation cargonauts is a Rust web framework intended for building maintainable, well-factored web apps. This pr

null 179 Dec 25, 2022
Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.

Actix 16.2k Jan 2, 2023
A rust web framework with safety and speed in mind.

darpi A web api framework with speed and safety in mind. One of the big goals is to catch all errors at compile time, if possible. The framework uses

null 32 Apr 11, 2022
A web framework for Rust.

Rocket Rocket is an async web framework for Rust with a focus on usability, security, extensibility, and speed. #[macro_use] extern crate rocket; #[g

Sergio Benitez 19.4k Jan 4, 2023
Rust / Wasm framework for building client web apps

Yew Rust / Wasm client web app framework Documentation (stable) | Documentation (latest) | Examples | Changelog | Roadmap | 简体中文文档 | 繁體中文文檔 | ドキュメント A

Yew Stack 25.8k Jan 2, 2023
Thruster - An fast and intuitive rust web framework

A fast, middleware based, web framework written in Rust

null 913 Dec 27, 2022
A full-featured and easy-to-use web framework with the Rust programming language.

Poem Framework A program is like a poem, you cannot write a poem without writing it. --- Dijkstra A full-featured and easy-to-use web framework with t

Poem Web 2.2k Jan 6, 2023
Perseus is a blazingly fast frontend web development framework built in Rust with support for major rendering strategies

Perseus is a blazingly fast frontend web development framework built in Rust with support for major rendering strategies, reactivity without a virtual DOM, and extreme customizability

arctic_hen7 1.2k Jan 8, 2023
Seed is a Rust front-end framework for creating fast and reliable web apps with an Elm-like architecture.

Seed is a Rust front-end framework for creating fast and reliable web apps with an Elm-like architecture.

null 3.6k Jan 6, 2023
Experiments with Rust CRDTs using Tokio web application framework Axum.

crdt-genome Synopsis Experiments with Rust CRDTs using Tokio web application framework Axum. Background Exploring some ideas of Martin Kleppmann, part

dougfort 3 Mar 18, 2022
A framework independent animation library for rust, works nicely with Iced and the others

anim A framework independent animation library for rust, works nicely with Iced and the others. Showcase How to install? Include anim in your Cargo.to

joylei 37 Nov 10, 2022
Implementation of the RealWorld backend API spec in Actix, Rust's powerful actor system and most fun web framework.

Actix codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld spec and API. ❗ (2021/05/13) This cod

Allen 475 Jan 2, 2023
Super Fast & High Performance minimalist web framework for rust

Super Fast & High Performance minimalist web framework for rust

null 6 Oct 12, 2022
A web framework for Rust programing language

kalgan A web framework for Rust programing language. Getting Started Create your project with cargo: cargo new project Add the dependency in Cargo.tom

Eduardo Casas 4 Jun 9, 2022
Demo of Rust and axum web framework

Demo of Rust and axum web framework Demonstration of: Rust: programming language that focuses on reliability and stability. axum: web framework that f

Joel Parker Henderson 115 Dec 29, 2022
⚡Rust Serverless Framework

Zapp Rust Serverless Framework The Zapp project was launched with the goal of reducing software development, operation and maintenance costs. Build Se

Epics 6 Dec 23, 2022
Sauron is an html web framework for building web-apps. It is heavily inspired by elm.

sauron Guide Sauron is an web framework for creating fast and interactive client side web application, as well as server-side rendering for back-end w

Jovansonlee Cesar 1.7k Dec 26, 2022
A super-easy, composable, web server framework for warp speeds.

warp A super-easy, composable, web server framework for warp speeds. The fundamental building block of warp is the Filter: they can be combined and co

Sean McArthur 7.5k Jan 2, 2023
Grape is a REST-like API framework for Ruby

Grape is a REST-like API framework for Ruby. It's designed to run on Rack or complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily develop RESTful APIs. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.

Ruby Grape 9.7k Jan 2, 2023