diff --git a/README.md b/README.md index 5cdd699a..dee44aeb 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,6 @@ - [Dialogues](https://github.com/teloxide/teloxide#dialogues) - [Recommendations](https://github.com/teloxide/teloxide#recommendations) - [FAQ](https://github.com/teloxide/teloxide#faq) - - [Where I can ask questions?](https://github.com/teloxide/teloxide#where-i-can-ask-questions) - - [Why Rust?](https://github.com/teloxide/teloxide#why-rust) - - [Can I use different loggers?](https://github.com/teloxide/teloxide#can-i-use-different-loggers) - [Community bots](https://github.com/teloxide/teloxide#community-bots) - [Contributing](https://github.com/teloxide/teloxide#contributing) @@ -225,11 +222,6 @@ pub type Dialogue = Coprod!( ReceiveAgeState, ReceiveFavouriteMusicState, ); - -wrap_dialogue!( - Wrapper(Dialogue), - default Self(Dialogue::inject(StartState)), -); ``` The [`wrap_dialogue!`](https://docs.rs/teloxide/latest/teloxide/macro.wrap_dialogue.html) macro generates a new-type of `Dialogue` with a default implementation. @@ -348,7 +340,7 @@ async fn main() { [start, receive_full_name, receive_age, receive_favourite_music] ) .expect("Something wrong with the bot!") - })) + }, || Dialogue::inject(StartState))) .dispatch() .await; } @@ -382,24 +374,32 @@ async fn main() { The second one produces very strange compiler messages because of the `#[tokio::main]` macro. However, the examples in this README use the second variant for brevity. ## FAQ -### Where I can ask questions? -[Issues](https://github.com/teloxide/teloxide/issues) is a good place for well-formed questions, for example, about the library design, enhancements, bug reports. But if you can't compile your bot due to compilation errors and need quick help, feel free to ask in [our official group](https://t.me/teloxide). +Q: Where I can ask questions? -### Why Rust? -Most programming languages have their own implementations of Telegram bots frameworks, so why not Rust? We think Rust provides enough good ecosystem and the language itself to be suitable for writing bots. +A: [Issues](https://github.com/teloxide/teloxide/issues) is a good place for well-formed questions, for example, about the library design, enhancements, bug reports. But if you can't compile your bot due to compilation errors and need quick help, feel free to ask in [our official group](https://t.me/teloxide). -### Can I use webhooks? -teloxide doesn't provide special API for working with webhooks due to their nature with lots of subtle settings. Instead, you setup your webhook by yourself, as shown in [webhook_ping_pong_bot](examples/ngrok_ping_pong_bot/src/main.rs). +Q: Why Rust? + +A: Most programming languages have their own implementations of Telegram bots frameworks, so why not Rust? We think Rust provides enough good ecosystem and the language itself to be suitable for writing bots. + +Q: Can I use webhooks? + +A: teloxide doesn't provide special API for working with webhooks due to their nature with lots of subtle settings. Instead, you setup your webhook by yourself, as shown in [webhook_ping_pong_bot](examples/ngrok_ping_pong_bot/src/main.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) -### Can I use different loggers? -Of course, you can. The [`enable_logging!`](https://docs.rs/teloxide/latest/teloxide/macro.enable_logging.html) and [`enable_logging_with_filter!`](https://docs.rs/teloxide/latest/teloxide/macro.enable_logging_with_filter.html) macros are just convenient utilities, not necessary to use them. You can setup a different logger, for example, [fern](https://crates.io/crates/fern), as usual, e.g. teloxide has no specific requirements as it depends only on [log](https://crates.io/crates/log). +Q: Can I use different loggers? + +A: Of course, you can. The [`enable_logging!`](https://docs.rs/teloxide/latest/teloxide/macro.enable_logging.html) and [`enable_logging_with_filter!`](https://docs.rs/teloxide/latest/teloxide/macro.enable_logging_with_filter.html) macros are just convenient utilities, not necessary to use them. You can setup a different logger, for example, [fern](https://crates.io/crates/fern), as usual, e.g. teloxide has no specific requirements as it depends only on [log](https://crates.io/crates/log). ## Community bots -Feel free to push your own bot into our collection: https://github.com/teloxide/community-bots. Later you will be able to play with them right in our official chat: https://t.me/teloxide. +Feel free to push your own bot into our collection! + + - [Rust subreddit reader](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader) + - [with_webserver - An example of the teloxide + warp combination](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/with_webserver) + - [vzmuinebot - Telegram bot for food menu navigate](https://github.com/ArtHome12/vzmuinebot) ## Contributing See [CONRIBUTING.md](https://github.com/teloxide/teloxide/blob/master/CONTRIBUTING.md). diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index fac687fb..a907142d 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -38,18 +38,21 @@ async fn run() { let bot = Bot::from_env(); Dispatcher::new(bot) - .messages_handler(DialogueDispatcher::new(|cx| async move { - let DialogueWithCx { cx, dialogue } = cx; + .messages_handler(DialogueDispatcher::new( + |cx| async move { + let DialogueWithCx { cx, dialogue } = cx; - // Unwrap without panic because of std::convert::Infallible. - let Wrapper(dialogue) = dialogue.unwrap(); + // Unwrap without panic because of std::convert::Infallible. + let dialogue = dialogue.unwrap(); - dispatch!( + dispatch!( [cx, dialogue] -> [start, receive_full_name, receive_age, receive_favourite_music] ) .expect("Something wrong with the bot!") - })) + }, + || Dialogue::inject(StartState), + )) .dispatch() .await; } diff --git a/examples/dialogue_bot/src/states.rs b/examples/dialogue_bot/src/states.rs index c920392a..e40d2778 100644 --- a/examples/dialogue_bot/src/states.rs +++ b/examples/dialogue_bot/src/states.rs @@ -42,8 +42,3 @@ pub type Dialogue = Coprod!( ReceiveAgeState, ReceiveFavouriteMusicState, ); - -wrap_dialogue!( - Wrapper(Dialogue), - default Self(Dialogue::inject(StartState)), -); diff --git a/examples/dialogue_bot/src/transitions.rs b/examples/dialogue_bot/src/transitions.rs index 4a20fafb..9960bc8d 100644 --- a/examples/dialogue_bot/src/transitions.rs +++ b/examples/dialogue_bot/src/transitions.rs @@ -3,7 +3,7 @@ use teloxide::prelude::*; use super::{favourite_music::FavouriteMusic, states::*}; pub type In = TransitionIn; -pub type Out = TransitionOut; +pub type Out = TransitionOut; pub async fn start(cx: In) -> Out { let (cx, dialogue) = cx.unpack(); diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index 385545c2..91f473b6 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -26,6 +26,7 @@ use std::sync::{Arc, Mutex}; pub struct DialogueDispatcher { storage: Arc, handler: Arc, + default: Arc D + Send + Sync + 'static>, _phantom: PhantomData>, /// A lock-free map to handle updates from the same chat sequentially, but @@ -41,17 +42,22 @@ impl DialogueDispatcher, H, Upd> where H: DialogueDispatcherHandler + Send + Sync + 'static, Upd: GetChatId + Send + 'static, - D: Default + Send + 'static, + D: Send + 'static, { - /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] - /// (a default storage). + /// Creates a dispatcher with the specified `handler, [`InMemStorage`] + /// (a default storage), and a function that returns a default dialogue, + /// used when a user initiates a new dialogue. /// /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage #[must_use] - pub fn new(handler: H) -> Self { + pub fn new(handler: H, default: F) -> Self + where + F: Fn() -> D + Send + Sync + 'static, + { Self { storage: InMemStorage::new(), handler: Arc::new(handler), + default: Arc::new(default), senders: Arc::new(Map::new()), _phantom: PhantomData, } @@ -62,16 +68,22 @@ impl DialogueDispatcher where H: DialogueDispatcherHandler + Send + Sync + 'static, Upd: GetChatId + Send + 'static, - D: Default + Send + 'static, + D: Send + 'static, S: Storage + Send + Sync + 'static, S::Error: Send + 'static, { - /// Creates a dispatcher with the specified `handler` and `storage`. + /// Creates a dispatcher with the specified `handler`, `storage`, and a + /// function that returns a default dialogue, used when a user initiates a + /// new dialogue. #[must_use] - pub fn with_storage(handler: H, storage: Arc) -> Self { + pub fn with_storage(handler: H, storage: Arc, default: F) -> Self + where + F: Fn() -> D + Send + Sync + 'static, + { Self { storage, handler: Arc::new(handler), + default: Arc::new(default), senders: Arc::new(Map::new()), _phantom: PhantomData, } @@ -83,11 +95,13 @@ where let storage = Arc::clone(&self.storage); let handler = Arc::clone(&self.handler); + let default = Arc::clone(&self.default); let senders = Arc::clone(&self.senders); tokio::spawn(rx.for_each(move |cx: UpdateWithCx| { let storage = Arc::clone(&storage); let handler = Arc::clone(&handler); + let default = Arc::clone(&default); let senders = Arc::clone(&senders); async move { @@ -96,7 +110,7 @@ where let dialogue = Arc::clone(&storage) .remove_dialogue(chat_id) .await - .map(Option::unwrap_or_default); + .map(|opt| opt.unwrap_or_else(move || default())); match handler.handle(DialogueWithCx { cx, dialogue }).await { DialogueStage::Next(new_dialogue) => { @@ -131,7 +145,7 @@ impl DispatcherHandler for DialogueDispatcher where H: DialogueDispatcherHandler + Send + Sync + 'static, Upd: GetChatId + Send + 'static, - D: Default + Send + 'static, + D: Send + 'static, S: Storage + Send + Sync + 'static, S::Error: Send + 'static, { @@ -189,7 +203,7 @@ mod tests { #[tokio::test] async fn updates_from_same_chat_executed_sequentially() { - #[derive(Debug)] + #[derive(Debug, Default)] struct MyUpdate { chat_id: i64, unique_number: u32, @@ -232,6 +246,7 @@ mod tests { DialogueStage::Next(()) }, + || (), ); let updates = stream::iter( diff --git a/src/dispatching/dialogue/dialogue_stage.rs b/src/dispatching/dialogue/dialogue_stage.rs index 2fd3c8e7..7778e334 100644 --- a/src/dispatching/dialogue/dialogue_stage.rs +++ b/src/dispatching/dialogue/dialogue_stage.rs @@ -11,29 +11,21 @@ pub enum DialogueStage { Exit, } -/// A dialogue wrapper to bypass orphan rules. -pub trait DialogueWrapper { - fn new(dialogue: D) -> Self; -} - /// Returns a new dialogue state. /// /// See [the module-level documentation for the design /// overview](crate::dispatching::dialogue). -pub fn next( - new_state: State, -) -> TransitionOut +pub fn next(new_state: State) -> TransitionOut where Dialogue: CoprodInjector, - DWrapper: DialogueWrapper, { - Ok(DialogueStage::Next(DWrapper::new(Dialogue::inject(new_state)))) + Ok(DialogueStage::Next(Dialogue::inject(new_state))) } /// Exits a dialogue. /// /// See [the module-level documentation for the design /// overview](crate::dispatching::dialogue). -pub fn exit() -> TransitionOut { +pub fn exit() -> TransitionOut { Ok(DialogueStage::Exit) } diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 297f2c1d..da806612 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -52,7 +52,7 @@ mod storage; use crate::{requests::ResponseResult, types::Message}; pub use dialogue_dispatcher::DialogueDispatcher; pub use dialogue_dispatcher_handler::DialogueDispatcherHandler; -pub use dialogue_stage::{exit, next, DialogueStage, DialogueWrapper}; +pub use dialogue_stage::{exit, next, DialogueStage}; pub use dialogue_with_cx::DialogueWithCx; pub use get_chat_id::GetChatId; @@ -79,13 +79,8 @@ pub use storage::{InMemStorage, Storage}; /// ReceiveNumberState, /// ); /// -/// wrap_dialogue!( -/// Wrapper(Dialogue), -/// default Self(Dialogue::inject(StartState)), -/// ); -/// /// pub type In = TransitionIn; -/// pub type Out = TransitionOut; +/// pub type Out = TransitionOut; /// /// pub async fn start(cx: In) -> Out { todo!() } /// pub async fn receive_word(cx: In) -> Out { todo!() } @@ -126,59 +121,6 @@ macro_rules! dispatch { }; } -/// Generates a dialogue wrapper and implements `Default` for it. -/// -/// The reason is to bypass orphan rules to be able to pass a user-defined -/// dialogue into [`DialogueDispatcher`]. Since a dialogue is -/// [`frunk::Coproduct`], we cannot directly satisfy the `D: Default` -/// constraint. -/// -/// # Examples -/// ``` -/// use teloxide::prelude::*; -/// -/// struct StartState; -/// struct ReceiveWordState; -/// struct ReceiveNumberState; -/// struct ExitState; -/// -/// type Dialogue = Coprod!( -/// StartState, -/// ReceiveWordState, -/// ReceiveNumberState, -/// ); -/// -/// wrap_dialogue!( -/// Wrapper(Dialogue), -/// default Self(Dialogue::inject(StartState)), -/// ); -/// -/// let start_state = Wrapper::default(); -/// ``` -/// -/// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher -/// [`frunk::Coproduct`]: https://docs.rs/frunk/0.3.1/frunk/coproduct/enum.Coproduct.html -#[macro_export] -macro_rules! wrap_dialogue { - ($name:ident($dialogue:ident), default $default_block:expr, ) => { - pub struct $name(pub $dialogue); - - impl teloxide::dispatching::dialogue::DialogueWrapper<$dialogue> - for $name - { - fn new(d: $dialogue) -> Wrapper { - $name(d) - } - } - - impl Default for $name { - fn default() -> $name { - $default_block - } - } - }; -} - /// Generates `.up(field)` methods for dialogue states. /// /// Given inductively defined states, this macro generates `.up(field)` methods @@ -232,4 +174,4 @@ macro_rules! up { pub type TransitionIn = DialogueWithCx; // A type returned from a FSM transition function. -pub type TransitionOut = ResponseResult>; +pub type TransitionOut = ResponseResult>; diff --git a/src/prelude.rs b/src/prelude.rs index 43e29238..64d04656 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,14 +5,14 @@ pub use crate::{ dispatching::{ dialogue::{ exit, next, DialogueDispatcher, DialogueStage, DialogueWithCx, - DialogueWrapper, GetChatId, TransitionIn, TransitionOut, + GetChatId, TransitionIn, TransitionOut, }, Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx, }, error_handlers::{LoggingErrorHandler, OnError}, requests::{Request, ResponseResult}, types::{Message, Update}, - up, wrap_dialogue, Bot, RequestError, + up, Bot, RequestError, }; pub use frunk::{Coprod, Coproduct};