diff --git a/.gitignore b/.gitignore index 7c5b00ed..7d7050cf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Cargo.lock .idea/ .vscode/ examples/target +examples/ping_pong_bot/target \ No newline at end of file diff --git a/examples/ping_pong_bot.rs b/examples/ping_pong_bot.rs deleted file mode 100644 index 07126937..00000000 --- a/examples/ping_pong_bot.rs +++ /dev/null @@ -1,28 +0,0 @@ -use teloxide::{ - dispatching::{ - session::{SessionDispatcher, SessionHandlerCtx, SessionState}, - Dispatcher, - }, - requests::Request, - types::Message, - Bot, -}; - -#[tokio::main] -async fn main() { - Dispatcher::<(), (), _, _, ()>::new(&Bot::new( - "1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0", - )) - .private_message_dp(SessionDispatcher::new( - |ctx: SessionHandlerCtx| async move { - ctx.bot - .send_message(ctx.update.chat.id, "pong") - .send() - .await - .unwrap(); - SessionState::Continue(()) - }, - )) - .dispatch() - .await -} diff --git a/examples/ping_pong_bot/Cargo.toml b/examples/ping_pong_bot/Cargo.toml new file mode 100644 index 00000000..1834b76b --- /dev/null +++ b/examples/ping_pong_bot/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ping_pong_bot" +version = "0.1.0" +authors = ["Temirkhan Myrzamadi "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pretty_env_logger = "0.3.1" +log = "0.4.8" +tokio = "0.2.9" +teloxide = { path = "../../" } \ No newline at end of file diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs new file mode 100644 index 00000000..295e7c5a --- /dev/null +++ b/examples/ping_pong_bot/src/main.rs @@ -0,0 +1,21 @@ +use teloxide::{ + dispatching::{Dispatcher, HandlerCtx}, + types::Message, + Bot, +}; + +use std::env; + +#[tokio::main] +async fn main() { + env::set_var("RUST_LOG", "ping_pong_bot=trace"); + pretty_env_logger::init(); + log::info!("Starting the ping-pong bot!"); + + let bot = Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); + + Dispatcher::new(bot) + .message_handler(|ctx: HandlerCtx| ctx.reply("pong")) + .dispatch() + .await; +} diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 09a7e65f..c87217a6 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -1,82 +1,72 @@ use crate::{ dispatching::{ - error_handlers, - session::{SessionDispatcher, SessionHandlerCtx, SessionState}, - update_listeners, - update_listeners::UpdateListener, + error_handlers, update_listeners, update_listeners::UpdateListener, AsyncHandler, }, + requests::{Request, ResponseResult}, types::{ - CallbackQuery, ChatKind, ChosenInlineResult, InlineQuery, Message, - Poll, PreCheckoutQuery, ShippingQuery, UpdateKind, + CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, + PreCheckoutQuery, ShippingQuery, UpdateKind, }, Bot, }; use futures::StreamExt; -use std::fmt::Debug; +use std::{fmt::Debug, sync::Arc}; -pub struct BasicHandlerCtx<'a, Upd> { - pub bot: &'a Bot, +/// A dispatcher's handler's context of a bot and an update. +pub struct HandlerCtx { + pub bot: Arc, pub update: Upd, } -/// The main dispatcher that joins all the parts together. -pub struct Dispatcher<'a, Session1, Session2, H1, H2, HandlerE> { - bot: &'a Bot, +impl HandlerCtx { + pub fn chat_id(&self) -> i64 { + self.update.chat_id() + } + + pub async fn reply(self, text: T) -> ResponseResult<()> + where + T: Into, + { + self.bot + .send_message(self.chat_id(), text) + .send() + .await + .map(|_| ()) + } +} + +type H<'a, Upd, HandlerE> = + Option, Result<(), HandlerE>> + 'a>>; + +/// The main dispatcher to rule them all. +pub struct Dispatcher<'a, HandlerE> { + bot: Arc, handlers_error_handler: Box + 'a>, - private_message_dp: Option>, - private_edited_message_dp: Option>, - - message_handler: - Option, ()> + 'a>>, - edited_message_handler: - Option, ()> + 'a>>, - channel_post_handler: - Option, ()> + 'a>>, - edited_channel_post_handler: - Option, ()> + 'a>>, - inline_query_handler: Option< - Box, ()> + 'a>, - >, - chosen_inline_result_handler: Option< - Box, ()> + 'a>, - >, - callback_query_handler: Option< - Box, ()> + 'a>, - >, - shipping_query_handler: Option< - Box, ()> + 'a>, - >, - pre_checkout_query_handler: Option< - Box, ()> + 'a>, - >, - poll_handler: - Option, ()> + 'a>>, + message_handler: H<'a, Message, HandlerE>, + edited_message_handler: H<'a, Message, HandlerE>, + channel_post_handler: H<'a, Message, HandlerE>, + edited_channel_post_handler: H<'a, Message, HandlerE>, + inline_query_handler: H<'a, InlineQuery, HandlerE>, + chosen_inline_result_handler: H<'a, ChosenInlineResult, HandlerE>, + callback_query_handler: H<'a, CallbackQuery, HandlerE>, + shipping_query_handler: H<'a, ShippingQuery, HandlerE>, + pre_checkout_query_handler: H<'a, PreCheckoutQuery, HandlerE>, + poll_handler: H<'a, Poll, HandlerE>, } -impl<'a, Session1, Session2, H1, H2, HandlerE> - Dispatcher<'a, Session1, Session2, H1, H2, HandlerE> +impl<'a, HandlerE> Dispatcher<'a, HandlerE> where - Session1: Default + 'a, - Session2: Default + 'a, - H1: AsyncHandler< - SessionHandlerCtx<'a, Message, Session1>, - SessionState, - > + 'a, - H2: AsyncHandler< - SessionHandlerCtx<'a, Message, Session2>, - SessionState, - > + 'a, HandlerE: Debug + 'a, { - pub fn new(bot: &'a Bot) -> Self { + /// Constructs a new dispatcher with this `bot`. + #[must_use] + pub fn new(bot: Bot) -> Self { Self { - bot, + bot: Arc::new(bot), handlers_error_handler: Box::new(error_handlers::Log), - private_message_dp: None, - private_edited_message_dp: None, message_handler: None, edited_message_handler: None, channel_post_handler: None, @@ -90,6 +80,7 @@ where } } + #[must_use] pub fn handlers_error_handler(mut self, val: T) -> Self where T: AsyncHandler + 'a, @@ -98,110 +89,109 @@ where self } - pub fn private_message_dp( - mut self, - dp: SessionDispatcher<'a, Session1, H1>, - ) -> Self { - self.private_message_dp = Some(dp); - self - } - - pub fn private_edited_message_dp( - mut self, - dp: SessionDispatcher<'a, Session2, H2>, - ) -> Self { - self.private_edited_message_dp = Some(dp); - self - } - + #[must_use] pub fn message_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + 'a, { self.message_handler = Some(Box::new(h)); self } + #[must_use] pub fn edited_message_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + 'a, { self.edited_message_handler = Some(Box::new(h)); self } + #[must_use] pub fn channel_post_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + 'a, { self.channel_post_handler = Some(Box::new(h)); self } + #[must_use] pub fn edited_channel_post_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + 'a, { self.edited_channel_post_handler = Some(Box::new(h)); self } + #[must_use] pub fn inline_query_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + 'a, { self.inline_query_handler = Some(Box::new(h)); self } + #[must_use] pub fn chosen_inline_result_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + + 'a, { self.chosen_inline_result_handler = Some(Box::new(h)); self } + #[must_use] pub fn callback_query_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + 'a, { self.callback_query_handler = Some(Box::new(h)); self } + #[must_use] pub fn shipping_query_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + 'a, { self.shipping_query_handler = Some(Box::new(h)); self } + #[must_use] pub fn pre_checkout_query_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + + 'a, { self.pre_checkout_query_handler = Some(Box::new(h)); self } + #[must_use] pub fn poll_handler(mut self, h: H) -> Self where - H: AsyncHandler, ()> + 'a, + H: AsyncHandler, Result<(), HandlerE>> + 'a, { self.poll_handler = Some(Box::new(h)); self } - pub async fn dispatch(&'a mut self) { + /// Starts your bot. + pub async fn dispatch(&'a self) { self.dispatch_with_listener( - update_listeners::polling_default(self.bot), + update_listeners::polling_default(Arc::clone(&self.bot)), &error_handlers::Log, ) .await; } + /// Starts your bot with custom `update_listener` and + /// `update_listener_error_handler`. pub async fn dispatch_with_listener( &'a self, update_listener: UListener, @@ -224,51 +214,31 @@ where }; match update.kind { - UpdateKind::Message(message) => match message.chat.kind { - ChatKind::Private { .. } => { - if let Some(private_message_dp) = - &self.private_message_dp + UpdateKind::Message(message) => { + if let Some(message_handler) = &self.message_handler { + if let Err(error) = message_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), + update: message, + }) + .await { - private_message_dp - .dispatch(self.bot, message) - .await; + self.handlers_error_handler.handle(error).await; } } - _ => { - if let Some(message_handler) = &self.message_handler - { - message_handler - .handle(BasicHandlerCtx { - bot: self.bot, - update: message, - }) - .await - } - } - }, - + } UpdateKind::EditedMessage(message) => { - match message.chat.kind { - ChatKind::Private { .. } => { - if let Some(private_edited_message_dp) = - &self.private_edited_message_dp - { - private_edited_message_dp - .dispatch(self.bot, message) - .await; - } - } - _ => { - if let Some(edited_message_handler) = - &self.edited_message_handler - { - edited_message_handler - .handle(BasicHandlerCtx { - bot: self.bot, - update: message, - }) - .await - } + if let Some(edited_message_handler) = + &self.edited_message_handler + { + if let Err(error) = edited_message_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), + update: message, + }) + .await + { + self.handlers_error_handler.handle(error).await; } } } @@ -276,94 +246,118 @@ where if let Some(channel_post_handler) = &self.channel_post_handler { - channel_post_handler - .handle(BasicHandlerCtx { - bot: self.bot, + if let Err(error) = channel_post_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), update: post, }) - .await; + .await + { + self.handlers_error_handler.handle(error).await; + } } } UpdateKind::EditedChannelPost(post) => { if let Some(edited_channel_post_handler) = &self.edited_channel_post_handler { - edited_channel_post_handler - .handle(BasicHandlerCtx { - bot: self.bot, + if let Err(error) = edited_channel_post_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), update: post, }) - .await; + .await + { + self.handlers_error_handler.handle(error).await; + } } } UpdateKind::InlineQuery(query) => { if let Some(inline_query_handler) = &self.inline_query_handler { - inline_query_handler - .handle(BasicHandlerCtx { - bot: self.bot, + if let Err(error) = inline_query_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), update: query, }) - .await; + .await + { + self.handlers_error_handler.handle(error).await; + } } } UpdateKind::ChosenInlineResult(result) => { if let Some(chosen_inline_result_handler) = &self.chosen_inline_result_handler { - chosen_inline_result_handler - .handle(BasicHandlerCtx { - bot: self.bot, + if let Err(error) = chosen_inline_result_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), update: result, }) - .await; + .await + { + self.handlers_error_handler.handle(error).await; + } } } UpdateKind::CallbackQuery(query) => { if let Some(callback_query_handler) = &self.callback_query_handler { - callback_query_handler - .handle(BasicHandlerCtx { - bot: self.bot, + if let Err(error) = callback_query_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), update: query, }) - .await; + .await + { + self.handlers_error_handler.handle(error).await; + } } } UpdateKind::ShippingQuery(query) => { if let Some(shipping_query_handler) = &self.shipping_query_handler { - shipping_query_handler - .handle(BasicHandlerCtx { - bot: self.bot, + if let Err(error) = shipping_query_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), update: query, }) - .await; + .await + { + self.handlers_error_handler.handle(error).await; + } } } UpdateKind::PreCheckoutQuery(query) => { if let Some(pre_checkout_query_handler) = &self.pre_checkout_query_handler { - pre_checkout_query_handler - .handle(BasicHandlerCtx { - bot: self.bot, + if let Err(error) = pre_checkout_query_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), update: query, }) - .await; + .await + { + self.handlers_error_handler.handle(error).await; + } } } UpdateKind::Poll(poll) => { if let Some(poll_handler) = &self.poll_handler { - poll_handler - .handle(BasicHandlerCtx { - bot: self.bot, + if let Err(error) = poll_handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), update: poll, }) - .await; + .await + { + self.handlers_error_handler.handle(error).await; + } } } } diff --git a/src/dispatching/error_handlers.rs b/src/dispatching/error_handlers.rs index 76f360df..879f87dd 100644 --- a/src/dispatching/error_handlers.rs +++ b/src/dispatching/error_handlers.rs @@ -1,4 +1,4 @@ -//! Handlers of errors. +//! Commonly used handlers of errors. use crate::dispatching::AsyncHandler; use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; @@ -9,11 +9,11 @@ use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; /// ``` /// # #[tokio::main] /// # async fn main_() { -/// use teloxide::dispatching::error_handlers::{ErrorHandler, Ignore}; +/// use teloxide::dispatching::{error_handlers::Ignore, AsyncHandler}; /// -/// Ignore.handle_error(()).await; -/// Ignore.handle_error(404).await; -/// Ignore.handle_error(String::from("error")).await; +/// Ignore.handle(()).await; +/// Ignore.handle(404).await; +/// Ignore.handle(String::from("error")).await; /// # } /// ``` pub struct Ignore; @@ -36,15 +36,15 @@ impl AsyncHandler for Ignore { /// # async fn main_() { /// use std::convert::{Infallible, TryInto}; /// -/// use teloxide::dispatching::error_handlers::{ErrorHandler, IgnoreSafe}; +/// use teloxide::dispatching::{AsyncHandler, error_handlers::IgnoreSafe}; /// /// let result: Result = "str".try_into(); /// match result { /// Ok(string) => println!("{}", string), -/// Err(inf) => IgnoreSafe.handle_error(inf).await, +/// Err(inf) => IgnoreSafe.handle(inf).await, /// } /// -/// IgnoreSafe.handle_error(return;).await; // return type of `return` is `!` (aka never) +/// IgnoreSafe.handle(return).await; // return type of `return` is `!` (aka never) /// # } /// ``` /// @@ -79,11 +79,11 @@ impl AsyncHandler for IgnoreSafe { /// ``` /// # #[tokio::main] /// # async fn main_() { -/// use teloxide::dispatching::error_handlers::{ErrorHandler, Log}; +/// use teloxide::dispatching::{error_handlers::Log, AsyncHandler}; /// -/// Log.handle_error(()).await; -/// Log.handle_error(404).await; -/// Log.handle_error(String::from("error")).await; +/// Log.handle(()).await; +/// Log.handle(404).await; +/// Log.handle(String::from("error")).await; /// # } /// ``` pub struct Log; diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index c2d499fc..d157a051 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -20,11 +20,11 @@ //! 3. If a handler has returned [`SessionState::Terminate`], remove the //! session from a storage, otherwise force the storage to update the session. //! -//! [`Storage`]: crate::dispatching::Storage -//! [`SessionDispatcher`]: crate::dispatching::SessionDispatcher +//! [`Storage`]: crate::dispatching::session::Storage +//! [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher //! [`dispatch(Bot, Upd)`]: -//! crate::dispatching::SessionDispatcher::dispatch -//! [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate +//! crate::dispatching::session::SessionDispatcher::dispatch +//! [`SessionState::Terminate`]: crate::dispatching::session::SessionState::Terminate // TODO: examples @@ -62,7 +62,7 @@ where /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] /// (a default storage). /// - /// [`InMemStorage`]: crate::dispatching::InMemStorage + /// [`InMemStorage`]: crate::dispatching::session::InMemStorage #[must_use] pub fn new(handler: H) -> Self { Self { diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 9a0722f6..21c4c1a2 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -108,7 +108,7 @@ use crate::{ types::{AllowedUpdate, Update}, RequestError, }; -use std::{convert::TryInto, time::Duration}; +use std::{convert::TryInto, sync::Arc, time::Duration}; /// A generic update listener. pub trait UpdateListener: Stream> { @@ -119,7 +119,7 @@ impl UpdateListener for S where S: Stream> {} /// Returns a long polling update listener with the default configuration. /// /// See also: [`polling`](polling). -pub fn polling_default(bot: &Bot) -> impl UpdateListener + '_ { +pub fn polling_default(bot: Arc) -> impl UpdateListener { polling(bot, None, None, None) } @@ -136,11 +136,11 @@ pub fn polling_default(bot: &Bot) -> impl UpdateListener + '_ { /// /// [`GetUpdates`]: crate::requests::GetUpdates pub fn polling( - bot: &Bot, + bot: Arc, timeout: Option, limit: Option, allowed_updates: Option>, -) -> impl UpdateListener + '_ { +) -> impl UpdateListener { let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); diff --git a/src/types/message.rs b/src/types/message.rs index 1e8a891f..a5279cf5 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -356,6 +356,10 @@ mod getters { } } + pub fn chat_id(&self) -> i64 { + self.chat.id + } + /// NOTE: this is getter for both `forward_from` and /// `forward_sender_name` pub fn forward_from(&self) -> Option<&ForwardedFrom> { @@ -727,21 +731,21 @@ mod getters { } } - pub fn migrate_to_chat_id(&self) -> Option<&i64> { + pub fn migrate_to_chat_id(&self) -> Option { match &self.kind { Migrate { migrate_to_chat_id, .. - } => Some(migrate_to_chat_id), + } => Some(*migrate_to_chat_id), _ => None, } } - pub fn migrate_from_chat_id(&self) -> Option<&i64> { + pub fn migrate_from_chat_id(&self) -> Option { match &self.kind { Migrate { migrate_from_chat_id, .. - } => Some(migrate_from_chat_id), + } => Some(*migrate_from_chat_id), _ => None, } }