Skip to content

Commit

Permalink
Add methods to start and stop typing (#978)
Browse files Browse the repository at this point in the history
  • Loading branch information
nextonesfaster authored Sep 30, 2020
1 parent f0a3947 commit bcf8249
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 3 deletions.
43 changes: 43 additions & 0 deletions src/http/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use super::{
ratelimiting::{Ratelimiter, RatelimitedRequest},
request::Request,
routing::RouteInfo,
typing::Typing,
AttachmentType,
GuildPagination,
HttpError,
Expand Down Expand Up @@ -1646,6 +1647,48 @@ impl Http {
}).await
}

/// Starts typing in the specified [`Channel`] for an indefinite period of time.
///
/// Returns [`Typing`] that is used to trigger the typing. [`Typing::stop`] must be called
/// on the returned struct to stop typing. Note that on some clients, typing may persist
/// for a few seconds after `stop` is called.
/// Typing is also stopped when the struct is dropped.
///
/// If a message is sent while typing is triggered, the user will stop typing for a brief period
/// of time and then resume again until either `stop` is called or the struct is dropped.
///
/// This should rarely be used for bots, although it is a good indicator that a
/// long-running command is still being processed.
///
/// ## Examples
///
/// ```rust,no_run
/// # use serenity::{http::{Http, Typing}, Result};
/// # use std::sync::Arc;
/// #
/// # fn long_process() {}
/// # fn main() -> Result<()> {
/// # let http = Arc::new(Http::default());
/// // Initiate typing (assuming http is `Arc<Http>`)
/// let typing = http.start_typing(7)?;
///
/// // Run some long-running process
/// long_process();
///
/// // Stop typing
/// typing.stop();
/// #
/// # Ok(())
/// # }
/// ```
///
/// [`Channel`]: ../../model/channel/enum.Channel.html
/// [`Typing`]: ../typing/struct.Typing.html
/// [`Typing::stop`]: ../typing/struct.Typing.html#method.stop
pub fn start_typing(self: &Arc<Self>, channel_id: u64) -> Result<Typing> {
Typing::start(self.clone(), channel_id)
}

/// Unpins a message from a channel.
pub async fn unpin_message(&self, channel_id: u64, message_id: u64) -> Result<()> {
self.wind(204, Request {
Expand Down
2 changes: 2 additions & 0 deletions src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ pub mod error;
pub mod ratelimiting;
pub mod request;
pub mod routing;
pub mod typing;

pub use reqwest::StatusCode;
pub use self::client::*;
pub use self::error::Error as HttpError;
pub use self::typing::*;

use reqwest::Method;
use crate::model::prelude::*;
Expand Down
91 changes: 91 additions & 0 deletions src/http/typing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use crate::{error::Result, http::Http};
use std::sync::Arc;
use tokio::{sync::oneshot::{self, Sender, error::TryRecvError}, time::{delay_for, Duration}};

/// A struct to start typing in a [`Channel`] for an indefinite period of time.
///
/// It indicates that the current user is currently typing in the channel.
///
/// Typing is started by using the [`Typing::start`] method
/// and stopped by using the [`Typing::stop`] method.
/// Note that on some clients, typing may persist for a few seconds after `stop` is called.
/// Typing is also stopped when the struct is dropped.
///
/// If a message is sent while typing is triggered, the user will stop typing for a brief period
/// of time and then resume again until either `stop` is called or the struct is dropped.
///
/// This should rarely be used for bots, although it is a good indicator that a
/// long-running command is still being processed.
///
/// ## Examples
///
/// ```rust,no_run
/// # use serenity::{http::{Http, Typing}, Result};
/// # use std::sync::Arc;
/// #
/// # fn long_process() {}
/// # fn main() -> Result<()> {
/// # let http = Http::default();
/// // Initiate typing (assuming `http` is bound)
/// let typing = Typing::start(Arc::new(http), 7)?;
///
/// // Run some long-running process
/// long_process();
///
/// // Stop typing
/// typing.stop();
/// #
/// # Ok(())
/// # }
/// ```
///
/// [`Channel`]: ../../model/channel/enum.Channel.html
/// [`Typing::start`]: struct.Typing.html#method.start
/// [`Typing::stop`]: struct.Typing.html#method.stop
#[derive(Debug)]
pub struct Typing(Sender<()>);

impl Typing {
/// Starts typing in the specified [`Channel`] for an indefinite period of time.
///
/// Returns [`Typing`]. To stop typing, you must call the [`Typing::stop`] method on
/// the returned `Typing` object or wait for it to be dropped. Note that on some
/// clients, typing may persist for a few seconds after stopped.
///
/// [`Channel`]: ../../model/channel/enum.Channel.html
/// [`Typing`]: struct.Typing.html
/// [`Typing::stop`]: struct.Typing.html#method.stop
pub fn start(http: Arc<Http>, channel_id: u64) -> Result<Self> {
let (sx, mut rx) = oneshot::channel();

tokio::spawn(async move {
loop {
match rx.try_recv() {
Ok(_) | Err(TryRecvError::Closed) => break,
_ => (),
}

http.broadcast_typing(channel_id).await?;

// It is unclear for how long typing persists after this method is called.
// It is generally assumed to be 7 or 10 seconds, so we use 7 to be safe.
delay_for(Duration::from_secs(7)).await;
}

Result::Ok(())
});

Ok(Self(sx))
}

/// Stops typing in [`Channel`].
///
/// This should be used to stop typing after it is started using [`Typing::start`].
/// Typing may persist for a few seconds on some clients after this is called.
///
/// [`Channel`]: ../../model/channel/enum.Channel.html
/// [`Typing::start`]: struct.Typing.html#method.start
pub fn stop(self) -> Option<()> {
self.0.send(()).ok()
}
}
45 changes: 44 additions & 1 deletion src/model/channel/channel_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ use crate::http::AttachmentType;
#[cfg(feature = "model")]
use crate::utils;
#[cfg(feature = "model")]
use crate::http::{Http, CacheHttp};
use crate::http::{Http, CacheHttp, Typing};
#[cfg(feature = "model")]
use serde_json::json;
#[cfg(feature = "model")]
use std::sync::Arc;
use futures::stream::Stream;
#[cfg(feature = "collector")]
use crate::client::bridge::gateway::ShardMessenger;
Expand Down Expand Up @@ -673,6 +675,47 @@ impl ChannelId {
Ok(message)
}

/// Starts typing in the channel for an indefinite period of time.
///
/// Returns [`Typing`] that is used to trigger the typing. [`Typing::stop`] must be called
/// on the returned struct to stop typing. Note that on some clients, typing may persist
/// for a few seconds after `stop` is called.
/// Typing is also stopped when the struct is dropped.
///
/// If a message is sent while typing is triggered, the user will stop typing for a brief period
/// of time and then resume again until either `stop` is called or the struct is dropped.
///
/// This should rarely be used for bots, although it is a good indicator that a
/// long-running command is still being processed.
///
/// ## Examples
///
/// ```rust,no_run
/// # use serenity::{http::{Http, Typing}, Result, model::id::ChannelId};
/// # use std::sync::Arc;
/// #
/// # fn long_process() {}
/// # fn main() -> Result<()> {
/// # let http = Arc::new(Http::default());
/// // Initiate typing (assuming http is `Arc<Http>`)
/// let typing = ChannelId(7).start_typing(&http)?;
///
/// // Run some long-running process
/// long_process();
///
/// // Stop typing
/// typing.stop();
/// #
/// # Ok(())
/// # }
/// ```
///
/// [`Typing`]: ../../http/typing/struct.Typing.html
/// [`Typing::stop`]: ../../http/typing/struct.Typing.html#method.stop
pub fn start_typing(self, http: &Arc<Http>) -> Result<Typing> {
http.start_typing(self.0)
}

/// Unpins a [`Message`] in the channel given by its Id.
///
/// Requires the [Manage Messages] permission.
Expand Down
55 changes: 54 additions & 1 deletion src/model/channel/guild_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ use crate::collector::{
CollectReply, MessageCollectorBuilder,
};
#[cfg(feature = "model")]
use crate::http::{Http, CacheHttp};
use crate::http::{Http, CacheHttp, Typing};
#[cfg(feature = "model")]
use std::sync::Arc;

/// Represents a guild's text, news, or voice channel. Some methods are available
/// only for voice channels and some are only available for text channels.
Expand Down Expand Up @@ -719,6 +721,57 @@ impl GuildChannel {
self.id.send_message(&cache_http.http(), f).await
}

/// Starts typing in the channel for an indefinite period of time.
///
/// Returns [`Typing`] that is used to trigger the typing. [`Typing::stop`] must be called
/// on the returned struct to stop typing. Note that on some clients, typing may persist
/// for a few seconds after `stop` is called.
/// Typing is also stopped when the struct is dropped.
///
/// If a message is sent while typing is triggered, the user will stop typing for a brief period
/// of time and then resume again until either `stop` is called or the struct is dropped.
///
/// This should rarely be used for bots, although it is a good indicator that a
/// long-running command is still being processed.
///
/// ## Examples
///
/// ```rust,no_run
/// # #[cfg(feature = "cache")]
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// # use serenity::{
/// # cache::Cache,
/// # http::{Http, Typing},
/// # model::{ModelError, channel::GuildChannel, id::ChannelId},
/// # Result,
/// # };
/// # use std::sync::Arc;
/// #
/// # fn long_process() {}
/// # let http = Arc::new(Http::default());
/// # let cache = Cache::default();
/// # let channel = cache
/// # .guild_channel(ChannelId(7))
/// # .await.ok_or(ModelError::ItemMissing)?;
/// // Initiate typing (assuming http is `Arc<Http>` and `channel` is bound)
/// let typing = channel.start_typing(&http)?;
///
/// // Run some long-running process
/// long_process();
///
/// // Stop typing
/// typing.stop();
/// #
/// # Ok(())
/// # }
/// ```
///
/// [`Typing`]: ../../http/typing/struct.Typing.html
/// [`Typing::stop`]: ../../http/typing/struct.Typing.html#method.stop
pub fn start_typing(self, http: &Arc<Http>) -> Result<Typing> {
http.start_typing(self.id.0)
}

/// Unpins a [`Message`] in the channel given by its Id.
///
/// Requires the [Manage Messages] permission.
Expand Down
55 changes: 54 additions & 1 deletion src/model/channel/private_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use crate::builder::{
#[cfg(feature = "model")]
use crate::http::AttachmentType;
#[cfg(feature = "http")]
use crate::http::Http;
use crate::http::{Http, Typing};
#[cfg(feature = "model")]
use std::sync::Arc;

/// A Direct Message text channel with another user.
#[derive(Clone, Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -310,6 +312,57 @@ impl PrivateChannel {
self.id.send_message(&http, f).await
}

/// Starts typing in the channel for an indefinite period of time.
///
/// Returns [`Typing`] that is used to trigger the typing. [`Typing::stop`] must be called
/// on the returned struct to stop typing. Note that on some clients, typing may persist
/// for a few seconds after `stop` is called.
/// Typing is also stopped when the struct is dropped.
///
/// If a message is sent while typing is triggered, the user will stop typing for a brief period
/// of time and then resume again until either `stop` is called or the struct is dropped.
///
/// This should rarely be used for bots, although it is a good indicator that a
/// long-running command is still being processed.
///
/// ## Examples
///
/// ```rust,no_run
/// # #[cfg(feature = "cache")]
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// # use serenity::{
/// # cache::Cache,
/// # http::{Http, Typing},
/// # model::{ModelError, channel::PrivateChannel, id::ChannelId},
/// # Result,
/// # };
/// # use std::sync::Arc;
/// #
/// # fn long_process() {}
/// # let http = Arc::new(Http::default());
/// # let cache = Cache::default();
/// # let channel = cache.private_channel(ChannelId(7))
/// # .await
/// # .ok_or(ModelError::ItemMissing)?;
/// // Initiate typing (assuming http is `Arc<Http>` and `channel` is bound)
/// let typing = channel.start_typing(&http)?;
///
/// // Run some long-running process
/// long_process();
///
/// // Stop typing
/// typing.stop();
/// #
/// # Ok(())
/// # }
/// ```
///
/// [`Typing`]: ../../http/typing/struct.Typing.html
/// [`Typing::stop`]: ../../http/typing/struct.Typing.html#method.stop
pub fn start_typing(self, http: &Arc<Http>) -> Result<Typing> {
http.start_typing(self.id.0)
}

/// Unpins a [`Message`] in the channel given by its Id.
///
/// Requires the [Manage Messages] permission.
Expand Down

0 comments on commit bcf8249

Please sign in to comment.