Merge pull request #677 from teloxide/update_dptree

Bump version -> 0.10

Former-commit-id: 85a11a6064
This commit is contained in:
Waffle Maybe 2022-07-21 14:58:15 +04:00 committed by GitHub
commit 80bf7c6b16
21 changed files with 494 additions and 210 deletions

View file

@ -123,6 +123,7 @@ jobs:
args: --tests --verbose ${{ matrix.features }} args: --tests --verbose ${{ matrix.features }}
- name: Test documentation tests - name: Test documentation tests
if: ${{ matrix.rust != 'msrv' }}
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test

View file

@ -6,23 +6,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## unreleased ## unreleased
## 0.10.0 - 2022-07-21
### Added ### Added
- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks - Security checks based on `secret_token` param of `set_webhook` to built-in webhooks.
- `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}` - `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}`.
- `DispatcherBuilder::enable_ctrlc_handler` method.
### Fixed ### Fixed
- `Dispatcher` no longer "leaks" memory for every inactive user ([PR 657](https://github.com/teloxide/teloxide/pull/657)). - `Dispatcher` no longer "leaks" memory for every inactive user ([PR 657](https://github.com/teloxide/teloxide/pull/657)).
- Allow specifying a path to a custom command parser in `parse_with` ([issue 668](https://github.com/teloxide/teloxide/issues/668)).
### Changed ### Changed
- Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**]. - Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**].
- `dispatching::update_listeners::{polling_default, polling}` now return a named, `Polling<_>` type - `dispatching::update_listeners::{polling_default, polling}` now return a named, `Polling<_>` type.
- Update teloxide-core to v0.7.0 with Bot API 6.1 support, see [its changelog][core07c] for more information [**BC**].
[core07c]: https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md#070---2022-07-19
### Deprecated ### Deprecated
- `dispatching::update_listeners::polling` - The `dispatching::update_listeners::polling` function.
- `Dispatcher::setup_ctrlc_handler` method.
## 0.9.2 - 2022-06-07 ## 0.9.2 - 2022-06-07

View file

@ -1,6 +1,6 @@
[package] [package]
name = "teloxide" name = "teloxide"
version = "0.9.2" version = "0.10.0"
edition = "2021" edition = "2021"
description = "An elegant Telegram bots framework for Rust" description = "An elegant Telegram bots framework for Rust"
repository = "https://github.com/teloxide/teloxide" repository = "https://github.com/teloxide/teloxide"
@ -57,17 +57,21 @@ full = [
] ]
[dependencies] [dependencies]
#teloxide-core = { version = "0.6.0", default-features = false } teloxide-core = { version = "0.7.0", default-features = false }
teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false } teloxide-macros = { version = "0.6.3", optional = true }
teloxide-macros = { version = "0.6.2", optional = true }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
dptree = "0.2.1" dptree = "0.3.0"
# These lines are used only for development.
# teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false }
# teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", rev = "44d91c5", optional = true }
# dptree = { git = "https://github.com/teloxide/dptree", rev = "df578e4" }
tokio = { version = "1.8", features = ["fs"] } tokio = { version = "1.8", features = ["fs"] }
tokio-util = "0.6" tokio-util = "0.7"
tokio-stream = "0.1.8" tokio-stream = "0.1.8"
url = "2.2.2" url = "2.2.2"
@ -82,17 +86,17 @@ pin-project = "1.0"
serde_with_macros = "1.4" serde_with_macros = "1.4"
aquamarine = "0.1.11" aquamarine = "0.1.11"
sqlx = { version = "0.5", optional = true, default-features = false, features = [ sqlx = { version = "0.6", optional = true, default-features = false, features = [
"runtime-tokio-native-tls", "runtime-tokio-native-tls",
"macros", "macros",
"sqlite", "sqlite",
] } ] }
redis = { version = "0.20", features = ["tokio-comp"], optional = true } redis = { version = "0.21", features = ["tokio-comp"], optional = true }
serde_cbor = { version = "0.11", optional = true } serde_cbor = { version = "0.11", optional = true }
bincode = { version = "1.3", optional = true } bincode = { version = "1.3", optional = true }
axum = { version = "0.4.8", optional = true } axum = { version = "0.5.13", optional = true }
tower = { version = "0.4.12", optional = true } tower = { version = "0.4.12", optional = true }
tower-http = { version = "0.2.5", features = ["trace"], optional = true } tower-http = { version = "0.3.4", features = ["trace"], optional = true }
rand = { version = "0.8.5", optional = true } rand = { version = "0.8.5", optional = true }
[dev-dependencies] [dev-dependencies]
@ -101,7 +105,7 @@ pretty_env_logger = "0.4.0"
serde = "1" serde = "1"
serde_json = "1" serde_json = "1"
tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] } tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] }
reqwest = "0.10.4" reqwest = "0.11.11"
chrono = "0.4" chrono = "0.4"
tokio-stream = "0.1" tokio-stream = "0.1"

View file

@ -1,6 +1,47 @@
This document describes breaking changes of `teloxide` crate, as well as the ways to update code. This document describes breaking changes of `teloxide` crate, as well as the ways to update code.
Note that the list of required changes is not fully exhaustive and it may lack something in rare cases. Note that the list of required changes is not fully exhaustive and it may lack something in rare cases.
## 0.9 -> 0.10
### core
We've added some convenience functions to `InlineKeyboardButton` so it's easier to construct it. Consider using them instead of variants:
```diff
-InlineKeyboardButton::new("text", InlineKeyboardButtonKind::Url(url))
+InlineKeyboardButton::url("text", url)
```
`file_size` fields are now `u32`, you may need to update your code accordingly:
```diff
-let file_size: u64 = audio.file_size?;
+let file_size: u32 = audio.file_size;
```
Some places now use `FileMeta` instead of `File`, you may need to change types.
`Sticker` and `StickerSet` now has a `kind` field instead of `is_animated` and `is_video`:
```diff
+use teloxide::types::StickerKind::*;
-match () {
+match sticker.kind {
- _ if sticker.is_animated => /* handle animated */,
+ Animated => /* handle animated */,
- _ if sticker.is_video => /* handle video */,
+ Video => /* handle video */,
- _ => /* handle normal */,
+ Webp => /* handle normal */,
}
```
### teloxide
Teloxide itself doesn't have any major API changes.
Note however that some function were deprecated:
- Instead of `dispatching::update_listeners::polling` use `polling_builder`
- Instead of `Dispatcher::setup_ctrlc_handler` use `DispatcherBuilder::enable_ctrlc_handler`
## 0.7 -> 0.8 ## 0.7 -> 0.8
### core ### core
@ -8,7 +49,7 @@ Note that the list of required changes is not fully exhaustive and it may lack s
`user.id` now uses `UserId` type, `ChatId` now represents only _chat id_, not channel username, all `chat_id` function parameters now accept `Recipient` (if they allow for channel usernames). `user.id` now uses `UserId` type, `ChatId` now represents only _chat id_, not channel username, all `chat_id` function parameters now accept `Recipient` (if they allow for channel usernames).
If you used to work with chat/user ids (for example saving them to a database), you may need to change your code to account for new types. Some examples how that may look like: If you used to work with chat/user ids (for example saving them to a database), you may need to change your code to account for new types. Some examples how that may look like:
```diff, ```diff
-let user_id: i64 = user.id; -let user_id: i64 = user.id;
+let UserId(user_id) = user.id; +let UserId(user_id) = user.id;
db.save(user_id, ...); db.save(user_id, ...);

View file

@ -1,6 +1,4 @@
> [v0.7 -> v0.8 migration guide >>](MIGRATION_GUIDE.md#07---08) > [v0.9 -> v0.10 migration guide >>](MIGRATION_GUIDE.md#09---010)
> `teloxide-core` versions less that `0.4.5` (`teloxide` versions less than 0.7.3) have a low-severity security vulnerability, [learn more >>](https://github.com/teloxide/teloxide/discussions/574)
<div align="center"> <div align="center">
<img src="../../raw/master/ICON.png" width="250"/> <img src="../../raw/master/ICON.png" width="250"/>
@ -15,7 +13,7 @@
<img src="https://img.shields.io/crates/v/teloxide.svg"> <img src="https://img.shields.io/crates/v/teloxide.svg">
</a> </a>
<a href="https://core.telegram.org/bots/api"> <a href="https://core.telegram.org/bots/api">
<img src="https://img.shields.io/badge/API%20coverage-Up%20to%206.0%20(inclusively)-green.svg"> <img src="https://img.shields.io/badge/API%20coverage-Up%20to%206.1%20(inclusively)-green.svg">
</a> </a>
<a href="https://t.me/teloxide"> <a href="https://t.me/teloxide">
<img src="https://img.shields.io/badge/support-t.me%2Fteloxide-blueviolet"> <img src="https://img.shields.io/badge/support-t.me%2Fteloxide-blueviolet">
@ -72,7 +70,7 @@ $ rustup override set nightly
5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`: 5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]
teloxide = { version = "0.9", features = ["macros", "auto-send"] } teloxide = { version = "0.10", features = ["macros", "auto-send"] }
log = "0.4" log = "0.4"
pretty_env_logger = "0.4" pretty_env_logger = "0.4"
tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
@ -225,8 +223,8 @@ async fn main() {
), ),
) )
.dependencies(dptree::deps![InMemStorage::<State>::new()]) .dependencies(dptree::deps![InMemStorage::<State>::new()])
.enable_ctrlc_handler()
.build() .build()
.setup_ctrlc_handler()
.dispatch() .dispatch()
.await; .await;
} }
@ -321,11 +319,7 @@ A: No, only the bots API.
**Q: Can I use webhooks?** **Q: Can I use webhooks?**
A: teloxide doesn't provide a 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`](examples/ngrok_ping_pong.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong.rs). A: You can! Teloxide has a built-in support for webhooks in `dispatching::update_listeners::webhooks` module. See how it's used in [`examples/ngrok_ping_pong_bot`](examples/ngrok_ping_pong.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong.rs).
Associated links:
- [Marvin's Marvellous Guide to All Things Webhook](https://core.telegram.org/bots/webhooks)
- [Using self-signed certificates](https://core.telegram.org/bots/self-signed)
**Q: Can I handle both callback queries and messages within a single dialogue?** **Q: Can I handle both callback queries and messages within a single dialogue?**
@ -335,32 +329,32 @@ A: Yes, see [`examples/purchase.rs`](examples/purchase.rs).
Feel free to propose your own bot to our collection! Feel free to propose your own bot to our collection!
- [WaffleLapkin/crate_upd_bot](https://github.com/WaffleLapkin/crate_upd_bot) — A bot that notifies about crate updates. - [`raine/tgreddit`](https://github.com/raine/tgreddit) — A bot that sends the top posts of your favorite subreddits to Telegram.
- [mxseev/logram](https://github.com/mxseev/logram) — Utility that takes logs from anywhere and sends them to Telegram. - [`magnickolas/remindee-bot`](https://github.com/magnickolas/remindee-bot) — Telegram bot for managing reminders.
- [alexkonovalov/PedigreeBot](https://github.com/alexkonovalov/PedigreeBot) — A Telegram bot for building family trees. - [`WaffleLapkin/crate_upd_bot`](https://github.com/WaffleLapkin/crate_upd_bot) — A bot that notifies about crate updates.
- [Hermitter/tepe](https://github.com/Hermitter/tepe) — A CLI to command a bot to send messages and files over Telegram. - [`mattrighetti/GroupActivityBot`](https://github.com/mattrighetti/group-activity-bot-rs) — Telegram bot that keeps track of user activity in groups.
- [mattrighetti/GroupActivityBot](https://github.com/mattrighetti/group-activity-bot-rs) — Telegram bot that keeps track of user activity in groups. - [`alenpaul2001/AurSearchBot`](https://gitlab.com/alenpaul2001/aursearchbot) — Telegram bot for searching in Arch User Repository (AUR).
- [mattrighetti/libgen-bot-rs](https://github.com/mattrighetti/libgen-bot-rs) — Telgram bot to interface with libgen - [`ArtHome12/vzmuinebot`](https://github.com/ArtHome12/vzmuinebot) — Telegram bot for food menu navigate.
- [dracarys18/grpmr-rs](https://github.com/dracarys18/grpmr-rs) — A Telegram group manager bot with variety of extra features. - [`studiedlist/EddieBot`](https://gitlab.com/studiedlist/eddie-bot) — Chatting bot with several entertainment features.
- [steadylearner/subreddit_reader](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader) — A bot that shows the latest posts at Rust subreddit. - [`modos189/tg_blackbox_bot`](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project.
- [myblackbeard/basketball-betting-bot](https://github.com/myblackbeard/basketball-betting-bot) — The bot lets you bet on NBA games against your buddies. - [`0xNima/spacecraft`](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces.
- [ArtHome12/vzmuinebot](https://github.com/ArtHome12/vzmuinebot) — Telegram bot for food menu navigate. - [`0xNima/Twideo`](https://github.com/0xNima/Twideo) — Simple Telegram Bot for downloading videos from Twitter via their links.
- [ArtHome12/cognito_bot](https://github.com/ArtHome12/cognito_bot) — The bot is designed to anonymize messages to a group. - [`mattrighetti/libgen-bot-rs`](https://github.com/mattrighetti/libgen-bot-rs) — Telgram bot to interface with libgen.
- [pro-vim/tg-vimhelpbot](https://github.com/pro-vim/tg-vimhelpbot) — Link `:help` for Vim in Telegram. - [`zamazan4ik/npaperbot-telegram`](https://github.com/zamazan4ik/npaperbot-telegram) — Telegram bot for searching via C++ proposals.
- [sschiz/janitor-bot](https://github.com/sschiz/janitor-bot) — A bot that removes users trying to join to a chat that is designed for comments.
- [slondr/BeerHolderBot](https://gitlab.com/slondr/BeerHolderBot) — A bot that holds your beer. <details>
- [MustafaSalih1993/Miss-Vodka-Telegram-Bot](https://github.com/MustafaSalih1993/Miss-Vodka-Telegram-Bot) — A Telegram bot written in rust using "Teloxide" library. <summary>Show bots using teloxide older than v0.6.0</summary>
- [x13a/tg-prompt](https://github.com/x13a/tg-prompt) — Telegram prompt.
- [magnickolas/remindee-bot](https://github.com/magnickolas/remindee-bot) — Telegram bot for managing reminders. - [`mxseev/logram`](https://github.com/mxseev/logram) — Utility that takes logs from anywhere and sends them to Telegram.
- [cyberknight777/knight-bot](https://gitlab.com/cyberknight777/knight-bot) — A Telegram bot with variety of fun features. - [`alexkonovalov/PedigreeBot`](https://github.com/alexkonovalov/PedigreeBot) — A Telegram bot for building family trees.
- [wa7sa34cx/the-black-box-bot](https://github.com/wa7sa34cx/the-black-box-bot) — This is the Black Box Telegram bot. You can hold any items in it. - [`Hermitter/tepe`](https://github.com/Hermitter/tepe) — A CLI to command a bot to send messages and files over Telegram.
- [crapstone/hsctt](https://codeberg.org/crapstones-bots/hsctt) — A Telegram bot that searches for HTTP status codes in all messages and replies with the text form. - [`myblackbeard/basketball-betting-bot`](https://github.com/myblackbeard/basketball-betting-bot) — The bot lets you bet on NBA games against your buddies.
- [alenpaul2001/AurSearchBot](https://gitlab.com/alenpaul2001/aursearchbot) — Telegram bot for searching AUR in inline mode. - [`dracarys18/grpmr-rs`](https://github.com/dracarys18/grpmr-rs) — Modular Telegram Group Manager Bot written in Rust.
- [studiedlist/EddieBot](https://gitlab.com/studiedlist/eddie-bot) — Chatting bot with several entertainment features. - [`ArtHome12/vzmuinebot`](https://github.com/ArtHome12/vzmuinebot) — Telegram bot for food menu navigate.
- [modos189/tg_blackbox_bot](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project. This bot in Docker from scratch container. - [`ArtHome12/cognito_bot`](https://github.com/ArtHome12/cognito_bot) — The bot is designed to anonymize messages to a group.
- [0xNima/spacecraft](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces. - [`crapstone/hsctt`](https://codeberg.org/crapstones-bots/hsctt) — A bot that converts HTTP status codes into text.
- [0xNima/Twideo](https://github.com/0xNima/Twideo) — Telegram Bot for downloading videos from Twitter via their links, as well as converting tweets to telegram messages.
- [raine/tgreddit](https://github.com/raine/tgreddit) — A bot that sends the top posts of your favorite subreddits to Telegram. </details>
## Contributing ## Contributing

View file

@ -30,7 +30,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
.branch(Update::filter_callback_query().endpoint(callback_handler)) .branch(Update::filter_callback_query().endpoint(callback_handler))
.branch(Update::filter_inline_query().endpoint(inline_query_handler)); .branch(Update::filter_inline_query().endpoint(inline_query_handler));
Dispatcher::builder(bot, handler).build().setup_ctrlc_handler().dispatch().await; Dispatcher::builder(bot, handler).enable_ctrlc_handler().build().dispatch().await;
Ok(()) Ok(())
} }

View file

@ -54,8 +54,8 @@ async fn main() {
Dispatcher::builder(bot, handler) Dispatcher::builder(bot, handler)
.dependencies(dptree::deps![storage]) .dependencies(dptree::deps![storage])
.enable_ctrlc_handler()
.build() .build()
.setup_ctrlc_handler()
.dispatch() .dispatch()
.await; .await;
} }

View file

@ -51,8 +51,8 @@ async fn main() {
), ),
) )
.dependencies(dptree::deps![InMemStorage::<State>::new()]) .dependencies(dptree::deps![InMemStorage::<State>::new()])
.enable_ctrlc_handler()
.build() .build()
.setup_ctrlc_handler()
.dispatch() .dispatch()
.await; .await;
} }

View file

@ -87,8 +87,8 @@ async fn main() {
.error_handler(LoggingErrorHandler::with_custom_text( .error_handler(LoggingErrorHandler::with_custom_text(
"An error has occurred in the dispatcher", "An error has occurred in the dispatcher",
)) ))
.enable_ctrlc_handler()
.build() .build()
.setup_ctrlc_handler()
.dispatch() .dispatch()
.await; .await;
} }

View file

@ -60,5 +60,5 @@ async fn main() {
}, },
)); ));
Dispatcher::builder(bot, handler).build().setup_ctrlc_handler().dispatch().await; Dispatcher::builder(bot, handler).enable_ctrlc_handler().build().dispatch().await;
} }

View file

@ -13,10 +13,7 @@
// ``` // ```
use teloxide::{ use teloxide::{
dispatching::{ dispatching::{dialogue, dialogue::InMemStorage, UpdateHandler},
dialogue::{self, InMemStorage},
UpdateHandler,
},
prelude::*, prelude::*,
types::{InlineKeyboardButton, InlineKeyboardMarkup}, types::{InlineKeyboardButton, InlineKeyboardMarkup},
utils::command::BotCommands, utils::command::BotCommands,
@ -55,29 +52,30 @@ async fn main() {
Dispatcher::builder(bot, schema()) Dispatcher::builder(bot, schema())
.dependencies(dptree::deps![InMemStorage::<State>::new()]) .dependencies(dptree::deps![InMemStorage::<State>::new()])
.enable_ctrlc_handler()
.build() .build()
.setup_ctrlc_handler()
.dispatch() .dispatch()
.await; .await;
} }
fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> { fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
use dptree::case;
let command_handler = teloxide::filter_command::<Command, _>() let command_handler = teloxide::filter_command::<Command, _>()
.branch( .branch(
dptree::case![State::Start] case![State::Start]
.branch(dptree::case![Command::Help].endpoint(help)) .branch(case![Command::Help].endpoint(help))
.branch(dptree::case![Command::Start].endpoint(start)), .branch(case![Command::Start].endpoint(start)),
) )
.branch(dptree::case![Command::Cancel].endpoint(cancel)); .branch(case![Command::Cancel].endpoint(cancel));
let message_handler = Update::filter_message() let message_handler = Update::filter_message()
.branch(command_handler) .branch(command_handler)
.branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name)) .branch(case![State::ReceiveFullName].endpoint(receive_full_name))
.branch(dptree::endpoint(invalid_state)); .branch(dptree::endpoint(invalid_state));
let callback_query_handler = Update::filter_callback_query().chain( let callback_query_handler = Update::filter_callback_query().branch(
dptree::case![State::ReceiveProductChoice { full_name }] case![State::ReceiveProductChoice { full_name }].endpoint(receive_product_selection),
.endpoint(receive_product_selection),
); );
dialogue::enter::<Update, InMemStorage<State>, State, _>() dialogue::enter::<Update, InMemStorage<State>, State, _>()

View file

@ -27,8 +27,8 @@ async fn main() {
Dispatcher::builder(bot, handler) Dispatcher::builder(bot, handler)
// Pass the shared state to the handler as a dependency. // Pass the shared state to the handler as a dependency.
.dependencies(dptree::deps![messages_total]) .dependencies(dptree::deps![messages_total])
.enable_ctrlc_handler()
.build() .build()
.setup_ctrlc_handler()
.dispatch() .dispatch()
.await; .await;
} }

View file

@ -1,76 +1,198 @@
//! An update dispatching model based on [`dptree`]. //! An update dispatching model based on [`dptree`].
//! //!
//! In teloxide, updates are dispatched by a pipeline. The central type is //! In teloxide, update dispatching is declarative: it takes the form of a
//! [`dptree::Handler`] -- it represents a handler of an update; since the API //! [chain of responsibility] pattern enriched with a number of combinator
//! is highly declarative, you can combine handlers with each other via such //! functions, which together form an instance of the [`dptree::Handler`] type.
//! methods as [`dptree::Handler::chain`] and [`dptree::Handler::branch`]. The
//! former method pipes one handler to another one, whilst the latter creates a
//! new node, as communicated by the name. For more information, please refer to
//! the documentation of [`dptree`].
//! //!
//! The pattern itself is called [chain of responsibility], a well-known design //! Take [`examples/purchase.rs`] as an example of dispatching logic. First, we
//! technique across OOP developers. But unlike typical object-oriented design, //! define a type named `State` to represent the current state of a dialogue:
//! we employ declarative FP-style functions like [`dptree::filter`],
//! [`dptree::filter_map`], and [`dptree::endpoint`]; these functions create
//! special forms of [`dptree::Handler`]; for more information, please refer to
//! their respective documentation. Each of these higher-order functions accept
//! a closure that is made into a handler -- this closure can take any
//! additional parameters, which must be supplied while creating [`Dispatcher`]
//! (see [`DispatcherBuilder::dependencies`]).
//!
//! The [`Dispatcher`] type puts all these things together: it only provides
//! [`Dispatcher::dispatch`] and a handful of other methods. Once you call
//! `.dispatch()`, it will retrieve updates from the Telegram server and pass
//! them to your handler, which is a parameter of [`Dispatcher::builder`].
//!
//! Let us look at a simple example:
//!
//!
//! ([Full](https://github.com/teloxide/teloxide/blob/master/examples/shared_state.rs))
//! //!
//! ```no_run //! ```no_run
//! // TODO: examples/purchase.rs //! #[derive(Clone, Default)]
//! fn main() {} //! pub enum State {
//! #[default]
//! Start,
//! ReceiveFullName,
//! ReceiveProductChoice {
//! full_name: String,
//! },
//! }
//! ``` //! ```
//! //!
//! 1. First, we create the bot: `let bot = Bot::from_env().auto_send()`. //! Then, we define a type `Command` to represent user commands such as
//! 2. Then we construct an update handler. While it is possible to handle all //! `/start` or `/help`:
//! kinds of [`crate::types::Update`], here we are only interested in
//! [`crate::types::Message`]: [`UpdateFilterExt::filter_message`] create a
//! handler object which filters all messages out of a generic update.
//! 3. By doing `.endpoint(...)` we set up a custom handling closure that
//! receives `msg: Message` and `bot: AutoSend<Bot>`. There are
//! called dependencies: `msg` is supplied by
//! [`UpdateFilterExt::filter_message`], while `bot` is supplied by
//! [`Dispatcher`].
//! //!
//! That being said, if we receive a message, the dispatcher will call our //! ```no_run
//! handler, but if we receive something other than a message (e.g., a channel //! # use teloxide::utils::command::BotCommands;
//! post), you will see an unhandled update notice in your terminal. //! #[derive(BotCommands, Clone)]
//! #[command(rename = "lowercase", description = "These commands are supported:")]
//! enum Command {
//! #[command(description = "display this text.")]
//! Help,
//! #[command(description = "start the purchase procedure.")]
//! Start,
//! #[command(description = "cancel the purchase procedure.")]
//! Cancel,
//! }
//! ```
//! //!
//! This is a very limited example of update pipelining facilities. In more //! Now the key question: how to elegantly dispatch on different combinations of
//! involved scenarios, there are multiple branches and chains; if one element //! `State`, `Command`, and Telegram updates? -- i.e., we may want to execute
//! of a chain fails to handle an update, the update will be passed forwards; if //! specific endpoints only in response to specific user commands and while we
//! no handler succeeds at handling the update, [`Dispatcher`] will invoke a //! are in a given dialogue state (and possibly under other circumstances!). The
//! default handler set up via [`DispatcherBuilder::default_handler`]. //! solution is to use [`dptree`]:
//! //!
//! Update pipelining provides several advantages over the typical `match //! ```no_run
//! (update.kind) { ... }` approach: //! # // That's a lot of context needed to compile this, oof
//! # use teloxide::dispatching::{UpdateHandler, UpdateFilterExt, dialogue, dialogue::InMemStorage};
//! # use teloxide::utils::command::BotCommands;
//! # use teloxide::types::Update;
//! # #[derive(Clone, Default)] pub enum State { #[default] Start, ReceiveFullName, ReceiveProductChoice { full_name: String } }
//! # #[derive(BotCommands, Clone)] enum Command { Help, Start, Cancel }
//! # type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
//! # async fn help() -> HandlerResult { Ok(()) }
//! # async fn start() -> HandlerResult { Ok(()) }
//! # async fn cancel() -> HandlerResult { Ok(()) }
//! # async fn receive_full_name() -> HandlerResult { Ok(()) }
//! # async fn invalid_state() -> HandlerResult { Ok(()) }
//! # async fn receive_product_selection() -> HandlerResult { Ok(()) }
//! #
//! fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
//! use dptree::case;
//! //!
//! 1. It supports _extension_: e.g., you //! let command_handler = teloxide::filter_command::<Command, _>()
//! can define extension filters or some other handlers and then combine them in //! .branch(
//! a single place, thus facilitating loose coupling. //! case![State::Start]
//! 2. Pipelining exhibits a natural syntax for expressing message processing. //! .branch(case![Command::Help].endpoint(help))
//! 3. Lastly, it provides a primitive form of [dependency injection (DI)], //! .branch(case![Command::Start].endpoint(start)),
//! which allows you to deal with such objects as a bot and various update types //! )
//! easily. //! .branch(case![Command::Cancel].endpoint(cancel));
//! //!
//! For a more involved example, see [`examples/dispatching_features.rs`](https://github.com/teloxide/teloxide/blob/master/examples/dispatching_features.rs). //! let message_handler = Update::filter_message()
//! .branch(command_handler)
//! .branch(case![State::ReceiveFullName].endpoint(receive_full_name))
//! .branch(dptree::endpoint(invalid_state));
//! //!
//! TODO: explain a more involved example with multiple branches. //! let callback_query_handler = Update::filter_callback_query().branch(
//! case![State::ReceiveProductChoice { full_name }].endpoint(receive_product_selection),
//! );
//! //!
//! dialogue::enter::<Update, InMemStorage<State>, State, _>()
//! .branch(message_handler)
//! .branch(callback_query_handler)
//! }
//! ```
//!
//! The overall logic should be clear. Throughout the above example, we use
//! several techniques:
//!
//! - **Branching:** `a.branch(b)` roughly means "try to handle an update with
//! `a`, then, if it
//! neglects the update, try `b`".
//! - **Pattern matching:** We also use the [`dptree::case!`] macro
//! extensively, which acts as a filter on an enumeration: if it is of a
//! certain variant, it passes the variant's payload down the handler chain;
//! otherwise, it neglects an update.
//! - **Endpoints:** To specify the final function to handle an update, we use
//! [`dptree::Handler::endpoint`].
//!
//! Notice the clear and uniform code structure: regardless of the dispatch
//! criteria, we use the same program constructions. In future, you may want to
//! introduce your application-specific filters or data structures to match upon
//! -- no problem, reuse [`dptree::Handler::filter`], [`dptree::case!`], and
//! other combinators in the same way!
//!
//! Finally, we define our endpoints like this:
//!
//! ```no_run
//! # use teloxide::{Bot, adaptors::AutoSend};
//! # use teloxide::types::{Message, CallbackQuery};
//! # use teloxide::dispatching::dialogue::{InMemStorage, Dialogue};
//! # enum State{}
//! #
//! type MyDialogue = Dialogue<State, InMemStorage<State>>;
//! type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
//!
//! async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
//! todo!()
//! }
//!
//! async fn help(bot: AutoSend<Bot>, msg: Message) -> HandlerResult {
//! todo!()
//! }
//!
//! async fn cancel(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
//! todo!()
//! }
//!
//! async fn invalid_state(bot: AutoSend<Bot>, msg: Message) -> HandlerResult {
//! todo!()
//! }
//!
//! async fn receive_full_name(
//! bot: AutoSend<Bot>,
//! msg: Message,
//! dialogue: MyDialogue,
//! ) -> HandlerResult {
//! todo!()
//! }
//!
//! async fn receive_product_selection(
//! bot: AutoSend<Bot>,
//! q: CallbackQuery,
//! dialogue: MyDialogue,
//! full_name: String,
//! ) -> HandlerResult {
//! todo!()
//! }
//! ```
//!
//! Each parameter is supplied as a dependency by teloxide. In particular:
//! - `bot: AutoSend<Bot>` comes from the dispatcher (see below);
//! - `msg: Message` comes from [`Update::filter_message`];
//! - `q: CallbackQuery` comes from [`Update::filter_callback_query`];
//! - `dialogue: MyDialogue` comes from [`dialogue::enter`];
//! - `full_name: String` comes from `dptree::case![State::ReceiveProductChoice
//! { full_name }]`.
//!
//! Inside `main`, we plug the schema into [`Dispatcher`] like this:
//!
//! ```no_run
//! # use teloxide::Bot;
//! # use teloxide::requests::RequesterExt;
//! # use teloxide::dispatching::{Dispatcher, dialogue::InMemStorage};
//! # enum State {}
//! # fn schema() -> teloxide::dispatching::UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> { teloxide::dptree::entry() }
//! #[tokio::main]
//! async fn main() {
//! let bot = Bot::from_env().auto_send();
//!
//! Dispatcher::builder(bot, schema())
//! .dependencies(dptree::deps![InMemStorage::<State>::new()])
//! .enable_ctrlc_handler()
//! .build()
//! .dispatch()
//! .await;
//! }
//! ```
//!
//! In a call to [`DispatcherBuilder::dependencies`], we specify a list of
//! additional dependencies that all handlers will receive as parameters. Here,
//! we only specify an in-memory storage of dialogues needed for
//! [`dialogue::enter`]. However, in production bots, you normally also pass a
//! database connection, configuration, and other stuff.
//!
//! All in all, [`dptree`] can be seen as an extensible alternative to pattern
//! matching, with support for [dependency injection (DI)] and a few other
//! useful features. See [`examples/dispatching_features.rs`] as a more involved
//! example.
//!
//! [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs
//! [`Update::filter_message`]: crate::types::Update::filter_message
//! [`Update::filter_callback_query`]: crate::types::Update::filter_callback_query
//! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern //! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
//! [dependency injection (DI)]: https://en.wikipedia.org/wiki/Dependency_injection //! [dependency injection (DI)]: https://en.wikipedia.org/wiki/Dependency_injection
//! [`examples/dispatching_features.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/dispatching_features.rs
#[cfg(all(feature = "ctrlc_handler"))] #[cfg(all(feature = "ctrlc_handler"))]
pub mod repls; pub mod repls;

View file

@ -4,10 +4,11 @@
//! wrapper over [`Storage`] and a chat ID. All it does is provides convenient //! wrapper over [`Storage`] and a chat ID. All it does is provides convenient
//! method for manipulating the dialogue state. [`Storage`] is where all //! method for manipulating the dialogue state. [`Storage`] is where all
//! dialogue states are stored; it can be either [`InMemStorage`], which is a //! dialogue states are stored; it can be either [`InMemStorage`], which is a
//! simple hash map, or database wrappers such as [`SqliteStorage`]. In the //! simple hash map from [`std::collections`], or an advanced database wrapper
//! latter case, your dialogues are _persistent_, meaning that you can safely //! such as [`SqliteStorage`]. In the latter case, your dialogues are
//! restart your bot and all dialogues will remain in the database -- this is a //! _persistent_, meaning that you can safely restart your bot and all ongoing
//! preferred method for production bots. //! dialogues will remain in the database -- this is a preferred method for
//! production bots.
//! //!
//! [`examples/dialogue.rs`] clearly demonstrates the typical usage of //! [`examples/dialogue.rs`] clearly demonstrates the typical usage of
//! dialogues. Your dialogue state can be represented as an enumeration: //! dialogues. Your dialogue state can be represented as an enumeration:
@ -31,8 +32,8 @@
//! bot: AutoSend<Bot>, //! bot: AutoSend<Bot>,
//! msg: Message, //! msg: Message,
//! dialogue: MyDialogue, //! dialogue: MyDialogue,
//! (full_name,): (String,), // Available from `State::ReceiveAge`. //! full_name: String, // Available from `State::ReceiveAge`.
//! ) -> anyhow::Result<()> { //! ) -> HandlerResult {
//! match msg.text().map(|text| text.parse::<u8>()) { //! match msg.text().map(|text| text.parse::<u8>()) {
//! Some(Ok(age)) => { //! Some(Ok(age)) => {
//! bot.send_message(msg.chat.id, "What's your location?").await?; //! bot.send_message(msg.chat.id, "What's your location?").await?;
@ -47,11 +48,12 @@
//! } //! }
//! ``` //! ```
//! //!
//! Variant's fields are passed to state handlers as tuples: `(full_name,): //! Variant's fields are passed to state handlers as single arguments like
//! (String,)`. Using [`Dialogue::update`], you can update the dialogue with a //! `full_name: String` or tuples in case of two or more variant parameters (see
//! new state, in our case -- `State::ReceiveLocation { full_name, age }`. To //! below). Using [`Dialogue::update`], you can update the dialogue with a new
//! exit the dialogue, just call [`Dialogue::exit`] and it will be removed from //! state, in our case -- `State::ReceiveLocation { full_name, age }`. To exit
//! the inner storage: //! the dialogue, just call [`Dialogue::exit`] and it will be removed from the
//! underlying storage:
//! //!
//! ```ignore //! ```ignore
//! async fn receive_location( //! async fn receive_location(

View file

@ -33,6 +33,7 @@ pub struct DispatcherBuilder<R, Err, Key> {
handler: Arc<UpdateHandler<Err>>, handler: Arc<UpdateHandler<Err>>,
default_handler: DefaultHandler, default_handler: DefaultHandler,
error_handler: Arc<dyn ErrorHandler<Err> + Send + Sync>, error_handler: Arc<dyn ErrorHandler<Err> + Send + Sync>,
ctrlc_handler: bool,
distribution_f: fn(&Update) -> Option<Key>, distribution_f: fn(&Update) -> Option<Key>,
worker_queue_size: usize, worker_queue_size: usize,
} }
@ -78,6 +79,14 @@ where
Self { dependencies, ..self } Self { dependencies, ..self }
} }
/// Enables the `^C` handler that [`shutdown`]s dispatching.
///
/// [`shutdown`]: ShutdownToken::shutdown
#[cfg(feature = "ctrlc_handler")]
pub fn enable_ctrlc_handler(self) -> Self {
Self { ctrlc_handler: true, ..self }
}
/// Specifies size of the queue for workers. /// Specifies size of the queue for workers.
/// ///
/// By default it's 64. /// By default it's 64.
@ -101,6 +110,7 @@ where
handler, handler,
default_handler, default_handler,
error_handler, error_handler,
ctrlc_handler,
distribution_f: _, distribution_f: _,
worker_queue_size, worker_queue_size,
} = self; } = self;
@ -111,6 +121,7 @@ where
handler, handler,
default_handler, default_handler,
error_handler, error_handler,
ctrlc_handler,
distribution_f: f, distribution_f: f,
worker_queue_size, worker_queue_size,
} }
@ -127,9 +138,10 @@ where
error_handler, error_handler,
distribution_f, distribution_f,
worker_queue_size, worker_queue_size,
ctrlc_handler,
} = self; } = self;
Dispatcher { let dp = Dispatcher {
bot, bot,
dependencies, dependencies,
handler, handler,
@ -142,7 +154,18 @@ where
default_worker: None, default_worker: None,
current_number_of_active_workers: Default::default(), current_number_of_active_workers: Default::default(),
max_number_of_active_workers: Default::default(), max_number_of_active_workers: Default::default(),
};
#[cfg(feature = "ctrlc_handler")]
{
if ctrlc_handler {
let mut dp = dp;
dp.setup_ctrlc_handler_inner();
return dp;
}
} }
dp
} }
} }
@ -212,6 +235,7 @@ where
Box::pin(async {}) Box::pin(async {})
}), }),
error_handler: LoggingErrorHandler::new(), error_handler: LoggingErrorHandler::new(),
ctrlc_handler: false,
worker_queue_size: DEFAULT_WORKER_QUEUE_SIZE, worker_queue_size: DEFAULT_WORKER_QUEUE_SIZE,
distribution_f: default_distribution_function, distribution_f: default_distribution_function,
} }
@ -238,7 +262,6 @@ where
/// - [`crate::types::Me`] (can be used in [`HandlerExt::filter_command`]). /// - [`crate::types::Me`] (can be used in [`HandlerExt::filter_command`]).
/// ///
/// [`shutdown`]: ShutdownToken::shutdown /// [`shutdown`]: ShutdownToken::shutdown
/// [a ctrlc signal]: Dispatcher::setup_ctrlc_handler
/// [`HandlerExt::filter_command`]: crate::dispatching::HandlerExt::filter_command /// [`HandlerExt::filter_command`]: crate::dispatching::HandlerExt::filter_command
pub async fn dispatch(&mut self) pub async fn dispatch(&mut self)
where where
@ -258,7 +281,6 @@ where
/// This method adds the same dependencies as [`Dispatcher::dispatch`]. /// This method adds the same dependencies as [`Dispatcher::dispatch`].
/// ///
/// [`shutdown`]: ShutdownToken::shutdown /// [`shutdown`]: ShutdownToken::shutdown
/// [a ctrlc signal]: Dispatcher::setup_ctrlc_handler
pub async fn dispatch_with_listener<'a, UListener, ListenerE, Eh>( pub async fn dispatch_with_listener<'a, UListener, ListenerE, Eh>(
&'a mut self, &'a mut self,
mut update_listener: UListener, mut update_listener: UListener,
@ -425,7 +447,22 @@ where
/// ///
/// [`shutdown`]: ShutdownToken::shutdown /// [`shutdown`]: ShutdownToken::shutdown
#[cfg(feature = "ctrlc_handler")] #[cfg(feature = "ctrlc_handler")]
#[deprecated(since = "0.10.0", note = "use `enable_ctrlc_handler` on builder instead")]
pub fn setup_ctrlc_handler(&mut self) -> &mut Self { pub fn setup_ctrlc_handler(&mut self) -> &mut Self {
self.setup_ctrlc_handler_inner();
self
}
/// Returns a shutdown token, which can later be used to shutdown
/// dispatching.
pub fn shutdown_token(&self) -> ShutdownToken {
self.state.clone()
}
}
impl<R, Err, Key> Dispatcher<R, Err, Key> {
#[cfg(feature = "ctrlc_handler")]
fn setup_ctrlc_handler_inner(&mut self) {
let token = self.state.clone(); let token = self.state.clone();
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {
@ -443,14 +480,6 @@ where
} }
} }
}); });
self
}
/// Returns a shutdown token, which can later be used to shutdown
/// dispatching.
pub fn shutdown_token(&self) -> ShutdownToken {
self.state.clone()
} }
} }

View file

@ -1,44 +1,27 @@
use std::collections::HashSet; use std::collections::HashSet;
use dptree::{description::EventKind, HandlerDescription}; use dptree::{
description::{EventKind, InterestSet},
HandlerDescription,
};
use teloxide_core::types::AllowedUpdate; use teloxide_core::types::AllowedUpdate;
/// Handler description that is used by [`Dispatcher`]. /// Handler description that is used by [`Dispatcher`].
/// ///
/// [`Dispatcher`]: crate::dispatching::Dispatcher /// [`Dispatcher`]: crate::dispatching::Dispatcher
pub struct DpHandlerDescription { pub struct DpHandlerDescription {
allowed: EventKind<AllowedUpdate>, allowed: InterestSet<Kind>,
} }
impl DpHandlerDescription { impl DpHandlerDescription {
pub(crate) fn of(allowed: AllowedUpdate) -> Self { pub(crate) fn of(allowed: AllowedUpdate) -> Self {
let mut set = HashSet::with_capacity(1); let mut set = HashSet::with_capacity(1);
set.insert(allowed); set.insert(Kind(allowed));
Self { allowed: EventKind::InterestList(set) } Self { allowed: InterestSet::new_filter(set) }
} }
pub(crate) fn allowed_updates(&self) -> Vec<AllowedUpdate> { pub(crate) fn allowed_updates(&self) -> Vec<AllowedUpdate> {
use AllowedUpdate::*; self.allowed.observed.iter().map(|Kind(x)| x).copied().collect()
match &self.allowed {
EventKind::InterestList(set) => set.iter().copied().collect(),
EventKind::Entry => panic!("No updates were allowed"),
EventKind::UserDefined => vec![
Message,
EditedMessage,
ChannelPost,
EditedChannelPost,
InlineQuery,
ChosenInlineResult,
CallbackQuery,
ShippingQuery,
PreCheckoutQuery,
Poll,
PollAnswer,
MyChatMember,
ChatMember,
],
}
} }
} }
@ -59,3 +42,70 @@ impl HandlerDescription for DpHandlerDescription {
Self { allowed: self.allowed.merge_branch(&other.allowed) } Self { allowed: self.allowed.merge_branch(&other.allowed) }
} }
} }
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
struct Kind(AllowedUpdate);
impl EventKind for Kind {
fn full_set() -> HashSet<Self> {
use AllowedUpdate::*;
[
Message,
EditedMessage,
ChannelPost,
EditedChannelPost,
InlineQuery,
ChosenInlineResult,
CallbackQuery,
ShippingQuery,
PreCheckoutQuery,
Poll,
PollAnswer,
MyChatMember,
ChatMember,
]
.into_iter()
.map(Kind)
.collect()
}
fn empty_set() -> HashSet<Self> {
HashSet::new()
}
}
#[cfg(test)]
mod tests {
use crate::{
dispatching::{HandlerExt, UpdateFilterExt},
types::{AllowedUpdate::*, Update},
utils::command::BotCommands,
};
use crate as teloxide; // fixup for the `BotCommands` macro
#[derive(BotCommands, Clone)]
#[command(rename = "lowercase")]
enum Cmd {
B,
}
// <https://github.com/teloxide/teloxide/discussions/648>
#[test]
fn discussion_648() {
let h =
dptree::entry().branch(Update::filter_my_chat_member().endpoint(|| async {})).branch(
Update::filter_message()
.branch(dptree::entry().filter_command::<Cmd>().endpoint(|| async {}))
.endpoint(|| async {}),
);
let mut v = h.description().allowed_updates();
// Hash set randomizes element order, so to compare we need to sort
v.sort_by_key(|&a| a as u8);
assert_eq!(v, [Message, MyChatMember])
}
}

View file

@ -14,7 +14,12 @@ use teloxide_core::requests::Requester;
/// ///
/// All errors from an update listener and handler will be logged. /// All errors from an update listener and handler will be logged.
/// ///
/// REPLs are meant only for simple bots and rapid prototyping. If you need to
/// supply dependencies or describe more complex dispatch logic, please use
/// [`Dispatcher`].
///
/// ## Caution /// ## Caution
///
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs, /// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same /// because Telegram disallow multiple requests at the same time from the same
/// bot. /// bot.
@ -49,7 +54,12 @@ where
/// ///
/// All errors from an update listener and handler will be logged. /// All errors from an update listener and handler will be logged.
/// ///
/// REPLs are meant only for simple bots and rapid prototyping. If you need to
/// supply dependencies or describe more complex dispatch logic, please use
/// [`Dispatcher`].
///
/// ## Caution /// ## Caution
///
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs, /// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same /// because Telegram disallow multiple requests at the same time from the same
/// bot. /// bot.
@ -86,8 +96,8 @@ pub async fn commands_repl_with_listener<'a, R, Cmd, H, L, ListenerE, E, Args>(
Update::filter_message().filter_command::<Cmd>().chain(dptree::endpoint(handler)), Update::filter_message().filter_command::<Cmd>().chain(dptree::endpoint(handler)),
) )
.default_handler(ignore_update) .default_handler(ignore_update)
.enable_ctrlc_handler()
.build() .build()
.setup_ctrlc_handler()
.dispatch_with_listener( .dispatch_with_listener(
listener, listener,
LoggingErrorHandler::with_custom_text("An error from the update listener"), LoggingErrorHandler::with_custom_text("An error from the update listener"),

View file

@ -11,7 +11,12 @@ use teloxide_core::requests::Requester;
/// ///
/// All errors from an update listener and a handler will be logged. /// All errors from an update listener and a handler will be logged.
/// ///
/// # Caution /// REPLs are meant only for simple bots and rapid prototyping. If you need to
/// supply dependencies or describe more complex dispatch logic, please use
/// [`Dispatcher`].
///
/// ## Caution
///
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs, /// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same /// because Telegram disallow multiple requests at the same time from the same
/// bot. /// bot.
@ -35,7 +40,12 @@ where
/// ///
/// All errors from an update listener and handler will be logged. /// All errors from an update listener and handler will be logged.
/// ///
/// REPLs are meant only for simple bots and rapid prototyping. If you need to
/// supply dependencies or describe more complex dispatch logic, please use
/// [`Dispatcher`].
///
/// # Caution /// # Caution
///
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs, /// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same /// because Telegram disallow multiple requests at the same time from the same
/// bot. /// bot.
@ -61,8 +71,8 @@ where
Dispatcher::builder(bot, Update::filter_message().chain(dptree::endpoint(handler))) Dispatcher::builder(bot, Update::filter_message().chain(dptree::endpoint(handler)))
.default_handler(ignore_update) .default_handler(ignore_update)
.enable_ctrlc_handler()
.build() .build()
.setup_ctrlc_handler()
.dispatch_with_listener( .dispatch_with_listener(
listener, listener,
LoggingErrorHandler::with_custom_text("An error from the update listener"), LoggingErrorHandler::with_custom_text("An error from the update listener"),

View file

@ -272,7 +272,7 @@ impl<B> FromRequest<B> for XTelegramBotApiSecretToken {
let res = req let res = req
.headers_mut() .headers_mut()
.and_then(|map| map.remove("x-telegram-bot-api-secret-token")) .remove("x-telegram-bot-api-secret-token")
.map(|header| { .map(|header| {
check_secret(header.as_bytes()) check_secret(header.as_bytes())
.map(<_>::to_owned) .map(<_>::to_owned)

View file

@ -1,24 +1,24 @@
## Cargo features ## Cargo features
| Feature | Description | | Feature | Description |
|----------------------|------------------------------------------------------------------------------------| |----------------------|--------------------------------------------------------------------------------------------|
| `webhooks` | Enables general webhook utilities (almost useless on its own) | | `webhooks` | Enables general webhook utilities (almost useless on its own) |
| `webhooks-axum` | Enables webhook implementation based on axum framework | | `webhooks-axum` | Enables webhook implementation based on axum framework |
| `macros` | Re-exports macros from [`teloxide-macros`]. | | `macros` | Re-exports macros from [`teloxide-macros`]. |
| `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`] function (**enabled by default**). | | `ctrlc_handler` | Enables the [`DispatcherBuilder::enable_ctrlc_handler`] function (**enabled by default**). |
| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). | | `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). |
| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. | | `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. |
| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. | | `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. |
| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. | | `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. |
| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. | | `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. |
| `full` | Enables all the features except `nightly`. | | `full` | Enables all the features except `nightly`. |
| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | | `nightly` | Enables nightly-only features (see the [teloxide-core features]). |
| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). | | `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). |
| `rustls` | Enables the [`rustls`] TLS implementation. | | `rustls` | Enables the [`rustls`] TLS implementation. |
| `redis-storage` | Enables the [Redis] storage support for dialogues. | | `redis-storage` | Enables the [Redis] storage support for dialogues. |
| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | | `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. |
| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | | `cbor-serializer` | Enables the [CBOR] serializer for dialogues. |
| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | | `bincode-serializer` | Enables the [Bincode] serializer for dialogues. |
[Redis]: https://redis.io/ [Redis]: https://redis.io/
@ -31,4 +31,4 @@
[`teloxide::utils::UpState`]: utils::UpState [`teloxide::utils::UpState`]: utils::UpState
[teloxide-core features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features [teloxide-core features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features
[`Dispatcher::setup_ctrlc_handler`]: dispatching::Dispatcher::setup_ctrlc_handler [`DispatcherBuilder::enable_ctrlc_handler`]: dispatching::DispatcherBuilder::enable_ctrlc_handler

View file

@ -2,7 +2,7 @@
#![allow(clippy::nonstandard_macro_braces)] #![allow(clippy::nonstandard_macro_braces)]
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
use teloxide::utils::command::{BotCommands, ParseError}; use teloxide::utils::command::BotCommands;
// We put tests here because macro expand in unit tests in module // We put tests here because macro expand in unit tests in module
// teloxide::utils::command was a failure // teloxide::utils::command was a failure
@ -141,22 +141,33 @@ fn parse_with_split2() {
#[test] #[test]
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
fn parse_custom_parser() { fn parse_custom_parser() {
fn custom_parse_function(s: String) -> Result<(u8, String), ParseError> { mod parser {
let vec = s.split_whitespace().collect::<Vec<_>>(); use teloxide::utils::command::ParseError;
let (left, right) = match vec.as_slice() {
[l, r] => (l, r), pub fn custom_parse_function(s: String) -> Result<(u8, String), ParseError> {
_ => return Err(ParseError::IncorrectFormat("might be 2 arguments!".into())), let vec = s.split_whitespace().collect::<Vec<_>>();
}; let (left, right) = match vec.as_slice() {
left.parse::<u8>() [l, r] => (l, r),
.map(|res| (res, (*right).to_string())) _ => return Err(ParseError::IncorrectFormat("might be 2 arguments!".into())),
.map_err(|_| ParseError::Custom("First argument must be a integer!".to_owned().into())) };
left.parse::<u8>().map(|res| (res, (*right).to_string())).map_err(|_| {
ParseError::Custom("First argument must be a integer!".to_owned().into())
})
}
} }
use parser::custom_parse_function;
#[derive(BotCommands, Debug, PartialEq)] #[derive(BotCommands, Debug, PartialEq)]
#[command(rename = "lowercase")] #[command(rename = "lowercase")]
enum DefaultCommands { enum DefaultCommands {
#[command(parse_with = "custom_parse_function")] #[command(parse_with = "custom_parse_function")]
Start(u8, String), Start(u8, String),
// Test <https://github.com/teloxide/teloxide/issues/668>.
#[command(parse_with = "parser::custom_parse_function")]
TestPath(u8, String),
Help, Help,
} }
@ -164,6 +175,10 @@ fn parse_custom_parser() {
DefaultCommands::Start(10, "hello".to_string()), DefaultCommands::Start(10, "hello".to_string()),
DefaultCommands::parse("/start 10 hello", "").unwrap() DefaultCommands::parse("/start 10 hello", "").unwrap()
); );
assert_eq!(
DefaultCommands::TestPath(10, "hello".to_string()),
DefaultCommands::parse("/testpath 10 hello", "").unwrap()
);
} }
#[test] #[test]