From 84d4e6fb2c4e002df839ba96a24bfb21cd85b6f8 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 29 Jan 2020 10:47:17 +0600 Subject: [PATCH 01/91] Rework teloxide::dispatching (fails now) --- src/bot/api.rs | 1 + src/dispatching/chat/chat_update.rs | 18 -- src/dispatching/chat/dispatcher.rs | 106 ------- src/dispatching/chat/mod.rs | 41 --- src/dispatching/dispatcher.rs | 280 ++++++++++++++++++ src/dispatching/error_handlers.rs | 100 +++++++ src/dispatching/handler.rs | 44 +-- src/dispatching/mod.rs | 12 +- src/dispatching/session/get_chat_id.rs | 13 + src/dispatching/session/mod.rs | 122 ++++++++ .../storage/in_mem_storage.rs | 0 .../{chat => session}/storage/mod.rs | 0 12 files changed, 532 insertions(+), 205 deletions(-) delete mode 100644 src/dispatching/chat/chat_update.rs delete mode 100644 src/dispatching/chat/dispatcher.rs delete mode 100644 src/dispatching/chat/mod.rs create mode 100644 src/dispatching/dispatcher.rs create mode 100644 src/dispatching/error_handlers.rs create mode 100644 src/dispatching/session/get_chat_id.rs create mode 100644 src/dispatching/session/mod.rs rename src/dispatching/{chat => session}/storage/in_mem_storage.rs (100%) rename src/dispatching/{chat => session}/storage/mod.rs (100%) diff --git a/src/bot/api.rs b/src/bot/api.rs index ff3a8b88..fe44008d 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -1397,6 +1397,7 @@ impl Bot { /// - `ok`: Specify `true` if everything is alright (goods are available, /// etc.) and the bot is ready to proceed with the order. Use False if /// there are any problems. + /// /// [`Update`]: crate::types::Update pub fn answer_pre_checkout_query

( &self, diff --git a/src/dispatching/chat/chat_update.rs b/src/dispatching/chat/chat_update.rs deleted file mode 100644 index 32242b49..00000000 --- a/src/dispatching/chat/chat_update.rs +++ /dev/null @@ -1,18 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{CallbackQuery, Message}; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct ChatUpdate { - pub id: i32, - - pub kind: ChatUpdateKind, -} - -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum ChatUpdateKind { - Message(Message), - EditedMessage(Message), - CallbackQuery(CallbackQuery), -} diff --git a/src/dispatching/chat/dispatcher.rs b/src/dispatching/chat/dispatcher.rs deleted file mode 100644 index 14505ed9..00000000 --- a/src/dispatching/chat/dispatcher.rs +++ /dev/null @@ -1,106 +0,0 @@ -use super::{ - super::DispatchResult, - storage::{InMemStorage, Storage}, -}; -use crate::{ - dispatching::{ - chat::{ChatUpdate, ChatUpdateKind}, - Handler, SessionState, - }, - types::{Update, UpdateKind}, -}; - -/// A dispatcher that dispatches updates from chats. -pub struct Dispatcher<'a, Session, H> { - storage: Box + 'a>, - handler: H, -} - -impl<'a, Session, H> Dispatcher<'a, Session, H> -where - Session: Default + 'a, - H: Handler, -{ - /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] - /// (a default storage). - /// - /// [`InMemStorage`]: crate::dispatching::private::InMemStorage - pub fn new(handler: H) -> Self { - Self { - storage: Box::new(InMemStorage::default()), - handler, - } - } - - /// Creates a dispatcher with the specified `handler` and `storage`. - pub fn with_storage(handler: H, storage: Stg) -> Self - where - Stg: Storage + 'a, - { - Self { - storage: Box::new(storage), - handler, - } - } - - /// Dispatches a single `update`. - /// - /// ## Returns - /// Returns [`DispatchResult::Handled`] if `update` was supplied to a - /// handler, and [`DispatchResult::Unhandled`] if it was an update not - /// from a chat. - /// - /// [`DispatchResult::Handled`]: crate::dispatching::DispatchResult::Handled - /// [`DispatchResult::Unhandled`]: - /// crate::dispatching::DispatchResult::Unhandled - pub async fn dispatch(&mut self, update: Update) -> DispatchResult { - let chat_update = match update.kind { - UpdateKind::Message(msg) => ChatUpdate { - id: update.id, - kind: ChatUpdateKind::Message(msg), - }, - UpdateKind::EditedMessage(msg) => ChatUpdate { - id: update.id, - kind: ChatUpdateKind::EditedMessage(msg), - }, - UpdateKind::CallbackQuery(query) => ChatUpdate { - id: update.id, - kind: ChatUpdateKind::CallbackQuery(query), - }, - _ => return DispatchResult::Unhandled, - }; - - let chat_id = match &chat_update.kind { - ChatUpdateKind::Message(msg) => msg.chat.id, - ChatUpdateKind::EditedMessage(msg) => msg.chat.id, - ChatUpdateKind::CallbackQuery(query) => match &query.message { - None => return DispatchResult::Unhandled, - Some(msg) => msg.chat.id, - }, - }; - - let session = self - .storage - .remove_session(chat_id) - .await - .unwrap_or_default(); - - if let SessionState::Continue(session) = - self.handler.handle(session, chat_update).await - { - if self - .storage - .update_session(chat_id, session) - .await - .is_some() - { - panic!( - "We previously storage.remove_session() so \ - storage.update_session() must return None" - ); - } - } - - DispatchResult::Handled - } -} diff --git a/src/dispatching/chat/mod.rs b/src/dispatching/chat/mod.rs deleted file mode 100644 index 7accabc6..00000000 --- a/src/dispatching/chat/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Dispatching updates from chats. -//! -//! There are four main components: -//! -//! 1. Your session type `Session`, which designates a dialogue state at the -//! current moment. -//! 2. [`Storage`] that encapsulates all the sessions. -//! 3. Your handler of type `H: async Fn(Session, Update) -> -//! HandleResult` that receives an update and turns your session into -//! the next state. -//! 4. [`Dispatcher`], which encapsulates your handler and [`Storage`], and has -//! the [`dispatch(Update) -> DispatchResult`] function. -//! -//! Every time you call [`.dispatch(update)`] on your dispatcher, the following -//! steps are executed: -//! -//! 1. If a supplied update is not from a chat, return -//! [`DispatchResult::Unhandled`]. -//! 2. If a storage doesn't contain a session from this chat, supply -//! `Session::default()` into you handler, otherwise, supply the previous -//! session. -//! 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::private::Storage -//! [`Dispatcher`]: crate::dispatching::private::Dispatcher -//! [`dispatch(Update) -> DispatchResult`]: -//! crate::dispatching::private::Dispatcher::dispatch -//! [`.dispatch(update)`]: crate::dispatching::private::Dispatcher::dispatch -//! [`DispatchResult::Unhandled`]: crate::dispatching::DispatchResult::Unhandled -//! [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate - -// TODO: examples - -mod chat_update; -mod dispatcher; -mod storage; - -pub use chat_update::*; -pub use dispatcher::*; -pub use storage::*; diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs new file mode 100644 index 00000000..0d094fc0 --- /dev/null +++ b/src/dispatching/dispatcher.rs @@ -0,0 +1,280 @@ +use crate::{ + dispatching::{ + error_handlers, + session::{SessionDispatcher, SessionHandlerCtx, SessionState}, + update_listeners, + update_listeners::UpdateListener, + Handler, + }, + types::{ + CallbackQuery, ChatKind, ChosenInlineResult, InlineQuery, Message, + Poll, PreCheckoutQuery, ShippingQuery, UpdateKind, + }, + Bot, +}; +use futures::StreamExt; +use std::fmt::Debug; + +pub struct BasicHandlerCtx<'a, Upd> { + bot: &'a Bot, + update: Upd, +} + +pub struct Dispatcher<'a, Session1, Session2, H1, H2, HandlerE> { + bot: &'a Bot, + + handlers_error_handler: Box>, + + private_message_dp: Option>, + private_edited_message_dp: Option>, + + message_handler: Option, ()>>>, + edited_message_handler: + Option, ()>>>, + channel_post_handler: + Option, ()>>>, + edited_channel_post_handler: + Option, ()>>>, + inline_query_handler: + Option, ()>>>, + chosen_inline_result_handler: + Option, ()>>>, + callback_query_handler: + Option, ()>>>, + shipping_query_handler: + Option, ()>>>, + pre_checkout_query_handler: + Option, ()>>>, + poll_handler: Option, ()>>>, +} + +impl<'a, Session1, Session2, H1, H2, HandlerE> + Dispatcher<'a, Session1, Session2, H1, H2, HandlerE> +where + Session1: Default, + Session2: Default, + H1: Handler< + SessionHandlerCtx<'a, Message, Session1>, + SessionState, + >, + H2: Handler< + SessionHandlerCtx<'a, Message, Session2>, + SessionState, + >, + HandlerE: Debug, +{ + pub fn new(bot: &'a Bot) -> Self { + Self { + 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, + edited_channel_post_handler: None, + inline_query_handler: None, + chosen_inline_result_handler: None, + callback_query_handler: None, + shipping_query_handler: None, + pre_checkout_query_handler: None, + poll_handler: None, + } + } + + pub fn private_message_dp( + mut self, + dp: SessionDispatcher<'a, Session1, H1>, + ) -> Self { + self.private_message_dp = Some(dp); + self + } + + async fn dispatch(&'a mut self) + where + Session1: 'a, + Session2: 'a, + H1: 'a, + H2: 'a, + HandlerE: 'a, + { + self.dispatch_with_listener( + update_listeners::polling_default(self.bot), + error_handlers::Log, + ) + .await; + } + + async fn dispatch_with_listener( + &'a mut self, + update_listener: UListener, + update_listener_error_handler: Eh, + ) where + UListener: UpdateListener + 'a, + Eh: Handler + 'a, + Session1: 'a, + Session2: 'a, + H1: 'a, + H2: 'a, + HandlerE: 'a, + ListenerE: Debug, + { + update_listener + .for_each_concurrent(None, move |update| async { + let update = match update { + Ok(update) => update, + Err(error) => { + update_listener_error_handler.handle(error).await; + return; + } + }; + + match update.kind { + UpdateKind::Message(message) => match message.chat.kind { + ChatKind::Private { .. } => { + if let Some(private_message_dp) = + &mut self.private_message_dp + { + private_message_dp + .dispatch(self.bot, message) + .await; + } + } + _ => { + if let Some(message_handler) = + &mut 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) = + &mut self.private_edited_message_dp + { + private_edited_message_dp + .dispatch(self.bot, message) + .await; + } + } + _ => { + if let Some(edited_message_handler) = + &mut self.edited_message_handler + { + edited_message_handler + .handle(BasicHandlerCtx { + bot: self.bot, + update: message, + }) + .await + } + } + } + } + UpdateKind::ChannelPost(post) => { + if let Some(channel_post_handler) = + &mut self.channel_post_handler + { + channel_post_handler + .handle(BasicHandlerCtx { + bot: self.bot, + update: post, + }) + .await; + } + } + UpdateKind::EditedChannelPost(post) => { + if let Some(edited_channel_post_handler) = + &mut self.edited_channel_post_handler + { + edited_channel_post_handler + .handle(BasicHandlerCtx { + bot: self.bot, + update: post, + }) + .await; + } + } + UpdateKind::InlineQuery(query) => { + if let Some(inline_query_handler) = + &mut self.inline_query_handler + { + inline_query_handler + .handle(BasicHandlerCtx { + bot: self.bot, + update: query, + }) + .await; + } + } + UpdateKind::ChosenInlineResult(result) => { + if let Some(chosen_inline_result_handler) = + &mut self.chosen_inline_result_handler + { + chosen_inline_result_handler + .handle(BasicHandlerCtx { + bot: self.bot, + update: result, + }) + .await; + } + } + UpdateKind::CallbackQuery(query) => { + if let Some(callback_query_handler) = + &mut self.callback_query_handler + { + callback_query_handler + .handle(BasicHandlerCtx { + bot: self.bot, + update: query, + }) + .await; + } + } + UpdateKind::ShippingQuery(query) => { + if let Some(shipping_query_handler) = + &mut self.shipping_query_handler + { + shipping_query_handler + .handle(BasicHandlerCtx { + bot: self.bot, + update: query, + }) + .await; + } + } + UpdateKind::PreCheckoutQuery(query) => { + if let Some(pre_checkout_query_handler) = + &mut self.pre_checkout_query_handler + { + pre_checkout_query_handler + .handle(BasicHandlerCtx { + bot: self.bot, + update: query, + }) + .await; + } + } + UpdateKind::Poll(poll) => { + if let Some(poll_handler) = &mut self.poll_handler { + poll_handler + .handle(BasicHandlerCtx { + bot: self.bot, + update: poll, + }) + .await; + } + } + } + }) + .await + } +} diff --git a/src/dispatching/error_handlers.rs b/src/dispatching/error_handlers.rs new file mode 100644 index 00000000..848b4502 --- /dev/null +++ b/src/dispatching/error_handlers.rs @@ -0,0 +1,100 @@ +use crate::dispatching::Handler; +use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; + +/// A handler that silently ignores all errors. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::error_handlers::{ErrorHandler, Ignore}; +/// +/// Ignore.handle_error(()).await; +/// Ignore.handle_error(404).await; +/// Ignore.handle_error(String::from("error")).await; +/// # } +/// ``` +pub struct Ignore; + +impl Handler for Ignore { + fn handle<'a>(&'a self, _: E) -> Pin + 'a>> + where + E: 'a, + { + Box::pin(async {}) + } +} + +/// An error handler that silently ignores all errors that can never happen +/// (e.g.: [`!`] or [`Infallible`]). +/// +/// ## Examples +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use std::convert::{Infallible, TryInto}; +/// +/// use teloxide::dispatching::error_handlers::{ErrorHandler, IgnoreSafe}; +/// +/// let result: Result = "str".try_into(); +/// match result { +/// Ok(string) => println!("{}", string), +/// Err(inf) => IgnoreSafe.handle_error(inf).await, +/// } +/// +/// IgnoreSafe.handle_error(return;).await; // return type of `return` is `!` (aka never) +/// # } +/// ``` +/// +/// ```compile_fail +/// use teloxide::dispatching::dispatchers::filter::error_policy::{ +/// ErrorPolicy, IgnoreSafe, +/// }; +/// +/// IgnoreSafe.handle_error(0); +/// ``` +/// +/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html +/// [`Infallible`]: std::convert::Infallible +pub struct IgnoreSafe; + +#[allow(unreachable_code)] +impl Handler for IgnoreSafe { + fn handle<'a>( + &'a self, + _: Infallible, + ) -> Pin + 'a>> + where + Infallible: 'a, + { + Box::pin(async {}) + } +} + +/// An error handler that prints all errors passed into it. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::error_handlers::{ErrorHandler, Log}; +/// +/// Log.handle_error(()).await; +/// Log.handle_error(404).await; +/// Log.handle_error(String::from("error")).await; +/// # } +/// ``` +pub struct Log; + +impl Handler for Log +where + E: Debug, +{ + fn handle<'a>(&'a self, error: E) -> Pin + 'a>> + where + E: 'a, + { + log::debug!("error: {:?}", error); + Box::pin(async {}) + } +} diff --git a/src/dispatching/handler.rs b/src/dispatching/handler.rs index b40ac794..a2b94130 100644 --- a/src/dispatching/handler.rs +++ b/src/dispatching/handler.rs @@ -1,49 +1,29 @@ use std::{future::Future, pin::Pin}; -/// Continue or terminate a user session. -#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum SessionState { - Continue(Session), - Terminate, -} - -/// A handler of a user session and an update. -/// -/// ## Returns -/// Returns [`SessionState::Continue(session)`] if it wants to be called again -/// after a new update, or [`SessionState::Terminate`] if not. -/// -/// [`SessionState::Continue(session)`]: -/// crate::dispatching::SessionState::Continue -/// [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate -pub trait Handler { +/// An asynchronous polymorphic handler of a context. +pub trait Handler { #[must_use] fn handle<'a>( &'a self, - session: Session, - update: U, - ) -> Pin> + 'a>> + ctx: Ctx, + ) -> Pin + 'a>> where - Session: 'a, - U: 'a; + Ctx: 'a; } -/// The implementation of `Handler` for `Fn(Session, Update) -> Future>`. -impl Handler for F +/// The implementation of `Handler` for `Fn(Ctx) -> Future`. +impl Handler for F where - F: Fn(Session, U) -> Fut, - Fut: Future>, + F: Fn(Ctx) -> Fut, + Fut: Future, { fn handle<'a>( &'a self, - session: Session, - update: U, + ctx: Ctx, ) -> Pin + 'a>> where - Session: 'a, - U: 'a, + Ctx: 'a, { - Box::pin(async move { self(session, update).await }) + Box::pin(async move { self(ctx).await }) } } diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index cfd4f87b..1db811c2 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,14 +1,10 @@ //! Update dispatching. -/// If an update was handled by a dispatcher or not. -#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum DispatchResult { - Handled, - Unhandled, -} - -pub mod chat; +mod dispatcher; +pub mod error_handlers; mod handler; +pub mod session; pub mod update_listeners; +pub use dispatcher::*; pub use handler::*; diff --git a/src/dispatching/session/get_chat_id.rs b/src/dispatching/session/get_chat_id.rs new file mode 100644 index 00000000..d7e64206 --- /dev/null +++ b/src/dispatching/session/get_chat_id.rs @@ -0,0 +1,13 @@ +use crate::types::Message; + +/// Something that has a chat ID. +pub trait GetChatId { + #[must_use] + fn chat_id(&self) -> i64; +} + +impl GetChatId for Message { + fn chat_id(&self) -> i64 { + self.chat.id + } +} diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs new file mode 100644 index 00000000..04f41a5c --- /dev/null +++ b/src/dispatching/session/mod.rs @@ -0,0 +1,122 @@ +//! Dispatching user sessions. +//! +//! There are four main components: +//! +//! 1. Your session type `Session`, which designates a dialogue state at the +//! current moment. +//! 2. [`Storage`] that encapsulates all the sessions. +//! 3. Your handler of type `H: async Fn(Session, Update) -> +//! SessionState` that receives an update and turns your session into +//! the next state. +//! 4. [`SessionDispatcher`], which encapsulates your handler and +//! [`Storage`], and has the [`dispatch(Bot, Upd)`] function. +//! +//! Every time you call `.dispatch(bot, update)` on your dispatcher, the +//! following steps are executed: +//! +//! 1. If a storage doesn't contain a session from this chat, supply +//! `Session::default()` into you handler, otherwise, supply the previous +//! session. +//! 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 +//! [`dispatch(Bot, Upd)`]: +//! crate::dispatching::SessionDispatcher::dispatch +//! [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate + +// TODO: examples + +mod get_chat_id; +mod storage; + +use crate::{dispatching::Handler, Bot}; +pub use get_chat_id::*; +pub use storage::*; + +/// A context of a private message handler. +pub struct SessionHandlerCtx<'a, Upd, Session> { + pub bot: &'a Bot, + pub update: Upd, + pub session: Session, +} + +/// Continue or terminate a user session. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum SessionState { + Continue(Session), + Terminate, +} + +/// A dispatcher of user sessions. +pub struct SessionDispatcher<'a, Session, H> { + storage: Box + 'a>, + handler: H, +} + +impl<'a, Session, H> SessionDispatcher<'a, Session, H> +where + Session: Default + 'a, +{ + /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] + /// (a default storage). + /// + /// [`InMemStorage`]: crate::dispatching::InMemStorage + #[must_use] + pub fn new(handler: H) -> Self { + Self { + storage: Box::new(InMemStorage::default()), + handler, + } + } + + /// Creates a dispatcher with the specified `handler` and `storage`. + #[must_use] + pub fn with_storage(handler: H, storage: Stg) -> Self + where + Stg: Storage + 'a, + { + Self { + storage: Box::new(storage), + handler, + } + } + + /// Dispatches a single `message` from a private chat. + pub async fn dispatch(&'a mut self, bot: &'a Bot, update: Upd) + where + H: Handler, SessionState>, + Upd: GetChatId, + { + let chat_id = update.chat_id(); + + let session = self + .storage + .remove_session(chat_id) + .await + .unwrap_or_default(); + + if let SessionState::Continue(new_session) = self + .handler + .handle(SessionHandlerCtx { + bot, + update, + session, + }) + .await + { + if self + .storage + .update_session(chat_id, new_session) + .await + .is_some() + { + panic!( + "We previously storage.remove_session() so \ + storage.update_session() must return None" + ); + } + } + } +} diff --git a/src/dispatching/chat/storage/in_mem_storage.rs b/src/dispatching/session/storage/in_mem_storage.rs similarity index 100% rename from src/dispatching/chat/storage/in_mem_storage.rs rename to src/dispatching/session/storage/in_mem_storage.rs diff --git a/src/dispatching/chat/storage/mod.rs b/src/dispatching/session/storage/mod.rs similarity index 100% rename from src/dispatching/chat/storage/mod.rs rename to src/dispatching/session/storage/mod.rs From 3bad400c03a7a4d0d7eeec6c1fe3923ea686eb31 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 29 Jan 2020 20:48:57 +0600 Subject: [PATCH 02/91] Fix the error --- src/dispatching/dispatcher.rs | 194 +++++++++++++----- src/dispatching/session/mod.rs | 2 +- .../session/storage/in_mem_storage.rs | 13 +- src/dispatching/session/storage/mod.rs | 4 +- 4 files changed, 151 insertions(+), 62 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 0d094fc0..97c63f1b 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -16,52 +16,55 @@ use futures::StreamExt; use std::fmt::Debug; pub struct BasicHandlerCtx<'a, Upd> { - bot: &'a Bot, - update: Upd, + pub bot: &'a Bot, + pub update: Upd, } pub struct Dispatcher<'a, Session1, Session2, H1, H2, HandlerE> { bot: &'a Bot, - handlers_error_handler: Box>, + handlers_error_handler: Box + 'a>, private_message_dp: Option>, private_edited_message_dp: Option>, - message_handler: Option, ()>>>, + message_handler: + Option, ()> + 'a>>, edited_message_handler: - Option, ()>>>, + Option, ()> + 'a>>, channel_post_handler: - Option, ()>>>, + Option, ()> + 'a>>, edited_channel_post_handler: - Option, ()>>>, + Option, ()> + 'a>>, inline_query_handler: - Option, ()>>>, - chosen_inline_result_handler: - Option, ()>>>, + Option, ()> + 'a>>, + chosen_inline_result_handler: Option< + Box, ()> + 'a>, + >, callback_query_handler: - Option, ()>>>, + Option, ()> + 'a>>, shipping_query_handler: - Option, ()>>>, - pre_checkout_query_handler: - Option, ()>>>, - poll_handler: Option, ()>>>, + Option, ()> + 'a>>, + pre_checkout_query_handler: Option< + Box, ()> + 'a>, + >, + poll_handler: Option, ()> + 'a>>, } impl<'a, Session1, Session2, H1, H2, HandlerE> Dispatcher<'a, Session1, Session2, H1, H2, HandlerE> where - Session1: Default, - Session2: Default, + Session1: Default + 'a, + Session2: Default + 'a, H1: Handler< - SessionHandlerCtx<'a, Message, Session1>, - SessionState, - >, + SessionHandlerCtx<'a, Message, Session1>, + SessionState, + > + 'a, H2: Handler< - SessionHandlerCtx<'a, Message, Session2>, - SessionState, - >, - HandlerE: Debug, + SessionHandlerCtx<'a, Message, Session2>, + SessionState, + > + 'a, + HandlerE: Debug + 'a, { pub fn new(bot: &'a Bot) -> Self { Self { @@ -82,6 +85,14 @@ where } } + pub fn handlers_error_handler(mut self, val: T) -> Self + where + T: Handler + 'a, + { + self.handlers_error_handler = Box::new(val); + self + } + pub fn private_message_dp( mut self, dp: SessionDispatcher<'a, Session1, H1>, @@ -90,37 +101,115 @@ where self } - async fn dispatch(&'a mut self) + pub fn private_edited_message_dp( + mut self, + dp: SessionDispatcher<'a, Session2, H2>, + ) -> Self { + self.private_edited_message_dp = Some(dp); + self + } + + pub fn message_handler(mut self, h: H) -> Self where - Session1: 'a, - Session2: 'a, - H1: 'a, - H2: 'a, - HandlerE: 'a, + H: Handler, ()> + 'a, { + self.message_handler = Some(Box::new(h)); + self + } + + pub fn edited_message_handler(mut self, h: H) -> Self + where + H: Handler, ()> + 'a, + { + self.edited_message_handler = Some(Box::new(h)); + self + } + + pub fn channel_post_handler(mut self, h: H) -> Self + where + H: Handler, ()> + 'a, + { + self.channel_post_handler = Some(Box::new(h)); + self + } + + pub fn edited_channel_post_handler(mut self, h: H) -> Self + where + H: Handler, ()> + 'a, + { + self.edited_channel_post_handler = Some(Box::new(h)); + self + } + + pub fn inline_query_handler(mut self, h: H) -> Self + where + H: Handler, ()> + 'a, + { + self.inline_query_handler = Some(Box::new(h)); + self + } + + pub fn chosen_inline_result_handler(mut self, h: H) -> Self + where + H: Handler, ()> + 'a, + { + self.chosen_inline_result_handler = Some(Box::new(h)); + self + } + + pub fn callback_query_handler(mut self, h: H) -> Self + where + H: Handler, ()> + 'a, + { + self.callback_query_handler = Some(Box::new(h)); + self + } + + pub fn shipping_query_handler(mut self, h: H) -> Self + where + H: Handler, ()> + 'a, + { + self.shipping_query_handler = Some(Box::new(h)); + self + } + + pub fn pre_checkout_query_handler(mut self, h: H) -> Self + where + H: Handler, ()> + 'a, + { + self.pre_checkout_query_handler = Some(Box::new(h)); + self + } + + pub fn poll_handler(mut self, h: H) -> Self + where + H: Handler, ()> + 'a, + { + self.poll_handler = Some(Box::new(h)); + self + } + + pub async fn dispatch(&'a mut self) { self.dispatch_with_listener( update_listeners::polling_default(self.bot), - error_handlers::Log, + &error_handlers::Log, ) .await; } - async fn dispatch_with_listener( - &'a mut self, + pub async fn dispatch_with_listener( + &'a self, update_listener: UListener, - update_listener_error_handler: Eh, + update_listener_error_handler: &'a Eh, ) where UListener: UpdateListener + 'a, Eh: Handler + 'a, - Session1: 'a, - Session2: 'a, - H1: 'a, - H2: 'a, - HandlerE: 'a, ListenerE: Debug, { + let update_listener = Box::pin(update_listener); + update_listener - .for_each_concurrent(None, move |update| async { + .for_each_concurrent(None, move |update| async move { let update = match update { Ok(update) => update, Err(error) => { @@ -133,7 +222,7 @@ where UpdateKind::Message(message) => match message.chat.kind { ChatKind::Private { .. } => { if let Some(private_message_dp) = - &mut self.private_message_dp + &self.private_message_dp { private_message_dp .dispatch(self.bot, message) @@ -141,8 +230,7 @@ where } } _ => { - if let Some(message_handler) = - &mut self.message_handler + if let Some(message_handler) = &self.message_handler { message_handler .handle(BasicHandlerCtx { @@ -158,7 +246,7 @@ where match message.chat.kind { ChatKind::Private { .. } => { if let Some(private_edited_message_dp) = - &mut self.private_edited_message_dp + &self.private_edited_message_dp { private_edited_message_dp .dispatch(self.bot, message) @@ -167,7 +255,7 @@ where } _ => { if let Some(edited_message_handler) = - &mut self.edited_message_handler + &self.edited_message_handler { edited_message_handler .handle(BasicHandlerCtx { @@ -181,7 +269,7 @@ where } UpdateKind::ChannelPost(post) => { if let Some(channel_post_handler) = - &mut self.channel_post_handler + &self.channel_post_handler { channel_post_handler .handle(BasicHandlerCtx { @@ -193,7 +281,7 @@ where } UpdateKind::EditedChannelPost(post) => { if let Some(edited_channel_post_handler) = - &mut self.edited_channel_post_handler + &self.edited_channel_post_handler { edited_channel_post_handler .handle(BasicHandlerCtx { @@ -205,7 +293,7 @@ where } UpdateKind::InlineQuery(query) => { if let Some(inline_query_handler) = - &mut self.inline_query_handler + &self.inline_query_handler { inline_query_handler .handle(BasicHandlerCtx { @@ -217,7 +305,7 @@ where } UpdateKind::ChosenInlineResult(result) => { if let Some(chosen_inline_result_handler) = - &mut self.chosen_inline_result_handler + &self.chosen_inline_result_handler { chosen_inline_result_handler .handle(BasicHandlerCtx { @@ -229,7 +317,7 @@ where } UpdateKind::CallbackQuery(query) => { if let Some(callback_query_handler) = - &mut self.callback_query_handler + &self.callback_query_handler { callback_query_handler .handle(BasicHandlerCtx { @@ -241,7 +329,7 @@ where } UpdateKind::ShippingQuery(query) => { if let Some(shipping_query_handler) = - &mut self.shipping_query_handler + &self.shipping_query_handler { shipping_query_handler .handle(BasicHandlerCtx { @@ -253,7 +341,7 @@ where } UpdateKind::PreCheckoutQuery(query) => { if let Some(pre_checkout_query_handler) = - &mut self.pre_checkout_query_handler + &self.pre_checkout_query_handler { pre_checkout_query_handler .handle(BasicHandlerCtx { @@ -264,7 +352,7 @@ where } } UpdateKind::Poll(poll) => { - if let Some(poll_handler) = &mut self.poll_handler { + if let Some(poll_handler) = &self.poll_handler { poll_handler .handle(BasicHandlerCtx { bot: self.bot, diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index 04f41a5c..ec47ab25 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -84,7 +84,7 @@ where } /// Dispatches a single `message` from a private chat. - pub async fn dispatch(&'a mut self, bot: &'a Bot, update: Upd) + pub async fn dispatch(&'a self, bot: &'a Bot, update: Upd) where H: Handler, SessionState>, Upd: GetChatId, diff --git a/src/dispatching/session/storage/in_mem_storage.rs b/src/dispatching/session/storage/in_mem_storage.rs index 140ee133..3203093c 100644 --- a/src/dispatching/session/storage/in_mem_storage.rs +++ b/src/dispatching/session/storage/in_mem_storage.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use super::Storage; use std::collections::HashMap; +use tokio::sync::Mutex; /// A memory storage based on a hash map. Stores all the sessions directly in /// RAM. @@ -10,23 +11,23 @@ use std::collections::HashMap; /// All the sessions will be lost after you restart your bot. If you need to /// store them somewhere on a drive, you need to implement a storage /// communicating with a DB. -#[derive(Clone, Debug, Eq, PartialEq, Default)] +#[derive(Debug, Default)] pub struct InMemStorage { - map: HashMap, + map: Mutex>, } #[async_trait(?Send)] #[async_trait] impl Storage for InMemStorage { - async fn remove_session(&mut self, chat_id: i64) -> Option { - self.map.remove(&chat_id) + async fn remove_session(&self, chat_id: i64) -> Option { + self.map.lock().await.remove(&chat_id) } async fn update_session( - &mut self, + &self, chat_id: i64, state: Session, ) -> Option { - self.map.insert(chat_id, state) + self.map.lock().await.insert(chat_id, state) } } diff --git a/src/dispatching/session/storage/mod.rs b/src/dispatching/session/storage/mod.rs index e3e12bbf..5bff11f6 100644 --- a/src/dispatching/session/storage/mod.rs +++ b/src/dispatching/session/storage/mod.rs @@ -18,14 +18,14 @@ pub trait Storage { /// /// Returns `None` if there wasn't such a session, `Some(session)` if a /// `session` was deleted. - async fn remove_session(&mut self, chat_id: i64) -> Option; + async fn remove_session(&self, chat_id: i64) -> Option; /// Updates a session with the specified `chat_id`. /// /// Returns `None` if there wasn't such a session, `Some(session)` if a /// `session` was updated. async fn update_session( - &mut self, + &self, chat_id: i64, session: Session, ) -> Option; From 27ae3e13cf4dd9139bc987311ce64a77e1fa46f9 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 29 Jan 2020 21:08:15 +0600 Subject: [PATCH 03/91] Trying to fix ping_pong_bot.rs --- examples/ping_pong_bot.rs | 42 +++++++++------------ src/dispatching/dispatcher.rs | 63 +++++++++++++++++-------------- src/dispatching/error_handlers.rs | 10 +++-- src/dispatching/handler.rs | 5 +-- src/dispatching/session/mod.rs | 7 +++- 5 files changed, 65 insertions(+), 62 deletions(-) diff --git a/examples/ping_pong_bot.rs b/examples/ping_pong_bot.rs index 8948dada..07126937 100644 --- a/examples/ping_pong_bot.rs +++ b/examples/ping_pong_bot.rs @@ -1,34 +1,28 @@ -use futures::stream::StreamExt; use teloxide::{ dispatching::{ - chat::{ChatUpdate, ChatUpdateKind, Dispatcher}, - update_listeners::polling_default, - SessionState, + session::{SessionDispatcher, SessionHandlerCtx, SessionState}, + Dispatcher, }, requests::Request, + types::Message, Bot, }; #[tokio::main] async fn main() { - let bot = &Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); - let mut updater = Box::pin(polling_default(bot)); - let handler = |_, upd: ChatUpdate| async move { - if let ChatUpdateKind::Message(m) = upd.kind { - let msg = bot.send_message(m.chat.id, "pong"); - msg.send().await.unwrap(); - } - SessionState::Continue(()) - }; - let mut dp = Dispatcher::<'_, (), _>::new(handler); - println!("Starting the message handler."); - loop { - let u = updater.next().await.unwrap(); - match u { - Err(e) => eprintln!("Error: {}", e), - Ok(u) => { - let _ = dp.dispatch(u).await; - } - } - } + 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/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 97c63f1b..09a7e65f 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -4,7 +4,7 @@ use crate::{ session::{SessionDispatcher, SessionHandlerCtx, SessionState}, update_listeners, update_listeners::UpdateListener, - Handler, + AsyncHandler, }, types::{ CallbackQuery, ChatKind, ChosenInlineResult, InlineQuery, Message, @@ -20,35 +20,40 @@ pub struct BasicHandlerCtx<'a, Upd> { pub update: Upd, } +/// The main dispatcher that joins all the parts together. pub struct Dispatcher<'a, Session1, Session2, H1, H2, HandlerE> { bot: &'a Bot, - handlers_error_handler: Box + 'a>, + handlers_error_handler: Box + 'a>, private_message_dp: Option>, private_edited_message_dp: Option>, message_handler: - Option, ()> + 'a>>, + Option, ()> + 'a>>, edited_message_handler: - Option, ()> + 'a>>, + Option, ()> + 'a>>, channel_post_handler: - Option, ()> + 'a>>, + Option, ()> + 'a>>, edited_channel_post_handler: - Option, ()> + 'a>>, - inline_query_handler: - Option, ()> + 'a>>, + Option, ()> + 'a>>, + inline_query_handler: Option< + Box, ()> + 'a>, + >, chosen_inline_result_handler: Option< - Box, ()> + 'a>, + Box, ()> + 'a>, + >, + callback_query_handler: Option< + Box, ()> + 'a>, + >, + shipping_query_handler: Option< + Box, ()> + 'a>, >, - callback_query_handler: - Option, ()> + 'a>>, - shipping_query_handler: - Option, ()> + 'a>>, pre_checkout_query_handler: Option< - Box, ()> + 'a>, + Box, ()> + 'a>, >, - poll_handler: Option, ()> + 'a>>, + poll_handler: + Option, ()> + 'a>>, } impl<'a, Session1, Session2, H1, H2, HandlerE> @@ -56,11 +61,11 @@ impl<'a, Session1, Session2, H1, H2, HandlerE> where Session1: Default + 'a, Session2: Default + 'a, - H1: Handler< + H1: AsyncHandler< SessionHandlerCtx<'a, Message, Session1>, SessionState, > + 'a, - H2: Handler< + H2: AsyncHandler< SessionHandlerCtx<'a, Message, Session2>, SessionState, > + 'a, @@ -87,7 +92,7 @@ where pub fn handlers_error_handler(mut self, val: T) -> Self where - T: Handler + 'a, + T: AsyncHandler + 'a, { self.handlers_error_handler = Box::new(val); self @@ -111,7 +116,7 @@ where pub fn message_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.message_handler = Some(Box::new(h)); self @@ -119,7 +124,7 @@ where pub fn edited_message_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.edited_message_handler = Some(Box::new(h)); self @@ -127,7 +132,7 @@ where pub fn channel_post_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.channel_post_handler = Some(Box::new(h)); self @@ -135,7 +140,7 @@ where pub fn edited_channel_post_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.edited_channel_post_handler = Some(Box::new(h)); self @@ -143,7 +148,7 @@ where pub fn inline_query_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.inline_query_handler = Some(Box::new(h)); self @@ -151,7 +156,7 @@ where pub fn chosen_inline_result_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.chosen_inline_result_handler = Some(Box::new(h)); self @@ -159,7 +164,7 @@ where pub fn callback_query_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.callback_query_handler = Some(Box::new(h)); self @@ -167,7 +172,7 @@ where pub fn shipping_query_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.shipping_query_handler = Some(Box::new(h)); self @@ -175,7 +180,7 @@ where pub fn pre_checkout_query_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.pre_checkout_query_handler = Some(Box::new(h)); self @@ -183,7 +188,7 @@ where pub fn poll_handler(mut self, h: H) -> Self where - H: Handler, ()> + 'a, + H: AsyncHandler, ()> + 'a, { self.poll_handler = Some(Box::new(h)); self @@ -203,7 +208,7 @@ where update_listener_error_handler: &'a Eh, ) where UListener: UpdateListener + 'a, - Eh: Handler + 'a, + Eh: AsyncHandler + 'a, ListenerE: Debug, { let update_listener = Box::pin(update_listener); diff --git a/src/dispatching/error_handlers.rs b/src/dispatching/error_handlers.rs index 848b4502..76f360df 100644 --- a/src/dispatching/error_handlers.rs +++ b/src/dispatching/error_handlers.rs @@ -1,4 +1,6 @@ -use crate::dispatching::Handler; +//! Handlers of errors. + +use crate::dispatching::AsyncHandler; use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; /// A handler that silently ignores all errors. @@ -16,7 +18,7 @@ use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; /// ``` pub struct Ignore; -impl Handler for Ignore { +impl AsyncHandler for Ignore { fn handle<'a>(&'a self, _: E) -> Pin + 'a>> where E: 'a, @@ -59,7 +61,7 @@ impl Handler for Ignore { pub struct IgnoreSafe; #[allow(unreachable_code)] -impl Handler for IgnoreSafe { +impl AsyncHandler for IgnoreSafe { fn handle<'a>( &'a self, _: Infallible, @@ -86,7 +88,7 @@ impl Handler for IgnoreSafe { /// ``` pub struct Log; -impl Handler for Log +impl AsyncHandler for Log where E: Debug, { diff --git a/src/dispatching/handler.rs b/src/dispatching/handler.rs index a2b94130..292e5e71 100644 --- a/src/dispatching/handler.rs +++ b/src/dispatching/handler.rs @@ -1,7 +1,7 @@ use std::{future::Future, pin::Pin}; /// An asynchronous polymorphic handler of a context. -pub trait Handler { +pub trait AsyncHandler { #[must_use] fn handle<'a>( &'a self, @@ -11,8 +11,7 @@ pub trait Handler { Ctx: 'a; } -/// The implementation of `Handler` for `Fn(Ctx) -> Future`. -impl Handler for F +impl AsyncHandler for F where F: Fn(Ctx) -> Fut, Fut: Future, diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index ec47ab25..c2d499fc 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -31,7 +31,7 @@ mod get_chat_id; mod storage; -use crate::{dispatching::Handler, Bot}; +use crate::{dispatching::AsyncHandler, Bot}; pub use get_chat_id::*; pub use storage::*; @@ -86,7 +86,10 @@ where /// Dispatches a single `message` from a private chat. pub async fn dispatch(&'a self, bot: &'a Bot, update: Upd) where - H: Handler, SessionState>, + H: AsyncHandler< + SessionHandlerCtx<'a, Upd, Session>, + SessionState, + >, Upd: GetChatId, { let chat_id = update.chat_id(); From 6f2abd10ef27b80b22e321fc550c48cf6f1e9495 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 29 Jan 2020 23:43:47 +0600 Subject: [PATCH 04/91] Finally fix the error --- .gitignore | 1 + examples/ping_pong_bot.rs | 28 --- examples/ping_pong_bot/Cargo.toml | 13 ++ examples/ping_pong_bot/src/main.rs | 21 ++ src/dispatching/dispatcher.rs | 308 ++++++++++++++-------------- src/dispatching/error_handlers.rs | 24 +-- src/dispatching/session/mod.rs | 10 +- src/dispatching/update_listeners.rs | 8 +- src/types/message.rs | 12 +- 9 files changed, 215 insertions(+), 210 deletions(-) delete mode 100644 examples/ping_pong_bot.rs create mode 100644 examples/ping_pong_bot/Cargo.toml create mode 100644 examples/ping_pong_bot/src/main.rs 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, } } From 1d6a21acb0eacc815e38964b60e00e59c7bf2948 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 30 Jan 2020 00:11:52 +0600 Subject: [PATCH 05/91] Add teloxide::prelude --- examples/ping_pong_bot/src/main.rs | 10 ++-------- src/dispatching/session/mod.rs | 3 ++- src/lib.rs | 1 + src/prelude.rs | 7 +++++++ 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 src/prelude.rs diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 295e7c5a..448a1d5e 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -1,14 +1,8 @@ -use teloxide::{ - dispatching::{Dispatcher, HandlerCtx}, - types::Message, - Bot, -}; - -use std::env; +use teloxide::prelude::*; #[tokio::main] async fn main() { - env::set_var("RUST_LOG", "ping_pong_bot=trace"); + std::env::set_var("RUST_LOG", "ping_pong_bot=trace"); pretty_env_logger::init(); log::info!("Starting the ping-pong bot!"); diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index d157a051..86a6fdd3 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -24,7 +24,8 @@ //! [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher //! [`dispatch(Bot, Upd)`]: //! crate::dispatching::session::SessionDispatcher::dispatch -//! [`SessionState::Terminate`]: crate::dispatching::session::SessionState::Terminate +//! [`SessionState::Terminate`]: +//! crate::dispatching::session::SessionState::Terminate // TODO: examples diff --git a/src/lib.rs b/src/lib.rs index d34b277d..d78d8762 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ mod net; mod bot; pub mod dispatching; +pub mod prelude; pub mod requests; pub mod types; pub mod utils; diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 00000000..8882fb4c --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,7 @@ +//! Commonly used items. + +pub use crate::{ + dispatching::{Dispatcher, HandlerCtx}, + types::Message, + Bot, +}; From 6d737dabb431026d03ead28b283245001c80b7f5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 30 Jan 2020 00:14:30 +0600 Subject: [PATCH 06/91] Remove the actual token --- examples/ping_pong_bot/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 448a1d5e..9a621787 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -6,9 +6,7 @@ async fn main() { pretty_env_logger::init(); log::info!("Starting the ping-pong bot!"); - let bot = Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); - - Dispatcher::new(bot) + Dispatcher::new(Bot::new("MyAwesomeToken")) .message_handler(|ctx: HandlerCtx| ctx.reply("pong")) .dispatch() .await; From c98b53b9a83ba6ce0815150c39d772a12fc49bcf Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 30 Jan 2020 01:10:02 +0600 Subject: [PATCH 07/91] Impl AsyncHandler for SessionDispatcher --- src/dispatching/session/mod.rs | 80 ++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index 86a6fdd3..4d209572 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -34,15 +34,22 @@ mod storage; use crate::{dispatching::AsyncHandler, Bot}; pub use get_chat_id::*; +use std::{future::Future, pin::Pin, sync::Arc}; pub use storage::*; /// A context of a private message handler. -pub struct SessionHandlerCtx<'a, Upd, Session> { - pub bot: &'a Bot, +pub struct SessionHandlerCtx { + pub bot: Arc, pub update: Upd, pub session: Session, } +/// A context of a session dispatcher. +pub struct SessionDispatcherCtx { + pub bot: Arc, + pub update: Upd, +} + /// Continue or terminate a user session. #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] pub enum SessionState { @@ -83,44 +90,53 @@ where handler, } } +} +impl<'a, Session, H, Upd> AsyncHandler, ()> + for SessionDispatcher<'a, Session, H> +where + H: AsyncHandler, SessionState>, + Upd: GetChatId, + Session: Default, +{ /// Dispatches a single `message` from a private chat. - pub async fn dispatch(&'a self, bot: &'a Bot, update: Upd) + fn handle<'b>( + &'b self, + ctx: SessionDispatcherCtx, + ) -> Pin + 'b>> where - H: AsyncHandler< - SessionHandlerCtx<'a, Upd, Session>, - SessionState, - >, - Upd: GetChatId, + Upd: 'b, { - let chat_id = update.chat_id(); + Box::pin(async move { + let chat_id = ctx.update.chat_id(); - let session = self - .storage - .remove_session(chat_id) - .await - .unwrap_or_default(); - - if let SessionState::Continue(new_session) = self - .handler - .handle(SessionHandlerCtx { - bot, - update, - session, - }) - .await - { - if self + let session = self .storage - .update_session(chat_id, new_session) + .remove_session(chat_id) + .await + .unwrap_or_default(); + + if let SessionState::Continue(new_session) = self + .handler + .handle(SessionHandlerCtx { + bot: ctx.bot, + update: ctx.update, + session, + }) .await - .is_some() { - panic!( - "We previously storage.remove_session() so \ - storage.update_session() must return None" - ); + if self + .storage + .update_session(chat_id, new_session) + .await + .is_some() + { + panic!( + "We previously storage.remove_session() so \ + storage.update_session() must return None" + ); + } } - } + }) } } From 1a6297747c32ceff7857edf9bdf1626e67af72e4 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 30 Jan 2020 04:54:40 +0600 Subject: [PATCH 08/91] Add examples/simple_fsm --- .gitignore | 3 +- examples/simple_fsm/Cargo.toml | 15 +++ examples/simple_fsm/src/main.rs | 174 ++++++++++++++++++++++++++++++++ src/dispatching/session/mod.rs | 34 +++++-- src/prelude.rs | 8 +- 5 files changed, 223 insertions(+), 11 deletions(-) create mode 100644 examples/simple_fsm/Cargo.toml create mode 100644 examples/simple_fsm/src/main.rs diff --git a/.gitignore b/.gitignore index 7d7050cf..f1ae605b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ Cargo.lock .idea/ .vscode/ examples/target -examples/ping_pong_bot/target \ No newline at end of file +examples/ping_pong_bot/target +examples/simple_fsm/target \ No newline at end of file diff --git a/examples/simple_fsm/Cargo.toml b/examples/simple_fsm/Cargo.toml new file mode 100644 index 00000000..bd41efe1 --- /dev/null +++ b/examples/simple_fsm/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "simple_fsm" +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" +strum = "0.17.1" +strum_macros = "0.17.1" +teloxide = { path = "../../" } \ No newline at end of file diff --git a/examples/simple_fsm/src/main.rs b/examples/simple_fsm/src/main.rs new file mode 100644 index 00000000..1a0f83fc --- /dev/null +++ b/examples/simple_fsm/src/main.rs @@ -0,0 +1,174 @@ +#[macro_use] +extern crate strum_macros; + +use std::fmt::{self, Display, Formatter}; +use teloxide::{ + prelude::*, + types::{KeyboardButton, ReplyKeyboardMarkup}, +}; + +// ============================================================================ +// [Favourite music kinds] +// ============================================================================ + +#[derive(Copy, Clone, Display, EnumString)] +enum FavouriteMusic { + Rock, + Metal, + Pop, + Other, +} + +impl FavouriteMusic { + fn markup() -> ReplyKeyboardMarkup { + ReplyKeyboardMarkup { + keyboard: vec![vec![ + KeyboardButton { + text: "Rock".to_owned(), + request: None, + }, + KeyboardButton { + text: "Metal".to_owned(), + request: None, + }, + KeyboardButton { + text: "Pop".to_owned(), + request: None, + }, + KeyboardButton { + text: "Other".to_owned(), + request: None, + }, + ]], + resize_keyboard: None, + one_time_keyboard: None, + selective: None, + } + } +} + +// ============================================================================ +// [A user's data] +// ============================================================================ + +#[derive(Default)] +struct User { + full_name: Option, + age: Option, + favourite_music: Option, +} + +impl Display for User { + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + write!( + f, + "Your full name: {}, your age: {}, your favourite music: {}", + self.full_name.as_ref().unwrap(), + self.age.unwrap(), + self.favourite_music.unwrap() + ) + } +} + +// ============================================================================ +// [Some macros] +// ============================================================================ + +#[macro_export] +macro_rules! reply { + ($ctx:ident, $text:expr) => { + $ctx.reply($text).await?; + }; +} + +// ============================================================================ +// [Control our FSM] +// ============================================================================ + +async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { + ctx.bot + .send_message(ctx.chat_id(), "Good. Now choose your favourite music:") + .reply_markup(FavouriteMusic::markup()) + .send() + .await?; + Ok(()) +} + +type Ctx = SessionHandlerCtx; +type Res = Result, RequestError>; + +async fn start(ctx: Ctx) -> Res { + reply!(ctx, "Let's start! First, what's your full name?"); + Ok(SessionState::Continue(ctx.session)) +} + +async fn full_name(mut ctx: Ctx) -> Res { + reply!(ctx, "What a wonderful name! Your age?"); + ctx.session.full_name = Some(ctx.update.text().unwrap().to_owned()); + Ok(SessionState::Continue(ctx.session)) +} + +async fn age(mut ctx: Ctx) -> Res { + match ctx.update.text().unwrap().parse() { + Ok(ok) => { + send_favourite_music_types(&ctx).await?; + ctx.session.age = Some(ok); + } + Err(_) => reply!(ctx, "Oh, please, enter a number!"), + } + + Ok(SessionState::Continue(ctx.session)) +} + +async fn favourite_music(mut ctx: Ctx) -> Res { + match ctx.update.text().unwrap().parse() { + Ok(ok) => { + ctx.session.favourite_music = Some(ok); + reply!(ctx, format!("Fine. {}", ctx.session)); + Ok(SessionState::Terminate) + } + Err(_) => { + reply!(ctx, "Oh, please, enter from the keyboard!"); + Ok(SessionState::Continue(ctx.session)) + } + } +} + +async fn handle_message(ctx: Ctx) -> Res { + if ctx.session.full_name.is_none() { + return full_name(ctx).await; + } + + if ctx.session.age.is_none() { + return age(ctx).await; + } + + if ctx.session.favourite_music.is_none() { + return favourite_music(ctx).await; + } + + Ok(SessionState::Terminate) +} + +// ============================================================================ +// [Run!] +// ============================================================================ + +#[tokio::main] +async fn main() { + std::env::set_var("RUST_LOG", "simple_fsm=trace"); + pretty_env_logger::init(); + log::info!("Starting the simple_fsm bot!"); + + Dispatcher::new(Bot::new("YourAwesomeToken")) + .message_handler(SessionDispatcher::new(|ctx| async move { + match handle_message(ctx).await { + Ok(ok) => ok, + Err(error) => { + panic!("Something wrong with the bot: {}!", error) + } + } + })) + .dispatch() + .await; +} diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index 4d209572..4fe65333 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -32,7 +32,12 @@ mod get_chat_id; mod storage; -use crate::{dispatching::AsyncHandler, Bot}; +use crate::{ + dispatching::{AsyncHandler, HandlerCtx}, + requests::{Request, ResponseResult}, + types::Message, + Bot, +}; pub use get_chat_id::*; use std::{future::Future, pin::Pin, sync::Arc}; pub use storage::*; @@ -44,10 +49,21 @@ pub struct SessionHandlerCtx { pub session: Session, } -/// A context of a session dispatcher. -pub struct SessionDispatcherCtx { - pub bot: Arc, - pub update: Upd, +impl SessionHandlerCtx { + 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(|_| ()) + } } /// Continue or terminate a user session. @@ -92,7 +108,7 @@ where } } -impl<'a, Session, H, Upd> AsyncHandler, ()> +impl<'a, Session, H, Upd> AsyncHandler, Result<(), ()>> for SessionDispatcher<'a, Session, H> where H: AsyncHandler, SessionState>, @@ -102,8 +118,8 @@ where /// Dispatches a single `message` from a private chat. fn handle<'b>( &'b self, - ctx: SessionDispatcherCtx, - ) -> Pin + 'b>> + ctx: HandlerCtx, + ) -> Pin> + 'b>> where Upd: 'b, { @@ -137,6 +153,8 @@ where ); } } + + Ok(()) }) } } diff --git a/src/prelude.rs b/src/prelude.rs index 8882fb4c..c5dcda36 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,7 +1,11 @@ //! Commonly used items. pub use crate::{ - dispatching::{Dispatcher, HandlerCtx}, + dispatching::{ + session::{SessionDispatcher, SessionHandlerCtx, SessionState}, + Dispatcher, HandlerCtx, + }, + requests::{Request, ResponseResult}, types::Message, - Bot, + Bot, RequestError, }; From 603640c5c0d39d382b30a5c850da115946443858 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 1 Feb 2020 10:05:18 +0600 Subject: [PATCH 09/91] Simplify simple_fsm --- examples/simple_fsm/src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/simple_fsm/src/main.rs b/examples/simple_fsm/src/main.rs index 1a0f83fc..82836802 100644 --- a/examples/simple_fsm/src/main.rs +++ b/examples/simple_fsm/src/main.rs @@ -162,12 +162,9 @@ async fn main() { Dispatcher::new(Bot::new("YourAwesomeToken")) .message_handler(SessionDispatcher::new(|ctx| async move { - match handle_message(ctx).await { - Ok(ok) => ok, - Err(error) => { - panic!("Something wrong with the bot: {}!", error) - } - } + handle_message(ctx) + .await + .expect("Something wrong with the bot!") })) .dispatch() .await; From 43a0a81d788b8888c3fade8b45d6e021c4d854a6 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 2 Feb 2020 21:16:23 +0600 Subject: [PATCH 10/91] Rename handler.rs -> async_handler.rs --- src/dispatching/{handler.rs => async_handler.rs} | 0 src/dispatching/mod.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/dispatching/{handler.rs => async_handler.rs} (100%) diff --git a/src/dispatching/handler.rs b/src/dispatching/async_handler.rs similarity index 100% rename from src/dispatching/handler.rs rename to src/dispatching/async_handler.rs diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 1db811c2..bd5d624f 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -2,9 +2,9 @@ mod dispatcher; pub mod error_handlers; -mod handler; +mod async_handler; pub mod session; pub mod update_listeners; pub use dispatcher::*; -pub use handler::*; +pub use async_handler::*; From d7d97ef1360149f7aab881f5a94cffdc5ce5b2fa Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 2 Feb 2020 22:03:18 +0600 Subject: [PATCH 11/91] Fix the docs --- src/dispatching/async_handler.rs | 3 +++ src/dispatching/dispatcher.rs | 11 +++++++++-- src/dispatching/mod.rs | 6 ++++-- src/dispatching/session/mod.rs | 5 +++++ src/types/animation.rs | 4 +--- src/types/callback_game.rs | 4 ++++ src/types/callback_query.rs | 12 +++++++----- src/types/encrypted_credentials.rs | 6 ++++-- src/types/file.rs | 5 +++-- src/types/force_reply.rs | 7 ++++--- src/types/game.rs | 6 ++++-- src/types/inline_query.rs | 6 ++++-- src/types/inline_query_result_audio.rs | 6 ++++-- src/types/inline_query_result_cached_audio.rs | 5 +++-- src/types/inline_query_result_cached_document.rs | 9 +++++---- src/types/inline_query_result_cached_gif.rs | 5 +++-- src/types/inline_query_result_cached_mpeg4_gif.rs | 9 +++++---- src/types/inline_query_result_cached_photo.rs | 9 +++++---- src/types/inline_query_result_cached_sticker.rs | 9 +++++---- src/types/inline_query_result_cached_video.rs | 9 +++++---- src/types/inline_query_result_cached_voice.rs | 7 ++++--- src/types/inline_query_result_contact.rs | 8 +++++--- src/types/inline_query_result_document.rs | 10 ++++++---- src/types/inline_query_result_gif.rs | 9 +++++---- src/types/inline_query_result_location.rs | 8 +++++--- src/types/inline_query_result_mpeg4_gif.rs | 6 ++++-- src/types/inline_query_result_photo.rs | 9 +++++---- src/types/inline_query_result_venue.rs | 8 +++++--- src/types/inline_query_result_video.rs | 4 +++- src/types/inline_query_result_voice.rs | 8 +++++--- src/types/keyboard_button.rs | 7 ++++--- src/types/login_url.rs | 8 +++++--- src/types/message_entity.rs | 5 +++-- src/types/non_telegram_types/mime_wrapper.rs | 9 ++++----- src/types/parse_mode.rs | 3 ++- src/types/passport_file.rs | 7 ++++--- src/types/reply_keyboard_remove.rs | 9 +++++---- src/types/unit_false.rs | 1 + src/types/unit_true.rs | 1 + 39 files changed, 163 insertions(+), 100 deletions(-) diff --git a/src/dispatching/async_handler.rs b/src/dispatching/async_handler.rs index 292e5e71..c29f2d7a 100644 --- a/src/dispatching/async_handler.rs +++ b/src/dispatching/async_handler.rs @@ -1,6 +1,9 @@ use std::{future::Future, pin::Pin}; /// An asynchronous polymorphic handler of a context. +/// +/// Note that `AsyncHandler` is implemented for asynchronous `Fn`s, that consume +/// `Ctx` and return `Output`. pub trait AsyncHandler { #[must_use] fn handle<'a>( diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index c87217a6..0fe98d2d 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -14,6 +14,9 @@ use futures::StreamExt; use std::{fmt::Debug, sync::Arc}; /// A dispatcher's handler's context of a bot and an update. +/// +/// See [the module-level documentation for the design +/// overview](teloxide::dispatching). pub struct HandlerCtx { pub bot: Arc, pub update: Upd, @@ -39,7 +42,7 @@ impl HandlerCtx { type H<'a, Upd, HandlerE> = Option, Result<(), HandlerE>> + 'a>>; -/// The main dispatcher to rule them all. +/// One dispatcher to rule them all. pub struct Dispatcher<'a, HandlerE> { bot: Arc, @@ -80,6 +83,7 @@ where } } + /// Registers a handler of errors, produced by other handlers. #[must_use] pub fn handlers_error_handler(mut self, val: T) -> Self where @@ -181,7 +185,10 @@ where self } - /// Starts your bot. + /// Starts your bot with the default parameters. + /// + /// The default parameters are a long polling update listener and log all + /// errors produced by this listener). pub async fn dispatch(&'a self) { self.dispatch_with_listener( update_listeners::polling_default(Arc::clone(&self.bot)), diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index bd5d624f..b878909a 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,10 +1,12 @@ //! Update dispatching. +//! +//! +mod async_handler; mod dispatcher; pub mod error_handlers; -mod async_handler; pub mod session; pub mod update_listeners; -pub use dispatcher::*; pub use async_handler::*; +pub use dispatcher::*; diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index 4fe65333..f4bc6100 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -74,6 +74,11 @@ pub enum SessionState { } /// A dispatcher of user sessions. +/// +/// Note that `SessionDispatcher` implements `AsyncHandler`, so you can just put +/// an instance of this dispatcher into the [`Dispatcher`]'s methods. +/// +/// [`Dispatcher`]: crate::dispatching::Dispatcher pub struct SessionDispatcher<'a, Session, H> { storage: Box + 'a>, handler: H, diff --git a/src/types/animation.rs b/src/types/animation.rs index 92d7e9e6..54694665 100644 --- a/src/types/animation.rs +++ b/src/types/animation.rs @@ -75,9 +75,7 @@ mod tests { file_size: Some(3452), }), file_name: Some("some".to_string()), - mime_type: Some(MimeWrapper { - mime: "video/gif".parse().unwrap(), - }), + mime_type: Some(MimeWrapper("video/gif".parse().unwrap())), file_size: Some(6500), }; let actual = serde_json::from_str::(json).unwrap(); diff --git a/src/types/callback_game.rs b/src/types/callback_game.rs index f2129185..358cee0c 100644 --- a/src/types/callback_game.rs +++ b/src/types/callback_game.rs @@ -3,5 +3,9 @@ /// [The official docs](https://core.telegram.org/bots/api#callbackgame). use serde::{Deserialize, Serialize}; +/// A placeholder, currently holds no information. Use [@Botfather] to set up +/// your game. +/// +/// [@Botfather]: https://t.me/botfather #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct CallbackGame; diff --git a/src/types/callback_query.rs b/src/types/callback_query.rs index d49a428e..47cedb96 100644 --- a/src/types/callback_query.rs +++ b/src/types/callback_query.rs @@ -3,11 +3,13 @@ use serde::{Deserialize, Serialize}; use crate::types::{Message, User}; /// This object represents an incoming callback query from a callback button in -/// an [inline keyboard]. If the button that originated the query was attached -/// to a message sent by the bot, the field message will be present. If the -/// button was attached to a message sent via the bot (in [inline mode]), the -/// field `inline_message_id` will be present. Exactly one of the fields data or -/// `game_short_name` will be present. +/// an [inline keyboard]. +/// +/// If the button that originated the query was attached to a message sent by +/// the bot, the field message will be present. If the button was attached to a +/// message sent via the bot (in [inline mode]), the field `inline_message_id` +/// will be present. Exactly one of the fields data or `game_short_name` will be +/// present. /// /// [The official docs](https://core.telegram.org/bots/api#callbackquery). /// diff --git a/src/types/encrypted_credentials.rs b/src/types/encrypted_credentials.rs index cd13b846..7e467192 100644 --- a/src/types/encrypted_credentials.rs +++ b/src/types/encrypted_credentials.rs @@ -1,8 +1,10 @@ use serde::{Deserialize, Serialize}; /// Contains data required for decrypting and authenticating -/// [`EncryptedPassportElement`]. See the [Telegram Passport Documentation] for -/// a complete description of the data decryption and authentication processes. +/// [`EncryptedPassportElement`]. +/// +/// See the [Telegram Passport Documentation] for a complete description of the +/// data decryption and authentication processes. /// /// [The official docs](https://core.telegram.org/bots/api#encryptedcredentials). /// diff --git a/src/types/file.rs b/src/types/file.rs index 523e6170..385a3930 100644 --- a/src/types/file.rs +++ b/src/types/file.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; -/// This object represents a file ready to be downloaded. The file can be -/// downloaded via the link `https://api.telegram.org/file/bot/`. +/// This object represents a file ready to be downloaded. +/// +/// The file can be downloaded via the link `https://api.telegram.org/file/bot/`. /// It is guaranteed that the link will be valid for at least 1 hour. When the /// link expires, a new one can be requested by calling [`Bot::get_file`]. /// diff --git a/src/types/force_reply.rs b/src/types/force_reply.rs index 6e3143ab..bd3fc44a 100644 --- a/src/types/force_reply.rs +++ b/src/types/force_reply.rs @@ -4,9 +4,10 @@ use crate::types::True; /// Upon receiving a message with this object, Telegram clients will display a /// reply interface to the user (act as if the user has selected the bot‘s -/// message and tapped ’Reply'). This can be extremely useful if you want to -/// create user-friendly step-by-step interfaces without having to sacrifice -/// [privacy mode]. +/// message and tapped ’Reply'). +/// +/// This can be extremely useful if you want to create user-friendly +/// step-by-step interfaces without having to sacrifice [privacy mode]. /// /// [The official docs](https://core.telegram.org/bots/api#forcereply). /// diff --git a/src/types/game.rs b/src/types/game.rs index 310b55d4..bd28d5c6 100644 --- a/src/types/game.rs +++ b/src/types/game.rs @@ -2,8 +2,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{Animation, MessageEntity, PhotoSize}; -/// This object represents a game. Use [@Botfather] to create and edit games, -/// their short names will act as unique identifiers. +/// This object represents a game. +/// +/// Use [@Botfather] to create and edit games, their short names will act as +/// unique identifiers. /// /// [@Botfather]: https://t.me/botfather #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query.rs b/src/types/inline_query.rs index 9740039d..8b2ab03d 100644 --- a/src/types/inline_query.rs +++ b/src/types/inline_query.rs @@ -2,8 +2,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{Location, User}; -/// This object represents an incoming inline query. When the user sends an -/// empty query, your bot could return some default or trending results. +/// This object represents an incoming inline query. +/// +/// When the user sends an empty query, your bot could return some default or +/// trending results. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequery). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_audio.rs b/src/types/inline_query_result_audio.rs index f229161a..9d9c1077 100644 --- a/src/types/inline_query_result_audio.rs +++ b/src/types/inline_query_result_audio.rs @@ -3,8 +3,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to an MP3 audio file. By default, this audio file will be -/// sent by the user. Alternatively, you can use `input_message_content` to send -/// a message with the specified content instead of the audio. +/// sent by the user. +/// +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the audio. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultaudio). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_audio.rs b/src/types/inline_query_result_cached_audio.rs index 96111406..47c2b4de 100644 --- a/src/types/inline_query_result_cached_audio.rs +++ b/src/types/inline_query_result_cached_audio.rs @@ -2,8 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to an MP3 audio file stored on the Telegram servers. By -/// default, this audio file will be sent by the user. Alternatively, you can +/// Represents a link to an MP3 audio file stored on the Telegram servers. +/// +/// By default, this audio file will be sent by the user. Alternatively, you can /// use `input_message_content` to send a message with the specified content /// instead of the audio. /// diff --git a/src/types/inline_query_result_cached_document.rs b/src/types/inline_query_result_cached_document.rs index 126a0e1b..b6c9acf6 100644 --- a/src/types/inline_query_result_cached_document.rs +++ b/src/types/inline_query_result_cached_document.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a file stored on the Telegram servers. By default, this -/// file will be sent by the user with an optional caption. Alternatively, you -/// can use `input_message_content` to send a message with the specified content -/// instead of the file. +/// Represents a link to a file stored on the Telegram servers. +/// +/// By default, this file will be sent by the user with an optional caption. +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the file. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcacheddocument). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_gif.rs b/src/types/inline_query_result_cached_gif.rs index 890f4746..5281cccb 100644 --- a/src/types/inline_query_result_cached_gif.rs +++ b/src/types/inline_query_result_cached_gif.rs @@ -2,8 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to an animated GIF file stored on the Telegram servers. By -/// default, this animated GIF file will be sent by the user with an optional +/// Represents a link to an animated GIF file stored on the Telegram servers. +/// +/// By default, this animated GIF file will be sent by the user with an optional /// caption. Alternatively, you can use `input_message_content` to send a /// message with specified content instead of the animation. /// diff --git a/src/types/inline_query_result_cached_mpeg4_gif.rs b/src/types/inline_query_result_cached_mpeg4_gif.rs index 49fe5f7f..010d2fd1 100644 --- a/src/types/inline_query_result_cached_mpeg4_gif.rs +++ b/src/types/inline_query_result_cached_mpeg4_gif.rs @@ -3,10 +3,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a video animation (H.264/MPEG-4 AVC video without -/// sound) stored on the Telegram servers. By default, this animated MPEG-4 file -/// will be sent by the user with an optional caption. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the animation. +/// sound) stored on the Telegram servers. +/// +/// By default, this animated MPEG-4 file will be sent by the user with an +/// optional caption. Alternatively, you can use `input_message_content` to send +/// a message with the specified content instead of the animation. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_photo.rs b/src/types/inline_query_result_cached_photo.rs index 57d1d849..d528e33c 100644 --- a/src/types/inline_query_result_cached_photo.rs +++ b/src/types/inline_query_result_cached_photo.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a photo stored on the Telegram servers. By default, -/// this photo will be sent by the user with an optional caption. Alternatively, -/// you can use `input_message_content` to send a message with the specified -/// content instead of the photo. +/// Represents a link to a photo stored on the Telegram servers. +/// +/// By default, this photo will be sent by the user with an optional caption. +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the photo. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedphoto). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_sticker.rs b/src/types/inline_query_result_cached_sticker.rs index 25e1cb3e..75437e5a 100644 --- a/src/types/inline_query_result_cached_sticker.rs +++ b/src/types/inline_query_result_cached_sticker.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent}; -/// Represents a link to a sticker stored on the Telegram servers. By default, -/// this sticker will be sent by the user. Alternatively, you can use -/// `input_message_content` to send a message with the specified content instead -/// of the sticker. +/// Represents a link to a sticker stored on the Telegram servers. +/// +/// By default, this sticker will be sent by the user. Alternatively, you can +/// use `input_message_content` to send a message with the specified content +/// instead of the sticker. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedsticker). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_video.rs b/src/types/inline_query_result_cached_video.rs index d800efa1..57ba728e 100644 --- a/src/types/inline_query_result_cached_video.rs +++ b/src/types/inline_query_result_cached_video.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a video file stored on the Telegram servers. By -/// default, this video file will be sent by the user with an optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the video. +/// Represents a link to a video file stored on the Telegram servers. +/// +/// By default, this video file will be sent by the user with an optional +/// caption. Alternatively, you can use `input_message_content` to send a +/// message with the specified content instead of the video. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_voice.rs b/src/types/inline_query_result_cached_voice.rs index 4a60020b..354f11f3 100644 --- a/src/types/inline_query_result_cached_voice.rs +++ b/src/types/inline_query_result_cached_voice.rs @@ -2,9 +2,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a voice message stored on the Telegram servers. By -/// default, this voice message will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content +/// Represents a link to a voice message stored on the Telegram servers. +/// +/// By default, this voice message will be sent by the user. Alternatively, you +/// can use `input_message_content` to send a message with the specified content /// instead of the voice message. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). diff --git a/src/types/inline_query_result_contact.rs b/src/types/inline_query_result_contact.rs index 4622d66e..819bbd75 100644 --- a/src/types/inline_query_result_contact.rs +++ b/src/types/inline_query_result_contact.rs @@ -2,9 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent}; -/// Represents a contact with a phone number. By default, this contact will be -/// sent by the user. Alternatively, you can use `input_message_content` to send -/// a message with the specified content instead of the contact. +/// Represents a contact with a phone number. +/// +/// By default, this contact will be sent by the user. Alternatively, you can +/// use `input_message_content` to send a message with the specified content +/// instead of the contact. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_document.rs b/src/types/inline_query_result_document.rs index 6063614a..248f53c7 100644 --- a/src/types/inline_query_result_document.rs +++ b/src/types/inline_query_result_document.rs @@ -2,10 +2,12 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a file. By default, this file will be sent by the user -/// with an optional caption. Alternatively, you can use `input_message_content` -/// to send a message with the specified content instead of the file. Currently, -/// only **.PDF** and **.ZIP** files can be sent using this method. +/// Represents a link to a file. +/// +/// By default, this file will be sent by the user with an optional caption. +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the file. Currently, only **.PDF** and +/// **.ZIP** files can be sent using this method. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultdocument). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_gif.rs b/src/types/inline_query_result_gif.rs index b6c7bf1d..3f0f8500 100644 --- a/src/types/inline_query_result_gif.rs +++ b/src/types/inline_query_result_gif.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to an animated GIF file. By default, this animated GIF -/// file will be sent by the user with optional caption. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the animation. +/// Represents a link to an animated GIF file. +/// +/// By default, this animated GIF file will be sent by the user with optional +/// caption. Alternatively, you can use `input_message_content` to send a +/// message with the specified content instead of the animation. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultgif). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_location.rs b/src/types/inline_query_result_location.rs index 5b88605a..e5ed5d4e 100644 --- a/src/types/inline_query_result_location.rs +++ b/src/types/inline_query_result_location.rs @@ -2,9 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent}; -/// Represents a location on a map. By default, the location will be sent by the -/// user. Alternatively, you can use `input_message_content` to send a message -/// with the specified content instead of the location. +/// Represents a location on a map. +/// +/// By default, the location will be sent by the user. Alternatively, you can +/// use `input_message_content` to send a message with the specified content +/// instead of the location. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultlocation). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_mpeg4_gif.rs b/src/types/inline_query_result_mpeg4_gif.rs index 0eb49890..d9f50e70 100644 --- a/src/types/inline_query_result_mpeg4_gif.rs +++ b/src/types/inline_query_result_mpeg4_gif.rs @@ -3,8 +3,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a video animation (H.264/MPEG-4 AVC video without -/// sound). By default, this animated MPEG-4 file will be sent by the user with -/// optional caption. Alternatively, you can use `input_message_content` to send +/// sound). +/// +/// By default, this animated MPEG-4 file will be sent by the user with optional +/// caption. Alternatively, you can use `input_message_content` to send /// a message with the specified content instead of the animation. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif). diff --git a/src/types/inline_query_result_photo.rs b/src/types/inline_query_result_photo.rs index b3a8eaca..8b08c267 100644 --- a/src/types/inline_query_result_photo.rs +++ b/src/types/inline_query_result_photo.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a photo. By default, this photo will be sent by the -/// user with optional caption. Alternatively, you can use -/// `input_message_content` to send a message with the specified content instead -/// of the photo. +/// Represents a link to a photo. +/// +/// By default, this photo will be sent by the user with optional caption. +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the photo. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultphoto). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_venue.rs b/src/types/inline_query_result_venue.rs index 94ef15db..46485a88 100644 --- a/src/types/inline_query_result_venue.rs +++ b/src/types/inline_query_result_venue.rs @@ -2,9 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent}; -/// Represents a venue. By default, the venue will be sent by the user. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the venue. +/// Represents a venue. +/// +/// By default, the venue will be sent by the user. Alternatively, you can use +/// `input_message_content` to send a message with the specified content instead +/// of the venue. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvenue). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_video.rs b/src/types/inline_query_result_video.rs index e0cea329..8df2ec7f 100644 --- a/src/types/inline_query_result_video.rs +++ b/src/types/inline_query_result_video.rs @@ -3,7 +3,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a page containing an embedded video player or a video -/// file. By default, this video file will be sent by the user with an optional +/// file. +/// +/// By default, this video file will be sent by the user with an optional /// caption. Alternatively, you can use `input_messaage_content` to send a /// message with the specified content instead of the video. /// diff --git a/src/types/inline_query_result_voice.rs b/src/types/inline_query_result_voice.rs index 4ddae8e3..efd6aaa2 100644 --- a/src/types/inline_query_result_voice.rs +++ b/src/types/inline_query_result_voice.rs @@ -3,9 +3,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a voice recording in an .ogg container encoded with -/// OPUS. By default, this voice recording will be sent by the user. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the the voice message. +/// OPUS. +/// +/// By default, this voice recording will be sent by the user. Alternatively, +/// you can use `input_message_content` to send a message with the specified +/// content instead of the the voice message. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvoice). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs index 5a3c994d..5e428201 100644 --- a/src/types/keyboard_button.rs +++ b/src/types/keyboard_button.rs @@ -2,9 +2,10 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; use crate::types::True; -/// This object represents one button of the reply keyboard. For filter text -/// buttons String can be used instead of this object to specify text of the -/// button. +/// This object represents one button of the reply keyboard. +/// +/// For filter text buttons String can be used instead of this object to specify +/// text of the button. /// /// [The official docs](https://core.telegram.org/bots/api#keyboardbutton). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/login_url.rs b/src/types/login_url.rs index 373e7f6c..2e2b7f94 100644 --- a/src/types/login_url.rs +++ b/src/types/login_url.rs @@ -1,9 +1,11 @@ use serde::{Deserialize, Serialize}; /// This object represents a parameter of the inline keyboard button used to -/// automatically authorize a user. Serves as a great replacement for the -/// [Telegram Login Widget] when the user is coming from Telegram. All the user -/// needs to do is tap/click a button and confirm that they want to log in: +/// automatically authorize a user. +/// +/// Serves as a great replacement for the [Telegram Login Widget] when the user +/// is coming from Telegram. All the user needs to do is tap/click a button and +/// confirm that they want to log in: /// ///

/// diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index 9a649176..da383300 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -2,8 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{Message, User}; -/// This object represents one special entity in a text message. For example, -/// hashtags, usernames, URLs, etc. +/// This object represents one special entity in a text message. +/// +/// For example, hashtags, usernames, URLs, etc. /// /// [The official docs](https://core.telegram.org/bots/api#messageentity). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] diff --git a/src/types/non_telegram_types/mime_wrapper.rs b/src/types/non_telegram_types/mime_wrapper.rs index 20222e1c..d45a2dfb 100644 --- a/src/types/non_telegram_types/mime_wrapper.rs +++ b/src/types/non_telegram_types/mime_wrapper.rs @@ -5,10 +5,9 @@ use serde::{ Serializer, }; +/// Serializable & deserializable `MIME` wrapper. #[derive(Clone, Debug, Eq, Hash, PartialEq, From)] -pub struct MimeWrapper { - pub mime: Mime, -} +pub struct MimeWrapper(pub Mime); impl Serialize for MimeWrapper { fn serialize( @@ -18,7 +17,7 @@ impl Serialize for MimeWrapper { where S: Serializer, { - serializer.serialize_str(self.mime.as_ref()) + serializer.serialize_str(self.0.as_ref()) } } @@ -38,7 +37,7 @@ impl<'a> Visitor<'a> for MimeVisitor { E: serde::de::Error, { match v.parse::() { - Ok(mime_type) => Ok(MimeWrapper { mime: mime_type }), + Ok(mime_type) => Ok(MimeWrapper(mime_type)), Err(e) => Err(E::custom(e)), } } diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs index 93ad99ae..862c7c02 100644 --- a/src/types/parse_mode.rs +++ b/src/types/parse_mode.rs @@ -9,7 +9,8 @@ use std::{ use serde::{Deserialize, Serialize}; -/// ## Formatting options +/// Formatting options. +/// /// The Bot API supports basic formatting for messages. You can use bold, /// italic, underlined and strikethrough text, as well as inline links and /// pre-formatted code in your bots' messages. Telegram clients will render diff --git a/src/types/passport_file.rs b/src/types/passport_file.rs index 160e546e..18f1a5d4 100644 --- a/src/types/passport_file.rs +++ b/src/types/passport_file.rs @@ -1,8 +1,9 @@ use serde::{Deserialize, Serialize}; -/// This object represents a file uploaded to Telegram Passport. Currently all -/// Telegram Passport files are in JPEG format when decrypted and don't exceed -/// 10MB. +/// This object represents a file uploaded to Telegram Passport. +/// +/// Currently all Telegram Passport files are in JPEG format when decrypted and +/// don't exceed 10MB. /// /// [The official docs](https://core.telegram.org/bots/api#passportfile). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] diff --git a/src/types/reply_keyboard_remove.rs b/src/types/reply_keyboard_remove.rs index 8537433d..35d55db7 100644 --- a/src/types/reply_keyboard_remove.rs +++ b/src/types/reply_keyboard_remove.rs @@ -3,10 +3,11 @@ use serde::{Deserialize, Serialize}; use crate::types::True; /// Upon receiving a message with this object, Telegram clients will remove the -/// current custom keyboard and display the default letter-keyboard. By default, -/// custom keyboards are displayed until a new keyboard is sent by a bot. An -/// exception is made for one-time keyboards that are hidden immediately after -/// the user presses a button (see [`ReplyKeyboardMarkup`]). +/// current custom keyboard and display the default letter-keyboard. +/// +/// By default, custom keyboards are displayed until a new keyboard is sent by a +/// bot. An exception is made for one-time keyboards that are hidden immediately +/// after the user presses a button (see [`ReplyKeyboardMarkup`]). /// /// [The official docs](https://core.telegram.org/bots/api#replykeyboardremove). /// diff --git a/src/types/unit_false.rs b/src/types/unit_false.rs index 7821f313..058758f9 100644 --- a/src/types/unit_false.rs +++ b/src/types/unit_false.rs @@ -1,5 +1,6 @@ use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +/// A type that is always false. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Default)] pub struct False; diff --git a/src/types/unit_true.rs b/src/types/unit_true.rs index 619cb7cc..cd71e5c2 100644 --- a/src/types/unit_true.rs +++ b/src/types/unit_true.rs @@ -3,6 +3,7 @@ use serde::{ ser::{Serialize, Serializer}, }; +/// A type that is always true. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Default)] pub struct True; From 61d002b8d4c7a8b7a35135b9db45160faefd2ca4 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 2 Feb 2020 22:32:27 +0600 Subject: [PATCH 12/91] Refactor --- examples/simple_fsm/Cargo.toml | 5 +- examples/simple_fsm/src/main.rs | 12 +- src/dispatching/dispatcher.rs | 29 +--- src/dispatching/handler_ctx.rs | 38 +++++ src/dispatching/mod.rs | 8 +- src/dispatching/session/mod.rs | 139 +----------------- src/dispatching/session/session_dispatcher.rs | 98 ++++++++++++ .../session/session_handler_ctx.rs | 38 +++++ src/dispatching/session/session_state.rs | 6 + src/prelude.rs | 4 +- 10 files changed, 206 insertions(+), 171 deletions(-) create mode 100644 src/dispatching/handler_ctx.rs create mode 100644 src/dispatching/session/session_dispatcher.rs create mode 100644 src/dispatching/session/session_handler_ctx.rs create mode 100644 src/dispatching/session/session_state.rs diff --git a/examples/simple_fsm/Cargo.toml b/examples/simple_fsm/Cargo.toml index bd41efe1..925430c6 100644 --- a/examples/simple_fsm/Cargo.toml +++ b/examples/simple_fsm/Cargo.toml @@ -12,4 +12,7 @@ log = "0.4.8" tokio = "0.2.9" strum = "0.17.1" strum_macros = "0.17.1" -teloxide = { path = "../../" } \ No newline at end of file +teloxide = { path = "../../" } + +[profile.release] +lto = true \ No newline at end of file diff --git a/examples/simple_fsm/src/main.rs b/examples/simple_fsm/src/main.rs index 82836802..743f781a 100644 --- a/examples/simple_fsm/src/main.rs +++ b/examples/simple_fsm/src/main.rs @@ -99,13 +99,13 @@ type Res = Result, RequestError>; async fn start(ctx: Ctx) -> Res { reply!(ctx, "Let's start! First, what's your full name?"); - Ok(SessionState::Continue(ctx.session)) + Ok(SessionState::Next(ctx.session)) } async fn full_name(mut ctx: Ctx) -> Res { reply!(ctx, "What a wonderful name! Your age?"); ctx.session.full_name = Some(ctx.update.text().unwrap().to_owned()); - Ok(SessionState::Continue(ctx.session)) + Ok(SessionState::Next(ctx.session)) } async fn age(mut ctx: Ctx) -> Res { @@ -117,7 +117,7 @@ async fn age(mut ctx: Ctx) -> Res { Err(_) => reply!(ctx, "Oh, please, enter a number!"), } - Ok(SessionState::Continue(ctx.session)) + Ok(SessionState::Next(ctx.session)) } async fn favourite_music(mut ctx: Ctx) -> Res { @@ -125,11 +125,11 @@ async fn favourite_music(mut ctx: Ctx) -> Res { Ok(ok) => { ctx.session.favourite_music = Some(ok); reply!(ctx, format!("Fine. {}", ctx.session)); - Ok(SessionState::Terminate) + Ok(SessionState::Exit) } Err(_) => { reply!(ctx, "Oh, please, enter from the keyboard!"); - Ok(SessionState::Continue(ctx.session)) + Ok(SessionState::Next(ctx.session)) } } } @@ -147,7 +147,7 @@ async fn handle_message(ctx: Ctx) -> Res { return favourite_music(ctx).await; } - Ok(SessionState::Terminate) + Ok(SessionState::Exit) } // ============================================================================ diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 0fe98d2d..75851ed6 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -1,9 +1,8 @@ use crate::{ dispatching::{ error_handlers, update_listeners, update_listeners::UpdateListener, - AsyncHandler, + AsyncHandler, HandlerCtx, }, - requests::{Request, ResponseResult}, types::{ CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, PreCheckoutQuery, ShippingQuery, UpdateKind, @@ -13,32 +12,6 @@ use crate::{ use futures::StreamExt; use std::{fmt::Debug, sync::Arc}; -/// A dispatcher's handler's context of a bot and an update. -/// -/// See [the module-level documentation for the design -/// overview](teloxide::dispatching). -pub struct HandlerCtx { - pub bot: Arc, - pub update: Upd, -} - -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>>; diff --git a/src/dispatching/handler_ctx.rs b/src/dispatching/handler_ctx.rs new file mode 100644 index 00000000..5b487575 --- /dev/null +++ b/src/dispatching/handler_ctx.rs @@ -0,0 +1,38 @@ +use crate::{ + dispatching::session::GetChatId, + requests::{Request, ResponseResult}, + types::Message, + Bot, +}; +use std::sync::Arc; + +/// A dispatcher's handler's context of a bot and an update. +/// +/// See [the module-level documentation for the design +/// overview](teloxide::dispatching). +pub struct HandlerCtx { + pub bot: Arc, + pub update: Upd, +} + +impl GetChatId for HandlerCtx +where + Upd: GetChatId, +{ + fn chat_id(&self) -> i64 { + self.update.chat_id() + } +} + +impl HandlerCtx { + pub async fn reply(&self, text: T) -> ResponseResult<()> + where + T: Into, + { + self.bot + .send_message(self.chat_id(), text) + .send() + .await + .map(|_| ()) + } +} diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index b878909a..d19dde82 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,12 +1,12 @@ //! Update dispatching. //! -//! - mod async_handler; mod dispatcher; pub mod error_handlers; +mod handler_ctx; pub mod session; pub mod update_listeners; -pub use async_handler::*; -pub use dispatcher::*; +pub use async_handler::AsyncHandler; +pub use dispatcher::Dispatcher; +pub use handler_ctx::HandlerCtx; diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index f4bc6100..a64b7161 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -30,136 +30,13 @@ // TODO: examples mod get_chat_id; +mod session_dispatcher; +mod session_handler_ctx; +mod session_state; mod storage; -use crate::{ - dispatching::{AsyncHandler, HandlerCtx}, - requests::{Request, ResponseResult}, - types::Message, - Bot, -}; -pub use get_chat_id::*; -use std::{future::Future, pin::Pin, sync::Arc}; -pub use storage::*; - -/// A context of a private message handler. -pub struct SessionHandlerCtx { - pub bot: Arc, - pub update: Upd, - pub session: Session, -} - -impl SessionHandlerCtx { - 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(|_| ()) - } -} - -/// Continue or terminate a user session. -#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum SessionState { - Continue(Session), - Terminate, -} - -/// A dispatcher of user sessions. -/// -/// Note that `SessionDispatcher` implements `AsyncHandler`, so you can just put -/// an instance of this dispatcher into the [`Dispatcher`]'s methods. -/// -/// [`Dispatcher`]: crate::dispatching::Dispatcher -pub struct SessionDispatcher<'a, Session, H> { - storage: Box + 'a>, - handler: H, -} - -impl<'a, Session, H> SessionDispatcher<'a, Session, H> -where - Session: Default + 'a, -{ - /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] - /// (a default storage). - /// - /// [`InMemStorage`]: crate::dispatching::session::InMemStorage - #[must_use] - pub fn new(handler: H) -> Self { - Self { - storage: Box::new(InMemStorage::default()), - handler, - } - } - - /// Creates a dispatcher with the specified `handler` and `storage`. - #[must_use] - pub fn with_storage(handler: H, storage: Stg) -> Self - where - Stg: Storage + 'a, - { - Self { - storage: Box::new(storage), - handler, - } - } -} - -impl<'a, Session, H, Upd> AsyncHandler, Result<(), ()>> - for SessionDispatcher<'a, Session, H> -where - H: AsyncHandler, SessionState>, - Upd: GetChatId, - Session: Default, -{ - /// Dispatches a single `message` from a private chat. - fn handle<'b>( - &'b self, - ctx: HandlerCtx, - ) -> Pin> + 'b>> - where - Upd: 'b, - { - Box::pin(async move { - let chat_id = ctx.update.chat_id(); - - let session = self - .storage - .remove_session(chat_id) - .await - .unwrap_or_default(); - - if let SessionState::Continue(new_session) = self - .handler - .handle(SessionHandlerCtx { - bot: ctx.bot, - update: ctx.update, - session, - }) - .await - { - if self - .storage - .update_session(chat_id, new_session) - .await - .is_some() - { - panic!( - "We previously storage.remove_session() so \ - storage.update_session() must return None" - ); - } - } - - Ok(()) - }) - } -} +pub use get_chat_id::GetChatId; +pub use session_dispatcher::SessionDispatcher; +pub use session_handler_ctx::SessionHandlerCtx; +pub use session_state::SessionState; +pub use storage::{InMemStorage, Storage}; diff --git a/src/dispatching/session/session_dispatcher.rs b/src/dispatching/session/session_dispatcher.rs new file mode 100644 index 00000000..6a64b720 --- /dev/null +++ b/src/dispatching/session/session_dispatcher.rs @@ -0,0 +1,98 @@ +use crate::dispatching::{ + session::{ + GetChatId, InMemStorage, SessionHandlerCtx, SessionState, Storage, + }, + AsyncHandler, HandlerCtx, +}; +use std::{future::Future, pin::Pin}; + +/// A dispatcher of user sessions. +/// +/// Note that `SessionDispatcher` implements `AsyncHandler`, so you can just put +/// an instance of this dispatcher into the [`Dispatcher`]'s methods. +/// +/// [`Dispatcher`]: crate::dispatching::Dispatcher +pub struct SessionDispatcher<'a, Session, H> { + storage: Box + 'a>, + handler: H, +} + +impl<'a, Session, H> SessionDispatcher<'a, Session, H> +where + Session: Default + 'a, +{ + /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] + /// (a default storage). + /// + /// [`InMemStorage`]: crate::dispatching::session::InMemStorage + #[must_use] + pub fn new(handler: H) -> Self { + Self { + storage: Box::new(InMemStorage::default()), + handler, + } + } + + /// Creates a dispatcher with the specified `handler` and `storage`. + #[must_use] + pub fn with_storage(handler: H, storage: Stg) -> Self + where + Stg: Storage + 'a, + { + Self { + storage: Box::new(storage), + handler, + } + } +} + +impl<'a, Session, H, Upd> AsyncHandler, Result<(), ()>> + for SessionDispatcher<'a, Session, H> +where + H: AsyncHandler, SessionState>, + Upd: GetChatId, + Session: Default, +{ + /// Dispatches a single `message` from a private chat. + fn handle<'b>( + &'b self, + ctx: HandlerCtx, + ) -> Pin> + 'b>> + where + Upd: 'b, + { + Box::pin(async move { + let chat_id = ctx.update.chat_id(); + + let session = self + .storage + .remove_session(chat_id) + .await + .unwrap_or_default(); + + if let SessionState::Next(new_session) = self + .handler + .handle(SessionHandlerCtx { + bot: ctx.bot, + update: ctx.update, + session, + }) + .await + { + if self + .storage + .update_session(chat_id, new_session) + .await + .is_some() + { + panic!( + "We previously storage.remove_session() so \ + storage.update_session() must return None" + ); + } + } + + Ok(()) + }) + } +} diff --git a/src/dispatching/session/session_handler_ctx.rs b/src/dispatching/session/session_handler_ctx.rs new file mode 100644 index 00000000..44ef8294 --- /dev/null +++ b/src/dispatching/session/session_handler_ctx.rs @@ -0,0 +1,38 @@ +use crate::{ + dispatching::session::GetChatId, + requests::{Request, ResponseResult}, + types::Message, + Bot, +}; +use std::sync::Arc; + +/// A context of a [`SessionDispatcher`]'s message handler. +/// +/// [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher +pub struct SessionHandlerCtx { + pub bot: Arc, + pub update: Upd, + pub session: Session, +} + +impl GetChatId for SessionHandlerCtx +where + Upd: GetChatId, +{ + fn chat_id(&self) -> i64 { + self.update.chat_id() + } +} + +impl SessionHandlerCtx { + pub async fn reply(&self, text: T) -> ResponseResult<()> + where + T: Into, + { + self.bot + .send_message(self.chat_id(), text) + .send() + .await + .map(|_| ()) + } +} diff --git a/src/dispatching/session/session_state.rs b/src/dispatching/session/session_state.rs new file mode 100644 index 00000000..636999cc --- /dev/null +++ b/src/dispatching/session/session_state.rs @@ -0,0 +1,6 @@ +/// Continue or terminate a user session. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum SessionState { + Next(Session), + Exit, +} diff --git a/src/prelude.rs b/src/prelude.rs index c5dcda36..e8874e39 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,7 +2,9 @@ pub use crate::{ dispatching::{ - session::{SessionDispatcher, SessionHandlerCtx, SessionState}, + session::{ + GetChatId, SessionDispatcher, SessionHandlerCtx, SessionState, + }, Dispatcher, HandlerCtx, }, requests::{Request, ResponseResult}, From f436a0269b057a23f40ff6810dccff802b24b201 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 2 Feb 2020 22:44:48 +0600 Subject: [PATCH 13/91] Add lto=true to ping_pong_bot for releases --- examples/ping_pong_bot/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/ping_pong_bot/Cargo.toml b/examples/ping_pong_bot/Cargo.toml index 1834b76b..291ee2d6 100644 --- a/examples/ping_pong_bot/Cargo.toml +++ b/examples/ping_pong_bot/Cargo.toml @@ -10,4 +10,7 @@ edition = "2018" pretty_env_logger = "0.3.1" log = "0.4.8" tokio = "0.2.9" -teloxide = { path = "../../" } \ No newline at end of file +teloxide = { path = "../../" } + +[profile.release] +lto = true \ No newline at end of file From 2c4102e2b35dab14d25da06718f659167cdbc3f2 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 3 Feb 2020 00:54:11 +0600 Subject: [PATCH 14/91] Simplify building ReplyKeyboardMarkup --- examples/simple_fsm/src/main.rs | 57 ++++++++--------------------- src/types/inline_keyboard_markup.rs | 4 -- src/types/keyboard_button.rs | 22 +++++++++++ src/types/reply_keyboard_markup.rs | 45 ++++++++++++++++++++++- 4 files changed, 81 insertions(+), 47 deletions(-) diff --git a/examples/simple_fsm/src/main.rs b/examples/simple_fsm/src/main.rs index 743f781a..3b7853c9 100644 --- a/examples/simple_fsm/src/main.rs +++ b/examples/simple_fsm/src/main.rs @@ -21,29 +21,12 @@ enum FavouriteMusic { impl FavouriteMusic { fn markup() -> ReplyKeyboardMarkup { - ReplyKeyboardMarkup { - keyboard: vec![vec![ - KeyboardButton { - text: "Rock".to_owned(), - request: None, - }, - KeyboardButton { - text: "Metal".to_owned(), - request: None, - }, - KeyboardButton { - text: "Pop".to_owned(), - request: None, - }, - KeyboardButton { - text: "Other".to_owned(), - request: None, - }, - ]], - resize_keyboard: None, - one_time_keyboard: None, - selective: None, - } + ReplyKeyboardMarkup::default().append_row(vec![ + KeyboardButton::new("Rock"), + KeyboardButton::new("Metal"), + KeyboardButton::new("Pop"), + KeyboardButton::new("Other"), + ]) } } @@ -70,21 +53,13 @@ impl Display for User { } } -// ============================================================================ -// [Some macros] -// ============================================================================ - -#[macro_export] -macro_rules! reply { - ($ctx:ident, $text:expr) => { - $ctx.reply($text).await?; - }; -} - // ============================================================================ // [Control our FSM] // ============================================================================ +type Ctx = SessionHandlerCtx; +type Res = Result, RequestError>; + async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { ctx.bot .send_message(ctx.chat_id(), "Good. Now choose your favourite music:") @@ -94,16 +69,14 @@ async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { Ok(()) } -type Ctx = SessionHandlerCtx; -type Res = Result, RequestError>; - async fn start(ctx: Ctx) -> Res { - reply!(ctx, "Let's start! First, what's your full name?"); + ctx.reply("Let's start! First, what's your full name?") + .await?; Ok(SessionState::Next(ctx.session)) } async fn full_name(mut ctx: Ctx) -> Res { - reply!(ctx, "What a wonderful name! Your age?"); + ctx.reply("What a wonderful name! Your age?").await?; ctx.session.full_name = Some(ctx.update.text().unwrap().to_owned()); Ok(SessionState::Next(ctx.session)) } @@ -114,7 +87,7 @@ async fn age(mut ctx: Ctx) -> Res { send_favourite_music_types(&ctx).await?; ctx.session.age = Some(ok); } - Err(_) => reply!(ctx, "Oh, please, enter a number!"), + Err(_) => ctx.reply("Oh, please, enter a number!").await?, } Ok(SessionState::Next(ctx.session)) @@ -124,11 +97,11 @@ async fn favourite_music(mut ctx: Ctx) -> Res { match ctx.update.text().unwrap().parse() { Ok(ok) => { ctx.session.favourite_music = Some(ok); - reply!(ctx, format!("Fine. {}", ctx.session)); + ctx.reply(format!("Fine. {}", ctx.session)).await?; Ok(SessionState::Exit) } Err(_) => { - reply!(ctx, "Oh, please, enter from the keyboard!"); + ctx.reply("Oh, please, enter from the keyboard!").await?; Ok(SessionState::Next(ctx.session)) } } diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs index 0a35e799..8361509d 100644 --- a/src/types/inline_keyboard_markup.rs +++ b/src/types/inline_keyboard_markup.rs @@ -33,10 +33,6 @@ pub struct InlineKeyboardMarkup { /// let keyboard = InlineKeyboardMarkup::new().append_row(vec![url_button]); /// ``` impl InlineKeyboardMarkup { - pub fn new() -> Self { - <_>::default() - } - pub fn append_row(mut self, buttons: Vec) -> Self { self.inline_keyboard.push(buttons); self diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs index 5e428201..ee68c4d1 100644 --- a/src/types/keyboard_button.rs +++ b/src/types/keyboard_button.rs @@ -24,6 +24,28 @@ pub struct KeyboardButton { pub request: Option, } +impl KeyboardButton { + /// Creates `KeyboardButton` with the provided `text` and all the other + /// fields set to `None`. + pub fn new(text: T) -> Self + where + T: Into, + { + Self { + text: text.into(), + request: None, + } + } + + pub fn request(mut self, val: T) -> Self + where + T: Into>, + { + self.request = val.into(); + self + } +} + // Serialize + Deserialize are implemented by hand #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum ButtonRequest { diff --git a/src/types/reply_keyboard_markup.rs b/src/types/reply_keyboard_markup.rs index f2cd674e..59707764 100644 --- a/src/types/reply_keyboard_markup.rs +++ b/src/types/reply_keyboard_markup.rs @@ -10,7 +10,7 @@ use crate::types::KeyboardButton; /// [custom keyboard]: https://core.telegram.org/bots#keyboards /// [Introduction to bots]: https://core.telegram.org/bots#keyboards #[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Default)] pub struct ReplyKeyboardMarkup { /// Array of button rows, each represented by an Array of /// [`KeyboardButton`] objects @@ -43,3 +43,46 @@ pub struct ReplyKeyboardMarkup { /// [`Message`]: crate::types::Message pub selective: Option, } + +impl ReplyKeyboardMarkup { + pub fn append_row(mut self, buttons: Vec) -> Self { + self.keyboard.push(buttons); + self + } + + pub fn append_to_row( + mut self, + button: KeyboardButton, + index: usize, + ) -> Self { + match self.keyboard.get_mut(index) { + Some(buttons) => buttons.push(button), + None => self.keyboard.push(vec![button]), + }; + self + } + + pub fn resize_keyboard(mut self, val: T) -> Self + where + T: Into>, + { + self.resize_keyboard = val.into(); + self + } + + pub fn one_time_keyboard(mut self, val: T) -> Self + where + T: Into>, + { + self.one_time_keyboard = val.into(); + self + } + + pub fn selective(mut self, val: T) -> Self + where + T: Into>, + { + self.selective = val.into(); + self + } +} From 32d607daa9f71aa500655a6f77e863dc9620290a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 3 Feb 2020 01:12:36 +0600 Subject: [PATCH 15/91] Fix ping-pong-bot --- examples/ping_pong_bot/src/main.rs | 4 +++- src/types/inline_keyboard_markup.rs | 6 +++--- src/types/inline_query_result.rs | 2 +- src/types/reply_markup.rs | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 9a621787..d8eb2abb 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -7,7 +7,9 @@ async fn main() { log::info!("Starting the ping-pong bot!"); Dispatcher::new(Bot::new("MyAwesomeToken")) - .message_handler(|ctx: HandlerCtx| ctx.reply("pong")) + .message_handler(|ctx: HandlerCtx| async move { + ctx.reply("pong").await + }) .dispatch() .await; } diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs index 8361509d..cfc58588 100644 --- a/src/types/inline_keyboard_markup.rs +++ b/src/types/inline_keyboard_markup.rs @@ -66,7 +66,7 @@ mod tests { "url 2".to_string(), ); - let markup = InlineKeyboardMarkup::new() + let markup = InlineKeyboardMarkup::default() .append_row(vec![button1.clone(), button2.clone()]); let expected = InlineKeyboardMarkup { @@ -87,7 +87,7 @@ mod tests { "url 2".to_string(), ); - let markup = InlineKeyboardMarkup::new() + let markup = InlineKeyboardMarkup::default() .append_row(vec![button1.clone()]) .append_to_row(button2.clone(), 0); @@ -109,7 +109,7 @@ mod tests { "url 2".to_string(), ); - let markup = InlineKeyboardMarkup::new() + let markup = InlineKeyboardMarkup::default() .append_row(vec![button1.clone()]) .append_to_row(button2.clone(), 1); diff --git a/src/types/inline_query_result.rs b/src/types/inline_query_result.rs index de429703..b4273dce 100644 --- a/src/types/inline_query_result.rs +++ b/src/types/inline_query_result.rs @@ -88,7 +88,7 @@ mod tests { audio_file_id: String::from("audio_file_id"), caption: Some(String::from("caption")), parse_mode: Some(ParseMode::HTML), - reply_markup: Some(InlineKeyboardMarkup::new()), + reply_markup: Some(InlineKeyboardMarkup::default()), input_message_content: Some(InputMessageContent::Text { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), diff --git a/src/types/reply_markup.rs b/src/types/reply_markup.rs index 57840d48..f69245e6 100644 --- a/src/types/reply_markup.rs +++ b/src/types/reply_markup.rs @@ -20,7 +20,7 @@ mod tests { #[test] fn inline_keyboard_markup() { - let data = InlineKeyboardMarkup::new(); + let data = InlineKeyboardMarkup::default(); let expected = ReplyMarkup::InlineKeyboardMarkup(data.clone()); let actual: ReplyMarkup = data.into(); assert_eq!(actual, expected) From 5dafe892ac5a9be1cd5621f7f7949ebc4fd9e266 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sun, 2 Feb 2020 21:22:58 +0200 Subject: [PATCH 16/91] added function handle in dispatcher --- src/dispatching/dispatcher.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 75851ed6..c59fc858 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -328,20 +328,26 @@ where } } UpdateKind::Poll(poll) => { - if let Some(poll_handler) = &self.poll_handler { - if let Err(error) = poll_handler - .handle(HandlerCtx { - bot: Arc::clone(&self.bot), - update: poll, - }) - .await - { - self.handlers_error_handler.handle(error).await; - } - } + self.handle(&self.poll_handler, poll).await; } } }) .await } + + async fn handle(&self, handler: &Option, Result<(), HandlerE>> + 'a>>, update: U) + { + if let Some(handler) = &handler { + if let Err(error) = + handler + .handle(HandlerCtx { + bot: Arc::clone(&self.bot), + update + }) + .await + { + self.handlers_error_handler.handle(error).await; + } + } + } } From b7b6cd988cc3df4ed503fbb1fd933436552c4f75 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 3 Feb 2020 01:32:01 +0600 Subject: [PATCH 17/91] Reduce the Dispatcher::dispatch_with_listener complexity --- src/dispatching/dispatcher.rs | 138 +++++----------------------------- 1 file changed, 17 insertions(+), 121 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index c59fc858..d44928fa 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -195,154 +195,50 @@ where match update.kind { 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 - { - self.handlers_error_handler.handle(error).await; - } - } + self.handle(&self.message_handler, message).await } UpdateKind::EditedMessage(message) => { - 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; - } - } + self.handle(&self.edited_message_handler, message).await } UpdateKind::ChannelPost(post) => { - if let Some(channel_post_handler) = - &self.channel_post_handler - { - if let Err(error) = channel_post_handler - .handle(HandlerCtx { - bot: Arc::clone(&self.bot), - update: post, - }) - .await - { - self.handlers_error_handler.handle(error).await; - } - } + self.handle(&self.channel_post_handler, post).await } UpdateKind::EditedChannelPost(post) => { - if let Some(edited_channel_post_handler) = - &self.edited_channel_post_handler - { - if let Err(error) = edited_channel_post_handler - .handle(HandlerCtx { - bot: Arc::clone(&self.bot), - update: post, - }) - .await - { - self.handlers_error_handler.handle(error).await; - } - } + self.handle(&self.edited_channel_post_handler, post) + .await } UpdateKind::InlineQuery(query) => { - if let Some(inline_query_handler) = - &self.inline_query_handler - { - if let Err(error) = inline_query_handler - .handle(HandlerCtx { - bot: Arc::clone(&self.bot), - update: query, - }) - .await - { - self.handlers_error_handler.handle(error).await; - } - } + self.handle(&self.inline_query_handler, query).await } UpdateKind::ChosenInlineResult(result) => { - if let Some(chosen_inline_result_handler) = - &self.chosen_inline_result_handler - { - if let Err(error) = chosen_inline_result_handler - .handle(HandlerCtx { - bot: Arc::clone(&self.bot), - update: result, - }) - .await - { - self.handlers_error_handler.handle(error).await; - } - } + self.handle(&self.chosen_inline_result_handler, result) + .await } UpdateKind::CallbackQuery(query) => { - if let Some(callback_query_handler) = - &self.callback_query_handler - { - if let Err(error) = callback_query_handler - .handle(HandlerCtx { - bot: Arc::clone(&self.bot), - update: query, - }) - .await - { - self.handlers_error_handler.handle(error).await; - } - } + self.handle(&self.callback_query_handler, query).await } UpdateKind::ShippingQuery(query) => { - if let Some(shipping_query_handler) = - &self.shipping_query_handler - { - if let Err(error) = shipping_query_handler - .handle(HandlerCtx { - bot: Arc::clone(&self.bot), - update: query, - }) - .await - { - self.handlers_error_handler.handle(error).await; - } - } + self.handle(&self.shipping_query_handler, query).await } UpdateKind::PreCheckoutQuery(query) => { - if let Some(pre_checkout_query_handler) = - &self.pre_checkout_query_handler - { - if let Err(error) = pre_checkout_query_handler - .handle(HandlerCtx { - bot: Arc::clone(&self.bot), - update: query, - }) - .await - { - self.handlers_error_handler.handle(error).await; - } - } + self.handle(&self.pre_checkout_query_handler, query) + .await } UpdateKind::Poll(poll) => { - self.handle(&self.poll_handler, poll).await; + self.handle(&self.poll_handler, poll).await } } }) .await } - async fn handle(&self, handler: &Option, Result<(), HandlerE>> + 'a>>, update: U) - { + // Handles a single update. + async fn handle(&self, handler: &H<'a, Upd, HandlerE>, update: Upd) { if let Some(handler) = &handler { - if let Err(error) = - handler + if let Err(error) = handler .handle(HandlerCtx { bot: Arc::clone(&self.bot), - update + update, }) .await { From 32cab96af5333d2a5d337a879c898bcf139fbeff Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 3 Feb 2020 01:42:32 +0600 Subject: [PATCH 18/91] Fix the docs --- src/types/inline_keyboard_markup.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs index cfc58588..9511bc66 100644 --- a/src/types/inline_keyboard_markup.rs +++ b/src/types/inline_keyboard_markup.rs @@ -30,7 +30,7 @@ pub struct InlineKeyboardMarkup { /// "text".to_string(), /// "http://url.com".to_string(), /// ); -/// let keyboard = InlineKeyboardMarkup::new().append_row(vec![url_button]); +/// let keyboard = InlineKeyboardMarkup::default().append_row(vec![url_button]); /// ``` impl InlineKeyboardMarkup { pub fn append_row(mut self, buttons: Vec) -> Self { From a7dee4cab59f7afd5cce7e032cff0a949be1bcf5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 3 Feb 2020 03:03:28 +0600 Subject: [PATCH 19/91] Improve the docs of teloxide::dispatching --- src/dispatching/async_handler.rs | 31 ------- src/dispatching/async_handlers.rs | 145 ++++++++++++++++++++++++++++++ src/dispatching/dispatcher.rs | 10 ++- src/dispatching/error_handlers.rs | 102 --------------------- src/dispatching/handler_ctx.rs | 6 +- src/dispatching/mod.rs | 54 ++++++++++- src/dispatching/session/mod.rs | 36 ++++---- 7 files changed, 225 insertions(+), 159 deletions(-) delete mode 100644 src/dispatching/async_handler.rs create mode 100644 src/dispatching/async_handlers.rs delete mode 100644 src/dispatching/error_handlers.rs diff --git a/src/dispatching/async_handler.rs b/src/dispatching/async_handler.rs deleted file mode 100644 index c29f2d7a..00000000 --- a/src/dispatching/async_handler.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::{future::Future, pin::Pin}; - -/// An asynchronous polymorphic handler of a context. -/// -/// Note that `AsyncHandler` is implemented for asynchronous `Fn`s, that consume -/// `Ctx` and return `Output`. -pub trait AsyncHandler { - #[must_use] - fn handle<'a>( - &'a self, - ctx: Ctx, - ) -> Pin + 'a>> - where - Ctx: 'a; -} - -impl AsyncHandler for F -where - F: Fn(Ctx) -> Fut, - Fut: Future, -{ - fn handle<'a>( - &'a self, - ctx: Ctx, - ) -> Pin + 'a>> - where - Ctx: 'a, - { - Box::pin(async move { self(ctx).await }) - } -} diff --git a/src/dispatching/async_handlers.rs b/src/dispatching/async_handlers.rs new file mode 100644 index 00000000..c3e5c0ea --- /dev/null +++ b/src/dispatching/async_handlers.rs @@ -0,0 +1,145 @@ +use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; + +/// An asynchronous polymorphic handler of a context. +/// +/// Note that `AsyncHandler` is implemented for asynchronous `Fn`s, that consume +/// `Ctx` and return `Output`. +pub trait AsyncHandler { + #[must_use] + fn handle<'a>( + &'a self, + ctx: Ctx, + ) -> Pin + 'a>> + where + Ctx: 'a; +} + +impl AsyncHandler for F +where + F: Fn(Ctx) -> Fut, + Fut: Future, +{ + fn handle<'a>( + &'a self, + ctx: Ctx, + ) -> Pin + 'a>> + where + Ctx: 'a, + { + Box::pin(async move { self(ctx).await }) + } +} + +/// A handler that silently ignores all values. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::{AsyncHandler, IgnoringHandler}; +/// +/// IgnoringHandler.handle(()).await; +/// IgnoringHandler.handle(404).await; +/// IgnoringHandler.handle("error").await; +/// # } +/// ``` +pub struct IgnoringHandler; + +impl AsyncHandler for IgnoringHandler { + fn handle<'a>(&'a self, _: Ctx) -> Pin + 'a>> + where + Ctx: 'a, + { + Box::pin(async {}) + } +} + +/// A handler that silently ignores all values that can never happen (e.g.: +/// [`!`] or [`Infallible`]). +/// +/// ## Examples +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use std::convert::{Infallible, TryInto}; +/// +/// use teloxide::dispatching::{AsyncHandler, IgnoringHandlerSafe}; +/// +/// let result: Result = "str".try_into(); +/// match result { +/// Ok(string) => println!("{}", string), +/// Err(inf) => IgnoringHandlerSafe.handle(inf).await, +/// } +/// +/// IgnoringHandlerSafe.handle(return).await; // return type of `return` is `!` (aka never) +/// # } +/// ``` +/// +/// ```compile_fail +/// use teloxide::dispatching::{AsyncHandler, IgnoringHandlerSafe}; +/// +/// IgnoringHandlerSafe.handle(0); +/// ``` +/// +/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html +/// [`Infallible`]: std::convert::Infallible +pub struct IgnoringHandlerSafe; + +#[allow(unreachable_code)] +impl AsyncHandler for IgnoringHandlerSafe { + fn handle<'a>( + &'a self, + _: Infallible, + ) -> Pin + 'a>> + where + Infallible: 'a, + { + Box::pin(async {}) + } +} + +/// A handler that log all values passed into it. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::{AsyncHandler, LoggingHandler}; +/// +/// LoggingHandler::default().handle(()).await; +/// LoggingHandler::new("error").handle(404).await; +/// LoggingHandler::new("error") +/// .handle("Invalid data type!") +/// .await; +/// # } +/// ``` +#[derive(Default)] +pub struct LoggingHandler { + text: String, +} + +impl LoggingHandler { + /// Creates `LoggingHandler` with a meta text before a log. + /// + /// The logs will be printed in this format: `{text}: {:?}`. + #[must_use] + pub fn new(text: T) -> Self + where + T: Into, + { + Self { text: text.into() } + } +} + +impl AsyncHandler for LoggingHandler +where + Ctx: Debug, +{ + fn handle<'a>(&'a self, ctx: Ctx) -> Pin + 'a>> + where + Ctx: 'a, + { + log::debug!("{text}: {:?}", ctx, text = self.text); + Box::pin(async {}) + } +} diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index d44928fa..2c480f1f 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -1,7 +1,7 @@ use crate::{ dispatching::{ - error_handlers, update_listeners, update_listeners::UpdateListener, - AsyncHandler, HandlerCtx, + update_listeners, update_listeners::UpdateListener, AsyncHandler, + HandlerCtx, LoggingHandler, }, types::{ CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, @@ -42,7 +42,9 @@ where pub fn new(bot: Bot) -> Self { Self { bot: Arc::new(bot), - handlers_error_handler: Box::new(error_handlers::Log), + handlers_error_handler: Box::new(LoggingHandler::new( + "An error from a Dispatcher's handler", + )), message_handler: None, edited_message_handler: None, channel_post_handler: None, @@ -165,7 +167,7 @@ where pub async fn dispatch(&'a self) { self.dispatch_with_listener( update_listeners::polling_default(Arc::clone(&self.bot)), - &error_handlers::Log, + &LoggingHandler::new("An error from the update listener"), ) .await; } diff --git a/src/dispatching/error_handlers.rs b/src/dispatching/error_handlers.rs deleted file mode 100644 index 879f87dd..00000000 --- a/src/dispatching/error_handlers.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! Commonly used handlers of errors. - -use crate::dispatching::AsyncHandler; -use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; - -/// A handler that silently ignores all errors. -/// -/// ## Example -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use teloxide::dispatching::{error_handlers::Ignore, AsyncHandler}; -/// -/// Ignore.handle(()).await; -/// Ignore.handle(404).await; -/// Ignore.handle(String::from("error")).await; -/// # } -/// ``` -pub struct Ignore; - -impl AsyncHandler for Ignore { - fn handle<'a>(&'a self, _: E) -> Pin + 'a>> - where - E: 'a, - { - Box::pin(async {}) - } -} - -/// An error handler that silently ignores all errors that can never happen -/// (e.g.: [`!`] or [`Infallible`]). -/// -/// ## Examples -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use std::convert::{Infallible, TryInto}; -/// -/// use teloxide::dispatching::{AsyncHandler, error_handlers::IgnoreSafe}; -/// -/// let result: Result = "str".try_into(); -/// match result { -/// Ok(string) => println!("{}", string), -/// Err(inf) => IgnoreSafe.handle(inf).await, -/// } -/// -/// IgnoreSafe.handle(return).await; // return type of `return` is `!` (aka never) -/// # } -/// ``` -/// -/// ```compile_fail -/// use teloxide::dispatching::dispatchers::filter::error_policy::{ -/// ErrorPolicy, IgnoreSafe, -/// }; -/// -/// IgnoreSafe.handle_error(0); -/// ``` -/// -/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html -/// [`Infallible`]: std::convert::Infallible -pub struct IgnoreSafe; - -#[allow(unreachable_code)] -impl AsyncHandler for IgnoreSafe { - fn handle<'a>( - &'a self, - _: Infallible, - ) -> Pin + 'a>> - where - Infallible: 'a, - { - Box::pin(async {}) - } -} - -/// An error handler that prints all errors passed into it. -/// -/// ## Example -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use teloxide::dispatching::{error_handlers::Log, AsyncHandler}; -/// -/// Log.handle(()).await; -/// Log.handle(404).await; -/// Log.handle(String::from("error")).await; -/// # } -/// ``` -pub struct Log; - -impl AsyncHandler for Log -where - E: Debug, -{ - fn handle<'a>(&'a self, error: E) -> Pin + 'a>> - where - E: 'a, - { - log::debug!("error: {:?}", error); - Box::pin(async {}) - } -} diff --git a/src/dispatching/handler_ctx.rs b/src/dispatching/handler_ctx.rs index 5b487575..355c131e 100644 --- a/src/dispatching/handler_ctx.rs +++ b/src/dispatching/handler_ctx.rs @@ -6,10 +6,12 @@ use crate::{ }; use std::sync::Arc; -/// A dispatcher's handler's context of a bot and an update. +/// A [`Dispatcher`]'s handler's context of a bot and an update. /// /// See [the module-level documentation for the design -/// overview](teloxide::dispatching). +/// overview](crate::dispatching). +/// +/// [`Dispatcher`]: crate::dispatching::Dispatcher pub struct HandlerCtx { pub bot: Arc, pub update: Upd, diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index d19dde82..3559dcff 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,12 +1,60 @@ //! Update dispatching. //! -mod async_handler; +//! The key type here is [`Dispatcher`]. It encapsulates [`UpdateListener`], a +//! handler of errors, and handlers for [10 update kinds]. When [`Update`] is +//! received from Telegram, it is supplied to an appropriate handler, and if a +//! handler has returned an error, the error is supplied into an error handler. +//! That's simple! +//! +//! All the handlers are of type [`AsyncHandler`]. It's like a [first-class +//! construction] in this module, because: +//! 1. It is implemented for [`SessionDispatcher`], which itself accepts +//! [`AsyncHandler`]. +//! 2. It is implemented for [`LoggingHandler`], [`IgnoringHandler`], and +//! [`IgnoringHandlerSafe`]. +//! 3. It is implemented even [for asynchronous functions]. +//! 4. You can use [`AsyncHandler`]s as error handlers. +//! 5. More... +//! +//! ## Examples +//! The ping-pong bot ([full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/)): +//! +//! ``` +//! # #[tokio::main] +//! # async fn main_() { +//! use teloxide::prelude::*; +//! +//! // Setup logging here... +//! +//! Dispatcher::new(Bot::new("MyAwesomeToken")) +//! .message_handler(|ctx: HandlerCtx| async move { +//! ctx.reply("pong").await +//! }) +//! .dispatch() +//! .await; +//! # } +//! ``` +//! +//! [first-class construction]: https://stackoverflow.com/questions/646794/what-is-a-first-class-programming-construct +//! [`Dispatcher`]: crate::dispatching::Dispatcher +//! [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener +//! [10 update kinds]: crate::types::UpdateKind +//! [`Update`]: crate::types::Update +//! [`AsyncHandler`]: crate::dispatching::AsyncHandler +//! [`LoggingHandler`]: crate::dispatching::LoggingHandler +//! [`IgnoringHandler`]: crate::dispatching::IgnoringHandler +//! [`IgnoringHandlerSafe`]: crate::dispatching::IgnoringHandlerSafe +//! [for asynchronous functions]: crate::dispatching::AsyncHandler +//! [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher + +mod async_handlers; mod dispatcher; -pub mod error_handlers; mod handler_ctx; pub mod session; pub mod update_listeners; -pub use async_handler::AsyncHandler; +pub use async_handlers::{ + AsyncHandler, IgnoringHandler, IgnoringHandlerSafe, LoggingHandler, +}; pub use dispatcher::Dispatcher; pub use handler_ctx::HandlerCtx; diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index a64b7161..7d2f6151 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -1,31 +1,33 @@ -//! Dispatching user sessions. +//! Dealing with dialogues. //! //! There are four main components: //! //! 1. Your session type `Session`, which designates a dialogue state at the //! current moment. -//! 2. [`Storage`] that encapsulates all the sessions. -//! 3. Your handler of type `H: async Fn(Session, Update) -> -//! SessionState` that receives an update and turns your session into -//! the next state. -//! 4. [`SessionDispatcher`], which encapsulates your handler and -//! [`Storage`], and has the [`dispatch(Bot, Upd)`] function. +//! 2. [`Storage`], which encapsulates all the sessions. +//! 3. Your handler, which receives an update and turns your session into the +//! next state. +//! 4. [`SessionDispatcher`], which encapsulates your handler, [`Storage`], and +//! implements [`AsyncHandler`]. //! -//! Every time you call `.dispatch(bot, update)` on your dispatcher, the -//! following steps are executed: +//! You supply [`SessionDispatcher`] into [`Dispatcher`]. Every time +//! [`Dispatcher`] calls `SessionDispatcher::handle(...)`, the following steps +//! are executed: //! //! 1. If a storage doesn't contain a session from this chat, supply -//! `Session::default()` into you handler, otherwise, supply the previous -//! session. -//! 3. If a handler has returned [`SessionState::Terminate`], remove the -//! session from a storage, otherwise force the storage to update the session. +//! `Session::default()` into you handler, otherwise, supply the saved session +//! from this chat. +//! 3. If a handler has returned [`SessionState::Exit`], remove the session +//! from the storage, otherwise ([`SessionState::Next`]) force the storage to +//! update the session. //! //! [`Storage`]: crate::dispatching::session::Storage //! [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher -//! [`dispatch(Bot, Upd)`]: -//! crate::dispatching::session::SessionDispatcher::dispatch -//! [`SessionState::Terminate`]: -//! crate::dispatching::session::SessionState::Terminate +//! [`SessionState::Exit`]: +//! crate::dispatching::session::SessionState::Exit +//! [`SessionState::Next`]: crate::dispatching::session::SessionState::Next +//! [`AsyncHandler`]: crate::dispatching::AsyncHandler +//! [`Dispatcher`]: crate::dispatching::Dispatcher // TODO: examples From 16aca0f0f85ead26bf3c9708f6408c04cd923d5f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 3 Feb 2020 03:06:31 +0600 Subject: [PATCH 20/91] Quick fix of the docs --- src/dispatching/mod.rs | 2 +- src/utils/mod.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 3559dcff..5206cd99 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,4 +1,4 @@ -//! Update dispatching. +//! Updates dispatching. //! //! The key type here is [`Dispatcher`]. It encapsulates [`UpdateListener`], a //! handler of errors, and handlers for [10 update kinds]. When [`Update`] is diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 06eba848..f906abbc 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,4 @@ +//! Some useful utilities. + pub mod html; pub mod markdown; From ee3a95b31ee0eea192856104c2e2279749a03693 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 3 Feb 2020 03:15:55 +0600 Subject: [PATCH 21/91] Yet another fix --- src/dispatching/async_handlers.rs | 4 ++-- src/dispatching/dispatcher.rs | 3 +++ src/dispatching/session/storage/mod.rs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/dispatching/async_handlers.rs b/src/dispatching/async_handlers.rs index c3e5c0ea..8c821bf7 100644 --- a/src/dispatching/async_handlers.rs +++ b/src/dispatching/async_handlers.rs @@ -2,8 +2,8 @@ use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; /// An asynchronous polymorphic handler of a context. /// -/// Note that `AsyncHandler` is implemented for asynchronous `Fn`s, that consume -/// `Ctx` and return `Output`. +/// See [the module-level documentation for the design +/// overview](crate::dispatching). pub trait AsyncHandler { #[must_use] fn handle<'a>( diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 2c480f1f..15c1e506 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -16,6 +16,9 @@ type H<'a, Upd, HandlerE> = Option, Result<(), HandlerE>> + 'a>>; /// One dispatcher to rule them all. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). pub struct Dispatcher<'a, HandlerE> { bot: Arc, diff --git a/src/dispatching/session/storage/mod.rs b/src/dispatching/session/storage/mod.rs index 5bff11f6..521cd934 100644 --- a/src/dispatching/session/storage/mod.rs +++ b/src/dispatching/session/storage/mod.rs @@ -10,7 +10,7 @@ pub use in_mem_storage::InMemStorage; /// /// For a storage based on a simple hash map, see [`InMemStorage`]. /// -/// [`InMemStorage`]: crate::dispatching::private::InMemStorage +/// [`InMemStorage`]: crate::dispatching::session::InMemStorage #[async_trait(?Send)] #[async_trait] pub trait Storage { From 7e34007a4deb378f8bc836e73f78288642fac2c5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 3 Feb 2020 16:25:05 +0600 Subject: [PATCH 22/91] Divide AsyncHandler into different traits --- src/dispatching/async_handlers.rs | 145 ----------------- src/dispatching/ctx_handlers.rs | 31 ++++ src/dispatching/dispatcher.rs | 84 +++++++--- ...ndler_ctx.rs => dispatcher_handler_ctx.rs} | 6 +- src/dispatching/error_handlers.rs | 151 ++++++++++++++++++ src/dispatching/middleware.rs | 25 +++ src/dispatching/mod.rs | 55 ++++--- src/dispatching/session/mod.rs | 8 +- src/dispatching/session/session_dispatcher.rs | 12 +- src/prelude.rs | 2 +- 10 files changed, 306 insertions(+), 213 deletions(-) delete mode 100644 src/dispatching/async_handlers.rs create mode 100644 src/dispatching/ctx_handlers.rs rename src/dispatching/{handler_ctx.rs => dispatcher_handler_ctx.rs} (85%) create mode 100644 src/dispatching/error_handlers.rs create mode 100644 src/dispatching/middleware.rs diff --git a/src/dispatching/async_handlers.rs b/src/dispatching/async_handlers.rs deleted file mode 100644 index 8c821bf7..00000000 --- a/src/dispatching/async_handlers.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; - -/// An asynchronous polymorphic handler of a context. -/// -/// See [the module-level documentation for the design -/// overview](crate::dispatching). -pub trait AsyncHandler { - #[must_use] - fn handle<'a>( - &'a self, - ctx: Ctx, - ) -> Pin + 'a>> - where - Ctx: 'a; -} - -impl AsyncHandler for F -where - F: Fn(Ctx) -> Fut, - Fut: Future, -{ - fn handle<'a>( - &'a self, - ctx: Ctx, - ) -> Pin + 'a>> - where - Ctx: 'a, - { - Box::pin(async move { self(ctx).await }) - } -} - -/// A handler that silently ignores all values. -/// -/// ## Example -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use teloxide::dispatching::{AsyncHandler, IgnoringHandler}; -/// -/// IgnoringHandler.handle(()).await; -/// IgnoringHandler.handle(404).await; -/// IgnoringHandler.handle("error").await; -/// # } -/// ``` -pub struct IgnoringHandler; - -impl AsyncHandler for IgnoringHandler { - fn handle<'a>(&'a self, _: Ctx) -> Pin + 'a>> - where - Ctx: 'a, - { - Box::pin(async {}) - } -} - -/// A handler that silently ignores all values that can never happen (e.g.: -/// [`!`] or [`Infallible`]). -/// -/// ## Examples -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use std::convert::{Infallible, TryInto}; -/// -/// use teloxide::dispatching::{AsyncHandler, IgnoringHandlerSafe}; -/// -/// let result: Result = "str".try_into(); -/// match result { -/// Ok(string) => println!("{}", string), -/// Err(inf) => IgnoringHandlerSafe.handle(inf).await, -/// } -/// -/// IgnoringHandlerSafe.handle(return).await; // return type of `return` is `!` (aka never) -/// # } -/// ``` -/// -/// ```compile_fail -/// use teloxide::dispatching::{AsyncHandler, IgnoringHandlerSafe}; -/// -/// IgnoringHandlerSafe.handle(0); -/// ``` -/// -/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html -/// [`Infallible`]: std::convert::Infallible -pub struct IgnoringHandlerSafe; - -#[allow(unreachable_code)] -impl AsyncHandler for IgnoringHandlerSafe { - fn handle<'a>( - &'a self, - _: Infallible, - ) -> Pin + 'a>> - where - Infallible: 'a, - { - Box::pin(async {}) - } -} - -/// A handler that log all values passed into it. -/// -/// ## Example -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use teloxide::dispatching::{AsyncHandler, LoggingHandler}; -/// -/// LoggingHandler::default().handle(()).await; -/// LoggingHandler::new("error").handle(404).await; -/// LoggingHandler::new("error") -/// .handle("Invalid data type!") -/// .await; -/// # } -/// ``` -#[derive(Default)] -pub struct LoggingHandler { - text: String, -} - -impl LoggingHandler { - /// Creates `LoggingHandler` with a meta text before a log. - /// - /// The logs will be printed in this format: `{text}: {:?}`. - #[must_use] - pub fn new(text: T) -> Self - where - T: Into, - { - Self { text: text.into() } - } -} - -impl AsyncHandler for LoggingHandler -where - Ctx: Debug, -{ - fn handle<'a>(&'a self, ctx: Ctx) -> Pin + 'a>> - where - Ctx: 'a, - { - log::debug!("{text}: {:?}", ctx, text = self.text); - Box::pin(async {}) - } -} diff --git a/src/dispatching/ctx_handlers.rs b/src/dispatching/ctx_handlers.rs new file mode 100644 index 00000000..ba6e22bc --- /dev/null +++ b/src/dispatching/ctx_handlers.rs @@ -0,0 +1,31 @@ +use std::{future::Future, pin::Pin}; + +/// An asynchronous handler of a context. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). +pub trait CtxHandler { + #[must_use] + fn handle_ctx<'a>( + &'a self, + ctx: Ctx, + ) -> Pin + 'a>> + where + Ctx: 'a; +} + +impl CtxHandler for F +where + F: Fn(Ctx) -> Fut, + Fut: Future, +{ + fn handle_ctx<'a>( + &'a self, + ctx: Ctx, + ) -> Pin + 'a>> + where + Ctx: 'a, + { + Box::pin(async move { self(ctx).await }) + } +} diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 15c1e506..a0c79ae2 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -1,19 +1,21 @@ use crate::{ dispatching::{ - update_listeners, update_listeners::UpdateListener, AsyncHandler, - HandlerCtx, LoggingHandler, + error_handlers::ErrorHandler, update_listeners, + update_listeners::UpdateListener, CtxHandler, DispatcherHandlerCtx, + LoggingErrorHandler, Middleware, }, types::{ CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, - PreCheckoutQuery, ShippingQuery, UpdateKind, + PreCheckoutQuery, ShippingQuery, Update, UpdateKind, }, Bot, }; -use futures::StreamExt; +use futures::{stream, StreamExt}; use std::{fmt::Debug, sync::Arc}; -type H<'a, Upd, HandlerE> = - Option, Result<(), HandlerE>> + 'a>>; +type H<'a, Upd, HandlerE> = Option< + Box, Result<(), HandlerE>> + 'a>, +>; /// One dispatcher to rule them all. /// @@ -22,7 +24,9 @@ type H<'a, Upd, HandlerE> = pub struct Dispatcher<'a, HandlerE> { bot: Arc, - handlers_error_handler: Box + 'a>, + middlewares: Vec + 'a>>, + + handlers_error_handler: Box + 'a>, message_handler: H<'a, Message, HandlerE>, edited_message_handler: H<'a, Message, HandlerE>, @@ -45,7 +49,8 @@ where pub fn new(bot: Bot) -> Self { Self { bot: Arc::new(bot), - handlers_error_handler: Box::new(LoggingHandler::new( + middlewares: Vec::new(), + handlers_error_handler: Box::new(LoggingErrorHandler::new( "An error from a Dispatcher's handler", )), message_handler: None, @@ -61,11 +66,21 @@ where } } + /// Appends a middleware. + #[must_use] + pub fn middleware(mut self, val: M) -> Self + where + M: Middleware + 'a, + { + self.middlewares.push(Box::new(val)); + self + } + /// Registers a handler of errors, produced by other handlers. #[must_use] pub fn handlers_error_handler(mut self, val: T) -> Self where - T: AsyncHandler + 'a, + T: ErrorHandler + 'a, { self.handlers_error_handler = Box::new(val); self @@ -74,7 +89,7 @@ where #[must_use] pub fn message_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, Result<(), HandlerE>> + 'a, { self.message_handler = Some(Box::new(h)); self @@ -83,7 +98,7 @@ where #[must_use] pub fn edited_message_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, Result<(), HandlerE>> + 'a, { self.edited_message_handler = Some(Box::new(h)); self @@ -92,7 +107,7 @@ where #[must_use] pub fn channel_post_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, Result<(), HandlerE>> + 'a, { self.channel_post_handler = Some(Box::new(h)); self @@ -101,7 +116,7 @@ where #[must_use] pub fn edited_channel_post_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, Result<(), HandlerE>> + 'a, { self.edited_channel_post_handler = Some(Box::new(h)); self @@ -110,7 +125,8 @@ where #[must_use] pub fn inline_query_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, Result<(), HandlerE>> + + 'a, { self.inline_query_handler = Some(Box::new(h)); self @@ -119,8 +135,10 @@ where #[must_use] pub fn chosen_inline_result_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> - + 'a, + H: CtxHandler< + DispatcherHandlerCtx, + Result<(), HandlerE>, + > + 'a, { self.chosen_inline_result_handler = Some(Box::new(h)); self @@ -129,7 +147,10 @@ where #[must_use] pub fn callback_query_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler< + DispatcherHandlerCtx, + Result<(), HandlerE>, + > + 'a, { self.callback_query_handler = Some(Box::new(h)); self @@ -138,7 +159,10 @@ where #[must_use] pub fn shipping_query_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler< + DispatcherHandlerCtx, + Result<(), HandlerE>, + > + 'a, { self.shipping_query_handler = Some(Box::new(h)); self @@ -147,8 +171,10 @@ where #[must_use] pub fn pre_checkout_query_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> - + 'a, + H: CtxHandler< + DispatcherHandlerCtx, + Result<(), HandlerE>, + > + 'a, { self.pre_checkout_query_handler = Some(Box::new(h)); self @@ -157,7 +183,7 @@ where #[must_use] pub fn poll_handler(mut self, h: H) -> Self where - H: AsyncHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, Result<(), HandlerE>> + 'a, { self.poll_handler = Some(Box::new(h)); self @@ -170,7 +196,7 @@ where pub async fn dispatch(&'a self) { self.dispatch_with_listener( update_listeners::polling_default(Arc::clone(&self.bot)), - &LoggingHandler::new("An error from the update listener"), + &LoggingErrorHandler::new("An error from the update listener"), ) .await; } @@ -183,7 +209,7 @@ where update_listener_error_handler: &'a Eh, ) where UListener: UpdateListener + 'a, - Eh: AsyncHandler + 'a, + Eh: ErrorHandler + 'a, ListenerE: Debug, { let update_listener = Box::pin(update_listener); @@ -193,11 +219,17 @@ where let update = match update { Ok(update) => update, Err(error) => { - update_listener_error_handler.handle(error).await; + update_listener_error_handler.handle_error(error).await; return; } }; + let update = stream::iter(&self.middlewares) + .fold(update, |acc, middleware| async move { + middleware.handle(acc).await + }) + .await; + match update.kind { UpdateKind::Message(message) => { self.handle(&self.message_handler, message).await @@ -241,13 +273,13 @@ where async fn handle(&self, handler: &H<'a, Upd, HandlerE>, update: Upd) { if let Some(handler) = &handler { if let Err(error) = handler - .handle(HandlerCtx { + .handle_ctx(DispatcherHandlerCtx { bot: Arc::clone(&self.bot), update, }) .await { - self.handlers_error_handler.handle(error).await; + self.handlers_error_handler.handle_error(error).await; } } } diff --git a/src/dispatching/handler_ctx.rs b/src/dispatching/dispatcher_handler_ctx.rs similarity index 85% rename from src/dispatching/handler_ctx.rs rename to src/dispatching/dispatcher_handler_ctx.rs index 355c131e..c48e53fc 100644 --- a/src/dispatching/handler_ctx.rs +++ b/src/dispatching/dispatcher_handler_ctx.rs @@ -12,12 +12,12 @@ use std::sync::Arc; /// overview](crate::dispatching). /// /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub struct HandlerCtx { +pub struct DispatcherHandlerCtx { pub bot: Arc, pub update: Upd, } -impl GetChatId for HandlerCtx +impl GetChatId for DispatcherHandlerCtx where Upd: GetChatId, { @@ -26,7 +26,7 @@ where } } -impl HandlerCtx { +impl DispatcherHandlerCtx { pub async fn reply(&self, text: T) -> ResponseResult<()> where T: Into, diff --git a/src/dispatching/error_handlers.rs b/src/dispatching/error_handlers.rs new file mode 100644 index 00000000..d7289a14 --- /dev/null +++ b/src/dispatching/error_handlers.rs @@ -0,0 +1,151 @@ +use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; + +/// An asynchronous handler of an error. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). +pub trait ErrorHandler { + #[must_use] + fn handle_error<'a>( + &'a self, + error: E, + ) -> Pin + 'a>> + where + E: 'a; +} + +impl ErrorHandler for F +where + F: Fn(E) -> Fut, + Fut: Future, +{ + fn handle_error<'a>( + &'a self, + error: E, + ) -> Pin + 'a>> + where + E: 'a, + { + Box::pin(async move { self(error).await }) + } +} + +/// A handler that silently ignores all errors. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::{ErrorHandler, IgnoringErrorHandler}; +/// +/// IgnoringErrorHandler.handle_error(()).await; +/// IgnoringErrorHandler.handle_error(404).await; +/// IgnoringErrorHandler.handle_error("error").await; +/// # } +/// ``` +pub struct IgnoringErrorHandler; + +impl ErrorHandler for IgnoringErrorHandler { + fn handle_error<'a>( + &'a self, + _: E, + ) -> Pin + 'a>> + where + E: 'a, + { + Box::pin(async {}) + } +} + +/// A handler that silently ignores all errors that can never happen (e.g.: +/// [`!`] or [`Infallible`]). +/// +/// ## Examples +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use std::convert::{Infallible, TryInto}; +/// +/// use teloxide::dispatching::{ErrorHandler, IgnoringErrorHandlerSafe}; +/// +/// let result: Result = "str".try_into(); +/// match result { +/// Ok(string) => println!("{}", string), +/// Err(inf) => IgnoringErrorHandlerSafe.handle_error(inf).await, +/// } +/// +/// IgnoringErrorHandlerSafe.handle_error(return).await; // return type of `return` is `!` (aka never) +/// # } +/// ``` +/// +/// ```compile_fail +/// use teloxide::dispatching::{ErrorHandler, IgnoringErrorHandlerSafe}; +/// +/// IgnoringErrorHandlerSafe.handle_error(0); +/// ``` +/// +/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html +/// [`Infallible`]: std::convert::Infallible +pub struct IgnoringErrorHandlerSafe; + +#[allow(unreachable_code)] +impl ErrorHandler for IgnoringErrorHandlerSafe { + fn handle_error<'a>( + &'a self, + _: Infallible, + ) -> Pin + 'a>> + where + Infallible: 'a, + { + Box::pin(async {}) + } +} + +/// A handler that log all errors passed into it. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::{ErrorHandler, LoggingErrorHandler}; +/// +/// LoggingErrorHandler::default().handle_error(()).await; +/// LoggingErrorHandler::new("error").handle_error(404).await; +/// LoggingErrorHandler::new("error") +/// .handle_error("Invalid data type!") +/// .await; +/// # } +/// ``` +#[derive(Default)] +pub struct LoggingErrorHandler { + text: String, +} + +impl LoggingErrorHandler { + /// Creates `LoggingErrorHandler` with a meta text before a log. + /// + /// The logs will be printed in this format: `{text}: {:?}`. + #[must_use] + pub fn new(text: T) -> Self + where + T: Into, + { + Self { text: text.into() } + } +} + +impl ErrorHandler for LoggingErrorHandler +where + E: Debug, +{ + fn handle_error<'a>( + &'a self, + error: E, + ) -> Pin + 'a>> + where + E: 'a, + { + log::debug!("{text}: {:?}", error, text = self.text); + Box::pin(async {}) + } +} diff --git a/src/dispatching/middleware.rs b/src/dispatching/middleware.rs new file mode 100644 index 00000000..6efdc1db --- /dev/null +++ b/src/dispatching/middleware.rs @@ -0,0 +1,25 @@ +use std::{future::Future, pin::Pin}; + +/// An asynchronous middleware. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). +pub trait Middleware { + #[must_use] + fn handle<'a>(&'a self, val: T) -> Pin + 'a>> + where + T: 'a; +} + +impl Middleware for F +where + F: Fn(T) -> Fut, + Fut: Future, +{ + fn handle<'a>(&'a self, val: T) -> Pin + 'a>> + where + T: 'a, + { + Box::pin(async move { self(val).await }) + } +} diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 5206cd99..2f2f5e9f 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,20 +1,19 @@ //! Updates dispatching. //! -//! The key type here is [`Dispatcher`]. It encapsulates [`UpdateListener`], a -//! handler of errors, and handlers for [10 update kinds]. When [`Update`] is -//! received from Telegram, it is supplied to an appropriate handler, and if a -//! handler has returned an error, the error is supplied into an error handler. +//! The key type here is [`Dispatcher`]. It encapsulates middlewares, handlers +//! for [10 update kinds], and [`ErrorHandler`] for them. When [`Update`] is +//! received from Telegram, the following steps are executed: +//! +//! 1. It is supplied into all registered middlewares. +//! 2. It is supplied to an appropriate handler. +//! 3. If a handler has returned an error, the error is supplied into an error +//! handler. +//! //! That's simple! //! -//! All the handlers are of type [`AsyncHandler`]. It's like a [first-class -//! construction] in this module, because: -//! 1. It is implemented for [`SessionDispatcher`], which itself accepts -//! [`AsyncHandler`]. -//! 2. It is implemented for [`LoggingHandler`], [`IgnoringHandler`], and -//! [`IgnoringHandlerSafe`]. -//! 3. It is implemented even [for asynchronous functions]. -//! 4. You can use [`AsyncHandler`]s as error handlers. -//! 5. More... +//! Note that handlers implement [`CtxHandler`], which means that you are able +//! to supply [`SessionDispatcher`] as a handler, since it implements +//! [`CtxHandler`] too! //! //! ## Examples //! The ping-pong bot ([full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/)): @@ -27,7 +26,7 @@ //! // Setup logging here... //! //! Dispatcher::new(Bot::new("MyAwesomeToken")) -//! .message_handler(|ctx: HandlerCtx| async move { +//! .message_handler(|ctx: DispatcherHandlerCtx| async move { //! ctx.reply("pong").await //! }) //! .dispatch() @@ -35,26 +34,26 @@ //! # } //! ``` //! -//! [first-class construction]: https://stackoverflow.com/questions/646794/what-is-a-first-class-programming-construct //! [`Dispatcher`]: crate::dispatching::Dispatcher -//! [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener //! [10 update kinds]: crate::types::UpdateKind //! [`Update`]: crate::types::Update -//! [`AsyncHandler`]: crate::dispatching::AsyncHandler -//! [`LoggingHandler`]: crate::dispatching::LoggingHandler -//! [`IgnoringHandler`]: crate::dispatching::IgnoringHandler -//! [`IgnoringHandlerSafe`]: crate::dispatching::IgnoringHandlerSafe -//! [for asynchronous functions]: crate::dispatching::AsyncHandler -//! [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher +//! [`ErrorHandler`]: crate::dispatching::ErrorHandler +//! [`CtxHandler`]: crate::dispatching::CtxHandler +//! [`SessionDispatcher`]: crate::dispatching::SessionDispatcher -mod async_handlers; +mod ctx_handlers; mod dispatcher; -mod handler_ctx; +mod dispatcher_handler_ctx; +mod error_handlers; +mod middleware; pub mod session; pub mod update_listeners; -pub use async_handlers::{ - AsyncHandler, IgnoringHandler, IgnoringHandlerSafe, LoggingHandler, -}; +pub use ctx_handlers::CtxHandler; pub use dispatcher::Dispatcher; -pub use handler_ctx::HandlerCtx; +pub use dispatcher_handler_ctx::DispatcherHandlerCtx; +pub use error_handlers::{ + ErrorHandler, IgnoringErrorHandler, IgnoringErrorHandlerSafe, + LoggingErrorHandler, +}; +pub use middleware::Middleware; diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index 7d2f6151..b693fba9 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -8,11 +8,11 @@ //! 3. Your handler, which receives an update and turns your session into the //! next state. //! 4. [`SessionDispatcher`], which encapsulates your handler, [`Storage`], and -//! implements [`AsyncHandler`]. +//! implements [`CtxHandler`]. //! //! You supply [`SessionDispatcher`] into [`Dispatcher`]. Every time -//! [`Dispatcher`] calls `SessionDispatcher::handle(...)`, the following steps -//! are executed: +//! [`Dispatcher`] calls `SessionDispatcher::handle_ctx(...)`, the following +//! steps are executed: //! //! 1. If a storage doesn't contain a session from this chat, supply //! `Session::default()` into you handler, otherwise, supply the saved session @@ -26,7 +26,7 @@ //! [`SessionState::Exit`]: //! crate::dispatching::session::SessionState::Exit //! [`SessionState::Next`]: crate::dispatching::session::SessionState::Next -//! [`AsyncHandler`]: crate::dispatching::AsyncHandler +//! [`CtxHandler`]: crate::dispatching::CtxHandler //! [`Dispatcher`]: crate::dispatching::Dispatcher // TODO: examples diff --git a/src/dispatching/session/session_dispatcher.rs b/src/dispatching/session/session_dispatcher.rs index 6a64b720..a30d7839 100644 --- a/src/dispatching/session/session_dispatcher.rs +++ b/src/dispatching/session/session_dispatcher.rs @@ -2,7 +2,7 @@ use crate::dispatching::{ session::{ GetChatId, InMemStorage, SessionHandlerCtx, SessionState, Storage, }, - AsyncHandler, HandlerCtx, + CtxHandler, DispatcherHandlerCtx, }; use std::{future::Future, pin::Pin}; @@ -46,17 +46,17 @@ where } } -impl<'a, Session, H, Upd> AsyncHandler, Result<(), ()>> +impl<'a, Session, H, Upd> CtxHandler, Result<(), ()>> for SessionDispatcher<'a, Session, H> where - H: AsyncHandler, SessionState>, + H: CtxHandler, SessionState>, Upd: GetChatId, Session: Default, { /// Dispatches a single `message` from a private chat. - fn handle<'b>( + fn handle_ctx<'b>( &'b self, - ctx: HandlerCtx, + ctx: DispatcherHandlerCtx, ) -> Pin> + 'b>> where Upd: 'b, @@ -72,7 +72,7 @@ where if let SessionState::Next(new_session) = self .handler - .handle(SessionHandlerCtx { + .handle_ctx(SessionHandlerCtx { bot: ctx.bot, update: ctx.update, session, diff --git a/src/prelude.rs b/src/prelude.rs index e8874e39..f753e3bd 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,7 +5,7 @@ pub use crate::{ session::{ GetChatId, SessionDispatcher, SessionHandlerCtx, SessionState, }, - Dispatcher, HandlerCtx, + Dispatcher, DispatcherHandlerCtx, }, requests::{Request, ResponseResult}, types::Message, From ca360aa1f4925dd27ce4e98134030b0276a1921d Mon Sep 17 00:00:00 2001 From: p0lunin Date: Mon, 3 Feb 2020 15:26:09 +0200 Subject: [PATCH 23/91] added functions to DispatcherHandlerCtx --- src/dispatching/dispatcher_handler_ctx.rs | 121 +++++++++++++++++++++- 1 file changed, 119 insertions(+), 2 deletions(-) diff --git a/src/dispatching/dispatcher_handler_ctx.rs b/src/dispatching/dispatcher_handler_ctx.rs index c48e53fc..f697ce3d 100644 --- a/src/dispatching/dispatcher_handler_ctx.rs +++ b/src/dispatching/dispatcher_handler_ctx.rs @@ -5,6 +5,7 @@ use crate::{ Bot, }; use std::sync::Arc; +use crate::types::{ChatId, InputFile, InputMedia}; /// A [`Dispatcher`]'s handler's context of a bot and an update. /// @@ -27,7 +28,7 @@ where } impl DispatcherHandlerCtx { - pub async fn reply(&self, text: T) -> ResponseResult<()> + pub async fn answer(&self, text: T) -> ResponseResult where T: Into, { @@ -35,6 +36,122 @@ impl DispatcherHandlerCtx { .send_message(self.chat_id(), text) .send() .await - .map(|_| ()) + } + + pub async fn reply_to(&self, text: T) -> ResponseResult + where + T: Into + { + self.bot + .send_message(self.chat_id(), text) + .reply_to_message_id(self.update.id) + .send() + .await + } + + pub async fn answer_photo(&self, photo: InputFile) -> ResponseResult + { + self.bot + .send_photo(self.update.chat.id, photo) + .send() + .await + } + + pub async fn answer_audio(&self, audio: InputFile) -> ResponseResult + { + self.bot + .send_audio(self.update.chat.id, audio) + .send() + .await + } + + pub async fn answer_animation(&self, animation: InputFile) -> ResponseResult + { + self.bot + .send_animation(self.update.chat.id, animation) + .send() + .await + } + + pub async fn answer_document(&self, document: InputFile) -> ResponseResult + { + self.bot + .send_document(self.update.chat.id, document) + .send() + .await + } + + pub async fn answer_video(&self, video: InputFile) -> ResponseResult + { + self.bot + .send_video(self.update.chat.id, video) + .send() + .await + } + + pub async fn answer_voice(&self, voice: InputFile) -> ResponseResult + { + self.bot + .send_voice(self.update.chat.id, voice) + .send() + .await + } + + pub async fn answer_media_group(&self, media_group: T) -> ResponseResult> + where + T: Into> + { + self.bot + .send_media_group(self.update.chat.id, T) + .send() + .await + } + + pub async fn answer_location(&self, latitude: f32, longitude: f32) -> ResponseResult + { + self.bot + .send_location(self.update.chat.id, latitude, longitude) + .send() + .await + } + + pub async fn answer_venue(&self, latitude: f32, longitude: f32, title: T, address: U) -> ResponseResult + where + T: Into, + U: Into + { + self.bot + .send_venue(self.update.chat.id, latitude, longitude, title, address) + .send() + .await + } + + pub async fn answer_video_note(&self, video_note: InputFile) -> ResponseResult + { + self.bot + .send_video_note(self.update.chat.id, video_note) + .send() + .await + } + + pub async fn answer_contact(&self, phone_number: T, first_name: U) -> ResponseResult + where + T: Into, + U: Into + { + self.bot + .send_contact(self.chat_id(), phone_number, first_name) + .send() + .await + } + + pub async fn forward_to(&self, chat_id: T) -> ResponseResult + where + T: Into + { + self.bot + .forward_message(chat_id, self.update.chat.id, self.update.id) + .send() + .await } } From 4002d8fbbc098817e76750f010a8e6f2f76acce5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 3 Feb 2020 20:53:12 +0600 Subject: [PATCH 24/91] Fix simple_fsm --- examples/simple_fsm/src/main.rs | 59 +++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/examples/simple_fsm/src/main.rs b/examples/simple_fsm/src/main.rs index 3b7853c9..a27f6e4a 100644 --- a/examples/simple_fsm/src/main.rs +++ b/examples/simple_fsm/src/main.rs @@ -53,12 +53,39 @@ impl Display for User { } } +// ============================================================================ +// [FSM - Finite-State Machine] +// ============================================================================ + +enum Fsm { + Start, + FullName, + Age, + FavouriteMusic, +} + +impl Default for Fsm { + fn default() -> Self { + Self::Start + } +} + +// ============================================================================ +// [Our Session type] +// ============================================================================ + +#[derive(Default)] +struct Session { + user: User, + fsm: Fsm, +} + // ============================================================================ // [Control our FSM] // ============================================================================ -type Ctx = SessionHandlerCtx; -type Res = Result, RequestError>; +type Ctx = SessionHandlerCtx; +type Res = Result, RequestError>; async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { ctx.bot @@ -72,12 +99,14 @@ async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { async fn start(ctx: Ctx) -> Res { ctx.reply("Let's start! First, what's your full name?") .await?; + ctx.session.state = Fsm::FullName; Ok(SessionState::Next(ctx.session)) } async fn full_name(mut ctx: Ctx) -> Res { ctx.reply("What a wonderful name! Your age?").await?; - ctx.session.full_name = Some(ctx.update.text().unwrap().to_owned()); + ctx.session.user.full_name = Some(ctx.update.text().unwrap().to_owned()); + ctx.session.fsm = Fsm::Age; Ok(SessionState::Next(ctx.session)) } @@ -85,7 +114,8 @@ async fn age(mut ctx: Ctx) -> Res { match ctx.update.text().unwrap().parse() { Ok(ok) => { send_favourite_music_types(&ctx).await?; - ctx.session.age = Some(ok); + ctx.session.user.age = Some(ok); + ctx.session.fsm = Fsm::FavouriteMusic; } Err(_) => ctx.reply("Oh, please, enter a number!").await?, } @@ -96,8 +126,8 @@ async fn age(mut ctx: Ctx) -> Res { async fn favourite_music(mut ctx: Ctx) -> Res { match ctx.update.text().unwrap().parse() { Ok(ok) => { - ctx.session.favourite_music = Some(ok); - ctx.reply(format!("Fine. {}", ctx.session)).await?; + ctx.session.user.favourite_music = Some(ok); + ctx.reply(format!("Fine. {}", ctx.session.user)).await?; Ok(SessionState::Exit) } Err(_) => { @@ -108,19 +138,12 @@ async fn favourite_music(mut ctx: Ctx) -> Res { } async fn handle_message(ctx: Ctx) -> Res { - if ctx.session.full_name.is_none() { - return full_name(ctx).await; + match ctx.session.fsm { + Fsm::Start => start(ctx).await, + Fsm::FullName => full_name(ctx).await, + Fsm::Age => age(ctx).await, + Fsm::FavouriteMusic => favourite_music(ctx).await, } - - if ctx.session.age.is_none() { - return age(ctx).await; - } - - if ctx.session.favourite_music.is_none() { - return favourite_music(ctx).await; - } - - Ok(SessionState::Exit) } // ============================================================================ From 17de4840d700cb315e2494e25f352558e9f706c3 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 4 Feb 2020 21:38:25 +0600 Subject: [PATCH 25/91] Refactor --- .gitignore | 2 +- .../Cargo.toml | 2 +- .../src/main.rs | 64 ++++++++----------- src/dispatching/dialogue/dialogue.rs | 34 ++++++++++ .../dialogue_dispatcher.rs} | 48 +++++++------- .../dialogue/dialogue_handler_ctx.rs | 38 +++++++++++ src/dispatching/dialogue/dialogue_state.rs | 8 +++ .../{session => dialogue}/get_chat_id.rs | 0 src/dispatching/{session => dialogue}/mod.rs | 17 +++-- .../dialogue/storage/in_mem_storage.rs | 37 +++++++++++ src/dispatching/dialogue/storage/mod.rs | 34 ++++++++++ src/dispatching/dispatcher_handler_ctx.rs | 2 +- src/dispatching/mod.rs | 2 +- .../session/session_handler_ctx.rs | 38 ----------- src/dispatching/session/session_state.rs | 6 -- .../session/storage/in_mem_storage.rs | 33 ---------- src/dispatching/session/storage/mod.rs | 32 ---------- src/prelude.rs | 4 +- 18 files changed, 220 insertions(+), 181 deletions(-) rename examples/{simple_fsm => simple_dialogue}/Cargo.toml (93%) rename examples/{simple_fsm => simple_dialogue}/src/main.rs (69%) create mode 100644 src/dispatching/dialogue/dialogue.rs rename src/dispatching/{session/session_dispatcher.rs => dialogue/dialogue_dispatcher.rs} (56%) create mode 100644 src/dispatching/dialogue/dialogue_handler_ctx.rs create mode 100644 src/dispatching/dialogue/dialogue_state.rs rename src/dispatching/{session => dialogue}/get_chat_id.rs (100%) rename src/dispatching/{session => dialogue}/mod.rs (82%) create mode 100644 src/dispatching/dialogue/storage/in_mem_storage.rs create mode 100644 src/dispatching/dialogue/storage/mod.rs delete mode 100644 src/dispatching/session/session_handler_ctx.rs delete mode 100644 src/dispatching/session/session_state.rs delete mode 100644 src/dispatching/session/storage/in_mem_storage.rs delete mode 100644 src/dispatching/session/storage/mod.rs diff --git a/.gitignore b/.gitignore index f1ae605b..dc7a5786 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ Cargo.lock .vscode/ examples/target examples/ping_pong_bot/target -examples/simple_fsm/target \ No newline at end of file +examples/simple_dialogue/target \ No newline at end of file diff --git a/examples/simple_fsm/Cargo.toml b/examples/simple_dialogue/Cargo.toml similarity index 93% rename from examples/simple_fsm/Cargo.toml rename to examples/simple_dialogue/Cargo.toml index 925430c6..42d2efd7 100644 --- a/examples/simple_fsm/Cargo.toml +++ b/examples/simple_dialogue/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "simple_fsm" +name = "simple_dialogue" version = "0.1.0" authors = ["Temirkhan Myrzamadi "] edition = "2018" diff --git a/examples/simple_fsm/src/main.rs b/examples/simple_dialogue/src/main.rs similarity index 69% rename from examples/simple_fsm/src/main.rs rename to examples/simple_dialogue/src/main.rs index a27f6e4a..a99dfac0 100644 --- a/examples/simple_fsm/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -54,38 +54,28 @@ impl Display for User { } // ============================================================================ -// [FSM - Finite-State Machine] +// [States of a dialogue] // ============================================================================ -enum Fsm { +enum State { Start, FullName, Age, FavouriteMusic, } -impl Default for Fsm { +impl Default for State { fn default() -> Self { Self::Start } } // ============================================================================ -// [Our Session type] +// [Control a dialogue] // ============================================================================ -#[derive(Default)] -struct Session { - user: User, - fsm: Fsm, -} - -// ============================================================================ -// [Control our FSM] -// ============================================================================ - -type Ctx = SessionHandlerCtx; -type Res = Result, RequestError>; +type Ctx = DialogueHandlerCtx; +type Res = Result, RequestError>; async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { ctx.bot @@ -96,53 +86,53 @@ async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { Ok(()) } -async fn start(ctx: Ctx) -> Res { +async fn start(mut ctx: Ctx) -> Res { ctx.reply("Let's start! First, what's your full name?") .await?; - ctx.session.state = Fsm::FullName; - Ok(SessionState::Next(ctx.session)) + ctx.dialogue.state = State::FullName; + Ok(DialogueStage::Next(ctx.dialogue)) } async fn full_name(mut ctx: Ctx) -> Res { ctx.reply("What a wonderful name! Your age?").await?; - ctx.session.user.full_name = Some(ctx.update.text().unwrap().to_owned()); - ctx.session.fsm = Fsm::Age; - Ok(SessionState::Next(ctx.session)) + ctx.dialogue.data.full_name = Some(ctx.update.text().unwrap().to_owned()); + ctx.dialogue.state = State::Age; + Ok(DialogueStage::Next(ctx.dialogue)) } async fn age(mut ctx: Ctx) -> Res { match ctx.update.text().unwrap().parse() { Ok(ok) => { send_favourite_music_types(&ctx).await?; - ctx.session.user.age = Some(ok); - ctx.session.fsm = Fsm::FavouriteMusic; + ctx.dialogue.data.age = Some(ok); + ctx.dialogue.state = State::FavouriteMusic; } Err(_) => ctx.reply("Oh, please, enter a number!").await?, } - Ok(SessionState::Next(ctx.session)) + Ok(DialogueStage::Next(ctx.dialogue)) } async fn favourite_music(mut ctx: Ctx) -> Res { match ctx.update.text().unwrap().parse() { Ok(ok) => { - ctx.session.user.favourite_music = Some(ok); - ctx.reply(format!("Fine. {}", ctx.session.user)).await?; - Ok(SessionState::Exit) + ctx.dialogue.data.favourite_music = Some(ok); + ctx.reply(format!("Fine. {}", ctx.dialogue.data)).await?; + Ok(DialogueStage::Exit) } Err(_) => { ctx.reply("Oh, please, enter from the keyboard!").await?; - Ok(SessionState::Next(ctx.session)) + Ok(DialogueStage::Next(ctx.dialogue)) } } } async fn handle_message(ctx: Ctx) -> Res { - match ctx.session.fsm { - Fsm::Start => start(ctx).await, - Fsm::FullName => full_name(ctx).await, - Fsm::Age => age(ctx).await, - Fsm::FavouriteMusic => favourite_music(ctx).await, + match ctx.dialogue.state { + State::Start => start(ctx).await, + State::FullName => full_name(ctx).await, + State::Age => age(ctx).await, + State::FavouriteMusic => favourite_music(ctx).await, } } @@ -152,12 +142,12 @@ async fn handle_message(ctx: Ctx) -> Res { #[tokio::main] async fn main() { - std::env::set_var("RUST_LOG", "simple_fsm=trace"); + std::env::set_var("RUST_LOG", "simple_dialogue=trace"); pretty_env_logger::init(); - log::info!("Starting the simple_fsm bot!"); + log::info!("Starting the simple_dialogue bot!"); Dispatcher::new(Bot::new("YourAwesomeToken")) - .message_handler(SessionDispatcher::new(|ctx| async move { + .message_handler(DialogueDispatcher::new(|ctx| async move { handle_message(ctx) .await .expect("Something wrong with the bot!") diff --git a/src/dispatching/dialogue/dialogue.rs b/src/dispatching/dialogue/dialogue.rs new file mode 100644 index 00000000..9245aca3 --- /dev/null +++ b/src/dispatching/dialogue/dialogue.rs @@ -0,0 +1,34 @@ +/// A type, encapsulating a dialogue state and arbitrary data. +/// +/// ## Example +/// ``` +/// use teloxide::dispatching::dialogue::Dialogue; +/// +/// enum MyState { +/// FullName, +/// Age, +/// FavouriteMusic, +/// } +/// +/// #[derive(Default)] +/// struct User { +/// full_name: Option, +/// age: Option, +/// favourite_music: Option, +/// } +/// +/// let _dialogue = Dialogue::new(MyState::FullName, User::default()); +/// ``` +#[derive(Default, Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub struct Dialogue { + pub state: State, + pub data: T, +} + +impl Dialogue { + /// Creates new `Dialogue` with the provided fields. + #[must_use] + pub fn new(state: State, data: T) -> Self { + Self { state, data } + } +} diff --git a/src/dispatching/session/session_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs similarity index 56% rename from src/dispatching/session/session_dispatcher.rs rename to src/dispatching/dialogue/dialogue_dispatcher.rs index a30d7839..bb1e9a79 100644 --- a/src/dispatching/session/session_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -1,30 +1,33 @@ use crate::dispatching::{ - session::{ - GetChatId, InMemStorage, SessionHandlerCtx, SessionState, Storage, + dialogue::{ + Dialogue, DialogueHandlerCtx, DialogueStage, GetChatId, InMemStorage, + Storage, }, CtxHandler, DispatcherHandlerCtx, }; use std::{future::Future, pin::Pin}; -/// A dispatcher of user sessions. +/// A dispatcher of dialogues. /// -/// Note that `SessionDispatcher` implements `AsyncHandler`, so you can just put +/// Note that `DialogueDispatcher` implements `CtxHandler`, so you can just put /// an instance of this dispatcher into the [`Dispatcher`]'s methods. /// /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub struct SessionDispatcher<'a, Session, H> { - storage: Box + 'a>, +pub struct DialogueDispatcher<'a, State, T, H> { + storage: Box + 'a>, handler: H, } -impl<'a, Session, H> SessionDispatcher<'a, Session, H> +impl<'a, State, T, H> DialogueDispatcher<'a, State, T, H> where - Session: Default + 'a, + Dialogue: Default + 'a, + T: Default + 'a, + State: Default + 'a, { /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] /// (a default storage). /// - /// [`InMemStorage`]: crate::dispatching::session::InMemStorage + /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage #[must_use] pub fn new(handler: H) -> Self { Self { @@ -37,7 +40,7 @@ where #[must_use] pub fn with_storage(handler: H, storage: Stg) -> Self where - Stg: Storage + 'a, + Stg: Storage + 'a, { Self { storage: Box::new(storage), @@ -46,14 +49,13 @@ where } } -impl<'a, Session, H, Upd> CtxHandler, Result<(), ()>> - for SessionDispatcher<'a, Session, H> +impl<'a, State, T, H, Upd> CtxHandler, Result<(), ()>> + for DialogueDispatcher<'a, State, T, H> where - H: CtxHandler, SessionState>, + H: CtxHandler, DialogueStage>, Upd: GetChatId, - Session: Default, + Dialogue: Default, { - /// Dispatches a single `message` from a private chat. fn handle_ctx<'b>( &'b self, ctx: DispatcherHandlerCtx, @@ -64,30 +66,30 @@ where Box::pin(async move { let chat_id = ctx.update.chat_id(); - let session = self + let dialogue = self .storage - .remove_session(chat_id) + .remove_dialogue(chat_id) .await .unwrap_or_default(); - if let SessionState::Next(new_session) = self + if let DialogueStage::Next(new_dialogue) = self .handler - .handle_ctx(SessionHandlerCtx { + .handle_ctx(DialogueHandlerCtx { bot: ctx.bot, update: ctx.update, - session, + dialogue, }) .await { if self .storage - .update_session(chat_id, new_session) + .update_dialogue(chat_id, new_dialogue) .await .is_some() { panic!( - "We previously storage.remove_session() so \ - storage.update_session() must return None" + "We previously storage.remove_dialogue() so \ + storage.update_dialogue() must return None" ); } } diff --git a/src/dispatching/dialogue/dialogue_handler_ctx.rs b/src/dispatching/dialogue/dialogue_handler_ctx.rs new file mode 100644 index 00000000..f238df6f --- /dev/null +++ b/src/dispatching/dialogue/dialogue_handler_ctx.rs @@ -0,0 +1,38 @@ +use crate::{ + dispatching::dialogue::{Dialogue, GetChatId}, + requests::{Request, ResponseResult}, + types::Message, + Bot, +}; +use std::sync::Arc; + +/// A context of a [`DialogueDispatcher`]'s message handler. +/// +/// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher +pub struct DialogueHandlerCtx { + pub bot: Arc, + pub update: Upd, + pub dialogue: Dialogue, +} + +impl GetChatId for DialogueHandlerCtx +where + Upd: GetChatId, +{ + fn chat_id(&self) -> i64 { + self.update.chat_id() + } +} + +impl DialogueHandlerCtx { + pub async fn reply(&self, text: S) -> ResponseResult<()> + where + S: Into, + { + self.bot + .send_message(self.chat_id(), text) + .send() + .await + .map(|_| ()) + } +} diff --git a/src/dispatching/dialogue/dialogue_state.rs b/src/dispatching/dialogue/dialogue_state.rs new file mode 100644 index 00000000..24a0413e --- /dev/null +++ b/src/dispatching/dialogue/dialogue_state.rs @@ -0,0 +1,8 @@ +use crate::dispatching::dialogue::Dialogue; + +/// Continue or terminate a dialogue. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum DialogueStage { + Next(Dialogue), + Exit, +} diff --git a/src/dispatching/session/get_chat_id.rs b/src/dispatching/dialogue/get_chat_id.rs similarity index 100% rename from src/dispatching/session/get_chat_id.rs rename to src/dispatching/dialogue/get_chat_id.rs diff --git a/src/dispatching/session/mod.rs b/src/dispatching/dialogue/mod.rs similarity index 82% rename from src/dispatching/session/mod.rs rename to src/dispatching/dialogue/mod.rs index b693fba9..7d97a6a8 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -31,14 +31,19 @@ // TODO: examples +#![allow(clippy::module_inception)] +#![allow(clippy::type_complexity)] + +mod dialogue; +mod dialogue_dispatcher; +mod dialogue_handler_ctx; +mod dialogue_state; mod get_chat_id; -mod session_dispatcher; -mod session_handler_ctx; -mod session_state; mod storage; +pub use dialogue::Dialogue; +pub use dialogue_dispatcher::DialogueDispatcher; +pub use dialogue_handler_ctx::DialogueHandlerCtx; +pub use dialogue_state::DialogueStage; pub use get_chat_id::GetChatId; -pub use session_dispatcher::SessionDispatcher; -pub use session_handler_ctx::SessionHandlerCtx; -pub use session_state::SessionState; pub use storage::{InMemStorage, Storage}; diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs new file mode 100644 index 00000000..9db2c5fd --- /dev/null +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -0,0 +1,37 @@ +use async_trait::async_trait; + +use super::Storage; +use crate::dispatching::dialogue::Dialogue; +use std::collections::HashMap; +use tokio::sync::Mutex; + +/// A memory storage based on a hash map. Stores all the dialogues directly in +/// RAM. +/// +/// ## Note +/// All the dialogues will be lost after you restart your bot. If you need to +/// store them somewhere on a drive, you need to implement a storage +/// communicating with a DB. +#[derive(Debug, Default)] +pub struct InMemStorage { + map: Mutex>>, +} + +#[async_trait(?Send)] +#[async_trait] +impl Storage for InMemStorage { + async fn remove_dialogue( + &self, + chat_id: i64, + ) -> Option> { + self.map.lock().await.remove(&chat_id) + } + + async fn update_dialogue( + &self, + chat_id: i64, + dialogue: Dialogue, + ) -> Option> { + self.map.lock().await.insert(chat_id, dialogue) + } +} diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs new file mode 100644 index 00000000..1fe4641e --- /dev/null +++ b/src/dispatching/dialogue/storage/mod.rs @@ -0,0 +1,34 @@ +mod in_mem_storage; + +use crate::dispatching::dialogue::Dialogue; +use async_trait::async_trait; +pub use in_mem_storage::InMemStorage; + +/// A storage of dialogues. +/// +/// You can implement this trait for a structure that communicates with a DB and +/// be sure that after you restart your bot, all the dialogues won't be lost. +/// +/// For a storage based on a simple hash map, see [`InMemStorage`]. +/// +/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage +#[async_trait(?Send)] +#[async_trait] +pub trait Storage { + /// Removes a dialogue with the specified `chat_id`. + /// + /// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a + /// `dialogue` was deleted. + async fn remove_dialogue(&self, chat_id: i64) + -> Option>; + + /// Updates a dialogue with the specified `chat_id`. + /// + /// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a + /// `dialogue` was updated. + async fn update_dialogue( + &self, + chat_id: i64, + dialogue: Dialogue, + ) -> Option>; +} diff --git a/src/dispatching/dispatcher_handler_ctx.rs b/src/dispatching/dispatcher_handler_ctx.rs index c48e53fc..3eae25fb 100644 --- a/src/dispatching/dispatcher_handler_ctx.rs +++ b/src/dispatching/dispatcher_handler_ctx.rs @@ -1,5 +1,5 @@ use crate::{ - dispatching::session::GetChatId, + dispatching::dialogue::GetChatId, requests::{Request, ResponseResult}, types::Message, Bot, diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 2f2f5e9f..75030f8f 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -42,11 +42,11 @@ //! [`SessionDispatcher`]: crate::dispatching::SessionDispatcher mod ctx_handlers; +pub mod dialogue; mod dispatcher; mod dispatcher_handler_ctx; mod error_handlers; mod middleware; -pub mod session; pub mod update_listeners; pub use ctx_handlers::CtxHandler; diff --git a/src/dispatching/session/session_handler_ctx.rs b/src/dispatching/session/session_handler_ctx.rs deleted file mode 100644 index 44ef8294..00000000 --- a/src/dispatching/session/session_handler_ctx.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::{ - dispatching::session::GetChatId, - requests::{Request, ResponseResult}, - types::Message, - Bot, -}; -use std::sync::Arc; - -/// A context of a [`SessionDispatcher`]'s message handler. -/// -/// [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher -pub struct SessionHandlerCtx { - pub bot: Arc, - pub update: Upd, - pub session: Session, -} - -impl GetChatId for SessionHandlerCtx -where - Upd: GetChatId, -{ - fn chat_id(&self) -> i64 { - self.update.chat_id() - } -} - -impl SessionHandlerCtx { - pub async fn reply(&self, text: T) -> ResponseResult<()> - where - T: Into, - { - self.bot - .send_message(self.chat_id(), text) - .send() - .await - .map(|_| ()) - } -} diff --git a/src/dispatching/session/session_state.rs b/src/dispatching/session/session_state.rs deleted file mode 100644 index 636999cc..00000000 --- a/src/dispatching/session/session_state.rs +++ /dev/null @@ -1,6 +0,0 @@ -/// Continue or terminate a user session. -#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum SessionState { - Next(Session), - Exit, -} diff --git a/src/dispatching/session/storage/in_mem_storage.rs b/src/dispatching/session/storage/in_mem_storage.rs deleted file mode 100644 index 3203093c..00000000 --- a/src/dispatching/session/storage/in_mem_storage.rs +++ /dev/null @@ -1,33 +0,0 @@ -use async_trait::async_trait; - -use super::Storage; -use std::collections::HashMap; -use tokio::sync::Mutex; - -/// A memory storage based on a hash map. Stores all the sessions directly in -/// RAM. -/// -/// ## Note -/// All the sessions will be lost after you restart your bot. If you need to -/// store them somewhere on a drive, you need to implement a storage -/// communicating with a DB. -#[derive(Debug, Default)] -pub struct InMemStorage { - map: Mutex>, -} - -#[async_trait(?Send)] -#[async_trait] -impl Storage for InMemStorage { - async fn remove_session(&self, chat_id: i64) -> Option { - self.map.lock().await.remove(&chat_id) - } - - async fn update_session( - &self, - chat_id: i64, - state: Session, - ) -> Option { - self.map.lock().await.insert(chat_id, state) - } -} diff --git a/src/dispatching/session/storage/mod.rs b/src/dispatching/session/storage/mod.rs deleted file mode 100644 index 521cd934..00000000 --- a/src/dispatching/session/storage/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -mod in_mem_storage; - -use async_trait::async_trait; -pub use in_mem_storage::InMemStorage; - -/// A storage of sessions. -/// -/// You can implement this trait for a structure that communicates with a DB and -/// be sure that after you restart your bot, all the sessions won't be lost. -/// -/// For a storage based on a simple hash map, see [`InMemStorage`]. -/// -/// [`InMemStorage`]: crate::dispatching::session::InMemStorage -#[async_trait(?Send)] -#[async_trait] -pub trait Storage { - /// Removes a session with the specified `chat_id`. - /// - /// Returns `None` if there wasn't such a session, `Some(session)` if a - /// `session` was deleted. - async fn remove_session(&self, chat_id: i64) -> Option; - - /// Updates a session with the specified `chat_id`. - /// - /// Returns `None` if there wasn't such a session, `Some(session)` if a - /// `session` was updated. - async fn update_session( - &self, - chat_id: i64, - session: Session, - ) -> Option; -} diff --git a/src/prelude.rs b/src/prelude.rs index f753e3bd..dabc41d7 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,8 +2,8 @@ pub use crate::{ dispatching::{ - session::{ - GetChatId, SessionDispatcher, SessionHandlerCtx, SessionState, + dialogue::{ + DialogueDispatcher, DialogueHandlerCtx, DialogueStage, GetChatId, }, Dispatcher, DispatcherHandlerCtx, }, From 32f44f83abc74f6efb8f18e0cc537bee8aaa8f27 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 4 Feb 2020 21:39:42 +0600 Subject: [PATCH 26/91] A quick fix --- src/dispatching/dialogue/dialogue.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/dispatching/dialogue/dialogue.rs b/src/dispatching/dialogue/dialogue.rs index 9245aca3..aec56ce0 100644 --- a/src/dispatching/dialogue/dialogue.rs +++ b/src/dispatching/dialogue/dialogue.rs @@ -1,24 +1,4 @@ /// A type, encapsulating a dialogue state and arbitrary data. -/// -/// ## Example -/// ``` -/// use teloxide::dispatching::dialogue::Dialogue; -/// -/// enum MyState { -/// FullName, -/// Age, -/// FavouriteMusic, -/// } -/// -/// #[derive(Default)] -/// struct User { -/// full_name: Option, -/// age: Option, -/// favourite_music: Option, -/// } -/// -/// let _dialogue = Dialogue::new(MyState::FullName, User::default()); -/// ``` #[derive(Default, Debug, Copy, Clone, Eq, Hash, PartialEq)] pub struct Dialogue { pub state: State, From 327be5811c9c2eb1d75daa82194242b6d18b0144 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Tue, 4 Feb 2020 20:16:28 +0200 Subject: [PATCH 27/91] added functions into DispatcherHandlerCtx --- src/dispatching/dispatcher_handler_ctx.rs | 167 ++++++++++++---------- 1 file changed, 89 insertions(+), 78 deletions(-) diff --git a/src/dispatching/dispatcher_handler_ctx.rs b/src/dispatching/dispatcher_handler_ctx.rs index f697ce3d..9c874163 100644 --- a/src/dispatching/dispatcher_handler_ctx.rs +++ b/src/dispatching/dispatcher_handler_ctx.rs @@ -1,11 +1,15 @@ use crate::{ dispatching::session::GetChatId, - requests::{Request, ResponseResult}, - types::Message, + requests::{ + DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage, + PinChatMessage, Request, ResponseResult, SendAnimation, SendAudio, + SendContact, SendDocument, SendLocation, SendMediaGroup, SendMessage, + SendPhoto, SendSticker, SendVenue, SendVideo, SendVideoNote, SendVoice, + }, + types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message}, Bot, }; use std::sync::Arc; -use crate::types::{ChatId, InputFile, InputMedia}; /// A [`Dispatcher`]'s handler's context of a bot and an update. /// @@ -28,130 +32,137 @@ where } impl DispatcherHandlerCtx { - pub async fn answer(&self, text: T) -> ResponseResult + pub fn answer(&self, text: T) -> SendMessage where T: Into, { - self.bot - .send_message(self.chat_id(), text) - .send() - .await + self.bot.send_message(self.chat_id(), text) } - pub async fn reply_to(&self, text: T) -> ResponseResult + pub fn reply_to(&self, text: T) -> SendMessage where - T: Into + T: Into, { self.bot .send_message(self.chat_id(), text) .reply_to_message_id(self.update.id) - .send() - .await } - pub async fn answer_photo(&self, photo: InputFile) -> ResponseResult - { - self.bot - .send_photo(self.update.chat.id, photo) - .send() - .await + pub fn answer_photo(&self, photo: InputFile) -> SendPhoto { + self.bot.send_photo(self.update.chat.id, photo) } - pub async fn answer_audio(&self, audio: InputFile) -> ResponseResult - { - self.bot - .send_audio(self.update.chat.id, audio) - .send() - .await + pub fn answer_audio(&self, audio: InputFile) -> SendAudio { + self.bot.send_audio(self.update.chat.id, audio) } - pub async fn answer_animation(&self, animation: InputFile) -> ResponseResult - { - self.bot - .send_animation(self.update.chat.id, animation) - .send() - .await + pub fn answer_animation(&self, animation: InputFile) -> SendAnimation { + self.bot.send_animation(self.update.chat.id, animation) } - pub async fn answer_document(&self, document: InputFile) -> ResponseResult - { - self.bot - .send_document(self.update.chat.id, document) - .send() - .await + pub fn answer_document(&self, document: InputFile) -> SendDocument { + self.bot.send_document(self.update.chat.id, document) } - pub async fn answer_video(&self, video: InputFile) -> ResponseResult - { - self.bot - .send_video(self.update.chat.id, video) - .send() - .await + pub fn answer_video(&self, video: InputFile) -> SendVideo { + self.bot.send_video(self.update.chat.id, video) } - pub async fn answer_voice(&self, voice: InputFile) -> ResponseResult - { - self.bot - .send_voice(self.update.chat.id, voice) - .send() - .await + pub fn answer_voice(&self, voice: InputFile) -> SendVoice { + self.bot.send_voice(self.update.chat.id, voice) } - pub async fn answer_media_group(&self, media_group: T) -> ResponseResult> + pub fn answer_media_group(&self, media_group: T) -> SendMediaGroup where - T: Into> + T: Into>, { - self.bot - .send_media_group(self.update.chat.id, T) - .send() - .await + self.bot.send_media_group(self.update.chat.id, media_group) } - pub async fn answer_location(&self, latitude: f32, longitude: f32) -> ResponseResult - { + pub fn answer_location( + &self, + latitude: f32, + longitude: f32, + ) -> SendLocation { self.bot .send_location(self.update.chat.id, latitude, longitude) - .send() - .await } - pub async fn answer_venue(&self, latitude: f32, longitude: f32, title: T, address: U) -> ResponseResult + pub fn answer_venue( + &self, + latitude: f32, + longitude: f32, + title: T, + address: U, + ) -> SendVenue where T: Into, - U: Into + U: Into, { - self.bot - .send_venue(self.update.chat.id, latitude, longitude, title, address) - .send() - .await + self.bot.send_venue( + self.update.chat.id, + latitude, + longitude, + title, + address, + ) } - pub async fn answer_video_note(&self, video_note: InputFile) -> ResponseResult - { - self.bot - .send_video_note(self.update.chat.id, video_note) - .send() - .await + pub fn answer_video_note(&self, video_note: InputFile) -> SendVideoNote { + self.bot.send_video_note(self.update.chat.id, video_note) } - pub async fn answer_contact(&self, phone_number: T, first_name: U) -> ResponseResult + pub fn answer_contact( + &self, + phone_number: T, + first_name: U, + ) -> SendContact where T: Into, - U: Into + U: Into, { self.bot .send_contact(self.chat_id(), phone_number, first_name) - .send() - .await } - pub async fn forward_to(&self, chat_id: T) -> ResponseResult + pub fn answer_sticker(&self, sticker: InputFile) -> SendSticker { + self.bot.send_sticker(self.update.chat.id, sticker) + } + + pub fn forward_to(&self, chat_id: T) -> ForwardMessage where - T: Into + T: Into, { self.bot .forward_message(chat_id, self.update.chat.id, self.update.id) - .send() - .await + } + + pub fn edit_message_text(&self, text: T) -> EditMessageText + where + T: Into, + { + self.bot.edit_message_text( + ChatOrInlineMessage::Chat { + chat_id: self.update.chat.id.into(), + message_id: self.update.id, + }, + text, + ) + } + + pub fn edit_message_caption(&self) -> EditMessageCaption { + self.bot.edit_message_caption(ChatOrInlineMessage::Chat { + chat_id: self.update.chat.id.into(), + message_id: self.update.id, + }) + } + + pub fn delete_message(&self) -> DeleteMessage { + self.bot.delete_message(self.update.chat.id, self.update.id) + } + + pub fn pin_message(&self) -> PinChatMessage { + self.bot + .pin_chat_message(self.update.chat.id, self.update.id) } } From 6bb8241f27a0e659cc0c4ad2289ad254fb3eb9bc Mon Sep 17 00:00:00 2001 From: p0lunin Date: Tue, 4 Feb 2020 20:21:41 +0200 Subject: [PATCH 28/91] fix merged --- src/dispatching/dispatcher_handler_ctx.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dispatching/dispatcher_handler_ctx.rs b/src/dispatching/dispatcher_handler_ctx.rs index 9c874163..ccda15ff 100644 --- a/src/dispatching/dispatcher_handler_ctx.rs +++ b/src/dispatching/dispatcher_handler_ctx.rs @@ -1,10 +1,10 @@ use crate::{ - dispatching::session::GetChatId, + dispatching::dialogue::GetChatId, requests::{ DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage, - PinChatMessage, Request, ResponseResult, SendAnimation, SendAudio, - SendContact, SendDocument, SendLocation, SendMediaGroup, SendMessage, - SendPhoto, SendSticker, SendVenue, SendVideo, SendVideoNote, SendVoice, + PinChatMessage, SendAnimation, SendAudio, SendContact, SendDocument, + SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, + SendVenue, SendVideo, SendVideoNote, SendVoice, }, types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message}, Bot, From b064b85d37d82214c66bb337303ae494c2ba37bb Mon Sep 17 00:00:00 2001 From: p0lunin Date: Tue, 4 Feb 2020 20:47:29 +0200 Subject: [PATCH 29/91] fixed test (but it still failed, help please) --- src/dispatching/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 75030f8f..77d7a752 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -26,8 +26,10 @@ //! // Setup logging here... //! //! Dispatcher::new(Bot::new("MyAwesomeToken")) -//! .message_handler(|ctx: DispatcherHandlerCtx| async move { -//! ctx.reply("pong").await +//! .message_handler(|ctx: DispatcherHandlerCtx| { +//! async move { +//! ctx.answer("pong").send().await?; +//! } //! }) //! .dispatch() //! .await; From 2785c892c06370c2ae16b4b88cb6e3530bc416be Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 7 Feb 2020 06:20:14 +0600 Subject: [PATCH 30/91] Extend the middlewares API --- src/dispatching/dispatcher.rs | 85 +++++++++++++++++++++-------------- src/dispatching/middleware.rs | 12 +++-- src/dispatching/mod.rs | 6 +-- src/types/update.rs | 69 ++++++++++------------------ 4 files changed, 86 insertions(+), 86 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index a0c79ae2..591fb32e 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -67,6 +67,10 @@ where } /// Appends a middleware. + /// + /// If a middleware has returned `None`, an update will not be handled by a + /// next middleware or an appropriate handler (if it's the last middleware). + /// Otherwise, an update in `Some(update)` is passed further. #[must_use] pub fn middleware(mut self, val: M) -> Self where @@ -225,44 +229,57 @@ where }; let update = stream::iter(&self.middlewares) - .fold(update, |acc, middleware| async move { - middleware.handle(acc).await + .fold(Some(update), |acc, middleware| async move { + // Option::and_then is not working here, because + // Middleware::handle is asynchronous. + match acc { + Some(update) => middleware.handle(update).await, + None => None, + } }) .await; - match update.kind { - UpdateKind::Message(message) => { - self.handle(&self.message_handler, message).await - } - UpdateKind::EditedMessage(message) => { - self.handle(&self.edited_message_handler, message).await - } - UpdateKind::ChannelPost(post) => { - self.handle(&self.channel_post_handler, post).await - } - UpdateKind::EditedChannelPost(post) => { - self.handle(&self.edited_channel_post_handler, post) + if let Some(update) = update { + match update.kind { + UpdateKind::Message(message) => { + self.handle(&self.message_handler, message).await + } + UpdateKind::EditedMessage(message) => { + self.handle(&self.edited_message_handler, message) + .await + } + UpdateKind::ChannelPost(post) => { + self.handle(&self.channel_post_handler, post).await + } + UpdateKind::EditedChannelPost(post) => { + self.handle(&self.edited_channel_post_handler, post) + .await + } + UpdateKind::InlineQuery(query) => { + self.handle(&self.inline_query_handler, query).await + } + UpdateKind::ChosenInlineResult(result) => { + self.handle( + &self.chosen_inline_result_handler, + result, + ) .await - } - UpdateKind::InlineQuery(query) => { - self.handle(&self.inline_query_handler, query).await - } - UpdateKind::ChosenInlineResult(result) => { - self.handle(&self.chosen_inline_result_handler, result) - .await - } - UpdateKind::CallbackQuery(query) => { - self.handle(&self.callback_query_handler, query).await - } - UpdateKind::ShippingQuery(query) => { - self.handle(&self.shipping_query_handler, query).await - } - UpdateKind::PreCheckoutQuery(query) => { - self.handle(&self.pre_checkout_query_handler, query) - .await - } - UpdateKind::Poll(poll) => { - self.handle(&self.poll_handler, poll).await + } + UpdateKind::CallbackQuery(query) => { + self.handle(&self.callback_query_handler, query) + .await + } + UpdateKind::ShippingQuery(query) => { + self.handle(&self.shipping_query_handler, query) + .await + } + UpdateKind::PreCheckoutQuery(query) => { + self.handle(&self.pre_checkout_query_handler, query) + .await + } + UpdateKind::Poll(poll) => { + self.handle(&self.poll_handler, poll).await + } } } }) diff --git a/src/dispatching/middleware.rs b/src/dispatching/middleware.rs index 6efdc1db..8accb800 100644 --- a/src/dispatching/middleware.rs +++ b/src/dispatching/middleware.rs @@ -6,7 +6,10 @@ use std::{future::Future, pin::Pin}; /// overview](crate::dispatching). pub trait Middleware { #[must_use] - fn handle<'a>(&'a self, val: T) -> Pin + 'a>> + fn handle<'a>( + &'a self, + val: T, + ) -> Pin> + 'a>> where T: 'a; } @@ -14,9 +17,12 @@ pub trait Middleware { impl Middleware for F where F: Fn(T) -> Fut, - Fut: Future, + Fut: Future>, { - fn handle<'a>(&'a self, val: T) -> Pin + 'a>> + fn handle<'a>( + &'a self, + val: T, + ) -> Pin + 'a>> where T: 'a, { diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 77d7a752..efd5e3f8 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -26,10 +26,8 @@ //! // Setup logging here... //! //! Dispatcher::new(Bot::new("MyAwesomeToken")) -//! .message_handler(|ctx: DispatcherHandlerCtx| { -//! async move { -//! ctx.answer("pong").send().await?; -//! } +//! .message_handler(|ctx: DispatcherHandlerCtx| async move { +//! ctx.answer("pong").send().await?; //! }) //! .dispatch() //! .await; diff --git a/src/types/update.rs b/src/types/update.rs index 31e6db9c..410de438 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -2,7 +2,10 @@ use serde::{Deserialize, Serialize}; -use crate::types::{CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, PreCheckoutQuery, ShippingQuery, User, Sender, Chat}; +use crate::types::{ + CallbackQuery, Chat, ChosenInlineResult, InlineQuery, Message, Poll, + PreCheckoutQuery, Sender, ShippingQuery, User, +}; /// This [object] represents an incoming update. /// @@ -73,55 +76,31 @@ pub enum UpdateKind { impl Update { pub fn user(&self) -> Option<&User> { match &self.kind { - UpdateKind::Message(m) => { - match m.from() { - Some(Sender::User(user)) => Some(user), - _ => None, - } - } - UpdateKind::EditedMessage(m) => { - match m.from() { - Some(Sender::User(user)) => Some(user), - _ => None, - } - } - UpdateKind::CallbackQuery(query) => { - Some(&query.from) - } - UpdateKind::ChosenInlineResult(chosen) => { - Some(&chosen.from) - } - UpdateKind::InlineQuery(query) => { - Some(&query.from) - } - UpdateKind::ShippingQuery(query) => { - Some(&query.from) - } - UpdateKind::PreCheckoutQuery(query) => { - Some(&query.from) - } - _ => None + UpdateKind::Message(m) => match m.from() { + Some(Sender::User(user)) => Some(user), + _ => None, + }, + UpdateKind::EditedMessage(m) => match m.from() { + Some(Sender::User(user)) => Some(user), + _ => None, + }, + UpdateKind::CallbackQuery(query) => Some(&query.from), + UpdateKind::ChosenInlineResult(chosen) => Some(&chosen.from), + UpdateKind::InlineQuery(query) => Some(&query.from), + UpdateKind::ShippingQuery(query) => Some(&query.from), + UpdateKind::PreCheckoutQuery(query) => Some(&query.from), + _ => None, } } pub fn chat(&self) -> Option<&Chat> { match &self.kind { - UpdateKind::Message(m) => { - Some(&m.chat) - } - UpdateKind::EditedMessage(m) => { - Some(&m.chat) - } - UpdateKind::ChannelPost(p) => { - Some(&p.chat) - } - UpdateKind::EditedChannelPost(p) => { - Some(&p.chat) - } - UpdateKind::CallbackQuery(q) => { - Some(&q.message.as_ref()?.chat) - } - _ => None + UpdateKind::Message(m) => Some(&m.chat), + UpdateKind::EditedMessage(m) => Some(&m.chat), + UpdateKind::ChannelPost(p) => Some(&p.chat), + UpdateKind::EditedChannelPost(p) => Some(&p.chat), + UpdateKind::CallbackQuery(q) => Some(&q.message.as_ref()?.chat), + _ => None, } } } From ab8dae92139492eda362a96fbf25e848eb7695b9 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 7 Feb 2020 20:45:00 +0600 Subject: [PATCH 31/91] Fix Clippy --- src/dispatching/dispatcher.rs | 1 + src/requests/all/send_poll.rs | 2 ++ teloxide-macros/src/command.rs | 2 +- teloxide-macros/src/enum_attributes.rs | 4 ++-- teloxide-macros/src/lib.rs | 7 +++---- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 591fb32e..0ce99464 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -280,6 +280,7 @@ where UpdateKind::Poll(poll) => { self.handle(&self.poll_handler, poll).await } + _ => unreachable!(), } } }) diff --git a/src/requests/all/send_poll.rs b/src/requests/all/send_poll.rs index cec5a81a..657b430a 100644 --- a/src/requests/all/send_poll.rs +++ b/src/requests/all/send_poll.rs @@ -106,6 +106,7 @@ impl<'a> SendPoll<'a> { } /// `true`, if the poll needs to be anonymous, defaults to `true`. + #[allow(clippy::wrong_self_convention)] pub fn is_anonymous(mut self, val: T) -> Self where T: Into, @@ -145,6 +146,7 @@ impl<'a> SendPoll<'a> { /// Pass `true`, if the poll needs to be immediately closed. /// /// This can be useful for poll preview. + #[allow(clippy::wrong_self_convention)] pub fn is_closed(mut self, val: T) -> Self where T: Into, diff --git a/teloxide-macros/src/command.rs b/teloxide-macros/src/command.rs index c362faa4..1569b5c8 100644 --- a/teloxide-macros/src/command.rs +++ b/teloxide-macros/src/command.rs @@ -47,7 +47,7 @@ fn parse_attrs(attrs: &[Attr]) -> Result { BotCommandAttribute::Description => description = Some(attr.value()), BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), #[allow(unreachable_patterns)] - _ => return Err(format!("unexpected attribute")), + _ => return Err("unexpected attribute".to_owned()), } } diff --git a/teloxide-macros/src/enum_attributes.rs b/teloxide-macros/src/enum_attributes.rs index da290ca7..08a0c021 100644 --- a/teloxide-macros/src/enum_attributes.rs +++ b/teloxide-macros/src/enum_attributes.rs @@ -16,7 +16,7 @@ impl CommandEnum { if let Some(rename_rule) = &rename { match rename_rule.as_str() { "lowercase" => {}, - _ => return Err(format!("unallowed value")), + _ => return Err("disallowed value".to_owned()), } } Ok(Self { @@ -44,7 +44,7 @@ fn parse_attrs(attrs: &[Attr]) -> Result { BotCommandAttribute::Description => description = Some(attr.value()), BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), #[allow(unreachable_patterns)] - _ => return Err(format!("unexpected attribute")), + _ => return Err("unexpected attribute".to_owned()), } } diff --git a/teloxide-macros/src/lib.rs b/teloxide-macros/src/lib.rs index 16de50cf..c0aa0842 100644 --- a/teloxide-macros/src/lib.rs +++ b/teloxide-macros/src/lib.rs @@ -81,7 +81,7 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { }); let variant_str1 = variant_prefixes.zip(variant_name).map(|(prefix, command)| prefix.to_string() + command.as_str()); let variant_str2 = variant_str1.clone(); - let variant_description = variant_infos.iter().map(|info| info.description.as_ref().map(String::as_str).unwrap_or("")); + let variant_description = variant_infos.iter().map(|info| info.description.as_deref().unwrap_or("")); let ident = &input.ident; @@ -114,8 +114,7 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { }; //for debug //println!("{}", &expanded.to_string()); - let tokens = TokenStream::from(expanded); - tokens + TokenStream::from(expanded) } fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { @@ -125,7 +124,7 @@ fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { } } -fn parse_attributes(input: &Vec) -> Result, TokenStream> { +fn parse_attributes(input: &[syn::Attribute]) -> Result, TokenStream> { let mut enum_attrs = Vec::new(); for attr in input.iter() { match attr.parse_args::() { From e6bf25b3bfc10385a4862179ee820fb44faf9994 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 8 Feb 2020 00:06:41 +0600 Subject: [PATCH 32/91] Rework handlers (failing now) --- src/dispatching/dispatcher.rs | 329 ++++++++++--------- src/dispatching/dispatcher_handler_result.rs | 23 ++ src/dispatching/middleware.rs | 31 -- src/dispatching/mod.rs | 4 +- 4 files changed, 203 insertions(+), 184 deletions(-) create mode 100644 src/dispatching/dispatcher_handler_result.rs delete mode 100644 src/dispatching/middleware.rs diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 0ce99464..26d942d2 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -2,19 +2,24 @@ use crate::{ dispatching::{ error_handlers::ErrorHandler, update_listeners, update_listeners::UpdateListener, CtxHandler, DispatcherHandlerCtx, - LoggingErrorHandler, Middleware, + DispatcherHandlerResult, LoggingErrorHandler, }, types::{ CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, - PreCheckoutQuery, ShippingQuery, Update, UpdateKind, + PollAnswer, PreCheckoutQuery, ShippingQuery, Update, UpdateKind, }, Bot, }; use futures::{stream, StreamExt}; -use std::{fmt::Debug, sync::Arc}; +use std::{fmt::Debug, future::Future, sync::Arc}; -type H<'a, Upd, HandlerE> = Option< - Box, Result<(), HandlerE>> + 'a>, +type Handlers<'a, Upd, HandlerE> = Vec< + Box< + dyn CtxHandler< + DispatcherHandlerCtx, + DispatcherHandlerResult, + > + 'a, + >, >; /// One dispatcher to rule them all. @@ -24,20 +29,20 @@ type H<'a, Upd, HandlerE> = Option< pub struct Dispatcher<'a, HandlerE> { bot: Arc, - middlewares: Vec + 'a>>, - handlers_error_handler: Box + '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>, + update_handlers: Handlers<'a, Update, HandlerE>, + message_handlers: Handlers<'a, Message, HandlerE>, + edited_message_handlers: Handlers<'a, Message, HandlerE>, + channel_post_handlers: Handlers<'a, Message, HandlerE>, + edited_channel_post_handlers: Handlers<'a, Message, HandlerE>, + inline_query_handlers: Handlers<'a, InlineQuery, HandlerE>, + chosen_inline_result_handlers: Handlers<'a, ChosenInlineResult, HandlerE>, + callback_query_handlers: Handlers<'a, CallbackQuery, HandlerE>, + shipping_query_handlers: Handlers<'a, ShippingQuery, HandlerE>, + pre_checkout_query_handlers: Handlers<'a, PreCheckoutQuery, HandlerE>, + poll_handlers: Handlers<'a, Poll, HandlerE>, + poll_answer_handlers: Handlers<'a, PollAnswer, HandlerE>, } impl<'a, HandlerE> Dispatcher<'a, HandlerE> @@ -49,37 +54,24 @@ where pub fn new(bot: Bot) -> Self { Self { bot: Arc::new(bot), - middlewares: Vec::new(), handlers_error_handler: Box::new(LoggingErrorHandler::new( "An error from a Dispatcher's handler", )), - message_handler: None, - edited_message_handler: None, - channel_post_handler: None, - edited_channel_post_handler: None, - inline_query_handler: None, - chosen_inline_result_handler: None, - callback_query_handler: None, - shipping_query_handler: None, - pre_checkout_query_handler: None, - poll_handler: None, + update_handlers: Vec::new(), + message_handlers: Vec::new(), + edited_message_handlers: Vec::new(), + channel_post_handlers: Vec::new(), + edited_channel_post_handlers: Vec::new(), + inline_query_handlers: Vec::new(), + chosen_inline_result_handlers: Vec::new(), + callback_query_handlers: Vec::new(), + shipping_query_handlers: Vec::new(), + pre_checkout_query_handlers: Vec::new(), + poll_handlers: Vec::new(), + poll_answer_handlers: Vec::new(), } } - /// Appends a middleware. - /// - /// If a middleware has returned `None`, an update will not be handled by a - /// next middleware or an appropriate handler (if it's the last middleware). - /// Otherwise, an update in `Some(update)` is passed further. - #[must_use] - pub fn middleware(mut self, val: M) -> Self - where - M: Middleware + 'a, - { - self.middlewares.push(Box::new(val)); - self - } - /// Registers a handler of errors, produced by other handlers. #[must_use] pub fn handlers_error_handler(mut self, val: T) -> Self @@ -90,106 +82,125 @@ where self } - #[must_use] - pub fn message_handler(mut self, h: H) -> Self - where - H: CtxHandler, Result<(), HandlerE>> + 'a, + /// Registers a single handler. + fn register_handler( + handlers: &mut Handlers<'a, Upd, HandlerE>, + h: H, + ) where + H: CtxHandler, I> + 'a, + I: Into>, { - self.message_handler = Some(Box::new(h)); + handlers + .push(Box::new(move |ctx| map_fut(h.handle_ctx(ctx), Into::into))); + } + + #[must_use] + pub fn message_handler(mut self, h: H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into>, + { + Self::register_handler(&mut self.message_handlers, h); self } #[must_use] - pub fn edited_message_handler(mut self, h: H) -> Self + pub fn edited_message_handler(mut self, h: H) -> Self where - H: CtxHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, I> + 'a, + I: Into>, { - self.edited_message_handler = Some(Box::new(h)); + Self::register_handler(&mut self.edited_message_handlers, h); self } #[must_use] - pub fn channel_post_handler(mut self, h: H) -> Self + pub fn channel_post_handler(mut self, h: H) -> Self where - H: CtxHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, I> + 'a, + I: Into>, { - self.channel_post_handler = Some(Box::new(h)); + Self::register_handler(&mut self.channel_post_handlers, h); self } #[must_use] - pub fn edited_channel_post_handler(mut self, h: H) -> Self + pub fn edited_channel_post_handler(mut self, h: H) -> Self where - H: CtxHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, I> + 'a, + I: Into>, { - self.edited_channel_post_handler = Some(Box::new(h)); + Self::register_handler(&mut self.edited_channel_post_handlers, h); self } #[must_use] - pub fn inline_query_handler(mut self, h: H) -> Self + pub fn inline_query_handler(mut self, h: H) -> Self where - H: CtxHandler, Result<(), HandlerE>> - + 'a, + H: CtxHandler, I> + 'a, + I: Into>, { - self.inline_query_handler = Some(Box::new(h)); + Self::register_handler(&mut self.inline_query_handlers, h); self } #[must_use] - pub fn chosen_inline_result_handler(mut self, h: H) -> Self + pub fn chosen_inline_result_handler(mut self, h: H) -> Self where - H: CtxHandler< - DispatcherHandlerCtx, - Result<(), HandlerE>, - > + 'a, + H: CtxHandler, I> + 'a, + I: Into>, { - self.chosen_inline_result_handler = Some(Box::new(h)); + Self::register_handler(&mut self.chosen_inline_result_handlers, h); self } #[must_use] - pub fn callback_query_handler(mut self, h: H) -> Self + pub fn callback_query_handler(mut self, h: H) -> Self where - H: CtxHandler< - DispatcherHandlerCtx, - Result<(), HandlerE>, - > + 'a, + H: CtxHandler, I> + 'a, + I: Into>, { - self.callback_query_handler = Some(Box::new(h)); + Self::register_handler(&mut self.callback_query_handlers, h); self } #[must_use] - pub fn shipping_query_handler(mut self, h: H) -> Self + pub fn shipping_query_handler(mut self, h: H) -> Self where - H: CtxHandler< - DispatcherHandlerCtx, - Result<(), HandlerE>, - > + 'a, + H: CtxHandler, I> + 'a, + I: Into>, { - self.shipping_query_handler = Some(Box::new(h)); + Self::register_handler(&mut self.shipping_query_handlers, h); self } #[must_use] - pub fn pre_checkout_query_handler(mut self, h: H) -> Self + pub fn pre_checkout_query_handler(mut self, h: H) -> Self where - H: CtxHandler< - DispatcherHandlerCtx, - Result<(), HandlerE>, - > + 'a, + H: CtxHandler, I> + 'a, + I: Into>, { - self.pre_checkout_query_handler = Some(Box::new(h)); + Self::register_handler(&mut self.pre_checkout_query_handlers, h); self } #[must_use] - pub fn poll_handler(mut self, h: H) -> Self + pub fn poll_handler(mut self, h: H) -> Self where - H: CtxHandler, Result<(), HandlerE>> + 'a, + H: CtxHandler, I> + 'a, + I: Into>, { - self.poll_handler = Some(Box::new(h)); + Self::register_handler(&mut self.poll_handlers, h); + self + } + + #[must_use] + pub fn poll_answer_handler(mut self, h: H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into>, + { + Self::register_handler(&mut self.poll_answer_handlers, h); self } @@ -228,59 +239,52 @@ where } }; - let update = stream::iter(&self.middlewares) - .fold(Some(update), |acc, middleware| async move { - // Option::and_then is not working here, because - // Middleware::handle is asynchronous. - match acc { - Some(update) => middleware.handle(update).await, - None => None, - } - }) - .await; + let update = + match self.handle(&self.update_handlers, update).await { + Some(update) => update, + None => return, + }; - if let Some(update) = update { - match update.kind { - UpdateKind::Message(message) => { - self.handle(&self.message_handler, message).await - } - UpdateKind::EditedMessage(message) => { - self.handle(&self.edited_message_handler, message) - .await - } - UpdateKind::ChannelPost(post) => { - self.handle(&self.channel_post_handler, post).await - } - UpdateKind::EditedChannelPost(post) => { - self.handle(&self.edited_channel_post_handler, post) - .await - } - UpdateKind::InlineQuery(query) => { - self.handle(&self.inline_query_handler, query).await - } - UpdateKind::ChosenInlineResult(result) => { - self.handle( - &self.chosen_inline_result_handler, - result, - ) - .await - } - UpdateKind::CallbackQuery(query) => { - self.handle(&self.callback_query_handler, query) - .await - } - UpdateKind::ShippingQuery(query) => { - self.handle(&self.shipping_query_handler, query) - .await - } - UpdateKind::PreCheckoutQuery(query) => { - self.handle(&self.pre_checkout_query_handler, query) - .await - } - UpdateKind::Poll(poll) => { - self.handle(&self.poll_handler, poll).await - } - _ => unreachable!(), + match update.kind { + UpdateKind::Message(message) => { + self.handle(&self.message_handlers, message).await; + } + UpdateKind::EditedMessage(message) => { + self.handle(&self.edited_message_handlers, message) + .await; + } + UpdateKind::ChannelPost(post) => { + self.handle(&self.channel_post_handlers, post).await; + } + UpdateKind::EditedChannelPost(post) => { + self.handle(&self.edited_channel_post_handlers, post) + .await; + } + UpdateKind::InlineQuery(query) => { + self.handle(&self.inline_query_handlers, query).await; + } + UpdateKind::ChosenInlineResult(result) => { + self.handle( + &self.chosen_inline_result_handlers, + result, + ) + .await; + } + UpdateKind::CallbackQuery(query) => { + self.handle(&self.callback_query_handlers, query).await; + } + UpdateKind::ShippingQuery(query) => { + self.handle(&self.shipping_query_handlers, query).await; + } + UpdateKind::PreCheckoutQuery(query) => { + self.handle(&self.pre_checkout_query_handlers, query) + .await; + } + UpdateKind::Poll(poll) => { + self.handle(&self.poll_handlers, poll).await; + } + UpdateKind::PollAnswer(answer) => { + self.handle(&self.poll_answer_handlers, answer).await; } } }) @@ -288,17 +292,40 @@ where } // Handles a single update. - async fn handle(&self, handler: &H<'a, Upd, HandlerE>, update: Upd) { - if let Some(handler) = &handler { - if let Err(error) = handler - .handle_ctx(DispatcherHandlerCtx { - bot: Arc::clone(&self.bot), - update, - }) - .await - { - self.handlers_error_handler.handle_error(error).await; - } - } + #[allow(clippy::ptr_arg)] + async fn handle( + &self, + handlers: &Handlers<'a, Upd, HandlerE>, + update: Upd, + ) -> Option { + stream::iter(handlers) + .fold(Some(update), |acc, handler| async move { + // Option::and_then is not working here, because + // Middleware::handle is asynchronous. + match acc { + Some(update) => { + let DispatcherHandlerResult { next, result } = handler + .handle_ctx(DispatcherHandlerCtx { + bot: Arc::clone(&self.bot), + update, + }) + .await; + + if let Err(error) = result { + self.handlers_error_handler + .handle_error(error) + .await + } + + next + } + None => None, + } + }) + .await } } + +async fn map_fut(fut: impl Future, f: impl Fn(T) -> U) -> U { + f(fut.await) +} diff --git a/src/dispatching/dispatcher_handler_result.rs b/src/dispatching/dispatcher_handler_result.rs new file mode 100644 index 00000000..dc7abd2d --- /dev/null +++ b/src/dispatching/dispatcher_handler_result.rs @@ -0,0 +1,23 @@ +/// A result of a handler in [`Dispatcher`]. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). +/// +/// [`Dispatcher`]: crate::dispatching::Dispatcher +pub struct DispatcherHandlerResult { + next: Option, + result: Result<(), E>, +} + +impl DispatcherHandlerResult { + /// Creates new `DispatcherHandlerResult`. + pub fn new(next: Option, result: Result<(), E>) -> Self { + Self { next, result } + } +} + +impl From> for DispatcherHandlerResult { + fn from(result: Result<(), E>) -> Self { + Self::new(None, result) + } +} diff --git a/src/dispatching/middleware.rs b/src/dispatching/middleware.rs deleted file mode 100644 index 8accb800..00000000 --- a/src/dispatching/middleware.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::{future::Future, pin::Pin}; - -/// An asynchronous middleware. -/// -/// See [the module-level documentation for the design -/// overview](crate::dispatching). -pub trait Middleware { - #[must_use] - fn handle<'a>( - &'a self, - val: T, - ) -> Pin> + 'a>> - where - T: 'a; -} - -impl Middleware for F -where - F: Fn(T) -> Fut, - Fut: Future>, -{ - fn handle<'a>( - &'a self, - val: T, - ) -> Pin + 'a>> - where - T: 'a, - { - Box::pin(async move { self(val).await }) - } -} diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index efd5e3f8..373c33ce 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -45,15 +45,15 @@ mod ctx_handlers; pub mod dialogue; mod dispatcher; mod dispatcher_handler_ctx; +mod dispatcher_handler_result; mod error_handlers; -mod middleware; pub mod update_listeners; pub use ctx_handlers::CtxHandler; pub use dispatcher::Dispatcher; pub use dispatcher_handler_ctx::DispatcherHandlerCtx; +pub use dispatcher_handler_result::DispatcherHandlerResult; pub use error_handlers::{ ErrorHandler, IgnoringErrorHandler, IgnoringErrorHandlerSafe, LoggingErrorHandler, }; -pub use middleware::Middleware; From fbd02c5a14f51ec370db9b881e9e10ba2f761333 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 8 Feb 2020 22:59:57 +0600 Subject: [PATCH 33/91] Trying to win the compiler --- src/dispatching/dispatcher.rs | 97 ++++++++++++-------- src/dispatching/dispatcher_handler_result.rs | 4 +- 2 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 26d942d2..4edcef5c 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -82,25 +82,13 @@ where self } - /// Registers a single handler. - fn register_handler( - handlers: &mut Handlers<'a, Upd, HandlerE>, - h: H, - ) where - H: CtxHandler, I> + 'a, - I: Into>, - { - handlers - .push(Box::new(move |ctx| map_fut(h.handle_ctx(ctx), Into::into))); - } - #[must_use] pub fn message_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.message_handlers, h); + self.message_handlers = register_handler(self.message_handlers, h); self } @@ -108,9 +96,10 @@ where pub fn edited_message_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.edited_message_handlers, h); + self.edited_message_handlers = + register_handler(self.edited_message_handlers, h); self } @@ -118,9 +107,10 @@ where pub fn channel_post_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.channel_post_handlers, h); + self.channel_post_handlers = + register_handler(self.channel_post_handlers, h); self } @@ -128,9 +118,10 @@ where pub fn edited_channel_post_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.edited_channel_post_handlers, h); + self.edited_channel_post_handlers = + register_handler(self.edited_channel_post_handlers, h); self } @@ -138,9 +129,10 @@ where pub fn inline_query_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.inline_query_handlers, h); + self.inline_query_handlers = + register_handler(self.inline_query_handlers, h); self } @@ -148,9 +140,10 @@ where pub fn chosen_inline_result_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.chosen_inline_result_handlers, h); + self.chosen_inline_result_handlers = + register_handler(self.chosen_inline_result_handlers, h); self } @@ -158,9 +151,10 @@ where pub fn callback_query_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.callback_query_handlers, h); + self.callback_query_handlers = + register_handler(self.callback_query_handlers, h); self } @@ -168,9 +162,10 @@ where pub fn shipping_query_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.shipping_query_handlers, h); + self.shipping_query_handlers = + register_handler(self.shipping_query_handlers, h); self } @@ -178,9 +173,10 @@ where pub fn pre_checkout_query_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.pre_checkout_query_handlers, h); + self.pre_checkout_query_handlers = + register_handler(self.pre_checkout_query_handlers, h); self } @@ -188,9 +184,9 @@ where pub fn poll_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.poll_handlers, h); + self.poll_handlers = register_handler(self.poll_handlers, h); self } @@ -198,9 +194,10 @@ where pub fn poll_answer_handler(mut self, h: H) -> Self where H: CtxHandler, I> + 'a, - I: Into>, + I: Into> + 'a, { - Self::register_handler(&mut self.poll_answer_handlers, h); + self.poll_answer_handlers = + register_handler(self.poll_answer_handlers, h); self } @@ -326,6 +323,34 @@ where } } -async fn map_fut(fut: impl Future, f: impl Fn(T) -> U) -> U { - f(fut.await) +// Transforms Future into Future by applying an Into +// conversion. +async fn intermediate_fut0(fut: impl Future) -> U +where + T: Into, +{ + fut.await.into() +} + +fn intermediate_fut1<'a, Upd, HandlerE, H, I>( + h: H, +) -> impl CtxHandler, DispatcherHandlerResult> +where + H: CtxHandler, I> + 'a, + I: Into> + 'a, +{ + move |ctx| intermediate_fut0(h.handle_ctx(ctx)) +} + +/// Registers a single handler. +fn register_handler<'a, Upd, H, I, HandlerE>( + mut handlers: Handlers<'a, Upd, HandlerE>, + h: H, +) -> Handlers<'a, Upd, HandlerE> +where + H: CtxHandler, I> + 'a, + I: Into> + 'a, +{ + // handlers.push(Box::new()); + handlers } diff --git a/src/dispatching/dispatcher_handler_result.rs b/src/dispatching/dispatcher_handler_result.rs index dc7abd2d..69b0ea4f 100644 --- a/src/dispatching/dispatcher_handler_result.rs +++ b/src/dispatching/dispatcher_handler_result.rs @@ -5,8 +5,8 @@ /// /// [`Dispatcher`]: crate::dispatching::Dispatcher pub struct DispatcherHandlerResult { - next: Option, - result: Result<(), E>, + pub next: Option, + pub result: Result<(), E>, } impl DispatcherHandlerResult { From 809aaef9b1da80d76bc21244f6592a08e3f99e93 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 10 Feb 2020 00:29:30 +0600 Subject: [PATCH 34/91] Winning the compiler... --- src/dispatching/dispatcher.rs | 42 +++++++++++++++++++++-------------- src/dispatching/mod.rs | 3 ++- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 4edcef5c..61795838 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -83,7 +83,7 @@ where } #[must_use] - pub fn message_handler(mut self, h: H) -> Self + pub fn message_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -93,7 +93,7 @@ where } #[must_use] - pub fn edited_message_handler(mut self, h: H) -> Self + pub fn edited_message_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -104,7 +104,7 @@ where } #[must_use] - pub fn channel_post_handler(mut self, h: H) -> Self + pub fn channel_post_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -115,7 +115,7 @@ where } #[must_use] - pub fn edited_channel_post_handler(mut self, h: H) -> Self + pub fn edited_channel_post_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -126,7 +126,7 @@ where } #[must_use] - pub fn inline_query_handler(mut self, h: H) -> Self + pub fn inline_query_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -137,7 +137,7 @@ where } #[must_use] - pub fn chosen_inline_result_handler(mut self, h: H) -> Self + pub fn chosen_inline_result_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -148,7 +148,7 @@ where } #[must_use] - pub fn callback_query_handler(mut self, h: H) -> Self + pub fn callback_query_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -159,7 +159,7 @@ where } #[must_use] - pub fn shipping_query_handler(mut self, h: H) -> Self + pub fn shipping_query_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -170,7 +170,7 @@ where } #[must_use] - pub fn pre_checkout_query_handler(mut self, h: H) -> Self + pub fn pre_checkout_query_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -181,7 +181,7 @@ where } #[must_use] - pub fn poll_handler(mut self, h: H) -> Self + pub fn poll_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -191,7 +191,7 @@ where } #[must_use] - pub fn poll_answer_handler(mut self, h: H) -> Self + pub fn poll_answer_handler(mut self, h: &'a H) -> Self where H: CtxHandler, I> + 'a, I: Into> + 'a, @@ -323,8 +323,8 @@ where } } -// Transforms Future into Future by applying an Into -// conversion. +/// Transforms Future into Future by applying an Into +/// conversion. async fn intermediate_fut0(fut: impl Future) -> U where T: Into, @@ -332,12 +332,18 @@ where fut.await.into() } +/// Transforms CtxHandler with Into> as a return +/// value into CtxHandler with DispatcherHandlerResult return value. fn intermediate_fut1<'a, Upd, HandlerE, H, I>( - h: H, -) -> impl CtxHandler, DispatcherHandlerResult> + h: &'a H, +) -> impl CtxHandler< + DispatcherHandlerCtx, + DispatcherHandlerResult, +> + 'a where H: CtxHandler, I> + 'a, I: Into> + 'a, + Upd: 'a, { move |ctx| intermediate_fut0(h.handle_ctx(ctx)) } @@ -345,12 +351,14 @@ where /// Registers a single handler. fn register_handler<'a, Upd, H, I, HandlerE>( mut handlers: Handlers<'a, Upd, HandlerE>, - h: H, + h: &'a H, ) -> Handlers<'a, Upd, HandlerE> where H: CtxHandler, I> + 'a, I: Into> + 'a, + HandlerE: 'a, + Upd: 'a, { - // handlers.push(Box::new()); + handlers.push(Box::new(intermediate_fut1(h))); handlers } diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 373c33ce..35ab0c65 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -26,8 +26,9 @@ //! // Setup logging here... //! //! Dispatcher::new(Bot::new("MyAwesomeToken")) -//! .message_handler(|ctx: DispatcherHandlerCtx| async move { +//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { //! ctx.answer("pong").send().await?; +//! Ok(()) //! }) //! .dispatch() //! .await; From bde4d09e5d64dda87b372dc76facb4a3386c9e4c Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 02:54:08 +0600 Subject: [PATCH 35/91] Add dialogue::{next, exit} --- examples/simple_dialogue/Cargo.toml | 1 + examples/simple_dialogue/src/main.rs | 22 ++++++++++------------ src/dispatching/dialogue/dialogue_stage.rs | 20 ++++++++++++++++++++ src/dispatching/dialogue/dialogue_state.rs | 8 -------- src/dispatching/dialogue/mod.rs | 4 ++-- src/dispatching/dispatcher.rs | 6 ++++-- src/dispatching/mod.rs | 2 +- src/prelude.rs | 3 ++- 8 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 src/dispatching/dialogue/dialogue_stage.rs delete mode 100644 src/dispatching/dialogue/dialogue_state.rs diff --git a/examples/simple_dialogue/Cargo.toml b/examples/simple_dialogue/Cargo.toml index 42d2efd7..d9c28590 100644 --- a/examples/simple_dialogue/Cargo.toml +++ b/examples/simple_dialogue/Cargo.toml @@ -11,6 +11,7 @@ pretty_env_logger = "0.3.1" log = "0.4.8" tokio = "0.2.9" strum = "0.17.1" +smart-default = "0.6.0" strum_macros = "0.17.1" teloxide = { path = "../../" } diff --git a/examples/simple_dialogue/src/main.rs b/examples/simple_dialogue/src/main.rs index a99dfac0..12b6e6d7 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -1,5 +1,7 @@ #[macro_use] extern crate strum_macros; +#[macro_use] +extern crate smart_default; use std::fmt::{self, Display, Formatter}; use teloxide::{ @@ -57,19 +59,15 @@ impl Display for User { // [States of a dialogue] // ============================================================================ +#[derive(SmartDefault)] enum State { + #[default] Start, FullName, Age, FavouriteMusic, } -impl Default for State { - fn default() -> Self { - Self::Start - } -} - // ============================================================================ // [Control a dialogue] // ============================================================================ @@ -90,14 +88,14 @@ async fn start(mut ctx: Ctx) -> Res { ctx.reply("Let's start! First, what's your full name?") .await?; ctx.dialogue.state = State::FullName; - Ok(DialogueStage::Next(ctx.dialogue)) + next(ctx.dialogue) } async fn full_name(mut ctx: Ctx) -> Res { ctx.reply("What a wonderful name! Your age?").await?; ctx.dialogue.data.full_name = Some(ctx.update.text().unwrap().to_owned()); ctx.dialogue.state = State::Age; - Ok(DialogueStage::Next(ctx.dialogue)) + next(ctx.dialogue) } async fn age(mut ctx: Ctx) -> Res { @@ -110,7 +108,7 @@ async fn age(mut ctx: Ctx) -> Res { Err(_) => ctx.reply("Oh, please, enter a number!").await?, } - Ok(DialogueStage::Next(ctx.dialogue)) + next(ctx.dialogue) } async fn favourite_music(mut ctx: Ctx) -> Res { @@ -118,11 +116,11 @@ async fn favourite_music(mut ctx: Ctx) -> Res { Ok(ok) => { ctx.dialogue.data.favourite_music = Some(ok); ctx.reply(format!("Fine. {}", ctx.dialogue.data)).await?; - Ok(DialogueStage::Exit) + exit() } Err(_) => { ctx.reply("Oh, please, enter from the keyboard!").await?; - Ok(DialogueStage::Next(ctx.dialogue)) + next(ctx.dialogue) } } } @@ -147,7 +145,7 @@ async fn main() { log::info!("Starting the simple_dialogue bot!"); Dispatcher::new(Bot::new("YourAwesomeToken")) - .message_handler(DialogueDispatcher::new(|ctx| async move { + .message_handler(&DialogueDispatcher::new(|ctx| async move { handle_message(ctx) .await .expect("Something wrong with the bot!") diff --git a/src/dispatching/dialogue/dialogue_stage.rs b/src/dispatching/dialogue/dialogue_stage.rs new file mode 100644 index 00000000..43ea25cd --- /dev/null +++ b/src/dispatching/dialogue/dialogue_stage.rs @@ -0,0 +1,20 @@ +use crate::dispatching::dialogue::Dialogue; + +/// Continue or terminate a dialogue. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum DialogueStage { + Next(Dialogue), + Exit, +} + +/// A shortcut for `Ok(DialogueStage::Next(dialogue))`. +pub fn next( + dialogue: Dialogue, +) -> Result, E> { + Ok(DialogueStage::Next(dialogue)) +} + +/// A shortcut for `Ok(DialogueStage::Exit)`. +pub fn exit() -> Result, E> { + Ok(DialogueStage::Exit) +} diff --git a/src/dispatching/dialogue/dialogue_state.rs b/src/dispatching/dialogue/dialogue_state.rs deleted file mode 100644 index 24a0413e..00000000 --- a/src/dispatching/dialogue/dialogue_state.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::dispatching::dialogue::Dialogue; - -/// Continue or terminate a dialogue. -#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum DialogueStage { - Next(Dialogue), - Exit, -} diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 7d97a6a8..4294eb5e 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -37,13 +37,13 @@ mod dialogue; mod dialogue_dispatcher; mod dialogue_handler_ctx; -mod dialogue_state; +mod dialogue_stage; mod get_chat_id; mod storage; pub use dialogue::Dialogue; pub use dialogue_dispatcher::DialogueDispatcher; pub use dialogue_handler_ctx::DialogueHandlerCtx; -pub use dialogue_state::DialogueStage; +pub use dialogue_stage::{exit, next, DialogueStage}; pub use get_chat_id::GetChatId; pub use storage::{InMemStorage, Storage}; diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 61795838..00b62163 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -8,7 +8,7 @@ use crate::{ CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, Update, UpdateKind, }, - Bot, + Bot, RequestError, }; use futures::{stream, StreamExt}; use std::{fmt::Debug, future::Future, sync::Arc}; @@ -26,7 +26,9 @@ type Handlers<'a, Upd, HandlerE> = Vec< /// /// See [the module-level documentation for the design /// overview](crate::dispatching). -pub struct Dispatcher<'a, HandlerE> { +// HandlerE=RequestError doesn't work now, because of very poor type inference. +// See https://github.com/rust-lang/rust/issues/27336 for more details. +pub struct Dispatcher<'a, HandlerE = RequestError> { bot: Arc, handlers_error_handler: Box + 'a>, diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 35ab0c65..8c158038 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -25,7 +25,7 @@ //! //! // Setup logging here... //! -//! Dispatcher::new(Bot::new("MyAwesomeToken")) +//! Dispatcher::::new(Bot::new("MyAwesomeToken")) //! .message_handler(&|ctx: DispatcherHandlerCtx| async move { //! ctx.answer("pong").send().await?; //! Ok(()) diff --git a/src/prelude.rs b/src/prelude.rs index dabc41d7..c70efa5b 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,7 +3,8 @@ pub use crate::{ dispatching::{ dialogue::{ - DialogueDispatcher, DialogueHandlerCtx, DialogueStage, GetChatId, + exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage, + GetChatId, }, Dispatcher, DispatcherHandlerCtx, }, From 9525414f8d2c1f1cbe2ee2db70920f081edf16fe Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 03:13:09 +0600 Subject: [PATCH 36/91] Simplify examples/simple_dialogue --- examples/simple_dialogue/src/main.rs | 24 ++- .../dialogue/dialogue_handler_ctx.rs | 144 +++++++++++++++++- 2 files changed, 153 insertions(+), 15 deletions(-) diff --git a/examples/simple_dialogue/src/main.rs b/examples/simple_dialogue/src/main.rs index 12b6e6d7..d5dd3b26 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -76,8 +76,7 @@ type Ctx = DialogueHandlerCtx; type Res = Result, RequestError>; async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { - ctx.bot - .send_message(ctx.chat_id(), "Good. Now choose your favourite music:") + ctx.answer("Good. Now choose your favourite music:") .reply_markup(FavouriteMusic::markup()) .send() .await?; @@ -85,14 +84,17 @@ async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { } async fn start(mut ctx: Ctx) -> Res { - ctx.reply("Let's start! First, what's your full name?") + ctx.answer("Let's start! First, what's your full name?") + .send() .await?; ctx.dialogue.state = State::FullName; next(ctx.dialogue) } async fn full_name(mut ctx: Ctx) -> Res { - ctx.reply("What a wonderful name! Your age?").await?; + ctx.answer("What a wonderful name! Your age?") + .send() + .await?; ctx.dialogue.data.full_name = Some(ctx.update.text().unwrap().to_owned()); ctx.dialogue.state = State::Age; next(ctx.dialogue) @@ -105,7 +107,11 @@ async fn age(mut ctx: Ctx) -> Res { ctx.dialogue.data.age = Some(ok); ctx.dialogue.state = State::FavouriteMusic; } - Err(_) => ctx.reply("Oh, please, enter a number!").await?, + Err(_) => ctx + .answer("Oh, please, enter a number!") + .send() + .await + .map(|_| ())?, } next(ctx.dialogue) @@ -115,11 +121,15 @@ async fn favourite_music(mut ctx: Ctx) -> Res { match ctx.update.text().unwrap().parse() { Ok(ok) => { ctx.dialogue.data.favourite_music = Some(ok); - ctx.reply(format!("Fine. {}", ctx.dialogue.data)).await?; + ctx.answer(format!("Fine. {}", ctx.dialogue.data)) + .send() + .await?; exit() } Err(_) => { - ctx.reply("Oh, please, enter from the keyboard!").await?; + ctx.answer("Oh, please, enter from the keyboard!") + .send() + .await?; next(ctx.dialogue) } } diff --git a/src/dispatching/dialogue/dialogue_handler_ctx.rs b/src/dispatching/dialogue/dialogue_handler_ctx.rs index f238df6f..6e4a283d 100644 --- a/src/dispatching/dialogue/dialogue_handler_ctx.rs +++ b/src/dispatching/dialogue/dialogue_handler_ctx.rs @@ -1,7 +1,12 @@ use crate::{ dispatching::dialogue::{Dialogue, GetChatId}, - requests::{Request, ResponseResult}, - types::Message, + requests::{ + DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage, + PinChatMessage, SendAnimation, SendAudio, SendContact, SendDocument, + SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, + SendVenue, SendVideo, SendVideoNote, SendVoice, + }, + types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message}, Bot, }; use std::sync::Arc; @@ -24,15 +29,138 @@ where } } -impl DialogueHandlerCtx { - pub async fn reply(&self, text: S) -> ResponseResult<()> +impl DialogueHandlerCtx { + pub fn answer(&self, text: T) -> SendMessage where - S: Into, + T: Into, + { + self.bot.send_message(self.chat_id(), text) + } + + pub fn reply_to(&self, text: T) -> SendMessage + where + T: Into, { self.bot .send_message(self.chat_id(), text) - .send() - .await - .map(|_| ()) + .reply_to_message_id(self.update.id) + } + + pub fn answer_photo(&self, photo: InputFile) -> SendPhoto { + self.bot.send_photo(self.update.chat.id, photo) + } + + pub fn answer_audio(&self, audio: InputFile) -> SendAudio { + self.bot.send_audio(self.update.chat.id, audio) + } + + pub fn answer_animation(&self, animation: InputFile) -> SendAnimation { + self.bot.send_animation(self.update.chat.id, animation) + } + + pub fn answer_document(&self, document: InputFile) -> SendDocument { + self.bot.send_document(self.update.chat.id, document) + } + + pub fn answer_video(&self, video: InputFile) -> SendVideo { + self.bot.send_video(self.update.chat.id, video) + } + + pub fn answer_voice(&self, voice: InputFile) -> SendVoice { + self.bot.send_voice(self.update.chat.id, voice) + } + + pub fn answer_media_group(&self, media_group: T) -> SendMediaGroup + where + T: Into>, + { + self.bot.send_media_group(self.update.chat.id, media_group) + } + + pub fn answer_location( + &self, + latitude: f32, + longitude: f32, + ) -> SendLocation { + self.bot + .send_location(self.update.chat.id, latitude, longitude) + } + + pub fn answer_venue( + &self, + latitude: f32, + longitude: f32, + title: T, + address: U, + ) -> SendVenue + where + T: Into, + U: Into, + { + self.bot.send_venue( + self.update.chat.id, + latitude, + longitude, + title, + address, + ) + } + + pub fn answer_video_note(&self, video_note: InputFile) -> SendVideoNote { + self.bot.send_video_note(self.update.chat.id, video_note) + } + + pub fn answer_contact( + &self, + phone_number: T, + first_name: U, + ) -> SendContact + where + T: Into, + U: Into, + { + self.bot + .send_contact(self.chat_id(), phone_number, first_name) + } + + pub fn answer_sticker(&self, sticker: InputFile) -> SendSticker { + self.bot.send_sticker(self.update.chat.id, sticker) + } + + pub fn forward_to(&self, chat_id: T) -> ForwardMessage + where + T: Into, + { + self.bot + .forward_message(chat_id, self.update.chat.id, self.update.id) + } + + pub fn edit_message_text(&self, text: T) -> EditMessageText + where + T: Into, + { + self.bot.edit_message_text( + ChatOrInlineMessage::Chat { + chat_id: self.update.chat.id.into(), + message_id: self.update.id, + }, + text, + ) + } + + pub fn edit_message_caption(&self) -> EditMessageCaption { + self.bot.edit_message_caption(ChatOrInlineMessage::Chat { + chat_id: self.update.chat.id.into(), + message_id: self.update.id, + }) + } + + pub fn delete_message(&self) -> DeleteMessage { + self.bot.delete_message(self.update.chat.id, self.update.id) + } + + pub fn pin_message(&self) -> PinChatMessage { + self.bot + .pin_chat_message(self.update.chat.id, self.update.id) } } From 4a7c31fec73647f5c977090f0cbe8d878c107296 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 03:46:33 +0600 Subject: [PATCH 37/91] Use Arc instead of BotWrapper --- src/bot/api.rs | 345 +++++++++++------- src/requests/all/add_sticker_to_set.rs | 16 +- src/requests/all/answer_callback_query.rs | 16 +- src/requests/all/answer_inline_query.rs | 16 +- src/requests/all/answer_pre_checkout_query.rs | 16 +- src/requests/all/answer_shipping_query.rs | 17 +- src/requests/all/bot_wrapper.rs | 33 -- src/requests/all/create_new_sticker_set.rs | 16 +- src/requests/all/delete_chat_photo.rs | 19 +- src/requests/all/delete_chat_sticker_set.rs | 19 +- src/requests/all/delete_message.rs | 16 +- src/requests/all/delete_sticker_from_set.rs | 19 +- src/requests/all/delete_webhook.rs | 18 +- src/requests/all/edit_message_caption.rs | 16 +- .../all/edit_message_live_location.rs | 16 +- src/requests/all/edit_message_media.rs | 16 +- src/requests/all/edit_message_reply_markup.rs | 16 +- src/requests/all/edit_message_text.rs | 16 +- src/requests/all/export_chat_invite_link.rs | 19 +- src/requests/all/forward_message.rs | 16 +- src/requests/all/get_chat.rs | 19 +- src/requests/all/get_chat_administrators.rs | 19 +- src/requests/all/get_chat_member.rs | 16 +- src/requests/all/get_chat_members_count.rs | 19 +- src/requests/all/get_file.rs | 16 +- src/requests/all/get_game_high_scores.rs | 16 +- src/requests/all/get_me.rs | 18 +- src/requests/all/get_sticker_set.rs | 19 +- src/requests/all/get_updates.rs | 16 +- src/requests/all/get_user_profile_photos.rs | 16 +- src/requests/all/get_webhook_info.rs | 18 +- src/requests/all/kick_chat_member.rs | 16 +- src/requests/all/leave_chat.rs | 19 +- src/requests/all/mod.rs | 3 - src/requests/all/pin_chat_message.rs | 16 +- src/requests/all/promote_chat_member.rs | 16 +- src/requests/all/restrict_chat_member.rs | 16 +- src/requests/all/send_animation.rs | 20 +- src/requests/all/send_audio.rs | 16 +- src/requests/all/send_chat_action.rs | 18 +- src/requests/all/send_contact.rs | 16 +- src/requests/all/send_document.rs | 16 +- src/requests/all/send_game.rs | 20 +- src/requests/all/send_invoice.rs | 16 +- src/requests/all/send_location.rs | 16 +- src/requests/all/send_media_group.rs | 16 +- src/requests/all/send_message.rs | 16 +- src/requests/all/send_photo.rs | 16 +- src/requests/all/send_poll.rs | 16 +- src/requests/all/send_sticker.rs | 16 +- src/requests/all/send_venue.rs | 16 +- src/requests/all/send_video.rs | 16 +- src/requests/all/send_video_note.rs | 16 +- src/requests/all/send_voice.rs | 16 +- .../set_chat_administrator_custom_title.rs | 16 +- src/requests/all/set_chat_description.rs | 16 +- src/requests/all/set_chat_permissions.rs | 16 +- src/requests/all/set_chat_photo.rs | 16 +- src/requests/all/set_chat_sticker_set.rs | 16 +- src/requests/all/set_chat_title.rs | 16 +- src/requests/all/set_game_score.rs | 16 +- .../all/set_sticker_position_in_set.rs | 16 +- src/requests/all/set_webhook.rs | 16 +- .../all/stop_message_live_location.rs | 16 +- src/requests/all/stop_poll.rs | 16 +- src/requests/all/unban_chat_member.rs | 16 +- src/requests/all/unpin_chat_message.rs | 19 +- src/requests/all/upload_sticker_file.rs | 16 +- 68 files changed, 745 insertions(+), 723 deletions(-) delete mode 100644 src/requests/all/bot_wrapper.rs diff --git a/src/bot/api.rs b/src/bot/api.rs index fe44008d..a1058d49 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -25,6 +25,7 @@ use crate::{ }, Bot, }; +use std::sync::Arc; impl Bot { /// Use this method to receive incoming updates using long polling ([wiki]). @@ -37,8 +38,8 @@ impl Bot { /// [The official docs](https://core.telegram.org/bots/api#getupdates). /// /// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling - pub fn get_updates(&self) -> GetUpdates { - GetUpdates::new(self) + pub fn get_updates(self: &Arc) -> GetUpdates { + GetUpdates::new(Arc::clone(self)) } /// Use this method to specify a url and receive incoming updates via an @@ -62,11 +63,11 @@ impl Bot { /// Use an empty string to remove webhook integration. /// /// [`Update`]: crate::types::Update - pub fn set_webhook(&self, url: U) -> SetWebhook + pub fn set_webhook(self: &Arc, url: U) -> SetWebhook where U: Into, { - SetWebhook::new(self, url) + SetWebhook::new(Arc::clone(self), url) } /// Use this method to remove webhook integration if you decide to switch @@ -75,8 +76,8 @@ impl Bot { /// [The official docs](https://core.telegram.org/bots/api#deletewebhook). /// /// [Bot::get_updates]: crate::Bot::get_updates - pub fn delete_webhook(&self) -> DeleteWebhook { - DeleteWebhook::new(self) + pub fn delete_webhook(self: &Arc) -> DeleteWebhook { + DeleteWebhook::new(Arc::clone(self)) } /// Use this method to get current webhook status. @@ -87,16 +88,16 @@ impl Bot { /// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). /// /// [`Bot::get_updates`]: crate::Bot::get_updates - pub fn get_webhook_info(&self) -> GetWebhookInfo { - GetWebhookInfo::new(self) + pub fn get_webhook_info(self: &Arc) -> GetWebhookInfo { + GetWebhookInfo::new(Arc::clone(self)) } /// A simple method for testing your bot's auth token. Requires no /// parameters. /// /// [The official docs](https://core.telegram.org/bots/api#getme). - pub fn get_me(&self) -> GetMe { - GetMe::new(self) + pub fn get_me(self: &Arc) -> GetMe { + GetMe::new(Arc::clone(self)) } /// Use this method to send text messages. @@ -107,12 +108,16 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). /// - `text`: Text of the message to be sent. - pub fn send_message(&self, chat_id: C, text: T) -> SendMessage + pub fn send_message( + self: &Arc, + chat_id: C, + text: T, + ) -> SendMessage where C: Into, T: Into, { - SendMessage::new(self, chat_id, text) + SendMessage::new(Arc::clone(self), chat_id, text) } /// Use this method to forward messages of any kind. @@ -130,7 +135,7 @@ impl Bot { /// /// [`from_chat_id`]: ForwardMessage::from_chat_id pub fn forward_message( - &self, + self: &Arc, chat_id: C, from_chat_id: F, message_id: i32, @@ -139,7 +144,7 @@ impl Bot { C: Into, F: Into, { - ForwardMessage::new(self, chat_id, from_chat_id, message_id) + ForwardMessage::new(Arc::clone(self), chat_id, from_chat_id, message_id) } /// Use this method to send photos. @@ -161,11 +166,15 @@ impl Bot { /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn send_photo(&self, chat_id: C, photo: InputFile) -> SendPhoto + pub fn send_photo( + self: &Arc, + chat_id: C, + photo: InputFile, + ) -> SendPhoto where C: Into, { - SendPhoto::new(self, chat_id, photo) + SendPhoto::new(Arc::clone(self), chat_id, photo) } /// @@ -173,11 +182,15 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn send_audio(&self, chat_id: C, audio: InputFile) -> SendAudio + pub fn send_audio( + self: &Arc, + chat_id: C, + audio: InputFile, + ) -> SendAudio where C: Into, { - SendAudio::new(self, chat_id, audio) + SendAudio::new(Arc::clone(self), chat_id, audio) } /// Use this method to send general files. @@ -199,14 +212,14 @@ impl Bot { /// /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files pub fn send_document( - &self, + self: &Arc, chat_id: C, document: InputFile, ) -> SendDocument where C: Into, { - SendDocument::new(self, chat_id, document) + SendDocument::new(Arc::clone(self), chat_id, document) } /// Use this method to send video files, Telegram clients support mp4 videos @@ -230,11 +243,15 @@ impl Bot { /// [`InputFile::File`]: crate::types::InputFile::File /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId - pub fn send_video(&self, chat_id: C, video: InputFile) -> SendVideo + pub fn send_video( + self: &Arc, + chat_id: C, + video: InputFile, + ) -> SendVideo where C: Into, { - SendVideo::new(self, chat_id, video) + SendVideo::new(Arc::clone(self), chat_id, video) } /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video @@ -250,14 +267,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `animation`: Animation to send. pub fn send_animation( - &self, + self: &Arc, chat_id: C, animation: InputFile, ) -> SendAnimation where C: Into, { - SendAnimation::new(self, chat_id, animation) + SendAnimation::new(Arc::clone(self), chat_id, animation) } /// Use this method to send audio files, if you want Telegram clients to @@ -287,11 +304,15 @@ impl Bot { /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn send_voice(&self, chat_id: C, voice: InputFile) -> SendVoice + pub fn send_voice( + self: &Arc, + chat_id: C, + voice: InputFile, + ) -> SendVoice where C: Into, { - SendVoice::new(self, chat_id, voice) + SendVoice::new(Arc::clone(self), chat_id, voice) } /// As of [v.4.0], Telegram clients support rounded square mp4 videos of up @@ -315,14 +336,14 @@ impl Bot { /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files pub fn send_video_note( - &self, + self: &Arc, chat_id: C, video_note: InputFile, ) -> SendVideoNote where C: Into, { - SendVideoNote::new(self, chat_id, video_note) + SendVideoNote::new(Arc::clone(self), chat_id, video_note) } /// Use this method to send a group of photos or videos as an album. @@ -334,12 +355,16 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `media`: A JSON-serialized array describing photos and videos to be /// sent, must include 2–10 items. - pub fn send_media_group(&self, chat_id: C, media: M) -> SendMediaGroup + pub fn send_media_group( + self: &Arc, + chat_id: C, + media: M, + ) -> SendMediaGroup where C: Into, M: Into>, { - SendMediaGroup::new(self, chat_id, media) + SendMediaGroup::new(Arc::clone(self), chat_id, media) } /// Use this method to send point on the map. @@ -352,7 +377,7 @@ impl Bot { /// - `latitude`: Latitude of the location. /// - `longitude`: Latitude of the location. pub fn send_location( - &self, + self: &Arc, chat_id: C, latitude: f32, longitude: f32, @@ -360,7 +385,7 @@ impl Bot { where C: Into, { - SendLocation::new(self, chat_id, latitude, longitude) + SendLocation::new(Arc::clone(self), chat_id, latitude, longitude) } /// Use this method to edit live location messages. @@ -379,13 +404,13 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_live_location( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, latitude: f32, longitude: f32, ) -> EditMessageLiveLocation { EditMessageLiveLocation::new( - self, + Arc::clone(self), chat_or_inline_message, latitude, longitude, @@ -403,10 +428,10 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn stop_message_live_location( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> StopMessageLiveLocation { - StopMessageLiveLocation::new(self, chat_or_inline_message) + StopMessageLiveLocation::new(Arc::clone(self), chat_or_inline_message) } /// Use this method to send information about a venue. @@ -421,7 +446,7 @@ impl Bot { /// - `title`: Name of the venue. /// - `address`: Address of the venue. pub fn send_venue( - &self, + self: &Arc, chat_id: C, latitude: f32, longitude: f32, @@ -433,7 +458,14 @@ impl Bot { T: Into, A: Into, { - SendVenue::new(self, chat_id, latitude, longitude, title, address) + SendVenue::new( + Arc::clone(self), + chat_id, + latitude, + longitude, + title, + address, + ) } /// Use this method to send phone contacts. @@ -447,7 +479,7 @@ impl Bot { /// - `phone_number`: Contact's phone number. /// - `first_name`: Contact's first name. pub fn send_contact( - &self, + self: &Arc, chat_id: C, phone_number: P, first_name: F, @@ -457,7 +489,7 @@ impl Bot { P: Into, F: Into, { - SendContact::new(self, chat_id, phone_number, first_name) + SendContact::new(Arc::clone(self), chat_id, phone_number, first_name) } /// Use this method to send a native poll. A native poll can't be sent to a @@ -473,7 +505,7 @@ impl Bot { /// - `options`: List of answer options, 2-10 strings 1-100 characters /// each. pub fn send_poll( - &self, + self: &Arc, chat_id: C, question: Q, options: O, @@ -483,7 +515,7 @@ impl Bot { Q: Into, O: Into>, { - SendPoll::new(self, chat_id, question, options) + SendPoll::new(Arc::clone(self), chat_id, question, options) } /// Use this method when you need to tell the user that something is @@ -509,14 +541,14 @@ impl Bot { /// [ImageBot]: https://t.me/imagebot /// [`Bot::send_chat_action`]: crate::Bot::send_chat_action pub fn send_chat_action( - &self, + self: &Arc, chat_id: C, action: SendChatActionKind, ) -> SendChatAction where C: Into, { - SendChatAction::new(self, chat_id, action) + SendChatAction::new(Arc::clone(self), chat_id, action) } /// Use this method to get a list of profile pictures for a user. @@ -526,10 +558,10 @@ impl Bot { /// # Params /// - `user_id`: Unique identifier of the target user. pub fn get_user_profile_photos( - &self, + self: &Arc, user_id: i32, ) -> GetUserProfilePhotos { - GetUserProfilePhotos::new(self, user_id) + GetUserProfilePhotos::new(Arc::clone(self), user_id) } /// Use this method to get basic info about a file and prepare it for @@ -554,11 +586,11 @@ impl Bot { /// /// [`File`]: crate::types::file /// [`GetFile`]: self::GetFile - pub fn get_file(&self, file_id: F) -> GetFile + pub fn get_file(self: &Arc, file_id: F) -> GetFile where F: Into, { - GetFile::new(self, file_id) + GetFile::new(Arc::clone(self), file_id) } /// Use this method to kick a user from a group, a supergroup or a channel. @@ -577,14 +609,14 @@ impl Bot { /// /// [unbanned]: crate::Bot::unban_chat_member pub fn kick_chat_member( - &self, + self: &Arc, chat_id: C, user_id: i32, ) -> KickChatMember where C: Into, { - KickChatMember::new(self, chat_id, user_id) + KickChatMember::new(Arc::clone(self), chat_id, user_id) } /// Use this method to unban a previously kicked user in a supergroup or @@ -599,14 +631,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `user_id`: Unique identifier of the target user. pub fn unban_chat_member( - &self, + self: &Arc, chat_id: C, user_id: i32, ) -> UnbanChatMember where C: Into, { - UnbanChatMember::new(self, chat_id, user_id) + UnbanChatMember::new(Arc::clone(self), chat_id, user_id) } /// Use this method to restrict a user in a supergroup. @@ -623,7 +655,7 @@ impl Bot { /// - `user_id`: Unique identifier of the target user. /// - `permissions`: New user permissions. pub fn restrict_chat_member( - &self, + self: &Arc, chat_id: C, user_id: i32, permissions: ChatPermissions, @@ -631,7 +663,7 @@ impl Bot { where C: Into, { - RestrictChatMember::new(self, chat_id, user_id, permissions) + RestrictChatMember::new(Arc::clone(self), chat_id, user_id, permissions) } /// Use this method to promote or demote a user in a supergroup or a @@ -648,14 +680,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `user_id`: Unique identifier of the target user. pub fn promote_chat_member( - &self, + self: &Arc, chat_id: C, user_id: i32, ) -> PromoteChatMember where C: Into, { - PromoteChatMember::new(self, chat_id, user_id) + PromoteChatMember::new(Arc::clone(self), chat_id, user_id) } /// Use this method to set default chat permissions for all members. @@ -670,14 +702,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `permissions`: New default chat permissions. pub fn set_chat_permissions( - &self, + self: &Arc, chat_id: C, permissions: ChatPermissions, ) -> SetChatPermissions where C: Into, { - SetChatPermissions::new(self, chat_id, permissions) + SetChatPermissions::new(Arc::clone(self), chat_id, permissions) } /// Use this method to generate a new invite link for a chat; any previously @@ -703,11 +735,14 @@ impl Bot { /// /// [`Bot::export_chat_invite_link`]: crate::Bot::export_chat_invite_link /// [`Bot::get_chat`]: crate::Bot::get_chat - pub fn export_chat_invite_link(&self, chat_id: C) -> ExportChatInviteLink + pub fn export_chat_invite_link( + self: &Arc, + chat_id: C, + ) -> ExportChatInviteLink where C: Into, { - ExportChatInviteLink::new(self, chat_id) + ExportChatInviteLink::new(Arc::clone(self), chat_id) } /// Use this method to set a new profile photo for the chat. @@ -723,14 +758,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `photo`: New chat photo, uploaded using `multipart/form-data`. pub fn set_chat_photo( - &self, + self: &Arc, chat_id: C, photo: InputFile, ) -> SetChatPhoto where C: Into, { - SetChatPhoto::new(self, chat_id, photo) + SetChatPhoto::new(Arc::clone(self), chat_id, photo) } /// Use this method to delete a chat photo. Photos can't be changed for @@ -742,11 +777,11 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn delete_chat_photo(&self, chat_id: C) -> DeleteChatPhoto + pub fn delete_chat_photo(self: &Arc, chat_id: C) -> DeleteChatPhoto where C: Into, { - DeleteChatPhoto::new(self, chat_id) + DeleteChatPhoto::new(Arc::clone(self), chat_id) } /// Use this method to change the title of a chat. @@ -761,12 +796,16 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). /// - `title`: New chat title, 1-255 characters. - pub fn set_chat_title(&self, chat_id: C, title: T) -> SetChatTitle + pub fn set_chat_title( + self: &Arc, + chat_id: C, + title: T, + ) -> SetChatTitle where C: Into, T: Into, { - SetChatTitle::new(self, chat_id, title) + SetChatTitle::new(Arc::clone(self), chat_id, title) } /// Use this method to change the description of a group, a supergroup or a @@ -780,11 +819,14 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn set_chat_description(&self, chat_id: C) -> SetChatDescription + pub fn set_chat_description( + self: &Arc, + chat_id: C, + ) -> SetChatDescription where C: Into, { - SetChatDescription::new(self, chat_id) + SetChatDescription::new(Arc::clone(self), chat_id) } /// Use this method to pin a message in a group, a supergroup, or a channel. @@ -800,14 +842,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `message_id`: Identifier of a message to pin. pub fn pin_chat_message( - &self, + self: &Arc, chat_id: C, message_id: i32, ) -> PinChatMessage where C: Into, { - PinChatMessage::new(self, chat_id, message_id) + PinChatMessage::new(Arc::clone(self), chat_id, message_id) } /// Use this method to unpin a message in a group, a supergroup, or a @@ -822,11 +864,14 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn unpin_chat_message(&self, chat_id: C) -> UnpinChatMessage + pub fn unpin_chat_message( + self: &Arc, + chat_id: C, + ) -> UnpinChatMessage where C: Into, { - UnpinChatMessage::new(self, chat_id) + UnpinChatMessage::new(Arc::clone(self), chat_id) } /// Use this method for your bot to leave a group, supergroup or channel. @@ -836,11 +881,11 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn leave_chat(&self, chat_id: C) -> LeaveChat + pub fn leave_chat(self: &Arc, chat_id: C) -> LeaveChat where C: Into, { - LeaveChat::new(self, chat_id) + LeaveChat::new(Arc::clone(self), chat_id) } /// Use this method to get up to date information about the chat (current @@ -852,11 +897,11 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat(&self, chat_id: C) -> GetChat + pub fn get_chat(self: &Arc, chat_id: C) -> GetChat where C: Into, { - GetChat::new(self, chat_id) + GetChat::new(Arc::clone(self), chat_id) } /// Use this method to get a list of administrators in a chat. @@ -870,13 +915,13 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). pub fn get_chat_administrators( - &self, + self: &Arc, chat_id: C, ) -> GetChatAdministrators where C: Into, { - GetChatAdministrators::new(self, chat_id) + GetChatAdministrators::new(Arc::clone(self), chat_id) } /// Use this method to get the number of members in a chat. @@ -886,11 +931,14 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat_members_count(&self, chat_id: C) -> GetChatMembersCount + pub fn get_chat_members_count( + self: &Arc, + chat_id: C, + ) -> GetChatMembersCount where C: Into, { - GetChatMembersCount::new(self, chat_id) + GetChatMembersCount::new(Arc::clone(self), chat_id) } /// Use this method to get information about a member of a chat. @@ -901,11 +949,15 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). /// - `user_id`: Unique identifier of the target user. - pub fn get_chat_member(&self, chat_id: C, user_id: i32) -> GetChatMember + pub fn get_chat_member( + self: &Arc, + chat_id: C, + user_id: i32, + ) -> GetChatMember where C: Into, { - GetChatMember::new(self, chat_id, user_id) + GetChatMember::new(Arc::clone(self), chat_id, user_id) } /// Use this method to set a new group sticker set for a supergroup. @@ -923,7 +975,7 @@ impl Bot { /// - `sticker_set_name`: Name of the sticker set to be set as the group /// sticker set. pub fn set_chat_sticker_set( - &self, + self: &Arc, chat_id: C, sticker_set_name: S, ) -> SetChatStickerSet @@ -931,7 +983,7 @@ impl Bot { C: Into, S: Into, { - SetChatStickerSet::new(self, chat_id, sticker_set_name) + SetChatStickerSet::new(Arc::clone(self), chat_id, sticker_set_name) } /// Use this method to delete a group sticker set from a supergroup. @@ -948,11 +1000,14 @@ impl Bot { /// target supergroup (in the format `@supergroupusername`). /// /// [`Bot::get_chat`]: crate::Bot::get_chat - pub fn delete_chat_sticker_set(&self, chat_id: C) -> DeleteChatStickerSet + pub fn delete_chat_sticker_set( + self: &Arc, + chat_id: C, + ) -> DeleteChatStickerSet where C: Into, { - DeleteChatStickerSet::new(self, chat_id) + DeleteChatStickerSet::new(Arc::clone(self), chat_id) } /// Use this method to send answers to callback queries sent from [inline @@ -968,13 +1023,13 @@ impl Bot { /// /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub fn answer_callback_query( - &self, + self: &Arc, callback_query_id: C, ) -> AnswerCallbackQuery where C: Into, { - AnswerCallbackQuery::new(self, callback_query_id) + AnswerCallbackQuery::new(Arc::clone(self), callback_query_id) } /// Use this method to edit text and game messages. @@ -990,14 +1045,14 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_text( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, text: T, ) -> EditMessageText where T: Into, { - EditMessageText::new(self, chat_or_inline_message, text) + EditMessageText::new(Arc::clone(self), chat_or_inline_message, text) } /// Use this method to edit captions of messages. @@ -1010,10 +1065,10 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_caption( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> EditMessageCaption { - EditMessageCaption::new(self, chat_or_inline_message) + EditMessageCaption::new(Arc::clone(self), chat_or_inline_message) } /// Use this method to edit animation, audio, document, photo, or video @@ -1031,11 +1086,11 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_media( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, media: InputMedia, ) -> EditMessageMedia { - EditMessageMedia::new(self, chat_or_inline_message, media) + EditMessageMedia::new(Arc::clone(self), chat_or_inline_message, media) } /// Use this method to edit only the reply markup of messages. @@ -1048,10 +1103,10 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_reply_markup( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> EditMessageReplyMarkup { - EditMessageReplyMarkup::new(self, chat_or_inline_message) + EditMessageReplyMarkup::new(Arc::clone(self), chat_or_inline_message) } /// Use this method to stop a poll which was sent by the bot. @@ -1063,11 +1118,15 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target channel (in the format `@channelusername`). /// - `message_id`: Identifier of the original message with the poll. - pub fn stop_poll(&self, chat_id: C, message_id: i32) -> StopPoll + pub fn stop_poll( + self: &Arc, + chat_id: C, + message_id: i32, + ) -> StopPoll where C: Into, { - StopPoll::new(self, chat_id, message_id) + StopPoll::new(Arc::clone(self), chat_id, message_id) } /// Use this method to delete a message, including service messages. @@ -1091,14 +1150,14 @@ impl Bot { /// target channel (in the format `@channelusername`). /// - `message_id`: Identifier of the message to delete. pub fn delete_message( - &self, + self: &Arc, chat_id: C, message_id: i32, ) -> DeleteMessage where C: Into, { - DeleteMessage::new(self, chat_id, message_id) + DeleteMessage::new(Arc::clone(self), chat_id, message_id) } /// Use this method to send static .WEBP or [animated] .TGS stickers. @@ -1120,11 +1179,15 @@ impl Bot { /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn send_sticker(&self, chat_id: C, sticker: InputFile) -> SendSticker + pub fn send_sticker( + self: &Arc, + chat_id: C, + sticker: InputFile, + ) -> SendSticker where C: Into, { - SendSticker::new(self, chat_id, sticker) + SendSticker::new(Arc::clone(self), chat_id, sticker) } /// Use this method to get a sticker set. @@ -1133,11 +1196,11 @@ impl Bot { /// /// # Params /// - `name`: Name of the sticker set. - pub fn get_sticker_set(&self, name: N) -> GetStickerSet + pub fn get_sticker_set(self: &Arc, name: N) -> GetStickerSet where N: Into, { - GetStickerSet::new(self, name) + GetStickerSet::new(Arc::clone(self), name) } /// Use this method to upload a .png file with a sticker for later use in @@ -1157,11 +1220,11 @@ impl Bot { /// [`Bot::create_new_sticker_set`]: crate::Bot::create_new_sticker_set /// [`Bot::add_sticker_to_set`]: crate::Bot::add_sticker_to_set pub fn upload_sticker_file( - &self, + self: &Arc, user_id: i32, png_sticker: InputFile, ) -> UploadStickerFile { - UploadStickerFile::new(self, user_id, png_sticker) + UploadStickerFile::new(Arc::clone(self), user_id, png_sticker) } /// Use this method to create new sticker set owned by a user. The bot will @@ -1193,7 +1256,7 @@ impl Bot { /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId pub fn create_new_sticker_set( - &self, + self: &Arc, user_id: i32, name: N, title: T, @@ -1206,7 +1269,7 @@ impl Bot { E: Into, { CreateNewStickerSet::new( - self, + Arc::clone(self), user_id, name, title, @@ -1236,7 +1299,7 @@ impl Bot { /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId pub fn add_sticker_to_set( - &self, + self: &Arc, user_id: i32, name: N, png_sticker: InputFile, @@ -1246,7 +1309,13 @@ impl Bot { N: Into, E: Into, { - AddStickerToSet::new(self, user_id, name, png_sticker, emojis) + AddStickerToSet::new( + Arc::clone(self), + user_id, + name, + png_sticker, + emojis, + ) } /// Use this method to move a sticker in a set created by the bot to a @@ -1258,14 +1327,14 @@ impl Bot { /// - `sticker`: File identifier of the sticker. /// - `position`: New sticker position in the set, zero-based. pub fn set_sticker_position_in_set( - &self, + self: &Arc, sticker: S, position: i32, ) -> SetStickerPositionInSet where S: Into, { - SetStickerPositionInSet::new(self, sticker, position) + SetStickerPositionInSet::new(Arc::clone(self), sticker, position) } /// Use this method to delete a sticker from a set created by the bot. @@ -1274,11 +1343,14 @@ impl Bot { /// /// # Params /// - `sticker`: File identifier of the sticker. - pub fn delete_sticker_from_set(&self, sticker: S) -> DeleteStickerFromSet + pub fn delete_sticker_from_set( + self: &Arc, + sticker: S, + ) -> DeleteStickerFromSet where S: Into, { - DeleteStickerFromSet::new(self, sticker) + DeleteStickerFromSet::new(Arc::clone(self), sticker) } /// Use this method to send answers to an inline query. @@ -1291,7 +1363,7 @@ impl Bot { /// - `inline_query_id`: Unique identifier for the answered query. /// - `results`: A JSON-serialized array of results for the inline query. pub fn answer_inline_query( - &self, + self: &Arc, inline_query_id: I, results: R, ) -> AnswerInlineQuery @@ -1299,7 +1371,7 @@ impl Bot { I: Into, R: Into>, { - AnswerInlineQuery::new(self, inline_query_id, results) + AnswerInlineQuery::new(Arc::clone(self), inline_query_id, results) } /// Use this method to send invoices. @@ -1325,7 +1397,7 @@ impl Bot { /// [@Botfather]: https://t.me/botfather #[allow(clippy::too_many_arguments)] pub fn send_invoice( - &self, + self: &Arc, chat_id: i32, title: T, description: D, @@ -1345,7 +1417,7 @@ impl Bot { Pr: Into>, { SendInvoice::new( - self, + Arc::clone(self), chat_id, title, description, @@ -1373,14 +1445,14 @@ impl Bot { /// /// [`Update`]: crate::types::Update pub fn answer_shipping_query( - &self, + self: &Arc, shipping_query_id: S, ok: bool, ) -> AnswerShippingQuery where S: Into, { - AnswerShippingQuery::new(self, shipping_query_id, ok) + AnswerShippingQuery::new(Arc::clone(self), shipping_query_id, ok) } /// Once the user has confirmed their payment and shipping details, the Bot @@ -1400,14 +1472,14 @@ impl Bot { /// /// [`Update`]: crate::types::Update pub fn answer_pre_checkout_query

( - &self, + self: &Arc, pre_checkout_query_id: P, ok: bool, ) -> AnswerPreCheckoutQuery where P: Into, { - AnswerPreCheckoutQuery::new(self, pre_checkout_query_id, ok) + AnswerPreCheckoutQuery::new(Arc::clone(self), pre_checkout_query_id, ok) } /// Use this method to send a game. @@ -1420,11 +1492,15 @@ impl Bot { /// identifier for the game. Set up your games via [@Botfather]. /// /// [@Botfather]: https://t.me/botfather - pub fn send_game(&self, chat_id: i32, game_short_name: G) -> SendGame + pub fn send_game( + self: &Arc, + chat_id: i32, + game_short_name: G, + ) -> SendGame where G: Into, { - SendGame::new(self, chat_id, game_short_name) + SendGame::new(Arc::clone(self), chat_id, game_short_name) } /// Use this method to set the score of the specified user in a game. @@ -1443,12 +1519,17 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn set_game_score( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, user_id: i32, score: i32, ) -> SetGameScore { - SetGameScore::new(self, chat_or_inline_message, user_id, score) + SetGameScore::new( + Arc::clone(self), + chat_or_inline_message, + user_id, + score, + ) } /// Use this method to get data for high score tables. @@ -1467,11 +1548,15 @@ impl Bot { /// # Params /// - `user_id`: Target user id. pub fn get_game_high_scores( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, user_id: i32, ) -> GetGameHighScores { - GetGameHighScores::new(self, chat_or_inline_message, user_id) + GetGameHighScores::new( + Arc::clone(self), + chat_or_inline_message, + user_id, + ) } /// Use this method to set a custom title for an administrator in a @@ -1486,7 +1571,7 @@ impl Bot { /// - `custom_title`: New custom title for the administrator; 0-16 /// characters, emoji are not allowed. pub fn set_chat_administrator_custom_title( - &self, + self: &Arc, chat_id: C, user_id: i32, custom_title: CT, @@ -1496,7 +1581,7 @@ impl Bot { CT: Into, { SetChatAdministratorCustomTitle::new( - self, + Arc::clone(self), chat_id, user_id, custom_title, diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs index 760f4b54..119bd7e1 100644 --- a/src/requests/all/add_sticker_to_set.rs +++ b/src/requests/all/add_sticker_to_set.rs @@ -5,15 +5,15 @@ use crate::{ Bot, }; -use super::BotWrapper; use crate::requests::{Request, ResponseResult}; +use std::sync::Arc; /// Use this method to add a new sticker to a set created by the bot. /// /// [The official docs](https://core.telegram.org/bots/api#addstickertoset). -#[derive(PartialEq, Debug, Clone)] -pub struct AddStickerToSet<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct AddStickerToSet { + bot: Arc, user_id: i32, name: String, png_sticker: InputFile, @@ -22,7 +22,7 @@ pub struct AddStickerToSet<'a> { } #[async_trait::async_trait] -impl Request for AddStickerToSet<'_> { +impl Request for AddStickerToSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -47,9 +47,9 @@ impl Request for AddStickerToSet<'_> { } } -impl<'a> AddStickerToSet<'a> { +impl AddStickerToSet { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, user_id: i32, name: N, png_sticker: InputFile, @@ -60,7 +60,7 @@ impl<'a> AddStickerToSet<'a> { E: Into, { Self { - bot: BotWrapper(bot), + bot, user_id, name: name.into(), png_sticker, diff --git a/src/requests/all/answer_callback_query.rs b/src/requests/all/answer_callback_query.rs index 38f0c856..a636e314 100644 --- a/src/requests/all/answer_callback_query.rs +++ b/src/requests/all/answer_callback_query.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Use this method to send answers to callback queries sent from [inline /// keyboards]. @@ -18,10 +18,10 @@ use crate::{ /// /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct AnswerCallbackQuery<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct AnswerCallbackQuery { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, callback_query_id: String, text: Option, show_alert: Option, @@ -30,7 +30,7 @@ pub struct AnswerCallbackQuery<'a> { } #[async_trait::async_trait] -impl Request for AnswerCallbackQuery<'_> { +impl Request for AnswerCallbackQuery { type Output = True; async fn send(&self) -> ResponseResult { @@ -44,14 +44,14 @@ impl Request for AnswerCallbackQuery<'_> { } } -impl<'a> AnswerCallbackQuery<'a> { - pub(crate) fn new(bot: &'a Bot, callback_query_id: C) -> Self +impl AnswerCallbackQuery { + pub(crate) fn new(bot: Arc, callback_query_id: C) -> Self where C: Into, { let callback_query_id = callback_query_id.into(); Self { - bot: BotWrapper(bot), + bot, callback_query_id, text: None, show_alert: None, diff --git a/src/requests/all/answer_inline_query.rs b/src/requests/all/answer_inline_query.rs index 1236423a..ad75ea06 100644 --- a/src/requests/all/answer_inline_query.rs +++ b/src/requests/all/answer_inline_query.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{InlineQueryResult, True}, Bot, }; +use std::sync::Arc; /// Use this method to send answers to an inline query. /// @@ -14,10 +14,10 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). #[serde_with_macros::skip_serializing_none] -#[derive(PartialEq, Debug, Clone, Serialize)] -pub struct AnswerInlineQuery<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct AnswerInlineQuery { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, inline_query_id: String, results: Vec, cache_time: Option, @@ -28,7 +28,7 @@ pub struct AnswerInlineQuery<'a> { } #[async_trait::async_trait] -impl Request for AnswerInlineQuery<'_> { +impl Request for AnswerInlineQuery { type Output = True; async fn send(&self) -> ResponseResult { @@ -42,9 +42,9 @@ impl Request for AnswerInlineQuery<'_> { } } -impl<'a> AnswerInlineQuery<'a> { +impl AnswerInlineQuery { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, inline_query_id: I, results: R, ) -> Self @@ -55,7 +55,7 @@ impl<'a> AnswerInlineQuery<'a> { let inline_query_id = inline_query_id.into(); let results = results.into(); Self { - bot: BotWrapper(bot), + bot, inline_query_id, results, cache_time: None, diff --git a/src/requests/all/answer_pre_checkout_query.rs b/src/requests/all/answer_pre_checkout_query.rs index 8d8dd0fb..cdff1e28 100644 --- a/src/requests/all/answer_pre_checkout_query.rs +++ b/src/requests/all/answer_pre_checkout_query.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Once the user has confirmed their payment and shipping details, the Bot API /// sends the final confirmation in the form of an [`Update`] with the field @@ -18,17 +18,17 @@ use crate::{ /// /// [`Update`]: crate::types::Update #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct AnswerPreCheckoutQuery<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct AnswerPreCheckoutQuery { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, pre_checkout_query_id: String, ok: bool, error_message: Option, } #[async_trait::async_trait] -impl Request for AnswerPreCheckoutQuery<'_> { +impl Request for AnswerPreCheckoutQuery { type Output = True; async fn send(&self) -> ResponseResult { @@ -42,9 +42,9 @@ impl Request for AnswerPreCheckoutQuery<'_> { } } -impl<'a> AnswerPreCheckoutQuery<'a> { +impl AnswerPreCheckoutQuery { pub(crate) fn new

( - bot: &'a Bot, + bot: Arc, pre_checkout_query_id: P, ok: bool, ) -> Self @@ -53,7 +53,7 @@ impl<'a> AnswerPreCheckoutQuery<'a> { { let pre_checkout_query_id = pre_checkout_query_id.into(); Self { - bot: BotWrapper(bot), + bot, pre_checkout_query_id, ok, error_message: None, diff --git a/src/requests/all/answer_shipping_query.rs b/src/requests/all/answer_shipping_query.rs index 45435362..452c93ed 100644 --- a/src/requests/all/answer_shipping_query.rs +++ b/src/requests/all/answer_shipping_query.rs @@ -1,12 +1,13 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ShippingOption, True}, Bot, }; +use std::sync::Arc; + /// If you sent an invoice requesting a shipping address and the parameter /// `is_flexible` was specified, the Bot API will send an [`Update`] with a /// shipping_query field to the bot. Use this method to reply to shipping @@ -16,10 +17,10 @@ use crate::{ /// /// [`Update`]: crate::types::Update #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct AnswerShippingQuery<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct AnswerShippingQuery { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, shipping_query_id: String, ok: bool, shipping_options: Option>, @@ -27,7 +28,7 @@ pub struct AnswerShippingQuery<'a> { } #[async_trait::async_trait] -impl Request for AnswerShippingQuery<'_> { +impl Request for AnswerShippingQuery { type Output = True; async fn send(&self) -> ResponseResult { @@ -41,14 +42,14 @@ impl Request for AnswerShippingQuery<'_> { } } -impl<'a> AnswerShippingQuery<'a> { - pub(crate) fn new(bot: &'a Bot, shipping_query_id: S, ok: bool) -> Self +impl AnswerShippingQuery { + pub(crate) fn new(bot: Arc, shipping_query_id: S, ok: bool) -> Self where S: Into, { let shipping_query_id = shipping_query_id.into(); Self { - bot: BotWrapper(bot), + bot, shipping_query_id, ok, shipping_options: None, diff --git a/src/requests/all/bot_wrapper.rs b/src/requests/all/bot_wrapper.rs deleted file mode 100644 index 87857725..00000000 --- a/src/requests/all/bot_wrapper.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::Bot; -use std::ops::Deref; - -/// A wrapper that implements `Clone`, Copy, `PartialEq`, `Eq`, `Debug`, but -/// performs no copying, cloning and comparison. -/// -/// Used in the requests bodies. -#[derive(Debug)] -pub struct BotWrapper<'a>(pub &'a Bot); - -impl PartialEq for BotWrapper<'_> { - fn eq(&self, other: &BotWrapper<'_>) -> bool { - self.0.token() == other.0.token() - } -} - -impl Eq for BotWrapper<'_> {} - -impl<'a> Clone for BotWrapper<'a> { - fn clone(&self) -> BotWrapper<'a> { - Self(self.0) - } -} - -impl Copy for BotWrapper<'_> {} - -impl Deref for BotWrapper<'_> { - type Target = Bot; - - fn deref(&self) -> &Bot { - &self.0 - } -} diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs index a72556f4..cfb98634 100644 --- a/src/requests/all/create_new_sticker_set.rs +++ b/src/requests/all/create_new_sticker_set.rs @@ -1,18 +1,18 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{InputFile, MaskPosition, True}, Bot, }; +use std::sync::Arc; /// Use this method to create new sticker set owned by a user. The bot will be /// able to edit the created sticker set. /// /// [The official docs](https://core.telegram.org/bots/api#createnewstickerset). -#[derive(PartialEq, Debug, Clone)] -pub struct CreateNewStickerSet<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct CreateNewStickerSet { + bot: Arc, user_id: i32, name: String, title: String, @@ -23,7 +23,7 @@ pub struct CreateNewStickerSet<'a> { } #[async_trait::async_trait] -impl Request for CreateNewStickerSet<'_> { +impl Request for CreateNewStickerSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -52,9 +52,9 @@ impl Request for CreateNewStickerSet<'_> { } } -impl<'a> CreateNewStickerSet<'a> { +impl CreateNewStickerSet { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, user_id: i32, name: N, title: T, @@ -67,7 +67,7 @@ impl<'a> CreateNewStickerSet<'a> { E: Into, { Self { - bot: BotWrapper(bot), + bot, user_id, name: name.into(), title: title.into(), diff --git a/src/requests/all/delete_chat_photo.rs b/src/requests/all/delete_chat_photo.rs index af1b330e..d9ab17e1 100644 --- a/src/requests/all/delete_chat_photo.rs +++ b/src/requests/all/delete_chat_photo.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to delete a chat photo. Photos can't be changed for private /// chats. The bot must be an administrator in the chat for this to work and @@ -14,15 +14,15 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteChatPhoto<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteChatPhoto { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for DeleteChatPhoto<'_> { +impl Request for DeleteChatPhoto { type Output = True; async fn send(&self) -> ResponseResult { @@ -36,16 +36,13 @@ impl Request for DeleteChatPhoto<'_> { } } -impl<'a> DeleteChatPhoto<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl DeleteChatPhoto { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target channel diff --git a/src/requests/all/delete_chat_sticker_set.rs b/src/requests/all/delete_chat_sticker_set.rs index b10962dc..0ad7ead9 100644 --- a/src/requests/all/delete_chat_sticker_set.rs +++ b/src/requests/all/delete_chat_sticker_set.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to delete a group sticker set from a supergroup. /// @@ -19,15 +19,15 @@ use crate::{ /// /// [`Bot::get_chat`]: crate::Bot::get_chat #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteChatStickerSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteChatStickerSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for DeleteChatStickerSet<'_> { +impl Request for DeleteChatStickerSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -41,16 +41,13 @@ impl Request for DeleteChatStickerSet<'_> { } } -impl<'a> DeleteChatStickerSet<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl DeleteChatStickerSet { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/delete_message.rs b/src/requests/all/delete_message.rs index 0dbb88c8..cf28eb23 100644 --- a/src/requests/all/delete_message.rs +++ b/src/requests/all/delete_message.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to delete a message, including service messages. /// @@ -24,16 +24,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#deletemessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, message_id: i32, } #[async_trait::async_trait] -impl Request for DeleteMessage<'_> { +impl Request for DeleteMessage { type Output = True; async fn send(&self) -> ResponseResult { @@ -47,14 +47,14 @@ impl Request for DeleteMessage<'_> { } } -impl<'a> DeleteMessage<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, message_id: i32) -> Self +impl DeleteMessage { + pub(crate) fn new(bot: Arc, chat_id: C, message_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, message_id, } diff --git a/src/requests/all/delete_sticker_from_set.rs b/src/requests/all/delete_sticker_from_set.rs index 111622f1..41f41f80 100644 --- a/src/requests/all/delete_sticker_from_set.rs +++ b/src/requests/all/delete_sticker_from_set.rs @@ -1,26 +1,26 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Use this method to delete a sticker from a set created by the bot. /// /// [The official docs](https://core.telegram.org/bots/api#deletestickerfromset). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteStickerFromSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteStickerFromSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, sticker: String, } #[async_trait::async_trait] -impl Request for DeleteStickerFromSet<'_> { +impl Request for DeleteStickerFromSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -34,16 +34,13 @@ impl Request for DeleteStickerFromSet<'_> { } } -impl<'a> DeleteStickerFromSet<'a> { - pub(crate) fn new(bot: &'a Bot, sticker: S) -> Self +impl DeleteStickerFromSet { + pub(crate) fn new(bot: Arc, sticker: S) -> Self where S: Into, { let sticker = sticker.into(); - Self { - bot: BotWrapper(bot), - sticker, - } + Self { bot, sticker } } /// File identifier of the sticker. diff --git a/src/requests/all/delete_webhook.rs b/src/requests/all/delete_webhook.rs index 10511a1b..dbfcda20 100644 --- a/src/requests/all/delete_webhook.rs +++ b/src/requests/all/delete_webhook.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Use this method to remove webhook integration if you decide to switch back /// to [Bot::get_updates]. @@ -15,14 +15,14 @@ use crate::{ /// /// [Bot::get_updates]: crate::Bot::get_updates #[serde_with_macros::skip_serializing_none] -#[derive(Copy, Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteWebhook<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteWebhook { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, } #[async_trait::async_trait] -impl Request for DeleteWebhook<'_> { +impl Request for DeleteWebhook { type Output = True; #[allow(clippy::trivially_copy_pass_by_ref)] @@ -37,10 +37,8 @@ impl Request for DeleteWebhook<'_> { } } -impl<'a> DeleteWebhook<'a> { - pub(crate) fn new(bot: &'a Bot) -> Self { - Self { - bot: BotWrapper(bot), - } +impl DeleteWebhook { + pub(crate) fn new(bot: Arc) -> Self { + Self { bot } } } diff --git a/src/requests/all/edit_message_caption.rs b/src/requests/all/edit_message_caption.rs index e873d2dc..6a5d0e1d 100644 --- a/src/requests/all/edit_message_caption.rs +++ b/src/requests/all/edit_message_caption.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode}, Bot, }; +use std::sync::Arc; /// Use this method to edit captions of messages. /// @@ -18,10 +18,10 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct EditMessageCaption<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageCaption { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, caption: Option, @@ -30,7 +30,7 @@ pub struct EditMessageCaption<'a> { } #[async_trait::async_trait] -impl Request for EditMessageCaption<'_> { +impl Request for EditMessageCaption { type Output = Message; async fn send(&self) -> ResponseResult { @@ -44,13 +44,13 @@ impl Request for EditMessageCaption<'_> { } } -impl<'a> EditMessageCaption<'a> { +impl EditMessageCaption { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, caption: None, parse_mode: None, diff --git a/src/requests/all/edit_message_live_location.rs b/src/requests/all/edit_message_live_location.rs index 1362366c..1e6bf9b1 100644 --- a/src/requests/all/edit_message_live_location.rs +++ b/src/requests/all/edit_message_live_location.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, Bot, }; +use std::sync::Arc; /// Use this method to edit live location messages. /// @@ -20,10 +20,10 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(PartialEq, Debug, Clone, Serialize)] -pub struct EditMessageLiveLocation<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageLiveLocation { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, latitude: f32, @@ -32,7 +32,7 @@ pub struct EditMessageLiveLocation<'a> { } #[async_trait::async_trait] -impl Request for EditMessageLiveLocation<'_> { +impl Request for EditMessageLiveLocation { type Output = Message; async fn send(&self) -> ResponseResult { @@ -46,15 +46,15 @@ impl Request for EditMessageLiveLocation<'_> { } } -impl<'a> EditMessageLiveLocation<'a> { +impl EditMessageLiveLocation { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, latitude: f32, longitude: f32, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, latitude, longitude, diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs index 685a0356..31cd350d 100644 --- a/src/requests/all/edit_message_media.rs +++ b/src/requests/all/edit_message_media.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, InputMedia, Message}, Bot, }; +use std::sync::Arc; /// Use this method to edit animation, audio, document, photo, or video /// messages. @@ -20,16 +20,16 @@ use crate::{ /// /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct EditMessageMedia<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct EditMessageMedia { + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, media: InputMedia, reply_markup: Option, } #[async_trait::async_trait] -impl Request for EditMessageMedia<'_> { +impl Request for EditMessageMedia { type Output = Message; async fn send(&self) -> ResponseResult { @@ -67,14 +67,14 @@ impl Request for EditMessageMedia<'_> { } } -impl<'a> EditMessageMedia<'a> { +impl EditMessageMedia { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, media: InputMedia, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, media, reply_markup: None, diff --git a/src/requests/all/edit_message_reply_markup.rs b/src/requests/all/edit_message_reply_markup.rs index 7c7de32e..9b60c224 100644 --- a/src/requests/all/edit_message_reply_markup.rs +++ b/src/requests/all/edit_message_reply_markup.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, Bot, }; +use std::sync::Arc; /// Use this method to edit only the reply markup of messages. /// @@ -18,17 +18,17 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct EditMessageReplyMarkup<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageReplyMarkup { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, reply_markup: Option, } #[async_trait::async_trait] -impl Request for EditMessageReplyMarkup<'_> { +impl Request for EditMessageReplyMarkup { type Output = Message; async fn send(&self) -> ResponseResult { @@ -42,13 +42,13 @@ impl Request for EditMessageReplyMarkup<'_> { } } -impl<'a> EditMessageReplyMarkup<'a> { +impl EditMessageReplyMarkup { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, reply_markup: None, } diff --git a/src/requests/all/edit_message_text.rs b/src/requests/all/edit_message_text.rs index ffe71b4c..d517db38 100644 --- a/src/requests/all/edit_message_text.rs +++ b/src/requests/all/edit_message_text.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode}, Bot, }; +use std::sync::Arc; /// Use this method to edit text and game messages. /// @@ -18,10 +18,10 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct EditMessageText<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageText { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, text: String, @@ -31,7 +31,7 @@ pub struct EditMessageText<'a> { } #[async_trait::async_trait] -impl Request for EditMessageText<'_> { +impl Request for EditMessageText { type Output = Message; async fn send(&self) -> ResponseResult { @@ -45,9 +45,9 @@ impl Request for EditMessageText<'_> { } } -impl<'a> EditMessageText<'a> { +impl EditMessageText { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, text: T, ) -> Self @@ -55,7 +55,7 @@ impl<'a> EditMessageText<'a> { T: Into, { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, text: text.into(), parse_mode: None, diff --git a/src/requests/all/export_chat_invite_link.rs b/src/requests/all/export_chat_invite_link.rs index ec2f93eb..7e31cf96 100644 --- a/src/requests/all/export_chat_invite_link.rs +++ b/src/requests/all/export_chat_invite_link.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::ChatId, Bot, }; +use std::sync::Arc; /// Use this method to generate a new invite link for a chat; any previously /// generated link is revoked. @@ -28,15 +28,15 @@ use crate::{ /// [`Bot::export_chat_invite_link`]: crate::Bot::export_chat_invite_link /// [`Bot::get_chat`]: crate::Bot::get_chat #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct ExportChatInviteLink<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct ExportChatInviteLink { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for ExportChatInviteLink<'_> { +impl Request for ExportChatInviteLink { type Output = String; /// Returns the new invite link as `String` on success. @@ -51,16 +51,13 @@ impl Request for ExportChatInviteLink<'_> { } } -impl<'a> ExportChatInviteLink<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl ExportChatInviteLink { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target channel diff --git a/src/requests/all/forward_message.rs b/src/requests/all/forward_message.rs index 0b765f09..c37b71c6 100644 --- a/src/requests/all/forward_message.rs +++ b/src/requests/all/forward_message.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message}, Bot, }; +use std::sync::Arc; /// Use this method to forward messages of any kind. /// /// [`The official docs`](https://core.telegram.org/bots/api#forwardmessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct ForwardMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct ForwardMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, from_chat_id: ChatId, disable_notification: Option, @@ -23,7 +23,7 @@ pub struct ForwardMessage<'a> { } #[async_trait::async_trait] -impl Request for ForwardMessage<'_> { +impl Request for ForwardMessage { type Output = Message; async fn send(&self) -> ResponseResult { @@ -37,9 +37,9 @@ impl Request for ForwardMessage<'_> { } } -impl<'a> ForwardMessage<'a> { +impl ForwardMessage { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, from_chat_id: F, message_id: i32, @@ -51,7 +51,7 @@ impl<'a> ForwardMessage<'a> { let chat_id = chat_id.into(); let from_chat_id = from_chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, from_chat_id, message_id, diff --git a/src/requests/all/get_chat.rs b/src/requests/all/get_chat.rs index ce939041..c726908e 100644 --- a/src/requests/all/get_chat.rs +++ b/src/requests/all/get_chat.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{Chat, ChatId}, Bot, }; +use std::sync::Arc; /// Use this method to get up to date information about the chat (current name /// of the user for one-on-one conversations, current username of a user, group @@ -14,15 +14,15 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#getchat). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetChat<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetChat { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for GetChat<'_> { +impl Request for GetChat { type Output = Chat; async fn send(&self) -> ResponseResult { @@ -31,16 +31,13 @@ impl Request for GetChat<'_> { } } -impl<'a> GetChat<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl GetChat { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/get_chat_administrators.rs b/src/requests/all/get_chat_administrators.rs index bff1adb2..cc575a27 100644 --- a/src/requests/all/get_chat_administrators.rs +++ b/src/requests/all/get_chat_administrators.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, ChatMember}, Bot, }; +use std::sync::Arc; /// Use this method to get a list of administrators in a chat. /// @@ -15,15 +15,15 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetChatAdministrators<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetChatAdministrators { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for GetChatAdministrators<'_> { +impl Request for GetChatAdministrators { type Output = Vec; /// On success, returns an array that contains information about all chat @@ -39,16 +39,13 @@ impl Request for GetChatAdministrators<'_> { } } -impl<'a> GetChatAdministrators<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl GetChatAdministrators { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/get_chat_member.rs b/src/requests/all/get_chat_member.rs index e3a4f190..6b5aabea 100644 --- a/src/requests/all/get_chat_member.rs +++ b/src/requests/all/get_chat_member.rs @@ -1,27 +1,27 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, ChatMember}, Bot, }; +use std::sync::Arc; /// Use this method to get information about a member of a chat. /// /// [The official docs](https://core.telegram.org/bots/api#getchatmember). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, } #[async_trait::async_trait] -impl Request for GetChatMember<'_> { +impl Request for GetChatMember { type Output = ChatMember; async fn send(&self) -> ResponseResult { @@ -35,14 +35,14 @@ impl Request for GetChatMember<'_> { } } -impl<'a> GetChatMember<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, user_id: i32) -> Self +impl GetChatMember { + pub(crate) fn new(bot: Arc, chat_id: C, user_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, } diff --git a/src/requests/all/get_chat_members_count.rs b/src/requests/all/get_chat_members_count.rs index 1d5e4c55..89deea55 100644 --- a/src/requests/all/get_chat_members_count.rs +++ b/src/requests/all/get_chat_members_count.rs @@ -1,26 +1,26 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::ChatId, Bot, }; +use std::sync::Arc; /// Use this method to get the number of members in a chat. /// /// [The official docs](https://core.telegram.org/bots/api#getchatmemberscount). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetChatMembersCount<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetChatMembersCount { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for GetChatMembersCount<'_> { +impl Request for GetChatMembersCount { type Output = i32; async fn send(&self) -> ResponseResult { @@ -34,16 +34,13 @@ impl Request for GetChatMembersCount<'_> { } } -impl<'a> GetChatMembersCount<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl GetChatMembersCount { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/get_file.rs b/src/requests/all/get_file.rs index 4c470827..2694e20f 100644 --- a/src/requests/all/get_file.rs +++ b/src/requests/all/get_file.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::File, Bot, }; +use std::sync::Arc; /// Use this method to get basic info about a file and prepare it for /// downloading. @@ -28,15 +28,15 @@ use crate::{ /// [`File`]: crate::types::file /// [`GetFile`]: self::GetFile #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetFile<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetFile { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, file_id: String, } #[async_trait::async_trait] -impl Request for GetFile<'_> { +impl Request for GetFile { type Output = File; async fn send(&self) -> ResponseResult { @@ -45,13 +45,13 @@ impl Request for GetFile<'_> { } } -impl<'a> GetFile<'a> { - pub(crate) fn new(bot: &'a Bot, file_id: F) -> Self +impl GetFile { + pub(crate) fn new(bot: Arc, file_id: F) -> Self where F: Into, { Self { - bot: BotWrapper(bot), + bot, file_id: file_id.into(), } } diff --git a/src/requests/all/get_game_high_scores.rs b/src/requests/all/get_game_high_scores.rs index 04d43c5d..6026bfef 100644 --- a/src/requests/all/get_game_high_scores.rs +++ b/src/requests/all/get_game_high_scores.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, GameHighScore}, Bot, }; +use std::sync::Arc; /// Use this method to get data for high score tables. /// @@ -21,17 +21,17 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#getgamehighscores). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetGameHighScores<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetGameHighScores { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, user_id: i32, } #[async_trait::async_trait] -impl Request for GetGameHighScores<'_> { +impl Request for GetGameHighScores { type Output = Vec; async fn send(&self) -> ResponseResult> { @@ -45,14 +45,14 @@ impl Request for GetGameHighScores<'_> { } } -impl<'a> GetGameHighScores<'a> { +impl GetGameHighScores { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, user_id: i32, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, user_id, } diff --git a/src/requests/all/get_me.rs b/src/requests/all/get_me.rs index e56ad3b6..857c8a6b 100644 --- a/src/requests/all/get_me.rs +++ b/src/requests/all/get_me.rs @@ -1,4 +1,3 @@ -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, @@ -6,18 +5,19 @@ use crate::{ Bot, }; use serde::Serialize; +use std::sync::Arc; /// A simple method for testing your bot's auth token. Requires no parameters. /// /// [The official docs](https://core.telegram.org/bots/api#getme). -#[derive(Eq, PartialEq, Debug, Clone, Copy, Serialize)] -pub struct GetMe<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetMe { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, } #[async_trait::async_trait] -impl Request for GetMe<'_> { +impl Request for GetMe { type Output = Me; /// Returns basic information about the bot. @@ -28,10 +28,8 @@ impl Request for GetMe<'_> { } } -impl<'a> GetMe<'a> { - pub(crate) fn new(bot: &'a Bot) -> Self { - Self { - bot: BotWrapper(bot), - } +impl GetMe { + pub(crate) fn new(bot: Arc) -> Self { + Self { bot } } } diff --git a/src/requests/all/get_sticker_set.rs b/src/requests/all/get_sticker_set.rs index a63911f8..8b1094a5 100644 --- a/src/requests/all/get_sticker_set.rs +++ b/src/requests/all/get_sticker_set.rs @@ -1,26 +1,26 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::StickerSet, Bot, }; +use std::sync::Arc; /// Use this method to get a sticker set. /// /// [The official docs](https://core.telegram.org/bots/api#getstickerset). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetStickerSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetStickerSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, name: String, } #[async_trait::async_trait] -impl Request for GetStickerSet<'_> { +impl Request for GetStickerSet { type Output = StickerSet; async fn send(&self) -> ResponseResult { @@ -34,16 +34,13 @@ impl Request for GetStickerSet<'_> { } } -impl<'a> GetStickerSet<'a> { - pub(crate) fn new(bot: &'a Bot, name: N) -> Self +impl GetStickerSet { + pub(crate) fn new(bot: Arc, name: N) -> Self where N: Into, { let name = name.into(); - Self { - bot: BotWrapper(bot), - name, - } + Self { bot, name } } /// Name of the sticker set. diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs index c7311591..2ef81d17 100644 --- a/src/requests/all/get_updates.rs +++ b/src/requests/all/get_updates.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{AllowedUpdate, Update}, Bot, }; +use std::sync::Arc; /// Use this method to receive incoming updates using long polling ([wiki]). /// @@ -19,10 +19,10 @@ use crate::{ /// /// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetUpdates<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetUpdates { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, pub(crate) offset: Option, pub(crate) limit: Option, pub(crate) timeout: Option, @@ -30,7 +30,7 @@ pub struct GetUpdates<'a> { } #[async_trait::async_trait] -impl Request for GetUpdates<'_> { +impl Request for GetUpdates { type Output = Vec; async fn send(&self) -> ResponseResult> { @@ -44,10 +44,10 @@ impl Request for GetUpdates<'_> { } } -impl<'a> GetUpdates<'a> { - pub(crate) fn new(bot: &'a Bot) -> Self { +impl GetUpdates { + pub(crate) fn new(bot: Arc) -> Self { Self { - bot: BotWrapper(bot), + bot, offset: None, limit: None, timeout: None, diff --git a/src/requests/all/get_user_profile_photos.rs b/src/requests/all/get_user_profile_photos.rs index bc9b79c3..c3939104 100644 --- a/src/requests/all/get_user_profile_photos.rs +++ b/src/requests/all/get_user_profile_photos.rs @@ -1,28 +1,28 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::UserProfilePhotos, Bot, }; +use std::sync::Arc; /// Use this method to get a list of profile pictures for a user. /// /// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos). #[serde_with_macros::skip_serializing_none] -#[derive(Copy, Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetUserProfilePhotos<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetUserProfilePhotos { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, user_id: i32, offset: Option, limit: Option, } #[async_trait::async_trait] -impl Request for GetUserProfilePhotos<'_> { +impl Request for GetUserProfilePhotos { type Output = UserProfilePhotos; async fn send(&self) -> ResponseResult { @@ -36,10 +36,10 @@ impl Request for GetUserProfilePhotos<'_> { } } -impl<'a> GetUserProfilePhotos<'a> { - pub(crate) fn new(bot: &'a Bot, user_id: i32) -> Self { +impl GetUserProfilePhotos { + pub(crate) fn new(bot: Arc, user_id: i32) -> Self { Self { - bot: BotWrapper(bot), + bot, user_id, offset: None, limit: None, diff --git a/src/requests/all/get_webhook_info.rs b/src/requests/all/get_webhook_info.rs index 88fa92f5..99ece7ea 100644 --- a/src/requests/all/get_webhook_info.rs +++ b/src/requests/all/get_webhook_info.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::WebhookInfo, Bot, }; +use std::sync::Arc; /// Use this method to get current webhook status. /// @@ -16,14 +16,14 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). /// /// [`Bot::get_updates`]: crate::Bot::get_updates -#[derive(Copy, Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetWebhookInfo<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetWebhookInfo { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, } #[async_trait::async_trait] -impl Request for GetWebhookInfo<'_> { +impl Request for GetWebhookInfo { type Output = WebhookInfo; #[allow(clippy::trivially_copy_pass_by_ref)] @@ -38,10 +38,8 @@ impl Request for GetWebhookInfo<'_> { } } -impl<'a> GetWebhookInfo<'a> { - pub(crate) fn new(bot: &'a Bot) -> Self { - Self { - bot: BotWrapper(bot), - } +impl GetWebhookInfo { + pub(crate) fn new(bot: Arc) -> Self { + Self { bot } } } diff --git a/src/requests/all/kick_chat_member.rs b/src/requests/all/kick_chat_member.rs index c766071f..fb02d384 100644 --- a/src/requests/all/kick_chat_member.rs +++ b/src/requests/all/kick_chat_member.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to kick a user from a group, a supergroup or a channel. /// @@ -19,17 +19,17 @@ use crate::{ /// /// [unbanned]: crate::Bot::unban_chat_member #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct KickChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct KickChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, until_date: Option, } #[async_trait::async_trait] -impl Request for KickChatMember<'_> { +impl Request for KickChatMember { type Output = True; async fn send(&self) -> ResponseResult { @@ -43,14 +43,14 @@ impl Request for KickChatMember<'_> { } } -impl<'a> KickChatMember<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, user_id: i32) -> Self +impl KickChatMember { + pub(crate) fn new(bot: Arc, chat_id: C, user_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, until_date: None, diff --git a/src/requests/all/leave_chat.rs b/src/requests/all/leave_chat.rs index a0ccd4b4..d2dc5f14 100644 --- a/src/requests/all/leave_chat.rs +++ b/src/requests/all/leave_chat.rs @@ -1,26 +1,26 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method for your bot to leave a group, supergroup or channel. /// /// [The official docs](https://core.telegram.org/bots/api#leavechat). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct LeaveChat<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct LeaveChat { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for LeaveChat<'_> { +impl Request for LeaveChat { type Output = True; async fn send(&self) -> ResponseResult { @@ -34,16 +34,13 @@ impl Request for LeaveChat<'_> { } } -impl<'a> LeaveChat<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl LeaveChat { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/mod.rs b/src/requests/all/mod.rs index 84acc2e1..7d8e6845 100644 --- a/src/requests/all/mod.rs +++ b/src/requests/all/mod.rs @@ -130,6 +130,3 @@ pub use stop_poll::*; pub use unban_chat_member::*; pub use unpin_chat_message::*; pub use upload_sticker_file::*; - -mod bot_wrapper; -use bot_wrapper::BotWrapper; diff --git a/src/requests/all/pin_chat_message.rs b/src/requests/all/pin_chat_message.rs index 6be8c7cf..256ddef1 100644 --- a/src/requests/all/pin_chat_message.rs +++ b/src/requests/all/pin_chat_message.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to pin a message in a group, a supergroup, or a channel. /// @@ -16,17 +16,17 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct PinChatMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct PinChatMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, message_id: i32, disable_notification: Option, } #[async_trait::async_trait] -impl Request for PinChatMessage<'_> { +impl Request for PinChatMessage { type Output = True; async fn send(&self) -> ResponseResult { @@ -40,14 +40,14 @@ impl Request for PinChatMessage<'_> { } } -impl<'a> PinChatMessage<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, message_id: i32) -> Self +impl PinChatMessage { + pub(crate) fn new(bot: Arc, chat_id: C, message_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, message_id, disable_notification: None, diff --git a/src/requests/all/promote_chat_member.rs b/src/requests/all/promote_chat_member.rs index ddd46e93..32919b0f 100644 --- a/src/requests/all/promote_chat_member.rs +++ b/src/requests/all/promote_chat_member.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to promote or demote a user in a supergroup or a channel. /// @@ -16,10 +16,10 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#promotechatmember). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct PromoteChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct PromoteChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, can_change_info: Option, @@ -33,7 +33,7 @@ pub struct PromoteChatMember<'a> { } #[async_trait::async_trait] -impl Request for PromoteChatMember<'_> { +impl Request for PromoteChatMember { type Output = True; async fn send(&self) -> ResponseResult { @@ -47,14 +47,14 @@ impl Request for PromoteChatMember<'_> { } } -impl<'a> PromoteChatMember<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, user_id: i32) -> Self +impl PromoteChatMember { + pub(crate) fn new(bot: Arc, chat_id: C, user_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, can_change_info: None, diff --git a/src/requests/all/restrict_chat_member.rs b/src/requests/all/restrict_chat_member.rs index 918571a6..90dda304 100644 --- a/src/requests/all/restrict_chat_member.rs +++ b/src/requests/all/restrict_chat_member.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, ChatPermissions, True}, Bot, }; +use std::sync::Arc; /// Use this method to restrict a user in a supergroup. /// @@ -16,10 +16,10 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct RestrictChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct RestrictChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, permissions: ChatPermissions, @@ -27,7 +27,7 @@ pub struct RestrictChatMember<'a> { } #[async_trait::async_trait] -impl Request for RestrictChatMember<'_> { +impl Request for RestrictChatMember { type Output = True; async fn send(&self) -> ResponseResult { @@ -41,9 +41,9 @@ impl Request for RestrictChatMember<'_> { } } -impl<'a> RestrictChatMember<'a> { +impl RestrictChatMember { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, user_id: i32, permissions: ChatPermissions, @@ -53,7 +53,7 @@ impl<'a> RestrictChatMember<'a> { { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, permissions, diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs index 1584cc16..cbd40485 100644 --- a/src/requests/all/send_animation.rs +++ b/src/requests/all/send_animation.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video /// without sound). @@ -13,9 +13,9 @@ use crate::{ /// may be changed in the future. /// /// [The official docs](https://core.telegram.org/bots/api#sendanimation). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendAnimation<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendAnimation { + bot: Arc, pub chat_id: ChatId, pub animation: InputFile, pub duration: Option, @@ -30,7 +30,7 @@ pub struct SendAnimation<'a> { } #[async_trait::async_trait] -impl Request for SendAnimation<'_> { +impl Request for SendAnimation { type Output = Message; async fn send(&self) -> ResponseResult { @@ -67,13 +67,17 @@ impl Request for SendAnimation<'_> { } } -impl<'a> SendAnimation<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, animation: InputFile) -> Self +impl SendAnimation { + pub(crate) fn new( + bot: Arc, + chat_id: C, + animation: InputFile, + ) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), animation, duration: None, diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs index 9be59300..829e41f9 100644 --- a/src/requests/all/send_audio.rs +++ b/src/requests/all/send_audio.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send audio files, if you want Telegram clients to display /// them in the music player. @@ -17,9 +17,9 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#sendaudio). /// /// [`Bot::send_voice`]: crate::Bot::send_voice -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendAudio<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendAudio { + bot: Arc, chat_id: ChatId, audio: InputFile, caption: Option, @@ -34,7 +34,7 @@ pub struct SendAudio<'a> { } #[async_trait::async_trait] -impl Request for SendAudio<'_> { +impl Request for SendAudio { type Output = Message; async fn send(&self) -> ResponseResult { @@ -71,13 +71,13 @@ impl Request for SendAudio<'_> { } } -impl<'a> SendAudio<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, audio: InputFile) -> Self +impl SendAudio { + pub(crate) fn new(bot: Arc, chat_id: C, audio: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), audio, caption: None, diff --git a/src/requests/all/send_chat_action.rs b/src/requests/all/send_chat_action.rs index 6718ae8f..1d7b0032 100644 --- a/src/requests/all/send_chat_action.rs +++ b/src/requests/all/send_chat_action.rs @@ -1,12 +1,12 @@ use serde::{Deserialize, Serialize}; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method when you need to tell the user that something is happening /// on the bot's side. @@ -26,10 +26,10 @@ use crate::{ /// [ImageBot]: https://t.me/imagebot /// [`Bot::send_chat_action`]: crate::Bot::send_chat_action #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendChatAction<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendChatAction { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, action: SendChatActionKind, } @@ -37,7 +37,7 @@ pub struct SendChatAction<'a> { /// A type of action used in [`SendChatAction`]. /// /// [`SendChatAction`]: crate::requests::SendChatAction -#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SendChatActionKind { /// For [text messages](crate::Bot::send_message). @@ -72,7 +72,7 @@ pub enum SendChatActionKind { } #[async_trait::async_trait] -impl Request for SendChatAction<'_> { +impl Request for SendChatAction { type Output = True; async fn send(&self) -> ResponseResult { @@ -86,9 +86,9 @@ impl Request for SendChatAction<'_> { } } -impl<'a> SendChatAction<'a> { +impl SendChatAction { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, action: SendChatActionKind, ) -> Self @@ -96,7 +96,7 @@ impl<'a> SendChatAction<'a> { C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), action, } diff --git a/src/requests/all/send_contact.rs b/src/requests/all/send_contact.rs index 97cda878..1ad6d0b6 100644 --- a/src/requests/all/send_contact.rs +++ b/src/requests/all/send_contact.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send phone contacts. /// /// [The official docs](https://core.telegram.org/bots/api#sendcontact). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendContact<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendContact { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, phone_number: String, first_name: String, @@ -27,7 +27,7 @@ pub struct SendContact<'a> { } #[async_trait::async_trait] -impl Request for SendContact<'_> { +impl Request for SendContact { type Output = Message; async fn send(&self) -> ResponseResult { @@ -41,9 +41,9 @@ impl Request for SendContact<'_> { } } -impl<'a> SendContact<'a> { +impl SendContact { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, phone_number: P, first_name: F, @@ -57,7 +57,7 @@ impl<'a> SendContact<'a> { let phone_number = phone_number.into(); let first_name = first_name.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, phone_number, first_name, diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs index a2175156..3e2cfd46 100644 --- a/src/requests/all/send_document.rs +++ b/src/requests/all/send_document.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send general files. /// @@ -12,9 +12,9 @@ use crate::{ /// may be changed in the future. /// /// [The official docs](https://core.telegram.org/bots/api#senddocument). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendDocument<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendDocument { + bot: Arc, chat_id: ChatId, document: InputFile, thumb: Option, @@ -26,7 +26,7 @@ pub struct SendDocument<'a> { } #[async_trait::async_trait] -impl Request for SendDocument<'_> { +impl Request for SendDocument { type Output = Message; async fn send(&self) -> ResponseResult { @@ -57,13 +57,13 @@ impl Request for SendDocument<'_> { } } -impl<'a> SendDocument<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, document: InputFile) -> Self +impl SendDocument { + pub(crate) fn new(bot: Arc, chat_id: C, document: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), document, thumb: None, diff --git a/src/requests/all/send_game.rs b/src/requests/all/send_game.rs index bc3056aa..53555c06 100644 --- a/src/requests/all/send_game.rs +++ b/src/requests/all/send_game.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{InlineKeyboardMarkup, Message}, Bot, }; +use std::sync::Arc; /// Use this method to send a game. /// /// [The official docs](https://core.telegram.org/bots/api#sendgame). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendGame<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendGame { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: i32, game_short_name: String, disable_notification: Option, @@ -24,7 +24,7 @@ pub struct SendGame<'a> { } #[async_trait::async_trait] -impl Request for SendGame<'_> { +impl Request for SendGame { type Output = Message; async fn send(&self) -> ResponseResult { @@ -38,14 +38,18 @@ impl Request for SendGame<'_> { } } -impl<'a> SendGame<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: i32, game_short_name: G) -> Self +impl SendGame { + pub(crate) fn new( + bot: Arc, + chat_id: i32, + game_short_name: G, + ) -> Self where G: Into, { let game_short_name = game_short_name.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, game_short_name, disable_notification: None, diff --git a/src/requests/all/send_invoice.rs b/src/requests/all/send_invoice.rs index 99fbab15..b9e710e3 100644 --- a/src/requests/all/send_invoice.rs +++ b/src/requests/all/send_invoice.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{InlineKeyboardMarkup, LabeledPrice, Message}, Bot, }; +use std::sync::Arc; /// Use this method to send invoices. /// /// [The official docs](https://core.telegram.org/bots/api#sendinvoice). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendInvoice<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendInvoice { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: i32, title: String, description: String, @@ -42,7 +42,7 @@ pub struct SendInvoice<'a> { } #[async_trait::async_trait] -impl Request for SendInvoice<'_> { +impl Request for SendInvoice { type Output = Message; async fn send(&self) -> ResponseResult { @@ -56,10 +56,10 @@ impl Request for SendInvoice<'_> { } } -impl<'a> SendInvoice<'a> { +impl SendInvoice { #[allow(clippy::too_many_arguments)] pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: i32, title: T, description: D, @@ -86,7 +86,7 @@ impl<'a> SendInvoice<'a> { let currency = currency.into(); let prices = prices.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, title, description, diff --git a/src/requests/all/send_location.rs b/src/requests/all/send_location.rs index 6ce4060e..149d7a5c 100644 --- a/src/requests/all/send_location.rs +++ b/src/requests/all/send_location.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send point on the map. /// /// [The official docs](https://core.telegram.org/bots/api#sendlocation). #[serde_with_macros::skip_serializing_none] -#[derive(PartialEq, Debug, Clone, Serialize)] -pub struct SendLocation<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendLocation { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, latitude: f32, longitude: f32, @@ -26,7 +26,7 @@ pub struct SendLocation<'a> { } #[async_trait::async_trait] -impl Request for SendLocation<'_> { +impl Request for SendLocation { type Output = Message; async fn send(&self) -> ResponseResult { @@ -40,9 +40,9 @@ impl Request for SendLocation<'_> { } } -impl<'a> SendLocation<'a> { +impl SendLocation { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, latitude: f32, longitude: f32, @@ -52,7 +52,7 @@ impl<'a> SendLocation<'a> { { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, latitude, longitude, diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs index 6576da97..cae6604d 100644 --- a/src/requests/all/send_media_group.rs +++ b/src/requests/all/send_media_group.rs @@ -1,17 +1,17 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputMedia, Message}, Bot, }; +use std::sync::Arc; /// Use this method to send a group of photos or videos as an album. /// /// [The official docs](https://core.telegram.org/bots/api#sendmediagroup). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendMediaGroup<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendMediaGroup { + bot: Arc, chat_id: ChatId, media: Vec, // TODO: InputMediaPhoto and InputMediaVideo disable_notification: Option, @@ -19,7 +19,7 @@ pub struct SendMediaGroup<'a> { } #[async_trait::async_trait] -impl Request for SendMediaGroup<'_> { +impl Request for SendMediaGroup { type Output = Vec; async fn send(&self) -> ResponseResult> { @@ -42,8 +42,8 @@ impl Request for SendMediaGroup<'_> { } } -impl<'a> SendMediaGroup<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, media: M) -> Self +impl SendMediaGroup { + pub(crate) fn new(bot: Arc, chat_id: C, media: M) -> Self where C: Into, M: Into>, @@ -51,7 +51,7 @@ impl<'a> SendMediaGroup<'a> { let chat_id = chat_id.into(); let media = media.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, media, disable_notification: None, diff --git a/src/requests/all/send_message.rs b/src/requests/all/send_message.rs index f19792fe..689d9671 100644 --- a/src/requests/all/send_message.rs +++ b/src/requests/all/send_message.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send text messages. /// /// [The official docs](https://core.telegram.org/bots/api#sendmessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, pub chat_id: ChatId, pub text: String, pub parse_mode: Option, @@ -26,7 +26,7 @@ pub struct SendMessage<'a> { } #[async_trait::async_trait] -impl Request for SendMessage<'_> { +impl Request for SendMessage { type Output = Message; async fn send(&self) -> ResponseResult { @@ -40,14 +40,14 @@ impl Request for SendMessage<'_> { } } -impl<'a> SendMessage<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, text: T) -> Self +impl SendMessage { + pub(crate) fn new(bot: Arc, chat_id: C, text: T) -> Self where C: Into, T: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), text: text.into(), parse_mode: None, diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs index e75580a3..ea603eca 100644 --- a/src/requests/all/send_photo.rs +++ b/src/requests/all/send_photo.rs @@ -1,17 +1,17 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send photos. /// /// [The official docs](https://core.telegram.org/bots/api#sendphoto). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendPhoto<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendPhoto { + bot: Arc, chat_id: ChatId, photo: InputFile, caption: Option, @@ -22,7 +22,7 @@ pub struct SendPhoto<'a> { } #[async_trait::async_trait] -impl Request for SendPhoto<'_> { +impl Request for SendPhoto { type Output = Message; async fn send(&self) -> ResponseResult { @@ -51,13 +51,13 @@ impl Request for SendPhoto<'_> { } } -impl<'a> SendPhoto<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, photo: InputFile) -> Self +impl SendPhoto { + pub(crate) fn new(bot: Arc, chat_id: C, photo: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), photo, caption: None, diff --git a/src/requests/all/send_poll.rs b/src/requests/all/send_poll.rs index 657b430a..a2fafd42 100644 --- a/src/requests/all/send_poll.rs +++ b/src/requests/all/send_poll.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, PollType, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send a native poll. /// /// [The official docs](https://core.telegram.org/bots/api#sendpoll). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendPoll<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendPoll { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, question: String, options: Vec, @@ -30,7 +30,7 @@ pub struct SendPoll<'a> { } #[async_trait::async_trait] -impl Request for SendPoll<'_> { +impl Request for SendPoll { type Output = Message; async fn send(&self) -> ResponseResult { @@ -44,9 +44,9 @@ impl Request for SendPoll<'_> { } } -impl<'a> SendPoll<'a> { +impl SendPoll { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, question: Q, options: O, @@ -60,7 +60,7 @@ impl<'a> SendPoll<'a> { let question = question.into(); let options = options.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, question, options, diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs index 1cf07e23..e44dbb80 100644 --- a/src/requests/all/send_sticker.rs +++ b/src/requests/all/send_sticker.rs @@ -1,19 +1,19 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send static .WEBP or [animated] .TGS stickers. /// /// [The official docs](https://core.telegram.org/bots/api#sendsticker). /// /// [animated]: https://telegram.org/blog/animated-stickers -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendSticker<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendSticker { + bot: Arc, chat_id: ChatId, sticker: InputFile, disable_notification: Option, @@ -22,7 +22,7 @@ pub struct SendSticker<'a> { } #[async_trait::async_trait] -impl Request for SendSticker<'_> { +impl Request for SendSticker { type Output = Message; async fn send(&self) -> ResponseResult { @@ -47,13 +47,13 @@ impl Request for SendSticker<'_> { } } -impl<'a> SendSticker<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, sticker: InputFile) -> Self +impl SendSticker { + pub(crate) fn new(bot: Arc, chat_id: C, sticker: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), sticker, disable_notification: None, diff --git a/src/requests/all/send_venue.rs b/src/requests/all/send_venue.rs index c0d06765..c049aeb2 100644 --- a/src/requests/all/send_venue.rs +++ b/src/requests/all/send_venue.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send information about a venue. /// /// [The official docs](https://core.telegram.org/bots/api#sendvenue). #[serde_with_macros::skip_serializing_none] -#[derive(PartialEq, Debug, Clone, Serialize)] -pub struct SendVenue<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendVenue { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, latitude: f32, longitude: f32, @@ -29,7 +29,7 @@ pub struct SendVenue<'a> { } #[async_trait::async_trait] -impl Request for SendVenue<'_> { +impl Request for SendVenue { type Output = Message; async fn send(&self) -> ResponseResult { @@ -43,9 +43,9 @@ impl Request for SendVenue<'_> { } } -impl<'a> SendVenue<'a> { +impl SendVenue { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, latitude: f32, longitude: f32, @@ -61,7 +61,7 @@ impl<'a> SendVenue<'a> { let title = title.into(); let address = address.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, latitude, longitude, diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs index 1671ad7c..8e89ef2e 100644 --- a/src/requests/all/send_video.rs +++ b/src/requests/all/send_video.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send video files, Telegram clients support mp4 videos /// (other formats may be sent as Document). @@ -13,9 +13,9 @@ use crate::{ /// limit may be changed in the future. /// /// [The official docs](https://core.telegram.org/bots/api#sendvideo). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendVideo<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendVideo { + bot: Arc, chat_id: ChatId, video: InputFile, duration: Option, @@ -31,7 +31,7 @@ pub struct SendVideo<'a> { } #[async_trait::async_trait] -impl Request for SendVideo<'_> { +impl Request for SendVideo { type Output = Message; async fn send(&self) -> ResponseResult { @@ -70,13 +70,13 @@ impl Request for SendVideo<'_> { } } -impl<'a> SendVideo<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, video: InputFile) -> Self +impl SendVideo { + pub(crate) fn new(bot: Arc, chat_id: C, video: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), video, duration: None, diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs index fbed9bbb..e3099c35 100644 --- a/src/requests/all/send_video_note.rs +++ b/src/requests/all/send_video_note.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// As of [v.4.0], Telegram clients support rounded square mp4 videos of up to 1 /// minute long. Use this method to send video messages. @@ -12,9 +12,9 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#sendvideonote). /// /// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendVideoNote<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendVideoNote { + bot: Arc, chat_id: ChatId, video_note: InputFile, duration: Option, @@ -26,7 +26,7 @@ pub struct SendVideoNote<'a> { } #[async_trait::async_trait] -impl Request for SendVideoNote<'_> { +impl Request for SendVideoNote { type Output = Message; async fn send(&self) -> ResponseResult { @@ -57,9 +57,9 @@ impl Request for SendVideoNote<'_> { } } -impl<'a> SendVideoNote<'a> { +impl SendVideoNote { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, video_note: InputFile, ) -> Self @@ -67,7 +67,7 @@ impl<'a> SendVideoNote<'a> { C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), video_note, duration: None, diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs index 00df1764..b5ef7b5d 100644 --- a/src/requests/all/send_voice.rs +++ b/src/requests/all/send_voice.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send audio files, if you want Telegram clients to display /// the file as a playable voice message. @@ -18,9 +18,9 @@ use crate::{ /// /// [`Audio`]: crate::types::Audio /// [`Document`]: crate::types::Document -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendVoice<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendVoice { + bot: Arc, chat_id: ChatId, voice: InputFile, caption: Option, @@ -32,7 +32,7 @@ pub struct SendVoice<'a> { } #[async_trait::async_trait] -impl Request for SendVoice<'_> { +impl Request for SendVoice { type Output = Message; async fn send(&self) -> ResponseResult { @@ -63,13 +63,13 @@ impl Request for SendVoice<'_> { } } -impl<'a> SendVoice<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, voice: InputFile) -> Self +impl SendVoice { + pub(crate) fn new(bot: Arc, chat_id: C, voice: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), voice, caption: None, diff --git a/src/requests/all/set_chat_administrator_custom_title.rs b/src/requests/all/set_chat_administrator_custom_title.rs index 94370bef..322f72f7 100644 --- a/src/requests/all/set_chat_administrator_custom_title.rs +++ b/src/requests/all/set_chat_administrator_custom_title.rs @@ -1,29 +1,29 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to set a custom title for an administrator in a supergroup /// promoted by the bot. /// /// [The official docs](https://core.telegram.org/bots/api#setchatadministratorcustomtitle). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatAdministratorCustomTitle<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatAdministratorCustomTitle { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, custom_title: String, } #[async_trait::async_trait] -impl Request for SetChatAdministratorCustomTitle<'_> { +impl Request for SetChatAdministratorCustomTitle { type Output = True; async fn send(&self) -> ResponseResult { @@ -37,9 +37,9 @@ impl Request for SetChatAdministratorCustomTitle<'_> { } } -impl<'a> SetChatAdministratorCustomTitle<'a> { +impl SetChatAdministratorCustomTitle { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, user_id: i32, custom_title: CT, @@ -51,7 +51,7 @@ impl<'a> SetChatAdministratorCustomTitle<'a> { let chat_id = chat_id.into(); let custom_title = custom_title.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, custom_title, diff --git a/src/requests/all/set_chat_description.rs b/src/requests/all/set_chat_description.rs index 2c731483..5896584e 100644 --- a/src/requests/all/set_chat_description.rs +++ b/src/requests/all/set_chat_description.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to change the description of a group, a supergroup or a /// channel. @@ -16,16 +16,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchatdescription). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatDescription<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatDescription { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, description: Option, } #[async_trait::async_trait] -impl Request for SetChatDescription<'_> { +impl Request for SetChatDescription { type Output = True; async fn send(&self) -> ResponseResult { @@ -39,14 +39,14 @@ impl Request for SetChatDescription<'_> { } } -impl<'a> SetChatDescription<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl SetChatDescription { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, description: None, } diff --git a/src/requests/all/set_chat_permissions.rs b/src/requests/all/set_chat_permissions.rs index 2a16f863..4dec39f8 100644 --- a/src/requests/all/set_chat_permissions.rs +++ b/src/requests/all/set_chat_permissions.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, ChatPermissions, True}, Bot, }; +use std::sync::Arc; /// Use this method to set default chat permissions for all members. /// @@ -15,16 +15,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatPermissions<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatPermissions { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, permissions: ChatPermissions, } #[async_trait::async_trait] -impl Request for SetChatPermissions<'_> { +impl Request for SetChatPermissions { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,9 +38,9 @@ impl Request for SetChatPermissions<'_> { } } -impl<'a> SetChatPermissions<'a> { +impl SetChatPermissions { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, permissions: ChatPermissions, ) -> Self @@ -49,7 +49,7 @@ impl<'a> SetChatPermissions<'a> { { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, permissions, } diff --git a/src/requests/all/set_chat_photo.rs b/src/requests/all/set_chat_photo.rs index 4279c7b1..0d152b4d 100644 --- a/src/requests/all/set_chat_photo.rs +++ b/src/requests/all/set_chat_photo.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, InputFile, True}, Bot, }; +use std::sync::Arc; /// Use this method to set a new profile photo for the chat. /// @@ -15,16 +15,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchatphoto). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatPhoto<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatPhoto { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, photo: InputFile, } #[async_trait::async_trait] -impl Request for SetChatPhoto<'_> { +impl Request for SetChatPhoto { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,14 +38,14 @@ impl Request for SetChatPhoto<'_> { } } -impl<'a> SetChatPhoto<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, photo: InputFile) -> Self +impl SetChatPhoto { + pub(crate) fn new(bot: Arc, chat_id: C, photo: InputFile) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, photo, } diff --git a/src/requests/all/set_chat_sticker_set.rs b/src/requests/all/set_chat_sticker_set.rs index 1b2874e4..64d37017 100644 --- a/src/requests/all/set_chat_sticker_set.rs +++ b/src/requests/all/set_chat_sticker_set.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to set a new group sticker set for a supergroup. /// @@ -16,16 +16,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatStickerSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatStickerSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, sticker_set_name: String, } #[async_trait::async_trait] -impl Request for SetChatStickerSet<'_> { +impl Request for SetChatStickerSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -39,9 +39,9 @@ impl Request for SetChatStickerSet<'_> { } } -impl<'a> SetChatStickerSet<'a> { +impl SetChatStickerSet { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, sticker_set_name: S, ) -> Self @@ -52,7 +52,7 @@ impl<'a> SetChatStickerSet<'a> { let chat_id = chat_id.into(); let sticker_set_name = sticker_set_name.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, sticker_set_name, } diff --git a/src/requests/all/set_chat_title.rs b/src/requests/all/set_chat_title.rs index 7b5349fa..6e05fc64 100644 --- a/src/requests/all/set_chat_title.rs +++ b/src/requests/all/set_chat_title.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to change the title of a chat. /// @@ -15,16 +15,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchattitle). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatTitle<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatTitle { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, title: String, } #[async_trait::async_trait] -impl Request for SetChatTitle<'_> { +impl Request for SetChatTitle { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,8 +38,8 @@ impl Request for SetChatTitle<'_> { } } -impl<'a> SetChatTitle<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, title: T) -> Self +impl SetChatTitle { + pub(crate) fn new(bot: Arc, chat_id: C, title: T) -> Self where C: Into, T: Into, @@ -47,7 +47,7 @@ impl<'a> SetChatTitle<'a> { let chat_id = chat_id.into(); let title = title.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, title, } diff --git a/src/requests/all/set_game_score.rs b/src/requests/all/set_game_score.rs index e21152d6..679ca708 100644 --- a/src/requests/all/set_game_score.rs +++ b/src/requests/all/set_game_score.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, Message}, Bot, }; +use std::sync::Arc; /// Use this method to set the score of the specified user in a game. /// @@ -20,10 +20,10 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetGameScore<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetGameScore { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, user_id: i32, @@ -33,7 +33,7 @@ pub struct SetGameScore<'a> { } #[async_trait::async_trait] -impl Request for SetGameScore<'_> { +impl Request for SetGameScore { type Output = Message; async fn send(&self) -> ResponseResult { @@ -47,15 +47,15 @@ impl Request for SetGameScore<'_> { } } -impl<'a> SetGameScore<'a> { +impl SetGameScore { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, user_id: i32, score: i32, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, user_id, score, diff --git a/src/requests/all/set_sticker_position_in_set.rs b/src/requests/all/set_sticker_position_in_set.rs index 11dfeceb..7bcea444 100644 --- a/src/requests/all/set_sticker_position_in_set.rs +++ b/src/requests/all/set_sticker_position_in_set.rs @@ -1,28 +1,28 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Use this method to move a sticker in a set created by the bot to a specific /// position. /// /// [The official docs](https://core.telegram.org/bots/api#setstickerpositioninset). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetStickerPositionInSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetStickerPositionInSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, sticker: String, position: i32, } #[async_trait::async_trait] -impl Request for SetStickerPositionInSet<'_> { +impl Request for SetStickerPositionInSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -36,14 +36,14 @@ impl Request for SetStickerPositionInSet<'_> { } } -impl<'a> SetStickerPositionInSet<'a> { - pub(crate) fn new(bot: &'a Bot, sticker: S, position: i32) -> Self +impl SetStickerPositionInSet { + pub(crate) fn new(bot: Arc, sticker: S, position: i32) -> Self where S: Into, { let sticker = sticker.into(); Self { - bot: BotWrapper(bot), + bot, sticker, position, } diff --git a/src/requests/all/set_webhook.rs b/src/requests/all/set_webhook.rs index 1233e0cf..ad293b5c 100644 --- a/src/requests/all/set_webhook.rs +++ b/src/requests/all/set_webhook.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{AllowedUpdate, InputFile, True}, Bot, }; +use std::sync::Arc; /// Use this method to specify a url and receive incoming updates via an /// outgoing webhook. @@ -25,10 +25,10 @@ use crate::{ /// /// [`Update`]: crate::types::Update #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetWebhook<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetWebhook { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, url: String, certificate: Option, max_connections: Option, @@ -36,7 +36,7 @@ pub struct SetWebhook<'a> { } #[async_trait::async_trait] -impl Request for SetWebhook<'_> { +impl Request for SetWebhook { type Output = True; async fn send(&self) -> ResponseResult { @@ -50,14 +50,14 @@ impl Request for SetWebhook<'_> { } } -impl<'a> SetWebhook<'a> { - pub(crate) fn new(bot: &'a Bot, url: U) -> Self +impl SetWebhook { + pub(crate) fn new(bot: Arc, url: U) -> Self where U: Into, { let url = url.into(); Self { - bot: BotWrapper(bot), + bot, url, certificate: None, max_connections: None, diff --git a/src/requests/all/stop_message_live_location.rs b/src/requests/all/stop_message_live_location.rs index 859a3a84..2c970b2b 100644 --- a/src/requests/all/stop_message_live_location.rs +++ b/src/requests/all/stop_message_live_location.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, Bot, }; +use std::sync::Arc; /// Use this method to stop updating a live location message before /// `live_period` expires. @@ -19,17 +19,17 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct StopMessageLiveLocation<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct StopMessageLiveLocation { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, reply_markup: Option, } #[async_trait::async_trait] -impl Request for StopMessageLiveLocation<'_> { +impl Request for StopMessageLiveLocation { type Output = Message; async fn send(&self) -> ResponseResult { @@ -43,13 +43,13 @@ impl Request for StopMessageLiveLocation<'_> { } } -impl<'a> StopMessageLiveLocation<'a> { +impl StopMessageLiveLocation { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, reply_markup: None, } diff --git a/src/requests/all/stop_poll.rs b/src/requests/all/stop_poll.rs index 6e99b6c5..f0d97b48 100644 --- a/src/requests/all/stop_poll.rs +++ b/src/requests/all/stop_poll.rs @@ -1,28 +1,28 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, Poll}, Bot, }; +use std::sync::Arc; /// Use this method to stop a poll which was sent by the bot. /// /// [The official docs](https://core.telegram.org/bots/api#stoppoll). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct StopPoll<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct StopPoll { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, message_id: i32, reply_markup: Option, } #[async_trait::async_trait] -impl Request for StopPoll<'_> { +impl Request for StopPoll { type Output = Poll; /// On success, the stopped [`Poll`] with the final results is returned. @@ -38,14 +38,14 @@ impl Request for StopPoll<'_> { .await } } -impl<'a> StopPoll<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, message_id: i32) -> Self +impl StopPoll { + pub(crate) fn new(bot: Arc, chat_id: C, message_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, message_id, reply_markup: None, diff --git a/src/requests/all/unban_chat_member.rs b/src/requests/all/unban_chat_member.rs index 749f31cc..eb643301 100644 --- a/src/requests/all/unban_chat_member.rs +++ b/src/requests/all/unban_chat_member.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to unban a previously kicked user in a supergroup or /// channel. The user will **not** return to the group or channel automatically, @@ -15,16 +15,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct UnbanChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct UnbanChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, } #[async_trait::async_trait] -impl Request for UnbanChatMember<'_> { +impl Request for UnbanChatMember { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,14 +38,14 @@ impl Request for UnbanChatMember<'_> { } } -impl<'a> UnbanChatMember<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, user_id: i32) -> Self +impl UnbanChatMember { + pub(crate) fn new(bot: Arc, chat_id: C, user_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, } diff --git a/src/requests/all/unpin_chat_message.rs b/src/requests/all/unpin_chat_message.rs index b4b42921..926719b2 100644 --- a/src/requests/all/unpin_chat_message.rs +++ b/src/requests/all/unpin_chat_message.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to unpin a message in a group, a supergroup, or a channel. /// @@ -16,15 +16,15 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct UnpinChatMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct UnpinChatMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for UnpinChatMessage<'_> { +impl Request for UnpinChatMessage { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,16 +38,13 @@ impl Request for UnpinChatMessage<'_> { } } -impl<'a> UnpinChatMessage<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl UnpinChatMessage { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target channel diff --git a/src/requests/all/upload_sticker_file.rs b/src/requests/all/upload_sticker_file.rs index 6183e2de..68015c1c 100644 --- a/src/requests/all/upload_sticker_file.rs +++ b/src/requests/all/upload_sticker_file.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{File, InputFile}, Bot, }; +use std::sync::Arc; /// Use this method to upload a .png file with a sticker for later use in /// [`Bot::create_new_sticker_set`] and [`Bot::add_sticker_to_set`] methods (can @@ -17,15 +17,15 @@ use crate::{ /// [`Bot::create_new_sticker_set`]: crate::Bot::create_new_sticker_set /// [`Bot::add_sticker_to_set`]: crate::Bot::add_sticker_to_set #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct UploadStickerFile<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct UploadStickerFile { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, user_id: i32, png_sticker: InputFile, } #[async_trait::async_trait] -impl Request for UploadStickerFile<'_> { +impl Request for UploadStickerFile { type Output = File; async fn send(&self) -> ResponseResult { @@ -39,14 +39,14 @@ impl Request for UploadStickerFile<'_> { } } -impl<'a> UploadStickerFile<'a> { +impl UploadStickerFile { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, user_id: i32, png_sticker: InputFile, ) -> Self { Self { - bot: BotWrapper(bot), + bot, user_id, png_sticker, } From afb6677a1e623830a903c107822b5934ea524221 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 03:51:50 +0600 Subject: [PATCH 38/91] Rename User -> UserInfo in examples/simple_dialogue --- examples/simple_dialogue/src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/simple_dialogue/src/main.rs b/examples/simple_dialogue/src/main.rs index d5dd3b26..b6eabcab 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -33,17 +33,17 @@ impl FavouriteMusic { } // ============================================================================ -// [A user's data] +// [A UserInfo's data] // ============================================================================ #[derive(Default)] -struct User { +struct UserInfo { full_name: Option, age: Option, favourite_music: Option, } -impl Display for User { +impl Display for UserInfo { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { write!( f, @@ -72,8 +72,8 @@ enum State { // [Control a dialogue] // ============================================================================ -type Ctx = DialogueHandlerCtx; -type Res = Result, RequestError>; +type Ctx = DialogueHandlerCtx; +type Res = Result, RequestError>; async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { ctx.answer("Good. Now choose your favourite music:") From 0ae2d975dfeb420f45e3987a8512248e3a583736 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 04:04:57 +0600 Subject: [PATCH 39/91] Add the state! macro --- examples/simple_dialogue/src/main.rs | 6 +++--- src/dispatching/dialogue/dialogue_handler_ctx.rs | 15 +++++++++++++++ src/prelude.rs | 1 + src/utils/command.rs | 5 +++-- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/examples/simple_dialogue/src/main.rs b/examples/simple_dialogue/src/main.rs index b6eabcab..cddd6bfd 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -87,7 +87,7 @@ async fn start(mut ctx: Ctx) -> Res { ctx.answer("Let's start! First, what's your full name?") .send() .await?; - ctx.dialogue.state = State::FullName; + state!(ctx, State::FullName); next(ctx.dialogue) } @@ -96,7 +96,7 @@ async fn full_name(mut ctx: Ctx) -> Res { .send() .await?; ctx.dialogue.data.full_name = Some(ctx.update.text().unwrap().to_owned()); - ctx.dialogue.state = State::Age; + state!(ctx, State::Age); next(ctx.dialogue) } @@ -105,7 +105,7 @@ async fn age(mut ctx: Ctx) -> Res { Ok(ok) => { send_favourite_music_types(&ctx).await?; ctx.dialogue.data.age = Some(ok); - ctx.dialogue.state = State::FavouriteMusic; + state!(ctx, State::FavouriteMusic); } Err(_) => ctx .answer("Oh, please, enter a number!") diff --git a/src/dispatching/dialogue/dialogue_handler_ctx.rs b/src/dispatching/dialogue/dialogue_handler_ctx.rs index 6e4a283d..d1725d2a 100644 --- a/src/dispatching/dialogue/dialogue_handler_ctx.rs +++ b/src/dispatching/dialogue/dialogue_handler_ctx.rs @@ -20,6 +20,21 @@ pub struct DialogueHandlerCtx { pub dialogue: Dialogue, } +/// Sets a new state. +/// +/// Use it like this: `state!(ctx, State::RequestAge)`, where `ctx` is +/// [`DialogueHandlerCtx`] and `State::RequestAge` is of type +/// `State`. +/// +/// [`DialogueHandlerCtx`]: +/// crate::dispatching::dialogue::DialogueHandlerCtx +#[macro_export] +macro_rules! state { + ($ctx:ident, $state:expr) => { + $ctx.dialogue.state = $state; + }; +} + impl GetChatId for DialogueHandlerCtx where Upd: GetChatId, diff --git a/src/prelude.rs b/src/prelude.rs index c70efa5b..d5fb766b 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,6 +9,7 @@ pub use crate::{ Dispatcher, DispatcherHandlerCtx, }, requests::{Request, ResponseResult}, + state, types::Message, Bot, RequestError, }; diff --git a/src/utils/command.rs b/src/utils/command.rs index 27969cac..53350936 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -39,8 +39,9 @@ //! assert_eq!(args, vec!["3", "hours"]); //! ``` //! -//! [`parse_command`]: crate::utils::parse_command -//! [`parse_command_with_prefix`]: crate::utils::parse_command_with_prefix +//! [`parse_command`]: crate::utils::command::parse_command +//! [`parse_command_with_prefix`]: +//! crate::utils::command::parse_command_with_prefix pub use teloxide_macros::BotCommand; From d803d8197a98af448d54692242d2c81ba75dfa41 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 16:58:32 +0600 Subject: [PATCH 40/91] Add TODO (examples/simple_dialogue) --- examples/simple_dialogue/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/simple_dialogue/src/main.rs b/examples/simple_dialogue/src/main.rs index cddd6bfd..7c6aae3c 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -36,6 +36,7 @@ impl FavouriteMusic { // [A UserInfo's data] // ============================================================================ +// TODO: implement a type-safe UserInfo without lots of .unwrap #[derive(Default)] struct UserInfo { full_name: Option, From 4b0dea21f1665a414eef7a25745c259d7e08c3db Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 19:12:14 +0600 Subject: [PATCH 41/91] Hot fixes --- examples/ping_pong_bot/src/main.rs | 7 ++++--- src/bot/mod.rs | 14 ++++++-------- src/dispatching/dispatcher.rs | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index d8eb2abb..5aa70401 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -6,9 +6,10 @@ async fn main() { pretty_env_logger::init(); log::info!("Starting the ping-pong bot!"); - Dispatcher::new(Bot::new("MyAwesomeToken")) - .message_handler(|ctx: HandlerCtx| async move { - ctx.reply("pong").await + Dispatcher::::new(Bot::new("MyAwesomeToken")) + .message_handler(&|ctx: DispatcherHandlerCtx| async move { + ctx.answer("pong").send().await?; + Ok(()) }) .dispatch() .await; diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 5e26de9e..0dbd9d89 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,4 +1,5 @@ use reqwest::Client; +use std::sync::Arc; mod api; mod download; @@ -11,24 +12,21 @@ pub struct Bot { } impl Bot { - pub fn new(token: S) -> Self + pub fn new(token: S) -> Arc where S: Into, { - Bot { - token: token.into(), - client: Client::new(), - } + Self::with_client(token, Client::new()) } - pub fn with_client(token: S, client: Client) -> Self + pub fn with_client(token: S, client: Client) -> Arc where S: Into, { - Bot { + Arc::new(Bot { token: token.into(), client, - } + }) } } diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 00b62163..ba2e4ee1 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -53,9 +53,9 @@ where { /// Constructs a new dispatcher with this `bot`. #[must_use] - pub fn new(bot: Bot) -> Self { + pub fn new(bot: Arc) -> Self { Self { - bot: Arc::new(bot), + bot, handlers_error_handler: Box::new(LoggingErrorHandler::new( "An error from a Dispatcher's handler", )), From 14561e437faa667e07775a671f08ae883453b159 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 19:34:02 +0600 Subject: [PATCH 42/91] Fix fmtcheck --- teloxide-macros/src/attr.rs | 27 +++++------ teloxide-macros/src/command.rs | 16 ++++--- teloxide-macros/src/enum_attributes.rs | 14 +++--- teloxide-macros/src/lib.rs | 64 ++++++++++++++------------ 4 files changed, 63 insertions(+), 58 deletions(-) diff --git a/teloxide-macros/src/attr.rs b/teloxide-macros/src/attr.rs index 281baa37..f57c1783 100644 --- a/teloxide-macros/src/attr.rs +++ b/teloxide-macros/src/attr.rs @@ -1,11 +1,12 @@ -use syn::parse::{Parse, ParseStream}; -use syn::{LitStr, Token}; - +use syn::{ + parse::{Parse, ParseStream}, + LitStr, Token, +}; pub enum BotCommandAttribute { Prefix, Description, - RenameRule + RenameRule, } impl Parse for BotCommandAttribute { @@ -15,27 +16,23 @@ impl Parse for BotCommandAttribute { "prefix" => Ok(BotCommandAttribute::Prefix), "description" => Ok(BotCommandAttribute::Description), "rename" => Ok(BotCommandAttribute::RenameRule), - _ => Err(syn::Error::new(name_arg.span(), "unexpected argument")) + _ => Err(syn::Error::new(name_arg.span(), "unexpected argument")), } } } pub struct Attr { name: BotCommandAttribute, - value: String + value: String, } -impl Parse for Attr -{ +impl Parse for Attr { fn parse(input: ParseStream) -> Result { let name = input.parse::()?; input.parse::()?; let value = input.parse::()?.value(); - Ok(Self { - name, - value - }) + Ok(Self { name, value }) } } @@ -50,7 +47,7 @@ impl Attr { } pub struct VecAttrs { - pub data: Vec + pub data: Vec, } impl Parse for VecAttrs { @@ -62,8 +59,6 @@ impl Parse for VecAttrs { input.parse::()?; } } - Ok(Self { - data - }) + Ok(Self { data }) } } diff --git a/teloxide-macros/src/command.rs b/teloxide-macros/src/command.rs index 1569b5c8..69be6e58 100644 --- a/teloxide-macros/src/command.rs +++ b/teloxide-macros/src/command.rs @@ -1,5 +1,7 @@ -use crate::attr::{Attr, BotCommandAttribute}; -use crate::rename_rules::rename_by_rule; +use crate::{ + attr::{Attr, BotCommandAttribute}, + rename_rules::rename_by_rule, +}; pub struct Command { pub prefix: Option, @@ -33,7 +35,7 @@ impl Command { struct CommandAttrs { prefix: Option, description: Option, - rename: Option + rename: Option, } fn parse_attrs(attrs: &[Attr]) -> Result { @@ -44,7 +46,9 @@ fn parse_attrs(attrs: &[Attr]) -> Result { for attr in attrs { match attr.name() { BotCommandAttribute::Prefix => prefix = Some(attr.value()), - BotCommandAttribute::Description => description = Some(attr.value()), + BotCommandAttribute::Description => { + description = Some(attr.value()) + } BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), #[allow(unreachable_patterns)] _ => return Err("unexpected attribute".to_owned()), @@ -54,6 +58,6 @@ fn parse_attrs(attrs: &[Attr]) -> Result { Ok(CommandAttrs { prefix, description, - rename: rename_rule + rename: rename_rule, }) -} \ No newline at end of file +} diff --git a/teloxide-macros/src/enum_attributes.rs b/teloxide-macros/src/enum_attributes.rs index 08a0c021..b54dc9a6 100644 --- a/teloxide-macros/src/enum_attributes.rs +++ b/teloxide-macros/src/enum_attributes.rs @@ -15,14 +15,14 @@ impl CommandEnum { let rename = attrs.rename; if let Some(rename_rule) = &rename { match rename_rule.as_str() { - "lowercase" => {}, + "lowercase" => {} _ => return Err("disallowed value".to_owned()), } } Ok(Self { prefix, description, - rename_rule: rename + rename_rule: rename, }) } } @@ -30,7 +30,7 @@ impl CommandEnum { struct CommandAttrs { prefix: Option, description: Option, - rename: Option + rename: Option, } fn parse_attrs(attrs: &[Attr]) -> Result { @@ -41,7 +41,9 @@ fn parse_attrs(attrs: &[Attr]) -> Result { for attr in attrs { match attr.name() { BotCommandAttribute::Prefix => prefix = Some(attr.value()), - BotCommandAttribute::Description => description = Some(attr.value()), + BotCommandAttribute::Description => { + description = Some(attr.value()) + } BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), #[allow(unreachable_patterns)] _ => return Err("unexpected attribute".to_owned()), @@ -51,6 +53,6 @@ fn parse_attrs(attrs: &[Attr]) -> Result { Ok(CommandAttrs { prefix, description, - rename: rename_rule + rename: rename_rule, }) -} \ No newline at end of file +} diff --git a/teloxide-macros/src/lib.rs b/teloxide-macros/src/lib.rs index c0aa0842..b15f3492 100644 --- a/teloxide-macros/src/lib.rs +++ b/teloxide-macros/src/lib.rs @@ -5,13 +5,15 @@ mod rename_rules; extern crate proc_macro; extern crate syn; +use crate::{ + attr::{Attr, VecAttrs}, + command::Command, + enum_attributes::CommandEnum, + rename_rules::rename_by_rule, +}; use proc_macro::TokenStream; use quote::{quote, ToTokens}; -use syn::{DeriveInput, parse_macro_input}; -use crate::command::Command; -use crate::attr::{Attr, VecAttrs}; -use crate::enum_attributes::CommandEnum; -use crate::rename_rules::rename_by_rule; +use syn::{parse_macro_input, DeriveInput}; macro_rules! get_or_return { ($($some:tt)*) => { @@ -35,7 +37,8 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { Err(e) => return compile_error(e), }; - let variants: Vec<&syn::Variant> = data_enum.variants.iter().map(|attr| attr).collect(); + let variants: Vec<&syn::Variant> = + data_enum.variants.iter().map(|attr| attr).collect(); let mut variant_infos = vec![]; for variant in variants.iter() { @@ -44,10 +47,10 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { match attr.parse_args::() { Ok(mut attrs_) => { attrs.append(attrs_.data.as_mut()); - }, + } Err(e) => { return compile_error(e.to_compile_error()); - }, + } } } match Command::try_from(attrs.as_slice(), &variant.ident.to_string()) { @@ -60,35 +63,34 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { let variant_name = variant_infos.iter().map(|info| { if info.renamed { info.name.clone() - } - else if let Some(rename_rule) = &command_enum.rename_rule { + } else if let Some(rename_rule) = &command_enum.rename_rule { rename_by_rule(&info.name, rename_rule) - } - else { + } else { info.name.clone() } }); let variant_prefixes = variant_infos.iter().map(|info| { - if let Some(prefix) = &info.prefix { - prefix - } - else if let Some(prefix) = &command_enum.prefix { - prefix - } - else { - "/" - } + if let Some(prefix) = &info.prefix { + prefix + } else if let Some(prefix) = &command_enum.prefix { + prefix + } else { + "/" + } }); - let variant_str1 = variant_prefixes.zip(variant_name).map(|(prefix, command)| prefix.to_string() + command.as_str()); + let variant_str1 = variant_prefixes + .zip(variant_name) + .map(|(prefix, command)| prefix.to_string() + command.as_str()); let variant_str2 = variant_str1.clone(); - let variant_description = variant_infos.iter().map(|info| info.description.as_deref().unwrap_or("")); + let variant_description = variant_infos + .iter() + .map(|info| info.description.as_deref().unwrap_or("")); let ident = &input.ident; let global_description = if let Some(s) = &command_enum.description { quote! { #s, "\n", } - } - else { + } else { quote! {} }; @@ -120,20 +122,22 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { match &input.data { syn::Data::Enum(data) => Ok(data), - _ => Err(compile_error("TelegramBotCommand allowed only for enums")) + _ => Err(compile_error("TelegramBotCommand allowed only for enums")), } } -fn parse_attributes(input: &[syn::Attribute]) -> Result, TokenStream> { +fn parse_attributes( + input: &[syn::Attribute], +) -> Result, TokenStream> { let mut enum_attrs = Vec::new(); for attr in input.iter() { match attr.parse_args::() { Ok(mut attrs_) => { enum_attrs.append(attrs_.data.as_mut()); - }, + } Err(e) => { return Err(compile_error(e.to_compile_error())); - }, + } } } Ok(enum_attrs) @@ -141,7 +145,7 @@ fn parse_attributes(input: &[syn::Attribute]) -> Result, TokenStream> fn compile_error(data: T) -> TokenStream where - T: ToTokens + T: ToTokens, { TokenStream::from(quote! { compile_error!(#data) }) } From 47a70b8587227c6652871e7d11f326a50288181e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 19:55:24 +0600 Subject: [PATCH 43/91] Fix the documentation --- src/dispatching/dialogue/mod.rs | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 4294eb5e..3602a669 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -2,35 +2,37 @@ //! //! There are four main components: //! -//! 1. Your session type `Session`, which designates a dialogue state at the -//! current moment. -//! 2. [`Storage`], which encapsulates all the sessions. -//! 3. Your handler, which receives an update and turns your session into the +//! 1. Your type `State`, which designates a dialogue state at the current +//! moment. +//! 2. Your type `T`, which represents dialogue data. +//! 3. [`Dialogue`], which encapsulates the two types, described above. +//! 4. [`Storage`], which encapsulates all the sessions. +//! 5. Your handler, which receives an update and turns your session into the //! next state. -//! 4. [`SessionDispatcher`], which encapsulates your handler, [`Storage`], and +//! 6. [`DialogueDispatcher`], which encapsulates your handler, [`Storage`], and //! implements [`CtxHandler`]. //! -//! You supply [`SessionDispatcher`] into [`Dispatcher`]. Every time -//! [`Dispatcher`] calls `SessionDispatcher::handle_ctx(...)`, the following +//! You supply [`DialogueDispatcher`] into [`Dispatcher`]. Every time +//! [`Dispatcher`] calls `DialogueDispatcher::handle_ctx(...)`, the following //! steps are executed: //! -//! 1. If a storage doesn't contain a session from this chat, supply -//! `Session::default()` into you handler, otherwise, supply the saved session +//! 1. If a storage doesn't contain a dialogue from this chat, supply +//! `Dialogue::default()` into you handler, otherwise, supply the saved session //! from this chat. -//! 3. If a handler has returned [`SessionState::Exit`], remove the session -//! from the storage, otherwise ([`SessionState::Next`]) force the storage to +//! 3. If a handler has returned [`DialogueStage::Exit`], remove the session +//! from the storage, otherwise ([`DialogueStage::Next`]) force the storage to //! update the session. //! +//! Please, see https://github.com/teloxide/teloxide/tree/dev/examples/simple_dialogue. +//! //! [`Storage`]: crate::dispatching::session::Storage -//! [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher -//! [`SessionState::Exit`]: -//! crate::dispatching::session::SessionState::Exit -//! [`SessionState::Next`]: crate::dispatching::session::SessionState::Next +//! [`DialogueDispatcher`]: crate::dispatching::session::SessionDispatcher +//! [`DialogueStage::Exit`]: +//! crate::dispatching::dialogue::DialogueStage::Exit +//! [`DialogueStage::Next`]: crate::dispatching::dialogue::DialogueStage::Next //! [`CtxHandler`]: crate::dispatching::CtxHandler //! [`Dispatcher`]: crate::dispatching::Dispatcher -// TODO: examples - #![allow(clippy::module_inception)] #![allow(clippy::type_complexity)] From 01645a2c5a1e0ffeb6b84a5075cf45448752d641 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 20:19:11 +0600 Subject: [PATCH 44/91] Eventually fix the docs --- src/dispatching/dialogue/mod.rs | 7 ++++--- src/dispatching/mod.rs | 36 ++++++++++++++++++++++----------- src/errors.rs | 4 ++-- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 3602a669..3cbde6b1 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -23,15 +23,16 @@ //! from the storage, otherwise ([`DialogueStage::Next`]) force the storage to //! update the session. //! -//! Please, see https://github.com/teloxide/teloxide/tree/dev/examples/simple_dialogue. +//! Please, see [examples/simple_dialogue] as an example. //! -//! [`Storage`]: crate::dispatching::session::Storage -//! [`DialogueDispatcher`]: crate::dispatching::session::SessionDispatcher +//! [`Storage`]: crate::dispatching::dialogue::Storage +//! [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher //! [`DialogueStage::Exit`]: //! crate::dispatching::dialogue::DialogueStage::Exit //! [`DialogueStage::Next`]: crate::dispatching::dialogue::DialogueStage::Next //! [`CtxHandler`]: crate::dispatching::CtxHandler //! [`Dispatcher`]: crate::dispatching::Dispatcher +//! [examples/simple_dialogue]: https://github.com/teloxide/teloxide/tree/dev/examples/simple_dialogue #![allow(clippy::module_inception)] #![allow(clippy::type_complexity)] diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 8c158038..77befc3d 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,19 +1,26 @@ //! Updates dispatching. //! -//! The key type here is [`Dispatcher`]. It encapsulates middlewares, handlers -//! for [10 update kinds], and [`ErrorHandler`] for them. When [`Update`] is -//! received from Telegram, the following steps are executed: +//! The key type here is [`Dispatcher`]. It encapsulates [`Bot`], handlers for +//! [11 update kinds] (+ for [`Update`]) and [`ErrorHandler`] for them. When +//! [`Update`] is received from Telegram, the following steps are executed: //! -//! 1. It is supplied into all registered middlewares. -//! 2. It is supplied to an appropriate handler. -//! 3. If a handler has returned an error, the error is supplied into an error -//! handler. +//! 1. It is supplied into an appropriate handler (the first ones is those who +//! accept [`Update`]). +//! 2. If a handler failed, invoke [`ErrorHandler`] with the corresponding +//! error. +//! 3. If a handler has returned [`DispatcherHandlerResult`] with `None`, +//! terminate the pipeline, otherwise supply an update into the next handler +//! (back to step 1). //! -//! That's simple! +//! The pipeline is executed until either all the registered handlers were +//! executed, or one of handlers has terminated the pipeline. That's simple! //! -//! Note that handlers implement [`CtxHandler`], which means that you are able -//! to supply [`SessionDispatcher`] as a handler, since it implements +//! 1. Note that handlers implement [`CtxHandler`], which means that you are +//! able to supply [`DialogueDispatcher`] as a handler, since it implements //! [`CtxHandler`] too! +//! 2. Note that you don't always need to return [`DispatcherHandlerResult`] +//! explicitly, because of automatic conversions. Just return `Result<(), E>` if +//! you want to terminate the pipeline (see the example below). //! //! ## Examples //! The ping-pong bot ([full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/)): @@ -35,12 +42,17 @@ //! # } //! ``` //! +//! For a bit more complicated example, please see [examples/simple_dialogue]. +//! //! [`Dispatcher`]: crate::dispatching::Dispatcher -//! [10 update kinds]: crate::types::UpdateKind +//! [11 update kinds]: crate::types::UpdateKind //! [`Update`]: crate::types::Update //! [`ErrorHandler`]: crate::dispatching::ErrorHandler //! [`CtxHandler`]: crate::dispatching::CtxHandler -//! [`SessionDispatcher`]: crate::dispatching::SessionDispatcher +//! [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher +//! [`DispatcherHandlerResult`]: crate::dispatching::DispatcherHandlerResult +//! [`Bot`]: crate::Bot +//! [examples/simple_dialogue]: https://github.com/teloxide/teloxide/tree/dev/examples/simple_dialogue mod ctx_handlers; pub mod dialogue; diff --git a/src/errors.rs b/src/errors.rs index 58c0b987..d64d073b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -348,9 +348,9 @@ pub enum ApiErrorKind { /// chat. /// /// May happen in methods: - /// 1. [`PinMessage`] + /// 1. [`PinChatMessage`] /// - /// [`PinMessage`]: crate::requests::PinMessage + /// [`PinChatMessage`]: crate::requests::PinChatMessage #[serde(rename = "Bad Request: not enough rights to pin a message")] NotEnoughRightsToPinMessage, From 868f39c2e8e413c6e3eccb4e452d4eaaa9d71d09 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 20:30:41 +0600 Subject: [PATCH 45/91] Add log::trace to Dispatcher --- src/dispatching/dispatcher.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index ba2e4ee1..4844af34 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -230,6 +230,8 @@ where update_listener .for_each_concurrent(None, move |update| async move { + log::trace!("Dispatcher received an update: {:?}", update); + let update = match update { Ok(update) => update, Err(error) => { From 423495d1c7f9356ef03f20929cf3e25c864ca119 Mon Sep 17 00:00:00 2001 From: Maximilian Siling Date: Tue, 11 Feb 2020 19:28:46 +0300 Subject: [PATCH 46/91] Fix parsing for posts in private channels --- src/types/message.rs | 4 ++-- src/types/message_entity.rs | 4 ++-- src/types/update.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/types/message.rs b/src/types/message.rs index 277287da..46c341b3 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -35,7 +35,7 @@ pub enum MessageKind { Common { /// Sender, empty for messages sent to channels. #[serde(flatten)] - from: Sender, + from: Option, #[serde(flatten)] forward_kind: ForwardKind, @@ -352,7 +352,7 @@ mod getters { /// NOTE: this is getter for both `from` and `author_signature` pub fn from(&self) -> Option<&Sender> { match &self.kind { - Common { from, .. } => Some(from), + Common { from, .. } => from.as_ref(), _ => None, } } diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index 75ae45b7..550145d2 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -116,14 +116,14 @@ mod tests { photo: None, }, kind: MessageKind::Common { - from: Sender::User(User { + from: Some(Sender::User(User { id: 0, is_bot: false, first_name: "".to_string(), last_name: None, username: None, language_code: None, - }), + })), forward_kind: ForwardKind::Origin { reply_to_message: None, }, diff --git a/src/types/update.rs b/src/types/update.rs index e3e18a0d..66ee1bfd 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -158,14 +158,14 @@ mod test { photo: None, }, kind: MessageKind::Common { - from: Sender::User(User { + from: Some(Sender::User(User { id: 218_485_655, is_bot: false, first_name: String::from("Waffle"), last_name: None, username: Some(String::from("WaffleLapkin")), language_code: Some(LanguageCode::EN), - }), + })), forward_kind: ForwardKind::Origin { reply_to_message: None, }, From ca9af25f922930bf794111110f6b80fb2c5850ef Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 11 Feb 2020 22:56:48 +0600 Subject: [PATCH 47/91] Use log::error in LoggingErrorHandler --- src/dispatching/error_handlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/error_handlers.rs b/src/dispatching/error_handlers.rs index d7289a14..c9e1aa44 100644 --- a/src/dispatching/error_handlers.rs +++ b/src/dispatching/error_handlers.rs @@ -145,7 +145,7 @@ where where E: 'a, { - log::debug!("{text}: {:?}", error, text = self.text); + log::error!("{text}: {:?}", error, text = self.text); Box::pin(async {}) } } From 2242a8bad899010f730639115a048e78452e25cd Mon Sep 17 00:00:00 2001 From: p0lunin Date: Tue, 11 Feb 2020 21:34:32 +0200 Subject: [PATCH 48/91] replace type Message.from from Sender to User --- src/types/message.rs | 19 ++++--------------- src/types/message_entity.rs | 6 +++--- src/types/update.rs | 18 ++++++------------ 3 files changed, 13 insertions(+), 30 deletions(-) diff --git a/src/types/message.rs b/src/types/message.rs index 46c341b3..296078d0 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -35,7 +35,7 @@ pub enum MessageKind { Common { /// Sender, empty for messages sent to channels. #[serde(flatten)] - from: Option, + from: User, #[serde(flatten)] forward_kind: ForwardKind, @@ -144,17 +144,6 @@ pub enum MessageKind { }, } -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub enum Sender { - /// Sender of a message from chat. - #[serde(rename = "from")] - User(User), - - /// Signature of a sender of a message from a channel. - #[serde(rename = "author_signature")] - Signature(String), -} - #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum ForwardedFrom { #[serde(rename = "forward_from")] @@ -340,7 +329,7 @@ mod getters { Pinned, SuccessfulPayment, SupergroupChatCreated, }, }, - Chat, ForwardedFrom, Message, MessageEntity, PhotoSize, Sender, True, + Chat, ForwardedFrom, Message, MessageEntity, PhotoSize, True, User, }; @@ -350,9 +339,9 @@ mod getters { /// [telegram docs]: https://core.telegram.org/bots/api#message impl Message { /// NOTE: this is getter for both `from` and `author_signature` - pub fn from(&self) -> Option<&Sender> { + pub fn from(&self) -> Option<&User> { match &self.kind { - Common { from, .. } => from.as_ref(), + Common { from, .. } => Some(&from), _ => None, } } diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index 550145d2..fd816503 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -51,7 +51,7 @@ impl MessageEntity { mod tests { use super::*; use crate::types::{ - Chat, ChatKind, ForwardKind, MediaKind, MessageKind, Sender, + Chat, ChatKind, ForwardKind, MediaKind, MessageKind, }; #[test] @@ -116,14 +116,14 @@ mod tests { photo: None, }, kind: MessageKind::Common { - from: Some(Sender::User(User { + from: Some(User { id: 0, is_bot: false, first_name: "".to_string(), last_name: None, username: None, language_code: None, - })), + }), forward_kind: ForwardKind::Origin { reply_to_message: None, }, diff --git a/src/types/update.rs b/src/types/update.rs index 66ee1bfd..72244431 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::types::{ CallbackQuery, Chat, ChosenInlineResult, InlineQuery, Message, Poll, - PollAnswer, PreCheckoutQuery, Sender, ShippingQuery, User, + PollAnswer, PreCheckoutQuery, ShippingQuery, User, }; /// This [object] represents an incoming update. @@ -80,14 +80,8 @@ pub enum UpdateKind { impl Update { pub fn user(&self) -> Option<&User> { match &self.kind { - UpdateKind::Message(m) => match m.from() { - Some(Sender::User(user)) => Some(user), - _ => None, - }, - UpdateKind::EditedMessage(m) => match m.from() { - Some(Sender::User(user)) => Some(user), - _ => None, - }, + UpdateKind::Message(m) => m.from(), + UpdateKind::EditedMessage(m) => m.from(), UpdateKind::CallbackQuery(query) => Some(&query.from), UpdateKind::ChosenInlineResult(chosen) => Some(&chosen.from), UpdateKind::InlineQuery(query) => Some(&query.from), @@ -114,7 +108,7 @@ impl Update { mod test { use crate::types::{ Chat, ChatKind, ForwardKind, LanguageCode, MediaKind, Message, - MessageKind, Sender, Update, UpdateKind, User, + MessageKind, Update, UpdateKind, User, }; // TODO: more tests for deserialization @@ -158,14 +152,14 @@ mod test { photo: None, }, kind: MessageKind::Common { - from: Some(Sender::User(User { + from: Some(User { id: 218_485_655, is_bot: false, first_name: String::from("Waffle"), last_name: None, username: Some(String::from("WaffleLapkin")), language_code: Some(LanguageCode::EN), - })), + }), forward_kind: ForwardKind::Origin { reply_to_message: None, }, From 98345b4d45f70b8bb98fc377bbc648c7a1f07ac3 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Tue, 11 Feb 2020 21:35:06 +0200 Subject: [PATCH 49/91] added Default to ChatPermissions --- src/types/chat_permissions.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/types/chat_permissions.rs b/src/types/chat_permissions.rs index a82b79d5..6f9b7ea3 100644 --- a/src/types/chat_permissions.rs +++ b/src/types/chat_permissions.rs @@ -39,3 +39,18 @@ pub struct ChatPermissions { /// supergroups. pub can_pin_messages: Option, } + +impl Default for ChatPermissions { + fn default() -> Self { + Self { + can_send_messages: None, + can_send_media_messages: None, + can_send_polls: None, + can_send_other_messages: None, + can_add_web_page_previews: None, + can_change_info: None, + can_invite_users: None, + can_pin_messages: None + } + } +} From 2a436789740242206f58f7615eda20c17339ac82 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Tue, 11 Feb 2020 21:35:22 +0200 Subject: [PATCH 50/91] added example --- examples/admin_bot/Cargo.toml | 16 ++++ examples/admin_bot/src/main.rs | 130 +++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 examples/admin_bot/Cargo.toml create mode 100644 examples/admin_bot/src/main.rs diff --git a/examples/admin_bot/Cargo.toml b/examples/admin_bot/Cargo.toml new file mode 100644 index 00000000..56c7a9f9 --- /dev/null +++ b/examples/admin_bot/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "admin_bot" +version = "0.1.0" +authors = ["p0lunin "] +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 = "../../" } + +[profile.release] +lto = true \ No newline at end of file diff --git a/examples/admin_bot/src/main.rs b/examples/admin_bot/src/main.rs new file mode 100644 index 00000000..9685175c --- /dev/null +++ b/examples/admin_bot/src/main.rs @@ -0,0 +1,130 @@ +use teloxide::prelude::*; +use teloxide::utils::command::BotCommand; +use teloxide::types::ChatPermissions; + +type Ctx = DispatcherHandlerCtx; + +#[derive(BotCommand)] +#[command(rename = "lowercase", description = "use command in format /%command% %num% %unit%")] +enum Command { + #[command(description = "kick user from chat.")] + Kick, + #[command(description = "ban user in chat.")] + Ban, + #[command(description = "mute user in chat.")] + Mute, + + Help, +} + +fn calc_restrict_time(num: i32, unit: &str) -> Result { + match unit { + "h"|"hours" => Ok(num * 3600), + "m"|"minutes" => Ok(num * 60), + "s"|"seconds" => Ok(num), + _ => Err("allowed units: *h*, *m*, *s*") + } +} + +fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> { + let num = match args.get(0) { + Some(s) => s, + None => return Err("Use command in format /%command% %num% %unit%"), + }; + let unit = match args.get(1) { + Some(s) => s, + None => return Err("Use command in format /%command% %num% %unit%") + }; + + match num.parse::() { + Ok(n) => Ok((n, unit)), + Err(_) => Err("input positive number!"), + } +} + +fn parse_time_restrict(args: Vec<&str>) -> Result { + let (num, unit) = parse_args(args)?; + calc_restrict_time(num, unit) +} + +async fn handle_command(ctx: Ctx) -> Result<(), ()> { + if let Some(text) = ctx.update.text() { + let (command, args): (Command, Vec<&str>) = Command::parse(text).unwrap_or((Command::Help, vec![])); + + match command { + Command::Help => { + ctx.answer(Command::descriptions()).send().await; + } + Command::Kick => { + match ctx.update.reply_to_message() { + Some(mes) => { + ctx.bot.unban_chat_member( + mes.chat_id(), + mes.from().unwrap().id + ).send().await; + }, + None => { + ctx.reply_to("Use this command in reply to another message").send().await; + } + } + } + Command::Ban => { + match ctx.update.reply_to_message() { + Some(mes) => match parse_time_restrict(args) { + Ok(time) => { + dbg!(&ctx.update); + ctx.bot.kick_chat_member( + mes.chat_id(), + mes.from().unwrap().id + ) + .until_date(time) + .send() + .await; + } + Err(msg) => { + ctx.answer(msg).send().await; + }, + }, + None => { + ctx.reply_to("Use this command in reply to another message").send().await; + }, + } + } + Command::Mute => { + match ctx.update.reply_to_message() { + Some(mes) => match parse_time_restrict(args) { + Ok(time) => { + ctx.bot.restrict_chat_member( + mes.chat_id(), + mes.from().unwrap().id, + ChatPermissions::default() + ) + .until_date(time) + .send() + .await; + } + Err(msg) => { + ctx.answer(msg).send().await; + } + }, + None => { + ctx.reply_to("Use this command in reply to another message").send().await; + }, + } + } + }; + } + + Ok(()) +} + +#[tokio::main] +async fn main() { + pretty_env_logger::init(); + + let bot = Bot::new("865293832:AAHD-ox6hi6Ws_pxBFb8VIp1uymHoMab2MM"); + Dispatcher::new(bot) + .message_handler(&handle_command) + .dispatch() + .await +} From 47aab582302f77ddd3f21e3195ae091bb6f0982f Mon Sep 17 00:00:00 2001 From: p0lunin Date: Tue, 11 Feb 2020 21:37:55 +0200 Subject: [PATCH 51/91] remove flatten in Message.from --- src/types/message.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/message.rs b/src/types/message.rs index 296078d0..c9709dc0 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -34,7 +34,6 @@ pub struct Message { pub enum MessageKind { Common { /// Sender, empty for messages sent to channels. - #[serde(flatten)] from: User, #[serde(flatten)] From 7433a2b072b82dee171dedb8fab38f3cdaec4efc Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 12 Feb 2020 01:45:38 +0600 Subject: [PATCH 52/91] Don't fail other updates if one is not parsed --- src/dispatching/update_listeners.rs | 16 ++++++++++++++++ src/requests/all/get_updates.rs | 25 ++++++++++++++++++++----- src/types/update.rs | 29 +++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 21c4c1a2..d5b8864b 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -155,6 +155,22 @@ pub fn polling( let updates = match req.send().await { Err(err) => vec![Err(err)], Ok(updates) => { + let updates = updates + .into_iter() + .filter(|update| match update { + Err(error) => { + log::error!("Cannot parse an update: {:?}! \ + This is a bug in teloxide, please open an issue here: \ + https://github.com/teloxide/teloxide/issues.", error); + false + } + Ok(_) => true, + }) + .map(|update| { + update.expect("See the previous .filter() call") + }) + .collect::>(); + if let Some(upd) = updates.last() { offset = upd.id + 1; } diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs index 2ef81d17..05e9e84b 100644 --- a/src/requests/all/get_updates.rs +++ b/src/requests/all/get_updates.rs @@ -4,8 +4,9 @@ use crate::{ net, requests::{Request, ResponseResult}, types::{AllowedUpdate, Update}, - Bot, + Bot, RequestError, }; +use serde_json::Value; use std::sync::Arc; /// Use this method to receive incoming updates using long polling ([wiki]). @@ -31,16 +32,30 @@ pub struct GetUpdates { #[async_trait::async_trait] impl Request for GetUpdates { - type Output = Vec; + type Output = Vec>; - async fn send(&self) -> ResponseResult> { - net::request_json( + /// Deserialize to `Vec>` instead of + /// `Vec`, because we want to parse the rest of updates even if our + /// library hasn't parsed one. + async fn send(&self) -> ResponseResult>> { + let value: Value = net::request_json( self.bot.client(), self.bot.token(), "getUpdates", &self, ) - .await + .await?; + + match value { + Value::Array(array) => Ok(array + .into_iter() + .map(|value| serde_json::from_str(&value.to_string())) + .collect()), + _ => Err(RequestError::InvalidJson( + serde_json::from_value::>(value) + .expect_err("get_update must return Value::Array"), + )), + } } } diff --git a/src/types/update.rs b/src/types/update.rs index 66ee1bfd..af1322ae 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -182,4 +182,33 @@ mod test { let actual = serde_json::from_str::(json).unwrap(); assert_eq!(expected, actual); } + + #[test] + fn de_private_chat_text_message() { + let text = r#" + { + "message": { + "chat": { + "first_name": "Hirrolot", + "id": 408258968, + "type": "private", + "username": "hirrolot" + }, + "date": 1581448857, + "from": { + "first_name": "Hirrolot", + "id": 408258968, + "is_bot": false, + "language_code": "en", + "username": "hirrolot" + }, + "message_id": 154, + "text": "4" + }, + "update_id": 306197398 + } +"#; + + assert!(serde_json::from_str::(text).is_ok()); + } } From dce662064fff3edc3333d2be24b5886e4b12e286 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 12 Feb 2020 04:50:10 +0600 Subject: [PATCH 53/91] Fix update_listeners --- examples/ping_pong_bot/src/main.rs | 1 + examples/simple_dialogue/src/main.rs | 1 + src/dispatching/update_listeners.rs | 26 ++++++++++++++++++++------ src/requests/all/get_updates.rs | 11 ++++++++--- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 5aa70401..70c30a88 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -3,6 +3,7 @@ use teloxide::prelude::*; #[tokio::main] async fn main() { std::env::set_var("RUST_LOG", "ping_pong_bot=trace"); + std::env::set_var("RUST_LOG", "teloxide=error"); pretty_env_logger::init(); log::info!("Starting the ping-pong bot!"); diff --git a/examples/simple_dialogue/src/main.rs b/examples/simple_dialogue/src/main.rs index 7c6aae3c..e02dc804 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -152,6 +152,7 @@ async fn handle_message(ctx: Ctx) -> Res { #[tokio::main] async fn main() { std::env::set_var("RUST_LOG", "simple_dialogue=trace"); + std::env::set_var("RUST_LOG", "teloxide=error"); pretty_env_logger::init(); log::info!("Starting the simple_dialogue bot!"); diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index d5b8864b..0b1190e3 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -155,13 +155,30 @@ pub fn polling( let updates = match req.send().await { Err(err) => vec![Err(err)], Ok(updates) => { + // Set offset to the last update's id + 1 + if let Some(upd) = updates.last() { + let id: i32 = match upd { + Ok(ok) => ok.id, + Err((value, _)) => value["update_id"] + .as_i64() + .expect( + "The 'update_id' field must always exist in \ + Update", + ) + .try_into() + .expect("update_id must be i32"), + }; + + offset = id + 1; + } + let updates = updates .into_iter() .filter(|update| match update { - Err(error) => { - log::error!("Cannot parse an update: {:?}! \ + Err((value, error)) => { + log::error!("Cannot parse an update.\nError: {:?}\nValue: {}\n\ This is a bug in teloxide, please open an issue here: \ - https://github.com/teloxide/teloxide/issues.", error); + https://github.com/teloxide/teloxide/issues.", error, value); false } Ok(_) => true, @@ -171,9 +188,6 @@ pub fn polling( }) .collect::>(); - if let Some(upd) = updates.last() { - offset = upd.id + 1; - } updates.into_iter().map(Ok).collect::>() } }; diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs index 05e9e84b..f3fbe0c4 100644 --- a/src/requests/all/get_updates.rs +++ b/src/requests/all/get_updates.rs @@ -32,12 +32,14 @@ pub struct GetUpdates { #[async_trait::async_trait] impl Request for GetUpdates { - type Output = Vec>; + type Output = Vec>; /// Deserialize to `Vec>` instead of /// `Vec`, because we want to parse the rest of updates even if our /// library hasn't parsed one. - async fn send(&self) -> ResponseResult>> { + async fn send( + &self, + ) -> ResponseResult>> { let value: Value = net::request_json( self.bot.client(), self.bot.token(), @@ -49,7 +51,10 @@ impl Request for GetUpdates { match value { Value::Array(array) => Ok(array .into_iter() - .map(|value| serde_json::from_str(&value.to_string())) + .map(|value| { + serde_json::from_str(&value.to_string()) + .map_err(|error| (value, error)) + }) .collect()), _ => Err(RequestError::InvalidJson( serde_json::from_value::>(value) From 9cab5e45f3b83807126c0532b59c8db555c3bc8a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 12 Feb 2020 15:55:36 +0600 Subject: [PATCH 54/91] Implement a type-safe finite automaton (examples/simple_dialogue) --- examples/simple_dialogue/Cargo.toml | 3 +- examples/simple_dialogue/src/main.rs | 180 ++++++++++-------- src/dispatching/dialogue/dialogue.rs | 14 -- .../dialogue/dialogue_dispatcher.rs | 23 +-- .../dialogue/dialogue_handler_ctx.rs | 45 +++-- src/dispatching/dialogue/dialogue_stage.rs | 12 +- src/dispatching/dialogue/mod.rs | 16 +- .../dialogue/storage/in_mem_storage.rs | 18 +- src/dispatching/dialogue/storage/mod.rs | 12 +- src/prelude.rs | 1 - 10 files changed, 157 insertions(+), 167 deletions(-) delete mode 100644 src/dispatching/dialogue/dialogue.rs diff --git a/examples/simple_dialogue/Cargo.toml b/examples/simple_dialogue/Cargo.toml index d9c28590..b8af369e 100644 --- a/examples/simple_dialogue/Cargo.toml +++ b/examples/simple_dialogue/Cargo.toml @@ -10,9 +10,8 @@ edition = "2018" pretty_env_logger = "0.3.1" log = "0.4.8" tokio = "0.2.9" -strum = "0.17.1" smart-default = "0.6.0" -strum_macros = "0.17.1" +parse-display = "0.1.1" teloxide = { path = "../../" } [profile.release] diff --git a/examples/simple_dialogue/src/main.rs b/examples/simple_dialogue/src/main.rs index e02dc804..69bb5e9a 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -1,19 +1,20 @@ -#[macro_use] -extern crate strum_macros; +#![allow(clippy::trivial_regex)] + #[macro_use] extern crate smart_default; -use std::fmt::{self, Display, Formatter}; use teloxide::{ prelude::*, types::{KeyboardButton, ReplyKeyboardMarkup}, }; +use parse_display::{Display, FromStr}; + // ============================================================================ // [Favourite music kinds] // ============================================================================ -#[derive(Copy, Clone, Display, EnumString)] +#[derive(Copy, Clone, Display, FromStr)] enum FavouriteMusic { Rock, Metal, @@ -33,115 +34,136 @@ impl FavouriteMusic { } // ============================================================================ -// [A UserInfo's data] +// [A type-safe finite automaton] // ============================================================================ -// TODO: implement a type-safe UserInfo without lots of .unwrap -#[derive(Default)] -struct UserInfo { - full_name: Option, - age: Option, - favourite_music: Option, +#[derive(Clone)] +struct ReceiveAgeState { + full_name: String, } -impl Display for UserInfo { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - write!( - f, - "Your full name: {}, your age: {}, your favourite music: {}", - self.full_name.as_ref().unwrap(), - self.age.unwrap(), - self.favourite_music.unwrap() - ) - } +#[derive(Clone)] +struct ReceiveFavouriteMusicState { + data: ReceiveAgeState, + age: u8, } -// ============================================================================ -// [States of a dialogue] -// ============================================================================ +#[derive(Display)] +#[display( + "Your full name: {data.data.full_name}, your age: {data.age}, your \ + favourite music: {favourite_music}" +)] +struct ExitState { + data: ReceiveFavouriteMusicState, + favourite_music: FavouriteMusic, +} #[derive(SmartDefault)] -enum State { +enum Dialogue { #[default] Start, - FullName, - Age, - FavouriteMusic, + ReceiveFullName, + ReceiveAge(ReceiveAgeState), + ReceiveFavouriteMusic(ReceiveFavouriteMusicState), } // ============================================================================ // [Control a dialogue] // ============================================================================ -type Ctx = DialogueHandlerCtx; -type Res = Result, RequestError>; +type Ctx = DialogueHandlerCtx; +type Res = Result, RequestError>; -async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> { - ctx.answer("Good. Now choose your favourite music:") - .reply_markup(FavouriteMusic::markup()) - .send() - .await?; - Ok(()) -} - -async fn start(mut ctx: Ctx) -> Res { +async fn start(ctx: Ctx<()>) -> Res { ctx.answer("Let's start! First, what's your full name?") .send() .await?; - state!(ctx, State::FullName); - next(ctx.dialogue) + next(Dialogue::ReceiveFullName) } -async fn full_name(mut ctx: Ctx) -> Res { - ctx.answer("What a wonderful name! Your age?") - .send() - .await?; - ctx.dialogue.data.full_name = Some(ctx.update.text().unwrap().to_owned()); - state!(ctx, State::Age); - next(ctx.dialogue) -} - -async fn age(mut ctx: Ctx) -> Res { - match ctx.update.text().unwrap().parse() { - Ok(ok) => { - send_favourite_music_types(&ctx).await?; - ctx.dialogue.data.age = Some(ok); - state!(ctx, State::FavouriteMusic); +async fn full_name(ctx: Ctx<()>) -> Res { + match ctx.update.text() { + None => { + ctx.answer("Please, send me a text message!").send().await?; + next(Dialogue::ReceiveFullName) } - Err(_) => ctx - .answer("Oh, please, enter a number!") - .send() - .await - .map(|_| ())?, - } - - next(ctx.dialogue) -} - -async fn favourite_music(mut ctx: Ctx) -> Res { - match ctx.update.text().unwrap().parse() { - Ok(ok) => { - ctx.dialogue.data.favourite_music = Some(ok); - ctx.answer(format!("Fine. {}", ctx.dialogue.data)) + Some(full_name) => { + ctx.answer("What a wonderful name! Your age?") .send() .await?; + next(Dialogue::ReceiveAge(ReceiveAgeState { + full_name: full_name.to_owned(), + })) + } + } +} + +async fn age(ctx: Ctx) -> Res { + match ctx.update.text().unwrap().parse() { + Ok(age) => { + ctx.answer("Good. Now choose your favourite music:") + .reply_markup(FavouriteMusic::markup()) + .send() + .await?; + next(Dialogue::ReceiveFavouriteMusic( + ReceiveFavouriteMusicState { + data: ctx.dialogue, + age, + }, + )) + } + Err(_) => { + ctx.answer("Oh, please, enter a number!").send().await?; + next(Dialogue::ReceiveAge(ctx.dialogue)) + } + } +} + +async fn favourite_music(ctx: Ctx) -> Res { + match ctx.update.text().unwrap().parse() { + Ok(favourite_music) => { + ctx.answer(format!( + "Fine. {}", + ExitState { + data: ctx.dialogue.clone(), + favourite_music + } + )) + .send() + .await?; exit() } Err(_) => { ctx.answer("Oh, please, enter from the keyboard!") .send() .await?; - next(ctx.dialogue) + next(Dialogue::ReceiveFavouriteMusic(ctx.dialogue)) } } } -async fn handle_message(ctx: Ctx) -> Res { - match ctx.dialogue.state { - State::Start => start(ctx).await, - State::FullName => full_name(ctx).await, - State::Age => age(ctx).await, - State::FavouriteMusic => favourite_music(ctx).await, +async fn handle_message(ctx: Ctx) -> Res { + match ctx { + DialogueHandlerCtx { + bot, + update, + dialogue: Dialogue::Start, + } => start(DialogueHandlerCtx::new(bot, update, ())).await, + DialogueHandlerCtx { + bot, + update, + dialogue: Dialogue::ReceiveFullName, + } => full_name(DialogueHandlerCtx::new(bot, update, ())).await, + DialogueHandlerCtx { + bot, + update, + dialogue: Dialogue::ReceiveAge(s), + } => age(DialogueHandlerCtx::new(bot, update, s)).await, + DialogueHandlerCtx { + bot, + update, + dialogue: Dialogue::ReceiveFavouriteMusic(s), + } => favourite_music(DialogueHandlerCtx::new(bot, update, s)).await, } } @@ -156,7 +178,7 @@ async fn main() { pretty_env_logger::init(); log::info!("Starting the simple_dialogue bot!"); - Dispatcher::new(Bot::new("YourAwesomeToken")) + Dispatcher::new(Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0")) .message_handler(&DialogueDispatcher::new(|ctx| async move { handle_message(ctx) .await diff --git a/src/dispatching/dialogue/dialogue.rs b/src/dispatching/dialogue/dialogue.rs deleted file mode 100644 index aec56ce0..00000000 --- a/src/dispatching/dialogue/dialogue.rs +++ /dev/null @@ -1,14 +0,0 @@ -/// A type, encapsulating a dialogue state and arbitrary data. -#[derive(Default, Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub struct Dialogue { - pub state: State, - pub data: T, -} - -impl Dialogue { - /// Creates new `Dialogue` with the provided fields. - #[must_use] - pub fn new(state: State, data: T) -> Self { - Self { state, data } - } -} diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index bb1e9a79..4b5fea97 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -1,7 +1,6 @@ use crate::dispatching::{ dialogue::{ - Dialogue, DialogueHandlerCtx, DialogueStage, GetChatId, InMemStorage, - Storage, + DialogueHandlerCtx, DialogueStage, GetChatId, InMemStorage, Storage, }, CtxHandler, DispatcherHandlerCtx, }; @@ -13,16 +12,14 @@ use std::{future::Future, pin::Pin}; /// an instance of this dispatcher into the [`Dispatcher`]'s methods. /// /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub struct DialogueDispatcher<'a, State, T, H> { - storage: Box + 'a>, +pub struct DialogueDispatcher<'a, D, H> { + storage: Box + 'a>, handler: H, } -impl<'a, State, T, H> DialogueDispatcher<'a, State, T, H> +impl<'a, D, H> DialogueDispatcher<'a, D, H> where - Dialogue: Default + 'a, - T: Default + 'a, - State: Default + 'a, + D: Default + 'a, { /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] /// (a default storage). @@ -40,7 +37,7 @@ where #[must_use] pub fn with_storage(handler: H, storage: Stg) -> Self where - Stg: Storage + 'a, + Stg: Storage + 'a, { Self { storage: Box::new(storage), @@ -49,12 +46,12 @@ where } } -impl<'a, State, T, H, Upd> CtxHandler, Result<(), ()>> - for DialogueDispatcher<'a, State, T, H> +impl<'a, D, H, Upd> CtxHandler, Result<(), ()>> + for DialogueDispatcher<'a, D, H> where - H: CtxHandler, DialogueStage>, + H: CtxHandler, DialogueStage>, Upd: GetChatId, - Dialogue: Default, + D: Default, { fn handle_ctx<'b>( &'b self, diff --git a/src/dispatching/dialogue/dialogue_handler_ctx.rs b/src/dispatching/dialogue/dialogue_handler_ctx.rs index d1725d2a..257744aa 100644 --- a/src/dispatching/dialogue/dialogue_handler_ctx.rs +++ b/src/dispatching/dialogue/dialogue_handler_ctx.rs @@ -1,5 +1,5 @@ use crate::{ - dispatching::dialogue::{Dialogue, GetChatId}, + dispatching::dialogue::GetChatId, requests::{ DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage, PinChatMessage, SendAnimation, SendAudio, SendContact, SendDocument, @@ -14,28 +14,37 @@ use std::sync::Arc; /// A context of a [`DialogueDispatcher`]'s message handler. /// /// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher -pub struct DialogueHandlerCtx { +pub struct DialogueHandlerCtx { pub bot: Arc, pub update: Upd, - pub dialogue: Dialogue, + pub dialogue: D, } -/// Sets a new state. -/// -/// Use it like this: `state!(ctx, State::RequestAge)`, where `ctx` is -/// [`DialogueHandlerCtx`] and `State::RequestAge` is of type -/// `State`. -/// -/// [`DialogueHandlerCtx`]: -/// crate::dispatching::dialogue::DialogueHandlerCtx -#[macro_export] -macro_rules! state { - ($ctx:ident, $state:expr) => { - $ctx.dialogue.state = $state; - }; +impl DialogueHandlerCtx { + /// Creates a new instance with the provided fields. + pub fn new(bot: Arc, update: Upd, dialogue: D) -> Self { + Self { + bot, + update, + dialogue, + } + } + + /// Creates a new instance by substituting a dialogue and preserving + /// `self.bot` and `self.update`. + pub fn with_new_dialogue( + self, + new_dialogue: Nd, + ) -> DialogueHandlerCtx { + DialogueHandlerCtx { + bot: self.bot, + update: self.update, + dialogue: new_dialogue, + } + } } -impl GetChatId for DialogueHandlerCtx +impl GetChatId for DialogueHandlerCtx where Upd: GetChatId, { @@ -44,7 +53,7 @@ where } } -impl DialogueHandlerCtx { +impl DialogueHandlerCtx { pub fn answer(&self, text: T) -> SendMessage where T: Into, diff --git a/src/dispatching/dialogue/dialogue_stage.rs b/src/dispatching/dialogue/dialogue_stage.rs index 43ea25cd..afb5a31c 100644 --- a/src/dispatching/dialogue/dialogue_stage.rs +++ b/src/dispatching/dialogue/dialogue_stage.rs @@ -1,20 +1,16 @@ -use crate::dispatching::dialogue::Dialogue; - /// Continue or terminate a dialogue. #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum DialogueStage { - Next(Dialogue), +pub enum DialogueStage { + Next(D), Exit, } /// A shortcut for `Ok(DialogueStage::Next(dialogue))`. -pub fn next( - dialogue: Dialogue, -) -> Result, E> { +pub fn next(dialogue: D) -> Result, E> { Ok(DialogueStage::Next(dialogue)) } /// A shortcut for `Ok(DialogueStage::Exit)`. -pub fn exit() -> Result, E> { +pub fn exit() -> Result, E> { Ok(DialogueStage::Exit) } diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 3cbde6b1..ed48b2c1 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -2,22 +2,20 @@ //! //! There are four main components: //! -//! 1. Your type `State`, which designates a dialogue state at the current +//! 1. Your type `D`, which designates a dialogue state at the current //! moment. -//! 2. Your type `T`, which represents dialogue data. -//! 3. [`Dialogue`], which encapsulates the two types, described above. -//! 4. [`Storage`], which encapsulates all the sessions. -//! 5. Your handler, which receives an update and turns your session into the +//! 2. [`Storage`], which encapsulates all the dialogues. +//! 3. Your handler, which receives an update and turns your dialogue into the //! next state. -//! 6. [`DialogueDispatcher`], which encapsulates your handler, [`Storage`], and -//! implements [`CtxHandler`]. +//! 4. [`DialogueDispatcher`], which encapsulates your handler, [`Storage`], +//! and implements [`CtxHandler`]. //! //! You supply [`DialogueDispatcher`] into [`Dispatcher`]. Every time //! [`Dispatcher`] calls `DialogueDispatcher::handle_ctx(...)`, the following //! steps are executed: //! //! 1. If a storage doesn't contain a dialogue from this chat, supply -//! `Dialogue::default()` into you handler, otherwise, supply the saved session +//! `D::default()` into you handler, otherwise, supply the saved session //! from this chat. //! 3. If a handler has returned [`DialogueStage::Exit`], remove the session //! from the storage, otherwise ([`DialogueStage::Next`]) force the storage to @@ -37,14 +35,12 @@ #![allow(clippy::module_inception)] #![allow(clippy::type_complexity)] -mod dialogue; mod dialogue_dispatcher; mod dialogue_handler_ctx; mod dialogue_stage; mod get_chat_id; mod storage; -pub use dialogue::Dialogue; pub use dialogue_dispatcher::DialogueDispatcher; pub use dialogue_handler_ctx::DialogueHandlerCtx; pub use dialogue_stage::{exit, next, DialogueStage}; diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs index 9db2c5fd..bfc1d033 100644 --- a/src/dispatching/dialogue/storage/in_mem_storage.rs +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -1,7 +1,6 @@ use async_trait::async_trait; use super::Storage; -use crate::dispatching::dialogue::Dialogue; use std::collections::HashMap; use tokio::sync::Mutex; @@ -13,25 +12,18 @@ use tokio::sync::Mutex; /// store them somewhere on a drive, you need to implement a storage /// communicating with a DB. #[derive(Debug, Default)] -pub struct InMemStorage { - map: Mutex>>, +pub struct InMemStorage { + map: Mutex>, } #[async_trait(?Send)] #[async_trait] -impl Storage for InMemStorage { - async fn remove_dialogue( - &self, - chat_id: i64, - ) -> Option> { +impl Storage for InMemStorage { + async fn remove_dialogue(&self, chat_id: i64) -> Option { self.map.lock().await.remove(&chat_id) } - async fn update_dialogue( - &self, - chat_id: i64, - dialogue: Dialogue, - ) -> Option> { + async fn update_dialogue(&self, chat_id: i64, dialogue: D) -> Option { self.map.lock().await.insert(chat_id, dialogue) } } diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 1fe4641e..f06fbf49 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -1,6 +1,5 @@ mod in_mem_storage; -use crate::dispatching::dialogue::Dialogue; use async_trait::async_trait; pub use in_mem_storage::InMemStorage; @@ -14,21 +13,16 @@ pub use in_mem_storage::InMemStorage; /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage #[async_trait(?Send)] #[async_trait] -pub trait Storage { +pub trait Storage { /// Removes a dialogue with the specified `chat_id`. /// /// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a /// `dialogue` was deleted. - async fn remove_dialogue(&self, chat_id: i64) - -> Option>; + async fn remove_dialogue(&self, chat_id: i64) -> Option; /// Updates a dialogue with the specified `chat_id`. /// /// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a /// `dialogue` was updated. - async fn update_dialogue( - &self, - chat_id: i64, - dialogue: Dialogue, - ) -> Option>; + async fn update_dialogue(&self, chat_id: i64, dialogue: D) -> Option; } diff --git a/src/prelude.rs b/src/prelude.rs index d5fb766b..c70efa5b 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,7 +9,6 @@ pub use crate::{ Dispatcher, DispatcherHandlerCtx, }, requests::{Request, ResponseResult}, - state, types::Message, Bot, RequestError, }; From 8d718255118b105ddcb67cb0e386efaeef22c3ea Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 12 Feb 2020 15:56:14 +0600 Subject: [PATCH 55/91] Update the token --- examples/simple_dialogue/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_dialogue/src/main.rs b/examples/simple_dialogue/src/main.rs index 69bb5e9a..9dd36bc5 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -178,7 +178,7 @@ async fn main() { pretty_env_logger::init(); log::info!("Starting the simple_dialogue bot!"); - Dispatcher::new(Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0")) + Dispatcher::new(Bot::new("MyAwesomeToken")) .message_handler(&DialogueDispatcher::new(|ctx| async move { handle_message(ctx) .await From 26d721d928184ffe59721d8e5699261d17965306 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 12 Feb 2020 16:34:38 +0600 Subject: [PATCH 56/91] Add comments to examples/ping_pong_bot --- examples/ping_pong_bot/src/main.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 70c30a88..4a024604 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -2,12 +2,18 @@ use teloxide::prelude::*; #[tokio::main] async fn main() { + // Configure a fancy logger. Let this bot print everything, but restrict + // teloxide to only log errors. std::env::set_var("RUST_LOG", "ping_pong_bot=trace"); std::env::set_var("RUST_LOG", "teloxide=error"); pretty_env_logger::init(); log::info!("Starting the ping-pong bot!"); + // Creates a dispatcher of updates with the specified bot. Don't forget to + // replace `MyAwesomeToken` with yours. Dispatcher::::new(Bot::new("MyAwesomeToken")) + // Registers a message handler. Inside a body of the closure, answer + // `"pong"` to an incoming message. .message_handler(&|ctx: DispatcherHandlerCtx| async move { ctx.answer("pong").send().await?; Ok(()) From 3aeb9c4295e3250552291274b71fed2b02877d8e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 12 Feb 2020 16:56:58 +0600 Subject: [PATCH 57/91] Update examples/simple_dialogue --- examples/ping_pong_bot/src/main.rs | 16 +++++++++------- examples/simple_dialogue/src/main.rs | 10 +++++++--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 4a024604..3ed49515 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -1,19 +1,21 @@ use teloxide::prelude::*; +use std::env::{set_var, var}; + #[tokio::main] async fn main() { // Configure a fancy logger. Let this bot print everything, but restrict // teloxide to only log errors. - std::env::set_var("RUST_LOG", "ping_pong_bot=trace"); - std::env::set_var("RUST_LOG", "teloxide=error"); + set_var("RUST_LOG", "ping_pong_bot=trace"); + set_var("RUST_LOG", "teloxide=error"); pretty_env_logger::init(); log::info!("Starting the ping-pong bot!"); - // Creates a dispatcher of updates with the specified bot. Don't forget to - // replace `MyAwesomeToken` with yours. - Dispatcher::::new(Bot::new("MyAwesomeToken")) - // Registers a message handler. Inside a body of the closure, answer - // `"pong"` to an incoming message. + let bot = Bot::new(var("TELOXIDE_TOKEN").unwrap()); + + // Create a dispatcher with a single message handler that answers "pong" to + // each incoming message. + Dispatcher::::new(bot) .message_handler(&|ctx: DispatcherHandlerCtx| async move { ctx.answer("pong").send().await?; Ok(()) diff --git a/examples/simple_dialogue/src/main.rs b/examples/simple_dialogue/src/main.rs index 9dd36bc5..c2ece8b9 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/simple_dialogue/src/main.rs @@ -3,6 +3,8 @@ #[macro_use] extern crate smart_default; +use std::env::{set_var, var}; + use teloxide::{ prelude::*, types::{KeyboardButton, ReplyKeyboardMarkup}, @@ -173,12 +175,14 @@ async fn handle_message(ctx: Ctx) -> Res { #[tokio::main] async fn main() { - std::env::set_var("RUST_LOG", "simple_dialogue=trace"); - std::env::set_var("RUST_LOG", "teloxide=error"); + set_var("RUST_LOG", "simple_dialogue=trace"); + set_var("RUST_LOG", "teloxide=error"); pretty_env_logger::init(); log::info!("Starting the simple_dialogue bot!"); - Dispatcher::new(Bot::new("MyAwesomeToken")) + let bot = Bot::new(var("TELOXIDE_TOKEN").unwrap()); + + Dispatcher::new(bot) .message_handler(&DialogueDispatcher::new(|ctx| async move { handle_message(ctx) .await From 40581266652921685d53328f34bd570846db6c22 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Wed, 12 Feb 2020 16:08:15 +0200 Subject: [PATCH 58/91] fixed example admin_bot --- examples/admin_bot/src/main.rs | 45 +++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/examples/admin_bot/src/main.rs b/examples/admin_bot/src/main.rs index 9685175c..c7c80ff5 100644 --- a/examples/admin_bot/src/main.rs +++ b/examples/admin_bot/src/main.rs @@ -5,7 +5,7 @@ use teloxide::types::ChatPermissions; type Ctx = DispatcherHandlerCtx; #[derive(BotCommand)] -#[command(rename = "lowercase", description = "use command in format /%command% %num% %unit%")] +#[command(rename = "lowercase", description = "Use commands in format /%command% %num% %unit%")] enum Command { #[command(description = "kick user from chat.")] Kick, @@ -22,7 +22,7 @@ fn calc_restrict_time(num: i32, unit: &str) -> Result { "h"|"hours" => Ok(num * 3600), "m"|"minutes" => Ok(num * 60), "s"|"seconds" => Ok(num), - _ => Err("allowed units: *h*, *m*, *s*") + _ => Err("allowed units: h, m, s") } } @@ -49,7 +49,7 @@ fn parse_time_restrict(args: Vec<&str>) -> Result { async fn handle_command(ctx: Ctx) -> Result<(), ()> { if let Some(text) = ctx.update.text() { - let (command, args): (Command, Vec<&str>) = Command::parse(text).unwrap_or((Command::Help, vec![])); + let (command, args): (Command, Vec<&str>) = Command::parse(text).ok_or(())?; match command { Command::Help => { @@ -58,10 +58,12 @@ async fn handle_command(ctx: Ctx) -> Result<(), ()> { Command::Kick => { match ctx.update.reply_to_message() { Some(mes) => { - ctx.bot.unban_chat_member( - mes.chat_id(), - mes.from().unwrap().id - ).send().await; + if let Some(user) = mes.from() { + ctx.bot.unban_chat_member( + ctx.update.chat_id(), + user.id + ).send().await; + } }, None => { ctx.reply_to("Use this command in reply to another message").send().await; @@ -72,14 +74,15 @@ async fn handle_command(ctx: Ctx) -> Result<(), ()> { match ctx.update.reply_to_message() { Some(mes) => match parse_time_restrict(args) { Ok(time) => { - dbg!(&ctx.update); - ctx.bot.kick_chat_member( - mes.chat_id(), - mes.from().unwrap().id - ) - .until_date(time) - .send() - .await; + if let Some(user) = mes.from() { + ctx.bot.kick_chat_member( + ctx.update.chat_id(), + user.id + ) + .until_date(ctx.update.date + time) + .send() + .await; + } } Err(msg) => { ctx.answer(msg).send().await; @@ -94,14 +97,16 @@ async fn handle_command(ctx: Ctx) -> Result<(), ()> { match ctx.update.reply_to_message() { Some(mes) => match parse_time_restrict(args) { Ok(time) => { - ctx.bot.restrict_chat_member( - mes.chat_id(), - mes.from().unwrap().id, + if let Some(user) = mes.from() { + ctx.bot.restrict_chat_member( + ctx.update.chat_id(), + user.id, ChatPermissions::default() ) - .until_date(time) + .until_date(ctx.update.date + time) .send() .await; + } } Err(msg) => { ctx.answer(msg).send().await; @@ -122,7 +127,7 @@ async fn handle_command(ctx: Ctx) -> Result<(), ()> { async fn main() { pretty_env_logger::init(); - let bot = Bot::new("865293832:AAHD-ox6hi6Ws_pxBFb8VIp1uymHoMab2MM"); + let bot = Bot::new("YourAwesomeToken"); Dispatcher::new(bot) .message_handler(&handle_command) .dispatch() From e9a62be4537085ff2a45a8a988e5d2290a942404 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 12 Feb 2020 20:44:40 +0600 Subject: [PATCH 59/91] Rename simple_dialogue -> dialogue_bot --- .gitignore | 2 +- examples/{simple_dialogue => dialogue_bot}/Cargo.toml | 2 +- examples/{simple_dialogue => dialogue_bot}/src/main.rs | 4 ++-- src/dispatching/dialogue/mod.rs | 4 ++-- src/dispatching/mod.rs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) rename examples/{simple_dialogue => dialogue_bot}/Cargo.toml (93%) rename examples/{simple_dialogue => dialogue_bot}/src/main.rs (98%) diff --git a/.gitignore b/.gitignore index dc7a5786..19037d54 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ Cargo.lock .vscode/ examples/target examples/ping_pong_bot/target -examples/simple_dialogue/target \ No newline at end of file +examples/dialogue_bot/target \ No newline at end of file diff --git a/examples/simple_dialogue/Cargo.toml b/examples/dialogue_bot/Cargo.toml similarity index 93% rename from examples/simple_dialogue/Cargo.toml rename to examples/dialogue_bot/Cargo.toml index b8af369e..95b79db0 100644 --- a/examples/simple_dialogue/Cargo.toml +++ b/examples/dialogue_bot/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "simple_dialogue" +name = "dialogue_bot" version = "0.1.0" authors = ["Temirkhan Myrzamadi "] edition = "2018" diff --git a/examples/simple_dialogue/src/main.rs b/examples/dialogue_bot/src/main.rs similarity index 98% rename from examples/simple_dialogue/src/main.rs rename to examples/dialogue_bot/src/main.rs index c2ece8b9..5da1d516 100644 --- a/examples/simple_dialogue/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -175,10 +175,10 @@ async fn handle_message(ctx: Ctx) -> Res { #[tokio::main] async fn main() { - set_var("RUST_LOG", "simple_dialogue=trace"); + set_var("RUST_LOG", "dialogue_bot=trace"); set_var("RUST_LOG", "teloxide=error"); pretty_env_logger::init(); - log::info!("Starting the simple_dialogue bot!"); + log::info!("Starting the dialogue_bot bot!"); let bot = Bot::new(var("TELOXIDE_TOKEN").unwrap()); diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index ed48b2c1..7a7249a4 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -21,7 +21,7 @@ //! from the storage, otherwise ([`DialogueStage::Next`]) force the storage to //! update the session. //! -//! Please, see [examples/simple_dialogue] as an example. +//! Please, see [examples/dialogue_bot] as an example. //! //! [`Storage`]: crate::dispatching::dialogue::Storage //! [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher @@ -30,7 +30,7 @@ //! [`DialogueStage::Next`]: crate::dispatching::dialogue::DialogueStage::Next //! [`CtxHandler`]: crate::dispatching::CtxHandler //! [`Dispatcher`]: crate::dispatching::Dispatcher -//! [examples/simple_dialogue]: https://github.com/teloxide/teloxide/tree/dev/examples/simple_dialogue +//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/tree/dev/examples/dialogue_bot #![allow(clippy::module_inception)] #![allow(clippy::type_complexity)] diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 77befc3d..e3ad2dff 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -42,7 +42,7 @@ //! # } //! ``` //! -//! For a bit more complicated example, please see [examples/simple_dialogue]. +//! For a bit more complicated example, please see [examples/dialogue_bot]. //! //! [`Dispatcher`]: crate::dispatching::Dispatcher //! [11 update kinds]: crate::types::UpdateKind @@ -52,7 +52,7 @@ //! [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher //! [`DispatcherHandlerResult`]: crate::dispatching::DispatcherHandlerResult //! [`Bot`]: crate::Bot -//! [examples/simple_dialogue]: https://github.com/teloxide/teloxide/tree/dev/examples/simple_dialogue +//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/tree/dev/examples/dialogue_bot mod ctx_handlers; pub mod dialogue; From a8ced80f78d01b8e0b5f8ee1fcf13c3b97deba76 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Wed, 12 Feb 2020 20:54:54 +0200 Subject: [PATCH 60/91] changed type of field Message::from to Option because message from channel no have sender + fmt --- src/dispatching/dispatcher.rs | 39 +++++++++++++++++++---------------- src/types/chat_permissions.rs | 2 +- src/types/message.rs | 7 +++---- src/types/message_entity.rs | 4 +--- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 4844af34..7cb55363 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -300,27 +300,30 @@ where update: Upd, ) -> Option { stream::iter(handlers) - .fold(Some(update), |acc, handler| async move { - // Option::and_then is not working here, because - // Middleware::handle is asynchronous. - match acc { - Some(update) => { - let DispatcherHandlerResult { next, result } = handler - .handle_ctx(DispatcherHandlerCtx { - bot: Arc::clone(&self.bot), - update, - }) - .await; + .fold(Some(update), |acc, handler| { + async move { + // Option::and_then is not working here, because + // Middleware::handle is asynchronous. + match acc { + Some(update) => { + let DispatcherHandlerResult { next, result } = + handler + .handle_ctx(DispatcherHandlerCtx { + bot: Arc::clone(&self.bot), + update, + }) + .await; - if let Err(error) = result { - self.handlers_error_handler - .handle_error(error) - .await + if let Err(error) = result { + self.handlers_error_handler + .handle_error(error) + .await + } + + next } - - next + None => None, } - None => None, } }) .await diff --git a/src/types/chat_permissions.rs b/src/types/chat_permissions.rs index 6f9b7ea3..a39314ea 100644 --- a/src/types/chat_permissions.rs +++ b/src/types/chat_permissions.rs @@ -50,7 +50,7 @@ impl Default for ChatPermissions { can_add_web_page_previews: None, can_change_info: None, can_invite_users: None, - can_pin_messages: None + can_pin_messages: None, } } } diff --git a/src/types/message.rs b/src/types/message.rs index c9709dc0..b324493f 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -34,7 +34,7 @@ pub struct Message { pub enum MessageKind { Common { /// Sender, empty for messages sent to channels. - from: User, + from: Option, #[serde(flatten)] forward_kind: ForwardKind, @@ -328,8 +328,7 @@ mod getters { Pinned, SuccessfulPayment, SupergroupChatCreated, }, }, - Chat, ForwardedFrom, Message, MessageEntity, PhotoSize, True, - User, + Chat, ForwardedFrom, Message, MessageEntity, PhotoSize, True, User, }; /// Getters for [Message] fields from [telegram docs]. @@ -340,7 +339,7 @@ mod getters { /// NOTE: this is getter for both `from` and `author_signature` pub fn from(&self) -> Option<&User> { match &self.kind { - Common { from, .. } => Some(&from), + Common { from, .. } => from.as_ref(), _ => None, } } diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index fd816503..31627b12 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -50,9 +50,7 @@ impl MessageEntity { #[cfg(test)] mod tests { use super::*; - use crate::types::{ - Chat, ChatKind, ForwardKind, MediaKind, MessageKind, - }; + use crate::types::{Chat, ChatKind, ForwardKind, MediaKind, MessageKind}; #[test] fn recursive_kind() { From 7cf6aaff9025ab0ca18682b4305406fb841e0408 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 13 Feb 2020 14:55:46 +0600 Subject: [PATCH 61/91] Add multiple_handlers_bot --- .gitignore | 3 +- examples/dialogue_bot/src/main.rs | 15 +++--- examples/multiple_handlers_bot/Cargo.toml | 13 +++++ examples/multiple_handlers_bot/src/main.rs | 48 ++++++++++++++++++ examples/ping_pong_bot/src/main.rs | 18 +++---- src/dispatching/dispatcher.rs | 10 ++++ src/dispatching/dispatcher_handler_result.rs | 16 ++++-- src/dispatching/mod.rs | 52 +++++++++++++++++++- src/prelude.rs | 4 +- 9 files changed, 154 insertions(+), 25 deletions(-) create mode 100644 examples/multiple_handlers_bot/Cargo.toml create mode 100644 examples/multiple_handlers_bot/src/main.rs diff --git a/.gitignore b/.gitignore index 19037d54..218bb4a8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ Cargo.lock .vscode/ examples/target examples/ping_pong_bot/target -examples/dialogue_bot/target \ No newline at end of file +examples/dialogue_bot/target +examples/multiple_handlers_bot/target \ No newline at end of file diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index 5da1d516..c2d0e522 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -3,8 +3,6 @@ #[macro_use] extern crate smart_default; -use std::env::{set_var, var}; - use teloxide::{ prelude::*, types::{KeyboardButton, ReplyKeyboardMarkup}, @@ -175,12 +173,15 @@ async fn handle_message(ctx: Ctx) -> Res { #[tokio::main] async fn main() { - set_var("RUST_LOG", "dialogue_bot=trace"); - set_var("RUST_LOG", "teloxide=error"); - pretty_env_logger::init(); - log::info!("Starting the dialogue_bot bot!"); + run().await; +} - let bot = Bot::new(var("TELOXIDE_TOKEN").unwrap()); +async fn run() { + std::env::set_var("RUST_LOG", "info"); + pretty_env_logger::init(); + log::info!("Starting dialogue_bot!"); + + let bot = Bot::new(std::env::var("TELOXIDE_TOKEN").unwrap()); Dispatcher::new(bot) .message_handler(&DialogueDispatcher::new(|ctx| async move { diff --git a/examples/multiple_handlers_bot/Cargo.toml b/examples/multiple_handlers_bot/Cargo.toml new file mode 100644 index 00000000..5b44ccbe --- /dev/null +++ b/examples/multiple_handlers_bot/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "multiple_handlers_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/multiple_handlers_bot/src/main.rs b/examples/multiple_handlers_bot/src/main.rs new file mode 100644 index 00000000..5ac6c96f --- /dev/null +++ b/examples/multiple_handlers_bot/src/main.rs @@ -0,0 +1,48 @@ +use teloxide::prelude::*; + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + // Configure the fancy logger. + std::env::set_var("RUST_LOG", "info"); + pretty_env_logger::init(); + log::info!("Starting multiple_handlers_bot!"); + + let bot = Bot::new(std::env::var("TELOXIDE_TOKEN").unwrap()); + + // Create a dispatcher with multiple handlers of different types. This will + // print One! and Two! on every incoming UpdateKind::Message. + Dispatcher::::new(bot) + // This is the first UpdateKind::Message handler, which will be called + // after the Update handler below. + .message_handler(&|ctx: DispatcherHandlerCtx| async move { + log::info!("Two!"); + DispatcherHandlerResult::next(ctx.update, Ok(())) + }) + // Remember: handler of Update are called first. + .update_handler(&|ctx: DispatcherHandlerCtx| async move { + log::info!("One!"); + DispatcherHandlerResult::next(ctx.update, Ok(())) + }) + // This handler will be called right after the first UpdateKind::Message + // handler, because it is registered after. + .message_handler(&|_ctx: DispatcherHandlerCtx| async move { + // The same as DispatcherHandlerResult::exit(Ok(())) + Ok(()) + }) + // This handler will never be called, because the UpdateKind::Message + // handler above terminates the pipeline. + .message_handler(&|ctx: DispatcherHandlerCtx| async move { + log::info!("This will never be printed!"); + DispatcherHandlerResult::next(ctx.update, Ok(())) + }) + .dispatch() + .await; + + // Note: if this bot receive, for example, UpdateKind::ChannelPost, it will + // only print "One!", because the UpdateKind::Message handlers will not be + // called. +} diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 3ed49515..39f39b9c 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -1,17 +1,17 @@ use teloxide::prelude::*; -use std::env::{set_var, var}; - #[tokio::main] async fn main() { - // Configure a fancy logger. Let this bot print everything, but restrict - // teloxide to only log errors. - set_var("RUST_LOG", "ping_pong_bot=trace"); - set_var("RUST_LOG", "teloxide=error"); - pretty_env_logger::init(); - log::info!("Starting the ping-pong bot!"); + run().await; +} - let bot = Bot::new(var("TELOXIDE_TOKEN").unwrap()); +async fn run() { + // Configure the fancy logger. + std::env::set_var("RUST_LOG", "info"); + pretty_env_logger::init(); + log::info!("Starting ping_pong_bot!"); + + let bot = Bot::new(std::env::var("TELOXIDE_TOKEN").unwrap()); // Create a dispatcher with a single message handler that answers "pong" to // each incoming message. diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 7cb55363..82f16b2d 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -84,6 +84,16 @@ where self } + #[must_use] + pub fn update_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.update_handlers = register_handler(self.update_handlers, h); + self + } + #[must_use] pub fn message_handler(mut self, h: &'a H) -> Self where diff --git a/src/dispatching/dispatcher_handler_result.rs b/src/dispatching/dispatcher_handler_result.rs index 69b0ea4f..d7cacf22 100644 --- a/src/dispatching/dispatcher_handler_result.rs +++ b/src/dispatching/dispatcher_handler_result.rs @@ -10,14 +10,22 @@ pub struct DispatcherHandlerResult { } impl DispatcherHandlerResult { - /// Creates new `DispatcherHandlerResult`. - pub fn new(next: Option, result: Result<(), E>) -> Self { - Self { next, result } + /// Creates new `DispatcherHandlerResult` that continues the pipeline. + pub fn next(update: Upd, result: Result<(), E>) -> Self { + Self { + next: Some(update), + result, + } + } + + /// Creates new `DispatcherHandlerResult` that terminates the pipeline. + pub fn exit(result: Result<(), E>) -> Self { + Self { next: None, result } } } impl From> for DispatcherHandlerResult { fn from(result: Result<(), E>) -> Self { - Self::new(None, result) + Self::exit(result) } } diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index e3ad2dff..d6d35317 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -22,8 +22,8 @@ //! explicitly, because of automatic conversions. Just return `Result<(), E>` if //! you want to terminate the pipeline (see the example below). //! -//! ## Examples -//! The ping-pong bot ([full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/)): +//! # Examples +//! ### The ping-pong bot //! //! ``` //! # #[tokio::main] @@ -32,6 +32,8 @@ //! //! // Setup logging here... //! +//! // Create a dispatcher with a single message handler that answers "pong" +//! // to each incoming message. //! Dispatcher::::new(Bot::new("MyAwesomeToken")) //! .message_handler(&|ctx: DispatcherHandlerCtx| async move { //! ctx.answer("pong").send().await?; @@ -42,6 +44,52 @@ //! # } //! ``` //! +//! [Full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/) +//! +//! ### Multiple handlers +//! +//! ``` +//! # #[tokio::main] +//! # async fn main_() { +//! use teloxide::prelude::*; +//! +//! // Create a dispatcher with multiple handlers of different types. This will +//! // print One! and Two! on every incoming UpdateKind::Message. +//! Dispatcher::::new(Bot::new("MyAwesomeToken")) +//! // This is the first UpdateKind::Message handler, which will be called +//! // after the Update handler below. +//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { +//! log::info!("Two!"); +//! DispatcherHandlerResult::next(ctx.update, Ok(())) +//! }) +//! // Remember: handler of Update are called first. +//! .update_handler(&|ctx: DispatcherHandlerCtx| async move { +//! log::info!("One!"); +//! DispatcherHandlerResult::next(ctx.update, Ok(())) +//! }) +//! // This handler will be called right after the first UpdateKind::Message +//! // handler, because it is registered after. +//! .message_handler(&|_ctx: DispatcherHandlerCtx| async move { +//! // The same as DispatcherHandlerResult::exit(Ok(())) +//! Ok(()) +//! }) +//! // This handler will never be called, because the UpdateKind::Message +//! // handler above terminates the pipeline. +//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { +//! log::info!("This will never be printed!"); +//! DispatcherHandlerResult::next(ctx.update, Ok(())) +//! }) +//! .dispatch() +//! .await; +//! +//! // Note: if this bot receive, for example, UpdateKind::ChannelPost, it will +//! // only print "One!", because the UpdateKind::Message handlers will not be +//! // called. +//! # } +//! ``` +//! +//! [Full](https://github.com/teloxide/teloxide/blob/dev/examples/miltiple_handlers_bot/) +//! //! For a bit more complicated example, please see [examples/dialogue_bot]. //! //! [`Dispatcher`]: crate::dispatching::Dispatcher diff --git a/src/prelude.rs b/src/prelude.rs index c70efa5b..97b01f16 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -6,9 +6,9 @@ pub use crate::{ exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage, GetChatId, }, - Dispatcher, DispatcherHandlerCtx, + Dispatcher, DispatcherHandlerCtx, DispatcherHandlerResult, }, requests::{Request, ResponseResult}, - types::Message, + types::{Message, Update}, Bot, RequestError, }; From ad0e241195df9e2d61398ab95202f395e79c10a8 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 13 Feb 2020 14:57:47 +0600 Subject: [PATCH 62/91] Add examples/README.md --- examples/README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..af2ee63f --- /dev/null +++ b/examples/README.md @@ -0,0 +1,2 @@ +# Examples +Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable. \ No newline at end of file From 690fdd06b45a2676f64457051f9acf7043224e3a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 13 Feb 2020 18:36:12 +0600 Subject: [PATCH 63/91] Add Bot::{from_env, from_env_with_client} --- examples/dialogue_bot/src/main.rs | 2 +- examples/multiple_handlers_bot/src/main.rs | 2 +- examples/ping_pong_bot/src/main.rs | 2 +- src/bot/mod.rs | 37 ++++++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index c2d0e522..e3363935 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -181,7 +181,7 @@ async fn run() { pretty_env_logger::init(); log::info!("Starting dialogue_bot!"); - let bot = Bot::new(std::env::var("TELOXIDE_TOKEN").unwrap()); + let bot = Bot::from_env(); Dispatcher::new(bot) .message_handler(&DialogueDispatcher::new(|ctx| async move { diff --git a/examples/multiple_handlers_bot/src/main.rs b/examples/multiple_handlers_bot/src/main.rs index 5ac6c96f..a3422798 100644 --- a/examples/multiple_handlers_bot/src/main.rs +++ b/examples/multiple_handlers_bot/src/main.rs @@ -11,7 +11,7 @@ async fn run() { pretty_env_logger::init(); log::info!("Starting multiple_handlers_bot!"); - let bot = Bot::new(std::env::var("TELOXIDE_TOKEN").unwrap()); + let bot = Bot::from_env(); // Create a dispatcher with multiple handlers of different types. This will // print One! and Two! on every incoming UpdateKind::Message. diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 39f39b9c..7485bd95 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -11,7 +11,7 @@ async fn run() { pretty_env_logger::init(); log::info!("Starting ping_pong_bot!"); - let bot = Bot::new(std::env::var("TELOXIDE_TOKEN").unwrap()); + let bot = Bot::from_env(); // Create a dispatcher with a single message handler that answers "pong" to // each incoming message. diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 0dbd9d89..a69f9caf 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -12,6 +12,39 @@ pub struct Bot { } impl Bot { + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a + /// bot's token) and the default [`reqwest::Client`]. + /// + /// # Panics + /// If cannot get the `TELOXIDE_TOKEN` environmental variable. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn from_env() -> Arc { + Self::new( + std::env::var("TELOXIDE_TOKEN") + .expect("Cannot get the TELOXIDE_TOKEN env variable"), + ) + } + + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a + /// bot's token) and your [`reqwest::Client`]. + /// + /// # Panics + /// If cannot get the `TELOXIDE_TOKEN` environmental variable. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn from_env_with_client(client: Client) -> Arc { + Self::with_client( + std::env::var("TELOXIDE_TOKEN") + .expect("Cannot get the TELOXIDE_TOKEN env variable"), + client, + ) + } + + /// Creates a new `Bot` with the specified token and the default + /// [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html pub fn new(token: S) -> Arc where S: Into, @@ -19,6 +52,10 @@ impl Bot { Self::with_client(token, Client::new()) } + /// Creates a new `Bot` with the specified token and your + /// [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html pub fn with_client(token: S, client: Client) -> Arc where S: Into, From 315e9ab80c1881d96d5f15ce02682d2923295584 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 13 Feb 2020 18:39:05 +0600 Subject: [PATCH 64/91] Fix the examples in docs --- src/dispatching/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index d6d35317..9f248b42 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -25,7 +25,7 @@ //! # Examples //! ### The ping-pong bot //! -//! ``` +//! ```no_run //! # #[tokio::main] //! # async fn main_() { //! use teloxide::prelude::*; @@ -34,7 +34,7 @@ //! //! // Create a dispatcher with a single message handler that answers "pong" //! // to each incoming message. -//! Dispatcher::::new(Bot::new("MyAwesomeToken")) +//! Dispatcher::::new(Bot::from_env()) //! .message_handler(&|ctx: DispatcherHandlerCtx| async move { //! ctx.answer("pong").send().await?; //! Ok(()) @@ -48,14 +48,14 @@ //! //! ### Multiple handlers //! -//! ``` +//! ```no_run //! # #[tokio::main] //! # async fn main_() { //! use teloxide::prelude::*; //! //! // Create a dispatcher with multiple handlers of different types. This will //! // print One! and Two! on every incoming UpdateKind::Message. -//! Dispatcher::::new(Bot::new("MyAwesomeToken")) +//! Dispatcher::::new(Bot::from_env()) //! // This is the first UpdateKind::Message handler, which will be called //! // after the Update handler below. //! .message_handler(&|ctx: DispatcherHandlerCtx| async move { From 7a7254b8a17c6a06640b86b797f32e5ba187ee3e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 13 Feb 2020 20:12:24 +0600 Subject: [PATCH 65/91] Add BotBuilder --- Cargo.toml | 1 + examples/dialogue_bot/src/main.rs | 5 +- examples/multiple_handlers_bot/src/main.rs | 6 +- examples/ping_pong_bot/src/main.rs | 6 +- src/bot/mod.rs | 115 ++++++++++++++------- src/lib.rs | 10 +- src/prelude.rs | 1 + 7 files changed, 90 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23823539..f57fcbf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ tokio-util = { version = "0.2.0", features = ["full"] } reqwest = { version = "0.10", features = ["json", "stream", "native-tls-vendored"] } log = "0.4.8" +pretty_env_logger = "0.4.0" bytes = "0.5.3" mime = "0.3.16" diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index e3363935..0a7003cb 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -177,12 +177,9 @@ async fn main() { } async fn run() { - std::env::set_var("RUST_LOG", "info"); - pretty_env_logger::init(); + let bot = Bot::from_env().enable_logging(crate_name!()).build(); log::info!("Starting dialogue_bot!"); - let bot = Bot::from_env(); - Dispatcher::new(bot) .message_handler(&DialogueDispatcher::new(|ctx| async move { handle_message(ctx) diff --git a/examples/multiple_handlers_bot/src/main.rs b/examples/multiple_handlers_bot/src/main.rs index a3422798..ff814ca4 100644 --- a/examples/multiple_handlers_bot/src/main.rs +++ b/examples/multiple_handlers_bot/src/main.rs @@ -6,13 +6,9 @@ async fn main() { } async fn run() { - // Configure the fancy logger. - std::env::set_var("RUST_LOG", "info"); - pretty_env_logger::init(); + let bot = Bot::from_env().enable_logging(crate_name!()).build(); log::info!("Starting multiple_handlers_bot!"); - let bot = Bot::from_env(); - // Create a dispatcher with multiple handlers of different types. This will // print One! and Two! on every incoming UpdateKind::Message. Dispatcher::::new(bot) diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 7485bd95..617fb1c0 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -6,13 +6,9 @@ async fn main() { } async fn run() { - // Configure the fancy logger. - std::env::set_var("RUST_LOG", "info"); - pretty_env_logger::init(); + let bot = Bot::from_env().enable_logging(crate_name!()).build(); log::info!("Starting ping_pong_bot!"); - let bot = Bot::from_env(); - // Create a dispatcher with a single message handler that answers "pong" to // each incoming message. Dispatcher::::new(bot) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index a69f9caf..ad251844 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,3 +1,5 @@ +use log::LevelFilter; +use pretty_env_logger::env_logger::WriteStyle; use reqwest::Client; use std::sync::Arc; @@ -12,57 +14,92 @@ pub struct Bot { } impl Bot { - /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a - /// bot's token) and the default [`reqwest::Client`]. + /// Returns [`BotBuilder`] from the `TELOXIDE_TOKEN` environmental variable + /// (a bot's token). /// /// # Panics /// If cannot get the `TELOXIDE_TOKEN` environmental variable. /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - pub fn from_env() -> Arc { - Self::new( - std::env::var("TELOXIDE_TOKEN") + /// [`BotBuilder`]: crate::BotBuilder + pub fn from_env() -> BotBuilder { + BotBuilder { + token: std::env::var("TELOXIDE_TOKEN") .expect("Cannot get the TELOXIDE_TOKEN env variable"), - ) + client: None, + } } - /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a - /// bot's token) and your [`reqwest::Client`]. + /// Returns [`BotBuilder`] with the specified token. /// - /// # Panics - /// If cannot get the `TELOXIDE_TOKEN` environmental variable. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - pub fn from_env_with_client(client: Client) -> Arc { - Self::with_client( - std::env::var("TELOXIDE_TOKEN") - .expect("Cannot get the TELOXIDE_TOKEN env variable"), - client, - ) - } - - /// Creates a new `Bot` with the specified token and the default - /// [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - pub fn new(token: S) -> Arc + /// [`BotBuilder`]: crate::BotBuilder + pub fn new(token: S) -> BotBuilder where S: Into, { - Self::with_client(token, Client::new()) - } - - /// Creates a new `Bot` with the specified token and your - /// [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - pub fn with_client(token: S, client: Client) -> Arc - where - S: Into, - { - Arc::new(Bot { + BotBuilder { token: token.into(), - client, + client: None, + } + } +} + +/// Used to build [`Bot`]. +/// +/// [`Bot`]: crate::Bot +pub struct BotBuilder { + token: String, + client: Option, +} + +impl BotBuilder { + /// Sets your custom [`reqwest::Client`] (teloxide will make all requests + /// using it). + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn client(mut self, client: Client) -> Self { + self.client = Some(client); + self + } + + /// Enables logging through [pretty-env-logger]. + /// + /// A logger will **only** print errors from teloxide and **all** logs from + /// your program. + /// + /// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger + pub fn enable_logging(self, crate_name: &'static str) -> Self { + Self::enable_logging_with_filter(self, crate_name, LevelFilter::Trace) + } + + /// Enables logging through [pretty-env-logger]. + /// + /// A logger will **only** print errors from teloxide and restrict logs from + /// your program by the specified filter. + /// + /// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger + pub fn enable_logging_with_filter( + self, + crate_name: &'static str, + filter: LevelFilter, + ) -> Self { + pretty_env_logger::formatted_builder() + .write_style(WriteStyle::Auto) + .filter(Some(crate_name), filter) + .filter(Some("teloxide"), LevelFilter::Error) + .init(); + self + } + + /// Builds [`Bot`]. + /// + /// Sets the default [`request::Client`] if you haven't specified yours. + /// + /// [`request::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + /// [`Bot`]: crate::Bot + pub fn build(self) -> Arc { + Arc::new(Bot { + token: self.token, + client: self.client.unwrap_or(Client::new()), }) } } diff --git a/src/lib.rs b/src/lib.rs index 8061b39f..2b2a3254 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ )] #![allow(clippy::match_bool)] -pub use bot::Bot; +pub use bot::{Bot, BotBuilder}; pub use errors::{ApiErrorKind, DownloadError, RequestError}; mod errors; @@ -18,3 +18,11 @@ pub mod types; pub mod utils; extern crate teloxide_macros; + +/// Expands to a name of your crate. +#[macro_export] +macro_rules! crate_name { + () => { + env!("CARGO_PKG_NAME") + }; +} diff --git a/src/prelude.rs b/src/prelude.rs index 97b01f16..7162d9b0 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,6 +1,7 @@ //! Commonly used items. pub use crate::{ + crate_name, dispatching::{ dialogue::{ exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage, From 726a46b5ab50b0f6040ce4e70f5473816ca4ac05 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 13 Feb 2020 20:21:23 +0600 Subject: [PATCH 66/91] Remove unnecessary dependencies --- Cargo.toml | 1 - examples/dialogue_bot/Cargo.toml | 1 - examples/multiple_handlers_bot/Cargo.toml | 1 - examples/ping_pong_bot/Cargo.toml | 1 - 4 files changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f57fcbf9..7b1da981 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ mime = "0.3.16" derive_more = "0.99.2" thiserror = "1.0.9" async-trait = "0.1.22" -duang = "0.1.2" futures = "0.3.1" pin-project = "0.4.6" serde_with_macros = "1.0.1" diff --git a/examples/dialogue_bot/Cargo.toml b/examples/dialogue_bot/Cargo.toml index 95b79db0..76482737 100644 --- a/examples/dialogue_bot/Cargo.toml +++ b/examples/dialogue_bot/Cargo.toml @@ -7,7 +7,6 @@ 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" smart-default = "0.6.0" diff --git a/examples/multiple_handlers_bot/Cargo.toml b/examples/multiple_handlers_bot/Cargo.toml index 5b44ccbe..37a9436a 100644 --- a/examples/multiple_handlers_bot/Cargo.toml +++ b/examples/multiple_handlers_bot/Cargo.toml @@ -7,7 +7,6 @@ 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/Cargo.toml b/examples/ping_pong_bot/Cargo.toml index 291ee2d6..5fc453e8 100644 --- a/examples/ping_pong_bot/Cargo.toml +++ b/examples/ping_pong_bot/Cargo.toml @@ -7,7 +7,6 @@ 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 = "../../" } From c9f811a31171dd736ca783b3e6d92998f13eaa8b Mon Sep 17 00:00:00 2001 From: p0lunin Date: Thu, 13 Feb 2020 17:42:24 +0200 Subject: [PATCH 67/91] fixed example and added documentation --- examples/admin_bot/Cargo.toml | 1 - examples/admin_bot/src/main.rs | 177 +++++++++++++++++++++------------ 2 files changed, 114 insertions(+), 64 deletions(-) diff --git a/examples/admin_bot/Cargo.toml b/examples/admin_bot/Cargo.toml index 56c7a9f9..90a0913c 100644 --- a/examples/admin_bot/Cargo.toml +++ b/examples/admin_bot/Cargo.toml @@ -7,7 +7,6 @@ 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 = "../../" } diff --git a/examples/admin_bot/src/main.rs b/examples/admin_bot/src/main.rs index c7c80ff5..cc32058c 100644 --- a/examples/admin_bot/src/main.rs +++ b/examples/admin_bot/src/main.rs @@ -2,8 +2,12 @@ use teloxide::prelude::*; use teloxide::utils::command::BotCommand; use teloxide::types::ChatPermissions; +// Declare type of handler context type Ctx = DispatcherHandlerCtx; +// Derive trait which allow to parse text with command into enum +// (rename = "lowercase") means that names of variants of enum will be lowercase before parsing +// `description` will be add before description of command when you call Command::descriptions() #[derive(BotCommand)] #[command(rename = "lowercase", description = "Use commands in format /%command% %num% %unit%")] enum Command { @@ -17,15 +21,17 @@ enum Command { Help, } +// Calculate time of restrict user. fn calc_restrict_time(num: i32, unit: &str) -> Result { match unit { "h"|"hours" => Ok(num * 3600), "m"|"minutes" => Ok(num * 60), "s"|"seconds" => Ok(num), - _ => Err("allowed units: h, m, s") + _ => Err("Allowed units: h, m, s") } } +// Parse args which user printed after command. fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> { let num = match args.get(0) { Some(s) => s, @@ -42,80 +48,127 @@ fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> { } } +// Parse input args into time to restrict fn parse_time_restrict(args: Vec<&str>) -> Result { let (num, unit) = parse_args(args)?; calc_restrict_time(num, unit) } -async fn handle_command(ctx: Ctx) -> Result<(), ()> { +// Mute user by replied message +async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { + match ctx.update.reply_to_message() { + Some(mes) => match parse_time_restrict(args) { + // Mute user temporarily... + Ok(time) => { + ctx.bot.restrict_chat_member( + ctx.update.chat_id(), + // Sender of message cannot be only in messages from channels + // so we can use unwrap() + mes.from().unwrap().id, + ChatPermissions::default() + ) + .until_date(ctx.update.date + time) + .send() + .await?; + } + // ...or permanently + Err(msg) => { + ctx.bot.restrict_chat_member( + ctx.update.chat_id(), + mes.from().unwrap().id, + ChatPermissions::default() + ) + .send() + .await?; + } + }, + None => { + ctx.reply_to("Use this command in reply to another message").send().await?; + }, + } + Ok(()) +} + +// Kick user by replied message +async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> { + match ctx.update.reply_to_message() { + Some(mes) => { + // `unban_chat_member` will also kick user from group chat + ctx.bot.unban_chat_member( + ctx.update.chat_id(), + mes.from().unwrap().id + ).send().await?; + }, + None => { + ctx.reply_to("Use this command in reply to another message").send().await?; + } + } + Ok(()) +} + +// Ban user by replied message +async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { + match ctx.update.reply_to_message() { + Some(mes) => match parse_time_restrict(args) { + // Mute user temporarily... + Ok(time) => { + ctx.bot.kick_chat_member( + ctx.update.chat_id(), + mes.from().unwrap().id + ) + .until_date(ctx.update.date + time) + .send() + .await?; + } + // ...or permanently + Err(msg) => { + ctx.bot.kick_chat_member( + ctx.update.chat_id(), + mes.from().unwrap().id + ) + .send() + .await?; + }, + }, + None => { + ctx.reply_to("Use this command in reply to another message").send().await?; + }, + } + Ok(()) +} + +// Handle all messages +async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { + // If message not from group stop handled. + // NOTE: in this case we have only one `message_handler`. If you have more, return + // DispatcherHandlerResult::next() so that the following handlers can receive this message! + if ctx.update.chat.is_group() { + return Ok(()); + } + if let Some(text) = ctx.update.text() { - let (command, args): (Command, Vec<&str>) = Command::parse(text).ok_or(())?; + // Parse text into command with args + let (command, args): (Command, Vec<&str>) = match Command::parse(text) { + Some(tuple) => tuple, + None => return Ok(()) + }; match command { Command::Help => { - ctx.answer(Command::descriptions()).send().await; + // Command::descriptions() return a message in format: + // + // %general_description% + // %prefix%%command% - %description% + ctx.answer(Command::descriptions()).send().await?; } Command::Kick => { - match ctx.update.reply_to_message() { - Some(mes) => { - if let Some(user) = mes.from() { - ctx.bot.unban_chat_member( - ctx.update.chat_id(), - user.id - ).send().await; - } - }, - None => { - ctx.reply_to("Use this command in reply to another message").send().await; - } - } + kick_user(&ctx).await?; } Command::Ban => { - match ctx.update.reply_to_message() { - Some(mes) => match parse_time_restrict(args) { - Ok(time) => { - if let Some(user) = mes.from() { - ctx.bot.kick_chat_member( - ctx.update.chat_id(), - user.id - ) - .until_date(ctx.update.date + time) - .send() - .await; - } - } - Err(msg) => { - ctx.answer(msg).send().await; - }, - }, - None => { - ctx.reply_to("Use this command in reply to another message").send().await; - }, - } + ban_user(&ctx, args).await?; } Command::Mute => { - match ctx.update.reply_to_message() { - Some(mes) => match parse_time_restrict(args) { - Ok(time) => { - if let Some(user) = mes.from() { - ctx.bot.restrict_chat_member( - ctx.update.chat_id(), - user.id, - ChatPermissions::default() - ) - .until_date(ctx.update.date + time) - .send() - .await; - } - } - Err(msg) => { - ctx.answer(msg).send().await; - } - }, - None => { - ctx.reply_to("Use this command in reply to another message").send().await; - }, - } + mute_user(&ctx, args).await?; } }; } @@ -125,9 +178,7 @@ async fn handle_command(ctx: Ctx) -> Result<(), ()> { #[tokio::main] async fn main() { - pretty_env_logger::init(); - - let bot = Bot::new("YourAwesomeToken"); + let bot = Bot::from_env().enable_logging(crate_name!()).build(); Dispatcher::new(bot) .message_handler(&handle_command) .dispatch() From 42e2f3fb42c5a34d50feca766935d1488de4e790 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 13 Feb 2020 23:23:22 +0600 Subject: [PATCH 68/91] Refactor --- .gitignore | 4 +- Cargo.toml | 1 - .../{admin_bot => commands_bot}/Cargo.toml | 3 +- .../{admin_bot => commands_bot}/src/main.rs | 100 +++++++++------- examples/dialogue_bot/Cargo.toml | 1 + examples/dialogue_bot/src/main.rs | 4 +- examples/multiple_handlers_bot/Cargo.toml | 1 + examples/multiple_handlers_bot/src/main.rs | 4 +- examples/ping_pong_bot/Cargo.toml | 1 + examples/ping_pong_bot/src/main.rs | 4 +- src/bot/mod.rs | 109 ++++++------------ src/lib.rs | 11 +- src/logging.rs | 50 ++++++++ src/prelude.rs | 1 - 14 files changed, 164 insertions(+), 130 deletions(-) rename examples/{admin_bot => commands_bot}/Cargo.toml (85%) rename examples/{admin_bot => commands_bot}/src/main.rs (70%) create mode 100644 src/logging.rs diff --git a/.gitignore b/.gitignore index 218bb4a8..0071482e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ Cargo.lock .idea/ .vscode/ -examples/target examples/ping_pong_bot/target examples/dialogue_bot/target -examples/multiple_handlers_bot/target \ No newline at end of file +examples/multiple_handlers_bot/target +examples/commands_bot/target \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 7b1da981..7df89eca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ tokio-util = { version = "0.2.0", features = ["full"] } reqwest = { version = "0.10", features = ["json", "stream", "native-tls-vendored"] } log = "0.4.8" -pretty_env_logger = "0.4.0" bytes = "0.5.3" mime = "0.3.16" diff --git a/examples/admin_bot/Cargo.toml b/examples/commands_bot/Cargo.toml similarity index 85% rename from examples/admin_bot/Cargo.toml rename to examples/commands_bot/Cargo.toml index 90a0913c..fc126db6 100644 --- a/examples/admin_bot/Cargo.toml +++ b/examples/commands_bot/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "admin_bot" +name = "commands_bot" version = "0.1.0" authors = ["p0lunin "] edition = "2018" @@ -9,6 +9,7 @@ edition = "2018" [dependencies] log = "0.4.8" tokio = "0.2.9" +pretty_env_logger = "0.4.0" teloxide = { path = "../../" } [profile.release] diff --git a/examples/admin_bot/src/main.rs b/examples/commands_bot/src/main.rs similarity index 70% rename from examples/admin_bot/src/main.rs rename to examples/commands_bot/src/main.rs index cc32058c..9cbf335c 100644 --- a/examples/admin_bot/src/main.rs +++ b/examples/commands_bot/src/main.rs @@ -1,15 +1,19 @@ -use teloxide::prelude::*; -use teloxide::utils::command::BotCommand; -use teloxide::types::ChatPermissions; +use teloxide::{ + prelude::*, types::ChatPermissions, utils::command::BotCommand, +}; // Declare type of handler context type Ctx = DispatcherHandlerCtx; // Derive trait which allow to parse text with command into enum -// (rename = "lowercase") means that names of variants of enum will be lowercase before parsing -// `description` will be add before description of command when you call Command::descriptions() +// (rename = "lowercase") means that names of variants of enum will be lowercase +// before parsing `description` will be add before description of command when +// you call Command::descriptions() #[derive(BotCommand)] -#[command(rename = "lowercase", description = "Use commands in format /%command% %num% %unit%")] +#[command( + rename = "lowercase", + description = "Use commands in format /%command% %num% %unit%" +)] enum Command { #[command(description = "kick user from chat.")] Kick, @@ -24,10 +28,10 @@ enum Command { // Calculate time of restrict user. fn calc_restrict_time(num: i32, unit: &str) -> Result { match unit { - "h"|"hours" => Ok(num * 3600), - "m"|"minutes" => Ok(num * 60), - "s"|"seconds" => Ok(num), - _ => Err("Allowed units: h, m, s") + "h" | "hours" => Ok(num * 3600), + "m" | "minutes" => Ok(num * 60), + "s" | "seconds" => Ok(num), + _ => Err("Allowed units: h, m, s"), } } @@ -39,7 +43,7 @@ fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> { }; let unit = match args.get(1) { Some(s) => s, - None => return Err("Use command in format /%command% %num% %unit%") + None => return Err("Use command in format /%command% %num% %unit%"), }; match num.parse::() { @@ -60,12 +64,14 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { Some(mes) => match parse_time_restrict(args) { // Mute user temporarily... Ok(time) => { - ctx.bot.restrict_chat_member( + ctx.bot + .restrict_chat_member( ctx.update.chat_id(), - // Sender of message cannot be only in messages from channels - // so we can use unwrap() + // Sender of message cannot be only in messages from + // channels so we can use + // unwrap() mes.from().unwrap().id, - ChatPermissions::default() + ChatPermissions::default(), ) .until_date(ctx.update.date + time) .send() @@ -73,18 +79,21 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { } // ...or permanently Err(msg) => { - ctx.bot.restrict_chat_member( + ctx.bot + .restrict_chat_member( ctx.update.chat_id(), mes.from().unwrap().id, - ChatPermissions::default() + ChatPermissions::default(), ) .send() .await?; } }, None => { - ctx.reply_to("Use this command in reply to another message").send().await?; - }, + ctx.reply_to("Use this command in reply to another message") + .send() + .await?; + } } Ok(()) } @@ -94,13 +103,15 @@ async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> { match ctx.update.reply_to_message() { Some(mes) => { // `unban_chat_member` will also kick user from group chat - ctx.bot.unban_chat_member( - ctx.update.chat_id(), - mes.from().unwrap().id - ).send().await?; - }, + ctx.bot + .unban_chat_member(ctx.update.chat_id(), mes.from().unwrap().id) + .send() + .await?; + } None => { - ctx.reply_to("Use this command in reply to another message").send().await?; + ctx.reply_to("Use this command in reply to another message") + .send() + .await?; } } Ok(()) @@ -112,27 +123,31 @@ async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { Some(mes) => match parse_time_restrict(args) { // Mute user temporarily... Ok(time) => { - ctx.bot.kick_chat_member( - ctx.update.chat_id(), - mes.from().unwrap().id - ) + ctx.bot + .kick_chat_member( + ctx.update.chat_id(), + mes.from().unwrap().id, + ) .until_date(ctx.update.date + time) .send() .await?; } // ...or permanently Err(msg) => { - ctx.bot.kick_chat_member( - ctx.update.chat_id(), - mes.from().unwrap().id - ) + ctx.bot + .kick_chat_member( + ctx.update.chat_id(), + mes.from().unwrap().id, + ) .send() .await?; - }, + } }, None => { - ctx.reply_to("Use this command in reply to another message").send().await?; - }, + ctx.reply_to("Use this command in reply to another message") + .send() + .await?; + } } Ok(()) } @@ -140,8 +155,9 @@ async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { // Handle all messages async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { // If message not from group stop handled. - // NOTE: in this case we have only one `message_handler`. If you have more, return - // DispatcherHandlerResult::next() so that the following handlers can receive this message! + // NOTE: in this case we have only one `message_handler`. If you have more, + // return DispatcherHandlerResult::next() so that the following handlers + // can receive this message! if ctx.update.chat.is_group() { return Ok(()); } @@ -150,7 +166,7 @@ async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { // Parse text into command with args let (command, args): (Command, Vec<&str>) = match Command::parse(text) { Some(tuple) => tuple, - None => return Ok(()) + None => return Ok(()), }; match command { @@ -178,7 +194,11 @@ async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { #[tokio::main] async fn main() { - let bot = Bot::from_env().enable_logging(crate_name!()).build(); + teloxide::enable_logging!(); + log::info!("Starting commands_bot!"); + + let bot = Bot::from_env(); + Dispatcher::new(bot) .message_handler(&handle_command) .dispatch() diff --git a/examples/dialogue_bot/Cargo.toml b/examples/dialogue_bot/Cargo.toml index 76482737..947470f0 100644 --- a/examples/dialogue_bot/Cargo.toml +++ b/examples/dialogue_bot/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] log = "0.4.8" tokio = "0.2.9" +pretty_env_logger = "0.4.0" smart-default = "0.6.0" parse-display = "0.1.1" teloxide = { path = "../../" } diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index 0a7003cb..4591523a 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -177,9 +177,11 @@ async fn main() { } async fn run() { - let bot = Bot::from_env().enable_logging(crate_name!()).build(); + teloxide::enable_logging!(); log::info!("Starting dialogue_bot!"); + let bot = Bot::from_env(); + Dispatcher::new(bot) .message_handler(&DialogueDispatcher::new(|ctx| async move { handle_message(ctx) diff --git a/examples/multiple_handlers_bot/Cargo.toml b/examples/multiple_handlers_bot/Cargo.toml index 37a9436a..89a1b61a 100644 --- a/examples/multiple_handlers_bot/Cargo.toml +++ b/examples/multiple_handlers_bot/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" [dependencies] log = "0.4.8" tokio = "0.2.9" +pretty_env_logger = "0.4.0" teloxide = { path = "../../" } \ No newline at end of file diff --git a/examples/multiple_handlers_bot/src/main.rs b/examples/multiple_handlers_bot/src/main.rs index ff814ca4..924fceac 100644 --- a/examples/multiple_handlers_bot/src/main.rs +++ b/examples/multiple_handlers_bot/src/main.rs @@ -6,9 +6,11 @@ async fn main() { } async fn run() { - let bot = Bot::from_env().enable_logging(crate_name!()).build(); + teloxide::enable_logging!(); log::info!("Starting multiple_handlers_bot!"); + let bot = Bot::from_env(); + // Create a dispatcher with multiple handlers of different types. This will // print One! and Two! on every incoming UpdateKind::Message. Dispatcher::::new(bot) diff --git a/examples/ping_pong_bot/Cargo.toml b/examples/ping_pong_bot/Cargo.toml index 5fc453e8..73a9d9ed 100644 --- a/examples/ping_pong_bot/Cargo.toml +++ b/examples/ping_pong_bot/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] log = "0.4.8" tokio = "0.2.9" +pretty_env_logger = "0.4.0" teloxide = { path = "../../" } [profile.release] diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 617fb1c0..e731200c 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -6,9 +6,11 @@ async fn main() { } async fn run() { - let bot = Bot::from_env().enable_logging(crate_name!()).build(); + teloxide::enable_logging!(); log::info!("Starting ping_pong_bot!"); + let bot = Bot::from_env(); + // Create a dispatcher with a single message handler that answers "pong" to // each incoming message. Dispatcher::::new(bot) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index ad251844..942caae1 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,5 +1,3 @@ -use log::LevelFilter; -use pretty_env_logger::env_logger::WriteStyle; use reqwest::Client; use std::sync::Arc; @@ -14,92 +12,57 @@ pub struct Bot { } impl Bot { - /// Returns [`BotBuilder`] from the `TELOXIDE_TOKEN` environmental variable - /// (a bot's token). + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a + /// bot's token) and the default [`reqwest::Client`]. /// /// # Panics /// If cannot get the `TELOXIDE_TOKEN` environmental variable. /// - /// [`BotBuilder`]: crate::BotBuilder - pub fn from_env() -> BotBuilder { - BotBuilder { - token: std::env::var("TELOXIDE_TOKEN") + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn from_env() -> Arc { + Self::new( + std::env::var("TELOXIDE_TOKEN") .expect("Cannot get the TELOXIDE_TOKEN env variable"), - client: None, - } + ) } - /// Returns [`BotBuilder`] with the specified token. + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a + /// bot's token) and your [`reqwest::Client`]. /// - /// [`BotBuilder`]: crate::BotBuilder - pub fn new(token: S) -> BotBuilder + /// # Panics + /// If cannot get the `TELOXIDE_TOKEN` environmental variable. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn from_env_with_client(client: Client) -> Arc { + Self::with_client( + std::env::var("TELOXIDE_TOKEN") + .expect("Cannot get the TELOXIDE_TOKEN env variable"), + client, + ) + } + + /// Creates a new `Bot` with the specified token and the default + /// [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn new(token: S) -> Arc where S: Into, { - BotBuilder { - token: token.into(), - client: None, - } + Self::with_client(token, Client::new()) } -} -/// Used to build [`Bot`]. -/// -/// [`Bot`]: crate::Bot -pub struct BotBuilder { - token: String, - client: Option, -} - -impl BotBuilder { - /// Sets your custom [`reqwest::Client`] (teloxide will make all requests - /// using it). + /// Creates a new `Bot` with the specified token and your + /// [`reqwest::Client`]. /// /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - pub fn client(mut self, client: Client) -> Self { - self.client = Some(client); - self - } - - /// Enables logging through [pretty-env-logger]. - /// - /// A logger will **only** print errors from teloxide and **all** logs from - /// your program. - /// - /// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger - pub fn enable_logging(self, crate_name: &'static str) -> Self { - Self::enable_logging_with_filter(self, crate_name, LevelFilter::Trace) - } - - /// Enables logging through [pretty-env-logger]. - /// - /// A logger will **only** print errors from teloxide and restrict logs from - /// your program by the specified filter. - /// - /// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger - pub fn enable_logging_with_filter( - self, - crate_name: &'static str, - filter: LevelFilter, - ) -> Self { - pretty_env_logger::formatted_builder() - .write_style(WriteStyle::Auto) - .filter(Some(crate_name), filter) - .filter(Some("teloxide"), LevelFilter::Error) - .init(); - self - } - - /// Builds [`Bot`]. - /// - /// Sets the default [`request::Client`] if you haven't specified yours. - /// - /// [`request::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// [`Bot`]: crate::Bot - pub fn build(self) -> Arc { - Arc::new(Bot { - token: self.token, - client: self.client.unwrap_or(Client::new()), + pub fn with_client(token: S, client: Client) -> Arc + where + S: Into, + { + Arc::new(Self { + token: token.into(), + client, }) } } diff --git a/src/lib.rs b/src/lib.rs index 2b2a3254..64f273ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ )] #![allow(clippy::match_bool)] -pub use bot::{Bot, BotBuilder}; +pub use bot::Bot; pub use errors::{ApiErrorKind, DownloadError, RequestError}; mod errors; @@ -12,17 +12,10 @@ mod net; mod bot; pub mod dispatching; +mod logging; pub mod prelude; pub mod requests; pub mod types; pub mod utils; extern crate teloxide_macros; - -/// Expands to a name of your crate. -#[macro_export] -macro_rules! crate_name { - () => { - env!("CARGO_PKG_NAME") - }; -} diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 00000000..5080fb8d --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,50 @@ +/// Enables logging through [pretty-env-logger]. +/// +/// A logger will **only** print errors from teloxide and **all** logs from +/// your program. +/// +/// # Example +/// ``` +/// teloxide::enable_logging!(); +/// ``` +/// +/// # Note +/// Calling this macro **is not mandatory**; you can setup if your own logger if +/// you want. +/// +/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger +#[macro_export] +macro_rules! enable_logging { + () => { + teloxide::enable_logging_with_filter!(log::LevelFilter::Trace); + }; +} + +/// Enables logging through [pretty-env-logger]. +/// +/// A logger will **only** print errors from teloxide and restrict logs from +/// your program by the specified filter. +/// +/// # Example +/// Allow printing all logs from your program up to [`LevelFilter::Debug`] (i.e. +/// do not print traces): +/// +/// ``` +/// teloxide::enable_logging_with_filter!(log::LevelFilter::Debug); +/// ``` +/// +/// # Note +/// Calling this macro **is not mandatory**; you can setup if your own logger if +/// you want. +/// +/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger +#[macro_export] +macro_rules! enable_logging_with_filter { + ($filter:expr) => { + pretty_env_logger::formatted_builder() + .write_style(pretty_env_logger::env_logger::WriteStyle::Auto) + .filter(Some(env!("CARGO_PKG_NAME")), $filter) + .filter(Some("teloxide"), log::LevelFilter::Error) + .init(); + }; +} diff --git a/src/prelude.rs b/src/prelude.rs index 7162d9b0..97b01f16 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,7 +1,6 @@ //! Commonly used items. pub use crate::{ - crate_name, dispatching::{ dialogue::{ exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage, From 34d44e519571a0b7c7bc561b68ba827a5e0e98d7 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 13 Feb 2020 23:38:05 +0600 Subject: [PATCH 69/91] Add a description to examples/dialogue_bot --- examples/dialogue_bot/src/main.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index 4591523a..365dabb3 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -1,3 +1,19 @@ +// This is a bot that asks your full name, your age, your favourite kind of +// music and sends all the gathered information back. +// +// # Example +// ``` +// - Let's start! First, what's your full name? +// - Luke Skywalker +// - WHat a wonderful name! Your age? +// - 26 +// - Good. Now choose your favourite music +// *A keyboard of music kinds is displayed* +// *You select Metal* +// - Metal +// - Fine. Your full name: Luke Skywalker, your age: 26, your favourite music: Metal +// ``` + #![allow(clippy::trivial_regex)] #[macro_use] From 653c2e3f66f9ad8f726f4bf1bee210c3ba3b7215 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 13 Feb 2020 23:45:16 +0600 Subject: [PATCH 70/91] Fix the docs --- src/logging.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/logging.rs b/src/logging.rs index 5080fb8d..5da843ab 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -20,7 +20,8 @@ macro_rules! enable_logging { }; } -/// Enables logging through [pretty-env-logger]. +/// Enables logging through [pretty-env-logger] with a custom filter for your +/// program. /// /// A logger will **only** print errors from teloxide and restrict logs from /// your program by the specified filter. @@ -38,6 +39,7 @@ macro_rules! enable_logging { /// you want. /// /// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger +/// [`LevelFilter::Debug`]: https://docs.rs/log/0.4.10/log/enum.LevelFilter.html #[macro_export] macro_rules! enable_logging_with_filter { ($filter:expr) => { From 5d0db21fca5509977a93736a72a86531f7652565 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 00:11:56 +0600 Subject: [PATCH 71/91] Fix examples/commands_bot --- examples/commands_bot/src/main.rs | 63 ++++++++++++++++--------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/examples/commands_bot/src/main.rs b/examples/commands_bot/src/main.rs index 9cbf335c..c9a50070 100644 --- a/examples/commands_bot/src/main.rs +++ b/examples/commands_bot/src/main.rs @@ -2,13 +2,13 @@ use teloxide::{ prelude::*, types::ChatPermissions, utils::command::BotCommand, }; -// Declare type of handler context -type Ctx = DispatcherHandlerCtx; - -// Derive trait which allow to parse text with command into enum -// (rename = "lowercase") means that names of variants of enum will be lowercase -// before parsing `description` will be add before description of command when -// you call Command::descriptions() +// Derive BotCommand to parse text with a command into this enumeration. +// +// 1. rename = "lowercase" turns all the commands into lowercase letters. +// 2. `description = "..."` specifies a text before all the commands. +// +// That is, you can just call Command::descriptions() to get a description of +// your commands. #[derive(BotCommand)] #[command( rename = "lowercase", @@ -25,7 +25,7 @@ enum Command { Help, } -// Calculate time of restrict user. +// Calculates time of user restriction. fn calc_restrict_time(num: i32, unit: &str) -> Result { match unit { "h" | "hours" => Ok(num * 3600), @@ -35,7 +35,7 @@ fn calc_restrict_time(num: i32, unit: &str) -> Result { } } -// Parse args which user printed after command. +// Parse arguments after a command. fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> { let num = match args.get(0) { Some(s) => s, @@ -52,16 +52,18 @@ fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> { } } -// Parse input args into time to restrict +// Parse arguments into a user restriction duration. fn parse_time_restrict(args: Vec<&str>) -> Result { let (num, unit) = parse_args(args)?; calc_restrict_time(num, unit) } -// Mute user by replied message +type Ctx = DispatcherHandlerCtx; + +// Mute a user with a replied message. async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { match ctx.update.reply_to_message() { - Some(mes) => match parse_time_restrict(args) { + Some(msg1) => match parse_time_restrict(args) { // Mute user temporarily... Ok(time) => { ctx.bot @@ -70,7 +72,7 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { // Sender of message cannot be only in messages from // channels so we can use // unwrap() - mes.from().unwrap().id, + msg1.from().unwrap().id, ChatPermissions::default(), ) .until_date(ctx.update.date + time) @@ -78,11 +80,11 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { .await?; } // ...or permanently - Err(msg) => { + Err(_) => { ctx.bot .restrict_chat_member( ctx.update.chat_id(), - mes.from().unwrap().id, + msg1.from().unwrap().id, ChatPermissions::default(), ) .send() @@ -98,7 +100,7 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { Ok(()) } -// Kick user by replied message +// Kick a user with replied message. async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> { match ctx.update.reply_to_message() { Some(mes) => { @@ -117,34 +119,34 @@ async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> { Ok(()) } -// Ban user by replied message +// Ban a user with replied message. async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { match ctx.update.reply_to_message() { - Some(mes) => match parse_time_restrict(args) { + Some(message) => match parse_time_restrict(args) { // Mute user temporarily... Ok(time) => { ctx.bot .kick_chat_member( ctx.update.chat_id(), - mes.from().unwrap().id, + message.from().expect("Must be MessageKind::Common").id, ) .until_date(ctx.update.date + time) .send() .await?; } // ...or permanently - Err(msg) => { + Err(_) => { ctx.bot .kick_chat_member( ctx.update.chat_id(), - mes.from().unwrap().id, + message.from().unwrap().id, ) .send() .await?; } }, None => { - ctx.reply_to("Use this command in reply to another message") + ctx.reply_to("Use this command in a reply to another message!") .send() .await?; } @@ -152,18 +154,17 @@ async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { Ok(()) } -// Handle all messages +// Handle all messages. async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { - // If message not from group stop handled. - // NOTE: in this case we have only one `message_handler`. If you have more, - // return DispatcherHandlerResult::next() so that the following handlers - // can receive this message! + // If a message isn't from a group, stop handling. if ctx.update.chat.is_group() { + // Note: this is the same as DispatcherHandlerResult::exit(Ok(())). If + // you have more handlers, use DispatcherHandlerResult::next(...) return Ok(()); } if let Some(text) = ctx.update.text() { - // Parse text into command with args + // Parse text into a command with args. let (command, args): (Command, Vec<&str>) = match Command::parse(text) { Some(tuple) => tuple, None => return Ok(()), @@ -171,10 +172,10 @@ async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { match command { Command::Help => { - // Command::descriptions() return a message in format: + // Command::descriptions() returns text in this format: // - // %general_description% - // %prefix%%command% - %description% + // %GENERAL-DESCRIPTION% + // %PREFIX%%COMMAND% - %DESCRIPTION% ctx.answer(Command::descriptions()).send().await?; } Command::Kick => { From fa5b725681f0dfe908caaeeae3f28c05bc434417 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 00:15:10 +0600 Subject: [PATCH 72/91] Add a description to examples/multiple_handlers_bot --- examples/multiple_handlers_bot/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/multiple_handlers_bot/src/main.rs b/examples/multiple_handlers_bot/src/main.rs index 924fceac..c09353e5 100644 --- a/examples/multiple_handlers_bot/src/main.rs +++ b/examples/multiple_handlers_bot/src/main.rs @@ -1,3 +1,6 @@ +// This example demonstrates the ability of Dispatcher to deal with multiple +// handlers. + use teloxide::prelude::*; #[tokio::main] From fc36cf142ae7cd179e6c71329811c149669d2b3b Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 00:17:31 +0600 Subject: [PATCH 73/91] Fix a typo in examples/dialogue_bot --- examples/dialogue_bot/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index 365dabb3..6a75d68c 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -5,7 +5,7 @@ // ``` // - Let's start! First, what's your full name? // - Luke Skywalker -// - WHat a wonderful name! Your age? +// - What a wonderful name! Your age? // - 26 // - Good. Now choose your favourite music // *A keyboard of music kinds is displayed* From 9f2aa741f71a7df4453f6815f84986c4da0e91c0 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 00:20:22 +0600 Subject: [PATCH 74/91] SImplify examples/commands_bot for a little bit --- examples/commands_bot/src/main.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/examples/commands_bot/src/main.rs b/examples/commands_bot/src/main.rs index c9a50070..de66a7df 100644 --- a/examples/commands_bot/src/main.rs +++ b/examples/commands_bot/src/main.rs @@ -8,7 +8,9 @@ use teloxide::{ // 2. `description = "..."` specifies a text before all the commands. // // That is, you can just call Command::descriptions() to get a description of -// your commands. +// your commands in this format: +// %GENERAL-DESCRIPTION% +// %PREFIX%%COMMAND% - %DESCRIPTION% #[derive(BotCommand)] #[command( rename = "lowercase", @@ -69,10 +71,7 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { ctx.bot .restrict_chat_member( ctx.update.chat_id(), - // Sender of message cannot be only in messages from - // channels so we can use - // unwrap() - msg1.from().unwrap().id, + msg1.from().expect("Must be MessageKind::Common").id, ChatPermissions::default(), ) .until_date(ctx.update.date + time) @@ -172,10 +171,6 @@ async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { match command { Command::Help => { - // Command::descriptions() returns text in this format: - // - // %GENERAL-DESCRIPTION% - // %PREFIX%%COMMAND% - %DESCRIPTION% ctx.answer(Command::descriptions()).send().await?; } Command::Kick => { From e3a032abff9967351f0806c3bc23750cea22c32f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 00:23:42 +0600 Subject: [PATCH 75/91] And again! --- examples/commands_bot/src/main.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/commands_bot/src/main.rs b/examples/commands_bot/src/main.rs index de66a7df..89a6971a 100644 --- a/examples/commands_bot/src/main.rs +++ b/examples/commands_bot/src/main.rs @@ -99,11 +99,11 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { Ok(()) } -// Kick a user with replied message. +// Kick a user with a replied message. async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> { match ctx.update.reply_to_message() { Some(mes) => { - // `unban_chat_member` will also kick user from group chat + // bot.unban_chat_member can also kicks a user from a group chat. ctx.bot .unban_chat_member(ctx.update.chat_id(), mes.from().unwrap().id) .send() @@ -155,10 +155,9 @@ async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { // Handle all messages. async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { - // If a message isn't from a group, stop handling. if ctx.update.chat.is_group() { - // Note: this is the same as DispatcherHandlerResult::exit(Ok(())). If - // you have more handlers, use DispatcherHandlerResult::next(...) + // The same as DispatcherHandlerResult::exit(Ok(())). If you have more + // handlers, use DispatcherHandlerResult::next(...) return Ok(()); } From b6cd019682304b404d36a254b4d21f4e4dda2fce Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 00:26:06 +0600 Subject: [PATCH 76/91] Add TODO to examples/commands_bot --- examples/commands_bot/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/commands_bot/src/main.rs b/examples/commands_bot/src/main.rs index 89a6971a..aa0823e4 100644 --- a/examples/commands_bot/src/main.rs +++ b/examples/commands_bot/src/main.rs @@ -1,3 +1,5 @@ +// TODO: simplify this and use typed command variants (see https://github.com/teloxide/teloxide/issues/152). + use teloxide::{ prelude::*, types::ChatPermissions, utils::command::BotCommand, }; From 52780419270033d84dcecf8ba1c554f51ae29980 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 00:29:26 +0600 Subject: [PATCH 77/91] Rename commands_bot -> admin_bot --- .gitignore | 2 +- examples/{commands_bot => admin_bot}/Cargo.toml | 2 +- examples/{commands_bot => admin_bot}/src/main.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename examples/{commands_bot => admin_bot}/Cargo.toml (93%) rename examples/{commands_bot => admin_bot}/src/main.rs (99%) diff --git a/.gitignore b/.gitignore index 0071482e..6fbae765 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ Cargo.lock examples/ping_pong_bot/target examples/dialogue_bot/target examples/multiple_handlers_bot/target -examples/commands_bot/target \ No newline at end of file +examples/admin_bot/target \ No newline at end of file diff --git a/examples/commands_bot/Cargo.toml b/examples/admin_bot/Cargo.toml similarity index 93% rename from examples/commands_bot/Cargo.toml rename to examples/admin_bot/Cargo.toml index fc126db6..c9470f3d 100644 --- a/examples/commands_bot/Cargo.toml +++ b/examples/admin_bot/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "commands_bot" +name = "admin_bot" version = "0.1.0" authors = ["p0lunin "] edition = "2018" diff --git a/examples/commands_bot/src/main.rs b/examples/admin_bot/src/main.rs similarity index 99% rename from examples/commands_bot/src/main.rs rename to examples/admin_bot/src/main.rs index aa0823e4..9a891a5f 100644 --- a/examples/commands_bot/src/main.rs +++ b/examples/admin_bot/src/main.rs @@ -192,7 +192,7 @@ async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { #[tokio::main] async fn main() { teloxide::enable_logging!(); - log::info!("Starting commands_bot!"); + log::info!("Starting admin_bot!"); let bot = Bot::from_env(); From 5f12170e5c7a23ffd2e15d27d18a7e5f446e72a8 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 00:32:21 +0600 Subject: [PATCH 78/91] Add a link to examples/admin_bot to teloxide::utils::command --- src/utils/command.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/command.rs b/src/utils/command.rs index 53350936..7ca76b83 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -39,9 +39,12 @@ //! assert_eq!(args, vec!["3", "hours"]); //! ``` //! +//! See [examples/admin_bot] as a more complicated examples. +//! //! [`parse_command`]: crate::utils::command::parse_command //! [`parse_command_with_prefix`]: //! crate::utils::command::parse_command_with_prefix +//! [examples/admin_bot]: https://github.com/teloxide/teloxide/blob/dev/examples/miltiple_handlers_bot/ pub use teloxide_macros::BotCommand; From d63135016a36197f44a6b0abe97913cd65c1db76 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 01:54:43 +0600 Subject: [PATCH 79/91] Add examples/guess_a_number_bot --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6fbae765..2f4d36a0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ Cargo.lock examples/ping_pong_bot/target examples/dialogue_bot/target examples/multiple_handlers_bot/target -examples/admin_bot/target \ No newline at end of file +examples/admin_bot/target +examples/guess_a_number_bot \ No newline at end of file From dad05955d18c070829d6cb156cb169efc3510023 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 01:59:02 +0600 Subject: [PATCH 80/91] Add descriptions to examples/README.md --- examples/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index af2ee63f..245cabf9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,2 +1,8 @@ # Examples -Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable. \ No newline at end of file +Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable. + + - [ping_pong_bot](ping_pong_bot) - Just answers "pong" to each incoming message. + - [guess_a_number_bot](guess_a_number_bot) - The "guess a number" game. + - [dialogue_bot](dialogue_bot) - Drive a dialogue with a user using a type-safe finite automaton. + - [admin_bot](admin_bot) - Shows how to deal with bot's commands. + - [multiple_handlers_bot](multiple_handlers_bot) - Shows how multiple dispatcher's handlers relate to each other. \ No newline at end of file From 027b80f19b27cb8e4d617af213abc1fcc43d27a4 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 02:00:54 +0600 Subject: [PATCH 81/91] Add examples/guess_a_number_bot --- .gitignore | 2 +- examples/guess_a_number_bot/Cargo.toml | 15 ++++ examples/guess_a_number_bot/src/main.rs | 112 ++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 examples/guess_a_number_bot/Cargo.toml create mode 100644 examples/guess_a_number_bot/src/main.rs diff --git a/.gitignore b/.gitignore index 2f4d36a0..9b9c35d6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ examples/ping_pong_bot/target examples/dialogue_bot/target examples/multiple_handlers_bot/target examples/admin_bot/target -examples/guess_a_number_bot \ No newline at end of file +examples/guess_a_number_bot/target \ No newline at end of file diff --git a/examples/guess_a_number_bot/Cargo.toml b/examples/guess_a_number_bot/Cargo.toml new file mode 100644 index 00000000..c81ef20f --- /dev/null +++ b/examples/guess_a_number_bot/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "guess_a_number_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] +log = "0.4.8" +tokio = "0.2.9" +smart-default = "0.6.0" +rand = "0.7.3" +pretty_env_logger = "0.4.0" +teloxide = { path = "../../" } \ No newline at end of file diff --git a/examples/guess_a_number_bot/src/main.rs b/examples/guess_a_number_bot/src/main.rs new file mode 100644 index 00000000..e0c1ca9f --- /dev/null +++ b/examples/guess_a_number_bot/src/main.rs @@ -0,0 +1,112 @@ +// This is a guess-a-number game! +// +// # Example +// ``` +// - Hello +// - Let's play a game! Guess a number from 1 to 10 (inclusively). +// - 4 +// - No. +// - 3 +// - No. +// - Blablabla +// - Oh, please, send me a text message! +// - 111 +// - Oh, please, send me a number in the range [1; 10]! +// - 5 +// - Congratulations! You won! +// ``` + +#[macro_use] +extern crate smart_default; + +use teloxide::prelude::*; + +use rand::{thread_rng, Rng}; + +// ============================================================================ +// [A type-safe finite automaton] +// ============================================================================ + +#[derive(SmartDefault)] +enum Dialogue { + #[default] + Start, + ReceiveAttempt(u8), +} + +// ============================================================================ +// [Control a dialogue] +// ============================================================================ + +async fn handle_message( + ctx: DialogueHandlerCtx, +) -> Result, RequestError> { + match ctx.dialogue { + Dialogue::Start => { + ctx.answer( + "Let's play a game! Guess a number from 1 to 10 (inclusively).", + ) + .send() + .await?; + next(Dialogue::ReceiveAttempt(thread_rng().gen_range(1, 11))) + } + Dialogue::ReceiveAttempt(secret) => match ctx.update.text() { + None => { + ctx.answer("Oh, please, send me a text message!") + .send() + .await?; + next(ctx.dialogue) + } + Some(text) => match text.parse::() { + Ok(attempt) => match attempt { + x if !(1..=10).contains(&x) => { + ctx.answer( + "Oh, please, send me a number in the range [1; \ + 10]!", + ) + .send() + .await?; + next(ctx.dialogue) + } + x if x == secret => { + ctx.answer("Congratulations! You won!").send().await?; + exit() + } + _ => { + ctx.answer("No.").send().await?; + next(ctx.dialogue) + } + }, + Err(_) => { + ctx.answer("Oh, please, send me a number!").send().await?; + next(ctx.dialogue) + } + }, + }, + } +} + +// ============================================================================ +// [Run!] +// ============================================================================ + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + teloxide::enable_logging!(); + log::info!("Starting guess_a_number_bot!"); + + let bot = Bot::from_env(); + + Dispatcher::new(bot) + .message_handler(&DialogueDispatcher::new(|ctx| async move { + handle_message(ctx) + .await + .expect("Something wrong with the bot!") + })) + .dispatch() + .await; +} From 2147ba204809e0e5f909b76ce25d52f5d81e665f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 03:23:41 +0600 Subject: [PATCH 82/91] Add examples/simple_commands_bot --- .gitignore | 3 +- examples/README.md | 5 ++- examples/simple_commands_bot/Cargo.toml | 13 ++++++ examples/simple_commands_bot/src/main.rs | 54 ++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 examples/simple_commands_bot/Cargo.toml create mode 100644 examples/simple_commands_bot/src/main.rs diff --git a/.gitignore b/.gitignore index 9b9c35d6..f2d88a0e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ examples/ping_pong_bot/target examples/dialogue_bot/target examples/multiple_handlers_bot/target examples/admin_bot/target -examples/guess_a_number_bot/target \ No newline at end of file +examples/guess_a_number_bot/target +examples/simple_commands_bot/target \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 245cabf9..7102117e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,8 +1,9 @@ # Examples Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable. - - [ping_pong_bot](ping_pong_bot) - Just answers "pong" to each incoming message. + - [ping_pong_bot](ping_pong_bot) - Answers "pong" to each incoming message. + - [simple_commands_bot](simple_commands_bot) - Shows how to deal with bot's commands. - [guess_a_number_bot](guess_a_number_bot) - The "guess a number" game. - [dialogue_bot](dialogue_bot) - Drive a dialogue with a user using a type-safe finite automaton. - - [admin_bot](admin_bot) - Shows how to deal with bot's commands. + - [admin_bot](admin_bot) - A bot, which can ban, kick, and mute on a command. - [multiple_handlers_bot](multiple_handlers_bot) - Shows how multiple dispatcher's handlers relate to each other. \ No newline at end of file diff --git a/examples/simple_commands_bot/Cargo.toml b/examples/simple_commands_bot/Cargo.toml new file mode 100644 index 00000000..f3e9db65 --- /dev/null +++ b/examples/simple_commands_bot/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "simple_commands_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] +log = "0.4.8" +tokio = "0.2.9" +pretty_env_logger = "0.4.0" +teloxide = { path = "../../" } diff --git a/examples/simple_commands_bot/src/main.rs b/examples/simple_commands_bot/src/main.rs new file mode 100644 index 00000000..fc6b449e --- /dev/null +++ b/examples/simple_commands_bot/src/main.rs @@ -0,0 +1,54 @@ +use teloxide::{prelude::*, utils::command::BotCommand}; + +#[derive(BotCommand)] +#[command(rename = "lowercase", description = "These commands are supported:")] +enum Command { + #[command(description = "display this text.")] + Help, + #[command(description = "be a cat.")] + Meow, + #[command(description = "be a dog.")] + Woof, + #[command(description = "be a cow.")] + Moo, +} + +async fn handle_command( + ctx: DispatcherHandlerCtx, +) -> Result<(), RequestError> { + let text = match ctx.update.text() { + Some(text) => text, + None => return Ok(()), + }; + + let command = match Command::parse(text) { + Some((command, _)) => command, + None => return Ok(()), + }; + + match command { + Command::Help => ctx.answer(Command::descriptions()).send().await?, + Command::Meow => ctx.answer("I am a cat! Meow!").send().await?, + Command::Woof => ctx.answer("I am a dog! Woof!").send().await?, + Command::Moo => ctx.answer("I am a cow! Moo!").send().await?, + }; + + Ok(()) +} + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + teloxide::enable_logging!(); + log::info!("Starting ping_pong_bot!"); + + let bot = Bot::from_env(); + + Dispatcher::::new(bot) + .message_handler(&handle_command) + .dispatch() + .await; +} From 7a582256ef0c4e2d2959d427d00e6fa85a4f5f8e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 03:26:46 +0600 Subject: [PATCH 83/91] Fix a typo in examples/simple_commands_bot --- examples/simple_commands_bot/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_commands_bot/src/main.rs b/examples/simple_commands_bot/src/main.rs index fc6b449e..24a73158 100644 --- a/examples/simple_commands_bot/src/main.rs +++ b/examples/simple_commands_bot/src/main.rs @@ -43,7 +43,7 @@ async fn main() { async fn run() { teloxide::enable_logging!(); - log::info!("Starting ping_pong_bot!"); + log::info!("Starting simple_commands_bot!"); let bot = Bot::from_env(); From 8c5b1d3db097d54b9d647e3aab55f798c7f7be71 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 03:48:26 +0600 Subject: [PATCH 84/91] Fix examples/simple_commands_bot --- examples/simple_commands_bot/src/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/simple_commands_bot/src/main.rs b/examples/simple_commands_bot/src/main.rs index 24a73158..20ceebbc 100644 --- a/examples/simple_commands_bot/src/main.rs +++ b/examples/simple_commands_bot/src/main.rs @@ -18,12 +18,18 @@ async fn handle_command( ) -> Result<(), RequestError> { let text = match ctx.update.text() { Some(text) => text, - None => return Ok(()), + None => { + log::info!("Received a message, but not text."); + return Ok(()); + } }; let command = match Command::parse(text) { Some((command, _)) => command, - None => return Ok(()), + None => { + log::info!("Received a text message, but not a command."); + return Ok(()); + } }; match command { From 141ad171819bf0de2da28f27371867531dd3aa7a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 04:09:46 +0600 Subject: [PATCH 85/91] Refactor --- examples/guess_a_number_bot/src/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/guess_a_number_bot/src/main.rs b/examples/guess_a_number_bot/src/main.rs index e0c1ca9f..2e9d573c 100644 --- a/examples/guess_a_number_bot/src/main.rs +++ b/examples/guess_a_number_bot/src/main.rs @@ -78,7 +78,11 @@ async fn handle_message( } }, Err(_) => { - ctx.answer("Oh, please, send me a number!").send().await?; + ctx.answer( + "Oh, please, send me a number in the range [1; 10]!", + ) + .send() + .await?; next(ctx.dialogue) } }, From 08ce1b4eed8c99fe20bb2f8cc61cd9b7257a2b14 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 04:14:14 +0600 Subject: [PATCH 86/91] Fix the CI --- src/logging.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logging.rs b/src/logging.rs index 5da843ab..ccb4c7c1 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -4,7 +4,7 @@ /// your program. /// /// # Example -/// ``` +/// ```no_compile /// teloxide::enable_logging!(); /// ``` /// @@ -30,7 +30,7 @@ macro_rules! enable_logging { /// Allow printing all logs from your program up to [`LevelFilter::Debug`] (i.e. /// do not print traces): /// -/// ``` +/// ```no_compile /// teloxide::enable_logging_with_filter!(log::LevelFilter::Debug); /// ``` /// From 67a781fb20f9ebf4276155ef7ad34074152645b9 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 04:35:36 +0600 Subject: [PATCH 87/91] Modify examples/simple_commands_bot --- examples/simple_commands_bot/Cargo.toml | 1 + examples/simple_commands_bot/src/main.rs | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/simple_commands_bot/Cargo.toml b/examples/simple_commands_bot/Cargo.toml index f3e9db65..6baf6eb8 100644 --- a/examples/simple_commands_bot/Cargo.toml +++ b/examples/simple_commands_bot/Cargo.toml @@ -9,5 +9,6 @@ edition = "2018" [dependencies] log = "0.4.8" tokio = "0.2.9" +rand = "0.7.3" pretty_env_logger = "0.4.0" teloxide = { path = "../../" } diff --git a/examples/simple_commands_bot/src/main.rs b/examples/simple_commands_bot/src/main.rs index 20ceebbc..3d6525c5 100644 --- a/examples/simple_commands_bot/src/main.rs +++ b/examples/simple_commands_bot/src/main.rs @@ -1,5 +1,7 @@ use teloxide::{prelude::*, utils::command::BotCommand}; +use rand::{thread_rng, Rng}; + #[derive(BotCommand)] #[command(rename = "lowercase", description = "These commands are supported:")] enum Command { @@ -7,10 +9,8 @@ enum Command { Help, #[command(description = "be a cat.")] Meow, - #[command(description = "be a dog.")] - Woof, - #[command(description = "be a cow.")] - Moo, + #[command(description = "generate a random number within [0; 1).")] + Generate, } async fn handle_command( @@ -34,9 +34,12 @@ async fn handle_command( match command { Command::Help => ctx.answer(Command::descriptions()).send().await?, + Command::Generate => { + ctx.answer(thread_rng().gen_range(0.0, 1.0).to_string()) + .send() + .await? + } Command::Meow => ctx.answer("I am a cat! Meow!").send().await?, - Command::Woof => ctx.answer("I am a dog! Woof!").send().await?, - Command::Moo => ctx.answer("I am a cow! Moo!").send().await?, }; Ok(()) From 0b05558aa85b9310cc970532f002a6055db67ee8 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 13:56:41 +0600 Subject: [PATCH 88/91] Add the main page into the documentation --- src/lib.rs | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 64f273ce..3783fa01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,236 @@ +//! A full-featured framework that empowers you to easily build [Telegram bots] +//! using the [`async`/`.await`] syntax in [Rust]. It handles all the difficult +//! stuff so you can focus only on your business logic. +//! +//! ## Features +//! - **Type-safe.** teloxide leverages the Rust's type system with two serious +//! implications: resistance to human mistakes and tight integration with +//! IDEs. Write fast, avoid debugging as possible. +//! +//! - **Persistency.** By default, teloxide stores all user dialogues in RAM, +//! but you can store them somewhere else (for example, in DB) just by +//! implementing 2 functions. +//! +//! - **Convenient dialogues system.** Define a type-safe [finite automaton] +//! and transition functions to drive a user dialogue with ease (see the +//! examples below). +//! +//! - **Convenient API.** Automatic conversions are used to avoid boilerplate. +//! For example, functions accept `Into`, rather than `&str` or +//! `String`, so you can call them without `.to_string()`/`.as_str()`/etc. +//! +//! ## The ping-pong bot +//! This bot has a single message handler, which answers "pong" to each incoming +//! message: +//! +//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/src/main.rs)) +//! ```rust +//! use teloxide::prelude::*; +//! +//! #[tokio::main] +//! async fn main() { +//! teloxide::enable_logging!(); +//! log::info!("Starting the ping-pong bot!"); +//! +//! let bot = Bot::from_env(); +//! +//! Dispatcher::::new(bot) +//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { +//! ctx.answer("pong").send().await?; +//! Ok(()) +//! }) +//! .dispatch() +//! .await; +//! } +//! ``` +//! +//! ## Commands +//! Commands are defined similar to how we define CLI using [structopt]. This +//! bot says "I am a cat! Meow!" on `/meow`, generates a random number within +//! [0; 1) on `/generate`, and shows the usage guide on `/help`: +//! +//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/simple_commands_bot/src/main.rs)) +//! ```rust +//! // Imports are omitted... +//! +//! #[derive(BotCommand)] +//! #[command( +//! rename = "lowercase", +//! description = "These commands are +//! #[command(rename supported:" +//! )] +//! enum Command { +//! #[command(description = "display this text.")] +//! Help, +//! #[command(description = "be a cat.")] +//! Meow, +//! #[command(description = "generate a random number within [0; 1).")] +//! Generate, +//! } +//! +//! async fn handle_command( +//! ctx: DispatcherHandlerCtx, +//! ) -> Result<(), RequestError> { +//! let text = match ctx.update.text() { +//! Some(text) => text, +//! None => { +//! log::info!("Received a message, but not text."); +//! return Ok(()); +//! } +//! }; +//! +//! let command = match Command::parse(text) { +//! Some((command, _)) => command, +//! None => { +//! log::info!("Received a text message, but not a command."); +//! return Ok(()); +//! } +//! }; +//! +//! match command { +//! Command::Help => ctx.answer(Command::descriptions()).send().await?, +//! Command::Generate => { +//! ctx.answer(thread_rng().gen_range(0.0, 1.0).to_string()) +//! .send() +//! .await? +//! } +//! Command::Meow => ctx.answer("I am a cat! Meow!").send().await?, +//! }; +//! +//! Ok(()) +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! // Setup is omitted... +//! } +//! ``` +//! +//! ## Guess a number +//! Wanna see more? This is a bot, which starts a game on each incoming message. +//! You must guess a number from 1 to 10 (inclusively): +//! +//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/guess_a_number_bot/src/main.rs)) +//! ```rust +//! // Imports are omitted... +//! +//! #[derive(SmartDefault)] +//! enum Dialogue { +//! #[default] +//! Start, +//! ReceiveAttempt(u8), +//! } +//! async fn handle_message( +//! ctx: DialogueHandlerCtx, +//! ) -> Result, RequestError> { +//! match ctx.dialogue { +//! Dialogue::Start => { +//! ctx.answer( +//! "Let's play a game! Guess a number from 1 to 10 +//! (inclusively).", +//! ) +//! .send() +//! .await?; +//! next(Dialogue::ReceiveAttempt(thread_rng().gen_range(1, 11))) +//! } +//! Dialogue::ReceiveAttempt(secret) => match ctx.update.text() { +//! None => { +//! ctx.answer("Oh, please, send me a text message!") +//! .send() +//! .await?; +//! next(ctx.dialogue) +//! } +//! Some(text) => match text.parse::() { +//! Ok(attempt) => match attempt { +//! x if !(1..=10).contains(&x) => { +//! ctx.answer( +//! "Oh, please, send me a number in the range \ +//! [1; 10]!", +//! ) +//! .send() +//! .await?; +//! next(ctx.dialogue) +//! } +//! x if x == secret => { +//! ctx.answer("Congratulations! You won!") +//! .send() +//! .await?; +//! exit() +//! } +//! _ => { +//! ctx.answer("No.").send().await?; +//! next(ctx.dialogue) +//! } +//! }, +//! Err(_) => { +//! ctx.answer( +//! "Oh, please, send me a number in the range [1; \ +//! 10]!", +//! ) +//! .send() +//! .await?; +//! next(ctx.dialogue) +//! } +//! }, +//! }, +//! } +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! // Setup is omitted... +//! +//! Dispatcher::new(bot) +//! .message_handler(&DialogueDispatcher::new(|ctx| async move { +//! handle_message(ctx) +//! .await +//! .expect("Something wrong with the bot!") +//! })) +//! .dispatch() +//! .await; +//! } +//! ``` +//! +//! Our [finite automaton], designating a user dialogue, cannot be in an invalid +//! state. See [examples/dialogue_bot] to see a bit more complicated bot with +//! dialogues. +//! +//! [See more examples](https://github.com/teloxide/teloxide/tree/dev/examples). +//! +//! ## Recommendations +//! +//! - Use this pattern: +//! +//! ```rust +//! #[tokio::main] +//! async fn main() { +//! run().await; +//! } +//! +//! async fn run() { +//! // Your logic here... +//! } +//! ``` +//! +//! Instead of this: +//! +//! ```rust +//! #[tokio::main] +//! async fn main() { +//! // Your logic here... +//! } +//! ``` +//! +//! The second one produces very strange compiler messages because of the +//! `#[tokio::main]` macro. The examples above use the first one for brevity. +//! +//! [Telegram bots]: https://telegram.org/blog/bot-revolution +//! [`async`/`.await`]: https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html +//! [Rust]: https://www.rust-lang.org/ +//! [finite automaton]: https://en.wikipedia.org/wiki/Finite-state_machine +//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/blob/dev/examples/dialogue_bot/src/main.rs +//! [structopt]: https://docs.rs/structopt/0.3.9/structopt/ + #![doc( html_logo_url = "https://github.com/teloxide/teloxide/raw/dev/logo.svg", html_favicon_url = "https://github.com/teloxide/teloxide/raw/dev/ICON.png" From 7d142f1f06bd5311fcafe06c2a7bafe9b22d0db7 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 14:41:03 +0600 Subject: [PATCH 89/91] Fix the tests --- Cargo.toml | 7 ++++++- src/lib.rs | 50 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7df89eca..9058d79a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,4 +25,9 @@ pin-project = "0.4.6" serde_with_macros = "1.0.1" either = "1.5.3" -teloxide-macros = { path = "teloxide-macros" } \ No newline at end of file +teloxide-macros = { path = "teloxide-macros" } + +[dev-dependencies] +smart-default = "0.6.0" +rand = "0.7.3" +pretty_env_logger = "0.4.0" diff --git a/src/lib.rs b/src/lib.rs index 3783fa01..e78c9abf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,24 +24,24 @@ //! message: //! //! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/src/main.rs)) -//! ```rust +//! ```rust,no_run //! use teloxide::prelude::*; //! -//! #[tokio::main] -//! async fn main() { -//! teloxide::enable_logging!(); -//! log::info!("Starting the ping-pong bot!"); +//! # #[tokio::main] +//! # async fn main() { +//! teloxide::enable_logging!(); +//! log::info!("Starting the ping-pong bot!"); //! -//! let bot = Bot::from_env(); +//! let bot = Bot::from_env(); //! -//! Dispatcher::::new(bot) -//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { -//! ctx.answer("pong").send().await?; -//! Ok(()) -//! }) -//! .dispatch() -//! .await; -//! } +//! Dispatcher::::new(bot) +//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { +//! ctx.answer("pong").send().await?; +//! Ok(()) +//! }) +//! .dispatch() +//! .await; +//! # } //! ``` //! //! ## Commands @@ -50,7 +50,9 @@ //! [0; 1) on `/generate`, and shows the usage guide on `/help`: //! //! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/simple_commands_bot/src/main.rs)) -//! ```rust +//! ```rust,no_run +//! # use teloxide::{prelude::*, utils::command::BotCommand}; +//! # use rand::{thread_rng, Rng}; //! // Imports are omitted... //! //! #[derive(BotCommand)] @@ -103,6 +105,15 @@ //! #[tokio::main] //! async fn main() { //! // Setup is omitted... +//! # teloxide::enable_logging!(); +//! # log::info!("Starting simple_commands_bot!"); +//! # +//! # let bot = Bot::from_env(); +//! # +//! # Dispatcher::::new(bot) +//! # .message_handler(&handle_command) +//! # .dispatch() +//! # .await; //! } //! ``` //! @@ -111,7 +122,11 @@ //! You must guess a number from 1 to 10 (inclusively): //! //! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/guess_a_number_bot/src/main.rs)) -//! ```rust +//! ```rust,no_run +//! # #[macro_use] +//! # extern crate smart_default; +//! # use teloxide::prelude::*; +//! # use rand::{thread_rng, Rng}; //! // Imports are omitted... //! //! #[derive(SmartDefault)] @@ -178,6 +193,9 @@ //! //! #[tokio::main] //! async fn main() { +//! # teloxide::enable_logging!(); +//! # log::info!("Starting guess_a_number_bot!"); +//! # let bot = Bot::from_env(); //! // Setup is omitted... //! //! Dispatcher::new(bot) From ff1a7c05d1e879f1c9e5ae0b3af9ab9c60f5902b Mon Sep 17 00:00:00 2001 From: p0lunin Date: Fri, 14 Feb 2020 11:44:50 +0200 Subject: [PATCH 90/91] fixed documentation --- src/dispatching/dialogue/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 7a7249a4..33fd28b6 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -17,7 +17,7 @@ //! 1. If a storage doesn't contain a dialogue from this chat, supply //! `D::default()` into you handler, otherwise, supply the saved session //! from this chat. -//! 3. If a handler has returned [`DialogueStage::Exit`], remove the session +//! 2. If a handler has returned [`DialogueStage::Exit`], remove the session //! from the storage, otherwise ([`DialogueStage::Next`]) force the storage to //! update the session. //! From 4210fc68d2d20b04b7ed4b91ac594aa76f9e6caf Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 14 Feb 2020 16:37:57 +0600 Subject: [PATCH 91/91] Fix lib.rs --- src/lib.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 19d71363..25b1b2a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,32 @@ //! For example, functions accept `Into`, rather than `&str` or //! `String`, so you can call them without `.to_string()`/`.as_str()`/etc. //! +//! ## Getting started +//! 1. Create a new bot using [@Botfather] to get a token in the format +//! `123456789:blablabla`. 2. Initialise the `TELOXIDE_TOKEN` environmental +//! variable to your token: +//! ```bash +//! # Unix +//! $ export TELOXIDE_TOKEN=MyAwesomeToken +//! +//! # Windows +//! $ set TELOXITE_TOKEN=MyAwesomeToken +//! ``` +//! 3. Be sure that you are up to date: +//! ```bash +//! $ rustup update stable +//! ``` +//! +//! 4. Execute `cargo new my_bot`, enter the directory and put these lines into +//! your `Cargo.toml`: +//! ```toml +//! [dependencies] +//! teloxide = "0.1.0" +//! log = "0.4.8" +//! tokio = "0.2.11" +//! pretty_env_logger = "0.4.0" +//! ``` +//! //! ## The ping-pong bot //! This bot has a single message handler, which answers "pong" to each incoming //! message: @@ -239,7 +265,8 @@ //! ``` //! //! The second one produces very strange compiler messages because of the -//! `#[tokio::main]` macro. The examples above use the first one for brevity. +//! `#[tokio::main]` macro. However, the examples above use the second one for +//! brevity. //! //! [Telegram bots]: https://telegram.org/blog/bot-revolution //! [`async`/`.await`]: https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html @@ -247,6 +274,7 @@ //! [finite automaton]: https://en.wikipedia.org/wiki/Finite-state_machine //! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/blob/dev/examples/dialogue_bot/src/main.rs //! [structopt]: https://docs.rs/structopt/0.3.9/structopt/ +//! [@Botfather]: https://t.me/botfather #![doc( html_logo_url = "https://github.com/teloxide/teloxide/raw/dev/logo.svg",