@vanzxy/baileys 1.6.5 → 1.6.7
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.
Potentially problematic release.
This version of @vanzxy/baileys might be problematic. Click here for more details.
- package/README.md +565 -54
- package/WAProto/index.js +774 -1
- package/lib/Defaults/index.d.ts +5 -0
- package/lib/Defaults/index.js +21 -4
- package/lib/Utils/A2UI.js +63 -3
- package/lib/Utils/anti-delete.d.ts +6 -0
- package/lib/Utils/anti-delete.js +29 -0
- package/lib/Utils/auth-utils.js +11 -6
- package/lib/Utils/auto-reply.js +4 -1
- package/lib/Utils/baileys-event-stream.d.ts +8 -0
- package/lib/Utils/baileys-event-stream.js +62 -0
- package/lib/Utils/business.js +8 -0
- package/lib/Utils/button-sender.js +8 -1
- package/lib/Utils/chat-control.d.ts +159 -0
- package/lib/Utils/chat-control.js +232 -0
- package/lib/Utils/generics.d.ts +6 -0
- package/lib/Utils/generics.js +24 -0
- package/lib/Utils/index.d.ts +4 -0
- package/lib/Utils/index.js +4 -0
- package/lib/Utils/message-search.js +20 -5
- package/lib/Utils/messages.js +5 -0
- package/lib/Utils/past-participants.d.ts +14 -0
- package/lib/Utils/past-participants.js +40 -0
- package/lib/Utils/process-message.js +6 -2
- package/lib/Utils/stickerpack.js +10 -11
- package/lib/Utils/use-cache-manager-auth-state.d.ts +13 -0
- package/lib/Utils/use-cache-manager-auth-state.js +82 -0
- package/lib/Utils/validate-connection.js +2 -6
- package/lib/Utils/vcard.js +15 -4
- package/package.json +40 -17
- package/postinstall-banner.js +37 -5
- package/NOTICE.md +0 -91
package/lib/Defaults/index.d.ts
CHANGED
|
@@ -5,6 +5,11 @@ export const CALL_VIDEO_PREFIX: "https://call.whatsapp.com/video/";
|
|
|
5
5
|
export const CALL_AUDIO_PREFIX: "https://call.whatsapp.com/voice/";
|
|
6
6
|
export const DONATE_URL: "";
|
|
7
7
|
export const LIBRARY_NAME: "vanzxy/baileys";
|
|
8
|
+
export const COMPANION_DEVICE_VERSION: {
|
|
9
|
+
primary: number;
|
|
10
|
+
secondary: number;
|
|
11
|
+
tertiary: number;
|
|
12
|
+
};
|
|
8
13
|
export const DEF_CALLBACK_PREFIX: "CB:";
|
|
9
14
|
export const DEF_TAG_PREFIX: "TAG:";
|
|
10
15
|
export const PHONE_CONNECTION_CB: "CB:Pong";
|
package/lib/Defaults/index.js
CHANGED
|
@@ -2,10 +2,27 @@ import { proto } from '../../WAProto/index.js';
|
|
|
2
2
|
import { makeLibSignalRepository } from '../Signal/libsignal.js';
|
|
3
3
|
import { Browsers } from '../Utils/browser-utils.js';
|
|
4
4
|
import logger from '../Utils/logger.js';
|
|
5
|
-
// Vanz@Fix:
|
|
6
|
-
// —
|
|
7
|
-
//
|
|
8
|
-
|
|
5
|
+
// Vanz@Fix: disamain sama baseline yang dipake pas diff WAProto (lihat
|
|
6
|
+
// WAProto/CHANGELOG-proto-upgrade.md — sempet lebih baru dari fallback
|
|
7
|
+
// lama, jadi socket ngaku versi lebih tua dari field yang udah didukung
|
|
8
|
+
// proto-nya sendiri). Ini cuma fallback kalau fetchLatestBaileysVersion()
|
|
9
|
+
// gagal total (mis. GitHub gak bisa diakses) — tetap worth di-update
|
|
10
|
+
// berkala, dan runtime tetap disaranin pake fetchLatestWaWebVersion()
|
|
11
|
+
// dulu (parse sw.js langsung) baru fallback ke fetchLatestBaileysVersion()
|
|
12
|
+
// / konstanta ini, karena fetchLatestBaileysVersion resmi Baileys ada
|
|
13
|
+
// history bug isLatest:true padahal versinya stale (WhiskeySockets#2679).
|
|
14
|
+
const version = [2, 3000, 1046350168];
|
|
15
|
+
// Vanz@Fix (bug 65): this was an untracked, uncommented magic number inline
|
|
16
|
+
// inside generateRegistrationNode() in validate-connection.js — a *second*,
|
|
17
|
+
// independent hardcoded version (WA's "companion/device-props" version sent
|
|
18
|
+
// during device pairing, distinct from `version` above which is the WA Web
|
|
19
|
+
// client version used for appVersion/buildHash). It can go stale the exact
|
|
20
|
+
// same way `version` can (same failure mode as WhiskeySockets#2370/#2485),
|
|
21
|
+
// but unlike `version`, it isn't covered by fetchLatestWaWebVersion(),
|
|
22
|
+
// fetchLatestBaileysVersion(), or fetchBestWaVersion() — none of those touch
|
|
23
|
+
// it, so it was silently unmaintained. Named + documented here so it's at
|
|
24
|
+
// least visible and update-able; still worth bumping periodically by hand.
|
|
25
|
+
export const COMPANION_DEVICE_VERSION = { primary: 10, secondary: 15, tertiary: 7 };
|
|
9
26
|
export const UNAUTHORIZED_CODES = [401, 403, 419];
|
|
10
27
|
export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_upload_client_logs":false,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}';
|
|
11
28
|
export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
|
package/lib/Utils/A2UI.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"use strict";
|
|
17
17
|
|
|
18
18
|
import crypto from "crypto";
|
|
19
|
-
import { generateWAMessageFromContent } from "./messages.js";
|
|
19
|
+
import { generateWAMessageFromContent, prepareWAMessageMedia } from "./messages.js";
|
|
20
20
|
import { getBizBinaryNode } from "../WABinary/index.js";
|
|
21
21
|
|
|
22
22
|
class A2UI {
|
|
@@ -133,11 +133,45 @@ class A2UI {
|
|
|
133
133
|
};
|
|
134
134
|
return this;
|
|
135
135
|
}
|
|
136
|
+
// Vanz@Add 30-08-26 --- send(): terminal method so A2UI can be used as a one-liner like
|
|
137
|
+
// the other builders (Button/ButtonV2/AIRich `.send()`), instead of always needing the
|
|
138
|
+
// separate `sendA2UIWidget()` call. Thin wrapper only — root([...ids])/listCard() must
|
|
139
|
+
// still be called first, same as before build(); this doesn't change the id-returning
|
|
140
|
+
// factory API (child methods still return ids, not `this`, since sibling nodes need to
|
|
141
|
+
// reference each other by id when wiring children/trigger/content).
|
|
142
|
+
async send(client, jid, opts = {}) {
|
|
143
|
+
return sendA2UIWidget(client, jid, { ...opts, a2ui: this });
|
|
144
|
+
}
|
|
145
|
+
// Vanz@Add 30-08-26 --- validates that every structural id-reference (child/children on
|
|
146
|
+
// Button/Card/Column/Row, trigger/content on Modal) points at a component that was
|
|
147
|
+
// actually registered. Without this, a typo'd id just gets written into the wire payload
|
|
148
|
+
// as-is and the failure only surfaces as a broken/blank widget on the WA client, with no
|
|
149
|
+
// error on this end. Only covers the dedicated methods' known reference keys — raw()'s
|
|
150
|
+
// free-form props aren't inspected, since there's no way to know which of those are id
|
|
151
|
+
// references vs plain data.
|
|
152
|
+
#validateRefs(components) {
|
|
153
|
+
const validIds = new Set(components.map((c) => c.id));
|
|
154
|
+
for (const c of components) {
|
|
155
|
+
for (const key of ["child", "trigger", "content"]) {
|
|
156
|
+
if (c[key] !== undefined && !validIds.has(c[key])) {
|
|
157
|
+
throw new Error(`A2UI: component "${c.id}" (${c.component}) references unknown id "${c[key]}" via "${key}"`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (Array.isArray(c.children)) {
|
|
161
|
+
for (const childId of c.children) {
|
|
162
|
+
if (!validIds.has(childId)) {
|
|
163
|
+
throw new Error(`A2UI: component "${c.id}" (${c.component}) references unknown id "${childId}" in "children"`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
136
169
|
build({ uuid = crypto.randomUUID(), surfaceId, type = "im_a2ui", wrapped = true } = {}) {
|
|
137
170
|
if (this._listCardPayload) return this._listCardPayload;
|
|
138
171
|
if (!this._rootChildren.length) throw new Error("Call root([...ids]) before build()");
|
|
139
172
|
const root = { id: "root", component: "Column", children: this._rootChildren };
|
|
140
173
|
const components = [root, ...this._components.values()];
|
|
174
|
+
this.#validateRefs(components);
|
|
141
175
|
const data = wrapped
|
|
142
176
|
? {
|
|
143
177
|
version: this._version,
|
|
@@ -171,7 +205,13 @@ async function sendA2UIWidget(client, jid, {
|
|
|
171
205
|
quoted,
|
|
172
206
|
type = "im_a2ui",
|
|
173
207
|
wrapped = true,
|
|
174
|
-
singleScreen = false
|
|
208
|
+
singleScreen = false,
|
|
209
|
+
// Vanz@Add 30-08-26 --- optional header media, mirroring Button/ButtonV2's toCard()
|
|
210
|
+
// pattern (prepareWAMessageMedia + client.waUploadToServer). { title, subtitle, image
|
|
211
|
+
// | video | document: path/buffer/{url} } — media is mutually exclusive, image wins if
|
|
212
|
+
// more than one is passed. Falls back to a title/subtitle-only header (no attachment)
|
|
213
|
+
// when no media is given, same shape as before this change.
|
|
214
|
+
header
|
|
175
215
|
} = {}) {
|
|
176
216
|
if (!client) throw new Error("Socket is required");
|
|
177
217
|
if (!(a2ui instanceof A2UI)) throw new TypeError("a2ui must be an A2UI instance");
|
|
@@ -187,6 +227,26 @@ async function sendA2UIWidget(client, jid, {
|
|
|
187
227
|
}
|
|
188
228
|
: { messageParamsJson: "" };
|
|
189
229
|
|
|
230
|
+
const headerMediaData = header?.image
|
|
231
|
+
? { image: header.image }
|
|
232
|
+
: header?.video
|
|
233
|
+
? { video: header.video }
|
|
234
|
+
: header?.document
|
|
235
|
+
? { document: header.document }
|
|
236
|
+
: null;
|
|
237
|
+
|
|
238
|
+
const headerBlock = {
|
|
239
|
+
...(header?.title !== undefined ? { title: header.title } : {}),
|
|
240
|
+
...(header?.subtitle !== undefined ? { subtitle: header.subtitle } : {}),
|
|
241
|
+
hasMediaAttachment: !!headerMediaData,
|
|
242
|
+
...(headerMediaData
|
|
243
|
+
? await prepareWAMessageMedia(headerMediaData, { upload: client.waUploadToServer }).catch((e) => {
|
|
244
|
+
if (String(e).includes("Invalid media type")) return headerMediaData;
|
|
245
|
+
throw e;
|
|
246
|
+
})
|
|
247
|
+
: {})
|
|
248
|
+
};
|
|
249
|
+
|
|
190
250
|
const interactiveMessage = singleScreen
|
|
191
251
|
? {
|
|
192
252
|
nativeFlowMessage,
|
|
@@ -194,7 +254,7 @@ async function sendA2UIWidget(client, jid, {
|
|
|
194
254
|
...(expiration || Object.keys(contextInfo).length ? { contextInfo: { ...(expiration ? { expiration } : {}), ...contextInfo } } : {})
|
|
195
255
|
}
|
|
196
256
|
: {
|
|
197
|
-
header:
|
|
257
|
+
header: headerBlock,
|
|
198
258
|
body: { text: bodyText },
|
|
199
259
|
...(footer ? { footer: { text: footer } } : {}),
|
|
200
260
|
nativeFlowMessage,
|
|
@@ -50,6 +50,9 @@ export declare const createAntiDeleteHandler: (store: MessageStore, onDelete?: (
|
|
|
50
50
|
key: WAMessageKey;
|
|
51
51
|
update: Partial<WAMessage>;
|
|
52
52
|
}>) => DeletedMessageInfo[];
|
|
53
|
+
export declare const createAntiDeleteUpsertHandler: (store: MessageStore, onDelete?: (info: DeletedMessageInfo) => void) => ({ messages }: {
|
|
54
|
+
messages: WAMessage[];
|
|
55
|
+
}) => DeletedMessageInfo[];
|
|
53
56
|
export declare const createMessageStoreHandler: (store: MessageStore) => ({ messages }: {
|
|
54
57
|
messages: WAMessage[];
|
|
55
58
|
}) => void;
|
|
@@ -61,6 +64,9 @@ declare const _default: {
|
|
|
61
64
|
key: WAMessageKey;
|
|
62
65
|
update: Partial<WAMessage>;
|
|
63
66
|
}>) => DeletedMessageInfo[];
|
|
67
|
+
createAntiDeleteUpsertHandler: (store: MessageStore, onDelete?: (info: DeletedMessageInfo) => void) => ({ messages }: {
|
|
68
|
+
messages: WAMessage[];
|
|
69
|
+
}) => DeletedMessageInfo[];
|
|
64
70
|
createMessageStoreHandler: (store: MessageStore) => ({ messages }: {
|
|
65
71
|
messages: WAMessage[];
|
|
66
72
|
}) => void;
|
package/lib/Utils/anti-delete.js
CHANGED
|
@@ -156,6 +156,34 @@ export const createAntiDeleteHandler = (store, onDelete) => {
|
|
|
156
156
|
return deletedMessages;
|
|
157
157
|
};
|
|
158
158
|
};
|
|
159
|
+
/**
|
|
160
|
+
* Vanz@Fix (bug 58): `createAntiDeleteHandler` above only listens on
|
|
161
|
+
* `messages.update` with `messageStubType === REVOKE`. In practice, most
|
|
162
|
+
* delete-for-everyone events actually arrive as a *new* message on
|
|
163
|
+
* `messages.upsert` carrying a `protocolMessage` of type REVOKE (the
|
|
164
|
+
* `isDeleteMessage`/`getDeletedMessageKey` helpers above existed for this but
|
|
165
|
+
* were never wired to anything). Wire this into `sock.ev.on('messages.upsert', ...)`
|
|
166
|
+
* alongside `createMessageStoreHandler` to actually catch those.
|
|
167
|
+
*/
|
|
168
|
+
export const createAntiDeleteUpsertHandler = (store, onDelete) => {
|
|
169
|
+
return ({ messages }) => {
|
|
170
|
+
const deletedMessages = [];
|
|
171
|
+
for (const message of messages) {
|
|
172
|
+
if (!isDeleteMessage(message))
|
|
173
|
+
continue;
|
|
174
|
+
const key = getDeletedMessageKey(message);
|
|
175
|
+
if (!key)
|
|
176
|
+
continue;
|
|
177
|
+
const deletedBy = message.key.participant || message.key.remoteJid;
|
|
178
|
+
const info = store.markAsDeleted(key, deletedBy);
|
|
179
|
+
if (info) {
|
|
180
|
+
deletedMessages.push(info);
|
|
181
|
+
onDelete?.(info);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return deletedMessages;
|
|
185
|
+
};
|
|
186
|
+
};
|
|
159
187
|
/**
|
|
160
188
|
* Wire this into `sock.ev.on('messages.upsert', ...)` to keep `store` populated.
|
|
161
189
|
* Skips protocol/sender-key-distribution messages, which carry no user content.
|
|
@@ -180,6 +208,7 @@ export default {
|
|
|
180
208
|
isDeleteMessage,
|
|
181
209
|
getDeletedMessageKey,
|
|
182
210
|
createAntiDeleteHandler,
|
|
211
|
+
createAntiDeleteUpsertHandler,
|
|
183
212
|
createMessageStoreHandler
|
|
184
213
|
};
|
|
185
214
|
//# sourceMappingURL=anti-delete.js.map
|
package/lib/Utils/auth-utils.js
CHANGED
|
@@ -195,15 +195,20 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela
|
|
|
195
195
|
if (!ctx) {
|
|
196
196
|
// No transaction - direct write with queue protection
|
|
197
197
|
const types = Object.keys(data);
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
// Vanz@Fix (bug 66): validateDeletions() used to run *before* this,
|
|
199
|
+
// serialized only against PreKeyManager's own internal queue —
|
|
200
|
+
// a separate PQueue instance from getQueue(type) below, both keyed
|
|
201
|
+
// 'pre-key' but never synchronized with each other. Two concurrent
|
|
202
|
+
// set() calls for pre-key data could validate against a store state
|
|
203
|
+
// that a still-pending write from the other call hadn't applied yet,
|
|
204
|
+
// letting a stale deletion go through or a just-deleted key get
|
|
205
|
+
// resurrected by data racing back in. Validation now happens inside
|
|
206
|
+
// the same per-type queue job as the write, so it's ordered against
|
|
207
|
+
// any other set() for that type instead of racing it.
|
|
208
|
+
await Promise.all(types.map(type => getQueue(type).add(async () => {
|
|
201
209
|
if (type === 'pre-key') {
|
|
202
210
|
await preKeyManager.validateDeletions(data, type);
|
|
203
211
|
}
|
|
204
|
-
}
|
|
205
|
-
// Write all data in parallel
|
|
206
|
-
await Promise.all(types.map(type => getQueue(type).add(async () => {
|
|
207
212
|
const typeData = { [type]: data[type] };
|
|
208
213
|
await state.set(typeData);
|
|
209
214
|
})));
|
package/lib/Utils/auto-reply.js
CHANGED
|
@@ -63,7 +63,6 @@ export class AutoReplyHandler {
|
|
|
63
63
|
}
|
|
64
64
|
setCooldown(ruleId, jid, cooldown) {
|
|
65
65
|
this.cooldowns.set(`${ruleId}:${jid}`, Date.now() + cooldown);
|
|
66
|
-
this.globalCooldown.set(jid, Date.now());
|
|
67
66
|
}
|
|
68
67
|
matchRule(text, rule) {
|
|
69
68
|
if (!rule.active)
|
|
@@ -137,6 +136,10 @@ export class AutoReplyHandler {
|
|
|
137
136
|
await this.sendPresence(jid, 'paused');
|
|
138
137
|
}
|
|
139
138
|
await this.sendMessage(jid, response, rule.quoted ? { quoted: message } : undefined);
|
|
139
|
+
// Vanz@Fix (bug 60): global cooldown used to only get stamped inside
|
|
140
|
+
// setCooldown(), which only ran when the matched rule had its own
|
|
141
|
+
// cooldown — so rules without one never fed the global anti-spam gate.
|
|
142
|
+
this.globalCooldown.set(jid, Date.now());
|
|
140
143
|
if (rule.cooldown)
|
|
141
144
|
this.setCooldown(rule.id, jid, rule.cooldown);
|
|
142
145
|
this.options.onReply(rule, message, response);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BaileysEventEmitter } from '../Types/index.js';
|
|
2
|
+
/** Monkey-patches ev.emit to append every event as an NDJSON line to `filename`. */
|
|
3
|
+
export declare const captureEventStream: (ev: BaileysEventEmitter, filename: string) => void;
|
|
4
|
+
/** Reads an NDJSON file written by captureEventStream and replays each event on a new EventEmitter. */
|
|
5
|
+
export declare const readAndEmitEventStream: (filename: string, delayIntervalMs?: number) => {
|
|
6
|
+
ev: BaileysEventEmitter;
|
|
7
|
+
task: Promise<void>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Ported from @queenanya/baileys `addons/baileys-event-stream.ts`.
|
|
2
|
+
// Adjustments for @vanzxy/baileys: import paths rewired to this fork's own
|
|
3
|
+
// `generics.js` (delay) and `make-mutex.js` (makeMutex) — both already
|
|
4
|
+
// present, no logic changes otherwise.
|
|
5
|
+
import EventEmitter from 'events';
|
|
6
|
+
import { createReadStream } from 'fs';
|
|
7
|
+
import { writeFile } from 'fs/promises';
|
|
8
|
+
import { createInterface } from 'readline';
|
|
9
|
+
import { delay } from './generics.js';
|
|
10
|
+
import { makeMutex } from './make-mutex.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Monkey-patches `ev.emit` to append every Baileys event as a JSON line
|
|
14
|
+
* (NDJSON) to `filename`. Useful for debugging or replaying sessions.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* captureEventStream(sock.ev, './events.ndjson')
|
|
18
|
+
*/
|
|
19
|
+
export const captureEventStream = (ev, filename) => {
|
|
20
|
+
const originalEmit = ev.emit.bind(ev);
|
|
21
|
+
const writeMutex = makeMutex();
|
|
22
|
+
const patchedEmit = (event, ...rest) => {
|
|
23
|
+
const line = JSON.stringify({ timestamp: Date.now(), event, data: rest[0] }) + '\n';
|
|
24
|
+
const result = originalEmit(event, ...rest);
|
|
25
|
+
void writeMutex.mutex(async () => {
|
|
26
|
+
await writeFile(filename, line, { flag: 'a' });
|
|
27
|
+
});
|
|
28
|
+
return result;
|
|
29
|
+
};
|
|
30
|
+
ev.emit = patchedEmit;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reads an NDJSON file written by {@link captureEventStream} and replays
|
|
35
|
+
* each event on a new EventEmitter.
|
|
36
|
+
*
|
|
37
|
+
* @param filename Path to the NDJSON file.
|
|
38
|
+
* @param delayIntervalMs Milliseconds to wait between events (default 0).
|
|
39
|
+
* @returns `{ ev, task }` — ev is the emitter, task resolves when done.
|
|
40
|
+
*/
|
|
41
|
+
export const readAndEmitEventStream = (filename, delayIntervalMs = 0) => {
|
|
42
|
+
const ev = new EventEmitter();
|
|
43
|
+
const fireEvents = async () => {
|
|
44
|
+
const fileStream = createReadStream(filename);
|
|
45
|
+
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
46
|
+
for await (const line of rl) {
|
|
47
|
+
if (!line.trim())
|
|
48
|
+
continue;
|
|
49
|
+
try {
|
|
50
|
+
const { event, data } = JSON.parse(line);
|
|
51
|
+
ev.emit(event, data);
|
|
52
|
+
if (delayIntervalMs)
|
|
53
|
+
await delay(delayIntervalMs);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// skip malformed lines
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
fileStream.destroy();
|
|
60
|
+
};
|
|
61
|
+
return { ev, task: fireEvents() };
|
|
62
|
+
};
|
package/lib/Utils/business.js
CHANGED
|
@@ -200,10 +200,18 @@ export const uploadingNecessaryImages = async (images, waUploadToServer, timeout
|
|
|
200
200
|
const hasher = createHash('sha256');
|
|
201
201
|
const filePath = join(tmpdir(), 'img' + generateMessageIDV2());
|
|
202
202
|
const encFileWriteStream = createWriteStream(filePath);
|
|
203
|
+
// Vanz@Fix (bug 61): no 'error' listener meant a write failure (e.g. disk
|
|
204
|
+
// full) would surface as an unhandled 'error' event instead of rejecting
|
|
205
|
+
// this promise; and the stream was never closed before unlink() below.
|
|
206
|
+
const writeStreamError = new Promise((_, reject) => encFileWriteStream.on('error', reject));
|
|
203
207
|
for await (const block of stream) {
|
|
204
208
|
hasher.update(block);
|
|
205
209
|
encFileWriteStream.write(block);
|
|
206
210
|
}
|
|
211
|
+
await Promise.race([
|
|
212
|
+
new Promise((resolve) => encFileWriteStream.end(resolve)),
|
|
213
|
+
writeStreamError
|
|
214
|
+
]);
|
|
207
215
|
const sha = hasher.digest('base64');
|
|
208
216
|
const { directPath } = await waUploadToServer(filePath, {
|
|
209
217
|
mediaType: 'product-catalog-image',
|
|
@@ -491,8 +491,15 @@ export function convertToInteractiveMessage(content) {
|
|
|
491
491
|
}
|
|
492
492
|
};
|
|
493
493
|
|
|
494
|
+
// Vanz@Fix 30-08-26 --- subtitle was being used only as a fallback for a missing
|
|
495
|
+
// title, then discarded — if BOTH title and subtitle were provided, subtitle never
|
|
496
|
+
// made it into the header at all (silently dropped, despite header.subtitle being a
|
|
497
|
+
// real proto field per MessageBuilder.js's toCard()). Write both independently.
|
|
494
498
|
if (content.title || content.subtitle) {
|
|
495
|
-
interactiveMessage.header = {
|
|
499
|
+
interactiveMessage.header = {
|
|
500
|
+
title: content.title ?? content.subtitle ?? '',
|
|
501
|
+
...(content.title && content.subtitle ? { subtitle: content.subtitle } : {})
|
|
502
|
+
};
|
|
496
503
|
}
|
|
497
504
|
|
|
498
505
|
if (content.text) {
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat Control Utilities
|
|
3
|
+
*
|
|
4
|
+
* Source: @innovatorssoft/baileys (chat-control.js)
|
|
5
|
+
* Rewritten as clean TypeScript with full types and JSDoc.
|
|
6
|
+
*
|
|
7
|
+
* Three components:
|
|
8
|
+
* - TypingIndicator — composing / recording presence helpers
|
|
9
|
+
* - PinnedMessagesManager — client-side pin tracking
|
|
10
|
+
* - ReadReceiptController — configurable automatic read receipts
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Standard disappearing-message duration constants (in seconds).
|
|
14
|
+
* Pass to `sock.sendMessage(jid, { disappearingMessagesInChat: DISAPPEARING_DURATIONS.DAYS_7 })`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DISAPPEARING_DURATIONS: {
|
|
17
|
+
/** Disable disappearing messages */
|
|
18
|
+
readonly OFF: 0;
|
|
19
|
+
/** 24 hours */
|
|
20
|
+
readonly HOURS_24: 86400;
|
|
21
|
+
/** 7 days */
|
|
22
|
+
readonly DAYS_7: 604800;
|
|
23
|
+
/** 90 days */
|
|
24
|
+
readonly DAYS_90: 7776000;
|
|
25
|
+
};
|
|
26
|
+
export type DisappearingDuration = (typeof DISAPPEARING_DURATIONS)[keyof typeof DISAPPEARING_DURATIONS];
|
|
27
|
+
type PresenceType = 'composing' | 'recording' | 'paused' | 'available' | 'unavailable';
|
|
28
|
+
type SendPresence = (jid: string, presence: PresenceType) => Promise<void>;
|
|
29
|
+
type TypingOptions = {
|
|
30
|
+
/** Auto-stop after this many ms (default: no auto-stop) */
|
|
31
|
+
duration?: number;
|
|
32
|
+
/** Whether to auto-pause on timeout — default `true` */
|
|
33
|
+
autoPause?: boolean;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Manages composing ("typing...") and recording ("recording...") presence
|
|
37
|
+
* indicators with per-JID timer tracking.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const typing = createTypingIndicator(
|
|
41
|
+
* (jid, presence) => sock.sendPresenceUpdate(presence, jid)
|
|
42
|
+
* )
|
|
43
|
+
*
|
|
44
|
+
* // Simulate typing then send a message
|
|
45
|
+
* const result = await typing.simulateTyping(jid, 1500, () =>
|
|
46
|
+
* sock.sendMessage(jid, { text: 'Hello!' })
|
|
47
|
+
* )
|
|
48
|
+
*/
|
|
49
|
+
export declare class TypingIndicator {
|
|
50
|
+
private readonly sendPresence;
|
|
51
|
+
private readonly timers;
|
|
52
|
+
constructor(sendPresence: SendPresence);
|
|
53
|
+
/** Show the "typing..." (composing) indicator for a JID. */
|
|
54
|
+
startTyping(jid: string, options?: TypingOptions): Promise<void>;
|
|
55
|
+
/** Show the "recording..." (audio/video) indicator for a JID. */
|
|
56
|
+
startRecording(jid: string, options?: TypingOptions): Promise<void>;
|
|
57
|
+
/** Stop any active composing/recording indicator for a JID. */
|
|
58
|
+
stopTyping(jid: string): Promise<void>;
|
|
59
|
+
/** Stop all active indicators. */
|
|
60
|
+
stopAll(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Show typing for `durationMs`, run `callback`, then stop the indicator.
|
|
63
|
+
*
|
|
64
|
+
* @template T
|
|
65
|
+
* @returns The return value of `callback`
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* await typing.simulateTyping(jid, 2000, async () => {
|
|
69
|
+
* await sock.sendMessage(jid, { text: 'Here is your answer' })
|
|
70
|
+
* })
|
|
71
|
+
*/
|
|
72
|
+
simulateTyping<T>(jid: string, durationMs: number, callback: () => Promise<T> | T): Promise<T>;
|
|
73
|
+
private clearTimer;
|
|
74
|
+
}
|
|
75
|
+
/** Factory — create a TypingIndicator. */
|
|
76
|
+
export declare const createTypingIndicator: (sendPresence: SendPresence) => TypingIndicator;
|
|
77
|
+
export type PinnedMessage = {
|
|
78
|
+
messageId: string;
|
|
79
|
+
jid: string;
|
|
80
|
+
pinnedAt: Date;
|
|
81
|
+
pinnedBy?: string;
|
|
82
|
+
expiresAt?: Date;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Client-side tracker for pinned messages.
|
|
86
|
+
* Listen to `messages.update` for `pinInChatMessage` protocol messages and call
|
|
87
|
+
* `manager.pin(jid, msgId, pinnedBy)` / `manager.unpin(jid, msgId)` accordingly.
|
|
88
|
+
*/
|
|
89
|
+
export declare class PinnedMessagesManager {
|
|
90
|
+
private readonly store;
|
|
91
|
+
/**
|
|
92
|
+
* Record a newly pinned message.
|
|
93
|
+
* @returns The created pin entry
|
|
94
|
+
*/
|
|
95
|
+
pin(jid: string, messageId: string, pinnedBy?: string, expiresAt?: Date): PinnedMessage;
|
|
96
|
+
/**
|
|
97
|
+
* Remove a pinned message.
|
|
98
|
+
* @returns `true` if the pin was found and removed, `false` otherwise
|
|
99
|
+
*/
|
|
100
|
+
unpin(jid: string, messageId: string): boolean;
|
|
101
|
+
/** Get all pinned messages for a chat. */
|
|
102
|
+
getPinned(jid: string): PinnedMessage[];
|
|
103
|
+
/** Check if a message is pinned in a chat. */
|
|
104
|
+
isPinned(jid: string, messageId: string): boolean;
|
|
105
|
+
/** Remove all pins for a chat. */
|
|
106
|
+
clearPins(jid: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Evict pins whose `expiresAt` is in the past.
|
|
109
|
+
* @returns Number of expired pins removed
|
|
110
|
+
*/
|
|
111
|
+
clearExpired(): number;
|
|
112
|
+
/** Total pin count across all chats. */
|
|
113
|
+
get totalPins(): number;
|
|
114
|
+
}
|
|
115
|
+
/** Factory — create a PinnedMessagesManager. */
|
|
116
|
+
export declare const createPinnedMessagesManager: () => PinnedMessagesManager;
|
|
117
|
+
export type ReadReceiptConfig = {
|
|
118
|
+
/** Whether to send read receipts at all (default: `true`) */
|
|
119
|
+
enabled?: boolean;
|
|
120
|
+
/** JIDs to never send receipts for */
|
|
121
|
+
excludeJids?: string[];
|
|
122
|
+
/** Delay before marking as read in ms (default: `0`) */
|
|
123
|
+
readDelay?: number;
|
|
124
|
+
};
|
|
125
|
+
export type ReadReceiptController = {
|
|
126
|
+
setConfig(config: Partial<ReadReceiptConfig>): void;
|
|
127
|
+
getConfig(): Required<ReadReceiptConfig>;
|
|
128
|
+
enable(): void;
|
|
129
|
+
disable(): void;
|
|
130
|
+
isEnabled(): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Mark messages as read, respecting the current config.
|
|
133
|
+
* No-op if disabled or JID is excluded.
|
|
134
|
+
*/
|
|
135
|
+
markRead(jid: string, participant: string | null | undefined, messageIds: string[]): Promise<void>;
|
|
136
|
+
/** Mark messages as read regardless of config. */
|
|
137
|
+
forceMarkRead(jid: string, participant: string | null | undefined, messageIds: string[]): Promise<void>;
|
|
138
|
+
};
|
|
139
|
+
type SendReadReceipt = (jid: string, participant: string | null | undefined, messageIds: string[]) => Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* Create a read-receipt controller with optional auto-delay and per-JID exclusions.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* const readCtrl = createReadReceiptController(
|
|
145
|
+
* (jid, participant, ids) => sock.readMessages(ids.map(id => ({ remoteJid: jid, id, participant }))),
|
|
146
|
+
* { enabled: true, readDelay: 500, excludeJids: [spamJid] }
|
|
147
|
+
* )
|
|
148
|
+
*
|
|
149
|
+
* sock.ev.on('messages.upsert', ({ messages }) => {
|
|
150
|
+
* for (const msg of messages) {
|
|
151
|
+
* const { key } = msg
|
|
152
|
+
* if (!key.fromMe)
|
|
153
|
+
* readCtrl.markRead(key.remoteJid!, key.participant, [key.id!])
|
|
154
|
+
* }
|
|
155
|
+
* })
|
|
156
|
+
*/
|
|
157
|
+
export declare const createReadReceiptController: (sendReadReceipt: SendReadReceipt, config?: ReadReceiptConfig) => ReadReceiptController;
|
|
158
|
+
export {};
|
|
159
|
+
//# sourceMappingURL=chat-control.d.ts.map
|