teloxide/repls/commands_repl.rs
1use std::fmt::Debug;
2
3use dptree::di::Injectable;
4use futures::future::BoxFuture;
5
6use crate::{
7 dispatching::{HandlerExt, UpdateFilterExt},
8 error_handlers::LoggingErrorHandler,
9 requests::{Requester, ResponseResult},
10 types::Update,
11 update_listeners::{self, UpdateListener},
12 utils::command::BotCommands,
13};
14
15/// A [REPL] for commands.
16///
17/// REPLs are meant only for simple bots and rapid prototyping. If you need to
18/// supply dependencies or describe more complex dispatch logic, please use
19/// [`Dispatcher`]. See also: ["Dispatching or
20/// REPLs?"](../index.html#dispatching-or-repls).
21///
22/// [`Dispatcher`]: crate::dispatching::Dispatcher
23///
24/// All errors from the handler and update listener will be logged.
25///
26/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
27///
28/// This trait extends your [`BotCommands`] type with REPL facilities.
29///
30/// ## Signatures
31///
32/// Don't be scared by many trait bounds in the signatures, in essence they
33/// require:
34///
35/// 1. `bot` is a bot, client for the Telegram bot API. It is represented via
36/// the [`Requester`] trait.
37/// 2. `handler` is an `async` function that takes arguments from
38/// [`DependencyMap`] (see below) and returns [`ResponseResult`].
39/// 3. `listener` is something that takes updates from a Telegram server and
40/// implements [`UpdateListener`].
41///
42/// All the other requirements are about thread safety and data validity and can
43/// be ignored for most of the time.
44///
45/// ## Handler arguments
46///
47/// `teloxide` provides the following types to the `handler`:
48/// - [`Message`]
49/// - `R` (type of the `bot`)
50/// - `Cmd` (type of the parsed command)
51/// - [`Me`]
52///
53/// Each of these types can be accepted as a handler parameter. Note that they
54/// aren't all required at the same time: e.g., you can take only the bot and
55/// the command without [`Me`] and [`Message`].
56///
57/// [`Me`]: crate::types::Me
58/// [`Message`]: crate::types::Message
59/// [`DependencyMap`]: dptree::di::DependencyMap
60///
61/// ## Stopping
62//
63#[doc = include_str!("stopping.md")]
64///
65/// ## Caution
66//
67#[doc = include_str!("caution.md")]
68///
69#[cfg(feature = "ctrlc_handler")]
70pub trait CommandReplExt {
71 /// A REPL for commands.
72 ///
73 /// See [`CommandReplExt`] for more details.
74 #[must_use]
75 fn repl<'a, R, H, Args>(bot: R, handler: H) -> BoxFuture<'a, ()>
76 where
77 R: Requester + Clone + Send + Sync + 'static,
78 <R as Requester>::GetUpdates: Send,
79 <R as Requester>::GetWebhookInfo: Send,
80 <R as Requester>::GetMe: Send,
81 <R as Requester>::DeleteWebhook: Send,
82 H: Injectable<ResponseResult<()>, Args> + Send + Sync + 'static;
83
84 /// A REPL for commands with a custom [`UpdateListener`].
85 ///
86 /// See [`CommandReplExt`] for more details.
87 #[must_use]
88 fn repl_with_listener<'a, R, H, L, Args>(bot: R, handler: H, listener: L) -> BoxFuture<'a, ()>
89 where
90 H: Injectable<ResponseResult<()>, Args> + Send + Sync + 'static,
91 L: UpdateListener + Send + 'a,
92 L::Err: Debug + Send + 'a,
93 R: Requester + Clone + Send + Sync + 'static,
94 <R as Requester>::GetMe: Send;
95}
96
97#[cfg(feature = "ctrlc_handler")]
98impl<Cmd> CommandReplExt for Cmd
99where
100 Cmd: BotCommands + Send + Sync + 'static,
101{
102 fn repl<'a, R, H, Args>(bot: R, handler: H) -> BoxFuture<'a, ()>
103 where
104 R: Requester + Clone + Send + Sync + 'static,
105 <R as Requester>::GetUpdates: Send,
106 <R as Requester>::GetWebhookInfo: Send,
107 <R as Requester>::GetMe: Send,
108 <R as Requester>::DeleteWebhook: Send,
109 H: Injectable<ResponseResult<()>, Args> + Send + Sync + 'static,
110 {
111 let cloned_bot = bot.clone();
112
113 Box::pin(async move {
114 Self::repl_with_listener(
115 bot,
116 handler,
117 update_listeners::polling_default(cloned_bot).await,
118 )
119 .await
120 })
121 }
122
123 fn repl_with_listener<'a, R, H, L, Args>(bot: R, handler: H, listener: L) -> BoxFuture<'a, ()>
124 where
125 H: Injectable<ResponseResult<()>, Args> + Send + Sync + 'static,
126 L: UpdateListener + Send + 'a,
127 L::Err: Debug + Send + 'a,
128 R: Requester + Clone + Send + Sync + 'static,
129 <R as Requester>::GetMe: Send,
130 {
131 use crate::dispatching::Dispatcher;
132
133 // Other update types are of no interest to use since this REPL is only for
134 // commands. See <https://github.com/teloxide/teloxide/issues/557>.
135 let ignore_update = |_upd| Box::pin(async {});
136
137 Box::pin(async move {
138 Dispatcher::builder(
139 bot,
140 Update::filter_message().filter_command::<Cmd>().endpoint(handler),
141 )
142 .default_handler(ignore_update)
143 .enable_ctrlc_handler()
144 .build()
145 .dispatch_with_listener(
146 listener,
147 LoggingErrorHandler::with_custom_text("An error from the update listener"),
148 )
149 .await
150 })
151 }
152}