whatsapp-web

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

whatsapp-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.py
This 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
/tmp/whatsapp-web/chrome_profile/
, so no re-scan is needed after the first login.
需要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
undefined
bash
undefined

Single 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 --sync
Output:
{"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
--sync
only if the user confirms syncing.
bash
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必须先向用户询问名字、姓氏(可选)以及是否要将联系人同步到手机,再执行此脚本。**仅当用户确认同步时才传递
--sync
参数。

Create 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"

Output:

```json
{
  "status": "created",
  "name": "LT Team",
  "requested_members": ["Ezra", "Adit", "Rani"],
  "added": ["Ezra", "Adit"],
  "failed": ["Rani"]
}
failed
lists members whose name/number didn't match a suggestion and were skipped. The group is still created as long as at least one member is added.
Agent 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
--members
flags.
python3 scripts/create_group.py --name "LT Team"
--members "Ezra,081234567890" --members "Adit"

输出:

```json
{
  "status": "created",
  "name": "LT Team",
  "requested_members": ["Ezra", "Adit", "Rani"],
  "added": ["Ezra", "Adit"],
  "failed": ["Rani"]
}
failed
列表包含那些姓名/号码无法匹配到联系人建议而被跳过的成员。只要至少添加了一名成员,群组仍会被创建。
**调用Agent必须先向用户询问群组名称和成员。**成员可以有多个——接受逗号分隔的输入,如果用户还有其他成员要添加可重复询问。将所有成员合并到一个或多个
--members
参数中。

Exit (leave) a group

退出群组

bash
python3 scripts/exit_group.py --name "LT Team" --confirm
Output:
{"status": "exited", "name": "LT Team", "exited": true, "already": false}
If the menu has no Exit option (already left), returns
{"status": "noop", "exited": false, "already": true}
. The group stays visible in your chat list as read-only — use
delete_chat.py
afterwards to hide it.
--confirm
required.
Leaving is reversible only if an admin re-invites you.
bash
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
隐藏它。
**必须携带
--confirm
参数。**退出后只有管理员重新邀请才能恢复权限。

Delete a chat

删除聊天

bash
python3 scripts/delete_chat.py --to "Ezra" --confirm
python3 scripts/delete_chat.py --to 081234567890 --confirm
Output:
{"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
exit_group.py
first, or
delete_group.py
for the full teardown.
--confirm
required.
bash
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.py
,或使用
delete_group.py
完成彻底移除。
必须携带
--confirm
参数。

Delete a group (kick all → exit → delete)

删除群组(踢出所有成员→退出→删除)

bash
python3 scripts/delete_group.py --name "LT Marketing Team" --confirm
Output:
json
{
  "status": "deleted",
  "name": "LT Marketing Team",
  "kicked": ["Adit", "Rani"],
  "skipped": [],
  "exited": true,
  "deleted": true
}
status
values:
  • deleted
    — kicked all kickable members, exited the group, removed it from the chat list.
  • exited
    — exit succeeded but delete didn't finalize (you can still remove it from the sidebar manually).
  • partial
    — something stopped before exit.
skipped
lists members whose Remove action didn't surface — usually means the caller isn't a group admin, so those members remain in the group.
DESTRUCTIVE. The script refuses to run without
--confirm
. Agent must ask the user for explicit confirmation before passing
--confirm
.
bash
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
列表包含那些无法执行移除操作的成员——通常是因为调用者不是群组管理员,这些成员会留在群组中。
**此操作具有破坏性。**不带
--confirm
参数时脚本会拒绝运行。调用Agent必须先获得用户的明确确认,再传递
--confirm
参数。

Pin / 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 081234567890
Output examples:
  • {"status": "pinned", "action": "pin", "name_or_number": "Ezra", "already": false}
  • {"status": "noop", "action": "pin", "name_or_number": "Ezra", "already": true}
    (already pinned)
  • {"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
status
stays
unpinned
(tell the user to unpin something first).
bash
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弹出的提示框——该聊天仍保持未置顶状态,
status
unpinned
(请告知用户先取消某个聊天的置顶)。

Send 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 20
Output:
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
is
"in"
(received) or
"out"
(sent by the logged-in user).
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
undefined
bash
undefined

Last 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"
  }
}
message
is
null
if no matching message is visible (e.g. asking for an incoming message in a chat the user has only sent to).
python3 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"
  }
}
如果没有匹配的可见消息(例如在一个只有登录用户发送消息的聊天中查询收到的消息),
message
会为
null

List 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 previews
Output:
{"total_in_sidebar": 188, "returned": 50, "chats": [{"name": "...", "preview": "...", "pinned": false}, ...]}
total_in_sidebar
is the full chat count WhatsApp reports (all archived + active),
returned
is how many entries the script actually collected.
bash
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_sidebar
是WhatsApp报告的聊天总数(包含所有归档和活跃聊天),
returned
是脚本实际收集到的条目数量。

List pinned chats

列出置顶聊天

bash
python3 scripts/list_pinned.py
Output:
{"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 totals
Output:
json
{
  "chat_count": 3,
  "message_count": 46,
  "chats": [
    {"name": "LT Marketing Team", "unread_count": 33, "unread": true, "pinned": false, ...}
  ]
}
chat_count
= number of chats with unread messages;
message_count
= sum of per-chat unread counts. Only chats whose rows are scanned are counted — raise
--limit
if you have many chats and want to look deeper.
bash
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_count
表示有未读消息的聊天数量;
message_count
表示所有聊天的未读消息总数。仅统计被扫描到的聊天——如果聊天数量较多,可提高
--limit
参数值来扫描更多内容。

Open WhatsApp Web / check login state

打开WhatsApp Web/检查登录状态

bash
undefined
bash
undefined

Default: 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:
    0
    = success,
    1
    = login required / login error / wait timeout,
    2
    = contact not found,
    3
    = destructive script missing
    --confirm
  • Destructive scripts (
    delete_group.py
    ,
    exit_group.py
    ,
    delete_chat.py
    ) refuse to run without
    --confirm
    (exit code 3). Agent MUST ask the user to confirm first before invoking them
  • Run
    python3 scripts/<name>.py --help
    for usage
  • Scripts use PEP 723 inline dependencies — run with
    uv run
    or install Playwright manually
  • 所有脚本将JSON输出至标准输出,诊断信息输出至标准错误输出
  • 退出码:
    0
    表示成功,
    1
    表示需要登录/登录错误/等待超时,
    2
    表示未找到联系人,
    3
    表示破坏性脚本缺少
    --confirm
    参数
  • 破坏性脚本(
    delete_group.py
    exit_group.py
    delete_chat.py
    )不带
    --confirm
    参数时会拒绝运行(退出码为3)。Agent必须先向用户确认,再调用这些脚本
  • 运行
    python3 scripts/<name>.py --help
    查看使用说明
  • 脚本使用PEP 723内联依赖——可使用
    uv run
    运行,或手动安装Playwright

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变化
  • 号码验证会检查多种电话号码格式变体以确保准确性