mirror of
https://github.com/teloxide/teloxide.git
synced 2024-10-24 01:47:08 +02:00
Merge pull request #570 from teloxide/teloxide-handler
Implement `teloxide::handler!`
This commit is contained in:
commit
c893309bc2
7 changed files with 236 additions and 84 deletions
|
@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- The old dispatching system and related stuff: `dispatching`, `utils::UpState`, `prelude`, `repls2`, `crate::{dialogues_repl, dialogues_repl_with_listener}`, and `#[teloxide(subtransition)]` [**BC**].
|
||||
|
||||
### Added
|
||||
|
||||
- The new API for dialogue handlers: `teloxide::handler!` ([issue 567](https://github.com/teloxide/teloxide/issues/567)).
|
||||
|
||||
### Changed
|
||||
|
||||
- Rename `dispatching2` => `dispatching` [**BC**].
|
||||
|
|
|
@ -55,7 +55,7 @@ full = [
|
|||
|
||||
[dependencies]
|
||||
teloxide-core = { version = "0.4", default-features = false }
|
||||
teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros.git", rev = "9e415b1", optional = true }
|
||||
teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros.git", rev = "dfba097c7146ba6244a36001d703e04b771baa05", optional = true }
|
||||
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
39
README.md
39
README.md
|
@ -192,23 +192,16 @@ Below is a bot that asks you three questions and then sends the answers back to
|
|||
([Full](examples/dialogue.rs))
|
||||
|
||||
```rust,ignore
|
||||
use teloxide::{dispatching::dialogue::InMemStorage, macros::DialogueState, prelude::*};
|
||||
use teloxide::{dispatching::dialogue::InMemStorage, prelude::*};
|
||||
|
||||
type MyDialogue = Dialogue<State, InMemStorage<State>>;
|
||||
type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
#[derive(DialogueState, Clone)]
|
||||
#[handler_out(anyhow::Result<()>)]
|
||||
#[derive(Clone)]
|
||||
pub enum State {
|
||||
#[handler(handle_start)]
|
||||
Start,
|
||||
|
||||
#[handler(handle_receive_full_name)]
|
||||
ReceiveFullName,
|
||||
|
||||
#[handler(handle_receive_age)]
|
||||
ReceiveAge { full_name: String },
|
||||
|
||||
#[handler(handle_receive_location)]
|
||||
ReceiveLocation { full_name: String, age: u8 },
|
||||
}
|
||||
|
||||
|
@ -229,7 +222,13 @@ async fn main() {
|
|||
bot,
|
||||
Update::filter_message()
|
||||
.enter_dialogue::<Message, InMemStorage<State>, State>()
|
||||
.dispatch_by::<State>(),
|
||||
.branch(teloxide::handler![State::Start].endpoint(start))
|
||||
.branch(teloxide::handler![State::ReceiveFullName].endpoint(receive_full_name))
|
||||
.branch(teloxide::handler![State::ReceiveAge { full_name }].endpoint(receive_age))
|
||||
.branch(
|
||||
teloxide::handler![State::ReceiveLocation { full_name, age }]
|
||||
.endpoint(receive_location),
|
||||
),
|
||||
)
|
||||
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||
.build()
|
||||
|
@ -238,21 +237,17 @@ async fn main() {
|
|||
.await;
|
||||
}
|
||||
|
||||
async fn handle_start(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
) -> anyhow::Result<()> {
|
||||
async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
bot.send_message(msg.chat.id, "Let's start! What's your full name?").await?;
|
||||
dialogue.update(State::ReceiveFullName).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_receive_full_name(
|
||||
async fn receive_full_name(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> HandlerResult {
|
||||
match msg.text() {
|
||||
Some(text) => {
|
||||
bot.send_message(msg.chat.id, "How old are you?").await?;
|
||||
|
@ -266,12 +261,12 @@ async fn handle_receive_full_name(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_receive_age(
|
||||
async fn receive_age(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
(full_name,): (String,), // Available from `State::ReceiveAge`.
|
||||
) -> anyhow::Result<()> {
|
||||
) -> HandlerResult {
|
||||
match msg.text().map(|text| text.parse::<u8>()) {
|
||||
Some(Ok(age)) => {
|
||||
bot.send_message(msg.chat.id, "What's your location?").await?;
|
||||
|
@ -285,12 +280,12 @@ async fn handle_receive_age(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_receive_location(
|
||||
async fn receive_location(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
(full_name, age): (String, u8), // Available from `State::ReceiveLocation`.
|
||||
) -> anyhow::Result<()> {
|
||||
) -> HandlerResult {
|
||||
match msg.text() {
|
||||
Some(location) => {
|
||||
let message = format!("Full name: {}\nAge: {}\nLocation: {}", full_name, age, location);
|
||||
|
|
|
@ -6,9 +6,7 @@ use teloxide::{
|
|||
serializer::{Bincode, Json},
|
||||
ErasedStorage, RedisStorage, SqliteStorage, Storage,
|
||||
},
|
||||
macros::DialogueState,
|
||||
prelude::*,
|
||||
types::Me,
|
||||
utils::command::BotCommands,
|
||||
};
|
||||
|
||||
|
@ -16,13 +14,9 @@ type MyDialogue = Dialogue<State, ErasedStorage<State>>;
|
|||
type MyStorage = std::sync::Arc<ErasedStorage<State>>;
|
||||
type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
#[derive(DialogueState, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[handler_out(HandlerResult)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum State {
|
||||
#[handler(handle_start)]
|
||||
Start,
|
||||
|
||||
#[handler(handle_got_number)]
|
||||
GotNumber(i32),
|
||||
}
|
||||
|
||||
|
@ -32,7 +26,7 @@ impl Default for State {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(BotCommands)]
|
||||
#[derive(Clone, BotCommands)]
|
||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||
pub enum Command {
|
||||
#[command(description = "get your number.")]
|
||||
|
@ -56,7 +50,12 @@ async fn main() {
|
|||
|
||||
let handler = Update::filter_message()
|
||||
.enter_dialogue::<Message, ErasedStorage<State>, State>()
|
||||
.dispatch_by::<State>();
|
||||
.branch(teloxide::handler![State::Start].endpoint(start))
|
||||
.branch(
|
||||
teloxide::handler![State::GotNumber(n)]
|
||||
.branch(dptree::entry().filter_command::<Command>().endpoint(got_number))
|
||||
.branch(dptree::endpoint(invalid_command)),
|
||||
);
|
||||
|
||||
Dispatcher::builder(bot, handler)
|
||||
.dependencies(dptree::deps![storage])
|
||||
|
@ -66,48 +65,44 @@ async fn main() {
|
|||
.await;
|
||||
}
|
||||
|
||||
async fn handle_start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
match msg.text().unwrap().parse() {
|
||||
Ok(number) => {
|
||||
dialogue.update(State::GotNumber(number)).await?;
|
||||
async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
match msg.text().map(|text| text.parse::<i32>()) {
|
||||
Some(Ok(n)) => {
|
||||
dialogue.update(State::GotNumber(n)).await?;
|
||||
bot.send_message(
|
||||
msg.chat.id,
|
||||
format!("Remembered number {}. Now use /get or /reset", number),
|
||||
format!("Remembered number {}. Now use /get or /reset.", n),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
_ => {
|
||||
bot.send_message(msg.chat.id, "Please, send me a number").await?;
|
||||
bot.send_message(msg.chat.id, "Please, send me a number.").await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_got_number(
|
||||
async fn got_number(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
num: i32,
|
||||
me: Me,
|
||||
cmd: Command,
|
||||
) -> HandlerResult {
|
||||
let ans = msg.text().unwrap();
|
||||
let bot_name = me.user.username.unwrap();
|
||||
|
||||
match Command::parse(ans, bot_name) {
|
||||
Ok(cmd) => match cmd {
|
||||
Command::Get => {
|
||||
bot.send_message(msg.chat.id, format!("Here is your number: {}", num)).await?;
|
||||
}
|
||||
Command::Reset => {
|
||||
dialogue.reset().await?;
|
||||
bot.send_message(msg.chat.id, "Number resetted").await?;
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
bot.send_message(msg.chat.id, "Please, send /get or /reset").await?;
|
||||
match cmd {
|
||||
Command::Get => {
|
||||
bot.send_message(msg.chat.id, format!("Here is your number: {}.", num)).await?;
|
||||
}
|
||||
Command::Reset => {
|
||||
dialogue.reset().await?;
|
||||
bot.send_message(msg.chat.id, "Number resetted.").await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn invalid_command(bot: AutoSend<Bot>, msg: Message) -> HandlerResult {
|
||||
bot.send_message(msg.chat.id, "Please, send /get or /reset.").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -13,24 +13,16 @@
|
|||
// Age: 223
|
||||
// Location: Middle-earth
|
||||
// ```
|
||||
use teloxide::{dispatching::dialogue::InMemStorage, macros::DialogueState, prelude::*};
|
||||
use teloxide::{dispatching::dialogue::InMemStorage, prelude::*};
|
||||
|
||||
type MyDialogue = Dialogue<State, InMemStorage<State>>;
|
||||
type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
#[derive(DialogueState, Clone)]
|
||||
#[handler_out(HandlerResult)]
|
||||
#[derive(Clone)]
|
||||
pub enum State {
|
||||
#[handler(handle_start)]
|
||||
Start,
|
||||
|
||||
#[handler(handle_receive_full_name)]
|
||||
ReceiveFullName,
|
||||
|
||||
#[handler(handle_receive_age)]
|
||||
ReceiveAge { full_name: String },
|
||||
|
||||
#[handler(handle_receive_location)]
|
||||
ReceiveLocation { full_name: String, age: u8 },
|
||||
}
|
||||
|
||||
|
@ -51,7 +43,13 @@ async fn main() {
|
|||
bot,
|
||||
Update::filter_message()
|
||||
.enter_dialogue::<Message, InMemStorage<State>, State>()
|
||||
.dispatch_by::<State>(),
|
||||
.branch(teloxide::handler![State::Start].endpoint(start))
|
||||
.branch(teloxide::handler![State::ReceiveFullName].endpoint(receive_full_name))
|
||||
.branch(teloxide::handler![State::ReceiveAge { full_name }].endpoint(receive_age))
|
||||
.branch(
|
||||
teloxide::handler![State::ReceiveLocation { full_name, age }]
|
||||
.endpoint(receive_location),
|
||||
),
|
||||
)
|
||||
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||
.build()
|
||||
|
@ -60,13 +58,13 @@ async fn main() {
|
|||
.await;
|
||||
}
|
||||
|
||||
async fn handle_start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
bot.send_message(msg.chat.id, "Let's start! What's your full name?").await?;
|
||||
dialogue.update(State::ReceiveFullName).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_receive_full_name(
|
||||
async fn receive_full_name(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
|
@ -84,11 +82,11 @@ async fn handle_receive_full_name(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_receive_age(
|
||||
async fn receive_age(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
(full_name,): (String,), // Available from `State::ReceiveAge`.
|
||||
full_name: String, // Available from `State::ReceiveAge`.
|
||||
) -> HandlerResult {
|
||||
match msg.text().map(|text| text.parse::<u8>()) {
|
||||
Some(Ok(age)) => {
|
||||
|
@ -103,7 +101,7 @@ async fn handle_receive_age(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_receive_location(
|
||||
async fn receive_location(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
|
|
|
@ -13,28 +13,20 @@
|
|||
//! dialogues. Your dialogue state can be represented as an enumeration:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[derive(DialogueState, Clone)]
|
||||
//! #[handler_out(anyhow::Result<()>)]
|
||||
//! #[derive(Clone)]
|
||||
//! pub enum State {
|
||||
//! #[handler(handle_start)]
|
||||
//! Start,
|
||||
//!
|
||||
//! #[handler(handle_receive_full_name)]
|
||||
//! ReceiveFullName,
|
||||
//!
|
||||
//! #[handler(handle_receive_age)]
|
||||
//! ReceiveAge { full_name: String },
|
||||
//!
|
||||
//! #[handler(handle_receive_location)]
|
||||
//! ReceiveLocation { full_name: String, age: u8 },
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Each state is associated with its respective handler: e.g., when a dialogue
|
||||
//! state is `ReceiveAge`, `handle_receive_age` is invoked:
|
||||
//! state is `ReceiveAge`, `receive_age` is invoked:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! async fn handle_receive_age(
|
||||
//! async fn receive_age(
|
||||
//! bot: AutoSend<Bot>,
|
||||
//! msg: Message,
|
||||
//! dialogue: MyDialogue,
|
||||
|
@ -61,7 +53,7 @@
|
|||
//! the inner storage:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! async fn handle_receive_location(
|
||||
//! async fn receive_location(
|
||||
//! bot: AutoSend<Bot>,
|
||||
//! msg: Message,
|
||||
//! dialogue: MyDialogue,
|
||||
|
@ -179,3 +171,163 @@ where
|
|||
self.storage.clone().remove_dialogue(self.chat_id).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a dialogue FSM transition.
|
||||
///
|
||||
/// This macro expands to a [`dptree::Handler`] that filters your dialogue
|
||||
/// state: if the state enumeration is of a certain variant, the execution
|
||||
/// continues; otherwise, `dptree` will try the next branch.
|
||||
///
|
||||
/// Variants can take the following forms:
|
||||
///
|
||||
/// - `State::MyVariant` for empty variants;
|
||||
/// - `State::MyVariant(param1, ..., paramN)` for function-like variants;
|
||||
/// - `State::MyVariant { param1, ..., paramN }` for `struct`-like variants.
|
||||
///
|
||||
/// In the first case, this macro results in a simple [`dptree::filter`]; in the
|
||||
/// second and third cases, this macro results in [`dptree::filter_map`] that
|
||||
/// passes the payload of `MyVariant` to the next handler if the match occurs.
|
||||
/// (This next handler can be an endpoint or a more complex one.) The payload
|
||||
/// format depend on the form of `MyVariant`:
|
||||
///
|
||||
/// - For `State::MyVariant(param)` and `State::MyVariant { param }`, the
|
||||
/// payload is `param`.
|
||||
/// - For `State::MyVariant(param,)` and `State::MyVariant { param, }`, the
|
||||
/// payload is `(param,)`.
|
||||
/// - For `State::MyVariant(param1, ..., paramN)` and `State::MyVariant {
|
||||
/// param1, ..., paramN }`, the payload is `(param1, ..., paramN)` (where `N`
|
||||
/// > 1).
|
||||
///
|
||||
/// ## Dependency requirements
|
||||
///
|
||||
/// - Your dialogue state enumeration `State`.
|
||||
#[macro_export]
|
||||
macro_rules! handler {
|
||||
($($variant:ident)::+) => {
|
||||
$crate::dptree::filter(|state| matches!(state, $($variant)::+))
|
||||
};
|
||||
($($variant:ident)::+ ($param:ident)) => {
|
||||
$crate::dptree::filter_map(|state| match state {
|
||||
$($variant)::+($param) => Some($param),
|
||||
_ => None,
|
||||
})
|
||||
};
|
||||
($($variant:ident)::+ ($($param:ident),+ $(,)?)) => {
|
||||
$crate::dptree::filter_map(|state| match state {
|
||||
$($variant)::+($($param),+) => Some(($($param),+ ,)),
|
||||
_ => None,
|
||||
})
|
||||
};
|
||||
($($variant:ident)::+ {$param:ident}) => {
|
||||
$crate::dptree::filter_map(|state| match state {
|
||||
$($variant)::+{$param} => Some($param),
|
||||
_ => None,
|
||||
})
|
||||
};
|
||||
($($variant:ident)::+ {$($param:ident),+ $(,)?}) => {
|
||||
$crate::dptree::filter_map(|state| match state {
|
||||
$($variant)::+ { $($param),+ } => Some(($($param),+ ,)),
|
||||
_ => None,
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
enum State {
|
||||
A,
|
||||
B(i32),
|
||||
C(i32, &'static str),
|
||||
D { foo: i32 },
|
||||
E { foo: i32, bar: &'static str },
|
||||
Other,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_empty_variant() {
|
||||
let input = State::A;
|
||||
let h = handler![State::A].endpoint(|| async move { 123 });
|
||||
|
||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_single_fn_variant() {
|
||||
let input = State::B(42);
|
||||
let h = handler![State::B(x)].endpoint(|x: i32| async move {
|
||||
assert_eq!(x, 42);
|
||||
123
|
||||
});
|
||||
|
||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_single_fn_variant_trailing_comma() {
|
||||
let input = State::B(42);
|
||||
let h = handler![State::B(x,)].endpoint(|(x,): (i32,)| async move {
|
||||
assert_eq!(x, 42);
|
||||
123
|
||||
});
|
||||
|
||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_fn_variant() {
|
||||
let input = State::C(42, "abc");
|
||||
let h = handler![State::C(x, y)].endpoint(|(x, str): (i32, &'static str)| async move {
|
||||
assert_eq!(x, 42);
|
||||
assert_eq!(str, "abc");
|
||||
123
|
||||
});
|
||||
|
||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_single_struct_variant() {
|
||||
let input = State::D { foo: 42 };
|
||||
let h = handler![State::D { foo }].endpoint(|x: i32| async move {
|
||||
assert_eq!(x, 42);
|
||||
123
|
||||
});
|
||||
|
||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_single_struct_variant_trailing_comma() {
|
||||
let input = State::D { foo: 42 };
|
||||
#[rustfmt::skip] // rustfmt removes the trailing comma from `State::D { foo, }`, but it plays a vital role in this test.
|
||||
let h = handler![State::D { foo, }].endpoint(|(x,): (i32,)| async move {
|
||||
assert_eq!(x, 42);
|
||||
123
|
||||
});
|
||||
|
||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_struct_variant() {
|
||||
let input = State::E { foo: 42, bar: "abc" };
|
||||
let h =
|
||||
handler![State::E { foo, bar }].endpoint(|(x, str): (i32, &'static str)| async move {
|
||||
assert_eq!(x, 42);
|
||||
assert_eq!(str, "abc");
|
||||
123
|
||||
});
|
||||
|
||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ use teloxide_core::requests::Requester;
|
|||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// bot.
|
||||
///
|
||||
/// ## Dependency requirements
|
||||
///
|
||||
/// - Those of [`HandlerExt::filter_command`].
|
||||
///
|
||||
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
|
||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
#[cfg(feature = "ctrlc_handler")]
|
||||
|
@ -50,6 +54,10 @@ where
|
|||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// bot.
|
||||
///
|
||||
/// ## Dependency requirements
|
||||
///
|
||||
/// - Those of [`HandlerExt::filter_command`].
|
||||
///
|
||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
/// [`commands_repl`]: crate::dispatching::repls::commands_repl()
|
||||
/// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener
|
||||
|
|
Loading…
Reference in a new issue