wazap-mcp 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/auth-state.js +116 -0
- package/dist/banner.js +7 -0
- package/dist/cli.js +372 -0
- package/dist/config.js +138 -0
- package/dist/connect.js +222 -0
- package/dist/doctor.js +114 -0
- package/dist/errors.js +48 -0
- package/dist/ids.js +50 -0
- package/dist/index.js +88 -0
- package/dist/lock.js +42 -0
- package/dist/logger.js +16 -0
- package/dist/messages.js +281 -0
- package/dist/ratelimit.js +35 -0
- package/dist/server.js +133 -0
- package/dist/settings.js +74 -0
- package/dist/tools.js +591 -0
- package/dist/wa-types.js +2 -0
- package/dist/whatsapp.js +1514 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Răzvan Girgiz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
```
|
|
2
|
+
██╗ ██╗ █████╗ ███████╗ █████╗ ██████╗
|
|
3
|
+
██║ ██║██╔══██╗╚══███╔╝██╔══██╗██╔══██╗
|
|
4
|
+
██║ █╗ ██║███████║ ███╔╝ ███████║██████╔╝
|
|
5
|
+
██║███╗██║██╔══██║ ███╔╝ ██╔══██║██╔═══╝
|
|
6
|
+
╚███╔███╔╝██║ ██║███████╗██║ ██║██║
|
|
7
|
+
╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
**WhatsApp for your AI agent.** An MCP server that puts your WhatsApp account —
|
|
11
|
+
chats, messages, media, contacts, groups — behind 22 tools any MCP client can
|
|
12
|
+
call. Pairing-code login, no browser, no phone-number reseller, ~20 MB of RAM.
|
|
13
|
+
|
|
14
|
+
Built on [Baileys](https://github.com/WhiskeySockets/Baileys), which speaks the
|
|
15
|
+
WhatsApp multi-device protocol over a WebSocket.
|
|
16
|
+
|
|
17
|
+
## Get started
|
|
18
|
+
|
|
19
|
+
The npm package is `wazap-mcp`; the command it installs is `wazap`.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx wazap-mcp login # link your account with a pairing code
|
|
23
|
+
npx wazap-mcp connect claude-code # write the MCP entry for your agent
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then ask your agent: *"what did I miss on WhatsApp today?"*
|
|
27
|
+
|
|
28
|
+
`login` asks for your number in international format, prints an 8-character
|
|
29
|
+
code, and you enter it on your phone under **Settings → Linked devices → Link a
|
|
30
|
+
device → Link with phone number instead**. Prefer a QR code? `npx wazap-mcp login --qr`.
|
|
31
|
+
It ends by asking whether the agent may send messages; the answer is no unless
|
|
32
|
+
you say yes, and `npx wazap-mcp config writes on` changes it later.
|
|
33
|
+
|
|
34
|
+
`npx wazap-mcp` on its own is safe to run: it prints where you stand and what to do
|
|
35
|
+
next, and starts no server. When something is off, `npx wazap-mcp status` is the
|
|
36
|
+
first thing to run — it checks Node, the data directory, the lock, the
|
|
37
|
+
credentials and whether a newer version is out, and prints the fix next to
|
|
38
|
+
anything broken.
|
|
39
|
+
|
|
40
|
+
### Connect a client
|
|
41
|
+
|
|
42
|
+
`wazap connect <client>` writes the entry for you, keeping whatever else is in
|
|
43
|
+
the file and backing it up once before the first change. `--dry-run` shows what
|
|
44
|
+
it would write.
|
|
45
|
+
|
|
46
|
+
| Client | What `connect` writes |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `claude-code` | runs `claude mcp add whatsapp` for you |
|
|
49
|
+
| `claude-desktop` | `claude_desktop_config.json` in the Claude application directory |
|
|
50
|
+
| `cursor` | `~/.cursor/mcp.json` |
|
|
51
|
+
| `codex` | `[mcp_servers.whatsapp]` in `~/.codex/config.toml` |
|
|
52
|
+
| `vscode` | `./.vscode/mcp.json`, for the current workspace |
|
|
53
|
+
| `gemini` | `~/.gemini/settings.json` |
|
|
54
|
+
| anything remote | client's MCP URL field: `https://your-host/mcp` with header `Authorization: Bearer <token>` (see [Self-host](#self-host)) |
|
|
55
|
+
|
|
56
|
+
Any MCP client works the same way: the command is `npx -y wazap-mcp`, the transport
|
|
57
|
+
is stdio. Tell the agent to call `learn` first — it returns the id formats, the
|
|
58
|
+
workflows and every error code with what to do about it.
|
|
59
|
+
|
|
60
|
+
<details>
|
|
61
|
+
<summary>The raw entries, for editing by hand</summary>
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"whatsapp": {
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": ["-y", "wazap-mcp"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Claude Desktop, Cursor and Gemini CLI take exactly that. VS Code nests it under
|
|
75
|
+
`servers` and wants a `"type": "stdio"` alongside `command`. Codex CLI is TOML:
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
[mcp_servers.whatsapp]
|
|
79
|
+
command = "npx"
|
|
80
|
+
args = ["-y", "wazap-mcp"]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
</details>
|
|
84
|
+
|
|
85
|
+
The `skills/` folder follows the [Agent Skills](https://agentskills.io) format, so Codex, Cursor and other skill-aware agents can load the same five skills.
|
|
86
|
+
|
|
87
|
+
## Tools
|
|
88
|
+
|
|
89
|
+
| Tool | Kind | What it does |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `learn` | read | The guide to every tool, id format and error code. Call it first. |
|
|
92
|
+
| `get_status` | read | Connection status, sync state, linked account, versions, data dir. |
|
|
93
|
+
| `list_chats` | read | Conversations newest-first; filter `all`/`unread`/`groups`/`individual`/`archived`. |
|
|
94
|
+
| `read_messages` | read | Messages in a chat; `before` pages further back, pulling older history from the phone. |
|
|
95
|
+
| `get_recent_messages` | read | Everything from the last N hours, grouped by chat. The catch-up tool. |
|
|
96
|
+
| `search_messages` | read | Text search across the locally held messages. |
|
|
97
|
+
| `get_message` | read | One message in full, with its quoted message and reactions. |
|
|
98
|
+
| `search_contacts` | read | Find contacts by name or number. |
|
|
99
|
+
| `get_contact` | read | Name, number, about text, profile picture. |
|
|
100
|
+
| `get_group_info` | read | Participants, admins, announcement mode, invite link (when you are admin). |
|
|
101
|
+
| `download_media` | read | Save an attachment to disk; small images also come back inline. |
|
|
102
|
+
| `send_message` | write | Send text, optionally as a reply, with @-mentions. |
|
|
103
|
+
| `send_media` | write | Send an image, video, audio, voice note or document from a path or URL. |
|
|
104
|
+
| `send_poll` | write | Send a poll with 2–12 options. |
|
|
105
|
+
| `send_location` | write | Send a map pin. |
|
|
106
|
+
| `edit_message` | write | Edit your own message, within WhatsApp's 15-minute window. |
|
|
107
|
+
| `react_to_message` | write | Add or remove an emoji reaction. |
|
|
108
|
+
| `forward_message` | write | Forward a message to another chat. |
|
|
109
|
+
| `delete_message` | write | Retract your own message, within WhatsApp's 2-day window. |
|
|
110
|
+
| `manage_chat` | write | Archive, pin, mute (8h by default), mark read/unread. |
|
|
111
|
+
| `create_group` | write | Create a group and add participants. |
|
|
112
|
+
| `manage_group` | write | Add, remove, promote, demote, leave, rename, invite links. |
|
|
113
|
+
|
|
114
|
+
Every message comes back with a non-empty `text`: media and system messages
|
|
115
|
+
carry a placeholder such as `[image] caption`, `[voice message]`, `[deleted]` or
|
|
116
|
+
`[poll] Pizza or pasta?`. Timestamps are ISO 8601 with the machine's UTC offset,
|
|
117
|
+
alongside a human `age` like `2h ago`.
|
|
118
|
+
|
|
119
|
+
## Skills
|
|
120
|
+
|
|
121
|
+
wazap ships five [Agent Skills](https://agentskills.io) that teach an agent the workflows behind the tools, not just the tools:
|
|
122
|
+
|
|
123
|
+
| Skill | What the agent does |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| `wazap-setup` | Diagnose with `wazap status`, link by pairing code, connect a client with `wazap connect`, repair an expired session |
|
|
126
|
+
| `whatsapp-inbox` | "What did I miss?" Triage into *needs you / FYI / noise*, ranked, plus forgotten replies. Read-only |
|
|
127
|
+
| `whatsapp-recall` | "Find the invoice Dan sent." Search with query variants, page back in time, download and read the file. Read-only |
|
|
128
|
+
| `whatsapp-groups` | Catch up on a 300-message group: decisions, dates, what is asked of you. Read-only |
|
|
129
|
+
| `whatsapp-send` | Draft in the chat's own register, show recipient and text, send only after the user says yes |
|
|
130
|
+
|
|
131
|
+
Install everything (server and skills) as a Claude Code plugin:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
/plugin marketplace add razvangirgiz/wazap
|
|
135
|
+
/plugin install wazap@wazap
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or copy `skills/<name>/` into any skills directory your agent reads.
|
|
139
|
+
|
|
140
|
+
## Errors
|
|
141
|
+
|
|
142
|
+
Every failure is a structured `{ error, message, fix }` rather than a stack
|
|
143
|
+
trace, so an agent can decide whether to retry, ask the user, or stop.
|
|
144
|
+
|
|
145
|
+
| Code | Meaning |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `NOT_LINKED` | No account linked. Run `npx wazap-mcp login`. |
|
|
148
|
+
| `SESSION_EXPIRED` | Unlinked from the phone. Run `npx wazap-mcp login`. |
|
|
149
|
+
| `SESSION_CORRUPT` | Credentials unreadable. Run `npx wazap-mcp logout` then `login`. |
|
|
150
|
+
| `NOT_CONNECTED` | Still connecting or reconnecting. |
|
|
151
|
+
| `SYNC_IN_PROGRESS` | History sync has not finished; results may be partial. |
|
|
152
|
+
| `INVALID_PHONE` | Number is not in international format. |
|
|
153
|
+
| `INVALID_ID` | Not a WhatsApp chat, contact or group id. |
|
|
154
|
+
| `NOT_ON_WHATSAPP` | That number has no WhatsApp account. |
|
|
155
|
+
| `CHAT_NOT_FOUND` / `MESSAGE_NOT_FOUND` / `CONTACT_NOT_FOUND` / `GROUP_NOT_FOUND` | Unknown id. |
|
|
156
|
+
| `NOT_A_PARTICIPANT` / `NOT_ADMIN` / `GROUP_ANNOUNCEMENT_ONLY` | Group permissions. |
|
|
157
|
+
| `MEDIA_UNAVAILABLE` | WhatsApp expired the file, or it was never synced here. |
|
|
158
|
+
| `FILE_NOT_FOUND` / `FILE_TOO_LARGE` / `URL_FETCH_FAILED` | Outbound media problems. |
|
|
159
|
+
| `TEXT_TOO_LONG` | Over WhatsApp's message limit. |
|
|
160
|
+
| `EDIT_WINDOW_EXPIRED` / `RETRACT_WINDOW_EXPIRED` / `NOT_OWN_MESSAGE` | WhatsApp's own limits on editing and deleting. |
|
|
161
|
+
| `READ_ONLY` | wazap is running read-only. |
|
|
162
|
+
| `RATE_LIMITED` | Too many writes; `fix` says how long to wait. |
|
|
163
|
+
| `TIMEOUT` / `WHATSAPP_ERROR` | WhatsApp did not answer, or rejected the operation. |
|
|
164
|
+
|
|
165
|
+
## Data directory
|
|
166
|
+
|
|
167
|
+
Everything lives in `~/.wazap` (override with `--data-dir` or `WAZAP_DATA_DIR`),
|
|
168
|
+
created `0700` with credentials written `0600`:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
~/.wazap/
|
|
172
|
+
auth/ WhatsApp credentials — treat this like a password
|
|
173
|
+
media/ downloads from download_media
|
|
174
|
+
history/ per-chat message history, so a restart is not amnesia
|
|
175
|
+
store.json chat-list snapshot
|
|
176
|
+
server.lock pid of the running server
|
|
177
|
+
.env optional settings, see .env.example
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Credential writes go to a temp file and are renamed into place, so killing the
|
|
181
|
+
process mid-write cannot leave you re-linking your phone.
|
|
182
|
+
|
|
183
|
+
One server per data directory: a second `wazap serve` on the same directory
|
|
184
|
+
exits with code 2 and tells you the pid of the one already running.
|
|
185
|
+
|
|
186
|
+
## Read-only mode
|
|
187
|
+
|
|
188
|
+
Writes are opt-in. `login` asks once and stores the answer in
|
|
189
|
+
`<data-dir>/.env`; `wazap config writes on|off` changes it, and `wazap config`
|
|
190
|
+
alone prints every effective setting with where it came from.
|
|
191
|
+
|
|
192
|
+
`WAZAP_READ_ONLY=1` or `wazap serve --read-only` does not register the write
|
|
193
|
+
tools at all. The agent never sees them, so it cannot message anyone from your
|
|
194
|
+
number even by mistake — useful when the linked account is your personal one.
|
|
195
|
+
|
|
196
|
+
Writes are also rate limited to `WAZAP_RATE_LIMIT` per minute (default 20, `0`
|
|
197
|
+
disables). Sending faster than a human is how accounts get banned.
|
|
198
|
+
|
|
199
|
+
## HTTP mode
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
WAZAP_READ_TOKEN=$(openssl rand -hex 32) \
|
|
203
|
+
WAZAP_WRITE_TOKEN=$(openssl rand -hex 32) \
|
|
204
|
+
npx wazap-mcp serve --http --host 0.0.0.0 --port 8766
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Streamable HTTP at `/mcp`, with a health check at `/healthz`. Two bearer tokens:
|
|
208
|
+
the read token gets the read tools, the write token also unlocks the write
|
|
209
|
+
tools, so a leaked read token can never message anyone. wazap refuses to bind a
|
|
210
|
+
non-loopback address without a read token.
|
|
211
|
+
|
|
212
|
+
## Self-host
|
|
213
|
+
|
|
214
|
+
Run wazap on a server of your own when the agent is not on your laptop: another machine, a VPS, a client's infrastructure. The session stays on that server; nothing goes through a third party.
|
|
215
|
+
|
|
216
|
+
### With systemd
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
npm install -g wazap-mcp
|
|
220
|
+
sudo useradd --system --home /var/lib/wazap --create-home wazap
|
|
221
|
+
sudo -u wazap WAZAP_DATA_DIR=/var/lib/wazap wazap login --phone +40722123456 # pairing code works over SSH
|
|
222
|
+
sudo -u wazap tee /var/lib/wazap/.env >/dev/null <<END
|
|
223
|
+
WAZAP_READ_TOKEN=$(openssl rand -hex 32)
|
|
224
|
+
WAZAP_WRITE_TOKEN=$(openssl rand -hex 32)
|
|
225
|
+
END
|
|
226
|
+
sudo curl -fsSL https://raw.githubusercontent.com/razvangirgiz/wazap/main/deploy/wazap.service -o /etc/systemd/system/wazap.service
|
|
227
|
+
sudo systemctl enable --now wazap
|
|
228
|
+
curl -s http://127.0.0.1:8766/healthz
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The unit binds loopback only. Put TLS in front with the two-line [`deploy/Caddyfile`](deploy/Caddyfile) (`caddy run --config deploy/Caddyfile` after editing the hostname) or any reverse proxy, then point the client at `https://your-host/mcp` with `Authorization: Bearer <read or write token>`.
|
|
232
|
+
|
|
233
|
+
### With Docker
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
git clone https://github.com/razvangirgiz/wazap && cd wazap
|
|
237
|
+
printf 'WAZAP_READ_TOKEN=%s\nWAZAP_WRITE_TOKEN=%s\n' $(openssl rand -hex 32) $(openssl rand -hex 32) > .env
|
|
238
|
+
docker compose run --rm wazap login --phone +40722123456 # once; the session lands in the wazap-data volume
|
|
239
|
+
docker compose up -d
|
|
240
|
+
curl -s http://127.0.0.1:8766/healthz
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The container publishes `8766` on loopback only; add the same TLS proxy in front. Upgrading is `git pull && docker compose up -d --build`; the volume keeps the session.
|
|
244
|
+
|
|
245
|
+
### Which clients can reach it
|
|
246
|
+
|
|
247
|
+
Claude Code, Claude Desktop, Cursor, Codex, VS Code and any client with an "MCP URL + header" field connect with the bearer token. claude.ai Connectors require OAuth rather than a static token, so they cannot use a self-hosted wazap yet. Keep the read token in clients that only need to read; hand out the write token deliberately.
|
|
248
|
+
|
|
249
|
+
## Settings
|
|
250
|
+
|
|
251
|
+
| Variable | Default | Meaning |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| `WAZAP_DATA_DIR` | `~/.wazap` | Where everything is stored. |
|
|
254
|
+
| `WAZAP_READ_ONLY` | `0` | Do not register the write tools. |
|
|
255
|
+
| `WAZAP_SYNC_FULL_HISTORY` | `0` | Ask WhatsApp for a fuller history sync. |
|
|
256
|
+
| `WAZAP_PERSIST_HISTORY` | `1` | Keep chats and messages across restarts. |
|
|
257
|
+
| `WAZAP_RATE_LIMIT` | `20` | Write tool calls per minute; `0` disables. |
|
|
258
|
+
| `WAZAP_TRANSPORT` | `stdio` | `stdio` or `http`. |
|
|
259
|
+
| `WAZAP_HOST` / `WAZAP_PORT` | `127.0.0.1` / `8766` | HTTP bind address. |
|
|
260
|
+
| `WAZAP_READ_TOKEN` / `WAZAP_WRITE_TOKEN` | unset | HTTP bearer tokens. |
|
|
261
|
+
| `WAZAP_NO_UPDATE_CHECK` | `0` | `1` stops `status` asking npm for a newer version. |
|
|
262
|
+
|
|
263
|
+
Flags beat environment variables, which beat `<data-dir>/.env`.
|
|
264
|
+
|
|
265
|
+
## Known limitations
|
|
266
|
+
|
|
267
|
+
- **Unofficial.** Baileys reverse-engineers the WhatsApp multi-device protocol.
|
|
268
|
+
This is not the WhatsApp Business API and Meta does not support it.
|
|
269
|
+
- **Ban risk is real.** Automated sending, bulk messaging or anything a human
|
|
270
|
+
would not plausibly type can get the number banned, and that is not
|
|
271
|
+
recoverable from here. The rate limit helps; it is not a guarantee.
|
|
272
|
+
- **Media keys expire.** WhatsApp drops old attachments from its servers, so
|
|
273
|
+
`download_media` on an old message returns `MEDIA_UNAVAILABLE`.
|
|
274
|
+
- **History is what the phone syncs.** wazap sees the history WhatsApp hands the
|
|
275
|
+
linked device, not your full phone archive. `read_messages` with `before` asks
|
|
276
|
+
for more, within whatever WhatsApp still keeps.
|
|
277
|
+
- **`@lid` ids.** Newer accounts are addressed by a privacy id rather than a
|
|
278
|
+
phone number. wazap translates them back to phone numbers when it has learned
|
|
279
|
+
the mapping, and passes the `@lid` through when it has not.
|
|
280
|
+
- **Your phone must stay reachable.** A linked device stops receiving once the
|
|
281
|
+
phone has been offline long enough; `get_status` says so in `hint`.
|
|
282
|
+
|
|
283
|
+
## Development
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
npm install
|
|
287
|
+
npm run typecheck
|
|
288
|
+
npm test # builds, then runs node --test
|
|
289
|
+
node test/smoke-stdio.mjs # drives the built binary over MCP stdio
|
|
290
|
+
npm run dev -- status # run from source with tsx
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
`npm test` needs no WhatsApp session. The stdio smoke test spawns the built
|
|
294
|
+
binary against a throwaway data directory and checks that an unlinked install
|
|
295
|
+
still answers `initialize`, `tools/list` and `get_status`.
|
|
296
|
+
|
|
297
|
+
MIT licensed.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { BufferJSON, initAuthCreds, proto, } from "baileys";
|
|
5
|
+
import { RESET_FIX, WazapError } from "./errors.js";
|
|
6
|
+
const CREDS_FILE = "creds.json";
|
|
7
|
+
function fileFor(dir, name) {
|
|
8
|
+
return join(dir, name.replace(/\//g, "__").replace(/:/g, "-"));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Baileys' useMultiFileAuthState contract, with every write done as
|
|
12
|
+
* tmp-file-then-rename. The stock implementation truncates the target before
|
|
13
|
+
* writing, so a kill -9 in that window leaves unreadable creds and the account
|
|
14
|
+
* has to be re-linked.
|
|
15
|
+
*/
|
|
16
|
+
export async function useAtomicAuthState(dir) {
|
|
17
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
18
|
+
const queues = new Map();
|
|
19
|
+
const serialize = (path, task) => {
|
|
20
|
+
const next = (queues.get(path) ?? Promise.resolve()).then(task, task);
|
|
21
|
+
queues.set(path, next.catch(() => undefined));
|
|
22
|
+
return next;
|
|
23
|
+
};
|
|
24
|
+
const writeData = (value, name) => {
|
|
25
|
+
const path = fileFor(dir, name);
|
|
26
|
+
return serialize(path, async () => {
|
|
27
|
+
const tmp = `${path}.tmp`;
|
|
28
|
+
await writeFile(tmp, JSON.stringify(value, BufferJSON.replacer), { mode: 0o600 });
|
|
29
|
+
await rename(tmp, path);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const readData = async (name) => {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(await readFile(fileFor(dir, name), "utf8"), BufferJSON.reviver);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const removeData = (name) => {
|
|
41
|
+
const path = fileFor(dir, name);
|
|
42
|
+
return serialize(path, () => unlink(path).catch(() => undefined));
|
|
43
|
+
};
|
|
44
|
+
const creds = (await readCreds(dir)) ?? initAuthCreds();
|
|
45
|
+
return {
|
|
46
|
+
state: {
|
|
47
|
+
creds,
|
|
48
|
+
keys: {
|
|
49
|
+
get: async (type, ids) => {
|
|
50
|
+
const data = {};
|
|
51
|
+
await Promise.all(ids.map(async (id) => {
|
|
52
|
+
let value = await readData(`${type}-${id}.json`);
|
|
53
|
+
if (type === "app-state-sync-key" && value) {
|
|
54
|
+
value = proto.Message.AppStateSyncKeyData.fromObject(value);
|
|
55
|
+
}
|
|
56
|
+
data[id] = value;
|
|
57
|
+
}));
|
|
58
|
+
return data;
|
|
59
|
+
},
|
|
60
|
+
set: async (data) => {
|
|
61
|
+
const tasks = [];
|
|
62
|
+
for (const category in data) {
|
|
63
|
+
const bucket = data[category] ?? {};
|
|
64
|
+
for (const id in bucket) {
|
|
65
|
+
const value = bucket[id];
|
|
66
|
+
const name = `${category}-${id}.json`;
|
|
67
|
+
tasks.push(value ? writeData(value, name) : removeData(name));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
await Promise.all(tasks);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
saveCreds: () => writeData(creds, CREDS_FILE),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Stored credentials, or null when nothing is linked yet. */
|
|
78
|
+
async function readCreds(dir) {
|
|
79
|
+
let text;
|
|
80
|
+
try {
|
|
81
|
+
text = await readFile(fileFor(dir, CREDS_FILE), "utf8");
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(text, BufferJSON.reviver);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
throw new WazapError("SESSION_CORRUPT", `Stored credentials in ${dir} are unreadable.`, RESET_FIX);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** The linked account, or null when no account is linked. Throws SESSION_CORRUPT. */
|
|
94
|
+
export function readLinkedAccount(dir) {
|
|
95
|
+
let text;
|
|
96
|
+
try {
|
|
97
|
+
text = readFileSync(fileFor(dir, CREDS_FILE), "utf8");
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
let creds;
|
|
103
|
+
try {
|
|
104
|
+
creds = JSON.parse(text, BufferJSON.reviver);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
throw new WazapError("SESSION_CORRUPT", `Stored credentials in ${dir} are unreadable.`, RESET_FIX);
|
|
108
|
+
}
|
|
109
|
+
if (!creds.registered || !creds.me?.id)
|
|
110
|
+
return null;
|
|
111
|
+
const number = creds.me.id.split(":")[0].split("@")[0];
|
|
112
|
+
return { id: `${number}@s.whatsapp.net`, name: creds.me.name || "", number };
|
|
113
|
+
}
|
|
114
|
+
export function clearAuth(dir) {
|
|
115
|
+
rmSync(dir, { recursive: true, force: true });
|
|
116
|
+
}
|
package/dist/banner.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const BANNER = `██╗ ██╗ █████╗ ███████╗ █████╗ ██████╗
|
|
2
|
+
██║ ██║██╔══██╗╚══███╔╝██╔══██╗██╔══██╗
|
|
3
|
+
██║ █╗ ██║███████║ ███╔╝ ███████║██████╔╝
|
|
4
|
+
██║███╗██║██╔══██║ ███╔╝ ██╔══██║██╔═══╝
|
|
5
|
+
╚███╔███╔╝██║ ██║███████╗██║ ██║██║
|
|
6
|
+
╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝
|
|
7
|
+
WhatsApp for your AI agent.`;
|