whisp-js 0.1.0 → 0.3.1
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/README.md +540 -96
- package/dist/cjs/client.d.ts +52 -3
- package/dist/cjs/client.js +113 -3
- package/dist/cjs/constants.d.ts +2 -2
- package/dist/cjs/constants.js +2 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/listener.d.ts +2 -2
- package/dist/cjs/listener.js +3 -14
- package/dist/cjs/types.d.ts +69 -2
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client.d.ts +52 -3
- package/dist/esm/client.js +113 -3
- package/dist/esm/constants.d.ts +2 -2
- package/dist/esm/constants.js +2 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/listener.d.ts +2 -2
- package/dist/esm/listener.js +3 -14
- package/dist/esm/types.d.ts +69 -2
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +1 -1
package/dist/cjs/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type EventHandlers } from './listener.js';
|
|
2
2
|
import { type MediaSource } from './media.js';
|
|
3
3
|
import { type Timers } from './runtime.js';
|
|
4
|
-
import type { Chat, ChatListQuery, ChatMessagesQuery, Message, PhoneCheck, ProjectInfo, PublicEventType, SendMessageBody, SendStrategy, WebhookEvent, WhatsAppNumber } from './types.js';
|
|
4
|
+
import type { Chat, ChatListQuery, ChatMessagesQuery, MarkReadResult, Message, PhoneCheck, ProjectInfo, PublicEventType, SendMessageBody, SendStrategy, TypingOptions, TypingResult, WebhookEvent, WhatsAppNumber } from './types.js';
|
|
5
5
|
import { type VerifyWebhookInput } from './webhook.js';
|
|
6
6
|
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
7
7
|
export declare const DEFAULT_MAX_RETRIES = 2;
|
|
@@ -59,10 +59,24 @@ export interface SendOptions {
|
|
|
59
59
|
dailyCap?: number;
|
|
60
60
|
/** Makes retries safe across your own restarts too. Default: a random UUID per call, kept across its retries. */
|
|
61
61
|
idempotencyKey?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Phones or `<digits>@lid` JIDs to mention (0.3.0), each written `@<digits>` in the text or caption. Groups
|
|
64
|
+
* only; at most 50.
|
|
65
|
+
*/
|
|
66
|
+
mentions?: string[];
|
|
62
67
|
}
|
|
63
68
|
export interface SendFileOptions extends SendOptions {
|
|
64
69
|
caption?: string;
|
|
65
70
|
}
|
|
71
|
+
/** A voice note has no text to carry `@<digits>`, so it takes no `mentions`. */
|
|
72
|
+
export type SendVoiceOptions = Omit<SendOptions, 'mentions'>;
|
|
73
|
+
export interface EditOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Replaces the message's mentions (phones or `<digits>@lid` JIDs, each written `@<digits>` in `text`). Left
|
|
76
|
+
* out, the mentions still named in the new text are kept.
|
|
77
|
+
*/
|
|
78
|
+
mentions?: string[];
|
|
79
|
+
}
|
|
66
80
|
export interface MessagesQuery {
|
|
67
81
|
reference: string;
|
|
68
82
|
}
|
|
@@ -105,12 +119,47 @@ export declare class Whisp {
|
|
|
105
119
|
sendImage(to: string, media: MediaSource, options?: SendFileOptions): Promise<Message>;
|
|
106
120
|
sendVideo(to: string, media: MediaSource, options?: SendFileOptions): Promise<Message>;
|
|
107
121
|
sendDocument(to: string, media: MediaSource, options?: SendFileOptions): Promise<Message>;
|
|
108
|
-
sendVoice(to: string, media: MediaSource, options?:
|
|
122
|
+
sendVoice(to: string, media: MediaSource, options?: SendVoiceOptions): Promise<Message>;
|
|
109
123
|
/**
|
|
110
124
|
* `POST /messages` with a body you build (5.6). The instance's `from`,
|
|
111
125
|
* `strategy`, `sticky` and `dailyCap` fill in what the body leaves out.
|
|
112
126
|
*/
|
|
113
127
|
sendMessage(body: SendMessageBody, options?: Pick<SendOptions, 'idempotencyKey'>): Promise<Message>;
|
|
128
|
+
/**
|
|
129
|
+
* Sets this number's reaction to a message (one emoji), replacing its earlier one. Resolves once the server
|
|
130
|
+
* queued it for the number's connection; a `message.reaction` event with `fromMe: true` follows.
|
|
131
|
+
*/
|
|
132
|
+
react(message: Message | string, emoji: string): Promise<void>;
|
|
133
|
+
/** Removes this number's reaction to a message. */
|
|
134
|
+
unreact(message: Message | string): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Edits the text or caption of a message this number sent, within WhatsApp's 15 minutes
|
|
137
|
+
* (EDIT_WINDOW_PASSED after). Refused on a paused number (NUMBER_PAUSED). Resolves the edited message; the
|
|
138
|
+
* same text without `mentions` resolves it unchanged.
|
|
139
|
+
*/
|
|
140
|
+
edit(message: Message | string, text: string, options?: EditOptions): Promise<Message>;
|
|
141
|
+
/**
|
|
142
|
+
* Deletes a message this number sent for everyone, within 2 days (REVOKE_WINDOW_PASSED after). Resolves the
|
|
143
|
+
* message with `deletedAt` set, also when it was deleted already.
|
|
144
|
+
*/
|
|
145
|
+
revoke(message: Message | string): Promise<Message>;
|
|
146
|
+
/**
|
|
147
|
+
* Marks a chat of this instance's number read (a phone, a `cht_` id or a group JID). Unread is one count per
|
|
148
|
+
* chat, so this clears it for the panel's members too. `readReceipts` says what happened to the blue ticks:
|
|
149
|
+
* `queued` (handed to the server, not proof WhatsApp got them), `off` (the number's setting) or `none`
|
|
150
|
+
* (nothing was unread).
|
|
151
|
+
*/
|
|
152
|
+
markRead(chat: string): Promise<MarkReadResult>;
|
|
153
|
+
/**
|
|
154
|
+
* Shows "typing..." (or "recording audio..." with `state: 'recording'`) in a chat of this instance's number (a
|
|
155
|
+
* phone, a `cht_` id or a group JID). The server keeps it up until a message of the number goes out in that chat
|
|
156
|
+
* (no need to stop before sending), stopTyping(), or 2 minutes from the latest start (call it again to keep
|
|
157
|
+
* going). Refused on a paused number (NUMBER_PAUSED), in a group the number left (NOT_IN_GROUP) and on a number
|
|
158
|
+
* that isn't connected or connecting (NUMBER_NOT_CONNECTED).
|
|
159
|
+
*/
|
|
160
|
+
startTyping(chat: string, options?: TypingOptions): Promise<TypingResult>;
|
|
161
|
+
/** Stops this number's typing in a chat at once ("paused"). Safe to call when nothing is typing. */
|
|
162
|
+
stopTyping(chat: string): Promise<void>;
|
|
114
163
|
/** Every number of the project, any status. */
|
|
115
164
|
numbers(): Promise<WhatsAppNumber[]>;
|
|
116
165
|
/** One message of the project's numbers; 404 MESSAGE_NOT_FOUND otherwise. */
|
|
@@ -137,7 +186,7 @@ export declare class Whisp {
|
|
|
137
186
|
*/
|
|
138
187
|
downloadMedia(message: Message | string, options?: DownloadOptions): Promise<Response>;
|
|
139
188
|
/**
|
|
140
|
-
* Calls `handler` for each event of this name (12.4): any of the
|
|
189
|
+
* Calls `handler` for each event of this name (12.4): any of the public
|
|
141
190
|
* types, `'message'` (= `message.received`), `'*'`, or the stream's own
|
|
142
191
|
* `'open'`, `'close'`, `'gap'` and `'error'`. The first event handler
|
|
143
192
|
* opens the stream. Returns `off()`.
|
package/dist/cjs/client.js
CHANGED
|
@@ -17,6 +17,11 @@ exports.Whisp = exports.MEDIA_WAIT_MAX_MS = exports.DEFAULT_MAX_RETRIES = export
|
|
|
17
17
|
* that need one number require. `dailyCap` (phase3-design 12.4) goes with
|
|
18
18
|
* every send; the server counts and enforces it, never the SDK, since
|
|
19
19
|
* several app instances share one number.
|
|
20
|
+
*
|
|
21
|
+
* 0.3.0 (api-gaps-design 12, T6): reactions, edits, deletes for everyone,
|
|
22
|
+
* marking a chat read and typing presence, and `mentions` on text and
|
|
23
|
+
* caption sends. None of them carries an `Idempotency-Key`: each is
|
|
24
|
+
* idempotent by state on the server, so they are retried like reads.
|
|
20
25
|
*/
|
|
21
26
|
const constants_ts_1 = require("./constants.js");
|
|
22
27
|
const errors_ts_1 = require("./errors.js");
|
|
@@ -83,6 +88,23 @@ const normalizeFrom = (phone) => {
|
|
|
83
88
|
throw (0, errors_ts_1.localError)('OPTIONS_INVALID', `A pool has at most ${constants_ts_1.FROM_MAX} numbers`);
|
|
84
89
|
return refs;
|
|
85
90
|
};
|
|
91
|
+
/** `mentions` as the API takes them: a list of strings, checked here only for its shape (the server parses each). */
|
|
92
|
+
const checkMentions = (value, name) => {
|
|
93
|
+
if (value === undefined)
|
|
94
|
+
return undefined;
|
|
95
|
+
if (!Array.isArray(value) || value.length === 0 || value.some(item => typeof item !== 'string')) {
|
|
96
|
+
throw (0, errors_ts_1.localError)('OPTIONS_INVALID', `${name} must be a non-empty list of phones or <digits>@lid JIDs`);
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
};
|
|
100
|
+
/** A message's id: the message itself or its `msg_` id. */
|
|
101
|
+
const messageIdOf = (message, method) => {
|
|
102
|
+
const id = typeof message === 'string' ? message : message?.id;
|
|
103
|
+
if (typeof id !== 'string' || !/^msg_[0-9A-Za-z]{1,64}$/.test(id)) {
|
|
104
|
+
throw (0, errors_ts_1.localError)('OPTIONS_INVALID', `${method}() takes a message or its msg_ id`);
|
|
105
|
+
}
|
|
106
|
+
return id;
|
|
107
|
+
};
|
|
86
108
|
const waitMs = (wait) => wait === true ? constants_ts_1.WAIT_DEFAULT_MS : typeof wait === 'number' && Number.isFinite(wait) ? wait : 0;
|
|
87
109
|
const defaultReport = (error) => {
|
|
88
110
|
console.error(`[whisp-sdk] ${error.name}: ${error.message}`);
|
|
@@ -256,7 +278,8 @@ class Whisp {
|
|
|
256
278
|
const cap = checkDailyCap(perCall, 'dailyCap') ?? this.#options.dailyCap;
|
|
257
279
|
return cap === undefined ? {} : { dailyCap: cap };
|
|
258
280
|
}
|
|
259
|
-
|
|
281
|
+
/** The body fields every send shares; `mentions` only where the kind has text to carry them. */
|
|
282
|
+
#common(options, { mentions = true } = {}) {
|
|
260
283
|
const out = { ...this.#pool(), ...this.#cap(options.dailyCap) };
|
|
261
284
|
if (options.replyTo !== undefined)
|
|
262
285
|
out.replyTo = options.replyTo;
|
|
@@ -264,6 +287,9 @@ class Whisp {
|
|
|
264
287
|
out.reference = options.reference;
|
|
265
288
|
if (options.wait !== undefined)
|
|
266
289
|
out.wait = options.wait;
|
|
290
|
+
const listed = mentions ? checkMentions(options.mentions, 'mentions') : undefined;
|
|
291
|
+
if (listed !== undefined)
|
|
292
|
+
out.mentions = listed;
|
|
267
293
|
return out;
|
|
268
294
|
}
|
|
269
295
|
async #send(body, options) {
|
|
@@ -280,8 +306,8 @@ class Whisp {
|
|
|
280
306
|
return expect(answer, 'POST /messages', message => typeof message.id === 'string');
|
|
281
307
|
}
|
|
282
308
|
async #sendFile(kind, to, media, options, caption) {
|
|
283
|
-
// The options first: a bad one fails before any file is uploaded.
|
|
284
|
-
const common = this.#common(options);
|
|
309
|
+
// The options first: a bad one fails before any file is uploaded. A voice note never carries mentions.
|
|
310
|
+
const common = this.#common(options, { mentions: kind !== 'voice' });
|
|
285
311
|
let input;
|
|
286
312
|
if (isRecord(media) && typeof media.url === 'string') {
|
|
287
313
|
input = { url: media.url };
|
|
@@ -318,6 +344,90 @@ class Whisp {
|
|
|
318
344
|
}, upload.url, upload.headers, file);
|
|
319
345
|
return upload.uploadId;
|
|
320
346
|
}
|
|
347
|
+
// Message actions (0.3.0) ---------------------------------------------------------------------
|
|
348
|
+
/**
|
|
349
|
+
* Sets this number's reaction to a message (one emoji), replacing its earlier one. Resolves once the server
|
|
350
|
+
* queued it for the number's connection; a `message.reaction` event with `fromMe: true` follows.
|
|
351
|
+
*/
|
|
352
|
+
async react(message, emoji) {
|
|
353
|
+
if (typeof emoji !== 'string' || emoji === '') {
|
|
354
|
+
throw (0, errors_ts_1.localError)('OPTIONS_INVALID', 'react() takes one emoji; unreact() removes the reaction');
|
|
355
|
+
}
|
|
356
|
+
await this.#reaction(messageIdOf(message, 'react'), emoji);
|
|
357
|
+
}
|
|
358
|
+
/** Removes this number's reaction to a message. */
|
|
359
|
+
async unreact(message) {
|
|
360
|
+
await this.#reaction(messageIdOf(message, 'unreact'), '');
|
|
361
|
+
}
|
|
362
|
+
async #reaction(id, emoji) {
|
|
363
|
+
expect(await this.#call({ method: 'POST', path: `/messages/${segment(id)}/reaction`, body: { emoji } }), 'POST /messages/:messageId/reaction', body => body.accepted === true);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Edits the text or caption of a message this number sent, within WhatsApp's 15 minutes
|
|
367
|
+
* (EDIT_WINDOW_PASSED after). Refused on a paused number (NUMBER_PAUSED). Resolves the edited message; the
|
|
368
|
+
* same text without `mentions` resolves it unchanged.
|
|
369
|
+
*/
|
|
370
|
+
async edit(message, text, options = {}) {
|
|
371
|
+
const id = messageIdOf(message, 'edit');
|
|
372
|
+
if (typeof text !== 'string' || text === '')
|
|
373
|
+
throw (0, errors_ts_1.localError)('OPTIONS_INVALID', 'edit() takes the new text');
|
|
374
|
+
const body = { text };
|
|
375
|
+
const mentions = checkMentions(options.mentions, 'mentions');
|
|
376
|
+
if (mentions !== undefined)
|
|
377
|
+
body.mentions = mentions;
|
|
378
|
+
return expect(await this.#call({ method: 'POST', path: `/messages/${segment(id)}/edit`, body }), 'POST /messages/:messageId/edit', answer => typeof answer.id === 'string');
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Deletes a message this number sent for everyone, within 2 days (REVOKE_WINDOW_PASSED after). Resolves the
|
|
382
|
+
* message with `deletedAt` set, also when it was deleted already.
|
|
383
|
+
*/
|
|
384
|
+
async revoke(message) {
|
|
385
|
+
const id = messageIdOf(message, 'revoke');
|
|
386
|
+
return expect(
|
|
387
|
+
// An empty object: the server takes no body, and every runtime then sends a length.
|
|
388
|
+
await this.#call({ method: 'POST', path: `/messages/${segment(id)}/revoke`, body: {} }), 'POST /messages/:messageId/revoke', answer => typeof answer.id === 'string');
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Marks a chat of this instance's number read (a phone, a `cht_` id or a group JID). Unread is one count per
|
|
392
|
+
* chat, so this clears it for the panel's members too. `readReceipts` says what happened to the blue ticks:
|
|
393
|
+
* `queued` (handed to the server, not proof WhatsApp got them), `off` (the number's setting) or `none`
|
|
394
|
+
* (nothing was unread).
|
|
395
|
+
*/
|
|
396
|
+
async markRead(chat) {
|
|
397
|
+
const number = this.#single('markRead');
|
|
398
|
+
return expect(await this.#call({
|
|
399
|
+
method: 'POST',
|
|
400
|
+
path: `/numbers/${segment(number)}/chats/${segment(chat)}/read`,
|
|
401
|
+
body: {},
|
|
402
|
+
}), 'POST /numbers/:number/chats/:chat/read', body => isRecord(body.chat) && typeof body.readReceipts === 'string');
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Shows "typing..." (or "recording audio..." with `state: 'recording'`) in a chat of this instance's number (a
|
|
406
|
+
* phone, a `cht_` id or a group JID). The server keeps it up until a message of the number goes out in that chat
|
|
407
|
+
* (no need to stop before sending), stopTyping(), or 2 minutes from the latest start (call it again to keep
|
|
408
|
+
* going). Refused on a paused number (NUMBER_PAUSED), in a group the number left (NOT_IN_GROUP) and on a number
|
|
409
|
+
* that isn't connected or connecting (NUMBER_NOT_CONNECTED).
|
|
410
|
+
*/
|
|
411
|
+
async startTyping(chat, options = {}) {
|
|
412
|
+
const number = this.#single('startTyping');
|
|
413
|
+
const state = options.state ?? 'composing';
|
|
414
|
+
if (state !== 'composing' && state !== 'recording') {
|
|
415
|
+
throw (0, errors_ts_1.localError)('OPTIONS_INVALID', "startTyping()'s state must be 'composing' or 'recording'");
|
|
416
|
+
}
|
|
417
|
+
const body = { state };
|
|
418
|
+
return expect(await this.#call({ method: 'POST', path: `/numbers/${segment(number)}/chats/${segment(chat)}/typing`, body }), 'POST /numbers/:number/chats/:chat/typing', answer => typeof answer.state === 'string');
|
|
419
|
+
}
|
|
420
|
+
/** Stops this number's typing in a chat at once ("paused"). Safe to call when nothing is typing. */
|
|
421
|
+
async stopTyping(chat) {
|
|
422
|
+
const number = this.#single('stopTyping');
|
|
423
|
+
expect(
|
|
424
|
+
// An empty object: the server takes no body, and every runtime then sends a length.
|
|
425
|
+
await this.#call({
|
|
426
|
+
method: 'POST',
|
|
427
|
+
path: `/numbers/${segment(number)}/chats/${segment(chat)}/typing/stop`,
|
|
428
|
+
body: {},
|
|
429
|
+
}), 'POST /numbers/:number/chats/:chat/typing/stop', answer => answer.state === 'paused');
|
|
430
|
+
}
|
|
321
431
|
// Reads ---------------------------------------------------------------------------------------
|
|
322
432
|
/** Every number of the project, any status. */
|
|
323
433
|
async numbers() {
|
package/dist/cjs/constants.d.ts
CHANGED
|
@@ -39,5 +39,5 @@ export declare const UPLOAD_MAX_BYTES: {
|
|
|
39
39
|
readonly voice: number;
|
|
40
40
|
readonly document: number;
|
|
41
41
|
};
|
|
42
|
-
/** PLAN's twelve, in the order the API lists them. */
|
|
43
|
-
export declare const PUBLIC_EVENT_TYPES: readonly ["message.received", "message.sent", "message.status", "message.reaction", "message.deleted", "chat.updated", "presence.updated", "group.updated", "group.participants", "number.status", "number.risk", "call.received"];
|
|
42
|
+
/** PLAN's twelve, then `message.edited` (0.3.0), in the order the API lists them. */
|
|
43
|
+
export declare const PUBLIC_EVENT_TYPES: readonly ["message.received", "message.sent", "message.status", "message.reaction", "message.deleted", "chat.updated", "presence.updated", "group.updated", "group.participants", "number.status", "number.risk", "call.received", "message.edited"];
|
package/dist/cjs/constants.js
CHANGED
|
@@ -42,7 +42,7 @@ exports.UPLOAD_MAX_BYTES = {
|
|
|
42
42
|
voice: 16 * 1024 * 1024,
|
|
43
43
|
document: 100 * 1024 * 1024,
|
|
44
44
|
};
|
|
45
|
-
/** PLAN's twelve, in the order the API lists them. */
|
|
45
|
+
/** PLAN's twelve, then `message.edited` (0.3.0), in the order the API lists them. */
|
|
46
46
|
exports.PUBLIC_EVENT_TYPES = [
|
|
47
47
|
'message.received',
|
|
48
48
|
'message.sent',
|
|
@@ -56,4 +56,5 @@ exports.PUBLIC_EVENT_TYPES = [
|
|
|
56
56
|
'number.status',
|
|
57
57
|
'number.risk',
|
|
58
58
|
'call.received',
|
|
59
|
+
'message.edited',
|
|
59
60
|
];
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* await whisp.send('972521112233', 'Hello from Whisp')
|
|
10
10
|
* whisp.on('message', msg => console.log(msg.from, msg.text))
|
|
11
11
|
*/
|
|
12
|
-
export { DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, type DownloadOptions, type MessagesQuery, type Page, type PageResult, type SendFileOptions, type SendOptions, type VerifyResult, Whisp, type WhispOptions, } from './client.js';
|
|
12
|
+
export { DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, type DownloadOptions, type EditOptions, type MessagesQuery, type Page, type PageResult, type SendFileOptions, type SendOptions, type SendVoiceOptions, type VerifyResult, Whisp, type WhispOptions, } from './client.js';
|
|
13
13
|
export { API_TOKEN_ENV, API_URL_ENV, DEFAULT_BASE_URL, EVENT_CURSOR_PREFIX, PUBLIC_EVENT_TYPES, SDK_PACKAGE_NAME, SIGNATURE_HEADER, SIGNATURE_TOLERANCE_SECONDS, TOKEN_PREFIX, USER_AGENT_PREFIX, WEBHOOK_SECRET_PREFIX, } from './constants.js';
|
|
14
14
|
export { type ErrorParams, SDK_ERROR_CODES, type SdkErrorCode, type SignatureFailureReason, WhispConnectionError, WhispError, WhispSignatureError, } from './errors.js';
|
|
15
15
|
export type { AnyListenerEvent, EventHandlers, EventMeta, ListenerEvent, ListenerName, StreamClose, StreamGap, } from './listener.js';
|
package/dist/cjs/listener.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export type ListenerEvent<T extends PublicEventType> = Omit<EventData[T], 'numbe
|
|
|
28
28
|
numberId: string;
|
|
29
29
|
event: EventMeta<T>;
|
|
30
30
|
};
|
|
31
|
-
/** Any of the
|
|
31
|
+
/** Any of the public types; `event.event.type` says which. */
|
|
32
32
|
export type AnyListenerEvent = {
|
|
33
33
|
[T in PublicEventType]: ListenerEvent<T>;
|
|
34
34
|
}[PublicEventType];
|
|
@@ -51,7 +51,7 @@ export type EventHandlers = {
|
|
|
51
51
|
} & {
|
|
52
52
|
/** `message.received`. */
|
|
53
53
|
message: Handler<ListenerEvent<'message.received'>>;
|
|
54
|
-
/** Every one of the
|
|
54
|
+
/** Every one of the public types. */
|
|
55
55
|
'*': Handler<AnyListenerEvent>;
|
|
56
56
|
/** A connection reached `ready`. */
|
|
57
57
|
open: Handler<StreamReady>;
|
package/dist/cjs/listener.js
CHANGED
|
@@ -27,6 +27,7 @@ exports.Listener = exports.toListenerEvent = exports.RATE_LIMITED_WAIT_MS = expo
|
|
|
27
27
|
* `'error'` and the stream goes on. The cursor moves when an event's
|
|
28
28
|
* handlers have settled, and at once for an event nobody listens to.
|
|
29
29
|
*/
|
|
30
|
+
const constants_ts_1 = require("./constants.js");
|
|
30
31
|
const errors_ts_1 = require("./errors.js");
|
|
31
32
|
const runtime_ts_1 = require("./runtime.js");
|
|
32
33
|
const sse_ts_1 = require("./sse.js");
|
|
@@ -38,20 +39,8 @@ exports.WATCHDOG_MS = 45_000;
|
|
|
38
39
|
/** The wait after a 429 (TOO_MANY_STREAMS or RATE_LIMITED), unless `Retry-After` asks for longer (up to a minute). */
|
|
39
40
|
exports.RATE_LIMITED_WAIT_MS = 30_000;
|
|
40
41
|
const RATE_LIMITED_MAX_WAIT_MS = 60_000;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
'message.sent',
|
|
44
|
-
'message.status',
|
|
45
|
-
'message.reaction',
|
|
46
|
-
'message.deleted',
|
|
47
|
-
'chat.updated',
|
|
48
|
-
'presence.updated',
|
|
49
|
-
'group.updated',
|
|
50
|
-
'group.participants',
|
|
51
|
-
'number.status',
|
|
52
|
-
'number.risk',
|
|
53
|
-
'call.received',
|
|
54
|
-
]);
|
|
42
|
+
/** The types this version delivers; any other is skipped (its cursor still moves). */
|
|
43
|
+
const KNOWN_TYPES = new Set(constants_ts_1.PUBLIC_EVENT_TYPES);
|
|
55
44
|
const LIFECYCLE = new Set(['open', 'close', 'gap', 'error']);
|
|
56
45
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
57
46
|
const asError = (value) => (value instanceof Error ? value : new Error(String(value)));
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -34,8 +34,12 @@ export type ChatChange = 'unread' | 'done' | 'pinned' | 'assignee' | 'labels' |
|
|
|
34
34
|
export type CursorRejection = 'cursor_unknown' | 'cursor_expired';
|
|
35
35
|
/** Every message type but the inbox's own `waiting` stubs and `system` lines. */
|
|
36
36
|
export type MessageType = 'text' | 'image' | 'video' | 'audio' | 'voice' | 'document' | 'sticker' | 'location' | 'contact' | 'poll' | 'unsupported';
|
|
37
|
-
/** PLAN's twelve public event types (6.1). */
|
|
38
|
-
export type PublicEventType = 'message.received' | 'message.sent' | 'message.status' | 'message.reaction' | 'message.deleted' | 'chat.updated' | 'presence.updated' | 'group.updated' | 'group.participants' | 'number.status' | 'number.risk' | 'call.received';
|
|
37
|
+
/** PLAN's twelve public event types (6.1), then `message.edited` (0.3.0). */
|
|
38
|
+
export type PublicEventType = 'message.received' | 'message.sent' | 'message.status' | 'message.reaction' | 'message.deleted' | 'chat.updated' | 'presence.updated' | 'group.updated' | 'group.participants' | 'number.status' | 'number.risk' | 'call.received' | 'message.edited';
|
|
39
|
+
/** What marking a chat read did about read receipts: handed to the server (not delivery), off, or nothing unread. */
|
|
40
|
+
export type ReadReceiptOutcome = 'queued' | 'off' | 'none';
|
|
41
|
+
/** What startTyping shows: `composing` ("typing...") or `recording` ("recording audio..."). */
|
|
42
|
+
export type TypingStartState = 'composing' | 'recording';
|
|
39
43
|
/** Someone on WhatsApp. `phone` is null for a LID-only contact. */
|
|
40
44
|
export type Actor = {
|
|
41
45
|
phone: string | null;
|
|
@@ -179,6 +183,11 @@ export type Message = {
|
|
|
179
183
|
at: string;
|
|
180
184
|
editedAt: string | null;
|
|
181
185
|
deletedAt: string | null;
|
|
186
|
+
/**
|
|
187
|
+
* The people the text mentions, in order (0.3.0); `@` and the digits of their `phone` or `lid` mark each in
|
|
188
|
+
* `text`. Empty for none and for a deleted message.
|
|
189
|
+
*/
|
|
190
|
+
mentions: Actor[];
|
|
182
191
|
};
|
|
183
192
|
/** The number an event happened on. */
|
|
184
193
|
export type EventNumber = {
|
|
@@ -216,6 +225,19 @@ export type MessageDeletedData = {
|
|
|
216
225
|
reference: string | null;
|
|
217
226
|
at: string;
|
|
218
227
|
};
|
|
228
|
+
/** A message's text or caption changed, by its sender or by us (0.3.0). `at` is the edit's own time. */
|
|
229
|
+
export type MessageEditedData = {
|
|
230
|
+
messageId: string;
|
|
231
|
+
waId: string | null;
|
|
232
|
+
chatId: string;
|
|
233
|
+
fromMe: boolean;
|
|
234
|
+
/** The new text or caption. */
|
|
235
|
+
text: string | null;
|
|
236
|
+
/** The message's mentions after the edit. */
|
|
237
|
+
mentions: Actor[];
|
|
238
|
+
reference: string | null;
|
|
239
|
+
at: string;
|
|
240
|
+
};
|
|
219
241
|
export type ChatUpdatedData = {
|
|
220
242
|
chat: Chat;
|
|
221
243
|
changes: ChatChange[];
|
|
@@ -308,6 +330,7 @@ export type EventData = {
|
|
|
308
330
|
'number.status': NumberStatusData;
|
|
309
331
|
'number.risk': NumberRiskData;
|
|
310
332
|
'call.received': CallReceivedData;
|
|
333
|
+
'message.edited': MessageEditedData;
|
|
311
334
|
};
|
|
312
335
|
/** The envelope: the SSE `data:`, an item of `GET /events` and a webhook body are this same object. */
|
|
313
336
|
export type EventOf<T extends PublicEventType> = {
|
|
@@ -370,6 +393,7 @@ export type SendMessageBody = {
|
|
|
370
393
|
dailyCap?: number;
|
|
371
394
|
type?: 'text';
|
|
372
395
|
text: string;
|
|
396
|
+
mentions?: string[];
|
|
373
397
|
from?: string | string[];
|
|
374
398
|
strategy?: SendStrategy;
|
|
375
399
|
sticky?: boolean;
|
|
@@ -382,6 +406,7 @@ export type SendMessageBody = {
|
|
|
382
406
|
type: 'image' | 'video' | 'document';
|
|
383
407
|
media: MediaInput;
|
|
384
408
|
caption?: string;
|
|
409
|
+
mentions?: string[];
|
|
385
410
|
from?: string | string[];
|
|
386
411
|
strategy?: SendStrategy;
|
|
387
412
|
sticky?: boolean;
|
|
@@ -406,6 +431,7 @@ export type SendFromNumberBody = {
|
|
|
406
431
|
dailyCap?: number;
|
|
407
432
|
type?: 'text';
|
|
408
433
|
text: string;
|
|
434
|
+
mentions?: string[];
|
|
409
435
|
} | {
|
|
410
436
|
to: string;
|
|
411
437
|
replyTo?: string;
|
|
@@ -415,6 +441,7 @@ export type SendFromNumberBody = {
|
|
|
415
441
|
type: 'image' | 'video' | 'document';
|
|
416
442
|
media: MediaInput;
|
|
417
443
|
caption?: string;
|
|
444
|
+
mentions?: string[];
|
|
418
445
|
} | {
|
|
419
446
|
to: string;
|
|
420
447
|
replyTo?: string;
|
|
@@ -434,6 +461,7 @@ export type SendMediaFromNumberBody = {
|
|
|
434
461
|
type: 'image' | 'video' | 'document';
|
|
435
462
|
media: MediaInput;
|
|
436
463
|
caption?: string;
|
|
464
|
+
mentions?: string[];
|
|
437
465
|
} | {
|
|
438
466
|
to: string;
|
|
439
467
|
replyTo?: string;
|
|
@@ -508,6 +536,45 @@ export type Upload = {
|
|
|
508
536
|
headers: Record<string, string>;
|
|
509
537
|
expiresAt: string;
|
|
510
538
|
};
|
|
539
|
+
/** `POST /messages/:messageId/reaction`: one emoji, or `''` to remove ours. */
|
|
540
|
+
export type ReactionBody = {
|
|
541
|
+
emoji: string;
|
|
542
|
+
};
|
|
543
|
+
/**
|
|
544
|
+
* `POST /messages/:messageId/edit`: the new text or caption. `mentions` (phones or `<digits>@lid` JIDs, each
|
|
545
|
+
* written `@<digits>` in the text) replaces the message's; left out, the ones still named in the text are kept.
|
|
546
|
+
*/
|
|
547
|
+
export type EditMessageBody = {
|
|
548
|
+
text: string;
|
|
549
|
+
mentions?: string[];
|
|
550
|
+
};
|
|
551
|
+
/** 202 for a reaction: queued for the number's socket. */
|
|
552
|
+
export type ActionAccepted = {
|
|
553
|
+
accepted: true;
|
|
554
|
+
};
|
|
555
|
+
/** `POST /numbers/:number/chats/:chat/read`: the chat as it is now, and what happened to read receipts. */
|
|
556
|
+
export type MarkReadResult = {
|
|
557
|
+
chat: Chat;
|
|
558
|
+
readReceipts: ReadReceiptOutcome;
|
|
559
|
+
};
|
|
560
|
+
/** `POST /numbers/:number/chats/:chat/typing`: `composing` when left out. */
|
|
561
|
+
export type StartTypingBody = {
|
|
562
|
+
state?: TypingStartState;
|
|
563
|
+
};
|
|
564
|
+
/** startTyping's options. */
|
|
565
|
+
export interface TypingOptions {
|
|
566
|
+
/** `composing` (the default): "typing...". `recording`: "recording audio...". */
|
|
567
|
+
state?: TypingStartState;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* 202 from both typing routes: the state asked for (`paused` for a stop), and when the server stops refreshing it
|
|
571
|
+
* at the latest (2 minutes after this start). Null for a stop, and while the server's worker is an older build that
|
|
572
|
+
* shows typing once, with no keep-alive.
|
|
573
|
+
*/
|
|
574
|
+
export type TypingResult = {
|
|
575
|
+
state: TypingStartState | 'paused';
|
|
576
|
+
keptUntil: string | null;
|
|
577
|
+
};
|
|
511
578
|
/** Every error the API answers. `code` is stable; `params` carries the values it names. */
|
|
512
579
|
export type ApiError = {
|
|
513
580
|
code: string;
|
package/dist/cjs/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The package's version: sent in the User-Agent. packages/sdk/test/constants.test.ts keeps it equal to package.json's. */
|
|
2
|
-
export declare const SDK_VERSION = "0.1
|
|
2
|
+
export declare const SDK_VERSION = "0.3.1";
|
package/dist/cjs/version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SDK_VERSION = void 0;
|
|
4
4
|
/** The package's version: sent in the User-Agent. packages/sdk/test/constants.test.ts keeps it equal to package.json's. */
|
|
5
|
-
exports.SDK_VERSION = '0.1
|
|
5
|
+
exports.SDK_VERSION = '0.3.1';
|
package/dist/esm/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type EventHandlers } from './listener.js';
|
|
2
2
|
import { type MediaSource } from './media.js';
|
|
3
3
|
import { type Timers } from './runtime.js';
|
|
4
|
-
import type { Chat, ChatListQuery, ChatMessagesQuery, Message, PhoneCheck, ProjectInfo, PublicEventType, SendMessageBody, SendStrategy, WebhookEvent, WhatsAppNumber } from './types.js';
|
|
4
|
+
import type { Chat, ChatListQuery, ChatMessagesQuery, MarkReadResult, Message, PhoneCheck, ProjectInfo, PublicEventType, SendMessageBody, SendStrategy, TypingOptions, TypingResult, WebhookEvent, WhatsAppNumber } from './types.js';
|
|
5
5
|
import { type VerifyWebhookInput } from './webhook.js';
|
|
6
6
|
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
7
7
|
export declare const DEFAULT_MAX_RETRIES = 2;
|
|
@@ -59,10 +59,24 @@ export interface SendOptions {
|
|
|
59
59
|
dailyCap?: number;
|
|
60
60
|
/** Makes retries safe across your own restarts too. Default: a random UUID per call, kept across its retries. */
|
|
61
61
|
idempotencyKey?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Phones or `<digits>@lid` JIDs to mention (0.3.0), each written `@<digits>` in the text or caption. Groups
|
|
64
|
+
* only; at most 50.
|
|
65
|
+
*/
|
|
66
|
+
mentions?: string[];
|
|
62
67
|
}
|
|
63
68
|
export interface SendFileOptions extends SendOptions {
|
|
64
69
|
caption?: string;
|
|
65
70
|
}
|
|
71
|
+
/** A voice note has no text to carry `@<digits>`, so it takes no `mentions`. */
|
|
72
|
+
export type SendVoiceOptions = Omit<SendOptions, 'mentions'>;
|
|
73
|
+
export interface EditOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Replaces the message's mentions (phones or `<digits>@lid` JIDs, each written `@<digits>` in `text`). Left
|
|
76
|
+
* out, the mentions still named in the new text are kept.
|
|
77
|
+
*/
|
|
78
|
+
mentions?: string[];
|
|
79
|
+
}
|
|
66
80
|
export interface MessagesQuery {
|
|
67
81
|
reference: string;
|
|
68
82
|
}
|
|
@@ -105,12 +119,47 @@ export declare class Whisp {
|
|
|
105
119
|
sendImage(to: string, media: MediaSource, options?: SendFileOptions): Promise<Message>;
|
|
106
120
|
sendVideo(to: string, media: MediaSource, options?: SendFileOptions): Promise<Message>;
|
|
107
121
|
sendDocument(to: string, media: MediaSource, options?: SendFileOptions): Promise<Message>;
|
|
108
|
-
sendVoice(to: string, media: MediaSource, options?:
|
|
122
|
+
sendVoice(to: string, media: MediaSource, options?: SendVoiceOptions): Promise<Message>;
|
|
109
123
|
/**
|
|
110
124
|
* `POST /messages` with a body you build (5.6). The instance's `from`,
|
|
111
125
|
* `strategy`, `sticky` and `dailyCap` fill in what the body leaves out.
|
|
112
126
|
*/
|
|
113
127
|
sendMessage(body: SendMessageBody, options?: Pick<SendOptions, 'idempotencyKey'>): Promise<Message>;
|
|
128
|
+
/**
|
|
129
|
+
* Sets this number's reaction to a message (one emoji), replacing its earlier one. Resolves once the server
|
|
130
|
+
* queued it for the number's connection; a `message.reaction` event with `fromMe: true` follows.
|
|
131
|
+
*/
|
|
132
|
+
react(message: Message | string, emoji: string): Promise<void>;
|
|
133
|
+
/** Removes this number's reaction to a message. */
|
|
134
|
+
unreact(message: Message | string): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Edits the text or caption of a message this number sent, within WhatsApp's 15 minutes
|
|
137
|
+
* (EDIT_WINDOW_PASSED after). Refused on a paused number (NUMBER_PAUSED). Resolves the edited message; the
|
|
138
|
+
* same text without `mentions` resolves it unchanged.
|
|
139
|
+
*/
|
|
140
|
+
edit(message: Message | string, text: string, options?: EditOptions): Promise<Message>;
|
|
141
|
+
/**
|
|
142
|
+
* Deletes a message this number sent for everyone, within 2 days (REVOKE_WINDOW_PASSED after). Resolves the
|
|
143
|
+
* message with `deletedAt` set, also when it was deleted already.
|
|
144
|
+
*/
|
|
145
|
+
revoke(message: Message | string): Promise<Message>;
|
|
146
|
+
/**
|
|
147
|
+
* Marks a chat of this instance's number read (a phone, a `cht_` id or a group JID). Unread is one count per
|
|
148
|
+
* chat, so this clears it for the panel's members too. `readReceipts` says what happened to the blue ticks:
|
|
149
|
+
* `queued` (handed to the server, not proof WhatsApp got them), `off` (the number's setting) or `none`
|
|
150
|
+
* (nothing was unread).
|
|
151
|
+
*/
|
|
152
|
+
markRead(chat: string): Promise<MarkReadResult>;
|
|
153
|
+
/**
|
|
154
|
+
* Shows "typing..." (or "recording audio..." with `state: 'recording'`) in a chat of this instance's number (a
|
|
155
|
+
* phone, a `cht_` id or a group JID). The server keeps it up until a message of the number goes out in that chat
|
|
156
|
+
* (no need to stop before sending), stopTyping(), or 2 minutes from the latest start (call it again to keep
|
|
157
|
+
* going). Refused on a paused number (NUMBER_PAUSED), in a group the number left (NOT_IN_GROUP) and on a number
|
|
158
|
+
* that isn't connected or connecting (NUMBER_NOT_CONNECTED).
|
|
159
|
+
*/
|
|
160
|
+
startTyping(chat: string, options?: TypingOptions): Promise<TypingResult>;
|
|
161
|
+
/** Stops this number's typing in a chat at once ("paused"). Safe to call when nothing is typing. */
|
|
162
|
+
stopTyping(chat: string): Promise<void>;
|
|
114
163
|
/** Every number of the project, any status. */
|
|
115
164
|
numbers(): Promise<WhatsAppNumber[]>;
|
|
116
165
|
/** One message of the project's numbers; 404 MESSAGE_NOT_FOUND otherwise. */
|
|
@@ -137,7 +186,7 @@ export declare class Whisp {
|
|
|
137
186
|
*/
|
|
138
187
|
downloadMedia(message: Message | string, options?: DownloadOptions): Promise<Response>;
|
|
139
188
|
/**
|
|
140
|
-
* Calls `handler` for each event of this name (12.4): any of the
|
|
189
|
+
* Calls `handler` for each event of this name (12.4): any of the public
|
|
141
190
|
* types, `'message'` (= `message.received`), `'*'`, or the stream's own
|
|
142
191
|
* `'open'`, `'close'`, `'gap'` and `'error'`. The first event handler
|
|
143
192
|
* opens the stream. Returns `off()`.
|