whatsapp-rust 0.7.0

Rust client for WhatsApp Web
Documentation
//! Chat state (typing indicators) feature.

use crate::client::{Client, ClientError};
use log::debug;
use thiserror::Error;
use wacore::WireEnum;
use wacore_binary::Jid;
use wacore_binary::builder::NodeBuilder;

/// Error returned by chat-state (typing indicator) operations.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ChatStateError {
    /// Connection/transport failure sending the `<chatstate>` stanza.
    #[error("{0}")]
    Client(#[from] ClientError),
}

/// Chat state type for typing indicators.
#[derive(Debug, Clone, Copy, PartialEq, Eq, WireEnum)]
#[non_exhaustive]
pub enum ChatStateType {
    #[wire = "composing"]
    Composing,
    #[wire = "recording"]
    Recording,
    #[wire = "paused"]
    Paused,
}

/// Feature handle for chat state operations.
pub struct Chatstate<'a> {
    client: &'a Client,
}

impl<'a> Chatstate<'a> {
    pub(crate) fn new(client: &'a Client) -> Self {
        Self { client }
    }

    /// Send a chat state update to a recipient.
    pub async fn send(&self, to: &Jid, state: ChatStateType) -> Result<(), ChatStateError> {
        debug!(target: "Chatstate", "Sending {} to {}", state, to);

        let node = self.build_chatstate_node(to, state);
        self.client.send_node(node).await?;
        Ok(())
    }

    pub async fn send_composing(&self, to: &Jid) -> Result<(), ChatStateError> {
        self.send(to, ChatStateType::Composing).await
    }

    pub async fn send_recording(&self, to: &Jid) -> Result<(), ChatStateError> {
        self.send(to, ChatStateType::Recording).await
    }

    pub async fn send_paused(&self, to: &Jid) -> Result<(), ChatStateError> {
        self.send(to, ChatStateType::Paused).await
    }

    fn build_chatstate_node(&self, to: &Jid, state: ChatStateType) -> wacore_binary::Node {
        let child = match state {
            ChatStateType::Composing => NodeBuilder::new("composing").build(),
            ChatStateType::Recording => {
                NodeBuilder::new("composing").attr("media", "audio").build()
            }
            ChatStateType::Paused => NodeBuilder::new("paused").build(),
        };

        NodeBuilder::new("chatstate")
            .attr("to", to)
            .children([child])
            .build()
    }
}

impl Client {
    /// Access chat state operations.
    pub fn chatstate(&self) -> Chatstate<'_> {
        Chatstate::new(self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_chat_state_type_string_enum() {
        assert_eq!(ChatStateType::Composing.as_str(), "composing");
        assert_eq!(ChatStateType::Recording.to_string(), "recording");
        assert_eq!(
            ChatStateType::try_from("paused").unwrap(),
            ChatStateType::Paused
        );
    }
}