whatsapp-web
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesewhatsapp-web
WhatsApp Web自动化
WhatsApp Web automation via Playwright + Chrome CDP. Scripts output JSON to stdout.
基于Playwright + Chrome CDP实现WhatsApp Web自动化。脚本将JSON输出至标准输出。
Setup
环境配置
Requires Python 3.10+, Google Chrome, and Playwright.
First-time login — scan QR code once:
bash
python3 scripts/login.pyThis opens Chrome, navigates to web.whatsapp.com, reports the current login state, and exits immediately so the calling agent stays responsive. If the user still needs to scan the QR code, tell them to scan it from their phone and re-run the task once signed in. Chrome profile persists in , so no re-scan is needed after the first login.
/tmp/whatsapp-web/chrome_profile/需要Python 3.10+、Google Chrome和Playwright。
首次登录——只需扫描一次二维码:
bash
python3 scripts/login.py此命令会打开Chrome浏览器,导航至web.whatsapp.com,报告当前登录状态,然后立即退出,以便调用的Agent保持响应。如果用户仍需扫描二维码,请告知用户用手机扫描,并在登录后重新运行任务。Chrome配置文件会持久化存储在中,因此首次登录后无需再次扫描二维码。
/tmp/whatsapp-web/chrome_profile/Available Scripts
可用脚本
Check if number(s) are on WhatsApp
检查号码是否已注册WhatsApp
bash
undefinedbash
undefinedSingle number
单个号码
python3 scripts/check_number.py --phone 081234567890
python3 scripts/check_number.py --phone 081234567890
Multiple numbers (comma-separated)
多个号码(逗号分隔)
python3 scripts/check_number.py --phones 08111,08222,08333
Output: `{"081234567890": true}`python3 scripts/check_number.py --phones 08111,08222,08333
输出:`{"081234567890": true}`Add a new contact
添加新联系人
bash
python3 scripts/add_contact.py --phone 081234567890 --first-name Ezra
python3 scripts/add_contact.py --phone 081234567890 --first-name Ezra \
--last-name Wijaya --syncOutput:
{"status": "saved", "first_name": "Ezra", "last_name": "Wijaya", "phone": "081234567890", "sync_to_phone": true}Agent must ask the user for First Name, Last Name (optional), and whether to sync the contact to the phone before invoking this script. Pass only if the user confirms syncing.
--syncbash
python3 scripts/add_contact.py --phone 081234567890 --first-name Ezra
python3 scripts/add_contact.py --phone 081234567890 --first-name Ezra \
--last-name Wijaya --sync输出:
{"status": "saved", "first_name": "Ezra", "last_name": "Wijaya", "phone": "081234567890", "sync_to_phone": true}**调用Agent必须先向用户询问名字、姓氏(可选)以及是否要将联系人同步到手机,再执行此脚本。**仅当用户确认同步时才传递参数。
--syncCreate a group
创建群组
bash
python3 scripts/create_group.py --name "LT Team" --members "Ezra,Adit,Rani"bash
python3 scripts/create_group.py --name "LT Team" --members "Ezra,Adit,Rani"Members can be repeated — useful when the user provides them in batches
成员参数可重复使用——适用于用户分批提供成员的场景
python3 scripts/create_group.py --name "LT Team"
--members "Ezra,081234567890" --members "Adit"
--members "Ezra,081234567890" --members "Adit"
Output:
```json
{
"status": "created",
"name": "LT Team",
"requested_members": ["Ezra", "Adit", "Rani"],
"added": ["Ezra", "Adit"],
"failed": ["Rani"]
}failedAgent must ask the user for both the group name and the members. Members can be many — accept comma-separated input and repeat the prompt if the user has more to add. Combine everything into one or multiple flags.
--memberspython3 scripts/create_group.py --name "LT Team"
--members "Ezra,081234567890" --members "Adit"
--members "Ezra,081234567890" --members "Adit"
输出:
```json
{
"status": "created",
"name": "LT Team",
"requested_members": ["Ezra", "Adit", "Rani"],
"added": ["Ezra", "Adit"],
"failed": ["Rani"]
}failed**调用Agent必须先向用户询问群组名称和成员。**成员可以有多个——接受逗号分隔的输入,如果用户还有其他成员要添加可重复询问。将所有成员合并到一个或多个参数中。
--membersExit (leave) a group
退出群组
bash
python3 scripts/exit_group.py --name "LT Team" --confirmOutput:
{"status": "exited", "name": "LT Team", "exited": true, "already": false}If the menu has no Exit option (already left), returns . The group stays visible in your chat list as read-only — use afterwards to hide it.
{"status": "noop", "exited": false, "already": true}delete_chat.py--confirmbash
python3 scripts/exit_group.py --name "LT Team" --confirm输出:
{"status": "exited", "name": "LT Team", "exited": true, "already": false}如果菜单中没有退出选项(已退出),则返回。群组仍会以只读状态显示在聊天列表中——之后可使用隐藏它。
{"status": "noop", "exited": false, "already": true}delete_chat.py**必须携带参数。**退出后只有管理员重新邀请才能恢复权限。
--confirmDelete a chat
删除聊天
bash
python3 scripts/delete_chat.py --to "Ezra" --confirm
python3 scripts/delete_chat.py --to 081234567890 --confirmOutput:
{"status": "deleted", "name_or_number": "Ezra", "deleted": true}Removes the chat from YOUR sidebar and clears your copy of the history. The other party still sees the conversation. Not reversible.
For active groups WA won't offer "Delete chat" — use first, or for the full teardown.
exit_group.pydelete_group.py--confirmbash
python3 scripts/delete_chat.py --to "Ezra" --confirm
python3 scripts/delete_chat.py --to 081234567890 --confirm输出:
{"status": "deleted", "name_or_number": "Ezra", "deleted": true}此操作会将聊天从你的侧边栏移除,并清除本地聊天记录。对方仍能看到对话内容,且操作不可撤销。
对于活跃群组,WhatsApp不会提供“删除聊天”选项——请先使用,或使用完成彻底移除。
exit_group.pydelete_group.py必须携带参数。
--confirmDelete a group (kick all → exit → delete)
删除群组(踢出所有成员→退出→删除)
bash
python3 scripts/delete_group.py --name "LT Marketing Team" --confirmOutput:
json
{
"status": "deleted",
"name": "LT Marketing Team",
"kicked": ["Adit", "Rani"],
"skipped": [],
"exited": true,
"deleted": true
}status- — kicked all kickable members, exited the group, removed it from the chat list.
deleted - — exit succeeded but delete didn't finalize (you can still remove it from the sidebar manually).
exited - — something stopped before exit.
partial
skippedDESTRUCTIVE. The script refuses to run without . Agent must ask the user for explicit confirmation before passing .
--confirm--confirmbash
python3 scripts/delete_group.py --name "LT Marketing Team" --confirm输出:
json
{
"status": "deleted",
"name": "LT Marketing Team",
"kicked": ["Adit", "Rani"],
"skipped": [],
"exited": true,
"deleted": true
}status- ——已踢出所有可踢出的成员,退出群组并将其从聊天列表中移除。
deleted - ——退出成功但删除未完成(你仍可手动从侧边栏移除它)。
exited - ——在退出前出现错误。
partial
skipped**此操作具有破坏性。**不带参数时脚本会拒绝运行。调用Agent必须先获得用户的明确确认,再传递参数。
--confirm--confirmPin / unpin a chat
置顶/取消置顶聊天
bash
python3 scripts/pin_chat.py --to "Ezra"
python3 scripts/pin_chat.py --to "Ezra" --unpin
python3 scripts/pin_chat.py --to 081234567890Output examples:
{"status": "pinned", "action": "pin", "name_or_number": "Ezra", "already": false}- (already pinned)
{"status": "noop", "action": "pin", "name_or_number": "Ezra", "already": true} {"status": "unpinned", "action": "unpin", ...}
WhatsApp Web allows at most 3 pinned chats. If pinning a 4th, WA shows a modal the script auto-dismisses — the chat stays unpinned and stays (tell the user to unpin something first).
statusunpinnedbash
python3 scripts/pin_chat.py --to "Ezra"
python3 scripts/pin_chat.py --to "Ezra" --unpin
python3 scripts/pin_chat.py --to 081234567890输出示例:
{"status": "pinned", "action": "pin", "name_or_number": "Ezra", "already": false}- (已置顶)
{"status": "noop", "action": "pin", "name_or_number": "Ezra", "already": true} {"status": "unpinned", "action": "unpin", ...}
WhatsApp Web最多允许3个置顶聊天。如果尝试置顶第4个,脚本会自动关闭WhatsApp弹出的提示框——该聊天仍保持未置顶状态,为(请告知用户先取消某个聊天的置顶)。
statusunpinnedSend a message
发送消息
bash
python3 scripts/send_message.py --to "Ezra" --message "Hello!"
python3 scripts/send_message.py --to 081234567890 --message "Hi there"Output:
{"status": "sent", "to": "Ezra"}bash
python3 scripts/send_message.py --to "Ezra" --message "Hello!"
python3 scripts/send_message.py --to 081234567890 --message "Hi there"输出:
{"status": "sent", "to": "Ezra"}Read recent messages from a chat
读取聊天中的最近消息
bash
python3 scripts/read_messages.py --from "Ezra"
python3 scripts/read_messages.py --from 081234567890 --count 20Output:
json
{
"from": "Ezra",
"count": 10,
"messages": [
{"direction": "in", "sender": "Ezra", "time": "08.42", "date": "17/04/2026", "text": "..."},
{"direction": "out", "sender": "Me", "time": "08.43", "date": "17/04/2026", "text": "..."}
]
}direction"in""out"bash
python3 scripts/read_messages.py --from "Ezra"
python3 scripts/read_messages.py --from 081234567890 --count 20输出:
json
{
"from": "Ezra",
"count": 10,
"messages": [
{"direction": "in", "sender": "Ezra", "time": "08.42", "date": "17/04/2026", "text": "..."},
{"direction": "out", "sender": "Me", "time": "08.43", "date": "17/04/2026", "text": "..."}
]
}direction"in""out"Last reply from a contact
获取联系人的最后一条回复
bash
undefinedbash
undefinedLast incoming message (what the contact said) — maps to "X bales apa"
最后一条收到的消息(联系人发送的内容)——对应"X bales apa"这类请求
python3 scripts/last_reply.py --from "Ezra"
python3 scripts/last_reply.py --from "Ezra"
Last message regardless of sender — maps to "apa chat terakhir X"
不区分发送方的最后一条消息——对应"apa chat terakhir X"这类请求
python3 scripts/last_reply.py --from "Ezra" --any-direction
Output:
```json
{
"from": "Ezra",
"mode": "incoming",
"message": {
"direction": "in",
"sender": "Ezra",
"time": "08.42",
"date": "17/04/2026",
"text": "oke siap"
}
}messagenullpython3 scripts/last_reply.py --from "Ezra" --any-direction
输出:
```json
{
"from": "Ezra",
"mode": "incoming",
"message": {
"direction": "in",
"sender": "Ezra",
"time": "08.42",
"date": "17/04/2026",
"text": "oke siap"
}
}如果没有匹配的可见消息(例如在一个只有登录用户发送消息的聊天中查询收到的消息),会为。
messagenullList chats in the sidebar
列出侧边栏中的聊天
bash
python3 scripts/list_chats.py # top 50 chats
python3 scripts/list_chats.py --limit 20 # top 20
python3 scripts/list_chats.py --names-only # drop previewsOutput:
{"total_in_sidebar": 188, "returned": 50, "chats": [{"name": "...", "preview": "...", "pinned": false}, ...]}total_in_sidebarreturnedbash
python3 scripts/list_chats.py # 前50条聊天
python3 scripts/list_chats.py --limit 20 # 前20条聊天
python3 scripts/list_chats.py --names-only # 仅返回名称,不包含预览内容输出:
{"total_in_sidebar": 188, "returned": 50, "chats": [{"name": "...", "preview": "...", "pinned": false}, ...]}total_in_sidebarreturnedList pinned chats
列出置顶聊天
bash
python3 scripts/list_pinned.pyOutput:
{"count": 2, "chats": [{"name": "...", "preview": "...", "pinned": true}, ...]}WhatsApp Web allows at most 3 pinned chats.
bash
python3 scripts/list_pinned.py输出:
{"count": 2, "chats": [{"name": "...", "preview": "...", "pinned": true}, ...]}WhatsApp Web最多允许3个置顶聊天。
List unread chats / count unread messages
列出未读聊天/统计未读消息数量
bash
python3 scripts/list_unread.py # scan top 50 rows
python3 scripts/list_unread.py --limit 100 # scan deeper
python3 scripts/list_unread.py --count-only # just the totalsOutput:
json
{
"chat_count": 3,
"message_count": 46,
"chats": [
{"name": "LT Marketing Team", "unread_count": 33, "unread": true, "pinned": false, ...}
]
}chat_countmessage_count--limitbash
python3 scripts/list_unread.py # 扫描前50行
python3 scripts/list_unread.py --limit 100 # 扫描更多行
python3 scripts/list_unread.py --count-only # 仅返回统计总数输出:
json
{
"chat_count": 3,
"message_count": 46,
"chats": [
{"name": "LT Marketing Team", "unread_count": 33, "unread": true, "pinned": false, ...}
]
}chat_countmessage_count--limitOpen WhatsApp Web / check login state
打开WhatsApp Web/检查登录状态
bash
undefinedbash
undefinedDefault: open WA Web, report state, exit immediately (non-blocking)
默认模式:打开WhatsApp Web,报告状态,立即退出(非阻塞)
python3 scripts/login.py
python3 scripts/login.py
Block until the user signs in (only when explicitly requested)
阻塞直到用户登录(仅在用户明确要求时使用)
python3 scripts/login.py --wait
python3 scripts/login.py --wait --timeout 120
Default mode exits right after opening the window — agents MUST NOT use `--wait` unless the user explicitly asks to wait for login, otherwise the agent will appear to hang while the user scans the QR code.
Output examples:
- `{"state": "logged_in"}`
- `{"state": "qr_code", "action": "Scan the QR code with your phone", "message": "..."}`
- `{"state": "loading", "message": "..."}`
- `{"state": "timeout", "error": "..."}` (only with `--wait`)
- `{"state": "error", "error": "..."}` — Chrome / CDP / navigation failed
`login.py` never crashes with a traceback: Chrome-launch, CDP-connect, and navigation failures are reported as `{"state": "error", ...}` with exit code 1, same as a `--wait` timeout.python3 scripts/login.py --wait
python3 scripts/login.py --wait --timeout 120
默认模式会在打开窗口后立即退出——**Agent绝不能使用`--wait`参数,除非用户明确要求等待登录,否则在用户扫描二维码期间Agent会看似无响应**。
输出示例:
- `{"state": "logged_in"}`
- `{"state": "qr_code", "action": "Scan the QR code with your phone", "message": "..."}`
- `{"state": "loading", "message": "..."}`
- `{"state": "timeout", "error": "..."}`(仅在使用`--wait`时出现)
- `{"state": "error", "error": "..."}`——Chrome启动、CDP连接或导航失败
`login.py`绝不会因回溯而崩溃:Chrome启动、CDP连接和导航失败都会以`{"state": "error", ...}`的形式报告,退出码为1,与`--wait`超时的情况相同。Script conventions
脚本约定
- All scripts output JSON to stdout, diagnostics to stderr
- Exit codes: = success,
0= login required / login error / wait timeout,1= contact not found,2= destructive script missing3--confirm - Destructive scripts (,
delete_group.py,exit_group.py) refuse to run withoutdelete_chat.py(exit code 3). Agent MUST ask the user to confirm first before invoking them--confirm - Run for usage
python3 scripts/<name>.py --help - Scripts use PEP 723 inline dependencies — run with or install Playwright manually
uv run
- 所有脚本将JSON输出至标准输出,诊断信息输出至标准错误输出
- 退出码:表示成功,
0表示需要登录/登录错误/等待超时,1表示未找到联系人,2表示破坏性脚本缺少3参数--confirm - 破坏性脚本(、
delete_group.py、exit_group.py)不带delete_chat.py参数时会拒绝运行(退出码为3)。Agent必须先向用户确认,再调用这些脚本--confirm - 运行查看使用说明
python3 scripts/<name>.py --help - 脚本使用PEP 723内联依赖——可使用运行,或手动安装Playwright
uv run
Important notes
重要说明
- Chrome persists across runs — never killed by the skill
- Anti-ban delay (default 3s) between operations
- Phone formatting defaults to Indonesian numbers (+62)
- All interactions are keyboard/text-based (no CSS selectors) for resilience against WA Web DOM changes
- Number verification checks multiple phone format variants for accuracy
- Chrome会在多次运行间保持持久化——不会被本工具关闭
- 操作之间默认有3秒的防封禁延迟
- 电话号码格式默认采用印尼号码(+62)
- 所有交互基于键盘/文本(不使用CSS选择器),以应对WhatsApp Web的DOM变化
- 号码验证会检查多种电话号码格式变体以确保准确性