mirror of
https://github.com/teloxide/teloxide.git
synced 2025-01-03 09:49:07 +01:00
Merge pull request #677 from teloxide/update_dptree
Bump version -> 0.10
Former-commit-id: 85a11a6064
This commit is contained in:
commit
80bf7c6b16
21 changed files with 494 additions and 210 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
@ -123,6 +123,7 @@ jobs:
|
|||
args: --tests --verbose ${{ matrix.features }}
|
||||
|
||||
- name: Test documentation tests
|
||||
if: ${{ matrix.rust != 'msrv' }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -6,23 +6,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## unreleased
|
||||
|
||||
## 0.10.0 - 2022-07-21
|
||||
|
||||
### Added
|
||||
|
||||
- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks
|
||||
- `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}`
|
||||
- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks.
|
||||
- `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}`.
|
||||
- `DispatcherBuilder::enable_ctrlc_handler` method.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `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
|
||||
|
||||
- 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
|
||||
|
||||
- `dispatching::update_listeners::polling`
|
||||
- The `dispatching::update_listeners::polling` function.
|
||||
- `Dispatcher::setup_ctrlc_handler` method.
|
||||
|
||||
## 0.9.2 - 2022-06-07
|
||||
|
||||
|
|
26
Cargo.toml
26
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "teloxide"
|
||||
version = "0.9.2"
|
||||
version = "0.10.0"
|
||||
edition = "2021"
|
||||
description = "An elegant Telegram bots framework for Rust"
|
||||
repository = "https://github.com/teloxide/teloxide"
|
||||
|
@ -57,17 +57,21 @@ full = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
#teloxide-core = { version = "0.6.0", default-features = false }
|
||||
teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false }
|
||||
teloxide-macros = { version = "0.6.2", optional = true }
|
||||
teloxide-core = { version = "0.7.0", default-features = false }
|
||||
teloxide-macros = { version = "0.6.3", optional = true }
|
||||
|
||||
serde_json = "1.0"
|
||||
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-util = "0.6"
|
||||
tokio-util = "0.7"
|
||||
tokio-stream = "0.1.8"
|
||||
|
||||
url = "2.2.2"
|
||||
|
@ -82,17 +86,17 @@ pin-project = "1.0"
|
|||
serde_with_macros = "1.4"
|
||||
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",
|
||||
"macros",
|
||||
"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 }
|
||||
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-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 }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -101,7 +105,7 @@ pretty_env_logger = "0.4.0"
|
|||
serde = "1"
|
||||
serde_json = "1"
|
||||
tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
reqwest = "0.10.4"
|
||||
reqwest = "0.11.11"
|
||||
chrono = "0.4"
|
||||
tokio-stream = "0.1"
|
||||
|
||||
|
|
|
@ -1,6 +1,47 @@
|
|||
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.
|
||||
|
||||
## 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
|
||||
|
||||
### 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).
|
||||
|
||||
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 UserId(user_id) = user.id;
|
||||
db.save(user_id, ...);
|
||||
|
|
68
README.md
68
README.md
|
@ -1,6 +1,4 @@
|
|||
> [v0.7 -> v0.8 migration guide >>](MIGRATION_GUIDE.md#07---08)
|
||||
|
||||
> `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)
|
||||
> [v0.9 -> v0.10 migration guide >>](MIGRATION_GUIDE.md#09---010)
|
||||
|
||||
<div align="center">
|
||||
<img src="../../raw/master/ICON.png" width="250"/>
|
||||
|
@ -15,7 +13,7 @@
|
|||
<img src="https://img.shields.io/crates/v/teloxide.svg">
|
||||
</a>
|
||||
<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 href="https://t.me/teloxide">
|
||||
<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`:
|
||||
```toml
|
||||
[dependencies]
|
||||
teloxide = { version = "0.9", features = ["macros", "auto-send"] }
|
||||
teloxide = { version = "0.10", features = ["macros", "auto-send"] }
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
|
||||
|
@ -225,8 +223,8 @@ async fn main() {
|
|||
),
|
||||
)
|
||||
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
.setup_ctrlc_handler()
|
||||
.dispatch()
|
||||
.await;
|
||||
}
|
||||
|
@ -321,11 +319,7 @@ A: No, only the bots API.
|
|||
|
||||
**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).
|
||||
|
||||
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)
|
||||
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).
|
||||
|
||||
**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!
|
||||
|
||||
- [WaffleLapkin/crate_upd_bot](https://github.com/WaffleLapkin/crate_upd_bot) — A bot that notifies about crate updates.
|
||||
- [mxseev/logram](https://github.com/mxseev/logram) — Utility that takes logs from anywhere and sends them to Telegram.
|
||||
- [alexkonovalov/PedigreeBot](https://github.com/alexkonovalov/PedigreeBot) — A Telegram bot for building family trees.
|
||||
- [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/libgen-bot-rs](https://github.com/mattrighetti/libgen-bot-rs) — Telgram bot to interface with libgen
|
||||
- [dracarys18/grpmr-rs](https://github.com/dracarys18/grpmr-rs) — A Telegram group manager bot with variety of extra 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.
|
||||
- [myblackbeard/basketball-betting-bot](https://github.com/myblackbeard/basketball-betting-bot) — The bot lets you bet on NBA games against your buddies.
|
||||
- [ArtHome12/vzmuinebot](https://github.com/ArtHome12/vzmuinebot) — Telegram bot for food menu navigate.
|
||||
- [ArtHome12/cognito_bot](https://github.com/ArtHome12/cognito_bot) — The bot is designed to anonymize messages to a group.
|
||||
- [pro-vim/tg-vimhelpbot](https://github.com/pro-vim/tg-vimhelpbot) — Link `:help` for Vim in Telegram.
|
||||
- [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.
|
||||
- [MustafaSalih1993/Miss-Vodka-Telegram-Bot](https://github.com/MustafaSalih1993/Miss-Vodka-Telegram-Bot) — A Telegram bot written in rust using "Teloxide" library.
|
||||
- [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.
|
||||
- [cyberknight777/knight-bot](https://gitlab.com/cyberknight777/knight-bot) — A Telegram bot with variety of fun features.
|
||||
- [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.
|
||||
- [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.
|
||||
- [alenpaul2001/AurSearchBot](https://gitlab.com/alenpaul2001/aursearchbot) — Telegram bot for searching AUR in inline mode.
|
||||
- [studiedlist/EddieBot](https://gitlab.com/studiedlist/eddie-bot) — Chatting bot with several entertainment features.
|
||||
- [modos189/tg_blackbox_bot](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project. This bot in Docker from scratch container.
|
||||
- [0xNima/spacecraft](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces.
|
||||
- [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.
|
||||
- [`raine/tgreddit`](https://github.com/raine/tgreddit) — A bot that sends the top posts of your favorite subreddits to Telegram.
|
||||
- [`magnickolas/remindee-bot`](https://github.com/magnickolas/remindee-bot) — Telegram bot for managing reminders.
|
||||
- [`WaffleLapkin/crate_upd_bot`](https://github.com/WaffleLapkin/crate_upd_bot) — A bot that notifies about crate updates.
|
||||
- [`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).
|
||||
- [`ArtHome12/vzmuinebot`](https://github.com/ArtHome12/vzmuinebot) — Telegram bot for food menu navigate.
|
||||
- [`studiedlist/EddieBot`](https://gitlab.com/studiedlist/eddie-bot) — Chatting bot with several entertainment features.
|
||||
- [`modos189/tg_blackbox_bot`](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project.
|
||||
- [`0xNima/spacecraft`](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces.
|
||||
- [`0xNima/Twideo`](https://github.com/0xNima/Twideo) — Simple Telegram Bot for downloading videos from Twitter via their links.
|
||||
- [`mattrighetti/libgen-bot-rs`](https://github.com/mattrighetti/libgen-bot-rs) — Telgram bot to interface with libgen.
|
||||
- [`zamazan4ik/npaperbot-telegram`](https://github.com/zamazan4ik/npaperbot-telegram) — Telegram bot for searching via C++ proposals.
|
||||
|
||||
<details>
|
||||
<summary>Show bots using teloxide older than v0.6.0</summary>
|
||||
|
||||
- [`mxseev/logram`](https://github.com/mxseev/logram) — Utility that takes logs from anywhere and sends them to Telegram.
|
||||
- [`alexkonovalov/PedigreeBot`](https://github.com/alexkonovalov/PedigreeBot) — A Telegram bot for building family trees.
|
||||
- [`Hermitter/tepe`](https://github.com/Hermitter/tepe) — A CLI to command a bot to send messages and files over Telegram.
|
||||
- [`myblackbeard/basketball-betting-bot`](https://github.com/myblackbeard/basketball-betting-bot) — The bot lets you bet on NBA games against your buddies.
|
||||
- [`dracarys18/grpmr-rs`](https://github.com/dracarys18/grpmr-rs) — Modular Telegram Group Manager Bot written in Rust.
|
||||
- [`ArtHome12/vzmuinebot`](https://github.com/ArtHome12/vzmuinebot) — Telegram bot for food menu navigate.
|
||||
- [`ArtHome12/cognito_bot`](https://github.com/ArtHome12/cognito_bot) — The bot is designed to anonymize messages to a group.
|
||||
- [`crapstone/hsctt`](https://codeberg.org/crapstones-bots/hsctt) — A bot that converts HTTP status codes into text.
|
||||
|
||||
</details>
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||
.branch(Update::filter_callback_query().endpoint(callback_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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -54,8 +54,8 @@ async fn main() {
|
|||
|
||||
Dispatcher::builder(bot, handler)
|
||||
.dependencies(dptree::deps![storage])
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
.setup_ctrlc_handler()
|
||||
.dispatch()
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -51,8 +51,8 @@ async fn main() {
|
|||
),
|
||||
)
|
||||
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
.setup_ctrlc_handler()
|
||||
.dispatch()
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -87,8 +87,8 @@ async fn main() {
|
|||
.error_handler(LoggingErrorHandler::with_custom_text(
|
||||
"An error has occurred in the dispatcher",
|
||||
))
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
.setup_ctrlc_handler()
|
||||
.dispatch()
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -13,10 +13,7 @@
|
|||
// ```
|
||||
|
||||
use teloxide::{
|
||||
dispatching::{
|
||||
dialogue::{self, InMemStorage},
|
||||
UpdateHandler,
|
||||
},
|
||||
dispatching::{dialogue, dialogue::InMemStorage, UpdateHandler},
|
||||
prelude::*,
|
||||
types::{InlineKeyboardButton, InlineKeyboardMarkup},
|
||||
utils::command::BotCommands,
|
||||
|
@ -55,29 +52,30 @@ async fn main() {
|
|||
|
||||
Dispatcher::builder(bot, schema())
|
||||
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
.setup_ctrlc_handler()
|
||||
.dispatch()
|
||||
.await;
|
||||
}
|
||||
|
||||
fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
use dptree::case;
|
||||
|
||||
let command_handler = teloxide::filter_command::<Command, _>()
|
||||
.branch(
|
||||
dptree::case![State::Start]
|
||||
.branch(dptree::case![Command::Help].endpoint(help))
|
||||
.branch(dptree::case![Command::Start].endpoint(start)),
|
||||
case![State::Start]
|
||||
.branch(case![Command::Help].endpoint(help))
|
||||
.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()
|
||||
.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));
|
||||
|
||||
let callback_query_handler = Update::filter_callback_query().chain(
|
||||
dptree::case![State::ReceiveProductChoice { full_name }]
|
||||
.endpoint(receive_product_selection),
|
||||
let callback_query_handler = Update::filter_callback_query().branch(
|
||||
case![State::ReceiveProductChoice { full_name }].endpoint(receive_product_selection),
|
||||
);
|
||||
|
||||
dialogue::enter::<Update, InMemStorage<State>, State, _>()
|
||||
|
|
|
@ -27,8 +27,8 @@ async fn main() {
|
|||
Dispatcher::builder(bot, handler)
|
||||
// Pass the shared state to the handler as a dependency.
|
||||
.dependencies(dptree::deps![messages_total])
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
.setup_ctrlc_handler()
|
||||
.dispatch()
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -1,76 +1,198 @@
|
|||
//! An update dispatching model based on [`dptree`].
|
||||
//!
|
||||
//! In teloxide, updates are dispatched by a pipeline. The central type is
|
||||
//! [`dptree::Handler`] -- it represents a handler of an update; since the API
|
||||
//! is highly declarative, you can combine handlers with each other via such
|
||||
//! 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`].
|
||||
//! In teloxide, update dispatching is declarative: it takes the form of a
|
||||
//! [chain of responsibility] pattern enriched with a number of combinator
|
||||
//! functions, which together form an instance of the [`dptree::Handler`] type.
|
||||
//!
|
||||
//! The pattern itself is called [chain of responsibility], a well-known design
|
||||
//! technique across OOP developers. But unlike typical object-oriented design,
|
||||
//! 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))
|
||||
//! Take [`examples/purchase.rs`] as an example of dispatching logic. First, we
|
||||
//! define a type named `State` to represent the current state of a dialogue:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! // TODO: examples/purchase.rs
|
||||
//! fn main() {}
|
||||
//! #[derive(Clone, Default)]
|
||||
//! pub enum State {
|
||||
//! #[default]
|
||||
//! Start,
|
||||
//! ReceiveFullName,
|
||||
//! ReceiveProductChoice {
|
||||
//! full_name: String,
|
||||
//! },
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! 1. First, we create the bot: `let bot = Bot::from_env().auto_send()`.
|
||||
//! 2. Then we construct an update handler. While it is possible to handle all
|
||||
//! 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`].
|
||||
//! Then, we define a type `Command` to represent user commands such as
|
||||
//! `/start` or `/help`:
|
||||
//!
|
||||
//! That being said, if we receive a message, the dispatcher will call our
|
||||
//! handler, but if we receive something other than a message (e.g., a channel
|
||||
//! post), you will see an unhandled update notice in your terminal.
|
||||
//! ```no_run
|
||||
//! # use teloxide::utils::command::BotCommands;
|
||||
//! #[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
|
||||
//! involved scenarios, there are multiple branches and chains; if one element
|
||||
//! of a chain fails to handle an update, the update will be passed forwards; if
|
||||
//! no handler succeeds at handling the update, [`Dispatcher`] will invoke a
|
||||
//! default handler set up via [`DispatcherBuilder::default_handler`].
|
||||
//! Now the key question: how to elegantly dispatch on different combinations of
|
||||
//! `State`, `Command`, and Telegram updates? -- i.e., we may want to execute
|
||||
//! specific endpoints only in response to specific user commands and while we
|
||||
//! are in a given dialogue state (and possibly under other circumstances!). The
|
||||
//! solution is to use [`dptree`]:
|
||||
//!
|
||||
//! Update pipelining provides several advantages over the typical `match
|
||||
//! (update.kind) { ... }` approach:
|
||||
//! ```no_run
|
||||
//! # // 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
|
||||
//! can define extension filters or some other handlers and then combine them in
|
||||
//! a single place, thus facilitating loose coupling.
|
||||
//! 2. Pipelining exhibits a natural syntax for expressing message processing.
|
||||
//! 3. Lastly, it provides a primitive form of [dependency injection (DI)],
|
||||
//! which allows you to deal with such objects as a bot and various update types
|
||||
//! easily.
|
||||
//! let command_handler = teloxide::filter_command::<Command, _>()
|
||||
//! .branch(
|
||||
//! case![State::Start]
|
||||
//! .branch(case![Command::Help].endpoint(help))
|
||||
//! .branch(case![Command::Start].endpoint(start)),
|
||||
//! )
|
||||
//! .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
|
||||
//! [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"))]
|
||||
pub mod repls;
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
//! wrapper over [`Storage`] and a chat ID. All it does is provides convenient
|
||||
//! method for manipulating the dialogue state. [`Storage`] is where all
|
||||
//! dialogue states are stored; it can be either [`InMemStorage`], which is a
|
||||
//! simple hash map, or database wrappers such as [`SqliteStorage`]. In the
|
||||
//! latter case, your dialogues are _persistent_, meaning that you can safely
|
||||
//! restart your bot and all dialogues will remain in the database -- this is a
|
||||
//! preferred method for production bots.
|
||||
//! simple hash map from [`std::collections`], or an advanced database wrapper
|
||||
//! such as [`SqliteStorage`]. In the latter case, your dialogues are
|
||||
//! _persistent_, meaning that you can safely restart your bot and all ongoing
|
||||
//! dialogues will remain in the database -- this is a preferred method for
|
||||
//! production bots.
|
||||
//!
|
||||
//! [`examples/dialogue.rs`] clearly demonstrates the typical usage of
|
||||
//! dialogues. Your dialogue state can be represented as an enumeration:
|
||||
|
@ -31,8 +32,8 @@
|
|||
//! bot: AutoSend<Bot>,
|
||||
//! msg: Message,
|
||||
//! dialogue: MyDialogue,
|
||||
//! (full_name,): (String,), // Available from `State::ReceiveAge`.
|
||||
//! ) -> anyhow::Result<()> {
|
||||
//! full_name: String, // Available from `State::ReceiveAge`.
|
||||
//! ) -> HandlerResult {
|
||||
//! match msg.text().map(|text| text.parse::<u8>()) {
|
||||
//! Some(Ok(age)) => {
|
||||
//! 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,):
|
||||
//! (String,)`. Using [`Dialogue::update`], you can update the dialogue with a
|
||||
//! new state, in our case -- `State::ReceiveLocation { full_name, age }`. To
|
||||
//! exit the dialogue, just call [`Dialogue::exit`] and it will be removed from
|
||||
//! the inner storage:
|
||||
//! Variant's fields are passed to state handlers as single arguments like
|
||||
//! `full_name: String` or tuples in case of two or more variant parameters (see
|
||||
//! below). Using [`Dialogue::update`], you can update the dialogue with a new
|
||||
//! state, in our case -- `State::ReceiveLocation { full_name, age }`. To exit
|
||||
//! the dialogue, just call [`Dialogue::exit`] and it will be removed from the
|
||||
//! underlying storage:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! async fn receive_location(
|
||||
|
|
|
@ -33,6 +33,7 @@ pub struct DispatcherBuilder<R, Err, Key> {
|
|||
handler: Arc<UpdateHandler<Err>>,
|
||||
default_handler: DefaultHandler,
|
||||
error_handler: Arc<dyn ErrorHandler<Err> + Send + Sync>,
|
||||
ctrlc_handler: bool,
|
||||
distribution_f: fn(&Update) -> Option<Key>,
|
||||
worker_queue_size: usize,
|
||||
}
|
||||
|
@ -78,6 +79,14 @@ where
|
|||
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.
|
||||
///
|
||||
/// By default it's 64.
|
||||
|
@ -101,6 +110,7 @@ where
|
|||
handler,
|
||||
default_handler,
|
||||
error_handler,
|
||||
ctrlc_handler,
|
||||
distribution_f: _,
|
||||
worker_queue_size,
|
||||
} = self;
|
||||
|
@ -111,6 +121,7 @@ where
|
|||
handler,
|
||||
default_handler,
|
||||
error_handler,
|
||||
ctrlc_handler,
|
||||
distribution_f: f,
|
||||
worker_queue_size,
|
||||
}
|
||||
|
@ -127,9 +138,10 @@ where
|
|||
error_handler,
|
||||
distribution_f,
|
||||
worker_queue_size,
|
||||
ctrlc_handler,
|
||||
} = self;
|
||||
|
||||
Dispatcher {
|
||||
let dp = Dispatcher {
|
||||
bot,
|
||||
dependencies,
|
||||
handler,
|
||||
|
@ -142,8 +154,19 @@ where
|
|||
default_worker: None,
|
||||
current_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
|
||||
}
|
||||
}
|
||||
|
||||
/// The base for update dispatching.
|
||||
|
@ -212,6 +235,7 @@ where
|
|||
Box::pin(async {})
|
||||
}),
|
||||
error_handler: LoggingErrorHandler::new(),
|
||||
ctrlc_handler: false,
|
||||
worker_queue_size: DEFAULT_WORKER_QUEUE_SIZE,
|
||||
distribution_f: default_distribution_function,
|
||||
}
|
||||
|
@ -238,7 +262,6 @@ where
|
|||
/// - [`crate::types::Me`] (can be used in [`HandlerExt::filter_command`]).
|
||||
///
|
||||
/// [`shutdown`]: ShutdownToken::shutdown
|
||||
/// [a ctrlc signal]: Dispatcher::setup_ctrlc_handler
|
||||
/// [`HandlerExt::filter_command`]: crate::dispatching::HandlerExt::filter_command
|
||||
pub async fn dispatch(&mut self)
|
||||
where
|
||||
|
@ -258,7 +281,6 @@ where
|
|||
/// This method adds the same dependencies as [`Dispatcher::dispatch`].
|
||||
///
|
||||
/// [`shutdown`]: ShutdownToken::shutdown
|
||||
/// [a ctrlc signal]: Dispatcher::setup_ctrlc_handler
|
||||
pub async fn dispatch_with_listener<'a, UListener, ListenerE, Eh>(
|
||||
&'a mut self,
|
||||
mut update_listener: UListener,
|
||||
|
@ -425,7 +447,22 @@ where
|
|||
///
|
||||
/// [`shutdown`]: ShutdownToken::shutdown
|
||||
#[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 {
|
||||
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();
|
||||
tokio::spawn(async move {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,44 +1,27 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use dptree::{description::EventKind, HandlerDescription};
|
||||
use dptree::{
|
||||
description::{EventKind, InterestSet},
|
||||
HandlerDescription,
|
||||
};
|
||||
use teloxide_core::types::AllowedUpdate;
|
||||
|
||||
/// Handler description that is used by [`Dispatcher`].
|
||||
///
|
||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
pub struct DpHandlerDescription {
|
||||
allowed: EventKind<AllowedUpdate>,
|
||||
allowed: InterestSet<Kind>,
|
||||
}
|
||||
|
||||
impl DpHandlerDescription {
|
||||
pub(crate) fn of(allowed: AllowedUpdate) -> Self {
|
||||
let mut set = HashSet::with_capacity(1);
|
||||
set.insert(allowed);
|
||||
Self { allowed: EventKind::InterestList(set) }
|
||||
set.insert(Kind(allowed));
|
||||
Self { allowed: InterestSet::new_filter(set) }
|
||||
}
|
||||
|
||||
pub(crate) fn allowed_updates(&self) -> Vec<AllowedUpdate> {
|
||||
use AllowedUpdate::*;
|
||||
|
||||
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,
|
||||
],
|
||||
}
|
||||
self.allowed.observed.iter().map(|Kind(x)| x).copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,3 +42,70 @@ impl HandlerDescription for DpHandlerDescription {
|
|||
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])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,12 @@ use teloxide_core::requests::Requester;
|
|||
///
|
||||
/// 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
|
||||
///
|
||||
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
|
||||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// bot.
|
||||
|
@ -49,7 +54,12 @@ where
|
|||
///
|
||||
/// 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
|
||||
///
|
||||
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
|
||||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// 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)),
|
||||
)
|
||||
.default_handler(ignore_update)
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
.setup_ctrlc_handler()
|
||||
.dispatch_with_listener(
|
||||
listener,
|
||||
LoggingErrorHandler::with_custom_text("An error from the update listener"),
|
||||
|
|
|
@ -11,7 +11,12 @@ use teloxide_core::requests::Requester;
|
|||
///
|
||||
/// 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,
|
||||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// bot.
|
||||
|
@ -35,7 +40,12 @@ where
|
|||
///
|
||||
/// 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
|
||||
///
|
||||
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
|
||||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// bot.
|
||||
|
@ -61,8 +71,8 @@ where
|
|||
|
||||
Dispatcher::builder(bot, Update::filter_message().chain(dptree::endpoint(handler)))
|
||||
.default_handler(ignore_update)
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
.setup_ctrlc_handler()
|
||||
.dispatch_with_listener(
|
||||
listener,
|
||||
LoggingErrorHandler::with_custom_text("An error from the update listener"),
|
||||
|
|
|
@ -272,7 +272,7 @@ impl<B> FromRequest<B> for XTelegramBotApiSecretToken {
|
|||
|
||||
let res = req
|
||||
.headers_mut()
|
||||
.and_then(|map| map.remove("x-telegram-bot-api-secret-token"))
|
||||
.remove("x-telegram-bot-api-secret-token")
|
||||
.map(|header| {
|
||||
check_secret(header.as_bytes())
|
||||
.map(<_>::to_owned)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
## Cargo features
|
||||
|
||||
| Feature | Description |
|
||||
|----------------------|------------------------------------------------------------------------------------|
|
||||
|----------------------|--------------------------------------------------------------------------------------------|
|
||||
| `webhooks` | Enables general webhook utilities (almost useless on its own) |
|
||||
| `webhooks-axum` | Enables webhook implementation based on axum framework |
|
||||
| `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**). |
|
||||
| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. |
|
||||
| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. |
|
||||
|
@ -31,4 +31,4 @@
|
|||
[`teloxide::utils::UpState`]: utils::UpState
|
||||
[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
|
|
@ -2,7 +2,7 @@
|
|||
#![allow(clippy::nonstandard_macro_braces)]
|
||||
|
||||
#[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
|
||||
// teloxide::utils::command was a failure
|
||||
|
@ -141,22 +141,33 @@ fn parse_with_split2() {
|
|||
#[test]
|
||||
#[cfg(feature = "macros")]
|
||||
fn parse_custom_parser() {
|
||||
fn custom_parse_function(s: String) -> Result<(u8, String), ParseError> {
|
||||
mod parser {
|
||||
use teloxide::utils::command::ParseError;
|
||||
|
||||
pub fn custom_parse_function(s: String) -> Result<(u8, String), ParseError> {
|
||||
let vec = s.split_whitespace().collect::<Vec<_>>();
|
||||
let (left, right) = match vec.as_slice() {
|
||||
[l, r] => (l, r),
|
||||
_ => return Err(ParseError::IncorrectFormat("might be 2 arguments!".into())),
|
||||
};
|
||||
left.parse::<u8>()
|
||||
.map(|res| (res, (*right).to_string()))
|
||||
.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)]
|
||||
#[command(rename = "lowercase")]
|
||||
enum DefaultCommands {
|
||||
#[command(parse_with = "custom_parse_function")]
|
||||
Start(u8, String),
|
||||
|
||||
// Test <https://github.com/teloxide/teloxide/issues/668>.
|
||||
#[command(parse_with = "parser::custom_parse_function")]
|
||||
TestPath(u8, String),
|
||||
|
||||
Help,
|
||||
}
|
||||
|
||||
|
@ -164,6 +175,10 @@ fn parse_custom_parser() {
|
|||
DefaultCommands::Start(10, "hello".to_string()),
|
||||
DefaultCommands::parse("/start 10 hello", "").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
DefaultCommands::TestPath(10, "hello".to_string()),
|
||||
DefaultCommands::parse("/testpath 10 hello", "").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue