Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InputReactions::remove() and Client.action() #261

Merged
merged 4 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>>(
Lonami marked this conversation as resolved.
Show resolved Hide resolved
&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
}
}
Loading