Skip to content

Commit

Permalink
Add InputReactions::remove() and Client.action() (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimentyy authored Aug 31, 2024
1 parent c7234fe commit ade3284
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 17 deletions.
59 changes: 59 additions & 0 deletions lib/grammers-client/src/client/chats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,65 @@ impl Client {
Some(_) => Ok(None),
}
}

/// Send a message action (such as typing, uploading photo, or viewing an emoji interaction)
///
/// # Examples
///
/// **Do a one-shot pulse and let it fade away**
/// ```
/// # async fn f(chat: grammers_client::types::Chat, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
/// use grammers_tl_types::enums::SendMessageAction;
///
/// client
/// .action(&chat)
/// .oneshot(SendMessageAction::SendMessageTypingAction)
/// .await?;
/// # Ok(())
/// # }
/// ```
///
/// **Repeat request until the future is done**
/// ```
/// # use std::time::Duration;
///
/// # async fn f(chat: grammers_client::types::Chat, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
/// use grammers_tl_types as tl;
///
/// let heavy_task = async {
/// tokio::time::sleep(Duration::from_secs(10)).await;
///
/// 42
/// };
///
/// tokio::pin!(heavy_task);
///
/// let (task_result, _) = client
/// .action(&chat)
/// .repeat(
/// // most clients doesn't actually show progress of an action
/// || tl::types::SendMessageUploadDocumentAction { progress: 0 },
/// heavy_task
/// )
/// .await;
///
/// // Note: repeat function does not cancel actions automatically, they will just fade away
///
/// assert_eq!(task_result, 42);
/// # Ok(())
/// # }
/// ```
///
/// **Cancel any actions**
/// ```
/// # async fn f(chat: grammers_client::types::Chat, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
/// client.action(&chat).cancel().await?;
/// # Ok(())
/// # }
/// ```
pub fn action<C: Into<PackedChat>>(&self, chat: C) -> crate::types::ActionSender {
crate::types::ActionSender::new(self, chat)
}
}

#[derive(Debug, Clone)]
Expand Down
34 changes: 19 additions & 15 deletions lib/grammers-client/src/client/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,6 @@ use grammers_session::PackedChat;
use grammers_tl_types as tl;
use std::collections::HashMap;
use tl::enums::InputPeer;
use tl::functions::messages::SendReaction;

fn get_message_id(message: &tl::enums::Message) -> i32 {
match message {
tl::enums::Message::Empty(m) => m.id,
tl::enums::Message::Message(m) => m.id,
tl::enums::Message::Service(m) => m.id,
}
}

fn map_random_ids_to_messages(
client: &Client,
Expand Down Expand Up @@ -151,12 +142,12 @@ impl<R: tl::RemoteCall<Return = tl::enums::messages::Messages>> IterBuffer<R, Me
// If the highest fetched message ID is lower than or equal to the limit,
// there can't be more messages after (highest ID - limit), because the
// absolute lowest message ID is 1.
self.last_chunk = m.messages.is_empty() || get_message_id(&m.messages[0]) <= limit;
self.last_chunk = m.messages.is_empty() || m.messages[0].id() <= limit;
self.total = Some(m.count as usize);
(m.messages, m.users, m.chats, m.next_rate)
}
Messages::ChannelMessages(m) => {
self.last_chunk = m.messages.is_empty() || get_message_id(&m.messages[0]) <= limit;
self.last_chunk = m.messages.is_empty() || m.messages[0].id() <= limit;
self.total = Some(m.count as usize);
(m.messages, m.users, m.chats, None)
}
Expand Down Expand Up @@ -1036,7 +1027,7 @@ impl Client {
/// # async fn f(chat: grammers_client::types::Chat, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
/// let message_id = 123;
///
/// client.send_reaction(&chat, message_id, "👍").await?;
/// client.send_reactions(&chat, message_id, "👍").await?;
/// # Ok(())
/// # }
/// ```
Expand All @@ -1050,19 +1041,32 @@ impl Client {
/// let message_id = 123;
/// let reactions = InputReactions::emoticon("🤯").big().add_to_recent();
///
/// client.send_reaction(&chat, message_id, reactions).await?;
/// client.send_reactions(&chat, message_id, reactions).await?;
/// # Ok(())
/// # }
/// ```
///
/// Remove reactions
///
/// ```
/// # async fn f(chat: grammers_client::types::Chat, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
/// use grammers_client::types::InputReactions;
///
/// let message_id = 123;
///
/// client.send_reactions(&chat, message_id, InputReactions::remove()).await?;
/// # Ok(())
/// # }
/// ```
pub async fn send_reaction<C: Into<PackedChat>, R: Into<InputReactions>>(
pub async fn send_reactions<C: Into<PackedChat>, R: Into<InputReactions>>(
&self,
chat: C,
message_id: i32,
reactions: R,
) -> Result<(), InvocationError> {
let reactions = reactions.into();

self.invoke(&SendReaction {
self.invoke(&tl::functions::messages::SendReaction {
big: reactions.big,
add_to_recent: reactions.add_to_recent,
peer: chat.into().to_input_peer(),
Expand Down
127 changes: 127 additions & 0 deletions lib/grammers-client/src/types/action.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use futures_util::future::Either;
use grammers_mtsender::InvocationError;
use grammers_session::PackedChat;
use grammers_tl_types as tl;
use std::future::Future;
use std::time::Duration;
use tl::enums::SendMessageAction;

use crate::Client;

const DEFAULT_REPEAT_DELAY: Duration = Duration::from_secs(4);

pub struct ActionSender {
client: Client,
chat: PackedChat,
topic_id: Option<i32>,
repeat_delay: Duration,
}

impl ActionSender {
pub fn new<C: Into<PackedChat>>(client: &Client, chat: C) -> Self {
Self {
client: client.clone(),
chat: chat.into(),
topic_id: None,
repeat_delay: DEFAULT_REPEAT_DELAY,
}
}

/// Set custom repeat delay
pub fn repeat_delay(mut self, repeat_delay: Duration) -> Self {
self.repeat_delay = repeat_delay;
self
}

/// Set a topic id
pub fn topic_id(mut self, topic_id: i32) -> Self {
self.topic_id = Some(topic_id);
self
}

/// Cancel any actions
pub async fn cancel(&self) -> Result<(), InvocationError> {
self.oneshot(SendMessageAction::SendMessageCancelAction)
.await?;

Ok(())
}

/// Do a one-shot set action request
pub async fn oneshot<A: Into<SendMessageAction>>(
&self,
action: A,
) -> Result<(), InvocationError> {
self.client
.invoke(&tl::functions::messages::SetTyping {
peer: self.chat.to_input_peer(),
top_msg_id: self.topic_id,
action: action.into(),
})
.await?;

Ok(())
}

/// Repeat set action request until the future is done
///
/// # Example
///
/// ```
/// # use std::time::Duration;
///
/// # async fn f(chat: grammers_client::types::Chat, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
/// use grammers_tl_types as tl;
///
/// let heavy_task = async {
/// tokio::time::sleep(Duration::from_secs(10)).await;
///
/// 42
/// };
///
/// tokio::pin!(heavy_task);
///
/// let (task_result, _) = client
/// .action(&chat)
/// .repeat(
/// // most clients doesn't actually show progress of an action
/// || tl::types::SendMessageUploadDocumentAction { progress: 0 },
/// heavy_task
/// )
/// .await;
///
/// // Note: repeat function does not cancel actions automatically, they will just fade away
///
/// assert_eq!(task_result, 42);
/// # Ok(())
/// # }
/// ```
pub async fn repeat<A: Into<SendMessageAction>, T>(
&self,
action: impl Fn() -> A,
mut future: impl Future<Output = T> + Unpin,
) -> (T, Result<(), InvocationError>) {
let mut request_result = Ok(());

let future_output = loop {
if request_result.is_err() {
// Don't try to make a request again
return (future.await, request_result);
}

let action = async {
request_result = self.oneshot(action().into()).await;
tokio::time::sleep(self.repeat_delay).await;
};

tokio::pin!(action);

match futures_util::future::select(action, &mut future).await {
Either::Left((_, _)) => continue,
Either::Right((output, _)) => break output,
}
};

(future_output, request_result)
}
}
13 changes: 12 additions & 1 deletion lib/grammers-client/src/types/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,12 +401,23 @@ impl Message {
/// # Ok(())
/// # }
/// ```
///
/// Remove reactions
///
/// ```
/// # async fn f(message: grammers_client::types::Message, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
/// use grammers_client::types::InputReactions;
///
/// message.react(InputReactions::remove()).await?;
/// # Ok(())
/// # }
/// ```
pub async fn react<R: Into<InputReactions>>(
&self,
reactions: R,
) -> Result<(), InvocationError> {
self.client
.send_reaction(self.chat(), self.id(), reactions)
.send_reactions(self.chat(), self.id(), reactions)
.await?;
Ok(())
}
Expand Down
2 changes: 2 additions & 0 deletions lib/grammers-client/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//!
//! A lot of fields in the types exported from this module are currently public even though
//! they directly uses `grammers-tl-types`. This will probably change before the 1.0 release.
pub mod action;
pub mod attributes;
pub mod button;
pub mod callback_query;
Expand All @@ -34,6 +35,7 @@ pub mod reply_markup;
pub mod terms_of_service;
pub mod update;

pub use action::ActionSender;
pub use attributes::Attribute;
pub use callback_query::CallbackQuery;
pub use chat::{Channel, Chat, Group, PackedChat, Platform, RestrictionReason, User};
Expand Down
7 changes: 6 additions & 1 deletion lib/grammers-client/src/types/reactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ impl InputReactions {
..Self::default()
}
}

/// Create an empty InputReactions which will remove reactions
pub fn remove() -> Self {
Self::default()
}
}

impl Default for InputReactions {
Expand Down Expand Up @@ -86,6 +91,6 @@ impl From<Vec<Reaction>> for InputReactions {

impl Into<Vec<Reaction>> for InputReactions {
fn into(self) -> Vec<Reaction> {
return self.reactions;
self.reactions
}
}

0 comments on commit ade3284

Please sign in to comment.