diff --git a/CHANGELOG.md b/CHANGELOG.md index 7687aa9b..5f6c5e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +### Added + + - `teloxide::dispatching::repls::CommandReplExt`, `teloxide::prelude::CommandReplExt` ([issue #740](https://github.com/teloxide/teloxide/issues/740)) + +### Deprecated + + - `teloxide::dispatching::repls::{commands_repl, commands_repl_with_listener}`, `teloxide::utils::command::BotCommands::ty` (use `CommandReplExt` instead) + ## 0.11.0 - 2022-10-07 ### Changed diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 1449cca1..937320a7 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -1,6 +1,22 @@ 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.10 -> 0.xxx.xxx + +### teloxide + +We have introduced the new trait `CommandRepl` that replaces the old `commands_repl_(with_listener)` functions: + +```diff,rust +- teloxide::commands_repl(bot, answer, Command::ty()) ++ Command::repl(bot, answer) +``` + +```diff,rust +- teloxide::commands_repl_with_listener(bot, answer, listener, Command::ty()) ++ Command::repl_with_listener(bot, answer, listener) +``` + ## 0.10 -> 0.11 ### core diff --git a/README.md b/README.md index 530dbdd9..17f5b904 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ async fn main() { let bot = Bot::from_env(); - teloxide::commands_repl(bot, answer, Command::ty()).await; + Command::repl(bot, answer).await; } #[derive(BotCommands, Clone)] diff --git a/examples/admin.rs b/examples/admin.rs index 17f7373f..113d6f07 100644 --- a/examples/admin.rs +++ b/examples/admin.rs @@ -60,7 +60,7 @@ async fn main() { let bot = teloxide::Bot::from_env(); - teloxide::commands_repl(bot, action, Command::ty()).await; + Command::repl(bot, action).await; } async fn action(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> { diff --git a/examples/command.rs b/examples/command.rs index 00f44315..26848015 100644 --- a/examples/command.rs +++ b/examples/command.rs @@ -7,7 +7,7 @@ async fn main() { let bot = Bot::from_env(); - teloxide::commands_repl(bot, answer, Command::ty()).await; + Command::repl(bot, answer).await; } #[derive(BotCommands, Clone)] diff --git a/src/dispatching/repls.rs b/src/dispatching/repls.rs index 71d70343..aea4806e 100644 --- a/src/dispatching/repls.rs +++ b/src/dispatching/repls.rs @@ -11,5 +11,7 @@ mod commands_repl; mod repl; +pub use commands_repl::CommandReplExt; +#[allow(deprecated)] pub use commands_repl::{commands_repl, commands_repl_with_listener}; pub use repl::{repl, repl_with_listener}; diff --git a/src/dispatching/repls/commands_repl.rs b/src/dispatching/repls/commands_repl.rs index fe9d9fb7..15f2396d 100644 --- a/src/dispatching/repls/commands_repl.rs +++ b/src/dispatching/repls/commands_repl.rs @@ -8,8 +8,147 @@ use crate::{ utils::command::BotCommands, }; use dptree::di::{DependencyMap, Injectable}; +use futures::future::BoxFuture; use std::{fmt::Debug, marker::PhantomData}; +/// A [REPL] for commands. +/// +/// 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`]. See also: ["Dispatching or +/// REPLs?"](../index.html#dispatching-or-repls). +/// +/// [`Dispatcher`]: crate::dispatching::Dispatcher +/// +/// All errors from the handler and update listener will be logged. +/// +/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop +/// +/// This trait extends your [`BotCommands`] type with REPL facilities. +/// +/// ## Signatures +/// +/// Don't be scared by many trait bounds in the signatures, in essence they +/// require: +/// +/// 1. `bot` is a bot, client for the Telegram bot API. It is represented via +/// the [`Requester`] trait. +/// 2. `handler` is an `async` function that takes arguments from +/// [`DependencyMap`] (see below) and returns [`ResponseResult`]. +/// 3. `listener` is something that takes updates from a Telegram server and +/// implements [`UpdateListener`]. +/// +/// All the other requirements are about thread safety and data validity and can +/// be ignored for most of the time. +/// +/// ## Handler arguments +/// +/// `teloxide` provides the following types to the `handler`: +/// - [`Message`] +/// - `R` (type of the `bot`) +/// - `Cmd` (type of the parsed command) +/// - [`Me`] +/// +/// Each of these types can be accepted as a handler parameter. Note that they +/// aren't all required at the same time: e.g., you can take only the bot and +/// the command without [`Me`] and [`Message`]. +/// +/// [`Me`]: crate::types::Me +/// [`Message`]: crate::types::Message +/// +/// ## Stopping +// +#[doc = include_str!("stopping.md")] +/// +/// ## Caution +// +#[doc = include_str!("caution.md")] +/// +#[cfg(feature = "ctrlc_handler")] +pub trait CommandReplExt { + /// A REPL for commands. + /// + /// See [`CommandReplExt`] for more details. + #[must_use] + fn repl<'a, R, H, Args>(bot: R, handler: H) -> BoxFuture<'a, ()> + where + R: Requester + Clone + Send + Sync + 'static, + ::GetUpdates: Send, + ::GetWebhookInfo: Send, + ::GetMe: Send, + ::DeleteWebhook: Send, + H: Injectable, Args> + Send + Sync + 'static; + + /// A REPL for commands with a custom [`UpdateListener`]. + /// + /// See [`CommandReplExt`] for more details. + #[must_use] + fn repl_with_listener<'a, R, H, L, Args>(bot: R, handler: H, listener: L) -> BoxFuture<'a, ()> + where + H: Injectable, Args> + Send + Sync + 'static, + L: UpdateListener + Send + 'a, + L::Err: Debug + Send + 'a, + R: Requester + Clone + Send + Sync + 'static, + ::GetMe: Send; +} + +#[cfg(feature = "ctrlc_handler")] +impl CommandReplExt for Cmd +where + Cmd: BotCommands + Send + Sync + 'static, +{ + fn repl<'a, R, H, Args>(bot: R, handler: H) -> BoxFuture<'a, ()> + where + R: Requester + Clone + Send + Sync + 'static, + ::GetUpdates: Send, + ::GetWebhookInfo: Send, + ::GetMe: Send, + ::DeleteWebhook: Send, + H: Injectable, Args> + Send + Sync + 'static, + { + let cloned_bot = bot.clone(); + + Box::pin(async move { + Self::repl_with_listener( + bot, + handler, + update_listeners::polling_default(cloned_bot).await, + ) + .await + }) + } + + fn repl_with_listener<'a, R, H, L, Args>(bot: R, handler: H, listener: L) -> BoxFuture<'a, ()> + where + H: Injectable, Args> + Send + Sync + 'static, + L: UpdateListener + Send + 'a, + L::Err: Debug + Send + 'a, + R: Requester + Clone + Send + Sync + 'static, + ::GetMe: Send, + { + use crate::dispatching::Dispatcher; + + // Other update types are of no interest to use since this REPL is only for + // commands. See . + let ignore_update = |_upd| Box::pin(async {}); + + Box::pin(async move { + Dispatcher::builder( + bot, + Update::filter_message().filter_command::().endpoint(handler), + ) + .default_handler(ignore_update) + .enable_ctrlc_handler() + .build() + .dispatch_with_listener( + listener, + LoggingErrorHandler::with_custom_text("An error from the update listener"), + ) + .await + }) + } +} + /// A [REPL] for commands. // /// @@ -59,6 +198,7 @@ use std::{fmt::Debug, marker::PhantomData}; #[doc = include_str!("caution.md")] /// #[cfg(feature = "ctrlc_handler")] +#[deprecated(note = "Use `CommandsRepl::repl` instead")] pub async fn commands_repl<'a, R, Cmd, H, Args>(bot: R, handler: H, cmd: PhantomData) where R: Requester + Clone + Send + Sync + 'static, @@ -68,6 +208,7 @@ where { let cloned_bot = bot.clone(); + #[allow(deprecated)] commands_repl_with_listener( bot, handler, @@ -127,6 +268,7 @@ where #[doc = include_str!("caution.md")] /// #[cfg(feature = "ctrlc_handler")] +#[deprecated(note = "Use `CommandsRepl::repl_with_listener` instead")] pub async fn commands_repl_with_listener<'a, R, Cmd, H, L, Args>( bot: R, handler: H, diff --git a/src/lib.rs b/src/lib.rs index 80237a9d..04012a60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,9 +58,10 @@ #![allow(clippy::nonstandard_macro_braces)] #[cfg(feature = "ctrlc_handler")] -pub use dispatching::repls::{ - commands_repl, commands_repl_with_listener, repl, repl_with_listener, -}; +pub use dispatching::repls::{repl, repl_with_listener}; + +#[allow(deprecated)] +pub use dispatching::repls::{commands_repl, commands_repl_with_listener}; pub mod dispatching; pub mod error_handlers; diff --git a/src/prelude.rs b/src/prelude.rs index 6dbe94df..ca6afa90 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -6,7 +6,8 @@ pub use crate::error_handlers::{LoggingErrorHandler, OnError}; pub use crate::respond; pub use crate::dispatching::{ - dialogue::Dialogue, Dispatcher, HandlerExt as _, MessageFilterExt as _, UpdateFilterExt as _, + dialogue::Dialogue, repls::CommandReplExt as _, Dispatcher, HandlerExt as _, + MessageFilterExt as _, UpdateFilterExt as _, }; pub use teloxide_core::{ diff --git a/src/utils/command.rs b/src/utils/command.rs index 785479d7..8f96998e 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -235,6 +235,7 @@ pub trait BotCommands: Sized { /// /// [`commands_repl`]: (crate::repls::commands_repl) #[must_use] + #[deprecated(note = "Use `CommandReplExt` instead")] fn ty() -> PhantomData { PhantomData }