1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
use std::sync::Arc;

#[cfg(all(feature = "tokio_compat", not(feature = "tokio")))]
use tokio::time::delay_for as sleep;
#[cfg(feature = "tokio")]
use tokio::time::sleep;
use tokio::{
    sync::oneshot::{self, error::TryRecvError, Sender},
    time::Duration,
};

use crate::{error::Result, http::Http};

/// 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 [`Typing::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 [`Typing::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`]: crate::model::channel::Channel
#[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.
    ///
    /// # Errors
    ///
    /// Returns an  [`Error::Http`] if there is an error.
    ///
    /// [`Channel`]: crate::model::channel::Channel
    /// [`Error::Http`]: crate::error::Error::Http
    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.
                sleep(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`]: crate::model::channel::Channel
    pub fn stop(self) -> Option<()> {
        self.0.send(()).ok()
    }
}