whatsappd 0.1.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/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +369 -0
- package/bin/whatsappd.js +18 -0
- package/dist/adapter-BES4PRA9.mjs +1778 -0
- package/dist/adapters/eve.d.mts +83 -0
- package/dist/adapters/eve.mjs +181 -0
- package/dist/index.d.mts +283 -0
- package/dist/index.mjs +15 -0
- package/dist/ports-DeTIMztA.d.mts +60 -0
- package/dist/sidecar/index.d.mts +47 -0
- package/dist/sidecar/index.mjs +326 -0
- package/dist/stores/libsql.d.mts +16 -0
- package/dist/stores/libsql.mjs +57 -0
- package/dist/stores/memory.d.mts +6 -0
- package/dist/stores/memory.mjs +18 -0
- package/dist/tools/index.d.mts +99 -0
- package/dist/tools/index.mjs +117 -0
- package/dist/types-B8d1OyHV.d.mts +71 -0
- package/dist/update-Bi5ZPUjP.d.mts +341 -0
- package/dist/wire-Bx6OmkxC.d.mts +135 -0
- package/dist/wire-CsVVkLhn.mjs +122 -0
- package/package.json +98 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-07-08
|
|
10
|
+
|
|
11
|
+
Initial release.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- WhatsApp session engine over Baileys: a narrow, fully-typed surface
|
|
16
|
+
(`createSession`) with status, inbound, update, contact, group, and presence
|
|
17
|
+
streams, plus `send`/`markRead`/`setTyping` commands. No protocol types cross
|
|
18
|
+
the public surface.
|
|
19
|
+
- Pluggable credential stores: `memoryStore`, `fileStore`, and an optional
|
|
20
|
+
`libsqlStore` (via the `whatsappd/stores/libsql` subpath).
|
|
21
|
+
- QR and pairing-code auth strategies (`qrAuth`, `pairingAuth`).
|
|
22
|
+
- Framework-agnostic channel adapter (`createChannelAdapter`) and eight
|
|
23
|
+
plug-and-play agent tools (`whatsappd/tools`).
|
|
24
|
+
- HTTP sidecar (`whatsappd/sidecar`, and the `whatsappd` CLI):
|
|
25
|
+
one process per WhatsApp number, forwarding inbound events and serving media
|
|
26
|
+
on demand.
|
|
27
|
+
- Eve framework adapter (`whatsappd/adapters/eve`).
|
|
28
|
+
|
|
29
|
+
[Unreleased]: https://github.com/AaronAbuUsama/whatsappd/compare/v0.1.0...HEAD
|
|
30
|
+
[0.1.0]: https://github.com/AaronAbuUsama/whatsappd/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aaron Griffith
|
|
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,369 @@
|
|
|
1
|
+
<h1 align="center">whatsappd</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
Turn a WhatsApp number into an AI agent channel — a sealed
|
|
5
|
+
<a href="https://github.com/WhiskeySockets/Baileys">Baileys</a> engine,
|
|
6
|
+
plug-and-play agent tools, an HTTP sidecar, and an
|
|
7
|
+
<a href="https://eve.dev">Eve</a> adapter.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://www.npmjs.com/package/whatsappd"><img src="https://img.shields.io/npm/v/whatsappd.svg" alt="npm version"></a>
|
|
12
|
+
<a href="https://github.com/AaronAbuUsama/whatsappd/actions/workflows/ci.yml"><img src="https://github.com/AaronAbuUsama/whatsappd/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
|
|
13
|
+
<a href="./LICENSE"><img src="https://img.shields.io/npm/l/whatsappd.svg" alt="MIT license"></a>
|
|
14
|
+
<img src="https://img.shields.io/node/v/whatsappd.svg" alt="Node version">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
A **deep module** over [Baileys](https://github.com/WhiskeySockets/Baileys): a
|
|
18
|
+
narrow, fully-typed WhatsApp client whose entire connection / auth / message space
|
|
19
|
+
is modeled — derived from Baileys' real behaviour, not invented. The Baileys
|
|
20
|
+
internals are **sealed**: no Baileys type ever crosses the surface (`grep baileys`
|
|
21
|
+
the published `.d.ts` and you get zero hits). You learn one function, three streams,
|
|
22
|
+
and two ports; you never reopen the rest.
|
|
23
|
+
|
|
24
|
+
> **Scope:** `createSession` is tenant-naive — one account per session, storage
|
|
25
|
+
> pluggable. The whole package is deliberately single-account: one sidecar
|
|
26
|
+
> process per WhatsApp number (see [Multiple accounts](#multiple-accounts)).
|
|
27
|
+
|
|
28
|
+
See [`ARCHITECTURE.md`](./ARCHITECTURE.md) for the full state-machine model and the Baileys
|
|
29
|
+
landmines it seals.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install whatsappd
|
|
35
|
+
# optional, only if you use the libsql store:
|
|
36
|
+
npm install @libsql/client
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Requires Node ≥ 20.
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { createSession, qrAuth, refOf } from "whatsappd";
|
|
45
|
+
import { fileStore } from "whatsappd";
|
|
46
|
+
|
|
47
|
+
const session = createSession({
|
|
48
|
+
store: fileStore("./.wa-auth"), // WHERE creds live (pluggable)
|
|
49
|
+
auth: qrAuth(), // or pairingAuth("+15551234567")
|
|
50
|
+
// logger optional; metrics, pacing, status filter all have sane defaults
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 1) Lifecycle — read the connection as a stream of facts.
|
|
54
|
+
void (async () => {
|
|
55
|
+
for await (const ev of session.connection) {
|
|
56
|
+
if (ev.phase === "pairing" && ev.pairing.step === "challenge_live") {
|
|
57
|
+
console.log("scan/enter:", ev.pairing.qr ?? ev.pairing.code);
|
|
58
|
+
}
|
|
59
|
+
if (ev.phase === "online") console.log("ready to send");
|
|
60
|
+
// logged_out / suspended are terminal and end this stream
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
|
|
64
|
+
// 2) Inbound — messages as a stream.
|
|
65
|
+
void (async () => {
|
|
66
|
+
for await (const m of session.inbound) {
|
|
67
|
+
if (m.fromMe || !m.live) continue;
|
|
68
|
+
if (m.kind === "text" && m.text === "ping") {
|
|
69
|
+
await session.send(m.chatId, { text: "pong" }, { quote: refOf(m) });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
|
|
74
|
+
await session.start();
|
|
75
|
+
// …later
|
|
76
|
+
await session.stop(); // intentional teardown — never reported as a fault
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## The surface
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const session = createSession(config);
|
|
83
|
+
|
|
84
|
+
session.status // current Status (sync read)
|
|
85
|
+
session.connection // AsyncIterable<Status> — lifecycle
|
|
86
|
+
session.inbound // AsyncIterable<InboundMessage> — messages in
|
|
87
|
+
session.updates // AsyncIterable<Update> — receipts/reactions/edits/revokes
|
|
88
|
+
|
|
89
|
+
session.start() // Promise<void> — connect + supervise (auto-reconnect)
|
|
90
|
+
session.send(to, msg, opts?) // Promise<MessageRef>
|
|
91
|
+
session.markRead(refs) // Promise<void> — blue ticks
|
|
92
|
+
session.setTyping(chatId, on) // Promise<void> — typing indicator
|
|
93
|
+
session.stop() // Promise<void> — intentional teardown
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Inbound messages
|
|
97
|
+
|
|
98
|
+
A closed discriminated union with an `unsupported` catch-all — it is
|
|
99
|
+
type-impossible to crash on or silently drop a message. `switch (m.kind)` is
|
|
100
|
+
exhaustive:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
for await (const m of session.inbound) {
|
|
104
|
+
switch (m.kind) {
|
|
105
|
+
case "text":
|
|
106
|
+
m.text;
|
|
107
|
+
break;
|
|
108
|
+
case "image":
|
|
109
|
+
case "video":
|
|
110
|
+
case "audio":
|
|
111
|
+
case "document":
|
|
112
|
+
case "sticker":
|
|
113
|
+
m.media;
|
|
114
|
+
m.text /* caption */;
|
|
115
|
+
break;
|
|
116
|
+
case "location":
|
|
117
|
+
m.lat;
|
|
118
|
+
m.lng;
|
|
119
|
+
m.name;
|
|
120
|
+
break;
|
|
121
|
+
case "contacts":
|
|
122
|
+
m.contacts;
|
|
123
|
+
break;
|
|
124
|
+
case "poll":
|
|
125
|
+
m.name;
|
|
126
|
+
m.options;
|
|
127
|
+
break;
|
|
128
|
+
case "unsupported":
|
|
129
|
+
m.rawType;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Every message carries `id`, `chatId`, `from`, `fromMe`, `timestamp`, `live`
|
|
136
|
+
(`true` = arrived now, `false` = history backfill), `isGroup`, and optionally
|
|
137
|
+
`context` (quote/mentions), `addressing` (LID/PN), and `flags`
|
|
138
|
+
(viewOnce/ephemeral/edited).
|
|
139
|
+
|
|
140
|
+
**Media is lazy** — bytes never sit in the stream. Pull them when you're ready;
|
|
141
|
+
expired media is transparently re-uploaded and re-fetched:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
if (m.kind === "image") {
|
|
145
|
+
const bytes: Buffer = await m.media.download();
|
|
146
|
+
// m.media also has mimetype, fileLength, width, height, caption, …
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Sending
|
|
151
|
+
|
|
152
|
+
`send` is one polymorphic verb and returns a `MessageRef` so you can act on what
|
|
153
|
+
you just sent:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const ref = await session.send(to, { text: "hello" });
|
|
157
|
+
await session.send(to, { image: buffer, caption: "hi" }); // Buffer | {url} | {stream}
|
|
158
|
+
await session.send(to, { audio: buf, ptt: true }); // voice note
|
|
159
|
+
await session.send(to, { location: { lat, lng, name } });
|
|
160
|
+
await session.send(to, { contacts: { displayName, vcards } });
|
|
161
|
+
|
|
162
|
+
// reference-based ops — no proto, just a MessageRef
|
|
163
|
+
await session.send(to, { react: { to: ref, emoji: "👍" } }); // emoji "" clears it
|
|
164
|
+
await session.send(to, { edit: { target: ref, text: "fixed" } });
|
|
165
|
+
await session.send(to, { delete: ref });
|
|
166
|
+
|
|
167
|
+
// options
|
|
168
|
+
await session.send(to, { text: "re:" }, { quote: ref, mentions: ["1555…@s.whatsapp.net"] });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`refOf(inboundMessage)` builds a `MessageRef` from something you received.
|
|
172
|
+
|
|
173
|
+
### Updates
|
|
174
|
+
|
|
175
|
+
Changes to _existing_ messages arrive on a separate stream:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
for await (const u of session.updates) {
|
|
179
|
+
switch (u.kind) {
|
|
180
|
+
case "receipt":
|
|
181
|
+
u.status;
|
|
182
|
+
/* server_ack → delivered → read → played */ break;
|
|
183
|
+
case "reaction":
|
|
184
|
+
u.emoji;
|
|
185
|
+
u.removed;
|
|
186
|
+
u.by;
|
|
187
|
+
break;
|
|
188
|
+
case "edit":
|
|
189
|
+
u.message;
|
|
190
|
+
/* the new content, same shape as inbound */ break;
|
|
191
|
+
case "revoke":
|
|
192
|
+
u.by;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## The two ports
|
|
199
|
+
|
|
200
|
+
**`SessionStore`** — where credentials live. An opaque key/value store; the module
|
|
201
|
+
serializes all Baileys auth state into plain strings, so your store needs **zero
|
|
202
|
+
Baileys knowledge**:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
interface SessionStore {
|
|
206
|
+
read(key: string): Promise<string | null>;
|
|
207
|
+
write(entries: Record<string, string | null>): Promise<void>; // null = delete
|
|
208
|
+
clear(): Promise<void>;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Three are included:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { memoryStore, fileStore } from "whatsappd";
|
|
216
|
+
import { libsqlStore } from "whatsappd/stores/libsql";
|
|
217
|
+
|
|
218
|
+
memoryStore(); // ephemeral (tests, scripts)
|
|
219
|
+
fileStore("./.wa-auth"); // one file per key
|
|
220
|
+
libsqlStore({ url: "file:wa.db", account: "1555…" }); // local SQLite…
|
|
221
|
+
libsqlStore({ url: "libsql://…turso.io", authToken, account }); // …or remote Turso
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The `account` field namespaces rows, so one libsql database can hold many
|
|
225
|
+
accounts. Bring your own (Redis, Postgres, DynamoDB, …) by implementing the
|
|
226
|
+
three methods over a `(key, value)` table.
|
|
227
|
+
|
|
228
|
+
**`AuthStrategy`** — how you log in: `qrAuth()` or `pairingAuth(phone)` (E.164,
|
|
229
|
+
validated at the edge).
|
|
230
|
+
|
|
231
|
+
## Multiple accounts
|
|
232
|
+
|
|
233
|
+
This package is deliberately **single-account**: one session, one adapter, one
|
|
234
|
+
sidecar process per WhatsApp number. Running several numbers means running
|
|
235
|
+
several sidecar processes — each with its own store dir, port, and
|
|
236
|
+
`WHATSAPP_ACCOUNT` label — all forwarding into the same app. Events carry
|
|
237
|
+
`accountId`, so the receiver can tell the numbers apart.
|
|
238
|
+
|
|
239
|
+
Process-per-account is a feature, not a limitation: Baileys sessions
|
|
240
|
+
reconnect, re-pair, and occasionally crash-loop; isolating each number in its
|
|
241
|
+
own process means one account's bad day never touches another's, and "which
|
|
242
|
+
QR is this?" always has an obvious answer. (An in-process multi-account
|
|
243
|
+
supervisor used to live here; it was removed — see git history if you ever
|
|
244
|
+
need the pattern back.)
|
|
245
|
+
|
|
246
|
+
**Single ownership still applies.** A WhatsApp number is exactly one
|
|
247
|
+
linked-device session — two processes on the same account kick each other off
|
|
248
|
+
(the `440 connection_replaced` fault). Point exactly one sidecar at each
|
|
249
|
+
credential store.
|
|
250
|
+
|
|
251
|
+
## Configuration
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
createSession({
|
|
255
|
+
store,
|
|
256
|
+
auth,
|
|
257
|
+
logger, // optional; defaults to pino at WA_LOG_LEVEL (or "warn")
|
|
258
|
+
receiveStatusBroadcast, // default false — drop status@broadcast story posts
|
|
259
|
+
sendMinGapMs, // default 1000 — anti-ban gap between sends; 0 disables
|
|
260
|
+
metrics, // (e: MetricEvent) => void — fire-and-forget hook
|
|
261
|
+
verdictWindowMs, // pairing-rejection (silent-400) window
|
|
262
|
+
syncGraceMs,
|
|
263
|
+
reconnectBaseMs,
|
|
264
|
+
reconnectMaxMs,
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Send pacing** is on by default: outbound sends are funnelled through a FIFO
|
|
269
|
+
queue with a minimum gap, because WhatsApp flags bursty accounts. Set
|
|
270
|
+
`sendMinGapMs: 0` to disable. (`markRead`/`setTyping` are not paced.)
|
|
271
|
+
|
|
272
|
+
**Metrics** is an optional observability seam — the session calls it on
|
|
273
|
+
`transition`, `message_in`, `update_in`, `message_out`, and `reconnect`. A thrown
|
|
274
|
+
hook can never break the connection.
|
|
275
|
+
|
|
276
|
+
## Connection model (summary)
|
|
277
|
+
|
|
278
|
+
`status.phase`: `disconnected → connecting → pairing → authenticated → online`,
|
|
279
|
+
with `backing_off` for retryable drops and two terminal sinks:
|
|
280
|
+
|
|
281
|
+
- **`logged_out`** (401/440, or a rejected pairing) — creds are dead; the store is
|
|
282
|
+
wiped for you before the event fires; re-pair.
|
|
283
|
+
- **`suspended`** (403/411/500) — account/device problem; re-pairing won't help.
|
|
284
|
+
|
|
285
|
+
`online` ≠ `authenticated`: the device is only sendable once history sync settles.
|
|
286
|
+
The pairing flow honestly reports WhatsApp's silent 400 rejection via a verdict
|
|
287
|
+
window. Full detail and Baileys citations in [`ARCHITECTURE.md`](./ARCHITECTURE.md).
|
|
288
|
+
|
|
289
|
+
## Agent channel: sidecar + Eve adapter
|
|
290
|
+
|
|
291
|
+
The deep module above is transport. On top of it this package ships an **agent
|
|
292
|
+
channel**: a sidecar process that owns the Baileys socket, plus a plug-and-play
|
|
293
|
+
adapter for the [Eve framework](https://eve.dev). The sidecar POSTs inbound
|
|
294
|
+
events to your app; your app POSTs replies back. No Baileys import ever enters
|
|
295
|
+
the framework side.
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
WhatsApp ←Baileys WS→ sidecar (this package) ←HTTP→ Eve app (thin adapter)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**1. Run the sidecar** (one process per number; first run prints a QR):
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
WHATSAPP_FORWARD_URLS=https://my-app.example/api/channels/whatsapp/event \
|
|
305
|
+
WHATSAPP_SIDECAR_TOKEN=s3cret \
|
|
306
|
+
npx whatsappd
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
or programmatically: `import { runSidecar } from "whatsappd/sidecar"`.
|
|
310
|
+
|
|
311
|
+
**2. Drop the channel into your Eve app** as `agent/channels/whatsapp.ts`:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
export { default } from "whatsappd/adapters/eve";
|
|
315
|
+
// reads WHATSAPP_SIDECAR_URL / WHATSAPP_SIDECAR_TOKEN, or configure:
|
|
316
|
+
// import { whatsappChannel } from "whatsappd/adapters/eve";
|
|
317
|
+
// export default whatsappChannel({ sidecarUrl: "http://localhost:8788" });
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
One WhatsApp conversation ↔ one Eve session (`continuationToken` = chat JID).
|
|
321
|
+
Replies deliver on `message.completed`; read receipts + typing presence fire on
|
|
322
|
+
`turn.started`; inbound media stages through Eve's `fetchFile` from the
|
|
323
|
+
sidecar's `/media/...` endpoint.
|
|
324
|
+
|
|
325
|
+
For non-Eve hosts, `whatsappd/tools` exports the 8 agent tools
|
|
326
|
+
(`sendText`, `sendMedia`, `reply`, `markRead`, `setTyping`, `react`, `edit`,
|
|
327
|
+
`deleteMsg`) over the same `WhatsAppChannelAdapter` surface, and the sidecar's
|
|
328
|
+
HTTP API (`/send`, `/markRead`, `/setTyping`, `/media`, `/health`) is
|
|
329
|
+
framework-neutral.
|
|
330
|
+
|
|
331
|
+
## Develop & test
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
npm test # no-phone tests (mappers, machine, stores, auth round-trip, pacer, channel, sidecar, eve adapter)
|
|
335
|
+
npm run typecheck
|
|
336
|
+
npm run build # → dist/ (ESM + types; Baileys kept external; 0 baileys leaks in .d.ts)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Live proof (needs a phone)
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
npm run proof # QR login; message "ping" from another phone → "pong"
|
|
343
|
+
npm run proof -- +1555… # pairing-code login
|
|
344
|
+
npm run e2e 1555… # full outbound suite + self-driven react/edit/delete (loopback)
|
|
345
|
+
npm run store-proof # QR once, then reconnect from libsql with no QR
|
|
346
|
+
npm run proof:reset # kill proof + wipe ./.wa-auth for a fresh QR
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Always `Ctrl-C` (or `proof:reset`) before clearing auth — never delete the auth
|
|
350
|
+
directory under a running session.
|
|
351
|
+
|
|
352
|
+
## Disclaimer
|
|
353
|
+
|
|
354
|
+
This package uses **Baileys**, an unofficial reverse-engineered implementation
|
|
355
|
+
of the WhatsApp Web multi-device protocol. It is **not affiliated with,
|
|
356
|
+
endorsed by, or connected to WhatsApp or Meta** in any way. Automating a
|
|
357
|
+
personal WhatsApp account can violate WhatsApp's Terms of Service and may lead
|
|
358
|
+
to your number being **temporarily or permanently banned**. Use it at your own
|
|
359
|
+
risk, ideally with a number you can afford to lose, and make sure your usage
|
|
360
|
+
complies with WhatsApp's terms and any laws that apply to you.
|
|
361
|
+
|
|
362
|
+
## Contributing
|
|
363
|
+
|
|
364
|
+
Bug reports and pull requests are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
365
|
+
For security issues, see [SECURITY.md](./SECURITY.md).
|
|
366
|
+
|
|
367
|
+
## License
|
|
368
|
+
|
|
369
|
+
[MIT](./LICENSE) © Aaron Griffith
|
package/bin/whatsappd.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* whatsappd — the WhatsApp daemon CLI.
|
|
4
|
+
*
|
|
5
|
+
* Runs one WhatsApp account session behind an HTTP surface, configured entirely
|
|
6
|
+
* through environment variables (see `whatsappd/sidecar` for the full list).
|
|
7
|
+
* First run prints a QR (or pairing code) to link the device.
|
|
8
|
+
*
|
|
9
|
+
* WHATSAPP_FORWARD_URLS=https://my-app.example/api/channels/whatsapp/event \
|
|
10
|
+
* WHATSAPP_SIDECAR_TOKEN=secret \
|
|
11
|
+
* npx whatsappd
|
|
12
|
+
*/
|
|
13
|
+
import { runSidecar } from "../dist/sidecar/index.mjs";
|
|
14
|
+
|
|
15
|
+
runSidecar().catch((err) => {
|
|
16
|
+
console.error(err);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
});
|