The Chatstate struct provides methods for sending typing indicators and recording notifications to recipients.
Access
Access chatstate operations through the client:
let chatstate = client.chatstate();
Methods
send
Send a chat state update to a recipient.
pub async fn send(
&self,
to: &Jid,
state: ChatStateType,
) -> Result<(), ClientError>
Parameters:
to - Recipient JID (user or group)
state: ChatStateType - Type of chat state to send
Returns:
Result<(), ClientError> - Success or client error
Example:
use whatsapp_rust::features::chatstate::ChatStateType;
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
// Send typing indicator
client.chatstate().send(&recipient, ChatStateType::Composing).await?;
// Send recording indicator
client.chatstate().send(&recipient, ChatStateType::Recording).await?;
// Send paused (stopped typing)
client.chatstate().send(&recipient, ChatStateType::Paused).await?;
send_composing
Convenience method to send typing indicator.
pub async fn send_composing(&self, to: &Jid) -> Result<(), ClientError>
Example:
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
client.chatstate().send_composing(&recipient).await?;
send_recording
Convenience method to send audio recording indicator.
pub async fn send_recording(&self, to: &Jid) -> Result<(), ClientError>
Example:
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
client.chatstate().send_recording(&recipient).await?;
println!("Showing 'recording audio' indicator");
send_paused
Convenience method to send paused/stopped typing indicator.
pub async fn send_paused(&self, to: &Jid) -> Result<(), ClientError>
Example:
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
client.chatstate().send_paused(&recipient).await?;
println!("Cleared typing indicator");
Receiving chat state events
ChatStateEvent
Incoming chatstate stanzas (typing indicators from other users) are dispatched as ChatStateEvent values. Register a handler with register_chatstate_handler to receive them.
#[derive(Debug, Clone)]
pub struct ChatStateEvent {
/// The chat where the event occurred (user JID for 1:1, group JID for groups)
pub chat: Jid,
/// For group chats, the participant who triggered the event
pub participant: Option<Jid>,
/// The chat state (typing, recording_audio, or idle)
pub state: ReceivedChatState,
}
The chat where the event occurred. For direct messages this is the sender’s JID; for groups this is the group JID.
For group chats, the participant who triggered the event. None for 1:1 chats.
The parsed chat state — see ReceivedChatState below.
Example:
use whatsapp_rust::ChatStateEvent;
use std::sync::Arc;
client.register_chatstate_handler(Arc::new(|event: ChatStateEvent| {
match event.state {
ReceivedChatState::Typing => {
println!("{} is typing in {}", event.participant.as_ref().unwrap_or(&event.chat), event.chat);
}
ReceivedChatState::RecordingAudio => {
println!("{} is recording audio", event.chat);
}
ReceivedChatState::Idle => {
println!("{} stopped typing", event.chat);
}
_ => {}
}
})).await;
ReceivedChatState
The state values for incoming chatstate events, aligned with WhatsApp Web’s WAChatState constants.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReceivedChatState {
Typing, // User is typing text
RecordingAudio, // User is recording a voice message
Idle, // User stopped typing/recording (default)
}
ReceivedChatState is #[non_exhaustive], so match statements should include a wildcard arm.
Wire format mapping:
| XML stanza | ReceivedChatState |
|---|
<composing/> | Typing |
<composing media="audio"/> | RecordingAudio |
<paused/> | Idle |
Sending chat state updates
ChatStateType Enum
#[non_exhaustive]
pub enum ChatStateType {
Composing, // Typing text
Recording, // Recording audio
Paused, // Stopped typing/recording
}
ChatStateType is #[non_exhaustive], so match statements should include a wildcard arm to handle future variants.
Methods:
as_str() - Returns "composing", "recording", or "paused"
String conversion:
use whatsapp_rust::features::chatstate::ChatStateType;
let state = ChatStateType::Composing;
assert_eq!(state.as_str(), "composing");
assert_eq!(state.to_string(), "composing");
// Parse from string
let parsed = ChatStateType::try_from("recording")?;
assert_eq!(parsed, ChatStateType::Recording);
Composing (Typing)
<chatstate to="15551234567@s.whatsapp.net">
<composing/>
</chatstate>
Recording (Voice)
<chatstate to="15551234567@s.whatsapp.net">
<composing media="audio"/>
</chatstate>
Note: Recording uses <composing media="audio"> rather than a separate tag.
Paused (Stopped)
<chatstate to="15551234567@s.whatsapp.net">
<paused/>
</chatstate>
Usage Patterns
Typing Indicator Lifecycle
use whatsapp_rust::features::chatstate::ChatStateType;
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
// User starts typing
client.chatstate().send_composing(&recipient).await?;
// User is typing... (you may want to throttle these)
std::thread::sleep(std::time::Duration::from_secs(2));
// User stopped typing (optional, will auto-clear)
client.chatstate().send_paused(&recipient).await?;
// Send the actual message
let message = wa::Message {
conversation: Some("Hello!".to_string()),
..Default::default()
};
client.send_message(recipient.clone(), message).await?;
Recording Audio
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
// Start recording
client.chatstate().send_recording(&recipient).await?;
// Record audio...
let audio_data = record_audio();
// Stop indicator (optional)
client.chatstate().send_paused(&recipient).await?;
// Upload and send audio message
let upload = client.upload(audio_data, MediaType::Audio, Default::default()).await?;
let message = wa::Message {
audio_message: Some(Box::new(wa::message::AudioMessage {
url: Some(upload.url),
direct_path: Some(upload.direct_path),
media_key: Some(upload.media_key_vec()),
file_enc_sha256: Some(upload.file_enc_sha256_vec()),
file_sha256: Some(upload.file_sha256_vec()),
file_length: Some(upload.file_length),
mimetype: Some("audio/ogg; codecs=opus".to_string()),
ptt: Some(true),
..Default::default()
})),
..Default::default()
};
client.send_message(recipient.clone(), message).await?;
Throttling
To avoid spamming chat state updates:
use std::time::{Duration, Instant};
struct ChatStateThrottler {
last_sent: Option<Instant>,
interval: Duration,
}
impl ChatStateThrottler {
fn new() -> Self {
Self {
last_sent: None,
interval: Duration::from_secs(3),
}
}
fn should_send(&mut self) -> bool {
if let Some(last) = self.last_sent {
if last.elapsed() < self.interval {
return false;
}
}
self.last_sent = Some(Instant::now());
true
}
}
// Usage
let mut throttler = ChatStateThrottler::new();
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
// In your typing event handler
if throttler.should_send() {
client.chatstate().send_composing(&recipient).await?;
}
Group Chats
Chat state indicators work in group chats as well:
let group_jid: Jid = "123456789@g.us".parse()?;
// Show typing in group
client.chatstate().send_composing(&group_jid).await?;
// Send message
let message = wa::Message {
conversation: Some("Hello everyone!".to_string()),
..Default::default()
};
client.send_message(group_jid.clone(), message).await?;
Error Handling
All methods return Result<(), ClientError>. Common errors:
- Not connected: Client not connected to WhatsApp
- Invalid JID: Malformed recipient JID
- Network errors: Connection issues
use whatsapp_rust::client::ClientError;
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
match client.chatstate().send_composing(&recipient).await {
Ok(_) => println!("Sent typing indicator"),
Err(ClientError::NotConnected) => {
eprintln!("Not connected to WhatsApp");
}
Err(e) => eprintln!("Error: {}", e),
}
Best Practices
- Throttle updates: Don’t send chat state updates more than once every 2-3 seconds
- Clear on send: Send
Paused before sending the actual message
- Auto-timeout: Consider auto-clearing typing indicators after 10-15 seconds of inactivity
- Don’t spam: Only send when state actually changes
- Groups: Be mindful that all group members will see the indicator
Complete Example
use whatsapp_rust::features::chatstate::ChatStateType;
use std::time::Duration;
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
// Simulate user typing
println!("User starts typing...");
client.chatstate().send_composing(&recipient).await?;
// Simulate typing delay
tokio::time::sleep(Duration::from_secs(2)).await;
// User still typing (throttled)
println!("Still typing...");
// User finishes and sends message
client.chatstate().send_paused(&recipient).await?;
let message = wa::Message {
conversation: Some("Hello there!".to_string()),
..Default::default()
};
client.send_message(recipient.clone(), message).await?;
println!("Message sent");
// Or simulate voice recording
println!("\nUser starts recording...");
client.chatstate().send_recording(&recipient).await?;
tokio::time::sleep(Duration::from_secs(3)).await;
client.chatstate().send_paused(&recipient).await?;
println!("Recording stopped (would send audio here)");