diff --git a/changelog/2026-06-15-send-command.mdx b/changelog/2026-06-15-send-command.mdx new file mode 100644 index 00000000..0ab4e1b9 --- /dev/null +++ b/changelog/2026-06-15-send-command.mdx @@ -0,0 +1,27 @@ +--- +title: "June 15, 2026 — Example bot: 🦀send command" +description: "Adds a 🦀send 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 ` 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. diff --git a/docs.json b/docs.json index d142b1a0..049c1c1d 100644 --- a/docs.json +++ b/docs.json @@ -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", diff --git a/quickstart.mdx b/quickstart.mdx index 20719643..481d649a 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -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 ` to relay a plain-text message to any group or DM JID and report send latency back to the originating chat. ## Using MessageContext @@ -349,7 +349,7 @@ async fn main() -> Result<(), Box> { ## 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}; @@ -357,6 +357,7 @@ 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 = "🏓"; @@ -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; + } +} + +/// Handles `🦀send `, 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} "); + return; + }; + let text = text.trim_start(); + if text.is_empty() { + error!("Usage: {SEND_TRIGGER} "); + 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}"); } }