> ## Documentation Index
> Fetch the complete documentation index at: https://whatsapp-rust.jlucaso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Chatstate

> Typing indicators and chat state notifications

The `Chatstate` struct provides methods for sending typing indicators and recording notifications to recipients.

## Access

Access chatstate operations through the client:

```rust theme={null}
let chatstate = client.chatstate();
```

## Methods

### send

Send a chat state update to a recipient.

```rust theme={null}
pub async fn send(
    &self,
    to: &Jid,
    state: ChatStateType,
) -> Result<(), ChatStateError>
```

**Parameters:**

* `to` - Recipient JID (user or group)
* `state: ChatStateType` - Type of chat state to send

**Returns:**

* `Result<(), ChatStateError>` — `Ok(())` on success; a `ChatStateError` if the client is disconnected or the JID is invalid

**Example:**

```rust theme={null}
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.

```rust theme={null}
pub async fn send_composing(&self, to: &Jid) -> Result<(), ChatStateError>
```

**Example:**

```rust theme={null}
let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;
client.chatstate().send_composing(&recipient).await?;
```

### send\_recording

Convenience method to send audio recording indicator.

```rust theme={null}
pub async fn send_recording(&self, to: &Jid) -> Result<(), ChatStateError>
```

**Example:**

```rust theme={null}
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.

```rust theme={null}
pub async fn send_paused(&self, to: &Jid) -> Result<(), ChatStateError>
```

**Example:**

```rust theme={null}
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`](/api/client#register_chatstate_handler) to receive them.

```rust theme={null}
#[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,
}
```

<ParamField path="chat" type="Jid">
  The chat where the event occurred. For direct messages this is the sender's JID; for groups this is the group JID.
</ParamField>

<ParamField path="participant" type="Option<Jid>">
  For group chats, the participant who triggered the event. `None` for 1:1 chats.
</ParamField>

<ParamField path="state" type="ReceivedChatState">
  The parsed chat state — see `ReceivedChatState` below.
</ParamField>

**Example:**

```rust theme={null}
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.

```rust theme={null}
#[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

```rust theme={null}
#[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:**

```rust theme={null}
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);
```

## Wire Format

### Composing (Typing)

```xml theme={null}
<chatstate to="15551234567@s.whatsapp.net">
  <composing/>
</chatstate>
```

### Recording (Voice)

```xml theme={null}
<chatstate to="15551234567@s.whatsapp.net">
  <composing media="audio"/>
</chatstate>
```

Note: Recording uses `<composing media="audio">` rather than a separate tag.

### Paused (Stopped)

```xml theme={null}
<chatstate to="15551234567@s.whatsapp.net">
  <paused/>
</chatstate>
```

## Usage Patterns

### Typing indicator lifecycle

```rust theme={null}
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

```rust theme={null}
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: buffa::MessageField::some(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:

```rust theme={null}
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:

```rust theme={null}
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<(), ChatStateError>`. You'll encounter these most often:

* **Not connected** — you are not connected to WhatsApp
* **Invalid JID** — the recipient JID is malformed
* **Network errors** — connection dropped mid-send

```rust theme={null}
use whatsapp_rust::ChatStateError;

let recipient: Jid = "15551234567@s.whatsapp.net".parse()?;

match client.chatstate().send_composing(&recipient).await {
    Ok(_) => println!("Sent typing indicator"),
    Err(ChatStateError::Client(e)) => {
        eprintln!("Client error: {}", e);
    }
    Err(e) => eprintln!("Error: {}", e),
}
```

## Best Practices

1. **Throttle updates**: Don't send chat state updates more than once every 2-3 seconds
2. **Clear on send**: Send `Paused` before sending the actual message
3. **Auto-timeout**: Consider auto-clearing typing indicators after 10-15 seconds of inactivity
4. **Don't spam**: Only send when state actually changes
5. **Groups**: Be mindful that all group members will see the indicator

## Complete Example

```rust theme={null}
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)");
```
