@vanzxy/baileys 1.5.8 → 1.6.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/lib/Socket/chats.js
CHANGED
|
@@ -712,21 +712,46 @@ export const makeChatsSocket = (config) => {
|
|
|
712
712
|
// has a courtesy retry via blockedCollections + the creds.update listener below — the key
|
|
713
713
|
// arrives async after connect (APP_STATE_SYNC_KEY_SHARE), so calling e.g. updateProfileName()
|
|
714
714
|
// right after connection.update === 'open' would reliably fail on a fresh/early connection.
|
|
715
|
-
// Now appPatch() waits (bounded,
|
|
715
|
+
// Now appPatch() waits (bounded, 20s default) for the same creds.update event before giving up,
|
|
716
716
|
// instead of failing hard on what's usually just a timing race.
|
|
717
|
-
|
|
717
|
+
//
|
|
718
|
+
// Vanz@Fix 25-08-26 --- the event-only wait had a blind spot: if `creds.update` never actually
|
|
719
|
+
// reaches this listener (swallowed/filtered by a wrapper around `ev`, a custom auth-state
|
|
720
|
+
// adapter that patches `authState.creds` directly without re-emitting the event, etc.) this
|
|
721
|
+
// would sit for the full timeout and fail even though `authState.creds.myAppStateKeyId` was
|
|
722
|
+
// genuinely set the whole time. Added a belt-and-suspenders poll of the creds object itself
|
|
723
|
+
// alongside the event listener — whichever one notices the key first wins, and both are torn
|
|
724
|
+
// down together so there's no leaked timer/listener either way.
|
|
725
|
+
const waitForAppStateKeyId = (timeoutMs = 20_000, pollMs = 500) => {
|
|
718
726
|
if (authState.creds.myAppStateKeyId) {
|
|
719
727
|
return Promise.resolve(authState.creds.myAppStateKeyId);
|
|
720
728
|
}
|
|
721
729
|
return new Promise((resolve, reject) => {
|
|
722
|
-
|
|
730
|
+
let settled = false;
|
|
731
|
+
const cleanup = () => {
|
|
732
|
+
settled = true;
|
|
733
|
+
clearTimeout(timeout);
|
|
734
|
+
clearInterval(poller);
|
|
723
735
|
ev.off('creds.update', onUpdate);
|
|
736
|
+
};
|
|
737
|
+
const timeout = setTimeout(() => {
|
|
738
|
+
if (settled) return;
|
|
739
|
+
cleanup();
|
|
740
|
+
logger.warn(
|
|
741
|
+
{ hasMyAppStateKeyId: !!authState.creds.myAppStateKeyId, registered: authState.creds.registered },
|
|
742
|
+
'waitForAppStateKeyId timed out — APP_STATE_SYNC_KEY_SHARE was never received/applied for this session'
|
|
743
|
+
);
|
|
724
744
|
reject(new Boom('App state key not present!', { statusCode: 400 }));
|
|
725
745
|
}, timeoutMs);
|
|
746
|
+
const poller = setInterval(() => {
|
|
747
|
+
if (settled || !authState.creds.myAppStateKeyId) return;
|
|
748
|
+
const id = authState.creds.myAppStateKeyId;
|
|
749
|
+
cleanup();
|
|
750
|
+
resolve(id);
|
|
751
|
+
}, pollMs);
|
|
726
752
|
const onUpdate = ({ myAppStateKeyId }) => {
|
|
727
|
-
if (!myAppStateKeyId) return;
|
|
728
|
-
|
|
729
|
-
ev.off('creds.update', onUpdate);
|
|
753
|
+
if (settled || !myAppStateKeyId) return;
|
|
754
|
+
cleanup();
|
|
730
755
|
resolve(myAppStateKeyId);
|
|
731
756
|
};
|
|
732
757
|
ev.on('creds.update', onUpdate);
|
|
@@ -2456,6 +2456,49 @@ class AIRich extends BaseBuilder {
|
|
|
2456
2456
|
return this;
|
|
2457
2457
|
}
|
|
2458
2458
|
|
|
2459
|
+
// Vanz@Add 25-08-26 --- ported from temen's MessageBuilderV4.7 (hasId/getIds/peek/delete).
|
|
2460
|
+
// That fork tracked every block in a unified `_nodes` array so query/delete-by-id was free;
|
|
2461
|
+
// this fork instead tracks blocks in the `_blocks` Map (id -> {subItems, secItems}, populated
|
|
2462
|
+
// by the constructor's Proxy on every add*/set* call that passes {id}) but never exposed a way
|
|
2463
|
+
// to query or undo one after the fact — you could insertAt an id but never inspect, check, or
|
|
2464
|
+
// remove it. These 4 read/delete that same Map, so no changes to the Proxy itself were needed.
|
|
2465
|
+
|
|
2466
|
+
/** Check whether a block id was registered by an earlier `add*()`/`set*()` call passing `{id}`. */
|
|
2467
|
+
hasId(id) {
|
|
2468
|
+
return typeof id === 'string' && this._blocks.has(id);
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
/** List every block id registered so far, in no particular order. */
|
|
2472
|
+
getIds() {
|
|
2473
|
+
return [...this._blocks.keys()];
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
/** Inspect a registered block without modifying it. Returns `null` if `id` isn't registered. */
|
|
2477
|
+
peek(id) {
|
|
2478
|
+
const block = this._blocks.get(id);
|
|
2479
|
+
if (!block) return null;
|
|
2480
|
+
|
|
2481
|
+
return { id, sections: [...block.secItems], submessages: [...block.subItems] };
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
/** Remove a previously-added block (by the `id` passed to its `add*()`/`set*()` call) from the message. Throws if `id` isn't registered. */
|
|
2485
|
+
delete(id) {
|
|
2486
|
+
const block = this._blocks.get(id);
|
|
2487
|
+
if (!block) throw new Error(`delete(id): no block registered with id "${id}"`);
|
|
2488
|
+
|
|
2489
|
+
for (const item of block.subItems) {
|
|
2490
|
+
const idx = this._submessages.indexOf(item);
|
|
2491
|
+
if (idx !== -1) this._submessages.splice(idx, 1);
|
|
2492
|
+
}
|
|
2493
|
+
for (const item of block.secItems) {
|
|
2494
|
+
const idx = this._sections.indexOf(item);
|
|
2495
|
+
if (idx !== -1) this._sections.splice(idx, 1);
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
this._blocks.delete(id);
|
|
2499
|
+
return this;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2459
2502
|
/** Add a small metadata-style text line (`GenAIMetadataTextPrimitive`) — same visual style as the auto-appended footer/`addTip()`'s callout, but insertable anywhere and without `addTip()`'s icon prefix. */
|
|
2460
2503
|
addMetadata(text) {
|
|
2461
2504
|
if (typeof text !== 'string' || !text) throw new TypeError('addMetadata(text) requires a non-empty string');
|
|
@@ -2496,11 +2539,14 @@ class AIRich extends BaseBuilder {
|
|
|
2496
2539
|
return this;
|
|
2497
2540
|
}
|
|
2498
2541
|
|
|
2499
|
-
// Vanz@Add 22-08-26 (v4.7) --- addHeading/
|
|
2542
|
+
// Vanz@Add 22-08-26 (v4.7) --- addHeading/addWidget/addFooterAction: 3 primitives
|
|
2500
2543
|
// reverse-engineered from captured Meta-AI-in-WhatsApp traffic that this project's own crm/snip
|
|
2501
2544
|
// tooling (see rich-message-utils.js) dumps for study. Not in any public Baileys schema, so
|
|
2502
2545
|
// unknown enum values (kind/state on addWidget's ctas) are passed through as observed rather
|
|
2503
2546
|
// than guessed at, and documented as experimental below.
|
|
2547
|
+
// Vanz@Fix 25-08-26 --- removed addImageCard(): its GenAIImagePrimitive/preview_image+full_image
|
|
2548
|
+
// shape was mis-reverse-engineered (not a real WA schema) and crashed the client renderer on
|
|
2549
|
+
// arrival. addImage() already covers static image cards correctly — use that instead.
|
|
2504
2550
|
|
|
2505
2551
|
/** Add a large heading-style text block (`FOATextPrimitive`) — visually distinct from `addText()`'s regular paragraph text. */
|
|
2506
2552
|
addHeading(text) {
|
|
@@ -2523,39 +2569,6 @@ class AIRich extends BaseBuilder {
|
|
|
2523
2569
|
return this;
|
|
2524
2570
|
}
|
|
2525
2571
|
|
|
2526
|
-
/**
|
|
2527
|
-
* Add a "ready" static image card (`GenAIImagePrimitive`: preview + full-res, no generating/status
|
|
2528
|
-
* state) — distinct from `addImage()`'s AI-generation-style `GenAIImaginePrimitive`.
|
|
2529
|
-
* @param {string|Buffer} previewUrl Preview/thumbnail image.
|
|
2530
|
-
* @param {string|Buffer} [fullUrl] Full-resolution image; defaults to `previewUrl`.
|
|
2531
|
-
*/
|
|
2532
|
-
addImageCard(previewUrl, fullUrl = previewUrl, { resolveUrl = false } = {}) {
|
|
2533
|
-
if (!(typeof previewUrl === 'string' || Buffer.isBuffer(previewUrl))) {
|
|
2534
|
-
throw new TypeError('addImageCard(previewUrl) requires a string url or buffer');
|
|
2535
|
-
}
|
|
2536
|
-
|
|
2537
|
-
const preview = Toolkit.resolveMedia(this.#client, previewUrl, 'image', { resolveUrl });
|
|
2538
|
-
const full = fullUrl === previewUrl ? preview : Toolkit.resolveMedia(this.#client, fullUrl, 'image', { resolveUrl });
|
|
2539
|
-
|
|
2540
|
-
this._submessages.push({
|
|
2541
|
-
messageType: 1,
|
|
2542
|
-
gridImageMetadata: {
|
|
2543
|
-
gridImageUrl: { imagePreviewUrl: preview },
|
|
2544
|
-
imageUrls: [{ imagePreviewUrl: preview, imageHighResUrl: full, sourceUrl: full }],
|
|
2545
|
-
},
|
|
2546
|
-
});
|
|
2547
|
-
|
|
2548
|
-
this._sections.push(
|
|
2549
|
-
AIRich.newLayout('Single', {
|
|
2550
|
-
preview_image: { url: preview, mime_type: 'image/jpeg', __typename: 'GenAIMediaItem' },
|
|
2551
|
-
full_image: { url: full, mime_type: 'image/jpeg', __typename: 'GenAIMediaItem' },
|
|
2552
|
-
__typename: 'GenAIImagePrimitive',
|
|
2553
|
-
})
|
|
2554
|
-
);
|
|
2555
|
-
|
|
2556
|
-
return this;
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
2572
|
/**
|
|
2560
2573
|
* Add a "3P extension" widget card (`GenAI3PExtWidgetPrimitive`) — a small panel with a title and
|
|
2561
2574
|
* a row of tappable CTA chips. Per captured traffic these CTAs call back into a tool (`tool_call_id`)
|
|
@@ -178,10 +178,12 @@ export class AIRich extends BaseBuilder {
|
|
|
178
178
|
refreshResponseId(): this;
|
|
179
179
|
setBotResponseId(id: string): this;
|
|
180
180
|
refreshBotResponseId(): this;
|
|
181
|
+
hasId(id: string): boolean;
|
|
182
|
+
getIds(): string[];
|
|
183
|
+
peek(id: string): { id: string; sections: any[]; submessages: any[] } | null;
|
|
184
|
+
delete(id: string): this;
|
|
181
185
|
/** FOATextPrimitive — large heading text, distinct from addText()'s paragraph text. */
|
|
182
186
|
addHeading(text: string): this;
|
|
183
|
-
/** GenAIImagePrimitive — "ready" static image (preview + full-res), distinct from addImage()'s generation-style card. */
|
|
184
|
-
addImageCard(previewUrl: string | Buffer, fullUrl?: string | Buffer, options?: { resolveUrl?: boolean }): this;
|
|
185
187
|
/** GenAI3PExtWidgetPrimitive — experimental, reverse-engineered; see JSDoc in the .js file for caveats. */
|
|
186
188
|
addWidget(data: Record<string, any> | Record<string, any>[], options?: { layout?: 'Single' | 'HScroll' | 'ActionRow' | string }): this;
|
|
187
189
|
/** GenAIFooterActionPrimitive — footer action link chips (e.g. "Join our Group"). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Mutex } from 'async-mutex';
|
|
2
|
-
import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises';
|
|
2
|
+
import { mkdir, readFile, readdir, stat, unlink, writeFile } from 'fs/promises';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { proto } from '../../WAProto/index.js';
|
|
5
5
|
import { initAuthCreds } from './auth-utils.js';
|
|
@@ -18,6 +18,49 @@ const getFileLock = (path) => {
|
|
|
18
18
|
}
|
|
19
19
|
return mutex;
|
|
20
20
|
};
|
|
21
|
+
// Vanz@Fix 25-08-26 (ENOSPC guard) --- useMultiFileAuthState writes one small file PER key id
|
|
22
|
+
// (session-*, sender-key-*, sender-key-memory-*, pre-key-*, app-state-sync-key-*, ...) and never
|
|
23
|
+
// prunes any of them on its own. Over months of uptime — especially in busy groups, where a
|
|
24
|
+
// sender-key-memory file is created per (group, participant) pair — this silently accumulates into
|
|
25
|
+
// hundreds of thousands of tiny files. That can hit the filesystem's inode/file-count ceiling and
|
|
26
|
+
// start throwing ENOSPC on writes long before disk *space* usage looks anywhere near full (a
|
|
27
|
+
// classic symptom: panel shows plenty of free MiB/GiB, but writes still fail with ENOSPC). When
|
|
28
|
+
// that happens mid-session, auth writes (including the app-state-sync key from the "App state key
|
|
29
|
+
// not present!" issue) fail silently and never actually persist.
|
|
30
|
+
//
|
|
31
|
+
// `sender-key-memory-*` entries are pure dedup/anti-replay cache — safe to delete anytime; Baileys
|
|
32
|
+
// regenerates them on demand with zero functional loss (worst case: one redundant re-decrypt). They
|
|
33
|
+
// also tend to be the single biggest contributor to file count in active groups, so they're the
|
|
34
|
+
// safest and highest-impact thing to prune automatically. Everything else (creds, real sessions,
|
|
35
|
+
// pre-keys, app-state-sync-keys) is left completely untouched — this never touches those.
|
|
36
|
+
export const pruneStaleAuthFiles = async (folder, { maxAgeDays = 14, categories = ['sender-key-memory'], dryRun = false } = {}) => {
|
|
37
|
+
let entries;
|
|
38
|
+
try {
|
|
39
|
+
entries = await readdir(folder);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { scanned: 0, removed: 0 };
|
|
43
|
+
}
|
|
44
|
+
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
45
|
+
let scanned = 0;
|
|
46
|
+
let removed = 0;
|
|
47
|
+
await Promise.all(entries.map(async (name) => {
|
|
48
|
+
if (!categories.some((cat) => name.startsWith(`${cat}-`) && name.endsWith('.json'))) return;
|
|
49
|
+
scanned++;
|
|
50
|
+
const filePath = join(folder, name);
|
|
51
|
+
try {
|
|
52
|
+
const info = await stat(filePath);
|
|
53
|
+
if (info.mtimeMs < cutoff) {
|
|
54
|
+
if (!dryRun) await unlink(filePath).catch(() => { });
|
|
55
|
+
removed++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// file vanished between readdir and stat — fine, nothing to prune
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
return { scanned, removed };
|
|
63
|
+
};
|
|
21
64
|
/**
|
|
22
65
|
* stores the full authentication state in a single folder.
|
|
23
66
|
* Far more efficient than singlefileauthstate
|
|
@@ -83,6 +126,9 @@ export const useMultiFileAuthState = async (folder) => {
|
|
|
83
126
|
else {
|
|
84
127
|
await mkdir(folder, { recursive: true });
|
|
85
128
|
}
|
|
129
|
+
// Vanz@Fix 25-08-26 (ENOSPC guard) --- best-effort prune on every startup. Failures here must
|
|
130
|
+
// never block auth from loading, so this is intentionally fire-and-forget with its own catch.
|
|
131
|
+
pruneStaleAuthFiles(folder).catch(() => { });
|
|
86
132
|
const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-');
|
|
87
133
|
const creds = (await readData('creds.json')) || initAuthCreds();
|
|
88
134
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanzxy/baileys",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Enhanced Baileys fork by Vanzxy — based on @itsliaaa/baileys + @whiskeysockets/baileys with fixes for audio group status and clean media without newsletter button.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./lib/index.js",
|