my-baileys 1.0.1 → 1.0.3

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 CHANGED
@@ -37,6 +37,7 @@ Built on top of [Baileys](https://github.com/WhiskeySockets/Baileys) (WhiskeySoc
37
37
  - [Guard against unexpected group-joins and DMs](#guard-against-unexpected-group-joins-and-dms)
38
38
  - [AntiBanned (fresh-number throttle)](#antibanned-fresh-number-send-throttle)
39
39
  - [AntiBan (full send-safety suite)](#antiban-full-send-safety-suite)
40
+ - [Binary codec (Protobuf wire-format primitives)](#binary-codec-protobuf-wire-format-primitives)
40
41
  - [Message-builder (extra send helpers)](#message-builder-extra-send-helpers)
41
42
  - [AI watermark on button messages](#ai-watermark-on-button-messages)
42
43
  - [ACK monitor](#ack-monitor)
@@ -529,6 +530,53 @@ for what each sub-module does and how they fit together.
529
530
 
530
531
  ---
531
532
 
533
+ ## Binary codec (Protobuf wire-format primitives)
534
+
535
+ Low-level Protocol Buffers wire-format helpers, exported for building your own custom
536
+ binary/protobuf-shaped payloads — see [`lib/Utils/binary-codec.js`](lib/Utils/binary-codec.js).
537
+ This doesn't touch how Baileys encodes/decodes WhatsApp's own messages (that's still
538
+ `protobufjs` via `WAProto/index.js`, which you should keep using for anything WhatsApp-facing);
539
+ it's for when *you* need direct control over some wire bytes of your own.
540
+
541
+ ```javascript
542
+ import { encodeVarint, decodeVarint, zigzagEncode32, zigzagDecode32,
543
+ encodeFixed32, encodeFixed64, encodeTag, decodeTag,
544
+ encodeLengthDelimited, decodeLengthDelimited,
545
+ BufferWriter, WireType } from '@xayz/baileys';
546
+
547
+ // varint — the base encoding used for VARINT fields and every length prefix
548
+ encodeVarint(300); // -> <Buffer ac 02>
549
+ encodeVarint(123456789012345678n); // BigInt for values a plain Number can't hold precisely
550
+
551
+ // ZigZag — for sint32/sint64 fields (keeps small negative numbers short)
552
+ zigzagEncode32(-1); // -> 1
553
+ zigzagDecode32(1); // -> -1
554
+
555
+ // fixed-width (always little-endian, matches fixed32/fixed64/float/double)
556
+ encodeFixed32(1); // -> <Buffer 01 00 00 00>
557
+ encodeFixed64(1n); // -> <Buffer 01 00 00 00 00 00 00 00>
558
+
559
+ // length-delimited framing — the spec-correct version of a manual
560
+ // `wrapPayload(tagBuffer, data)` helper: pass a field number, get a proper tag
561
+ encodeLengthDelimited(1, Buffer.from('hello')); // tag + varint length + data
562
+
563
+ // building a multi-field message without a Buffer.concat() per field
564
+ const w = new BufferWriter();
565
+ w.writeTag(1, WireType.VARINT).writeVarint(300);
566
+ w.writeLengthDelimited(2, Buffer.from('xayz'));
567
+ const message = w.finish();
568
+ ```
569
+
570
+ **On Buffer allocation efficiency** (`BufferWriter`'s reason for existing): chaining
571
+ `Buffer.concat([a, b])` calls in a loop allocates a brand-new buffer and copies *everything so
572
+ far* on every call — that's O(n²) total copying for n fields, and wastes the zero-fill
573
+ `Buffer.alloc()` normally does on bytes you're about to overwrite anyway. `BufferWriter` instead
574
+ pre-allocates with `Buffer.allocUnsafe()` and only grows (by doubling) when it actually runs
575
+ out of room, so N writes cost O(N) overall, not O(N²) — the difference matters once you're
576
+ writing more than a handful of fields per message.
577
+
578
+ ---
579
+
532
580
  ## Message-builder (extra send helpers)
533
581
 
534
582
  A set of convenience methods bolted onto every socket for content types that would otherwise
@@ -546,6 +594,34 @@ await sock.forwardMessage(jid, originalMsg);
546
594
  await sock.broadcastMessage(jidList, { text: 'Announcement' });
547
595
  ```
548
596
 
597
+ ### `sendHtml()` — experimental, read this first
598
+
599
+ ```javascript
600
+ await sock.sendHtml(jid, '<b>Hello</b> from my bot');
601
+ ```
602
+
603
+ This mirrors WhatsApp's own internal "Meta AI" chat message shape
604
+ (`botForwardedMessage` → `richResponseMessage`, internally named `GenAIUnifiedResponse` /
605
+ `FOAHtmlPrimitiveDemoDONOTUSE` in WhatsApp's own protocol — that `DONOTUSE` suffix is Meta's own
606
+ naming for an experimental feature flag, not a warning from us). It is **not** a documented,
607
+ stable public message type, so:
608
+
609
+ - WhatsApp's client may render it inline, show a "tap to view"/download prompt, or (if the
610
+ feature gets pulled server-side) not render it at all — behavior isn't guaranteed and can
611
+ change without notice, since your bot isn't the actual verified Meta AI account this format
612
+ was designed for.
613
+ - **Bug fixed:** earlier versions of this method unconditionally marked every message
614
+ `contextInfo: { isForwarded: true, forwardOrigin: 4 /* META_AI */ }` — literally claiming
615
+ every message was forwarded from Meta AI, which is almost certainly why some people saw it
616
+ require an extra tap/download before showing up (a non-verified sender claiming Meta AI
617
+ origin is exactly what you'd expect WhatsApp to treat cautiously). It now sends as a plain,
618
+ non-forwarded message by default; pass `{ contextInfo: {...} }` yourself if you have a
619
+ specific reason to set forwarding metadata.
620
+
621
+ If you need something guaranteed to render reliably, prefer a documented content type
622
+ (`sendButtonsMessage`, `sendListMessage`, a plain `{ text }`, etc.) — `sendHtml` is here for
623
+ experimentation, not as a recommended primary way to send rich content.
624
+
549
625
  If you have `antiban: true` (or a preset) set, these all go through it too — `forwardMessage`,
550
626
  `sendActionPoll`, and `broadcastMessage` call `sock.sendMessage()` internally, same as calling
551
627
  it yourself. (`sendJsonMessage`/`sendCarouselMessage` use the lower-level `relayMessage()` and
@@ -872,8 +948,17 @@ store.bind(client.ev);
872
948
  client.ev.on('contacts.upsert', () => {
873
949
  console.log('New contact: ' + Object.values(store.contacts()));
874
950
  });
951
+
952
+ // persist the whole store to disk, and restore it on the next run
953
+ setInterval(() => store.writeToFile('./baileys-store.json'), 10_000);
954
+ store.readFromFile('./baileys-store.json'); // call before store.bind() to load prior state
875
955
  ```
876
956
 
957
+ Two real bugs in the store's `KeyedDB` were found and fixed while adding `writeToFile`/
958
+ `readFromFile`: `chats.get(jid)` / `.update(jid, ...)` / `.deleteById(jid)` didn't actually work
959
+ before this fix (see [LITERACY.md → Changelog vs upstream](LITERACY.md#changelog-vs-upstream)
960
+ for the full story), so if you were relying on those specifically, they now behave as expected.
961
+
877
962
  ---
878
963
 
879
964
  ## Sending messages
@@ -18,13 +18,6 @@ export const makeMessageBuilderSocket = (sock) => {
18
18
  const msg = generateWAMessageFromContent(jid, content, {});
19
19
  return relayMessage(jid, msg.message, { messageId: msg.key.id, ...options });
20
20
  };
21
-
22
- /**
23
- * Sends an HTML GenAI primitive rich response message.
24
- * html: string template/raw HTML markup to be rendered.
25
- * (Added from xbailsync@1.0.7 — a plain, explicit send, nothing hidden:
26
- * only runs when you call it, same as every other method here.)
27
- */
28
21
  const sendHtml = async (jid, html = '', options = {}) => {
29
22
  const payloadData = Buffer.from(
30
23
  JSON.stringify({
@@ -54,10 +47,7 @@ export const makeMessageBuilderSocket = (sock) => {
54
47
  unifiedResponse: {
55
48
  data: payloadData
56
49
  },
57
- contextInfo: {
58
- isForwarded: true,
59
- forwardOrigin: 4
60
- }
50
+ ...(options.contextInfo ? { contextInfo: options.contextInfo } : {})
61
51
  }
62
52
  }
63
53
  }
@@ -23,7 +23,20 @@ export default class KeyedDB {
23
23
  this._dict[id] = entry;
24
24
  let inserted = false;
25
25
  for (let i = 0; i < this._array.length; i++) {
26
- const cmp = this._compare(this._idGetter(this._array[i]), id);
26
+ // BUG FIX (found while adding writeToFile/readFromFile — see
27
+ // make-in-memory-store.js changelog note): this used to compare
28
+ // this._idGetter(this._array[i]) against `id` — i.e. it fed the
29
+ // ID-getter's output into `_compare` on both sides. That only
30
+ // works if idGetter ALSO doubles as a sort-priority key (which
31
+ // it did for the old `waChatKey`, at the cost of breaking
32
+ // `get()`/`update()`/`deleteById()`, since those take a *plain*
33
+ // id, not a composite sort key, and index into `_dict` by
34
+ // whatever `idGetter` actually returns). Comparing the full
35
+ // entries directly instead — matching @vansnowi/baileys@1.5.9's
36
+ // implementation — lets idGetter go back to being just "the
37
+ // plain unique id", with `compare` doing its own real job of
38
+ // ordering two full entries.
39
+ const cmp = this._compare(entry, this._array[i]);
27
40
  if (cmp < 0) {
28
41
  this._array.splice(i, 0, entry);
29
42
  inserted = true;
@@ -6,26 +6,55 @@ import * as WABinary_1 from "../WABinary/index.js";
6
6
  import makeOrderedDictionary from "./make-ordered-dictionary.js";
7
7
  import { ObjectRepository } from "./object-repository.js";
8
8
  import KeyedDB from "./keyed-db.js";
9
-
9
+ import { existsSync, readFileSync, writeFileSync } from "fs";
10
+
11
+ // BUG FIX (see keyed-db.js's insert() for the full story): `key` used to
12
+ // return a composite string (pin flag + archive flag + hex timestamp + id)
13
+ // that doubled as BOTH the KeyedDB dict-lookup id AND the sort-priority
14
+ // encoding. That broke `chats.get(jid)` / `.update(jid, ...)` /
15
+ // `.deleteById(jid)` for every caller passing a plain JID (which is every
16
+ // caller in this codebase) — `_dict` was keyed by the composite string, so a
17
+ // plain-JID lookup never matched. `key` is now just the plain chat id;
18
+ // `compare` does the actual pin/archived/most-recent-first ordering
19
+ // directly on two full chat entries, preserving the exact same sort order
20
+ // as before (pinned first, then non-archived first, then newest
21
+ // conversationTimestamp first, id as the final tie-break to match the old
22
+ // descending-string-compare behavior).
10
23
  const waChatKey = (pin) => ({
11
- key: (c) =>
12
- (pin ? (c.pinned ? "1" : "0") : "") +
13
- (c.archived ? "0" : "1") +
14
- (c.conversationTimestamp
15
- ? c.conversationTimestamp.toString(16).padStart(8, "0")
16
- : "") +
17
- c.id,
18
- compare: (k1, k2) => k2.localeCompare(k1)
24
+ key: (c) => c.id,
25
+ compare: (c1, c2) => {
26
+ if (pin) {
27
+ const p1 = c1.pinned ? 1 : 0;
28
+ const p2 = c2.pinned ? 1 : 0;
29
+ if (p1 !== p2) return p2 - p1;
30
+ }
31
+ const a1 = c1.archived ? 0 : 1;
32
+ const a2 = c2.archived ? 0 : 1;
33
+ if (a1 !== a2) return a2 - a1;
34
+ const t1 = c1.conversationTimestamp ? Number(c1.conversationTimestamp) : 0;
35
+ const t2 = c2.conversationTimestamp ? Number(c2.conversationTimestamp) : 0;
36
+ if (t1 !== t2) return t2 - t1;
37
+ return (c2.id || "").localeCompare(c1.id || "");
38
+ }
19
39
  });
20
40
 
21
41
  export const waMessageID = (m) => m.key.id || "";
22
42
 
43
+ // BUG FIX, same root cause as waChatKey above: `compare` used to receive
44
+ // the two entries' composite `key(...)` STRING outputs (via keyed-db.js's
45
+ // old buggy insert()), which is why plain string `.localeCompare` worked
46
+ // there previously — but only by coincidence, since `key` here (unlike
47
+ // chats') already IS the plain lookup id, not a separate sort-priority
48
+ // encoding. Updated to a proper entry-to-entry comparator (computing the
49
+ // same composite key from each full entry) so it keeps working correctly
50
+ // now that keyed-db.js's insert() passes full entries instead of
51
+ // pre-computed key strings.
23
52
  export const waLabelAssociationKey = {
24
53
  key: (la) =>
25
54
  la.type === LabelAssociationType.Chat
26
55
  ? la.chatId + la.labelId
27
56
  : la.chatId + la.messageId + la.labelId,
28
- compare: (k1, k2) => k2.localeCompare(k1)
57
+ compare: (a, b) => waLabelAssociationKey.key(b).localeCompare(waLabelAssociationKey.key(a))
29
58
  };
30
59
 
31
60
  const makeMessagesDictionary = () => makeOrderedDictionary(waMessageID);
@@ -110,7 +139,11 @@ export default function makeInMemoryStore(config) {
110
139
 
111
140
  ev.on("labels.association", ({ type, association }) => {
112
141
  if (type === "add") labelAssociations.upsert(association);
113
- if (type === "remove") labelAssociations.delete(association);
142
+ // BUG FIX: was `labelAssociations.delete(association)` — KeyedDB
143
+ // has no `.delete()` method (only `.deleteById(id)`), so this
144
+ // threw `TypeError: labelAssociations.delete is not a function`
145
+ // every time a label association was actually removed.
146
+ if (type === "remove") labelAssociations.deleteById(labelAssociationKey.key(association));
114
147
  });
115
148
 
116
149
  ev.on("presence.update", ({ id, presences: update }) => {
@@ -181,6 +214,26 @@ export default function makeInMemoryStore(config) {
181
214
  const loadMessage = async (jid, id) => {
182
215
  return messages[jid]?.get(id)
183
216
  }
217
+ // Added from @vansnowi/baileys@1.5.9 — the two persistence entry points
218
+ // that were missing here even though toJSON()/fromJSON() (this fork's
219
+ // own, more careful versions — see fromJSON above, which reconstructs
220
+ // proper WebMessageInfo instances instead of leaving raw parsed JSON)
221
+ // already existed. Uses this fork's own shared BufferJSON.replacer/
222
+ // reviver (Utils/generics.js, already used by make-cache-manager-store.js)
223
+ // rather than duplicating a private copy, so Buffers inside the store
224
+ // (media keys, etc.) survive a save/load round-trip intact.
225
+ const writeToFile = (path) => {
226
+ const data = JSON.stringify(toJSON(), Utils_1.BufferJSON.replacer);
227
+ writeFileSync(path, data);
228
+ };
229
+ const readFromFile = (path) => {
230
+ if (existsSync(path)) {
231
+ logger?.debug?.({ path }, "reading store from file");
232
+ const jsonStr = readFileSync(path, { encoding: "utf-8" });
233
+ const json = JSON.parse(jsonStr, Utils_1.BufferJSON.reviver);
234
+ fromJSON(json);
235
+ }
236
+ };
184
237
  return {
185
238
  chats,
186
239
  contacts,
@@ -193,6 +246,8 @@ export default function makeInMemoryStore(config) {
193
246
  bind,
194
247
  toJSON,
195
248
  fromJSON,
249
+ writeToFile,
250
+ readFromFile,
196
251
  loadMessage
197
252
  };
198
253
  }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * lib/Utils/binary-codec.js — general-purpose Protocol Buffers wire-format
3
+ * primitives: varint, ZigZag (sint32/sint64), fixed32/fixed64, and
4
+ * length-delimited framing, plus a growable BufferWriter for building up a
5
+ * message without repeated small allocations.
6
+ *
7
+ * This is a from-scratch, standalone module — it does not replace or touch
8
+ * the small `encodeVarint`/`decodeVarint` pair already private to
9
+ * lib/Utils/reporting-utils.js (used only for one WAM-reporting feature).
10
+ * That pair is intentionally 32-bit-only for its narrow use case; the
11
+ * functions here are exported for general use and are correct up to the
12
+ * full 64-bit range protobuf's own spec allows (using BigInt where a
13
+ * regular Number would lose precision).
14
+ *
15
+ * None of this is wired into WhatsApp's own protocol handling — Baileys
16
+ * already encodes/decodes the real WAProto messages via `protobufjs`
17
+ * (see WAProto/index.js), which is the correct, spec-complete way to do
18
+ * that and shouldn't be replaced by hand-rolled encoding. This module is
19
+ * for your OWN custom binary/protobuf-shaped payloads (e.g. a custom field
20
+ * inside messageContextInfo, or a side-channel binary format) where you
21
+ * want direct control over the wire bytes.
22
+ *
23
+ * Wire format reference: https://protobuf.dev/programming-guides/encoding/
24
+ */
25
+
26
+ /** Protobuf wire types (the low 3 bits of every field tag). */
27
+ export const WireType = {
28
+ VARINT: 0, // int32, int64, uint32, uint64, sint32, sint64, bool, enum
29
+ FIXED64: 1, // fixed64, sfixed64, double
30
+ LENGTH_DELIMITED: 2, // string, bytes, embedded messages, packed repeated fields
31
+ START_GROUP: 3, // deprecated in proto3 — included for completeness only
32
+ END_GROUP: 4, // deprecated in proto3 — included for completeness only
33
+ FIXED32: 5 // fixed32, sfixed32, float
34
+ };
35
+
36
+ // --- Varint (used directly for VARINT wire type, and for the length prefix
37
+ // of every LENGTH_DELIMITED field) ---
38
+
39
+ /**
40
+ * Encodes a non-negative integer as a protobuf varint.
41
+ * Accepts a Number (must be a safe, non-negative integer) or a BigInt
42
+ * (use BigInt for values that don't fit in a 32-bit int, e.g. uint64/int64
43
+ * field values > 2^32 — a plain Number loses precision above 2^53).
44
+ */
45
+ export const encodeVarint = (value) => {
46
+ let n = typeof value === 'bigint' ? value : BigInt(value);
47
+ if (n < 0n) {
48
+ throw new RangeError('encodeVarint: value must be non-negative — use zigzagEncode32/64 first for signed (sint32/sint64) fields');
49
+ }
50
+ const bytes = [];
51
+ while (n >= 0x80n) {
52
+ bytes.push(Number(n & 0x7fn) | 0x80);
53
+ n >>= 7n;
54
+ }
55
+ bytes.push(Number(n));
56
+ return Buffer.from(bytes);
57
+ };
58
+
59
+ /**
60
+ * Decodes a varint starting at `offset`.
61
+ * Returns `{ value, bytesRead }`. `value` is a Number if it fits safely
62
+ * (<= Number.MAX_SAFE_INTEGER), otherwise a BigInt — check
63
+ * `typeof result.value` if you need to know which you got.
64
+ */
65
+ export const decodeVarint = (buffer, offset = 0) => {
66
+ let result = 0n;
67
+ let shift = 0n;
68
+ let bytesRead = 0;
69
+ while (true) {
70
+ if (offset + bytesRead >= buffer.length) {
71
+ throw new RangeError('decodeVarint: unexpected end of buffer');
72
+ }
73
+ const byte = buffer[offset + bytesRead];
74
+ result |= BigInt(byte & 0x7f) << shift;
75
+ bytesRead++;
76
+ if ((byte & 0x80) === 0) {
77
+ break;
78
+ }
79
+ shift += 7n;
80
+ if (shift > 70n) {
81
+ throw new RangeError('decodeVarint: varint is too long (more than 10 bytes)');
82
+ }
83
+ }
84
+ const value = result <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(result) : result;
85
+ return { value, bytesRead };
86
+ };
87
+
88
+ // --- ZigZag encoding (for sint32/sint64 — maps signed values to unsigned
89
+ // ones so small-magnitude negative numbers still encode as short varints) ---
90
+
91
+ /** ZigZag-encodes a 32-bit signed integer. `n` must be in the int32 range. */
92
+ export const zigzagEncode32 = (n) => ((n << 1) ^ (n >> 31)) >>> 0;
93
+
94
+ /** Decodes a ZigZag-encoded 32-bit unsigned integer back to a signed int32. */
95
+ export const zigzagDecode32 = (n) => (n >>> 1) ^ -(n & 1);
96
+
97
+ /** ZigZag-encodes a 64-bit signed integer (BigInt in, BigInt out). */
98
+ export const zigzagEncode64 = (n) => {
99
+ const v = typeof n === 'bigint' ? n : BigInt(n);
100
+ return BigInt.asUintN(64, (v << 1n) ^ (v >> 63n));
101
+ };
102
+
103
+ /** Decodes a ZigZag-encoded 64-bit unsigned BigInt back to a signed BigInt. */
104
+ export const zigzagDecode64 = (n) => {
105
+ const v = typeof n === 'bigint' ? n : BigInt(n);
106
+ return (v >> 1n) ^ -(v & 1n);
107
+ };
108
+
109
+ // --- Fixed-width encodings (always little-endian, per the protobuf spec) ---
110
+
111
+ /** Encodes a 32-bit value as 4 fixed little-endian bytes (fixed32/sfixed32/float use this width). */
112
+ export const encodeFixed32 = (n) => {
113
+ const buf = Buffer.allocUnsafe(4);
114
+ buf.writeUInt32LE(n >>> 0, 0);
115
+ return buf;
116
+ };
117
+
118
+ /** Decodes 4 little-endian bytes at `offset` back to an unsigned 32-bit integer. */
119
+ export const decodeFixed32 = (buffer, offset = 0) => buffer.readUInt32LE(offset);
120
+
121
+ /** Encodes a 64-bit value (BigInt) as 8 fixed little-endian bytes (fixed64/sfixed64/double use this width). */
122
+ export const encodeFixed64 = (n) => {
123
+ const buf = Buffer.allocUnsafe(8);
124
+ buf.writeBigUInt64LE(BigInt.asUintN(64, typeof n === 'bigint' ? n : BigInt(n)), 0);
125
+ return buf;
126
+ };
127
+
128
+ /** Decodes 8 little-endian bytes at `offset` back to an unsigned 64-bit BigInt. */
129
+ export const decodeFixed64 = (buffer, offset = 0) => buffer.readBigUInt64LE(offset);
130
+
131
+ // --- Field tags and length-delimited framing ---
132
+
133
+ /** Builds a protobuf field tag: `(fieldNumber << 3) | wireType`, varint-encoded. */
134
+ export const encodeTag = (fieldNumber, wireType) => encodeVarint((fieldNumber << 3) | wireType);
135
+
136
+ /** Splits a decoded tag varint back into `{ fieldNumber, wireType }`. */
137
+ export const decodeTag = (tagValue) => {
138
+ const n = typeof tagValue === 'bigint' ? Number(tagValue) : tagValue;
139
+ return { fieldNumber: n >>> 3, wireType: n & 0x7 };
140
+ };
141
+
142
+ /**
143
+ * Frames `data` as a protobuf LENGTH_DELIMITED field: tag + varint length + data.
144
+ * This is the generalized, spec-correct version of the `wrapPayload(tag, data)`
145
+ * helper — pass a real field number instead of a raw tag Buffer and this
146
+ * builds a proper varint tag for you:
147
+ *
148
+ * wrapPayload(tagBuffer, data) // your original
149
+ * encodeLengthDelimited(fieldNumber, data) // spec-correct equivalent
150
+ */
151
+ export const encodeLengthDelimited = (fieldNumber, data) => (
152
+ Buffer.concat([encodeTag(fieldNumber, WireType.LENGTH_DELIMITED), encodeVarint(data.length), data])
153
+ );
154
+
155
+ /**
156
+ * Reads one length-delimited field's payload starting at `offset` (which
157
+ * must point at the length varint, i.e. right after the tag). Returns
158
+ * `{ data, bytesRead }` where `bytesRead` covers the length prefix AND the
159
+ * payload, so `offset + bytesRead` is the start of the next field.
160
+ */
161
+ export const decodeLengthDelimited = (buffer, offset = 0) => {
162
+ const { value: length, bytesRead: lengthBytes } = decodeVarint(buffer, offset);
163
+ const len = typeof length === 'bigint' ? Number(length) : length;
164
+ const dataStart = offset + lengthBytes;
165
+ if (dataStart + len > buffer.length) {
166
+ throw new RangeError('decodeLengthDelimited: declared length exceeds buffer bounds');
167
+ }
168
+ return { data: buffer.subarray(dataStart, dataStart + len), bytesRead: lengthBytes + len };
169
+ };
170
+
171
+ // --- Efficient buffer building ---
172
+
173
+ /**
174
+ * A growable buffer writer for building up a message field-by-field without
175
+ * a `Buffer.concat()` (and therefore a full copy) on every single write.
176
+ *
177
+ * Why this matters: `Buffer.concat([a, b])` allocates a brand-new buffer and
178
+ * copies both `a` and `b` into it every time. Chaining many small
179
+ * `Buffer.concat()` calls in a loop (once per field) is O(n²) in the total
180
+ * bytes written — fine for a handful of fields, wasteful for a message with
181
+ * hundreds. `BufferWriter` instead pre-allocates a working buffer with
182
+ * `Buffer.allocUnsafe` (skips the zero-fill `Buffer.alloc` normally does,
183
+ * safe here because every byte gets overwritten before being read) and
184
+ * doubles its capacity only when it actually runs out — so the amortized
185
+ * cost of N writes is O(N), not O(N²), and there's no wasted zero-filling.
186
+ */
187
+ export class BufferWriter {
188
+ constructor(initialCapacity = 256) {
189
+ this._buf = Buffer.allocUnsafe(Math.max(initialCapacity, 16));
190
+ this._len = 0;
191
+ }
192
+ _ensure(extra) {
193
+ const needed = this._len + extra;
194
+ if (needed <= this._buf.length) return;
195
+ let newCapacity = this._buf.length * 2;
196
+ while (newCapacity < needed) newCapacity *= 2;
197
+ const grown = Buffer.allocUnsafe(newCapacity);
198
+ this._buf.copy(grown, 0, 0, this._len);
199
+ this._buf = grown;
200
+ }
201
+ writeBytes(data) {
202
+ this._ensure(data.length);
203
+ data.copy(this._buf, this._len);
204
+ this._len += data.length;
205
+ return this;
206
+ }
207
+ writeVarint(value) {
208
+ return this.writeBytes(encodeVarint(value));
209
+ }
210
+ writeTag(fieldNumber, wireType) {
211
+ return this.writeBytes(encodeTag(fieldNumber, wireType));
212
+ }
213
+ writeFixed32(n) {
214
+ return this.writeBytes(encodeFixed32(n));
215
+ }
216
+ writeFixed64(n) {
217
+ return this.writeBytes(encodeFixed64(n));
218
+ }
219
+ /** Writes a full LENGTH_DELIMITED field (tag + length + bytes) in one call. */
220
+ writeLengthDelimited(fieldNumber, data) {
221
+ return this.writeBytes(encodeLengthDelimited(fieldNumber, data));
222
+ }
223
+ get length() {
224
+ return this._len;
225
+ }
226
+ /** Returns the written bytes as a Buffer. Copies — safe to keep using the writer afterward. */
227
+ finish() {
228
+ return Buffer.from(this._buf.subarray(0, this._len));
229
+ }
230
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './generics.js';
2
+ export * from './binary-codec.js';
2
3
  export * from './decode-wa-message.js';
3
4
  export * from './messages.js';
4
5
  export * from './messages-media.js';
@@ -26,11 +26,6 @@ const MessageTypeProto = {
26
26
  sticker: WAProto.Message.StickerMessage,
27
27
  document: WAProto.Message.DocumentMessage
28
28
  };
29
- // Added from baileys@6.2.7: lets generateWAMessageFromContent() accept a raw
30
- // proto message-type key directly (e.g. `{ conversation: 'hi' }`), bypassing
31
- // the normal content-type-detection chain below — see the final `else` in
32
- // generateWAMessageFromContent(). Falls back to prepareWAMessageMedia() as
33
- // before for anything not in this list.
34
29
  const RAW_PROTO_MESSAGE_KEYS = [
35
30
  'conversation', 'senderKeyDistributionMessage', 'imageMessage', 'contactMessage', 'locationMessage',
36
31
  'extendedTextMessage', 'documentMessage', 'audioMessage', 'videoMessage', 'call', 'chat', 'protocolMessage',
@@ -887,8 +882,6 @@ export const generateWAMessageContent = async (message, options) => {
887
882
  };
888
883
  }
889
884
  else if (hasNonNullishProperty(message, 'stickerPacks')) {
890
- // Added from baileys@6.2.7: send a whole sticker pack (multiple
891
- // .webp stickers + a tray icon, zipped) as one stickerPackMessage.
892
885
  const pack = message.stickerPacks;
893
886
  const rawStickers = Array.isArray(pack.stickers) ? pack.stickers : [];
894
887
  if (rawStickers.length === 0) {
@@ -964,11 +957,6 @@ export const generateWAMessageContent = async (message, options) => {
964
957
  };
965
958
  }
966
959
  else if (hasNonNullishProperty(message, 'sticker') && (message.pack || message.author || message.isPrivate || message.premium || message.animated)) {
967
- // Added from baileys@6.2.7: `{ sticker, pack/author/isPrivate/animated/premium }`
968
- // auto-converts the given image/video into a proper sticker (via makeSticker)
969
- // instead of requiring an already-built .webp. Plain `{ sticker: <webp> }`
970
- // with none of those extra options still falls through to the normal
971
- // prepareWAMessageMedia path below, unchanged.
972
960
  const rawBuffer = await resolveStickerBuffer(message.sticker, options.options);
973
961
  const finalWebp = await makeSticker(rawBuffer, {
974
962
  pack: message.pack || '',
@@ -991,8 +979,6 @@ export const generateWAMessageContent = async (message, options) => {
991
979
  else {
992
980
  const rawProtoKey = RAW_PROTO_MESSAGE_KEYS.find(k => hasNonNullishProperty(message, k));
993
981
  if (rawProtoKey) {
994
- // Added from baileys@6.2.7: pass a raw proto message-type key straight
995
- // through, bypassing content-type detection entirely.
996
982
  const built = proto.Message.fromObject({ [rawProtoKey]: message[rawProtoKey] });
997
983
  m = { [rawProtoKey]: built[rawProtoKey] };
998
984
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "my-baileys",
3
3
  "type": "module",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "description": "A WebSockets library for interacting with WhatsApp Web — XYCoolcraft fork of Baileys",
6
6
  "keywords": [
7
7
  "xycoolcraft",
@@ -25,6 +25,7 @@
25
25
  "files": [
26
26
  "lib/**/*",
27
27
  "WAProto/**/*",
28
+ "scripts/**/*",
28
29
  "engine-requirements.js"
29
30
  ],
30
31
  "scripts": {
@@ -39,6 +40,7 @@
39
40
  "prepack": "",
40
41
  "prepare": "",
41
42
  "preinstall": "node ./engine-requirements.js",
43
+ "postinstall": "node ./scripts/self-test.js || exit 0",
42
44
  "release": "release-it",
43
45
  "test": "jest",
44
46
  "publish:npm": "bash upload-npm.sh"
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * self-test.js — runs automatically right after `npm install` (via the
4
+ * "postinstall" script in package.json). Checks, BEFORE you write a single
5
+ * line of bot code:
6
+ * 1. The package's own files loaded correctly (no broken install).
7
+ * 2. This machine can actually open a WebSocket to WhatsApp's servers.
8
+ *
9
+ * Why: the single most common "why doesn't my bot work" report is a
10
+ * hosting/network problem that has nothing to do with your bot code —
11
+ * finding that out AFTER writing your whole bot is a waste of your time.
12
+ * This catches it at install time instead, with a clear pass/fail printed
13
+ * right in your terminal / panel console.
14
+ *
15
+ * This never fails the install itself (always exits 0) — a failing
16
+ * connectivity check here doesn't mean your files are broken, and blocking
17
+ * `npm install` over a transient network hiccup would be worse than useless
18
+ * in CI/CD pipelines. It just makes sure you SEE the warning before you go
19
+ * looking for bugs in the wrong place.
20
+ *
21
+ * Skip this entirely with: BAILEYS_SKIP_SELFTEST=1 npm install
22
+ */
23
+ import chalk from 'chalk';
24
+ import WebSocket from 'ws';
25
+ import { fileURLToPath } from 'node:url';
26
+ import path from 'node:path';
27
+
28
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
+ const packageRoot = path.resolve(__dirname, '..');
30
+
31
+ const results = [];
32
+ const record = (name, pass, detail = '') => results.push({ name, pass, detail });
33
+
34
+ function banner() {
35
+ console.log('');
36
+ console.log(chalk.hex('#a855f7')('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
37
+ console.log(chalk.hex('#a855f7').bold(' @xayz/baileys — post-install self-test'));
38
+ console.log(chalk.gray(' Checking your environment before you start building your bot.'));
39
+ console.log(chalk.hex('#a855f7')('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
40
+ console.log('');
41
+ }
42
+
43
+ /** Check 1: did the package's own files load without throwing? */
44
+ async function testModuleIntegrity() {
45
+ try {
46
+ const mod = await import(path.join(packageRoot, 'lib', 'index.js'));
47
+ const ok = typeof mod.default === 'function';
48
+ record('Package installed correctly (makeWASocket loaded)', ok,
49
+ ok ? '' : 'lib/index.js loaded, but makeWASocket is not a function — the install may be corrupted. Try a clean `npm install` again.');
50
+ }
51
+ catch (e) {
52
+ record('Package installed correctly (makeWASocket loaded)', false,
53
+ `Failed to import lib/index.js: ${e.message} — try removing node_modules and package-lock.json, then \`npm install\` again.`);
54
+ }
55
+ }
56
+
57
+ /** Check 2: can this machine actually open a WebSocket to WhatsApp's servers? */
58
+ function testNetworkReachability() {
59
+ return new Promise((resolve) => {
60
+ const url = 'wss://web.whatsapp.com/ws/chat';
61
+ const start = Date.now();
62
+ let done = false;
63
+
64
+ let ws;
65
+ try {
66
+ ws = new WebSocket(url, {
67
+ origin: 'https://web.whatsapp.com',
68
+ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }
69
+ });
70
+ }
71
+ catch (e) {
72
+ record('This server can reach WhatsApp', false, `Failed to open a connection: ${e.message}`);
73
+ resolve();
74
+ return;
75
+ }
76
+
77
+ const finish = (pass, detail) => {
78
+ if (done) return;
79
+ done = true;
80
+ clearTimeout(timer);
81
+ record('This server can reach WhatsApp', pass, detail);
82
+ try { ws.terminate(); } catch { }
83
+ resolve();
84
+ };
85
+
86
+ const timer = setTimeout(() => {
87
+ finish(false, `Timed out after ${Date.now() - start}ms — the socket never opened. ` +
88
+ 'This strongly suggests this network/host is blocking or throttling connections to ' +
89
+ 'WhatsApp, NOT a code problem. Try a different server/IP if this keeps happening.');
90
+ }, 12000);
91
+
92
+ ws.on('open', () => finish(true, `Succeeded in ${Date.now() - start}ms`));
93
+ ws.on('error', (err) => finish(false, `${err.message} (after ${Date.now() - start}ms) — check your hosting's firewall/proxy.`));
94
+ ws.on('unexpected-response', (req, res) => {
95
+ finish(false, `Server responded, but it wasn't a WebSocket upgrade (HTTP ${res.statusCode}) — ` +
96
+ 'an ISP or hosting proxy/firewall is likely intercepting this connection.');
97
+ });
98
+ });
99
+ }
100
+
101
+ function printResults() {
102
+ for (const r of results) {
103
+ const icon = r.pass ? chalk.green('✅') : chalk.red('❌');
104
+ console.log(`${icon} ${r.name}`);
105
+ if (r.detail) console.log(chalk.gray(` ${r.detail}`));
106
+ }
107
+ console.log('');
108
+
109
+ const allPass = results.every(r => r.pass);
110
+ if (allPass) {
111
+ console.log(chalk.green.bold(' All checks passed. You\'re good to go — start building/running your bot.'));
112
+ }
113
+ else {
114
+ console.log(chalk.yellow.bold(' ⚠ Something above failed.'));
115
+ console.log(chalk.yellow(' This does NOT stop the install (so it won\'t break your CI/CD pipeline),'));
116
+ console.log(chalk.yellow(' but if your bot later fails to connect or pair, check the point above'));
117
+ console.log(chalk.yellow(' before you start debugging your code — it\'s the most likely cause.'));
118
+ }
119
+ console.log(chalk.hex('#a855f7')('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
120
+ console.log('');
121
+ }
122
+
123
+ (async () => {
124
+ if (process.env.BAILEYS_SKIP_SELFTEST === '1') {
125
+ console.log(chalk.gray('[@xayz/baileys] Self-test skipped (BAILEYS_SKIP_SELFTEST=1).'));
126
+ process.exit(0);
127
+ }
128
+
129
+ banner();
130
+ await testModuleIntegrity();
131
+ await testNetworkReachability();
132
+ printResults();
133
+
134
+ // Always exit 0 — see the comment at the top of this file for why.
135
+ process.exit(0);
136
+ })().catch((e) => {
137
+ // Safety net: nothing that goes wrong in this TEST script itself should
138
+ // ever fail the user's `npm install`.
139
+ console.log(chalk.red(`[@xayz/baileys] Self-test error (ignored): ${e.message}`));
140
+ process.exit(0);
141
+ });