diff --git a/lib/grammers-client/examples/downloader.rs b/lib/grammers-client/examples/downloader.rs index ac326414..b098d361 100644 --- a/lib/grammers-client/examples/downloader.rs +++ b/lib/grammers-client/examples/downloader.rs @@ -25,7 +25,7 @@ use tokio::runtime; use grammers_client::session::Session; use grammers_client::types::Media::{Contact, Document, Photo, Sticker}; -use grammers_client::types::*; +use grammers_client::types::{Downloadable, Media}; type Result = std::result::Result>; diff --git a/lib/grammers-client/examples/echo.rs b/lib/grammers-client/examples/echo.rs index 4ad186e6..bc7b9ea3 100644 --- a/lib/grammers-client/examples/echo.rs +++ b/lib/grammers-client/examples/echo.rs @@ -71,24 +71,17 @@ async fn async_main() -> Result { // This code uses `select` on Ctrl+C to gracefully stop the client and have a chance to // save the session. You could have fancier logic to save the session if you wanted to // (or even save it on every update). Or you could also ignore Ctrl+C and just use - // `let update = client.next_update().await?`. + // `while let Some(updates) = client.next_updates().await?`. // // Using `tokio::select!` would be a lot cleaner but add a heavy dependency, // so a manual `select` is used instead by pinning async blocks by hand. loop { - let update = { - let exit = pin!(async { tokio::signal::ctrl_c().await }); - let upd = pin!(async { client.next_update().await }); + let exit = pin!(async { tokio::signal::ctrl_c().await }); + let upd = pin!(async { client.next_update().await }); - match select(exit, upd).await { - Either::Left(_) => None, - Either::Right((u, _)) => Some(u), - } - }; - - let update = match update { - None => break, - Some(u) => u?, + let update = match select(exit, upd).await { + Either::Left(_) => break, + Either::Right((u, _)) => u?, }; let handle = client.clone(); diff --git a/lib/grammers-client/examples/inline-pagination.rs b/lib/grammers-client/examples/inline-pagination.rs index a173be6a..bba622d4 100644 --- a/lib/grammers-client/examples/inline-pagination.rs +++ b/lib/grammers-client/examples/inline-pagination.rs @@ -22,10 +22,12 @@ //! how much data a button's payload can contain, and to keep it simple, we're storing it inline //! in decimal, so the numbers can't get too large). +use futures_util::future::{select, Either}; use grammers_client::session::Session; use grammers_client::{button, reply_markup, Client, Config, InputMessage, Update}; use simple_logger::SimpleLogger; use std::env; +use std::pin::pin; use tokio::{runtime, task}; type Result = std::result::Result<(), Box>; @@ -132,26 +134,23 @@ async fn async_main() -> Result { println!("Waiting for messages..."); loop { - tokio::select! { + let exit = pin!(async { tokio::signal::ctrl_c().await }); + let upd = pin!(async { client.next_update().await }); - // A way to exit the loop - _ = tokio::signal::ctrl_c() => { + let update = match select(exit, upd).await { + Either::Left(_) => { println!("Exiting..."); - break - }, - - res = client.next_update() => { - let update = res?; - - let handle = client.clone(); - task::spawn(async move { - match handle_update(handle, update).await { - Ok(_) => {} - Err(e) => eprintln!("Error handling updates!: {e}"), - } - }); + break; } - } + Either::Right((u, _)) => u?, + }; + + let handle = client.clone(); + task::spawn(async move { + if let Err(e) = handle_update(handle, update).await { + eprintln!("Error handling updates!: {e}") + } + }); } println!("Saving session file..."); diff --git a/lib/grammers-client/src/client/bots.rs b/lib/grammers-client/src/client/bots.rs index 44033604..170c20b9 100644 --- a/lib/grammers-client/src/client/bots.rs +++ b/lib/grammers-client/src/client/bots.rs @@ -161,15 +161,18 @@ impl Client { let message: InputMessage = input_message.into(); let entities = parse_mention_entities(self, message.entities); let result = self - .invoke(&tl::functions::messages::EditInlineBotMessage { - id: message_id, - message: Some(message.text), - media: message.media, - entities, - no_webpage: !message.link_preview, - reply_markup: message.reply_markup, - invert_media: false, - }) + .invoke_in_dc( + &tl::functions::messages::EditInlineBotMessage { + id: message_id.clone(), + message: Some(message.text), + media: message.media, + entities, + no_webpage: !message.link_preview, + reply_markup: message.reply_markup, + invert_media: message.invert_media, + }, + message_id.dc_id(), + ) .await?; Ok(result) } diff --git a/lib/grammers-client/src/client/messages.rs b/lib/grammers-client/src/client/messages.rs index f99d7262..dd11ac01 100644 --- a/lib/grammers-client/src/client/messages.rs +++ b/lib/grammers-client/src/client/messages.rs @@ -505,7 +505,7 @@ impl Client { send_as: None, noforwards: false, update_stickersets_order: false, - invert_media: false, + invert_media: message.invert_media, quick_reply_shortcut: None, effect: None, }) @@ -536,7 +536,7 @@ impl Client { send_as: None, noforwards: false, update_stickersets_order: false, - invert_media: false, + invert_media: message.invert_media, quick_reply_shortcut: None, effect: None, }) @@ -583,7 +583,7 @@ impl Client { let entities = parse_mention_entities(self, new_message.entities); self.invoke(&tl::functions::messages::EditMessage { no_webpage: !new_message.link_preview, - invert_media: false, + invert_media: new_message.invert_media, peer: chat.into().to_input_peer(), id: message_id, message: Some(new_message.text), diff --git a/lib/grammers-client/src/client/updates.rs b/lib/grammers-client/src/client/updates.rs index f4cc3665..c6c8ff43 100644 --- a/lib/grammers-client/src/client/updates.rs +++ b/lib/grammers-client/src/client/updates.rs @@ -72,7 +72,7 @@ impl Client { /// /// ``` /// - /// P.S. To receive updateBotInlineSend, go to [@BotFather](https://t.me/BotFather), select your bot and click "Bot Settings", then "Inline Feedback" and select probability. + /// P.S. If you don't receive updateBotInlineSend, go to [@BotFather](https://t.me/BotFather), select your bot and click "Bot Settings", then "Inline Feedback" and select probability. /// pub async fn next_raw_update( &self, @@ -181,18 +181,12 @@ impl Client { continue; } - let step = { - let sleep = pin!(async { sleep_until(deadline.into()).await }); - let step = pin!(async { self.step().await }); + let sleep = pin!(async { sleep_until(deadline.into()).await }); + let step = pin!(async { self.step().await }); - match select(sleep, step).await { - Either::Left(_) => None, - Either::Right((step, _)) => Some(step), - } - }; - - if let Some(step) = step { - step?; + match select(sleep, step).await { + Either::Left(_) => {} + Either::Right((step, _)) => step?, } } } diff --git a/lib/grammers-client/src/parsers/html.rs b/lib/grammers-client/src/parsers/html.rs index 220d6cd4..aa38f0c9 100644 --- a/lib/grammers-client/src/parsers/html.rs +++ b/lib/grammers-client/src/parsers/html.rs @@ -125,7 +125,7 @@ pub fn parse_html_message(message: &str) -> (String, Vec (String, Vec (String, Vec or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub mod query; +pub mod send; diff --git a/lib/grammers-client/src/types/inline_query.rs b/lib/grammers-client/src/types/inline/query.rs similarity index 92% rename from lib/grammers-client/src/types/inline_query.rs rename to lib/grammers-client/src/types/inline/query.rs index 788efaef..e1dccbd0 100644 --- a/lib/grammers-client/src/types/inline_query.rs +++ b/lib/grammers-client/src/types/inline/query.rs @@ -5,7 +5,8 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -use super::{Chat, ChatMap, User}; + +use super::super::{Chat, ChatMap, User}; use crate::{client::Client, utils::generate_random_id, InputMessage}; use grammers_mtsender::InvocationError; use grammers_tl_types as tl; @@ -52,7 +53,7 @@ impl InlineQuery { } } - // User that sent the query. + /// User that sent the query pub fn sender(&self) -> &User { match self .chats @@ -69,12 +70,12 @@ impl InlineQuery { } } - // The text of the inline query. + /// The text of the inline query. pub fn text(&self) -> &str { self.raw.query.as_str() } - // The offset of the inline query. + /// The offset of the inline query. pub fn offset(&self) -> &str { self.raw.offset.as_str() } @@ -96,6 +97,16 @@ impl InlineQuery { client: self.client.clone(), } } + + /// Type of the chat from which the inline query was sent. + pub fn peer_type(&self) -> Option { + self.raw.peer_type.clone() + } + + /// Query ID + pub fn query_id(&self) -> i64 { + self.raw.query_id + } } impl Answer { @@ -217,7 +228,7 @@ impl From
for InlineResult { send_message: tl::enums::InputBotInlineMessage::Text( tl::types::InputBotInlineMessageText { no_webpage: !article.input_message.link_preview, - invert_media: false, + invert_media: article.input_message.invert_media, message: article.input_message.text, entities: Some(article.input_message.entities), reply_markup: article.input_message.reply_markup, @@ -232,7 +243,9 @@ impl fmt::Debug for InlineQuery { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("InlineQuery") .field("text", &self.text()) + .field("peer_type", &self.peer_type()) .field("sender", &self.sender()) + .field("query_id", &self.query_id()) .finish() } } diff --git a/lib/grammers-client/src/types/inline/send.rs b/lib/grammers-client/src/types/inline/send.rs new file mode 100644 index 00000000..34d93f5b --- /dev/null +++ b/lib/grammers-client/src/types/inline/send.rs @@ -0,0 +1,99 @@ +// Copyright 2020 - developers of the `grammers` project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::types::{Chat, User}; +use crate::{ChatMap, Client, InputMessage}; +use grammers_mtsender::InvocationError; +use grammers_tl_types as tl; +use std::fmt; +use std::sync::Arc; + +#[derive(Clone)] +pub struct InlineSend { + raw: tl::types::UpdateBotInlineSend, + client: Client, + chats: Arc, +} + +impl InlineSend { + pub fn from_raw( + query: tl::types::UpdateBotInlineSend, + client: &Client, + chats: &Arc, + ) -> Self { + Self { + raw: query, + client: client.clone(), + chats: chats.clone(), + } + } + + /// The query that was used to obtain the result. + pub fn text(&self) -> &str { + self.raw.query.as_str() + } + + /// The user that chose the result. + pub fn sender(&self) -> &User { + match self + .chats + .get( + &tl::types::PeerUser { + user_id: self.raw.user_id, + } + .into(), + ) + .unwrap() + { + Chat::User(user) => user, + _ => unreachable!(), + } + } + + /// The unique identifier for the result that was chosen + pub fn result_id(&self) -> &str { + self.raw.id.as_str() + } + + /// Identifier of sent inline message. + /// Available only if there is an inline keyboard attached. + /// Will be also received in callback queries and can be used to edit the message. + pub fn msg_id(&self) -> Option { + self.raw.msg_id.clone() + } + + /// Edits this inline message. + /// + /// **This method will return Ok(None) if message id is None (e.g. if an inline keyboard is not attached)** + pub async fn edit_msg( + &self, + input_message: impl Into, + ) -> Result, InvocationError> { + let msg_id = match self.raw.msg_id.clone() { + None => return Ok(None), + Some(msg_id) => msg_id, + }; + + Ok(Some( + self.client + .edit_inline_message(msg_id, input_message) + .await?, + )) + } +} + +impl fmt::Debug for InlineSend { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("InlineSend") + .field("text", &self.text()) + .field("sender", &self.sender()) + .field("result_id", &self.result_id()) + .field("msg_id", &self.msg_id()) + .finish() + } +} diff --git a/lib/grammers-client/src/types/input_message.rs b/lib/grammers-client/src/types/input_message.rs index 79e0cf1f..9677f07e 100644 --- a/lib/grammers-client/src/types/input_message.rs +++ b/lib/grammers-client/src/types/input_message.rs @@ -19,6 +19,7 @@ pub struct InputMessage { pub(crate) background: bool, pub(crate) clear_draft: bool, pub(crate) entities: Vec, + pub(crate) invert_media: bool, pub(crate) link_preview: bool, pub(crate) reply_markup: Option, pub(crate) reply_to: Option, @@ -51,6 +52,14 @@ impl InputMessage { self } + /// Whether the media will be inverted. + /// + /// If inverted, photos, videos, and documents will appear at the bottom and link previews at the top of the message. + pub fn invert_media(mut self, invert_media: bool) -> Self { + self.invert_media = invert_media; + self + } + /// Whether the link preview be shown for the message. /// /// This has no effect when sending media, which cannot contain a link preview. diff --git a/lib/grammers-client/src/types/message.rs b/lib/grammers-client/src/types/message.rs index 200cb1ea..fc4afc3c 100644 --- a/lib/grammers-client/src/types/message.rs +++ b/lib/grammers-client/src/types/message.rs @@ -39,14 +39,14 @@ pub struct Message { // server response contains a lot of chats, and some might be related to deep layers of // a message action for instance. Keeping the entire set like this allows for cheaper clones // and moves, and saves us from worrying about picking out all the chats we care about. - pub(crate) chats: Arc, + pub(crate) chats: Arc, } impl Message { pub fn from_raw( client: &Client, message: tl::enums::Message, - chats: &Arc, + chats: &Arc, ) -> Option { match message { // Don't even bother to expose empty messages to the user, even if they have an ID. diff --git a/lib/grammers-client/src/types/mod.rs b/lib/grammers-client/src/types/mod.rs index f84cbd78..2ab9f77e 100644 --- a/lib/grammers-client/src/types/mod.rs +++ b/lib/grammers-client/src/types/mod.rs @@ -18,7 +18,7 @@ pub mod chat_map; pub mod chats; pub mod dialog; pub mod downloadable; -pub mod inline_query; +pub mod inline; pub mod input_message; pub mod iter_buffer; pub mod login_token; @@ -41,7 +41,8 @@ pub(crate) use chat_map::Peer; pub use chats::{AdminRightsBuilder, BannedRightsBuilder}; pub use dialog::Dialog; pub use downloadable::{ChatPhoto, Downloadable, UserProfilePhoto}; -pub use inline_query::InlineQuery; +pub use inline::query::InlineQuery; +pub use inline::send::InlineSend; pub use input_message::InputMessage; pub use iter_buffer::IterBuffer; pub use login_token::LoginToken; diff --git a/lib/grammers-client/src/types/update.rs b/lib/grammers-client/src/types/update.rs index 93fe5efc..596b14e9 100644 --- a/lib/grammers-client/src/types/update.rs +++ b/lib/grammers-client/src/types/update.rs @@ -5,12 +5,12 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::sync::Arc; -use grammers_tl_types as tl; +use std::sync::Arc; -use super::{CallbackQuery, ChatMap, InlineQuery, Message}; +use super::{inline::send::InlineSend, CallbackQuery, ChatMap, InlineQuery, Message}; use crate::{types::MessageDeletion, Client}; +use grammers_tl_types as tl; #[non_exhaustive] #[derive(Debug, Clone)] @@ -27,6 +27,8 @@ pub enum Update { /// Occurs whenever you sign in as a bot and a user sends an inline query /// such as `@bot query`. InlineQuery(InlineQuery), + /// Occurs whenever you sign in as a bot and a user chooses result from an inline query answer. + InlineSend(InlineSend), /// Raw events are not actual events. /// Instead, they are the raw Update object that Telegram sends. You /// normally shouldn’t need these. @@ -84,6 +86,11 @@ impl Update { InlineQuery::from_raw(client, query, chats), )), + // InlineSend + tl::enums::Update::BotInlineSend(query) => { + Some(Self::InlineSend(InlineSend::from_raw(query, client, chats))) + } + // Raw update => Some(Self::Raw(update)), }