Skill v1.0.1
currentAutomated scan85/100+11 new
version: "1.0.1" name: whatsapp description: "WhatsApp automation (Green API primary, WAHA fallback). Use for: send/receive messages, send voice/images/files, retrieve incoming messages (text, voice, images), transcribe voice messages. WAME = 'WhatsApp ME' = send me by WhatsApp." enhancedBy:
- get-contact: "Auto-lookup phone by name. Without it: ask user for phone directly"
WhatsApp Automation
Send messages via unified script with Green API (primary) + WAHA (fallback if configured).
Quick Start - Unified Script
cd ~/.claude/skills/whatsapp/scripts# Text messagenpx ts-node send-message.ts "Hello!" --to 972501234567# Multiline text from shell: use $'...'npx ts-node send-message.ts $'Line 1\n\nLine 2\n- Bullet A\n- Bullet B' --to 972501234567# Image with captionnpx ts-node send-message.ts --to 972501234567 --image /path/to/image.jpg --caption "Check this out!"# Video with captionnpx ts-node send-message.ts --to 972501234567 --video /path/to/video.mp4 --caption "Watch this!"# Any media file (auto-detect)npx ts-node send-message.ts --to 972501234567 --media /path/to/any-file --caption "Here!"# Voice messagenpx ts-node send-message.ts --to 972501234567 --voice /path/to/audio.ogg# File/documentnpx ts-node send-message.ts --to 972501234567 --file /path/to/doc.pdf --caption "Here's the doc"# Send to groupnpx ts-node send-message.ts "Group message" --group 120363xxx@g.us# Reply to a specific message (quoted reply)npx ts-node send-message.ts "Reply!" --group 120363xxx@g.us --quote BAE5367237E13A1B# Voice as quoted replynpx ts-node send-message.ts --group 120363xxx@g.us --voice /tmp/voice.mp3 --quote BAE5367237E13A1B# Dry run (preview)npx ts-node send-message.ts "Test" --to 972501234567 --dry-run
Default Numbers
| Alias | Number | Use Case | |
|---|---|---|---|
| myself / me / test | $MY_PHONE (from config.sh) | Default recipient |
Sending Video & Audio — encode to AAC
WhatsApp (and Facebook) play a video silently if its audio track is MP3 muxed into an MP4 — the file is valid and has sound in desktop players, but the mobile app drops it. Always ship video with AAC audio:
# Re-encode ONLY the audio to AAC, leave the video untouched; +faststart for streamingffmpeg -y -i in.mp4 -c:v copy -c:a aac -b:a 192k -movflags +faststart out.mp4# Verify it isn't silent before sendingffmpeg -i out.mp4 -af volumedetect -f null /dev/null 2>&1 | grep mean_volume
Voice notes: convert the source to OGG/Opus first — raw MP3 in a container is unreliable in the app.
Script Options
| Option | Description | |
|---|---|---|
"message" | Text message (first positional argument) | |
--to, --phone <NUMBER> | Phone number (Israeli format accepted) | |
--group <ID> | Group ID (format: 120363xxx@g.us) | |
--image <PATH> | Attach image file | |
--video <PATH> | Attach video file (MP4, etc.) | |
--media <PATH> | Attach any media file (auto-detect) | |
--voice <PATH> | Send voice message (OGG/MP3) | |
--file <PATH> | Attach document/file | |
--caption <TEXT> | Caption for media files | |
--quote <MSG_ID> | Reply to a specific message (quotedMessageId) | |
--dry-run | Preview without sending |
Multiline Formatting
When sending a WhatsApp message with line breaks from the shell, do not pass literal \n inside a normal quoted string if you want actual newlines in the final message.
Use shell ANSI-C quoting instead:
npx ts-node send-message.ts $'Hi\n\nLine 2\n- Item 1\n- Item 2' --group 120363xxx@g.us
Why:
"text with \n"sends the characters\+n— but the script auto-converts these to real newlines$'text with \n'sends real line breaks natively
Either form works — the script normalizes literal \n to real newlines before sending.
Apostrophes inside `$'...'`: Use \' directly — do NOT use the '\'' trick. The '\'' pattern breaks out of ANSI-C quoting, and everything after it becomes regular single-quoted (where \n is literal).
# CORRECT — apostrophe inside $'...'$'I\'ve added a check.\n\nNext line.'# WRONG — breaks ANSI-C quoting, \n becomes literal after the apostrophe$'I'\''ve added a check.\n\nNext line.'
For structured WhatsApp messages, prefer:
- short opening line
- blank line
- short bullet list
- blank line
- closing line
Formatting Rule — Match Language
English messages: Use regular bullets/dashes. Never use Hebrew letters (א. ב. ג.) in English text.
- First item- Second item- Third item
Hebrew messages: Every line must start with a Hebrew character for correct RTL rendering. Use Hebrew letters for list items.
א. פריט ראשוןב. פריט שניג. פריט שלישי
Don't mix: No Hebrew letters in English messages, no dashes/numbers in Hebrew messages.
Don't:
- פריט ראשון1. פריט שני* פריט שלישי
Lines starting with non-Hebrew characters (dashes, numbers, asterisks, English) break RTL alignment in WhatsApp. Always ensure the first visible character is Hebrew.
Provider Logic
| Content Type | Primary | Fallback | |
|---|---|---|---|
| Text | Green API | WAHA (if configured) | |
| Image | Green API | - | |
| Video | Green API | - | |
| Voice | Green API | - | |
| File | Green API | - |
Note: WAHA is not currently installed. Green API handles all message types.
Configuration
Credentials in scripts/.env:
# Green API (primary provider)GREEN_API_URL=https://7103.api.greenapi.comGREEN_API_INSTANCE=your_instanceGREEN_API_TOKEN=your_token# WAHA (optional fallback — uncomment if installed)# WAHA_URL=http://localhost:3001# WAHA_API_KEY=your_key# WAHA_SESSION=default
Phone Number Format
| Input | Normalized | |
|---|---|---|
972501234567 | 972501234567@c.us | |
0501234567 | 972501234567@c.us | |
501234567 | 972501234567@c.us |
Direct API (WAHA)
For text messages only:
source ~/.claude/skills/whatsapp/config.shcurl -s -X POST "$WAHA_URL/api/sendText" \-H "X-Api-Key: $WAHA_API_KEY" \-H "Content-Type: application/json" \-d '{"session": "default","chatId": "972501234567@c.us","text": "Hello!"}'
Check Session Status
source ~/.claude/skills/whatsapp/config.shcurl -s -H "X-Api-Key: $WAHA_API_KEY" "$WAHA_URL/api/sessions/default"
Finding Contact by Name
When user says "send WhatsApp to [name]" without phone number:
- Use
get-contactskill to search CRM - If not found → ask user for phone
- If multiple → ask user to choose
- If one → confirm before sending
Always confirm contact before sending.
Finding a Group by Name or Project
When user says "send to [project] group" — search Green API directly (WAHA may be down):
TOKEN=$(grep "GREEN_API_TOKEN" ~/.claude/skills/whatsapp/scripts/.env | cut -d'=' -f2)INSTANCE=$(grep "GREEN_API_INSTANCE" ~/.claude/skills/whatsapp/scripts/.env | cut -d'=' -f2)curl -s "https://7103.api.greenapi.com/waInstance$INSTANCE/getChats/$TOKEN" | python3 -c "import json,sysdata=json.load(sys.stdin)for c in (data if isinstance(data,list) else []):if '@g.us' in c.get('id',''):print(c.get('id'), '|', c.get('name',''))"
Then send using send-message.ts --group <ID>.
Never check WAHA session before sending — the script handles fallback automatically.
Provider Notes
Green API is the primary provider. WAHA is not installed and only used as fallback if explicitly configured. Just run send-message.ts — it uses Green API directly.
Retrieving Messages (get-messages.ts)
Fetch incoming messages, voice recordings, and media from any contact:
# Get recent messages from a contactnpx ts-node get-messages.ts --chat 972526062921 --count 5# Get messages from last 2 hoursnpx ts-node get-messages.ts --chat 972526062921 --minutes 120# Get all messages from a time rangenpx ts-node get-messages.ts --start "2h ago" --end "now"# Output as JSON (includes download URLs for media)npx ts-node get-messages.ts --chat 972526062921 --json
What you get:
- Text messages with full content
- Voice messages with download URL (OGA/OGG format) — can be transcribed separately
- Images with download URL and metadata
- Timestamps, sender info, file names
- All message types (extended text, reactions, media)
Quoted/reply messages are shown inline — get-messages.ts now displays quoted context automatically with ↳ Reply to prefix. Both extendedTextMessage and quotedMessage types are handled.
Message types: textMessage, extendedTextMessage (with .text), quotedMessage (reply with .quotedMessage.textMessage), reactionMessage, imageMessage
Voice Message Workflow:
- Get messages with
get-messages.ts --json - Extract
downloadUrlfromaudioMessagetype - Download the OGA file
- Convert to WAV:
ffmpeg -i file.oga -ar 16000 file.wav - Transcribe:
/transcribe --language he /path/to/file.wav
Waiting for Replies — Monitor Integration
Sometimes after sending a message you need the contact's reply before you can continue — an approval, a decision, feedback, a "how much do I owe you". Instead of stopping and asking the user to watch the chat, hand the waiting to the Monitor tool and keep working; each new reply arrives as a notification.
Use scripts/watch-replies.sh — it polls for genuinely new incoming messages (dedupes by id, handles text / reactions / media) and prints one line per reply, which is exactly what Monitor consumes. Exits only when killed (Monitor timeout / TaskStop).
# usagescripts/watch-replies.sh --chat <number|groupId> [--since <epoch>] [--interval 30] [--label REPLY]
Launch it through the Monitor tool (not Bash), so replies notify you while you keep working:
command:~/.claude/skills/whatsapp/scripts/watch-replies.sh --chat 972501234567 --since "$(date +%s)" --label CLIENTtimeout_ms: how long to wait (e.g.3600000for an hour).- Always set `--since` to the moment you sent (
$(date +%s)), or old history re-fires as if new.
⚠️ Do NOT hand-roll an inline poller in the Monitor `command`. Passing a group id or JSON body directly in the shell breaks under zsh —@g.ustriggersbad math expression: illegal character: @, and the monitor exits 1.watch-replies.shtakes the chat as an argument (--chat <id>), so group ids are safe. Always reach for this script; never inline the group id + curl/python in the Monitor command.
Decide per situation whether to use it
Reach for it when the reply changes what you do next:
- you sent something that needs approval before the next step (a draft, a preview, a quote);
- you asked a question and will act on the answer;
- you're in an iterative loop (send fix → they review → send next);
- you started long work and want to hear back without blocking.
Skip it for fire-and-forget (a confirmation, an FYI, a "done") — no reply is expected, so there's nothing to watch.
Staying in the conversation
Often the right move is to remain engaged: when a reply lands, respond, then re-arm a fresh watcher (new --since) for the next turn. That's a real back-and-forth — send sample → they approve → send fix → wait again — until the thread naturally closes (final approval, thanks, payment settled, or the user says wrap up). Each round: send → watch → reply → re-watch. Use your judgement on when the exchange is genuinely done and it's time to stop watching.
Discipline
- Each watcher has its own dedupe file, so two watchers on the same chat double-fire —
TaskStopthe old one before arming a new one. - Reactions surface as
(reaction 👍)and media as[imageMessage]; when you need the image URL / caption / voice file, pull the full record withget-messages.ts --json.
When to Use
- Send notifications or alerts
- Share images/posters
- Voice messages
- Document sharing
- Automated reminders
- Workshop follow-ups
- Retrieve and transcribe voice messages from customers
- Check media attachments from contacts