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
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { C as SendOptions, E as InboundMessage, S as Outbound, a as Status, n as Update, v as PresenceKind, x as MessageRef } from "./update-Bi5ZPUjP.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/channel/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A resolved WhatsApp conversation — enough context for both Eve and Flue to
|
|
6
|
+
* address a session. `chatId` is always the JID (`xxx@s.whatsapp.net` or
|
|
7
|
+
* `xxx@g.us`). `from` is the sender within that chat (equal to `chatId` for
|
|
8
|
+
* DMs, the participant JID for groups).
|
|
9
|
+
*/
|
|
10
|
+
interface ConversationRef {
|
|
11
|
+
readonly chatId: string;
|
|
12
|
+
readonly isGroup: boolean;
|
|
13
|
+
/** Sender JID — equal to chatId for DMs, participant for groups. */
|
|
14
|
+
readonly from?: string;
|
|
15
|
+
/** Sender display name from WhatsApp. */
|
|
16
|
+
readonly pushName?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Events the channel adapter emits to its handlers. Framework adapters
|
|
20
|
+
* translate these into their own session-start/dispatch calls.
|
|
21
|
+
*/
|
|
22
|
+
type ChannelEvent = {
|
|
23
|
+
type: "message";
|
|
24
|
+
ref: ConversationRef;
|
|
25
|
+
message: InboundMessage;
|
|
26
|
+
} | {
|
|
27
|
+
type: "update";
|
|
28
|
+
ref: ConversationRef;
|
|
29
|
+
update: Update;
|
|
30
|
+
} | {
|
|
31
|
+
type: "status";
|
|
32
|
+
accountId: string;
|
|
33
|
+
status: Status;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Callbacks the framework adapter provides. The channel adapter calls these
|
|
37
|
+
* when WhatsApp events arrive. Returning a Promise is fine — the adapter
|
|
38
|
+
* awaits before continuing to drain its event queue.
|
|
39
|
+
*/
|
|
40
|
+
interface ChannelHandlers {
|
|
41
|
+
onEvent(event: ChannelEvent): void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The framework-agnostic WhatsApp channel surface: one adapter wraps one
|
|
45
|
+
* account's session. Framework adapters (and the sidecar) hold one of these
|
|
46
|
+
* and call its methods; the deep module sits behind it.
|
|
47
|
+
*/
|
|
48
|
+
interface WhatsAppChannelAdapter {
|
|
49
|
+
/** Label for this account, carried on status events and wire payloads. */
|
|
50
|
+
readonly accountId: string;
|
|
51
|
+
/** Start the underlying session (connects to WhatsApp). */
|
|
52
|
+
start(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Send an outbound message to a chat. Returns a ref for follow-up
|
|
55
|
+
* react/edit/delete/quote operations.
|
|
56
|
+
*/
|
|
57
|
+
send(chatId: string, content: Outbound, opts?: SendOptions): Promise<MessageRef>;
|
|
58
|
+
/** Mark a chat as read (blue ticks). */
|
|
59
|
+
markRead(chatId: string): Promise<void>;
|
|
60
|
+
/** Set or clear typing/recording presence in a chat. */
|
|
61
|
+
setTyping(chatId: string, kind: PresenceKind): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Subscribe to channel events. Returns an unsubscribe function.
|
|
64
|
+
* Multiple subscribers are supported; each receives every event.
|
|
65
|
+
*/
|
|
66
|
+
subscribe(handlers: ChannelHandlers): () => void;
|
|
67
|
+
/** Intentional teardown. */
|
|
68
|
+
stop(): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { WhatsAppChannelAdapter as i, ChannelHandlers as n, ConversationRef as r, ChannelEvent as t };
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
//#region src/model/message.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Inbound message shapes. Protocol-free types.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* A closed discriminated union with an `unsupported` catch-all, so it is
|
|
7
|
+
* type-impossible to crash on — or silently drop — a message. Media bodies are
|
|
8
|
+
* a lazy {@link MediaHandle}: the bytes never sit in the event stream and are
|
|
9
|
+
* fetched on demand, so the media kinds carry metadata only until you download.
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
/** Quote / mentions, lifted from the proto contextInfo (WAProto ContextInfo). */
|
|
14
|
+
interface MessageContext {
|
|
15
|
+
/** the message being replied to: contextInfo.stanzaId + participant */
|
|
16
|
+
readonly quoted?: {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly from: string;
|
|
19
|
+
};
|
|
20
|
+
/** contextInfo.mentionedJid */
|
|
21
|
+
readonly mentions?: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* How the sender identity was resolved. `from` on the message is the resolved
|
|
25
|
+
* id; `alt` carries the alternate form so a host can map between the two
|
|
26
|
+
* identity schemes (LID and phone number).
|
|
27
|
+
*/
|
|
28
|
+
interface Addressing {
|
|
29
|
+
readonly mode: "lid" | "pn";
|
|
30
|
+
/** The alternate identity, when available. */
|
|
31
|
+
readonly alt?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Unwrapped wrapper flags — kept even though we detect on the inner content. */
|
|
34
|
+
interface MessageFlags {
|
|
35
|
+
readonly viewOnce?: boolean;
|
|
36
|
+
readonly ephemeral?: boolean;
|
|
37
|
+
readonly edited?: boolean;
|
|
38
|
+
}
|
|
39
|
+
interface Base {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly chatId: string;
|
|
42
|
+
/** resolved sender identity (see `addressing` for the alternate form) */
|
|
43
|
+
readonly from: string;
|
|
44
|
+
/** sender's WhatsApp display name (proto pushName), when present. */
|
|
45
|
+
readonly pushName?: string;
|
|
46
|
+
readonly fromMe: boolean;
|
|
47
|
+
readonly timestamp: number;
|
|
48
|
+
/** true = live (`messages.upsert` type "notify"); false = history ("append"). */
|
|
49
|
+
readonly live: boolean;
|
|
50
|
+
readonly isGroup: boolean;
|
|
51
|
+
readonly context?: MessageContext;
|
|
52
|
+
readonly addressing?: Addressing;
|
|
53
|
+
readonly flags?: MessageFlags;
|
|
54
|
+
}
|
|
55
|
+
/** Media metadata, lifted from the proto. The bytes are fetched via `MediaHandle`. */
|
|
56
|
+
interface MediaMeta {
|
|
57
|
+
readonly mimetype?: string;
|
|
58
|
+
readonly fileLength?: number;
|
|
59
|
+
readonly fileName?: string;
|
|
60
|
+
readonly seconds?: number;
|
|
61
|
+
readonly ptt?: boolean;
|
|
62
|
+
readonly width?: number;
|
|
63
|
+
readonly height?: number;
|
|
64
|
+
readonly caption?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Opaque, on-demand media: metadata now, bytes when you ask.
|
|
68
|
+
* `download()` fetches + decrypts, transparently re-uploading expired media.
|
|
69
|
+
* Bytes never travel in the event stream — the consumer pulls them when ready.
|
|
70
|
+
*/
|
|
71
|
+
interface MediaHandle extends MediaMeta {
|
|
72
|
+
download(): Promise<Buffer>;
|
|
73
|
+
}
|
|
74
|
+
type InboundMessage = Base & ({
|
|
75
|
+
kind: "text";
|
|
76
|
+
text: string;
|
|
77
|
+
} | {
|
|
78
|
+
kind: "image" | "video" | "audio" | "document" | "sticker";
|
|
79
|
+
media: MediaHandle;
|
|
80
|
+
text?: string;
|
|
81
|
+
} | {
|
|
82
|
+
kind: "location";
|
|
83
|
+
lat: number;
|
|
84
|
+
lng: number;
|
|
85
|
+
name?: string;
|
|
86
|
+
address?: string;
|
|
87
|
+
} | {
|
|
88
|
+
kind: "contacts";
|
|
89
|
+
contacts: readonly {
|
|
90
|
+
name?: string;
|
|
91
|
+
vcard: string;
|
|
92
|
+
}[];
|
|
93
|
+
} | {
|
|
94
|
+
kind: "poll";
|
|
95
|
+
name: string;
|
|
96
|
+
options: readonly string[];
|
|
97
|
+
selectableCount: number;
|
|
98
|
+
} | {
|
|
99
|
+
kind: "unsupported";
|
|
100
|
+
rawType: string;
|
|
101
|
+
});
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/model/outbound.d.ts
|
|
104
|
+
/** Bytes to send: an in-memory buffer, a URL Baileys fetches, or a byte stream. */
|
|
105
|
+
type BinaryInput = Buffer | {
|
|
106
|
+
url: string;
|
|
107
|
+
} | {
|
|
108
|
+
stream: AsyncIterable<Uint8Array>;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* A plain reference to an existing message — enough to rebuild the Baileys key
|
|
112
|
+
* for react/edit/delete/quote WITHOUT leaking the proto. Build one from an
|
|
113
|
+
* `InboundMessage` via `refOf`, or construct it yourself.
|
|
114
|
+
*/
|
|
115
|
+
interface MessageRef {
|
|
116
|
+
readonly id: string;
|
|
117
|
+
readonly chatId: string;
|
|
118
|
+
readonly fromMe: boolean;
|
|
119
|
+
/** group sender, if the target is a group message */
|
|
120
|
+
readonly participant?: string;
|
|
121
|
+
}
|
|
122
|
+
type Outbound = {
|
|
123
|
+
text: string;
|
|
124
|
+
} | {
|
|
125
|
+
image: BinaryInput;
|
|
126
|
+
caption?: string;
|
|
127
|
+
} | {
|
|
128
|
+
video: BinaryInput;
|
|
129
|
+
caption?: string;
|
|
130
|
+
gifPlayback?: boolean;
|
|
131
|
+
} /** `ptt: true` sends a voice note (mimetype defaults to ogg/opus). */ | {
|
|
132
|
+
audio: BinaryInput;
|
|
133
|
+
ptt?: boolean;
|
|
134
|
+
seconds?: number;
|
|
135
|
+
mimetype?: string;
|
|
136
|
+
} | {
|
|
137
|
+
document: BinaryInput;
|
|
138
|
+
fileName: string;
|
|
139
|
+
mimetype: string;
|
|
140
|
+
caption?: string;
|
|
141
|
+
} | {
|
|
142
|
+
sticker: BinaryInput;
|
|
143
|
+
} | {
|
|
144
|
+
location: {
|
|
145
|
+
lat: number;
|
|
146
|
+
lng: number;
|
|
147
|
+
name?: string;
|
|
148
|
+
address?: string;
|
|
149
|
+
};
|
|
150
|
+
} | {
|
|
151
|
+
contacts: {
|
|
152
|
+
displayName?: string;
|
|
153
|
+
vcards: readonly string[];
|
|
154
|
+
};
|
|
155
|
+
} /** React to a message; `emoji: ""` clears the reaction. */ | {
|
|
156
|
+
react: {
|
|
157
|
+
to: MessageRef;
|
|
158
|
+
emoji: string;
|
|
159
|
+
};
|
|
160
|
+
} | {
|
|
161
|
+
edit: {
|
|
162
|
+
target: MessageRef;
|
|
163
|
+
text: string;
|
|
164
|
+
};
|
|
165
|
+
} | {
|
|
166
|
+
delete: MessageRef;
|
|
167
|
+
};
|
|
168
|
+
interface SendOptions {
|
|
169
|
+
/** reply to / quote this message */
|
|
170
|
+
readonly quote?: MessageRef;
|
|
171
|
+
/** jids to @mention (must also appear in the text) */
|
|
172
|
+
readonly mentions?: readonly string[];
|
|
173
|
+
}
|
|
174
|
+
/** Build a `MessageRef` from a received message, for react/edit/delete/quote. */
|
|
175
|
+
declare function refOf(m: InboundMessage): MessageRef;
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/model/presence.d.ts
|
|
178
|
+
/**
|
|
179
|
+
* Ephemeral WhatsApp presence. These signals are stream-only: they should drive
|
|
180
|
+
* live UI affordances and expire quickly, not become durable message history.
|
|
181
|
+
*/
|
|
182
|
+
type PresenceKind = "typing" | "recording" | "available" | "idle" | "unavailable";
|
|
183
|
+
interface PresenceUpdate {
|
|
184
|
+
readonly chatId: string;
|
|
185
|
+
readonly participant?: string;
|
|
186
|
+
readonly kind: PresenceKind;
|
|
187
|
+
readonly at?: number;
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/errors.d.ts
|
|
191
|
+
/**
|
|
192
|
+
* Closed union of domain reasons a WhatsApp connection can fault on, mapped
|
|
193
|
+
* from the transport's status codes rather than any raw error payload.
|
|
194
|
+
*/
|
|
195
|
+
type FaultReason = "restart_required" | "connection_lost" | "timed_out" | "service_unavailable" | "logged_out_remote" | "connection_replaced" | "credentials_invalid" | "multidevice_mismatch" | "bad_session" | "pairing_rejected" | "intentional" | "unknown";
|
|
196
|
+
/**
|
|
197
|
+
* Which terminal sink a fault lands in — the single source of truth.
|
|
198
|
+
* "retryable" → the spine reconnects automatically.
|
|
199
|
+
* "logged_out" → creds are dead; wipe the SessionStore and re-pair.
|
|
200
|
+
* "suspended" → account/device problem; re-pairing won't help.
|
|
201
|
+
*/
|
|
202
|
+
type Disposition = "retryable" | "logged_out" | "suspended";
|
|
203
|
+
/** Which sink this reason lands in. */
|
|
204
|
+
declare function dispositionFor(reason: FaultReason): Disposition;
|
|
205
|
+
interface WhatsAppFault {
|
|
206
|
+
readonly reason: FaultReason;
|
|
207
|
+
readonly statusCode?: number;
|
|
208
|
+
/** Whether reconnecting with the same creds could plausibly help. */
|
|
209
|
+
readonly retryable: boolean;
|
|
210
|
+
/** Which terminal sink (or retry) this fault lands in. */
|
|
211
|
+
readonly disposition: Disposition;
|
|
212
|
+
}
|
|
213
|
+
/** Reconnect only what is genuinely retryable. No fix-by-retry. */
|
|
214
|
+
declare function isRetryable(reason: FaultReason): boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Classify a disconnect into a sanitized domain {@link WhatsAppFault}.
|
|
217
|
+
*
|
|
218
|
+
* @param error - The transport error that closed the connection, or a falsy
|
|
219
|
+
* value for an intentional teardown. The raw payload is never surfaced on the
|
|
220
|
+
* result — only its mapped status code.
|
|
221
|
+
* @param intentional - `true` when the caller closed the connection on purpose;
|
|
222
|
+
* such a close is never treated as a fault.
|
|
223
|
+
* @returns The classified fault, including its {@link Disposition}.
|
|
224
|
+
*/
|
|
225
|
+
declare function classifyDisconnect(error: unknown, intentional: boolean): WhatsAppFault;
|
|
226
|
+
/** Why a pairing attempt failed. */
|
|
227
|
+
type PairingErrorReason = "connection_closed_before_ready" | "pairing_readiness_timeout" | "pairing_rejected" | "invalid_phone";
|
|
228
|
+
/**
|
|
229
|
+
* A tagged error for the pairing flow. Carries only safe metadata (the reason
|
|
230
|
+
* and an optional status code); the underlying error is logged, never attached.
|
|
231
|
+
*/
|
|
232
|
+
declare class PairingError extends Error {
|
|
233
|
+
readonly _tag = "PairingError";
|
|
234
|
+
readonly reason: PairingErrorReason;
|
|
235
|
+
readonly statusCode?: number;
|
|
236
|
+
constructor(reason: PairingErrorReason, statusCode?: number);
|
|
237
|
+
}
|
|
238
|
+
/** Validate E.164 at the edge so bad input fails loudly before reaching Baileys. */
|
|
239
|
+
declare function assertE164(input: string): string;
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/model/status.d.ts
|
|
242
|
+
/** The pairing sub-state, present while `Status.phase` is `"pairing"`. */
|
|
243
|
+
type PairingState = /** waiting for the first qr (ws open + handshake done); for pairing-code, also awaiting the code */{
|
|
244
|
+
step: "awaiting_ready";
|
|
245
|
+
} /** the qr/code is live and shown to the human */ | {
|
|
246
|
+
step: "challenge_live";
|
|
247
|
+
method: "qr" | "pairing_code";
|
|
248
|
+
qr?: string;
|
|
249
|
+
code?: string;
|
|
250
|
+
expiresAt: number;
|
|
251
|
+
} /** paired; awaiting the expected 515 restart before reconnecting with creds */ | {
|
|
252
|
+
step: "restart_pending";
|
|
253
|
+
};
|
|
254
|
+
/** The sync sub-state — an open socket is not yet sendable until sync settles. */
|
|
255
|
+
type SyncState = /** socket open; awaiting receivedPendingNotifications */{
|
|
256
|
+
step: "draining";
|
|
257
|
+
} /** history sync in flight */ | {
|
|
258
|
+
step: "syncing";
|
|
259
|
+
progress?: number;
|
|
260
|
+
};
|
|
261
|
+
/** The connection lifecycle, with the pairing and sync sub-states nested in. */
|
|
262
|
+
type Status = {
|
|
263
|
+
phase: "disconnected";
|
|
264
|
+
} | {
|
|
265
|
+
phase: "connecting";
|
|
266
|
+
retryAttempt?: number;
|
|
267
|
+
} | {
|
|
268
|
+
phase: "pairing";
|
|
269
|
+
pairing: PairingState;
|
|
270
|
+
} | {
|
|
271
|
+
phase: "authenticated";
|
|
272
|
+
sync: SyncState;
|
|
273
|
+
} | {
|
|
274
|
+
phase: "online";
|
|
275
|
+
} | {
|
|
276
|
+
phase: "backing_off";
|
|
277
|
+
reason: FaultReason;
|
|
278
|
+
retryAttempt: number;
|
|
279
|
+
nextRetryAt: number;
|
|
280
|
+
} | {
|
|
281
|
+
phase: "logged_out";
|
|
282
|
+
reason: FaultReason;
|
|
283
|
+
} | {
|
|
284
|
+
phase: "suspended";
|
|
285
|
+
reason: FaultReason;
|
|
286
|
+
};
|
|
287
|
+
/** Stream element on `session.connection` — a Status emitted on transition. */
|
|
288
|
+
type ConnectionEvent = Status;
|
|
289
|
+
/** True once a status is terminal (the `connection` stream ends here). */
|
|
290
|
+
declare function isTerminal(status: Status): boolean;
|
|
291
|
+
/** True once the device is genuinely sendable. */
|
|
292
|
+
declare function isOnline(status: Status): boolean;
|
|
293
|
+
/** The connected account's own identity, read from the live socket once open. */
|
|
294
|
+
interface WaIdentity {
|
|
295
|
+
/** the account's own jid (e.g. `15551234567:12@s.whatsapp.net`). */
|
|
296
|
+
readonly jid: string;
|
|
297
|
+
readonly pushName?: string;
|
|
298
|
+
/** E.164, derived from the jid's number part when it is purely numeric. */
|
|
299
|
+
readonly phoneE164?: string;
|
|
300
|
+
}
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/model/update.d.ts
|
|
303
|
+
/**
|
|
304
|
+
* Delivery progression of a message. Mirrors WhatsApp's own ladder; in practice
|
|
305
|
+
* monotonic (`server_ack → delivered → read → played`). `error` means the send
|
|
306
|
+
* was rejected after the fact.
|
|
307
|
+
*/
|
|
308
|
+
type ReceiptStatus = "pending" | "server_ack" | "delivered" | "read" | "played" | "error";
|
|
309
|
+
interface UpdateBase {
|
|
310
|
+
/** The message this update is about. */
|
|
311
|
+
ref: MessageRef;
|
|
312
|
+
/** When it happened (ms epoch), when the wire tells us. */
|
|
313
|
+
at?: number;
|
|
314
|
+
}
|
|
315
|
+
type Update =
|
|
316
|
+
/** A receipt for a message — usually one we sent. `by` is set for the
|
|
317
|
+
* per-participant receipts that groups produce. */
|
|
318
|
+
(UpdateBase & {
|
|
319
|
+
kind: "receipt";
|
|
320
|
+
status: ReceiptStatus;
|
|
321
|
+
by?: string;
|
|
322
|
+
})
|
|
323
|
+
/** Someone reacted, or cleared their reaction (`removed: true`, `emoji`
|
|
324
|
+
* undefined). `by` is the reactor. */
|
|
325
|
+
| (UpdateBase & {
|
|
326
|
+
kind: "reaction";
|
|
327
|
+
emoji?: string;
|
|
328
|
+
by?: string;
|
|
329
|
+
removed: boolean;
|
|
330
|
+
})
|
|
331
|
+
/** A message was edited; `message` is the new content, re-mapped to the same
|
|
332
|
+
* shape as anything on the `inbound` stream. */
|
|
333
|
+
| (UpdateBase & {
|
|
334
|
+
kind: "edit";
|
|
335
|
+
message: InboundMessage;
|
|
336
|
+
}) /** A message was deleted for everyone (revoked). `by` is who revoked it. */ | (UpdateBase & {
|
|
337
|
+
kind: "revoke";
|
|
338
|
+
by?: string;
|
|
339
|
+
});
|
|
340
|
+
//#endregion
|
|
341
|
+
export { MessageFlags as A, SendOptions as C, MediaHandle as D, InboundMessage as E, MediaMeta as O, Outbound as S, Addressing as T, isRetryable as _, Status as a, BinaryInput as b, isOnline as c, FaultReason as d, PairingError as f, dispositionFor as g, classifyDisconnect as h, PairingState as i, MessageContext as k, isTerminal as l, assertE164 as m, Update as n, SyncState as o, WhatsAppFault as p, ConnectionEvent as r, WaIdentity as s, ReceiptStatus as t, Disposition as u, PresenceKind as v, refOf as w, MessageRef as x, PresenceUpdate as y };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { C as SendOptions, E as InboundMessage, O as MediaMeta, S as Outbound, a as Status, n as Update, t as ReceiptStatus, v as PresenceKind, x as MessageRef } from "./update-Bi5ZPUjP.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/sidecar/wire.d.ts
|
|
4
|
+
/** Media metadata plus an optional sidecar URL where the bytes are served. */
|
|
5
|
+
interface WireMedia extends MediaMeta {
|
|
6
|
+
/**
|
|
7
|
+
* Where to fetch the decrypted bytes. Relative (`/media/...`) unless the
|
|
8
|
+
* sidecar was configured with a base URL — resolve against the sidecar URL.
|
|
9
|
+
*/
|
|
10
|
+
readonly url?: string;
|
|
11
|
+
}
|
|
12
|
+
/** `InboundMessage` with `MediaHandle` flattened to `WireMedia`. */
|
|
13
|
+
type WireMessage = {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly chatId: string;
|
|
16
|
+
readonly from: string;
|
|
17
|
+
readonly pushName?: string;
|
|
18
|
+
readonly fromMe: boolean;
|
|
19
|
+
readonly timestamp: number;
|
|
20
|
+
readonly isGroup: boolean;
|
|
21
|
+
} & ({
|
|
22
|
+
kind: "text";
|
|
23
|
+
text: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "image" | "video" | "audio" | "document" | "sticker";
|
|
26
|
+
media: WireMedia;
|
|
27
|
+
text?: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "location";
|
|
30
|
+
lat: number;
|
|
31
|
+
lng: number;
|
|
32
|
+
name?: string;
|
|
33
|
+
address?: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "contacts";
|
|
36
|
+
contacts: readonly {
|
|
37
|
+
name?: string;
|
|
38
|
+
vcard: string;
|
|
39
|
+
}[];
|
|
40
|
+
} | {
|
|
41
|
+
kind: "poll";
|
|
42
|
+
name: string;
|
|
43
|
+
options: readonly string[];
|
|
44
|
+
selectableCount: number;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "unsupported";
|
|
47
|
+
rawType: string;
|
|
48
|
+
});
|
|
49
|
+
/** `Update` with the edit arm's `InboundMessage` flattened to `WireMessage`. */
|
|
50
|
+
type WireUpdate = {
|
|
51
|
+
kind: "receipt";
|
|
52
|
+
ref: MessageRef;
|
|
53
|
+
at?: number;
|
|
54
|
+
status: ReceiptStatus;
|
|
55
|
+
by?: string;
|
|
56
|
+
} | {
|
|
57
|
+
kind: "reaction";
|
|
58
|
+
ref: MessageRef;
|
|
59
|
+
at?: number;
|
|
60
|
+
emoji?: string;
|
|
61
|
+
by?: string;
|
|
62
|
+
removed: boolean;
|
|
63
|
+
} | {
|
|
64
|
+
kind: "edit";
|
|
65
|
+
ref: MessageRef;
|
|
66
|
+
at?: number;
|
|
67
|
+
message: WireMessage;
|
|
68
|
+
} | {
|
|
69
|
+
kind: "revoke";
|
|
70
|
+
ref: MessageRef;
|
|
71
|
+
at?: number;
|
|
72
|
+
by?: string;
|
|
73
|
+
};
|
|
74
|
+
/** One event POSTed by the sidecar to each forward target. */
|
|
75
|
+
type SidecarEvent = {
|
|
76
|
+
type: "message";
|
|
77
|
+
accountId: string;
|
|
78
|
+
chatId: string;
|
|
79
|
+
isGroup: boolean;
|
|
80
|
+
from?: string;
|
|
81
|
+
pushName?: string;
|
|
82
|
+
message: WireMessage;
|
|
83
|
+
} | {
|
|
84
|
+
type: "update";
|
|
85
|
+
accountId: string;
|
|
86
|
+
chatId: string;
|
|
87
|
+
update: WireUpdate;
|
|
88
|
+
} | {
|
|
89
|
+
type: "status";
|
|
90
|
+
accountId: string;
|
|
91
|
+
status: Status;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Media bytes in a request body: base64, or a URL Baileys fetches itself.
|
|
95
|
+
* (A raw Buffer cannot travel as JSON.)
|
|
96
|
+
*/
|
|
97
|
+
type WireBinary = {
|
|
98
|
+
b64: string;
|
|
99
|
+
} | {
|
|
100
|
+
url: string;
|
|
101
|
+
};
|
|
102
|
+
/** Body of `POST /send`. `content` is `Outbound` with `WireBinary` media. */
|
|
103
|
+
interface SendRequest {
|
|
104
|
+
readonly accountId: string;
|
|
105
|
+
readonly chatId: string;
|
|
106
|
+
readonly content: Record<string, unknown>;
|
|
107
|
+
readonly opts?: SendOptions;
|
|
108
|
+
}
|
|
109
|
+
/** Body of `POST /markRead`. */
|
|
110
|
+
interface MarkReadRequest {
|
|
111
|
+
readonly accountId: string;
|
|
112
|
+
readonly chatId: string;
|
|
113
|
+
}
|
|
114
|
+
/** Body of `POST /setTyping`. */
|
|
115
|
+
interface SetTypingRequest {
|
|
116
|
+
readonly accountId: string;
|
|
117
|
+
readonly chatId: string;
|
|
118
|
+
readonly kind?: PresenceKind;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Flatten an `InboundMessage` for the wire. `mediaUrl` is the sidecar path
|
|
122
|
+
* serving this message's bytes (set only for media kinds).
|
|
123
|
+
*/
|
|
124
|
+
declare function toWireMessage(msg: InboundMessage, mediaUrl?: string): WireMessage;
|
|
125
|
+
/** Flatten an `Update` for the wire (the edit arm carries an `InboundMessage`). */
|
|
126
|
+
declare function toWireUpdate(update: Update): WireUpdate;
|
|
127
|
+
/**
|
|
128
|
+
* Revive a `POST /send` body's content into a real `Outbound`: `{ b64 }`
|
|
129
|
+
* media becomes a Buffer, `{ url }` passes through for Baileys to fetch.
|
|
130
|
+
*/
|
|
131
|
+
declare function reviveOutbound(content: Record<string, unknown>): Outbound;
|
|
132
|
+
/** A short human-readable placeholder for non-text message kinds. */
|
|
133
|
+
declare function messagePreview(msg: WireMessage): string;
|
|
134
|
+
//#endregion
|
|
135
|
+
export { WireBinary as a, WireUpdate as c, toWireMessage as d, toWireUpdate as f, SidecarEvent as i, messagePreview as l, SendRequest as n, WireMedia as o, SetTypingRequest as r, WireMessage as s, MarkReadRequest as t, reviveOutbound as u };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
//#region src/sidecar/wire.ts
|
|
2
|
+
/** Drop undefined values so wire objects survive a JSON round-trip unchanged. */
|
|
3
|
+
function compact(obj) {
|
|
4
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Flatten an `InboundMessage` for the wire. `mediaUrl` is the sidecar path
|
|
8
|
+
* serving this message's bytes (set only for media kinds).
|
|
9
|
+
*/
|
|
10
|
+
function toWireMessage(msg, mediaUrl) {
|
|
11
|
+
const base = {
|
|
12
|
+
id: msg.id,
|
|
13
|
+
chatId: msg.chatId,
|
|
14
|
+
from: msg.from,
|
|
15
|
+
...msg.pushName !== void 0 && { pushName: msg.pushName },
|
|
16
|
+
fromMe: msg.fromMe,
|
|
17
|
+
timestamp: msg.timestamp,
|
|
18
|
+
isGroup: msg.isGroup
|
|
19
|
+
};
|
|
20
|
+
switch (msg.kind) {
|
|
21
|
+
case "text": return {
|
|
22
|
+
...base,
|
|
23
|
+
kind: msg.kind,
|
|
24
|
+
text: msg.text
|
|
25
|
+
};
|
|
26
|
+
case "image":
|
|
27
|
+
case "video":
|
|
28
|
+
case "audio":
|
|
29
|
+
case "document":
|
|
30
|
+
case "sticker": {
|
|
31
|
+
const { mimetype, fileLength, fileName, seconds, ptt, width, height, caption } = msg.media;
|
|
32
|
+
const media = compact({
|
|
33
|
+
mimetype,
|
|
34
|
+
fileLength,
|
|
35
|
+
fileName,
|
|
36
|
+
seconds,
|
|
37
|
+
ptt,
|
|
38
|
+
width,
|
|
39
|
+
height,
|
|
40
|
+
caption,
|
|
41
|
+
url: mediaUrl
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
...base,
|
|
45
|
+
kind: msg.kind,
|
|
46
|
+
media,
|
|
47
|
+
...msg.text !== void 0 && { text: msg.text }
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
case "location": return compact({
|
|
51
|
+
...base,
|
|
52
|
+
kind: msg.kind,
|
|
53
|
+
lat: msg.lat,
|
|
54
|
+
lng: msg.lng,
|
|
55
|
+
name: msg.name,
|
|
56
|
+
address: msg.address
|
|
57
|
+
});
|
|
58
|
+
case "contacts": return {
|
|
59
|
+
...base,
|
|
60
|
+
kind: msg.kind,
|
|
61
|
+
contacts: msg.contacts
|
|
62
|
+
};
|
|
63
|
+
case "poll": return {
|
|
64
|
+
...base,
|
|
65
|
+
kind: msg.kind,
|
|
66
|
+
name: msg.name,
|
|
67
|
+
options: msg.options,
|
|
68
|
+
selectableCount: msg.selectableCount
|
|
69
|
+
};
|
|
70
|
+
case "unsupported": return {
|
|
71
|
+
...base,
|
|
72
|
+
kind: msg.kind,
|
|
73
|
+
rawType: msg.rawType
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Flatten an `Update` for the wire (the edit arm carries an `InboundMessage`). */
|
|
78
|
+
function toWireUpdate(update) {
|
|
79
|
+
if (update.kind === "edit") return compact({
|
|
80
|
+
kind: "edit",
|
|
81
|
+
ref: update.ref,
|
|
82
|
+
at: update.at,
|
|
83
|
+
message: toWireMessage(update.message)
|
|
84
|
+
});
|
|
85
|
+
return update;
|
|
86
|
+
}
|
|
87
|
+
const MEDIA_KEYS = [
|
|
88
|
+
"image",
|
|
89
|
+
"video",
|
|
90
|
+
"audio",
|
|
91
|
+
"document",
|
|
92
|
+
"sticker"
|
|
93
|
+
];
|
|
94
|
+
/**
|
|
95
|
+
* Revive a `POST /send` body's content into a real `Outbound`: `{ b64 }`
|
|
96
|
+
* media becomes a Buffer, `{ url }` passes through for Baileys to fetch.
|
|
97
|
+
*/
|
|
98
|
+
function reviveOutbound(content) {
|
|
99
|
+
const out = { ...content };
|
|
100
|
+
for (const key of MEDIA_KEYS) {
|
|
101
|
+
const media = out[key];
|
|
102
|
+
if (media && typeof media === "object" && "b64" in media) out[key] = Buffer.from(media.b64, "base64");
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
/** A short human-readable placeholder for non-text message kinds. */
|
|
107
|
+
function messagePreview(msg) {
|
|
108
|
+
switch (msg.kind) {
|
|
109
|
+
case "text": return msg.text;
|
|
110
|
+
case "image":
|
|
111
|
+
case "video":
|
|
112
|
+
case "audio":
|
|
113
|
+
case "document":
|
|
114
|
+
case "sticker": return msg.text ?? msg.media.caption ?? `[${msg.kind}]`;
|
|
115
|
+
case "location": return `[location${msg.name ? `: ${msg.name}` : ""} ${msg.lat},${msg.lng}]`;
|
|
116
|
+
case "contacts": return `[contact card${msg.contacts.length > 1 ? `s (${msg.contacts.length})` : ""}]`;
|
|
117
|
+
case "poll": return `[poll: ${msg.name}]`;
|
|
118
|
+
case "unsupported": return `[unsupported: ${msg.rawType}]`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { toWireUpdate as i, reviveOutbound as n, toWireMessage as r, messagePreview as t };
|