Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions changelog/2026-06-15-send-command.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: "June 15, 2026 — Example bot: 🦀send command"
description: "Adds a 🦀send <jid> <text> command that you can use in the demo bot to relay text to any group or DM JID and report send latency back to the originating chat."
---

PR [`#875`](https://github.com/oxidezap/whatsapp-rust/pull/875) adds a `🦀send` command that you can use in the demo bot in `src/main.rs`.

## What changed

The example bot now handles `🦀send <jid> <text>` in addition to `🦀ping`. When you send this command from any chat, the bot relays the text to an arbitrary JID — a group (`...@g.us`) or DM (`...@s.whatsapp.net`) — then replies in the originating chat with the send latency via `ctx.reply_quoting`.

```
🦀send 1234567890@s.whatsapp.net Hello there!
```

The reply in the originating chat looks like:

```
âś… Sent to 1234567890@s.whatsapp.net
`3.42ms`
```

## Implementation notes

The handler uses `split_once(char::is_whitespace)` to split the JID and message text in a single pass with no intermediate `Vec`. The JID is validated through `FromStr` — invalid JIDs are logged and the handler returns early. All failures (missing args, bad JID, send error) are logged rather than panicking, matching the rest of the example bot.

The message handler was also refactored to hoist the `text_content()` guard into a single `let … else { return }` at the top, avoiding repeated calls and making it straightforward to add new text-triggered commands.
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"group": "Changelog",
"pages": [
"changelog/overview",
"changelog/2026-06-15-send-command",
"changelog/2026-06-14-drop-moka-portable-cache",
"changelog/2026-06-12-binary-size-ci",
"changelog/2026-06-11-bot-api-overhaul",
Expand Down
48 changes: 45 additions & 3 deletions quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ cargo run -- -p 15551234567 --code MYCODE12 # Custom 8-char pair code
cargo run -- -p 15551234567 -c MYCODE12 # Short form
```

The demo bot responds to `🦀ping` with a quoted `🏓 Pong!` reply, edits the reply to append the send latency, and supports media ping/pong via CDN reuse.
The demo bot responds to `🦀ping` with a quoted `🏓 Pong!` reply and supports media ping/pong via CDN reuse. You can also use `🦀send <jid> <text>` to relay a plain-text message to any group or DM JID and report send latency back to the originating chat.

## Using MessageContext

Expand Down Expand Up @@ -349,14 +349,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

## Complete example with logging

Here's a production-ready example with proper logging, reactions, message editing, and media CDN reuse:
Use this production-ready example with proper logging, reactions, media CDN reuse, and the `🦀send` relay command:

```rust src/main.rs
use log::{error, info};
use whatsapp_rust::prelude::*;
use whatsapp_rust::pair_code::PairCodeOptions;

const PING_TRIGGER: &str = "🦀ping";
const SEND_TRIGGER: &str = "🦀send";
const PONG_TEXT: &str = "🏓 Pong!";
const REACTION_EMOJI: &str = "🏓";

Expand Down Expand Up @@ -410,13 +411,54 @@ async fn handle_message(ctx: MessageContext) {
return;
}

if ctx.message.text_content() == Some(PING_TRIGGER) {
let Some(text) = ctx.message.text_content() else {
return;
};
if text == PING_TRIGGER {
if let Err(e) = ctx.react(REACTION_EMOJI).await {
error!("Failed to send reaction: {e}");
}
if let Err(e) = ctx.reply_quoting(PONG_TEXT).await {
error!("Failed to send pong: {e}");
}
} else if let Some(args) = text.strip_prefix(SEND_TRIGGER) {
handle_send_command(&ctx, args).await;
Comment on lines +424 to +425

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate relay commands on non-self messages

When the relayed text itself starts with 🦀send, this branch will also run for the bot's own sent-message fanout unless the handler checks ctx.info.source.is_from_me first. A user can send one nested relay command and have the bot reinterpret its own relayed message as another command, causing unintended extra sends (or command chains) from the bot account; add a self-message guard before dispatching handle_send_command.

Useful? React with 👍 / 👎.

}
}

/// Handles `🦀send <jid> <text>`, relaying text to an arbitrary group or DM JID.
async fn handle_send_command(ctx: &MessageContext, args: &str) {
let Some((target, text)) = args.trim_start().split_once(char::is_whitespace) else {
error!("Usage: {SEND_TRIGGER} <jid> <text>");
return;
};
let text = text.trim_start();
if text.is_empty() {
error!("Usage: {SEND_TRIGGER} <jid> <text>");
return;
}
let jid: Jid = match target.parse() {
Ok(jid) => jid,
Err(e) => {
error!("Invalid JID '{target}': {e}");
return;
}
};
let start = wacore::time::Instant::now();
let sent = match ctx.client.send_message(jid, wa::Message::text(text)).await {
Ok(sent) => sent,
Err(e) => {
error!("Failed to send message: {e}");
return;
}
};
let duration = format!("{:.2?}", start.elapsed());
info!("Sent message {} to {} in {}", sent.message_id, sent.to, duration);
if let Err(e) = ctx
.reply_quoting(format!("âś… Sent to {}\n`{duration}`", sent.to))
.await
{
error!("Failed to send timing reply: {e}");
}
}

Expand Down