Skip to main content

whatsapp_rust/features/
chatstate.rs

1//! Chat state (typing indicators) feature.
2
3use crate::client::{Client, ClientError};
4use log::debug;
5use thiserror::Error;
6use wacore::WireEnum;
7use wacore_binary::Jid;
8use wacore_binary::builder::NodeBuilder;
9
10/// Error returned by chat-state (typing indicator) operations.
11#[derive(Debug, Error)]
12#[non_exhaustive]
13pub enum ChatStateError {
14    /// Connection/transport failure sending the `<chatstate>` stanza.
15    #[error("{0}")]
16    Client(#[from] ClientError),
17}
18
19/// Chat state type for typing indicators.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, WireEnum)]
21#[non_exhaustive]
22pub enum ChatStateType {
23    #[wire = "composing"]
24    Composing,
25    #[wire = "recording"]
26    Recording,
27    #[wire = "paused"]
28    Paused,
29}
30
31/// Feature handle for chat state operations.
32pub struct Chatstate<'a> {
33    client: &'a Client,
34}
35
36impl<'a> Chatstate<'a> {
37    pub(crate) fn new(client: &'a Client) -> Self {
38        Self { client }
39    }
40
41    /// Send a chat state update to a recipient.
42    pub async fn send(&self, to: &Jid, state: ChatStateType) -> Result<(), ChatStateError> {
43        debug!(target: "Chatstate", "Sending {} to {}", state, to);
44
45        let node = self.build_chatstate_node(to, state);
46        self.client.send_node(node).await?;
47        Ok(())
48    }
49
50    pub async fn send_composing(&self, to: &Jid) -> Result<(), ChatStateError> {
51        self.send(to, ChatStateType::Composing).await
52    }
53
54    pub async fn send_recording(&self, to: &Jid) -> Result<(), ChatStateError> {
55        self.send(to, ChatStateType::Recording).await
56    }
57
58    pub async fn send_paused(&self, to: &Jid) -> Result<(), ChatStateError> {
59        self.send(to, ChatStateType::Paused).await
60    }
61
62    fn build_chatstate_node(&self, to: &Jid, state: ChatStateType) -> wacore_binary::Node {
63        let child = match state {
64            ChatStateType::Composing => NodeBuilder::new("composing").build(),
65            ChatStateType::Recording => {
66                NodeBuilder::new("composing").attr("media", "audio").build()
67            }
68            ChatStateType::Paused => NodeBuilder::new("paused").build(),
69        };
70
71        NodeBuilder::new("chatstate")
72            .attr("to", to)
73            .children([child])
74            .build()
75    }
76}
77
78impl Client {
79    /// Access chat state operations.
80    pub fn chatstate(&self) -> Chatstate<'_> {
81        Chatstate::new(self)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_chat_state_type_string_enum() {
91        assert_eq!(ChatStateType::Composing.as_str(), "composing");
92        assert_eq!(ChatStateType::Recording.to_string(), "recording");
93        assert_eq!(
94            ChatStateType::try_from("paused").unwrap(),
95            ChatStateType::Paused
96        );
97    }
98}