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}