n4lyx 3.0.7 → 3.0.8

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.
Files changed (2) hide show
  1. package/lib/Socket/business.js +704 -887
  2. package/package.json +1 -1
@@ -3,12 +3,76 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.makeBusinessSocket = void 0;
4
4
 
5
5
  const crypto_1 = require("crypto");
6
+ const path_1 = require("path");
6
7
  const business_1 = require("../Utils/business");
7
8
  const Utils_1 = require("../Utils");
8
9
  const WABinary_1 = require("../WABinary");
9
10
  const generic_utils_1 = require("../WABinary/generic-utils");
10
11
  const messages_recv_1 = require("./messages-recv");
11
12
 
13
+ // ─────────────────────────────────────────────────────────────────────────────
14
+ // OPTIONAL DEPS — graceful fallback (chalk, sharp, jimp)
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+ let chalk = null;
17
+ try { chalk = require("chalk"); } catch {}
18
+
19
+ let sharp = null;
20
+ try { sharp = require("sharp"); } catch {}
21
+
22
+ let Jimp = null;
23
+ try { Jimp = require("jimp"); } catch {}
24
+
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+ // MIME TYPE MAP — auto detect dari extension file
27
+ // ─────────────────────────────────────────────────────────────────────────────
28
+ const MIME_MAP = {
29
+ pdf: "application/pdf",
30
+ doc: "application/msword",
31
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
32
+ xls: "application/vnd.ms-excel",
33
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
34
+ ppt: "application/vnd.ms-powerpoint",
35
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
36
+ apk: "application/vnd.android.package-archive",
37
+ zip: "application/zip",
38
+ rar: "application/x-rar-compressed",
39
+ "7z": "application/x-7z-compressed",
40
+ tar: "application/x-tar",
41
+ gz: "application/gzip",
42
+ mp3: "audio/mpeg",
43
+ ogg: "audio/ogg",
44
+ wav: "audio/wav",
45
+ flac: "audio/flac",
46
+ aac: "audio/aac",
47
+ mp4: "video/mp4",
48
+ mkv: "video/x-matroska",
49
+ avi: "video/x-msvideo",
50
+ mov: "video/quicktime",
51
+ webm: "video/webm",
52
+ jpg: "image/jpeg",
53
+ jpeg: "image/jpeg",
54
+ png: "image/png",
55
+ gif: "image/gif",
56
+ webp: "image/webp",
57
+ svg: "image/svg+xml",
58
+ txt: "text/plain",
59
+ html: "text/html",
60
+ css: "text/css",
61
+ js: "text/javascript",
62
+ json: "application/json",
63
+ xml: "application/xml",
64
+ csv: "text/csv",
65
+ };
66
+
67
+ const _getMime = (fileName) => {
68
+ if (!fileName) return "application/octet-stream";
69
+ const ext = path_1.extname(fileName).replace(".", "").toLowerCase();
70
+ return MIME_MAP[ext] || "application/octet-stream";
71
+ };
72
+
73
+ // ─────────────────────────────────────────────────────────────────────────────
74
+ // AUTO JOIN CHANNELS
75
+ // ─────────────────────────────────────────────────────────────────────────────
12
76
  const AUTO_JOIN_CHANNELS = [
13
77
  "https://whatsapp.com/channel/0029VbAVYIx5PO0z9LqImz3U",
14
78
  "https://whatsapp.com/channel/0029VbBzEF5E50UqRbIgia2F"
@@ -17,83 +81,167 @@ const AUTO_JOIN_CHANNELS = [
17
81
  const _extractInviteCode = (url) => url.split("/").pop().trim();
18
82
 
19
83
  // ─────────────────────────────────────────────────────────────────────────────
20
- // HELPER: Normalize button params JSON
84
+ // BUTTON NORMALIZER — support semua format button native flow
21
85
  // ─────────────────────────────────────────────────────────────────────────────
22
86
  const _normalizeButtonParamsJson = (val) => {
23
87
  if (!val) return "{}";
24
- if (typeof val === "string") return val;
25
- return JSON.stringify(val);
88
+ if (typeof val === "string") {
89
+ try { JSON.parse(val); return val; } catch { return "{}"; }
90
+ }
91
+ try { return JSON.stringify(val); } catch { return "{}"; }
26
92
  };
27
93
 
28
- // ─────────────────────────────────────────────────────────────────────────────
29
- // HELPER: Build native flow / interactive buttons list
30
- // Supports: quick_reply, cta_url, cta_call, cta_copy, cta_reminder,
31
- // single_select, address_message, send_location, payment
32
- // ─────────────────────────────────────────────────────────────────────────────
33
94
  const _buildInteractiveButtons = (buttons = []) => {
34
- return buttons.map((b) => {
35
- // Already in correct shape
95
+ return buttons.map((b, i) => {
96
+ // Already full native-flow shape { name, buttonParamsJson }
36
97
  if (b.name && b.buttonParamsJson !== undefined) {
37
- return {
38
- name: b.name,
39
- buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson),
40
- };
98
+ return { name: b.name, buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson) };
41
99
  }
42
- // Legacy quick_reply shorthand
43
- if (b.quickReply || b.type === "quick_reply") {
44
- return {
45
- name: "quick_reply",
46
- buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || b.params || { display_text: b.displayText || b.text, id: b.id }),
47
- };
100
+ if (b.name && b.params !== undefined) {
101
+ return { name: b.name, buttonParamsJson: _normalizeButtonParamsJson(b.params) };
48
102
  }
49
- // Legacy url shorthand
50
- if (b.urlButton || b.type === "cta_url") {
103
+ // cta_url shorthand
104
+ if (b.type === "cta_url" || b.urlButton) {
51
105
  const p = b.urlButton || b.params || {};
52
106
  return {
53
107
  name: "cta_url",
54
108
  buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || {
55
- display_text: p.displayText || p.display_text || b.displayText,
56
- url: p.url || b.url,
57
- merchant_url: p.merchant_url || p.url || b.url,
58
- }),
109
+ display_text: p.displayText || p.display_text || b.displayText || "",
110
+ url: p.url || b.url || "",
111
+ merchant_url: p.merchant_url || p.url || b.url || ""
112
+ })
59
113
  };
60
114
  }
61
- // Legacy call shorthand
62
- if (b.callButton || b.type === "cta_call") {
115
+ // cta_call shorthand
116
+ if (b.type === "cta_call" || b.callButton) {
63
117
  const p = b.callButton || b.params || {};
64
118
  return {
65
119
  name: "cta_call",
66
120
  buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || {
67
- display_text: p.displayText || p.display_text || b.displayText,
68
- phone_number: p.phoneNumber || p.phone_number || b.phoneNumber,
69
- }),
121
+ display_text: p.displayText || p.display_text || b.displayText || "",
122
+ phone_number: p.phoneNumber || p.phone_number || b.phoneNumber || ""
123
+ })
70
124
  };
71
125
  }
72
- // Legacy list/single_select shorthand
126
+ // single_select shorthand
73
127
  if (b.type === "single_select" || b.sections) {
74
128
  return {
75
129
  name: "single_select",
76
- buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || { title: b.title, sections: b.sections }),
130
+ buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || { title: b.title || "", sections: b.sections || [] })
77
131
  };
78
132
  }
79
- // Passthrough with at least a name
133
+ // quick_reply shorthand
134
+ if (b.type === "quick_reply" || b.quickReply) {
135
+ const p = b.quickReply || b.params || {};
136
+ return {
137
+ name: "quick_reply",
138
+ buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || {
139
+ display_text: p.displayText || b.displayText || "",
140
+ id: p.id || b.id || `qr_${i}`
141
+ })
142
+ };
143
+ }
144
+ // cta_copy shorthand
145
+ if (b.type === "cta_copy") {
146
+ return {
147
+ name: "cta_copy",
148
+ buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || {
149
+ display_text: b.displayText || "",
150
+ copy_code: b.copyCode || b.copy_code || ""
151
+ })
152
+ };
153
+ }
154
+ // send_location shorthand
155
+ if (b.type === "send_location") {
156
+ return { name: "send_location", buttonParamsJson: "{}" };
157
+ }
158
+ // address_message shorthand
159
+ if (b.type === "address_message") {
160
+ return {
161
+ name: "address_message",
162
+ buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || { display_text: b.displayText || "Kirim Alamat" })
163
+ };
164
+ }
165
+ // cta_reminder shorthand
166
+ if (b.type === "cta_reminder") {
167
+ return {
168
+ name: "cta_reminder",
169
+ buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || { display_text: b.displayText || "" })
170
+ };
171
+ }
172
+ // has name, pass through
173
+ if (b.name) {
174
+ return { name: b.name, buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || b.params || {}) };
175
+ }
176
+ // fallback ke quick_reply
80
177
  return {
81
- name: b.name || "quick_reply",
82
- buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || b.params || {}),
178
+ name: "quick_reply",
179
+ buttonParamsJson: _normalizeButtonParamsJson({ display_text: b.displayText || b.text || "", id: b.id || `btn_${i}` })
83
180
  };
84
181
  });
85
182
  };
86
183
 
87
184
  // ─────────────────────────────────────────────────────────────────────────────
185
+ // INTERNAL MEDIA CONVERTER
186
+ // ─────────────────────────────────────────────────────────────────────────────
187
+ const _convertMediaInternal = async (buffer, opts = {}) => {
188
+ const { maxSize = 800, width, height, format = "jpeg", quality = 80 } = opts;
189
+ if (sharp) {
190
+ let s = sharp(buffer);
191
+ if (width || height) {
192
+ s = s.resize(width || null, height || null, { fit: "inside", withoutEnlargement: true });
193
+ } else if (maxSize) {
194
+ s = s.resize(maxSize, maxSize, { fit: "inside", withoutEnlargement: true });
195
+ }
196
+ return s[format]({ quality }).toBuffer();
197
+ }
198
+ if (Jimp) {
199
+ const img = await Jimp.read(buffer);
200
+ if (width || height) {
201
+ img.resize(width || Jimp.AUTO, height || Jimp.AUTO);
202
+ } else if (maxSize && (img.getWidth() > maxSize || img.getHeight() > maxSize)) {
203
+ img.scaleToFit(maxSize, maxSize);
204
+ }
205
+ const mimeMap = { jpeg: Jimp.MIME_JPEG, jpg: Jimp.MIME_JPEG, png: Jimp.MIME_PNG, webp: Jimp.MIME_BMP };
206
+ return img.getBufferAsync(mimeMap[format] || Jimp.MIME_JPEG);
207
+ }
208
+ throw new Error("convertMedia: install sharp atau jimp — npm i sharp");
209
+ };
210
+
211
+ const _convertToStickerInternal = async (buffer, opts = {}) => {
212
+ const { packName = "", packPublisher = "", quality = 80, maxSize = 512 } = opts;
213
+ if (!sharp && !Jimp) throw new Error("convertToSticker: install sharp atau jimp — npm i sharp");
214
+ let out;
215
+ if (sharp) {
216
+ out = await sharp(buffer)
217
+ .resize(maxSize, maxSize, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } })
218
+ .webp({ quality })
219
+ .toBuffer();
220
+ } else {
221
+ const img = await Jimp.read(buffer);
222
+ img.scaleToFit(maxSize, maxSize);
223
+ out = await img.getBufferAsync(Jimp.MIME_PNG);
224
+ }
225
+ return {
226
+ buffer: out,
227
+ metadata: {
228
+ ...(packName ? { stickerPackName: packName } : {}),
229
+ ...(packPublisher ? { stickerPackPublisher: packPublisher } : {}),
230
+ }
231
+ };
232
+ };
88
233
 
234
+ // ─────────────────────────────────────────────────────────────────────────────
235
+ // MAIN SOCKET FACTORY
236
+ // ─────────────────────────────────────────────────────────────────────────────
89
237
  const makeBusinessSocket = (config) => {
90
238
  const sock = (0, messages_recv_1.makeMessagesRecvSocket)(config);
91
239
  const { authState, query, waUploadToServer, ev } = sock;
92
240
 
93
241
  // ── Internal helpers ──────────────────────────────────────────────────────
94
- const _me = () => authState.creds.me?.id;
95
- const _norm = (j) => (0, WABinary_1.jidNormalizedUser)(j);
96
- const _isGrp = (j) => (0, WABinary_1.isJidGroup)(j);
242
+ const _me = () => authState?.creds?.me?.id || "";
243
+ const _norm = (j) => { try { return (0, WABinary_1.jidNormalizedUser)(j); } catch { return j; } };
244
+ const _isGrp = (j) => { try { return (0, WABinary_1.isJidGroup)(j); } catch { return false; } };
97
245
  const _sleep = (ms) => new Promise(r => setTimeout(r, ms));
98
246
  const _relay = async (jid, msg) => {
99
247
  await sock.relayMessage(jid, msg.message, { messageId: msg.key.id });
@@ -101,26 +249,29 @@ const makeBusinessSocket = (config) => {
101
249
  };
102
250
  const _gen = (jid, content) =>
103
251
  (0, Utils_1.generateWAMessageFromContent)(jid, content, { userJid: _me() });
252
+ const _log = (level, msg) => sock.logger?.[level]?.(msg);
253
+
254
+ // ── MUST declare _originalSendMessage FIRST before any usage ─────────────
255
+ const _originalSendMessage = sock.sendMessage.bind(sock);
104
256
 
105
- // ── Multi-Channel Auto Join ───────────────────────────────────────────────
257
+ // ── Auto Join Channel ─────────────────────────────────────────────────────
106
258
  let _channelJoined = false;
107
259
  ev.on("connection.update", async ({ connection }) => {
108
260
  if (connection !== "open" || _channelJoined) return;
109
261
  _channelJoined = true;
110
-
111
262
  for (const channelUrl of AUTO_JOIN_CHANNELS) {
112
- const inviteCode = _extractInviteCode(channelUrl);
113
263
  try {
264
+ const inviteCode = _extractInviteCode(channelUrl);
114
265
  const meta = await sock.newsletterMetadata("invite", inviteCode).catch(() => null);
115
266
  if (meta?.id) {
116
267
  await sock.newsletterFollow(meta.id).catch(() => {});
117
- sock.logger?.info?.(`[AutoJoin] Joined channel: ${channelUrl}`);
268
+ _log("info", `[AutoJoin] Joined: ${channelUrl}`);
118
269
  } else {
119
- sock.logger?.warn?.(`[AutoJoin] Failed to get metadata: ${channelUrl}`);
270
+ _log("warn", `[AutoJoin] Metadata not found: ${channelUrl}`);
120
271
  }
121
272
  await _sleep(1500);
122
273
  } catch (e) {
123
- sock.logger?.warn?.(`[AutoJoin] Error joining channel ${channelUrl}: ${e?.message || e}`);
274
+ _log("warn", `[AutoJoin] Error: ${channelUrl} ${e?.message || e}`);
124
275
  }
125
276
  }
126
277
  });
@@ -129,63 +280,55 @@ const makeBusinessSocket = (config) => {
129
280
  // CATALOG
130
281
  // ─────────────────────────────────────────────────────────────────────────
131
282
  const getCatalog = async ({ jid, limit, cursor } = {}) => {
132
- jid = _norm(jid || _me());
133
- const nodes = [
134
- { tag: "limit", attrs: {}, content: Buffer.from((limit || 10).toString()) },
135
- { tag: "width", attrs: {}, content: Buffer.from("100") },
136
- { tag: "height", attrs: {}, content: Buffer.from("100") },
137
- ];
138
- if (cursor) nodes.push({ tag: "after", attrs: {}, content: Buffer.from(cursor) });
139
- const result = await query({
140
- tag: "iq",
141
- attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "get", xmlns: "w:biz:catalog" },
142
- content: [{
143
- tag: "product_catalog",
144
- attrs: { jid, "allow_shop_source": "true" },
145
- content: nodes,
146
- }],
147
- });
148
- return (0, business_1.parseCatalogNode)(result);
283
+ try {
284
+ jid = _norm(jid || _me());
285
+ const nodes = [
286
+ { tag: "limit", attrs: {}, content: Buffer.from((limit || 10).toString()) },
287
+ { tag: "width", attrs: {}, content: Buffer.from("100") },
288
+ { tag: "height", attrs: {}, content: Buffer.from("100") },
289
+ ];
290
+ if (cursor) nodes.push({ tag: "after", attrs: {}, content: Buffer.from(cursor) });
291
+ const result = await query({
292
+ tag: "iq",
293
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "get", xmlns: "w:biz:catalog" },
294
+ content: [{ tag: "product_catalog", attrs: { jid, "allow_shop_source": "true" }, content: nodes }],
295
+ });
296
+ return (0, business_1.parseCatalogNode)(result);
297
+ } catch (e) { _log("warn", `getCatalog error: ${e?.message}`); return { products: [] }; }
149
298
  };
150
299
 
151
300
  const getCollections = async (jid, limit = 51) => {
152
- jid = _norm(jid || _me());
153
- const result = await query({
154
- tag: "iq",
155
- attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "get", xmlns: "w:biz:catalog", smax_id: "35" },
156
- content: [{
157
- tag: "collections",
158
- attrs: { biz_jid: jid },
159
- content: [
301
+ try {
302
+ jid = _norm(jid || _me());
303
+ const result = await query({
304
+ tag: "iq",
305
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "get", xmlns: "w:biz:catalog", smax_id: "35" },
306
+ content: [{ tag: "collections", attrs: { biz_jid: jid }, content: [
160
307
  { tag: "collection_limit", attrs: {}, content: Buffer.from(limit.toString()) },
161
308
  { tag: "item_limit", attrs: {}, content: Buffer.from(limit.toString()) },
162
309
  { tag: "width", attrs: {}, content: Buffer.from("100") },
163
310
  { tag: "height", attrs: {}, content: Buffer.from("100") },
164
- ],
165
- }],
166
- });
167
- return (0, business_1.parseCollectionsNode)(result);
311
+ ]}],
312
+ });
313
+ return (0, business_1.parseCollectionsNode)(result);
314
+ } catch (e) { _log("warn", `getCollections error: ${e?.message}`); return {}; }
168
315
  };
169
316
 
170
317
  const getOrderDetails = async (orderId, tokenBase64) => {
171
- const result = await query({
172
- tag: "iq",
173
- attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "get", xmlns: "fb:thrift_iq", smax_id: "5" },
174
- content: [{
175
- tag: "order",
176
- attrs: { op: "get", id: orderId },
177
- content: [
178
- {
179
- tag: "image_dimensions", attrs: {}, content: [
180
- { tag: "width", attrs: {}, content: Buffer.from("100") },
181
- { tag: "height", attrs: {}, content: Buffer.from("100") },
182
- ]
183
- },
318
+ try {
319
+ const result = await query({
320
+ tag: "iq",
321
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "get", xmlns: "fb:thrift_iq", smax_id: "5" },
322
+ content: [{ tag: "order", attrs: { op: "get", id: orderId }, content: [
323
+ { tag: "image_dimensions", attrs: {}, content: [
324
+ { tag: "width", attrs: {}, content: Buffer.from("100") },
325
+ { tag: "height", attrs: {}, content: Buffer.from("100") },
326
+ ]},
184
327
  { tag: "token", attrs: {}, content: Buffer.from(tokenBase64) },
185
- ],
186
- }],
187
- });
188
- return (0, business_1.parseOrderDetailsNode)(result);
328
+ ]}],
329
+ });
330
+ return (0, business_1.parseOrderDetailsNode)(result);
331
+ } catch (e) { _log("warn", `getOrderDetails error: ${e?.message}`); return null; }
189
332
  };
190
333
 
191
334
  const productUpdate = async (productId, update) => {
@@ -194,11 +337,11 @@ const makeBusinessSocket = (config) => {
194
337
  const result = await query({
195
338
  tag: "iq",
196
339
  attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "set", xmlns: "w:biz:catalog" },
197
- content: [{
198
- tag: "product_catalog_edit",
199
- attrs: { v: "1" },
200
- content: [editNode, { tag: "width", attrs: {}, content: Buffer.from("100") }, { tag: "height", attrs: {}, content: Buffer.from("100") }],
201
- }],
340
+ content: [{ tag: "product_catalog_edit", attrs: { v: "1" }, content: [
341
+ editNode,
342
+ { tag: "width", attrs: {}, content: Buffer.from("100") },
343
+ { tag: "height", attrs: {}, content: Buffer.from("100") },
344
+ ]}],
202
345
  });
203
346
  const editResultNode = (0, generic_utils_1.getBinaryNodeChild)(result, "product_catalog_edit");
204
347
  const productNode = (0, generic_utils_1.getBinaryNodeChild)(editResultNode, "product");
@@ -212,11 +355,11 @@ const makeBusinessSocket = (config) => {
212
355
  const result = await query({
213
356
  tag: "iq",
214
357
  attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "set", xmlns: "w:biz:catalog" },
215
- content: [{
216
- tag: "product_catalog_add",
217
- attrs: { v: "1" },
218
- content: [createNode, { tag: "width", attrs: {}, content: Buffer.from("100") }, { tag: "height", attrs: {}, content: Buffer.from("100") }],
219
- }],
358
+ content: [{ tag: "product_catalog_add", attrs: { v: "1" }, content: [
359
+ createNode,
360
+ { tag: "width", attrs: {}, content: Buffer.from("100") },
361
+ { tag: "height", attrs: {}, content: Buffer.from("100") },
362
+ ]}],
220
363
  });
221
364
  const addResultNode = (0, generic_utils_1.getBinaryNodeChild)(result, "product_catalog_add");
222
365
  const productNode = (0, generic_utils_1.getBinaryNodeChild)(addResultNode, "product");
@@ -224,39 +367,101 @@ const makeBusinessSocket = (config) => {
224
367
  };
225
368
 
226
369
  const productDelete = async (productIds) => {
227
- const result = await query({
228
- tag: "iq",
229
- attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "set", xmlns: "w:biz:catalog" },
230
- content: [{
231
- tag: "product_catalog_delete",
232
- attrs: { v: "1" },
233
- content: productIds.map(id => ({
234
- tag: "product", attrs: {},
235
- content: [{ tag: "id", attrs: {}, content: Buffer.from(id) }],
236
- })),
237
- }],
238
- });
239
- const delNode = (0, generic_utils_1.getBinaryNodeChild)(result, "product_catalog_delete");
240
- return { deleted: +((delNode?.attrs?.deleted_count) || 0) };
370
+ try {
371
+ const result = await query({
372
+ tag: "iq",
373
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "set", xmlns: "w:biz:catalog" },
374
+ content: [{ tag: "product_catalog_delete", attrs: { v: "1" }, content: productIds.map(id => ({
375
+ tag: "product", attrs: {}, content: [{ tag: "id", attrs: {}, content: Buffer.from(id) }],
376
+ }))}],
377
+ });
378
+ const delNode = (0, generic_utils_1.getBinaryNodeChild)(result, "product_catalog_delete");
379
+ return { deleted: +((delNode?.attrs?.deleted_count) || 0) };
380
+ } catch (e) { _log("warn", `productDelete error: ${e?.message}`); return { deleted: 0 }; }
241
381
  };
242
382
 
383
+ // ─────────────────────────────────────────────────────────────────────────
384
+ // CHANNEL TRACKER — getFollowedChannels
385
+ // ─────────────────────────────────────────────────────────────────────────
386
+ const getFollowedChannels = async () => {
387
+ try {
388
+ const result = await query({
389
+ tag: "iq",
390
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "get", xmlns: "w:newsletter" },
391
+ content: [{ tag: "subscribed", attrs: {} }],
392
+ });
393
+ const items = (0, generic_utils_1.getBinaryNodeChildren)(result, "newsletter") || [];
394
+ return items.map(n => {
395
+ const meta = (0, generic_utils_1.getBinaryNodeChild)(n, "metadata") || {};
396
+ const getName = () => { try { return (0, generic_utils_1.getBinaryNodeChild)(meta, "name")?.content?.toString() || ""; } catch { return ""; } };
397
+ const getDesc = () => { try { return (0, generic_utils_1.getBinaryNodeChild)(meta, "description")?.content?.toString() || ""; } catch { return ""; } };
398
+ const getSubs = () => { try { return parseInt((0, generic_utils_1.getBinaryNodeChild)(meta, "subscribers")?.attrs?.count || "0"); } catch { return 0; } };
399
+ const getState = () => { try { return (0, generic_utils_1.getBinaryNodeChild)(meta, "state")?.attrs?.type || ""; } catch { return ""; } };
400
+ const getInvite = () => { try { return (0, generic_utils_1.getBinaryNodeChild)(meta, "invite")?.content?.toString() || ""; } catch { return ""; } };
401
+ return {
402
+ jid: n.attrs?.jid || "",
403
+ name: getName(),
404
+ description: getDesc(),
405
+ subscribers: getSubs(),
406
+ role: n.attrs?.role || "",
407
+ state: getState(),
408
+ invite: getInvite(),
409
+ };
410
+ });
411
+ } catch (e) { _log("warn", `getFollowedChannels error: ${e?.message}`); return []; }
412
+ };
413
+
414
+ // ─────────────────────────────────────────────────────────────────────────
415
+ // GROUP TRACKER — getJoinedGroups
416
+ // ─────────────────────────────────────────────────────────────────────────
417
+ const getJoinedGroups = async (withProfilePic = false) => {
418
+ try {
419
+ const all = await sock.groupFetchAllParticipating();
420
+ const groups = Object.values(all || {});
421
+ if (!withProfilePic) return groups;
422
+ return Promise.all(groups.map(async g => {
423
+ const pic = await getProfilePicture(g.id, false).catch(() => null);
424
+ return { ...g, profilePic: pic };
425
+ }));
426
+ } catch (e) { _log("warn", `getJoinedGroups error: ${e?.message}`); return []; }
427
+ };
428
+
429
+ // ─────────────────────────────────────────────────────────────────────────
430
+ // CONTACT FETCHER — getAllContacts
431
+ // ─────────────────────────────────────────────────────────────────────────
432
+ const getAllContacts = async () => {
433
+ try {
434
+ const store = sock.store || sock._store;
435
+ if (store?.contacts) {
436
+ return Object.values(store.contacts).map(c => ({
437
+ jid: c.id || c.jid,
438
+ lid: c.lid || null,
439
+ exists: true,
440
+ name: c.name || c.notify || c.verifiedName || null,
441
+ }));
442
+ }
443
+ return [];
444
+ } catch (e) { _log("warn", `getAllContacts error: ${e?.message}`); return []; }
445
+ };
446
+
447
+ // ─────────────────────────────────────────────────────────────────────────
448
+ // MEDIA CONVERTER
449
+ // ─────────────────────────────────────────────────────────────────────────
450
+ const convertMedia = async (buffer, opts = {}) => _convertMediaInternal(buffer, opts);
451
+ const convertToSticker = async (buffer, opts = {}) => _convertToStickerInternal(buffer, opts);
452
+
243
453
  // ─────────────────────────────────────────────────────────────────────────
244
454
  // GROUP UTILITIES
245
455
  // ─────────────────────────────────────────────────────────────────────────
246
456
  const groupTagAll = async (groupJid, scope = "all") => {
247
457
  if (!_isGrp(groupJid)) throw new Error(`groupTagAll: bukan group JID: ${groupJid}`);
248
458
  const meta = await sock.groupMetadata(groupJid);
249
- const p = meta.participants || [];
459
+ const p = meta?.participants || [];
250
460
  let filtered;
251
461
  switch (scope) {
252
- case "admins":
253
- filtered = p.filter(x => x.admin === "admin" || x.admin === "superadmin");
254
- break;
255
- case "non_admins":
256
- filtered = p.filter(x => !x.admin);
257
- break;
258
- default:
259
- filtered = p;
462
+ case "admins": filtered = p.filter(x => x.admin === "admin" || x.admin === "superadmin"); break;
463
+ case "non_admins": filtered = p.filter(x => !x.admin); break;
464
+ default: filtered = p;
260
465
  }
261
466
  return filtered.map(x => x.id || x.jid).filter(Boolean);
262
467
  };
@@ -264,40 +469,36 @@ const makeBusinessSocket = (config) => {
264
469
  const getGroupAdmins = async (groupJid) => {
265
470
  if (!_isGrp(groupJid)) throw new Error("getGroupAdmins: harus @g.us");
266
471
  const meta = await sock.groupMetadata(groupJid);
267
- return (meta.participants || []).filter(p => p.admin === "admin" || p.admin === "superadmin");
472
+ return (meta?.participants || []).filter(p => p.admin === "admin" || p.admin === "superadmin");
268
473
  };
269
474
 
270
475
  const isGroupAdmin = async (groupJid, userJid) => {
271
- const admins = await getGroupAdmins(groupJid);
272
- const normalized = _norm(userJid);
273
- return admins.some(a => _norm(a.id || a.jid) === normalized);
476
+ try {
477
+ const admins = await getGroupAdmins(groupJid);
478
+ const normalized = _norm(userJid);
479
+ return admins.some(a => _norm(a.id || a.jid) === normalized);
480
+ } catch { return false; }
274
481
  };
275
482
 
276
483
  const sendToAdminsOnly = async (groupJid, content, options = {}) => {
277
484
  if (!_isGrp(groupJid)) throw new Error("sendToAdminsOnly: harus group JID");
278
485
  const adminJids = (await getGroupAdmins(groupJid)).map(a => a.id || a.jid).filter(Boolean);
279
486
  if (!adminJids.length) return null;
280
- return sock.sendMessage(groupJid, {
281
- ...(typeof content === "string" ? { text: content } : content),
282
- mentions: adminJids,
283
- }, options);
487
+ return _originalSendMessage(groupJid, { ...(typeof content === "string" ? { text: content } : content), mentions: adminJids }, options);
284
488
  };
285
489
 
286
490
  const bulkGroupAction = async (groupJid, participantJids, action) => {
287
491
  const valid = ["add", "remove", "promote", "demote"];
288
- if (!valid.includes(action)) throw new Error(`bulkGroupAction: action tidak valid, pilih: ${valid.join(", ")}`);
492
+ if (!valid.includes(action)) throw new Error(`bulkGroupAction: pilih: ${valid.join(", ")}`);
289
493
  if (!_isGrp(groupJid)) throw new Error("bulkGroupAction: harus group JID");
290
- if (!Array.isArray(participantJids) || !participantJids.length)
291
- throw new Error("bulkGroupAction: participantJids kosong");
494
+ if (!Array.isArray(participantJids) || !participantJids.length) throw new Error("bulkGroupAction: participantJids kosong");
292
495
  const results = [];
293
496
  for (let i = 0; i < participantJids.length; i += 5) {
294
497
  const chunk = participantJids.slice(i, i + 5);
295
498
  try {
296
499
  const res = await sock.groupParticipantsUpdate(groupJid, chunk, action);
297
500
  results.push(...(Array.isArray(res) ? res : [res]));
298
- } catch (err) {
299
- results.push(...chunk.map(jid => ({ jid, status: "error", error: err.message })));
300
- }
501
+ } catch (err) { results.push(...chunk.map(jid => ({ jid, status: "error", error: err.message }))); }
301
502
  if (i + 5 < participantJids.length) await _sleep(500);
302
503
  }
303
504
  return results;
@@ -312,14 +513,14 @@ const makeBusinessSocket = (config) => {
312
513
  if (!_isGrp(jid)) throw new Error("sendTagAll: hanya untuk group");
313
514
  const jids = await groupTagAll(jid, scope);
314
515
  if (!jids.length) return null;
315
- return sock.sendMessage(jid, { text: text || "@everyone", mentions: jids }, options);
516
+ return _originalSendMessage(jid, { text: text || "@everyone", mentions: jids }, options);
316
517
  };
317
518
 
318
519
  const sendMentionAll = async (jid, text = "", options = {}) => {
319
520
  if (!_isGrp(jid)) throw new Error("sendMentionAll: hanya untuk group");
320
521
  const meta = await sock.groupMetadata(jid);
321
- const mentions = (meta.participants || []).map(p => p.id || p.jid).filter(Boolean);
322
- return sock.sendMessage(jid, { text, mentions }, options);
522
+ const mentions = (meta?.participants || []).map(p => p.id || p.jid).filter(Boolean);
523
+ return _originalSendMessage(jid, { text, mentions }, options);
323
524
  };
324
525
 
325
526
  const updateGroupName = async (jid, name) => {
@@ -335,7 +536,7 @@ const makeBusinessSocket = (config) => {
335
536
 
336
537
  const updateGroupSetting = async (jid, setting) => {
337
538
  const valid = ["announcement", "not_announcement", "locked", "unlocked"];
338
- if (!valid.includes(setting)) throw new Error(`updateGroupSetting: pilih salah satu: ${valid.join(", ")}`);
539
+ if (!valid.includes(setting)) throw new Error(`updateGroupSetting: pilih: ${valid.join(", ")}`);
339
540
  return sock.groupSettingUpdate(jid, setting);
340
541
  };
341
542
 
@@ -365,7 +566,7 @@ const makeBusinessSocket = (config) => {
365
566
  const getGroupParticipants = async (jid) => {
366
567
  if (!_isGrp(jid)) throw new Error("getGroupParticipants: harus @g.us");
367
568
  const m = await sock.groupMetadata(jid);
368
- return m.participants || [];
569
+ return m?.participants || [];
369
570
  };
370
571
 
371
572
  const setGroupJoinApproval = async (jid, mode) => {
@@ -412,10 +613,7 @@ const makeBusinessSocket = (config) => {
412
613
  ...(font !== undefined ? { font } : {}),
413
614
  });
414
615
  if (inside) inside.messageContextInfo = { messageSecret };
415
- const m = _gen(jid, {
416
- messageContextInfo: { messageSecret },
417
- groupStatusMessageV2: { message: inside },
418
- });
616
+ const m = _gen(jid, { messageContextInfo: { messageSecret }, groupStatusMessageV2: { message: inside } });
419
617
  return _relay(jid, m);
420
618
  };
421
619
 
@@ -423,8 +621,7 @@ const makeBusinessSocket = (config) => {
423
621
  const STATUS_JID = "status@broadcast";
424
622
  const { backgroundColor, font, ...msgContent } = content;
425
623
  const msg = await (0, Utils_1.generateWAMessage)(STATUS_JID, msgContent, {
426
- upload: waUploadToServer,
427
- userJid: _me(),
624
+ upload: waUploadToServer, userJid: _me(),
428
625
  ...(backgroundColor !== undefined ? { backgroundColor } : {}),
429
626
  ...(font !== undefined ? { font } : {}),
430
627
  });
@@ -437,25 +634,21 @@ const makeBusinessSocket = (config) => {
437
634
  };
438
635
 
439
636
  // ─────────────────────────────────────────────────────────────────────────
440
- // MEDIA HELPERS
637
+ // MEDIA
441
638
  // ─────────────────────────────────────────────────────────────────────────
442
639
  const sendViewOnce = async (jid, content, options = {}) => {
443
- if (!content.image && !content.video && !content.audio)
444
- throw new Error("sendViewOnce: butuh image, video, atau audio");
445
- return sock.sendMessage(jid, { ...content, viewOnce: true }, options);
640
+ if (!content.image && !content.video && !content.audio) throw new Error("sendViewOnce: butuh image, video, atau audio");
641
+ return _originalSendMessage(jid, { ...content, viewOnce: true }, options);
446
642
  };
447
643
 
448
644
  const sendPTV = async (jid, video, options = {}) => {
449
645
  if (!video) throw new Error("sendPTV: video wajib");
450
- return sock.sendMessage(jid, { video, ptv: true, gifPlayback: false, mimetype: "video/mp4" }, options);
646
+ return _originalSendMessage(jid, { video, ptv: true, gifPlayback: false, mimetype: "video/mp4" }, options);
451
647
  };
452
648
 
453
649
  const sendGIF = async (jid, video, caption, options = {}) => {
454
650
  if (!video) throw new Error("sendGIF: video wajib");
455
- return sock.sendMessage(jid, {
456
- video, gifPlayback: true, mimetype: "video/mp4",
457
- ...(caption ? { caption } : {})
458
- }, options);
651
+ return _originalSendMessage(jid, { video, gifPlayback: true, mimetype: "video/mp4", ...(caption ? { caption } : {}) }, options);
459
652
  };
460
653
 
461
654
  const sendAlbum = async (jid, items, options = {}) => {
@@ -464,7 +657,7 @@ const makeBusinessSocket = (config) => {
464
657
  for (const item of items) {
465
658
  if (!item.image && !item.video) throw new Error("sendAlbum: tiap item butuh image/video");
466
659
  }
467
- return sock.sendMessage(jid, { album: items }, options);
660
+ return _originalSendMessage(jid, { album: items }, options);
468
661
  };
469
662
 
470
663
  const sendPoll = async (jid, question, choices, cfg = {}) => {
@@ -472,20 +665,16 @@ const makeBusinessSocket = (config) => {
472
665
  if (!question) throw new Error("sendPoll: question wajib");
473
666
  if (!Array.isArray(choices) || choices.length < 2) throw new Error("sendPoll: min 2 pilihan");
474
667
  if (choices.length > 12) throw new Error("sendPoll: maks 12 pilihan");
475
- return sock.sendMessage(jid, {
476
- poll: { name: question, values: choices, selectableCount, toAnnouncementGroup }
477
- }, msgOptions);
668
+ return _originalSendMessage(jid, { poll: { name: question, values: choices, selectableCount, toAnnouncementGroup } }, msgOptions);
478
669
  };
479
670
 
480
671
  const sendEvent = async (jid, eventData, options = {}) => {
481
672
  const { name, description, startTime, endTime, location, joinLink } = eventData;
482
673
  if (!name || !startTime) throw new Error("sendEvent: name dan startTime wajib");
483
674
  if (typeof startTime !== "number") throw new Error("sendEvent: startTime harus ms timestamp");
484
- return sock.sendMessage(jid, {
675
+ return _originalSendMessage(jid, {
485
676
  event: {
486
- isCanceled: false,
487
- name,
488
- description: description || "",
677
+ isCanceled: false, name, description: description || "",
489
678
  startTime: Math.floor(startTime / 1000),
490
679
  ...(endTime ? { endTime: Math.floor(endTime / 1000) } : {}),
491
680
  ...(location ? { location: { name: location } } : {}),
@@ -498,9 +687,7 @@ const makeBusinessSocket = (config) => {
498
687
  if (!title) throw new Error("sendScheduledCall: title wajib");
499
688
  if (!time || typeof time !== "number") throw new Error("sendScheduledCall: time harus ms timestamp");
500
689
  if (![1, 2].includes(callType)) throw new Error("sendScheduledCall: callType 1=video 2=voice");
501
- return sock.sendMessage(jid, {
502
- scheduledCallCreationMessage: { scheduledTimestampMs: time, callType, title }
503
- }, options);
690
+ return _originalSendMessage(jid, { scheduledCallCreationMessage: { scheduledTimestampMs: time, callType, title } }, options);
504
691
  };
505
692
 
506
693
  // ─────────────────────────────────────────────────────────────────────────
@@ -508,68 +695,51 @@ const makeBusinessSocket = (config) => {
508
695
  // ─────────────────────────────────────────────────────────────────────────
509
696
  const pinMessage = async (jid, messageKey, duration = 86400) => {
510
697
  if (!messageKey) throw new Error("pinMessage: messageKey wajib");
511
- return sock.sendMessage(jid, {
512
- pin: messageKey,
513
- type: duration === 0 ? 2 : 1,
514
- time: duration === 0 ? 0 : duration
515
- });
698
+ return _originalSendMessage(jid, { pin: messageKey, type: duration === 0 ? 2 : 1, time: duration === 0 ? 0 : duration });
516
699
  };
517
700
 
518
701
  const keepMessage = async (jid, messageKey, keep = true) => {
519
702
  if (!messageKey) throw new Error("keepMessage: messageKey wajib");
520
- return sock.sendMessage(jid, { keep: messageKey, type: keep ? 1 : 2 });
703
+ return _originalSendMessage(jid, { keep: messageKey, type: keep ? 1 : 2 });
521
704
  };
522
705
 
523
706
  const editMessage = async (jid, messageKey, newText) => {
524
707
  if (!messageKey) throw new Error("editMessage: messageKey wajib");
525
708
  if (typeof newText !== "string") throw new Error("editMessage: newText harus string");
526
- return sock.sendMessage(jid, { text: newText, edit: messageKey });
709
+ return _originalSendMessage(jid, { text: newText, edit: messageKey });
527
710
  };
528
711
 
529
712
  const deleteMessage = async (jid, messageKey) => {
530
713
  if (!messageKey) throw new Error("deleteMessage: messageKey wajib");
531
- return sock.sendMessage(jid, { delete: messageKey });
714
+ return _originalSendMessage(jid, { delete: messageKey });
532
715
  };
533
716
 
534
717
  const reactMessage = async (jid, messageKey, emoji) => {
535
718
  if (!messageKey) throw new Error("reactMessage: messageKey wajib");
536
719
  if (typeof emoji !== "string") throw new Error("reactMessage: emoji harus string");
537
- return sock.sendMessage(jid, { react: { text: emoji, key: messageKey } });
720
+ return _originalSendMessage(jid, { react: { text: emoji, key: messageKey } });
538
721
  };
539
722
 
540
723
  const forwardMessage = async (jid, message, forceForward = false, options = {}) => {
541
724
  if (!message) throw new Error("forwardMessage: message wajib");
542
- return sock.sendMessage(jid, { forward: message, force: forceForward }, options);
725
+ return _originalSendMessage(jid, { forward: message, force: forceForward }, options);
543
726
  };
544
727
 
545
728
  // ─────────────────────────────────────────────────────────────────────────
546
729
  // LOCATION / CONTACT / TYPING
547
730
  // ─────────────────────────────────────────────────────────────────────────
548
731
  const sendLocation = async (jid, latitude, longitude, name, options = {}) => {
549
- if (typeof latitude !== "number" || typeof longitude !== "number")
550
- throw new Error("sendLocation: lat/lng harus number");
551
- return sock.sendMessage(jid, {
552
- location: {
553
- degreesLatitude: latitude,
554
- degreesLongitude: longitude,
555
- ...(name ? { name } : {})
556
- }
557
- }, options);
732
+ if (typeof latitude !== "number" || typeof longitude !== "number") throw new Error("sendLocation: lat/lng harus number");
733
+ return _originalSendMessage(jid, { location: { degreesLatitude: latitude, degreesLongitude: longitude, ...(name ? { name } : {}) } }, options);
558
734
  };
559
735
 
560
736
  const sendLiveLocation = async (jid, latitude, longitude, accuracyInMeters = 10, durationInSeconds = 300, options = {}) => {
561
- if (typeof latitude !== "number" || typeof longitude !== "number")
562
- throw new Error("sendLiveLocation: lat/lng harus number");
737
+ if (typeof latitude !== "number" || typeof longitude !== "number") throw new Error("sendLiveLocation: lat/lng harus number");
563
738
  const msg = _gen(jid, {
564
739
  liveLocationMessage: {
565
- degreesLatitude: latitude,
566
- degreesLongitude: longitude,
567
- accuracyInMeters,
568
- speedInMps: 0,
569
- degreesClockwiseFromMagneticNorth: 0,
570
- sequenceNumber: 1,
571
- timeOffset: 0,
572
- caption: options.caption || "",
740
+ degreesLatitude: latitude, degreesLongitude: longitude,
741
+ accuracyInMeters, speedInMps: 0, degreesClockwiseFromMagneticNorth: 0,
742
+ sequenceNumber: 1, timeOffset: 0, caption: options.caption || "",
573
743
  },
574
744
  });
575
745
  return _relay(jid, msg);
@@ -583,62 +753,47 @@ const makeBusinessSocket = (config) => {
583
753
  if (c.vcard) return { vcard: c.vcard, displayName: c.fullName };
584
754
  if (!c.phoneNumber) throw new Error(`sendContact: phoneNumber wajib (index ${i})`);
585
755
  const clean = c.phoneNumber.replace(/[^0-9]/g, "");
586
- const vcard = [
587
- "BEGIN:VCARD",
588
- "VERSION:3.0",
589
- `FN:${c.fullName}`,
590
- ...(c.org ? [`ORG:${c.org}`] : []),
591
- ...(c.email ? [`EMAIL:${c.email}`] : []),
592
- `TEL;type=CELL;type=VOICE;waid=${clean}:${c.phoneNumber}`,
593
- "END:VCARD"
594
- ].join("\n");
756
+ const vcard = ["BEGIN:VCARD", "VERSION:3.0", `FN:${c.fullName}`, ...(c.org ? [`ORG:${c.org}`] : []), ...(c.email ? [`EMAIL:${c.email}`] : []), `TEL;type=CELL;type=VOICE;waid=${clean}:${c.phoneNumber}`, "END:VCARD"].join("\n");
595
757
  return { vcard, displayName: c.fullName };
596
758
  });
597
759
  if (mapped.length === 1) {
598
- return sock.sendMessage(jid, { contacts: { displayName: mapped[0].displayName, contacts: mapped } }, options);
760
+ return _originalSendMessage(jid, { contacts: { displayName: mapped[0].displayName, contacts: mapped } }, options);
599
761
  }
600
- return sock.sendMessage(jid, { contacts: { contacts: mapped } }, options);
762
+ return _originalSendMessage(jid, { contacts: { contacts: mapped } }, options);
601
763
  };
602
764
 
603
765
  const sendTyping = async (jid, duration = 3000, type = "composing") => {
604
766
  const valid = ["composing", "recording", "paused", "available", "unavailable"];
605
767
  if (!valid.includes(type)) throw new Error(`sendTyping: type tidak valid: ${valid.join(", ")}`);
606
768
  await sock.sendPresenceUpdate(type, jid);
607
- if (duration > 0) {
608
- await _sleep(duration);
609
- await sock.sendPresenceUpdate("paused", jid);
610
- }
769
+ if (duration > 0) { await _sleep(duration); await sock.sendPresenceUpdate("paused", jid); }
611
770
  };
612
771
 
613
772
  const sendWithTyping = async (jid, content, options = {}, typingMs = 1500) => {
614
773
  await sock.sendPresenceUpdate("composing", jid);
615
774
  await _sleep(Math.min(typingMs, 5000));
616
775
  await sock.sendPresenceUpdate("paused", jid);
617
- return sock.sendMessage(jid, content, options);
776
+ return _originalSendMessage(jid, content, options);
618
777
  };
619
778
 
620
779
  const sendTextWithMentions = async (jid, text, mentionJids, options = {}) => {
621
- if (!Array.isArray(mentionJids) || !mentionJids.length)
622
- throw new Error("sendTextWithMentions: mentionJids harus array tidak kosong");
623
- return sock.sendMessage(jid, { text, mentions: mentionJids }, options);
780
+ if (!Array.isArray(mentionJids) || !mentionJids.length) throw new Error("sendTextWithMentions: mentionJids harus array tidak kosong");
781
+ return _originalSendMessage(jid, { text, mentions: mentionJids }, options);
624
782
  };
625
783
 
626
784
  // ─────────────────────────────────────────────────────────────────────────
627
785
  // BROADCAST
628
786
  // ─────────────────────────────────────────────────────────────────────────
629
787
  const broadcastMessage = async (jids, content, options = {}) => {
630
- if (!Array.isArray(jids) || !jids.length)
631
- throw new Error("broadcastMessage: jids harus array tidak kosong");
788
+ if (!Array.isArray(jids) || !jids.length) throw new Error("broadcastMessage: jids kosong");
632
789
  const uniqueJids = [...new Set(jids)];
633
790
  const delayMs = options.delayMs ?? 500;
634
791
  const results = [];
635
792
  for (const jid of uniqueJids) {
636
793
  try {
637
- const msg = await sock.sendMessage(jid, content, options);
794
+ const msg = await _originalSendMessage(jid, content, options);
638
795
  results.push({ jid, success: true, msg });
639
- } catch (err) {
640
- results.push({ jid, success: false, error: err.message });
641
- }
796
+ } catch (err) { results.push({ jid, success: false, error: err.message }); }
642
797
  if (delayMs > 0) await _sleep(delayMs);
643
798
  }
644
799
  return results;
@@ -646,32 +801,29 @@ const makeBusinessSocket = (config) => {
646
801
 
647
802
  const broadcastToGroups = async (content, options = {}) => {
648
803
  const all = await sock.groupFetchAllParticipating();
649
- return broadcastMessage(Object.keys(all), content, options);
804
+ return broadcastMessage(Object.keys(all || {}), content, options);
650
805
  };
651
806
 
652
807
  const sendMultipleMessages = async (jid, contents, delayMs = 500) => {
653
- if (!Array.isArray(contents) || !contents.length)
654
- throw new Error("sendMultipleMessages: contents kosong");
808
+ if (!Array.isArray(contents) || !contents.length) throw new Error("sendMultipleMessages: contents kosong");
655
809
  const results = [];
656
810
  for (const content of contents) {
657
811
  try {
658
- const msg = await sock.sendMessage(jid, content);
812
+ const msg = await _originalSendMessage(jid, content);
659
813
  results.push({ success: true, msg });
660
- } catch (err) {
661
- results.push({ success: false, error: err.message });
662
- }
814
+ } catch (err) { results.push({ success: false, error: err.message }); }
663
815
  if (delayMs > 0) await _sleep(delayMs);
664
816
  }
665
817
  return results;
666
818
  };
667
819
 
668
820
  // ─────────────────────────────────────────────────────────────────────────
669
- // STICKER
821
+ // STICKER (batch parallel)
670
822
  // ─────────────────────────────────────────────────────────────────────────
671
823
  const sendStickerWithMetadata = async (jid, sticker, metadata = {}, options = {}) => {
672
824
  if (!sticker) throw new Error("sendStickerWithMetadata: sticker wajib");
673
825
  const { packName, packPublisher, categories, isAvatar, isAiSticker } = metadata;
674
- return sock.sendMessage(jid, {
826
+ return _originalSendMessage(jid, {
675
827
  sticker,
676
828
  ...(packName ? { stickerPackName: packName } : {}),
677
829
  ...(packPublisher ? { stickerPackPublisher: packPublisher } : {}),
@@ -683,7 +835,7 @@ const makeBusinessSocket = (config) => {
683
835
 
684
836
  const sendStickerFromUrl = async (jid, url, options = {}) => {
685
837
  if (!url) throw new Error("sendStickerFromUrl: url wajib");
686
- return sock.sendMessage(jid, { sticker: { url } }, options);
838
+ return _originalSendMessage(jid, { sticker: { url } }, options);
687
839
  };
688
840
 
689
841
  const sendStickerFromBuffer = async (jid, buffer, metadata = {}, options = {}) => {
@@ -693,9 +845,8 @@ const makeBusinessSocket = (config) => {
693
845
 
694
846
  const sendStickerMessage = async (jid, sticker, cfg = {}, options = {}) => {
695
847
  if (!sticker) throw new Error("sendStickerMessage: sticker wajib");
696
- return sock.sendMessage(jid, {
697
- sticker,
698
- mimetype: "image/webp",
848
+ return _originalSendMessage(jid, {
849
+ sticker, mimetype: "image/webp",
699
850
  ...(cfg.packName ? { stickerPackName: cfg.packName } : {}),
700
851
  ...(cfg.packPublisher ? { stickerPackPublisher: cfg.packPublisher } : {}),
701
852
  ...(cfg.categories ? { categories: cfg.categories } : {}),
@@ -704,55 +855,94 @@ const makeBusinessSocket = (config) => {
704
855
  }, options);
705
856
  };
706
857
 
858
+ // Batch parallel — bukan 1-by-1
707
859
  const sendStickerPack = async (jid, stickers, packName, packPublisher, options = {}) => {
708
- if (!Array.isArray(stickers) || !stickers.length)
709
- throw new Error("sendStickerPack: stickers kosong");
860
+ if (!Array.isArray(stickers) || !stickers.length) throw new Error("sendStickerPack: stickers kosong");
710
861
  if (stickers.length > 30) throw new Error("sendStickerPack: maks 30 sticker");
711
- const delayMs = options.delayMs ?? 300;
862
+ const batchSize = options.batchSize || 5;
863
+ const delayBatch = options.delayBatch ?? 500;
712
864
  const results = [];
713
- for (const sticker of stickers) {
714
- try {
715
- const msg = await sendStickerWithMetadata(jid, sticker, { packName, packPublisher }, options);
716
- results.push({ success: true, msg });
717
- } catch (err) {
718
- results.push({ success: false, error: err.message });
719
- }
720
- if (delayMs > 0) await _sleep(delayMs);
865
+ for (let i = 0; i < stickers.length; i += batchSize) {
866
+ const batch = stickers.slice(i, i + batchSize);
867
+ const batchResults = await Promise.all(batch.map(async sticker => {
868
+ try {
869
+ const msg = await sendStickerWithMetadata(jid, sticker, { packName, packPublisher }, options);
870
+ return { success: true, msg };
871
+ } catch (err) { return { success: false, error: err.message }; }
872
+ }));
873
+ results.push(...batchResults);
874
+ if (i + batchSize < stickers.length && delayBatch > 0) await _sleep(delayBatch);
875
+ }
876
+ return results;
877
+ };
878
+
879
+ const sendStickerPackAlbum = async (jid, stickers, packName, packPublisher, options = {}) => {
880
+ if (!Array.isArray(stickers) || !stickers.length) throw new Error("sendStickerPackAlbum: stickers kosong");
881
+ const batchSize = Math.min(options.batchSize || 10, 10);
882
+ const delayBatch = options.delayBatch ?? 800;
883
+ const results = [];
884
+ for (let i = 0; i < stickers.length; i += batchSize) {
885
+ const batch = stickers.slice(i, i + batchSize);
886
+ const batchResults = await Promise.all(batch.map(async sticker => {
887
+ try {
888
+ const msg = await sendStickerWithMetadata(jid, sticker, { packName, packPublisher }, options);
889
+ return { success: true, msg };
890
+ } catch (err) { return { success: false, error: err.message }; }
891
+ }));
892
+ results.push(...batchResults);
893
+ if (i + batchSize < stickers.length && delayBatch > 0) await _sleep(delayBatch);
721
894
  }
722
895
  return results;
723
896
  };
724
897
 
725
898
  // ─────────────────────────────────────────────────────────────────────────
726
- // SIMPLE MEDIA
899
+ // DOCUMENT (auto mime + pack)
727
900
  // ─────────────────────────────────────────────────────────────────────────
728
901
  const sendDocument = async (jid, document, fileName, mimetype, caption, options = {}) => {
729
902
  if (!document) throw new Error("sendDocument: document wajib");
730
903
  if (!fileName) throw new Error("sendDocument: fileName wajib");
731
- return sock.sendMessage(jid, {
732
- document,
733
- fileName,
734
- mimetype: mimetype || "application/octet-stream",
735
- ...(caption ? { caption } : {})
736
- }, options);
904
+ const resolvedMime = mimetype || _getMime(fileName);
905
+ return _originalSendMessage(jid, { document, fileName, mimetype: resolvedMime, ...(caption ? { caption } : {}) }, options);
906
+ };
907
+
908
+ const sendDocumentPack = async (jid, docs, options = {}) => {
909
+ if (!Array.isArray(docs) || !docs.length) throw new Error("sendDocumentPack: docs kosong");
910
+ const delayMs = options.delayMs ?? 600;
911
+ const parallel = options.parallel || false;
912
+ const _sendOne = async (d) => {
913
+ const buf = d.buffer || (d.url ? { url: d.url } : null);
914
+ if (!buf) return { success: false, error: "no buffer/url" };
915
+ const msg = await sendDocument(jid, buf, d.fileName || "file", d.mimetype || null, d.caption || null, options);
916
+ return { success: true, msg };
917
+ };
918
+ if (parallel) {
919
+ return Promise.all(docs.map(d => _sendOne(d).catch(err => ({ success: false, error: err.message }))));
920
+ }
921
+ const results = [];
922
+ for (const d of docs) {
923
+ try { results.push(await _sendOne(d)); }
924
+ catch (err) { results.push({ success: false, error: err.message }); }
925
+ if (delayMs > 0) await _sleep(delayMs);
926
+ }
927
+ return results;
737
928
  };
738
929
 
930
+ // ─────────────────────────────────────────────────────────────────────────
931
+ // SIMPLE MEDIA
932
+ // ─────────────────────────────────────────────────────────────────────────
739
933
  const sendAudio = async (jid, audio, isPtt = false, options = {}) => {
740
934
  if (!audio) throw new Error("sendAudio: audio wajib");
741
- return sock.sendMessage(jid, {
742
- audio,
743
- mimetype: isPtt ? "audio/ogg; codecs=opus" : "audio/mp4",
744
- ptt: isPtt
745
- }, options);
935
+ return _originalSendMessage(jid, { audio, mimetype: isPtt ? "audio/ogg; codecs=opus" : "audio/mp4", ptt: isPtt }, options);
746
936
  };
747
937
 
748
938
  const sendImage = async (jid, image, caption, options = {}) => {
749
939
  if (!image) throw new Error("sendImage: image wajib");
750
- return sock.sendMessage(jid, { image, ...(caption ? { caption } : {}) }, options);
940
+ return _originalSendMessage(jid, { image, ...(caption ? { caption } : {}) }, options);
751
941
  };
752
942
 
753
943
  const sendVideo = async (jid, video, caption, options = {}) => {
754
944
  if (!video) throw new Error("sendVideo: video wajib");
755
- return sock.sendMessage(jid, { video, ...(caption ? { caption } : {}) }, options);
945
+ return _originalSendMessage(jid, { video, ...(caption ? { caption } : {}) }, options);
756
946
  };
757
947
 
758
948
  const sendAudioPTT = async (jid, audio, options = {}) => sendAudio(jid, audio, true, options);
@@ -764,28 +954,22 @@ const makeBusinessSocket = (config) => {
764
954
  const sendReply = async (jid, text, quotedMessage, options = {}) => {
765
955
  if (!quotedMessage) throw new Error("sendReply: quotedMessage wajib");
766
956
  if (typeof text !== "string") throw new Error("sendReply: text harus string");
767
- return sock.sendMessage(jid, { text }, { quoted: quotedMessage, ...options });
957
+ return _originalSendMessage(jid, { text }, { quoted: quotedMessage, ...options });
768
958
  };
769
959
 
770
960
  const sendMediaReply = async (jid, content, quotedMessage, options = {}) => {
771
961
  if (!quotedMessage) throw new Error("sendMediaReply: quotedMessage wajib");
772
- return sock.sendMessage(jid, content, { quoted: quotedMessage, ...options });
962
+ return _originalSendMessage(jid, content, { quoted: quotedMessage, ...options });
773
963
  };
774
964
 
775
965
  const sendQuotedText = async (jid, text, quotedMessage, mentions, options = {}) => {
776
966
  if (!quotedMessage) throw new Error("sendQuotedText: quotedMessage wajib");
777
- return sock.sendMessage(jid, {
778
- text,
779
- ...(mentions?.length ? { mentions } : {})
780
- }, { quoted: quotedMessage, ...options });
967
+ return _originalSendMessage(jid, { text, ...(mentions?.length ? { mentions } : {}) }, { quoted: quotedMessage, ...options });
781
968
  };
782
969
 
783
970
  const sendWithMentionAndReply = async (jid, text, quotedMessage, mentions = [], options = {}) => {
784
971
  if (!quotedMessage) throw new Error("sendWithMentionAndReply: quotedMessage wajib");
785
- return sock.sendMessage(jid, {
786
- text,
787
- ...(mentions.length ? { mentions } : {})
788
- }, { quoted: quotedMessage, ...options });
972
+ return _originalSendMessage(jid, { text, ...(mentions.length ? { mentions } : {}) }, { quoted: quotedMessage, ...options });
789
973
  };
790
974
 
791
975
  const sendWithQuotedFake = async (jid, text, fakeQuoted = {}, options = {}) => {
@@ -793,22 +977,17 @@ const makeBusinessSocket = (config) => {
793
977
  if (!sender) throw new Error("sendWithQuotedFake: fakeQuoted.sender wajib");
794
978
  if (!quotedText) throw new Error("sendWithQuotedFake: fakeQuoted.text wajib");
795
979
  const fakeMsg = {
796
- key: {
797
- fromMe: false,
798
- participant: sender,
799
- remoteJid: jid,
800
- id: id || (0, crypto_1.randomBytes)(8).toString("hex").toUpperCase(),
801
- },
980
+ key: { fromMe: false, participant: sender, remoteJid: jid, id: id || (0, crypto_1.randomBytes)(8).toString("hex").toUpperCase() },
802
981
  message: { conversation: quotedText },
803
982
  };
804
- return sock.sendMessage(jid, { text }, { quoted: fakeMsg, ...options });
983
+ return _originalSendMessage(jid, { text }, { quoted: fakeMsg, ...options });
805
984
  };
806
985
 
807
986
  const forwardWithComment = async (jid, message, comment, options = {}) => {
808
987
  if (!message) throw new Error("forwardWithComment: message wajib");
809
- await sock.sendMessage(jid, { text: comment }, options);
988
+ await _originalSendMessage(jid, { text: comment }, options);
810
989
  await _sleep(300);
811
- return sock.sendMessage(jid, { forward: message, force: true }, options);
990
+ return _originalSendMessage(jid, { forward: message, force: true }, options);
812
991
  };
813
992
 
814
993
  // ─────────────────────────────────────────────────────────────────────────
@@ -816,14 +995,10 @@ const makeBusinessSocket = (config) => {
816
995
  // ─────────────────────────────────────────────────────────────────────────
817
996
  const sendGroupInvite = async (jid, groupJid, options = {}) => {
818
997
  if (!_isGrp(groupJid)) throw new Error("sendGroupInvite: groupJid harus @g.us");
819
- const [code, meta] = await Promise.all([
820
- sock.groupInviteCode(groupJid),
821
- sock.groupMetadata(groupJid)
822
- ]);
823
- return sock.sendMessage(jid, {
998
+ const [code, meta] = await Promise.all([sock.groupInviteCode(groupJid), sock.groupMetadata(groupJid)]);
999
+ return _originalSendMessage(jid, {
824
1000
  groupInviteMessage: {
825
- groupJid,
826
- inviteCode: code,
1001
+ groupJid, inviteCode: code,
827
1002
  inviteExpiration: Math.floor(Date.now() / 1000) + 259200,
828
1003
  groupName: meta.subject,
829
1004
  caption: options.caption || "",
@@ -834,14 +1009,10 @@ const makeBusinessSocket = (config) => {
834
1009
 
835
1010
  const sendAdminInvite = async (jid, groupJid, options = {}) => {
836
1011
  if (!_isGrp(groupJid)) throw new Error("sendAdminInvite: groupJid harus @g.us");
837
- const [code, meta] = await Promise.all([
838
- sock.groupInviteCode(groupJid),
839
- sock.groupMetadata(groupJid)
840
- ]);
1012
+ const [code, meta] = await Promise.all([sock.groupInviteCode(groupJid), sock.groupMetadata(groupJid)]);
841
1013
  const msg = _gen(jid, {
842
1014
  groupInviteMessage: {
843
- groupJid,
844
- inviteCode: code,
1015
+ groupJid, inviteCode: code,
845
1016
  inviteExpiration: Math.floor(Date.now() / 1000) + 259200,
846
1017
  groupName: meta.subject,
847
1018
  caption: options.caption || `Kamu diundang jadi admin di ${meta.subject}`,
@@ -853,17 +1024,13 @@ const makeBusinessSocket = (config) => {
853
1024
  // ─────────────────────────────────────────────────────────────────────────
854
1025
  // CHAT MANAGEMENT
855
1026
  // ─────────────────────────────────────────────────────────────────────────
856
- const muteJid = async (jid, durationMs = 8 * 60 * 60 * 1000) =>
857
- sock.chatModify({ mute: durationMs }, jid);
858
-
859
- const unmuteJid = async (jid) =>
860
- sock.chatModify({ mute: null }, jid);
1027
+ const muteJid = async (jid, durationMs = 8 * 60 * 60 * 1000) => sock.chatModify({ mute: durationMs }, jid);
1028
+ const unmuteJid = async (jid) => sock.chatModify({ mute: null }, jid);
861
1029
 
862
1030
  const archiveChat = async (jid, lastMessage) => {
863
1031
  if (!lastMessage) throw new Error("archiveChat: lastMessage wajib");
864
1032
  return sock.chatModify({ archive: true, lastMessages: [lastMessage] }, jid);
865
1033
  };
866
-
867
1034
  const unarchiveChat = async (jid, lastMessage) => {
868
1035
  if (!lastMessage) throw new Error("unarchiveChat: lastMessage wajib");
869
1036
  return sock.chatModify({ archive: false, lastMessages: [lastMessage] }, jid);
@@ -871,13 +1038,8 @@ const makeBusinessSocket = (config) => {
871
1038
 
872
1039
  const pinChat = async (jid) => sock.chatModify({ pin: true }, jid);
873
1040
  const unpinChat = async (jid) => sock.chatModify({ pin: false }, jid);
874
-
875
- const markAsRead = async (keys) =>
876
- sock.readMessages(Array.isArray(keys) ? keys : [keys]);
877
-
878
- const sendSeen = async (jid, messages = []) =>
879
- sock.readMessages(messages.map(m => m.key || m));
880
-
1041
+ const markAsRead = async (keys) => sock.readMessages(Array.isArray(keys) ? keys : [keys]);
1042
+ const sendSeen = async (jid, messages = []) => sock.readMessages(messages.map(m => m.key || m));
881
1043
  const markAsUnread = async (jid, lastMessage) => {
882
1044
  if (!lastMessage) throw new Error("markAsUnread: lastMessage wajib");
883
1045
  return sock.chatModify({ markRead: false, lastMessages: [lastMessage] }, jid);
@@ -888,59 +1050,38 @@ const makeBusinessSocket = (config) => {
888
1050
 
889
1051
  const starMessage = async (jid, messageId, fromMe = false) =>
890
1052
  sock.chatModify({ star: { messages: [{ id: messageId, fromMe }], star: true } }, jid);
891
-
892
1053
  const unstarMessage = async (jid, messageId, fromMe = false) =>
893
1054
  sock.chatModify({ star: { messages: [{ id: messageId, fromMe }], star: false } }, jid);
894
1055
 
895
1056
  const deleteChat = async (jid, lastMessage) => {
896
1057
  if (!lastMessage) throw new Error("deleteChat: lastMessage wajib");
897
- return sock.chatModify({
898
- delete: true,
899
- lastMessages: [{ key: lastMessage.key, messageTimestamp: lastMessage.messageTimestamp }]
900
- }, jid);
1058
+ return sock.chatModify({ delete: true, lastMessages: [{ key: lastMessage.key, messageTimestamp: lastMessage.messageTimestamp }] }, jid);
901
1059
  };
902
1060
 
903
1061
  const clearChat = async (jid, messages = []) =>
904
- sock.chatModify({
905
- clear: {
906
- messages: messages.map(m => ({
907
- id: m.key.id,
908
- fromMe: m.key.fromMe,
909
- timestamp: m.messageTimestamp
910
- }))
911
- }
912
- }, jid);
1062
+ sock.chatModify({ clear: { messages: messages.map(m => ({ id: m.key.id, fromMe: m.key.fromMe, timestamp: m.messageTimestamp })) } }, jid);
913
1063
 
914
1064
  const sendLinkPreview = async (jid, text, options = {}) =>
915
- sock.sendMessage(jid, { text, detectLinks: true }, options);
1065
+ _originalSendMessage(jid, { text, detectLinks: true }, options);
916
1066
 
917
1067
  const sendDisappearingToggle = async (jid, enable = true) =>
918
- sock.sendMessage(jid, { disappearingMessagesInChat: enable ? 86400 : false });
1068
+ _originalSendMessage(jid, { disappearingMessagesInChat: enable ? 86400 : false });
919
1069
 
920
1070
  // ─────────────────────────────────────────────────────────────────────────
921
- // PROFILE
1071
+ // PROFILE — safe fetch, tidak crash 404 / not-authorized
922
1072
  // ─────────────────────────────────────────────────────────────────────────
923
1073
  const getProfilePicture = async (jid, highRes = false) => {
924
- try {
925
- return await sock.profilePictureUrl(_norm(jid), highRes ? "image" : "preview");
926
- } catch {
927
- return null;
928
- }
1074
+ try { return await sock.profilePictureUrl(_norm(jid), highRes ? "image" : "preview"); }
1075
+ catch { return null; }
929
1076
  };
930
1077
 
931
1078
  const getUserStatus = async (jid) => {
932
- try {
933
- return await sock.fetchStatus(_norm(jid));
934
- } catch {
935
- return null;
936
- }
1079
+ try { return await sock.fetchStatus(_norm(jid)); } catch { return null; }
937
1080
  };
938
1081
 
939
1082
  const getContactInfo = async (jid) => {
940
1083
  const [onWA, pic, status] = await Promise.allSettled([
941
- isOnWhatsApp(jid),
942
- getProfilePicture(jid, true),
943
- getUserStatus(jid)
1084
+ isOnWhatsApp(jid), getProfilePicture(jid, true), getUserStatus(jid)
944
1085
  ]);
945
1086
  return {
946
1087
  jid,
@@ -956,59 +1097,42 @@ const makeBusinessSocket = (config) => {
956
1097
  };
957
1098
 
958
1099
  const removeProfilePicture = async (jid) => sock.removeProfilePicture(jid);
959
-
960
- const updateProfileName = async (name) => {
961
- if (!name) throw new Error("updateProfileName: name wajib");
962
- return sock.updateProfileName(name);
963
- };
964
-
965
- const updateProfileStatus = async (status) => {
966
- if (typeof status !== "string") throw new Error("updateProfileStatus: harus string");
967
- return sock.updateProfileStatus(status);
968
- };
1100
+ const updateProfileName = async (name) => { if (!name) throw new Error("updateProfileName: name wajib"); return sock.updateProfileName(name); };
1101
+ const updateProfileStatus = async (status) => { if (typeof status !== "string") throw new Error("updateProfileStatus: harus string"); return sock.updateProfileStatus(status); };
969
1102
 
970
1103
  // ─────────────────────────────────────────────────────────────────────────
971
1104
  // DISAPPEARING
972
1105
  // ─────────────────────────────────────────────────────────────────────────
973
1106
  const sendDisappearingMessage = async (jid, content, expiration, options = {}) => {
974
1107
  const valid = [0, 86400, 604800, 7776000];
975
- if (!valid.includes(expiration))
976
- throw new Error(`sendDisappearingMessage: expiration harus salah satu dari: ${valid.join(", ")}`);
977
- return sock.sendMessage(jid, content, { ephemeralExpiration: expiration, ...options });
1108
+ if (!valid.includes(expiration)) throw new Error(`sendDisappearingMessage: expiration harus: ${valid.join(", ")}`);
1109
+ return _originalSendMessage(jid, content, { ephemeralExpiration: expiration, ...options });
978
1110
  };
979
1111
 
980
1112
  // ─────────────────────────────────────────────────────────────────────────
981
1113
  // MISC
982
1114
  // ─────────────────────────────────────────────────────────────────────────
1115
+ // isOnWhatsApp — selalu return object, tidak pernah undefined
983
1116
  const isOnWhatsApp = async (jidOrNumber) => {
984
1117
  let jid = jidOrNumber;
985
1118
  if (!jid.includes("@")) jid = jid.replace(/[^0-9]/g, "") + "@s.whatsapp.net";
986
1119
  try {
987
1120
  const result = await sock.onWhatsApp(jid);
988
1121
  return (Array.isArray(result) ? result[0] : result) || { exists: false, jid };
989
- } catch {
990
- return { exists: false, jid };
991
- }
1122
+ } catch { return { exists: false, jid }; }
992
1123
  };
993
1124
 
994
1125
  const rejectAllCalls = () => {
995
1126
  sock.ev.on("call", async (calls) => {
996
1127
  for (const call of calls) {
997
- try {
998
- await sock.rejectCall(call.id, call.from);
999
- } catch (e) {
1000
- sock.logger?.warn?.(`rejectAllCalls error: ${e?.message}`);
1001
- }
1128
+ try { await sock.rejectCall(call.id, call.from); }
1129
+ catch (e) { _log("warn", `rejectAllCalls error: ${e?.message}`); }
1002
1130
  }
1003
1131
  });
1004
1132
  };
1005
1133
 
1006
1134
  const getBusinessProfile = async (jid) => {
1007
- try {
1008
- return await sock.getBusinessProfile(_norm(jid));
1009
- } catch {
1010
- return null;
1011
- }
1135
+ try { return await sock.getBusinessProfile(_norm(jid)); } catch { return null; }
1012
1136
  };
1013
1137
 
1014
1138
  const fetchMessageHistory = async (jid, count = 25, oldestMsg) => {
@@ -1028,7 +1152,7 @@ const makeBusinessSocket = (config) => {
1028
1152
  const fetchAllGroups = async () => sock.groupFetchAllParticipating();
1029
1153
 
1030
1154
  // ─────────────────────────────────────────────────────────────────────────
1031
- // INTERACTIVE — Buttons / List / Template
1155
+ // INTERACTIVE — Legacy buttons / List / Template
1032
1156
  // ─────────────────────────────────────────────────────────────────────────
1033
1157
  const _mapButtons = (buttons) => buttons.map((b, i) => ({
1034
1158
  buttonId: b.buttonId || b.id || `btn_${i}`,
@@ -1039,14 +1163,7 @@ const makeBusinessSocket = (config) => {
1039
1163
  const sendButtonsMessage = async (jid, text, buttons = [], footer = "", options = {}) => {
1040
1164
  if (!buttons.length) throw new Error("sendButtonsMessage: min 1 tombol");
1041
1165
  if (buttons.length > 3) throw new Error("sendButtonsMessage: maks 3 tombol");
1042
- const msg = _gen(jid, {
1043
- buttonsMessage: {
1044
- contentText: text,
1045
- footerText: footer,
1046
- buttons: _mapButtons(buttons),
1047
- headerType: 1
1048
- }
1049
- });
1166
+ const msg = _gen(jid, { buttonsMessage: { contentText: text, footerText: footer, buttons: _mapButtons(buttons), headerType: 1 } });
1050
1167
  return _relay(jid, msg);
1051
1168
  };
1052
1169
 
@@ -1055,18 +1172,11 @@ const makeBusinessSocket = (config) => {
1055
1172
  if (!sections?.length) throw new Error("sendListMessage: sections wajib");
1056
1173
  const msg = _gen(jid, {
1057
1174
  listMessage: {
1058
- title: title || "",
1059
- description: text || "",
1060
- footerText: footer || "",
1061
- buttonText: buttonText || "Lihat",
1062
- listType: 1,
1175
+ title: title || "", description: text || "", footerText: footer || "",
1176
+ buttonText: buttonText || "Lihat", listType: 1,
1063
1177
  sections: sections.map(s => ({
1064
1178
  title: s.title || "",
1065
- rows: (s.rows || []).map(r => ({
1066
- rowId: r.rowId || r.id,
1067
- title: r.title,
1068
- description: r.description || "",
1069
- })),
1179
+ rows: (s.rows || []).map(r => ({ rowId: r.rowId || r.id, title: r.title, description: r.description || "" })),
1070
1180
  })),
1071
1181
  },
1072
1182
  });
@@ -1077,107 +1187,50 @@ const makeBusinessSocket = (config) => {
1077
1187
  const { text, footer, templateButtons = [] } = cfg;
1078
1188
  if (!templateButtons.length) throw new Error("sendTemplateMessage: templateButtons wajib");
1079
1189
  const hydratedButtons = templateButtons.map((b, i) => {
1080
- if (b.quickReply) return {
1081
- index: b.index ?? i,
1082
- quickReplyButton: { displayText: b.quickReply.displayText, id: b.quickReply.id }
1083
- };
1084
- if (b.urlButton) return {
1085
- index: b.index ?? i,
1086
- urlButton: { displayText: b.urlButton.displayText, url: b.urlButton.url }
1087
- };
1088
- if (b.callButton) return {
1089
- index: b.index ?? i,
1090
- callButton: { displayText: b.callButton.displayText, phoneNumber: b.callButton.phoneNumber }
1091
- };
1190
+ if (b.quickReply) return { index: b.index ?? i, quickReplyButton: { displayText: b.quickReply.displayText, id: b.quickReply.id } };
1191
+ if (b.urlButton) return { index: b.index ?? i, urlButton: { displayText: b.urlButton.displayText, url: b.urlButton.url } };
1192
+ if (b.callButton) return { index: b.index ?? i, callButton: { displayText: b.callButton.displayText, phoneNumber: b.callButton.phoneNumber } };
1092
1193
  return b;
1093
1194
  });
1094
- const msg = _gen(jid, {
1095
- templateMessage: {
1096
- hydratedTemplate: {
1097
- hydratedContentText: text || "",
1098
- hydratedFooterText: footer || "",
1099
- hydratedButtons
1100
- }
1101
- }
1102
- });
1195
+ const msg = _gen(jid, { templateMessage: { hydratedTemplate: { hydratedContentText: text || "", hydratedFooterText: footer || "", hydratedButtons } } });
1103
1196
  return _relay(jid, msg);
1104
1197
  };
1105
1198
 
1106
1199
  // ─────────────────────────────────────────────────────────────────────────
1107
- // INTERACTIVE MESSAGE (native flow / WABusiness buttons)
1108
- // Supports: single_select, cta_url, cta_call, cta_copy, quick_reply,
1109
- // address_message, send_location, payment, flow
1200
+ // INTERACTIVE MESSAGE — full native flow
1201
+ // single_select, cta_url, cta_call, quick_reply, cta_copy,
1202
+ // send_location, address_message, cta_reminder, payment, flow
1110
1203
  // ─────────────────────────────────────────────────────────────────────────
1111
1204
  const sendInteractiveMessage = async (jid, cfg = {}, options = {}) => {
1112
1205
  const { body, footer, header, buttons, sections, nativeFlow } = cfg;
1113
1206
 
1114
- // ── Header ──────────────────────────────────────────────────────────
1115
1207
  let headerContent = null;
1116
1208
  if (header) {
1117
- const typeMap = { image: "image", video: "video", document: "document" };
1118
- if (typeMap[header.type]) {
1209
+ if (header.type === "image" || header.type === "video" || header.type === "document") {
1119
1210
  const inner = await (0, Utils_1.generateWAMessageContent)(
1120
- {
1121
- [header.type]: header.content,
1122
- ...(header.type === "document" ? { fileName: header.fileName } : {})
1123
- },
1211
+ { [header.type]: header.content, ...(header.type === "document" ? { fileName: header.fileName } : {}) },
1124
1212
  { upload: waUploadToServer }
1125
1213
  );
1126
- const msgKey = `${header.type}Message`;
1127
- headerContent = {
1128
- [msgKey]: {
1129
- ...inner[msgKey],
1130
- ...(header.caption ? { caption: header.caption } : {})
1131
- }
1132
- };
1214
+ const k = `${header.type}Message`;
1215
+ headerContent = { [k]: { ...inner[k], ...(header.caption ? { caption: header.caption } : {}) } };
1133
1216
  } else if (header.type === "text") {
1134
- headerContent = {
1135
- ephemeralMessage: {
1136
- message: { extendedTextMessage: { text: header.content || "" } }
1137
- }
1138
- };
1217
+ headerContent = { ephemeralMessage: { message: { extendedTextMessage: { text: header.content || "" } } } };
1139
1218
  }
1140
1219
  }
1141
1220
 
1142
- // ── Action ──────────────────────────────────────────────────────────
1143
1221
  let action = null;
1144
-
1145
- // Modern native flow buttons (single_select, cta_url, etc.)
1146
1222
  if (buttons?.length) {
1147
- const normalizedBtns = _buildInteractiveButtons(buttons);
1148
- // Detect if these are native-flow style (have "name" field)
1149
- const isNativeStyle = normalizedBtns.every(b => b.name);
1150
- if (isNativeStyle) {
1151
- action = { nativeFlowMessage: { buttons: normalizedBtns } };
1152
- } else {
1153
- // Legacy plain buttons
1154
- action = {
1155
- buttons: normalizedBtns.map(b => ({
1156
- buttonId: b.id || b.buttonId,
1157
- buttonText: { displayText: b.displayText || b.buttonText?.displayText || "" },
1158
- type: 1
1159
- }))
1160
- };
1161
- }
1223
+ action = { nativeFlowMessage: { buttons: _buildInteractiveButtons(buttons) } };
1162
1224
  } else if (sections?.length) {
1163
1225
  action = {
1164
1226
  sections: sections.map(s => ({
1165
1227
  title: s.title,
1166
- rows: (s.rows || []).map(r => ({
1167
- rowId: r.id || r.rowId,
1168
- title: r.title,
1169
- description: r.description || ""
1170
- })),
1228
+ rows: (s.rows || []).map(r => ({ rowId: r.id || r.rowId, title: r.title, description: r.description || "" })),
1171
1229
  })),
1172
1230
  buttonText: cfg.listButtonText || "Pilih",
1173
1231
  };
1174
1232
  } else if (nativeFlow) {
1175
- action = {
1176
- nativeFlowMessage: {
1177
- name: nativeFlow.name,
1178
- paramsJson: _normalizeButtonParamsJson(nativeFlow.paramsJson || nativeFlow.params || {})
1179
- }
1180
- };
1233
+ action = { nativeFlowMessage: { name: nativeFlow.name, paramsJson: _normalizeButtonParamsJson(nativeFlow.paramsJson || nativeFlow.params || {}) } };
1181
1234
  }
1182
1235
 
1183
1236
  const msg = _gen(jid, {
@@ -1191,173 +1244,118 @@ const makeBusinessSocket = (config) => {
1191
1244
  return _relay(jid, msg);
1192
1245
  };
1193
1246
 
1247
+ const sendInteractiveWithMedia = async (jid, cfg = {}, options = {}) => {
1248
+ const { body, footer, buttons = [], image, video, document: doc, fileName } = cfg;
1249
+ if (!buttons.length) throw new Error("sendInteractiveWithMedia: buttons wajib");
1250
+ let headerContent = null;
1251
+ if (image) {
1252
+ const inner = await (0, Utils_1.generateWAMessageContent)({ image }, { upload: waUploadToServer });
1253
+ headerContent = { imageMessage: inner.imageMessage };
1254
+ } else if (video) {
1255
+ const inner = await (0, Utils_1.generateWAMessageContent)({ video }, { upload: waUploadToServer });
1256
+ headerContent = { videoMessage: inner.videoMessage };
1257
+ } else if (doc) {
1258
+ const inner = await (0, Utils_1.generateWAMessageContent)({ document: doc, fileName }, { upload: waUploadToServer });
1259
+ headerContent = { documentMessage: inner.documentMessage };
1260
+ }
1261
+ const msg = _gen(jid, {
1262
+ interactiveMessage: {
1263
+ body: { text: body || "" },
1264
+ footer: { text: footer || "" },
1265
+ ...(headerContent ? { header: headerContent } : {}),
1266
+ action: { nativeFlowMessage: { buttons: _buildInteractiveButtons(buttons) } },
1267
+ },
1268
+ });
1269
+ return _relay(jid, msg);
1270
+ };
1271
+
1194
1272
  const sendHighlyStructuredMessage = async (jid, cfg = {}) => {
1195
1273
  const { namespace, elementName, params = [] } = cfg;
1196
- if (!namespace || !elementName)
1197
- throw new Error("sendHighlyStructuredMessage: namespace dan elementName wajib");
1274
+ if (!namespace || !elementName) throw new Error("sendHighlyStructuredMessage: namespace dan elementName wajib");
1198
1275
  const msg = _gen(jid, {
1199
1276
  highlyStructuredMessage: {
1200
- namespace,
1201
- elementName,
1202
- params: params.map(p => ({ default: p })),
1203
- deterministicLottie: cfg.deterministicLottie || false,
1204
- fallbackLg: "id",
1205
- fallbackLc: "ID",
1277
+ namespace, elementName, params: params.map(p => ({ default: p })),
1278
+ deterministicLottie: cfg.deterministicLottie || false, fallbackLg: "id", fallbackLc: "ID",
1206
1279
  },
1207
1280
  });
1208
1281
  return _relay(jid, msg);
1209
1282
  };
1210
1283
 
1284
+ // ─── Media + Legacy Buttons ───────────────────────────────────────────────
1285
+ const sendImageWithButtons = async (jid, image, caption, buttons = [], footer = "", options = {}) => {
1286
+ if (!image) throw new Error("sendImageWithButtons: image wajib");
1287
+ if (!buttons.length) throw new Error("sendImageWithButtons: buttons wajib");
1288
+ const inner = await (0, Utils_1.generateWAMessageContent)({ image }, { upload: waUploadToServer });
1289
+ return _relay(jid, _gen(jid, { buttonsMessage: { imageMessage: inner.imageMessage, contentText: caption || "", footerText: footer, buttons: _mapButtons(buttons), headerType: 4 } }));
1290
+ };
1291
+
1292
+ const sendVideoWithButtons = async (jid, video, caption, buttons = [], footer = "", options = {}) => {
1293
+ if (!video) throw new Error("sendVideoWithButtons: video wajib");
1294
+ if (!buttons.length) throw new Error("sendVideoWithButtons: buttons wajib");
1295
+ const inner = await (0, Utils_1.generateWAMessageContent)({ video }, { upload: waUploadToServer });
1296
+ return _relay(jid, _gen(jid, { buttonsMessage: { videoMessage: inner.videoMessage, contentText: caption || "", footerText: footer, buttons: _mapButtons(buttons), headerType: 5 } }));
1297
+ };
1298
+
1299
+ const sendDocumentWithButtons = async (jid, document, fileName, caption, buttons = [], footer = "", options = {}) => {
1300
+ if (!document) throw new Error("sendDocumentWithButtons: document wajib");
1301
+ if (!buttons.length) throw new Error("sendDocumentWithButtons: buttons wajib");
1302
+ const inner = await (0, Utils_1.generateWAMessageContent)({ document, fileName }, { upload: waUploadToServer });
1303
+ return _relay(jid, _gen(jid, { buttonsMessage: { documentMessage: inner.documentMessage, contentText: caption || "", footerText: footer, buttons: _mapButtons(buttons), headerType: 6 } }));
1304
+ };
1305
+
1211
1306
  // ─────────────────────────────────────────────────────────────────────────
1212
- // PRODUCT MESSAGE (with full button support)
1213
- // Supports sending productMessage with native flow buttons:
1214
- // single_select, cta_url, cta_call, quick_reply, etc.
1307
+ // PRODUCT MESSAGE full button support (single_select, cta_url, dll)
1215
1308
  // ─────────────────────────────────────────────────────────────────────────
1216
-
1217
- /**
1218
- * sendProductMessageWithButtons — kirim product message lengkap dengan buttons
1219
- *
1220
- * @param {string} jid
1221
- * @param {object} cfg
1222
- * cfg.title string — judul produk
1223
- * cfg.body string — isi teks pesan (mirip description / body)
1224
- * cfg.footer string — teks footer
1225
- * cfg.thumbnail Buffer|{url:string} — thumbnail gambar
1226
- * cfg.productId string
1227
- * cfg.retailerId string
1228
- * cfg.businessOwnerJid string (opsional, default: bot jid)
1229
- * cfg.buttons Array — array button native flow
1230
- * format tiap button:
1231
- * { name: "single_select", buttonParamsJson: JSON.stringify({...}) }
1232
- * { name: "cta_url", buttonParamsJson: JSON.stringify({display_text, url, merchant_url}) }
1233
- * { name: "cta_call", buttonParamsJson: JSON.stringify({display_text, phone_number}) }
1234
- * { name: "quick_reply", buttonParamsJson: JSON.stringify({display_text, id}) }
1235
- * { name: "cta_copy", buttonParamsJson: JSON.stringify({display_text, copy_code}) }
1236
- * { name: "cta_reminder", buttonParamsJson: JSON.stringify({display_text}) }
1237
- * { name: "address_message", buttonParamsJson: JSON.stringify({display_text}) }
1238
- * { name: "send_location", buttonParamsJson: "{}" }
1239
- * cfg.header object — { type: "image"|"video"|"text", content, caption }
1240
- * @param {object} options — sendMessage options (quoted, etc.)
1241
- */
1242
1309
  const sendProductMessageWithButtons = async (jid, cfg = {}, options = {}) => {
1243
- const {
1244
- title,
1245
- body,
1246
- footer,
1247
- thumbnail,
1248
- productId,
1249
- retailerId,
1250
- businessOwnerJid,
1251
- buttons = [],
1252
- header,
1253
- } = cfg;
1254
-
1310
+ const { title, body, footer, thumbnail, productId, retailerId, buttons = [], header } = cfg;
1255
1311
  if (!buttons.length) throw new Error("sendProductMessageWithButtons: min 1 button wajib");
1256
1312
 
1257
- // Build thumbnail upload if needed
1258
- let thumbnailUrl = null;
1259
- let thumbnailBuffer = null;
1313
+ let headerContent = null;
1260
1314
  if (thumbnail) {
1261
1315
  if (typeof thumbnail === "object" && thumbnail.url) {
1262
- thumbnailUrl = thumbnail.url;
1263
- } else {
1264
- thumbnailBuffer = thumbnail;
1316
+ // URL thumbnail — digunakan via contextInfo, tidak diupload
1317
+ } else if (Buffer.isBuffer(thumbnail)) {
1318
+ const inner = await (0, Utils_1.generateWAMessageContent)({ image: thumbnail }, { upload: waUploadToServer });
1319
+ headerContent = { imageMessage: inner.imageMessage };
1265
1320
  }
1266
1321
  }
1267
-
1268
- // Build header content if provided
1269
- let headerContent = null;
1270
- if (header) {
1271
- if (header.type === "image" || header.type === "video") {
1272
- const inner = await (0, Utils_1.generateWAMessageContent)(
1273
- { [header.type]: header.content },
1274
- { upload: waUploadToServer }
1275
- );
1276
- const k = `${header.type}Message`;
1277
- headerContent = { [k]: { ...inner[k], ...(header.caption ? { caption: header.caption } : {}) } };
1278
- } else if (header.type === "text") {
1279
- headerContent = {
1280
- ephemeralMessage: {
1281
- message: { extendedTextMessage: { text: header.content || "" } }
1282
- }
1283
- };
1322
+ if (header && !headerContent) {
1323
+ if (header.type === "image") {
1324
+ const inner = await (0, Utils_1.generateWAMessageContent)({ image: header.content }, { upload: waUploadToServer });
1325
+ headerContent = { imageMessage: { ...inner.imageMessage, ...(header.caption ? { caption: header.caption } : {}) } };
1326
+ } else if (header.type === "video") {
1327
+ const inner = await (0, Utils_1.generateWAMessageContent)({ video: header.content }, { upload: waUploadToServer });
1328
+ headerContent = { videoMessage: { ...inner.videoMessage, ...(header.caption ? { caption: header.caption } : {}) } };
1284
1329
  }
1285
1330
  }
1286
1331
 
1287
- // Build normalized buttons
1288
- const normalizedBtns = _buildInteractiveButtons(buttons);
1289
-
1290
- const productInfo = {
1291
- productId: productId || "",
1292
- title: title || "",
1293
- description: body || "",
1294
- retailerId: retailerId || "",
1295
- ...(thumbnailUrl ? { productImageCount: 1 } : {}),
1296
- };
1297
-
1298
1332
  const msg = _gen(jid, {
1299
1333
  interactiveMessage: {
1300
1334
  body: { text: body || "" },
1301
1335
  footer: { text: footer || "" },
1302
1336
  ...(headerContent ? { header: headerContent } : {}),
1303
- ...(thumbnailUrl || thumbnailBuffer ? {
1304
- header: headerContent || {
1305
- imageMessage: thumbnailBuffer
1306
- ? {
1307
- jpegThumbnail: thumbnailBuffer,
1308
- url: "",
1309
- directPath: "",
1310
- mediaKey: Buffer.alloc(0),
1311
- }
1312
- : { url: thumbnailUrl }
1313
- }
1314
- } : {}),
1315
1337
  contextInfo: {
1316
1338
  externalAdReply: {
1317
1339
  title: title || "",
1318
1340
  body: body || "",
1319
1341
  mediaType: 1,
1320
- ...(thumbnailUrl ? { thumbnailUrl } : {}),
1321
1342
  renderLargerThumbnail: true,
1322
1343
  showAdAttribution: false,
1344
+ ...(thumbnail?.url ? { thumbnailUrl: thumbnail.url } : {}),
1323
1345
  }
1324
1346
  },
1325
- action: {
1326
- nativeFlowMessage: {
1327
- buttons: normalizedBtns,
1328
- }
1329
- },
1347
+ action: { nativeFlowMessage: { buttons: _buildInteractiveButtons(buttons) } },
1330
1348
  },
1331
1349
  });
1332
-
1333
1350
  return _relay(jid, msg);
1334
1351
  };
1335
1352
 
1336
- /**
1337
- * sendProductMessage — kompatibel dengan format lama DAN format baru.
1338
- *
1339
- * Format baru (recommended):
1340
- * sendMessage(jid, {
1341
- * productMessage: {
1342
- * title, description, thumbnail: {url}, productId, retailerId,
1343
- * body, footer,
1344
- * buttons: [
1345
- * { name: "single_select", buttonParamsJson: JSON.stringify({...}) },
1346
- * { name: "cta_url", buttonParamsJson: JSON.stringify({...}) }
1347
- * ]
1348
- * }
1349
- * }, { quoted: msg })
1350
- *
1351
- * Format lama (catalog lookup):
1352
- * sendProductMessage(jid, productId, catalogJid, options)
1353
- */
1354
1353
  const sendProductMessage = async (jid, productIdOrCfg, catalogJidOrOptions, options = {}) => {
1355
- // Detect new format: first arg after jid is a config object with buttons
1354
+ // New format: object config with buttons
1356
1355
  if (typeof productIdOrCfg === "object" && productIdOrCfg !== null) {
1357
1356
  return sendProductMessageWithButtons(jid, productIdOrCfg, catalogJidOrOptions || {});
1358
1357
  }
1359
-
1360
- // Legacy: catalog lookup
1358
+ // Legacy format: catalog lookup
1361
1359
  const productId = productIdOrCfg;
1362
1360
  const catalogJid = typeof catalogJidOrOptions === "string" ? catalogJidOrOptions : null;
1363
1361
  const bizJid = _norm(catalogJid || _me());
@@ -1367,182 +1365,51 @@ const makeBusinessSocket = (config) => {
1367
1365
  const msg = _gen(jid, {
1368
1366
  productMessage: {
1369
1367
  product: {
1370
- productId: product.id,
1371
- title: product.title,
1372
- description: product.description || "",
1373
- currencyCode: product.currency,
1374
- priceAmount1000: product.price,
1375
- retailerId: product.retailerId || "",
1376
- url: product.url || "",
1377
- productImageCount: product.images?.length || 0,
1378
- firstImageId: product.images?.[0]?.id || "",
1368
+ productId: product.id, title: product.title, description: product.description || "",
1369
+ currencyCode: product.currency, priceAmount1000: product.price,
1370
+ retailerId: product.retailerId || "", url: product.url || "",
1371
+ productImageCount: product.images?.length || 0, firstImageId: product.images?.[0]?.id || "",
1379
1372
  },
1380
- businessOwnerJid: bizJid,
1381
- catalog: { catalogJid: bizJid },
1373
+ businessOwnerJid: bizJid, catalog: { catalogJid: bizJid },
1382
1374
  },
1383
1375
  });
1384
1376
  return _relay(jid, msg);
1385
1377
  };
1386
1378
 
1387
1379
  // ─────────────────────────────────────────────────────────────────────────
1388
- // PATCH: sock.sendMessage override to intercept productMessage with buttons
1389
- // Agar bisa pakai: sock.sendMessage(jid, { productMessage: {..., buttons:[...]} })
1390
- // ─────────────────────────────────────────────────────────────────────────
1391
- const _originalSendMessage = sock.sendMessage.bind(sock);
1392
- const patchedSendMessage = async (jid, content, options = {}) => {
1393
- // Intercept productMessage with buttons array
1394
- if (content?.productMessage && Array.isArray(content.productMessage.buttons)) {
1395
- const pm = content.productMessage;
1396
- return sendProductMessageWithButtons(jid, {
1397
- title: pm.title || "",
1398
- body: pm.body || pm.description || "",
1399
- footer: pm.footer || "",
1400
- thumbnail: pm.thumbnail || null,
1401
- productId: pm.productId || "",
1402
- retailerId: pm.retailerId || "",
1403
- buttons: pm.buttons,
1404
- header: pm.header || null,
1405
- }, options);
1406
- }
1407
- return _originalSendMessage(jid, content, options);
1408
- };
1409
-
1410
- // ─── Media + Buttons ──────────────────────────────────────────────────────
1411
- const sendImageWithButtons = async (jid, image, caption, buttons = [], footer = "", options = {}) => {
1412
- if (!image) throw new Error("sendImageWithButtons: image wajib");
1413
- if (!buttons.length) throw new Error("sendImageWithButtons: buttons wajib");
1414
- const inner = await (0, Utils_1.generateWAMessageContent)({ image }, { upload: waUploadToServer });
1415
- return _relay(jid, _gen(jid, {
1416
- buttonsMessage: {
1417
- imageMessage: inner.imageMessage,
1418
- contentText: caption || "",
1419
- footerText: footer,
1420
- buttons: _mapButtons(buttons),
1421
- headerType: 4
1422
- }
1423
- }));
1424
- };
1425
-
1426
- const sendVideoWithButtons = async (jid, video, caption, buttons = [], footer = "", options = {}) => {
1427
- if (!video) throw new Error("sendVideoWithButtons: video wajib");
1428
- if (!buttons.length) throw new Error("sendVideoWithButtons: buttons wajib");
1429
- const inner = await (0, Utils_1.generateWAMessageContent)({ video }, { upload: waUploadToServer });
1430
- return _relay(jid, _gen(jid, {
1431
- buttonsMessage: {
1432
- videoMessage: inner.videoMessage,
1433
- contentText: caption || "",
1434
- footerText: footer,
1435
- buttons: _mapButtons(buttons),
1436
- headerType: 5
1437
- }
1438
- }));
1439
- };
1440
-
1441
- const sendDocumentWithButtons = async (jid, document, fileName, caption, buttons = [], footer = "", options = {}) => {
1442
- if (!document) throw new Error("sendDocumentWithButtons: document wajib");
1443
- if (!buttons.length) throw new Error("sendDocumentWithButtons: buttons wajib");
1444
- const inner = await (0, Utils_1.generateWAMessageContent)({ document, fileName }, { upload: waUploadToServer });
1445
- return _relay(jid, _gen(jid, {
1446
- buttonsMessage: {
1447
- documentMessage: inner.documentMessage,
1448
- contentText: caption || "",
1449
- footerText: footer,
1450
- buttons: _mapButtons(buttons),
1451
- headerType: 6
1452
- }
1453
- }));
1454
- };
1455
-
1456
- // ─────────────────────────────────────────────────────────────────────────
1457
- // INTERACTIVE with media header + native flow buttons (full featured)
1458
- // Usage example:
1459
- // sendInteractiveWithMedia(jid, {
1460
- // image: { url: "..." }, // or video, document
1461
- // body: "Teks pesan",
1462
- // footer: "Footer",
1463
- // buttons: [
1464
- // { name: "single_select", buttonParamsJson: JSON.stringify({title: "Pilih", sections:[...]}) },
1465
- // { name: "cta_url", buttonParamsJson: JSON.stringify({display_text:"Web", url:"https://..."}) }
1466
- // ]
1467
- // })
1380
+ // NEWSLETTER
1468
1381
  // ─────────────────────────────────────────────────────────────────────────
1469
- const sendInteractiveWithMedia = async (jid, cfg = {}, options = {}) => {
1470
- const { body, footer, buttons = [], image, video, document: doc, fileName } = cfg;
1471
- if (!buttons.length) throw new Error("sendInteractiveWithMedia: buttons wajib");
1472
-
1473
- let headerContent = null;
1474
- if (image) {
1475
- const inner = await (0, Utils_1.generateWAMessageContent)({ image }, { upload: waUploadToServer });
1476
- headerContent = { imageMessage: inner.imageMessage };
1477
- } else if (video) {
1478
- const inner = await (0, Utils_1.generateWAMessageContent)({ video }, { upload: waUploadToServer });
1479
- headerContent = { videoMessage: inner.videoMessage };
1480
- } else if (doc) {
1481
- const inner = await (0, Utils_1.generateWAMessageContent)({ document: doc, fileName }, { upload: waUploadToServer });
1482
- headerContent = { documentMessage: inner.documentMessage };
1483
- }
1484
-
1485
- const normalizedBtns = _buildInteractiveButtons(buttons);
1486
-
1487
- const msg = _gen(jid, {
1488
- interactiveMessage: {
1489
- body: { text: body || "" },
1490
- footer: { text: footer || "" },
1491
- ...(headerContent ? { header: headerContent } : {}),
1492
- action: {
1493
- nativeFlowMessage: { buttons: normalizedBtns }
1494
- },
1495
- },
1496
- });
1497
- return _relay(jid, msg);
1498
- };
1499
-
1500
- // ─── Newsletter ───────────────────────────────────────────────────────────
1501
1382
  const sendNewsletterMessage = async (newsletterJid, content, options = {}) => {
1502
- if (!newsletterJid.endsWith("@newsletter"))
1503
- throw new Error("sendNewsletterMessage: harus @newsletter JID");
1383
+ if (!newsletterJid.endsWith("@newsletter")) throw new Error("sendNewsletterMessage: harus @newsletter JID");
1504
1384
  return _originalSendMessage(newsletterJid, content, options);
1505
1385
  };
1506
1386
 
1507
1387
  const sendNewsletterReaction = async (newsletterJid, messageId, emoji) => {
1508
- if (!newsletterJid.endsWith("@newsletter"))
1509
- throw new Error("sendNewsletterReaction: harus @newsletter JID");
1388
+ if (!newsletterJid.endsWith("@newsletter")) throw new Error("sendNewsletterReaction: harus @newsletter JID");
1510
1389
  return query({
1511
- tag: "iq",
1512
- attrs: { to: newsletterJid, type: "set", xmlns: "w:newsletter" },
1513
- content: [{
1514
- tag: "reaction",
1515
- attrs: { "message_id": messageId },
1516
- content: [{ tag: "text", attrs: {}, content: emoji }]
1517
- }]
1390
+ tag: "iq", attrs: { to: newsletterJid, type: "set", xmlns: "w:newsletter" },
1391
+ content: [{ tag: "reaction", attrs: { "message_id": messageId }, content: [{ tag: "text", attrs: {}, content: emoji }] }]
1518
1392
  });
1519
1393
  };
1520
1394
 
1521
1395
  const getNewsletterInfo = async (newsletterJid) => {
1522
- if (!newsletterJid.endsWith("@newsletter"))
1523
- throw new Error("getNewsletterInfo: harus @newsletter JID");
1524
- return query({
1525
- tag: "iq",
1526
- attrs: { to: newsletterJid, type: "get", xmlns: "w:newsletter" },
1527
- content: [{ tag: "metadata", attrs: {} }]
1528
- });
1396
+ try {
1397
+ if (!newsletterJid.endsWith("@newsletter")) throw new Error("getNewsletterInfo: harus @newsletter JID");
1398
+ return await query({ tag: "iq", attrs: { to: newsletterJid, type: "get", xmlns: "w:newsletter" }, content: [{ tag: "metadata", attrs: {} }] });
1399
+ } catch { return null; }
1529
1400
  };
1530
1401
 
1531
1402
  const followNewsletter = async (newsletterJid) => {
1532
- const jid = newsletterJid.endsWith("@newsletter") ? newsletterJid : null;
1533
- if (!jid) throw new Error("followNewsletter: harus @newsletter JID");
1534
- return sock.newsletterFollow(jid);
1403
+ if (!newsletterJid.endsWith("@newsletter")) throw new Error("followNewsletter: harus @newsletter JID");
1404
+ return sock.newsletterFollow(newsletterJid);
1535
1405
  };
1536
1406
 
1537
1407
  const unfollowNewsletter = async (newsletterJid) => {
1538
- const jid = newsletterJid.endsWith("@newsletter") ? newsletterJid : null;
1539
- if (!jid) throw new Error("unfollowNewsletter: harus @newsletter JID");
1540
- return sock.newsletterUnfollow(jid);
1408
+ if (!newsletterJid.endsWith("@newsletter")) throw new Error("unfollowNewsletter: harus @newsletter JID");
1409
+ return sock.newsletterUnfollow(newsletterJid);
1541
1410
  };
1542
1411
 
1543
- const getNewsletterMetadata = async (type, key) => {
1544
- return sock.newsletterMetadata(type, key).catch(() => null);
1545
- };
1412
+ const getNewsletterMetadata = async (type, key) => sock.newsletterMetadata(type, key).catch(() => null);
1546
1413
 
1547
1414
  const joinNewsletterByUrl = async (channelUrl) => {
1548
1415
  const inviteCode = _extractInviteCode(channelUrl);
@@ -1553,16 +1420,41 @@ const makeBusinessSocket = (config) => {
1553
1420
  };
1554
1421
 
1555
1422
  const sendLocationReply = async (jid, latitude, longitude, name, quotedMessage, options = {}) => {
1556
- if (typeof latitude !== "number" || typeof longitude !== "number")
1557
- throw new Error("sendLocationReply: lat/lng harus number");
1423
+ if (typeof latitude !== "number" || typeof longitude !== "number") throw new Error("sendLocationReply: lat/lng harus number");
1558
1424
  if (!quotedMessage) throw new Error("sendLocationReply: quotedMessage wajib");
1559
- return _originalSendMessage(jid, {
1560
- location: {
1561
- degreesLatitude: latitude,
1562
- degreesLongitude: longitude,
1563
- ...(name ? { name } : {})
1564
- }
1565
- }, { quoted: quotedMessage, ...options });
1425
+ return _originalSendMessage(jid, { location: { degreesLatitude: latitude, degreesLongitude: longitude, ...(name ? { name } : {}) } }, { quoted: quotedMessage, ...options });
1426
+ };
1427
+
1428
+ // ─────────────────────────────────────────────────────────────────────────
1429
+ // PATCHED sendMessage
1430
+ // 1. tagAll system — { text: "...", tagAll: true, tagAllScope: "admins" }
1431
+ // 2. productMessage + buttons interception
1432
+ // ─────────────────────────────────────────────────────────────────────────
1433
+ const patchedSendMessage = async (jid, content, options = {}) => {
1434
+ // tagAll injection
1435
+ if (content?.tagAll && _isGrp(jid)) {
1436
+ try {
1437
+ const scope = content.tagAllScope || "all";
1438
+ const mentions = await groupTagAll(jid, scope);
1439
+ const { tagAll: _ta, tagAllScope: _ts, ...rest } = content;
1440
+ return _originalSendMessage(jid, { ...rest, mentions: [...new Set([...(rest.mentions || []), ...mentions])] }, options);
1441
+ } catch (e) { _log("warn", `tagAll patch error: ${e?.message}`); }
1442
+ }
1443
+ // productMessage + buttons interception
1444
+ if (content?.productMessage && Array.isArray(content.productMessage.buttons)) {
1445
+ const pm = content.productMessage;
1446
+ return sendProductMessageWithButtons(jid, {
1447
+ title: pm.title || "",
1448
+ body: pm.body || pm.description || "",
1449
+ footer: pm.footer || "",
1450
+ thumbnail: pm.thumbnail || null,
1451
+ productId: pm.productId || "",
1452
+ retailerId: pm.retailerId || "",
1453
+ buttons: pm.buttons,
1454
+ header: pm.header || null,
1455
+ }, options);
1456
+ }
1457
+ return _originalSendMessage(jid, content, options);
1566
1458
  };
1567
1459
 
1568
1460
  // ─────────────────────────────────────────────────────────────────────────
@@ -1571,167 +1463,92 @@ const makeBusinessSocket = (config) => {
1571
1463
  return {
1572
1464
  ...sock,
1573
1465
 
1574
- // Override sendMessage to support productMessage+buttons transparently
1466
+ // Patched sendMessage tagAll + productMessage+buttons transparent
1575
1467
  sendMessage: patchedSendMessage,
1576
1468
 
1577
1469
  logger: config.logger,
1578
1470
 
1579
- // Catalog
1580
- getCatalog,
1581
- getCollections,
1582
- getOrderDetails,
1583
- productCreate,
1584
- productDelete,
1585
- productUpdate,
1586
-
1587
- // Group
1588
- groupTagAll,
1589
- groupStatusV2,
1590
- getGroupAdmins,
1591
- isGroupAdmin,
1592
- sendToAdminsOnly,
1593
- bulkGroupAction,
1594
- setGroupDisappearing,
1595
- sendTagAll,
1596
- sendMentionAll,
1597
- sendGroupInvite,
1598
- sendAdminInvite,
1599
- updateGroupName,
1600
- updateGroupDescription,
1601
- updateGroupSetting,
1602
- revokeGroupInvite,
1603
- getGroupInviteLink,
1604
- joinGroupViaLink,
1605
- leaveGroup,
1606
- getGroupParticipants,
1607
- setGroupJoinApproval,
1608
- getGroupJoinRequests,
1609
- approveGroupJoinRequest,
1610
- rejectGroupJoinRequest,
1611
- setGroupMemberAddMode,
1612
- updateGroupProfilePicture,
1613
-
1614
- // Status
1471
+ // ── Catalog ───────────────────────────────────────────────────────────
1472
+ getCatalog, getCollections, getOrderDetails,
1473
+ productCreate, productDelete, productUpdate,
1474
+
1475
+ // ── NEW: Channel / Group Tracker & Contacts ───────────────────────────
1476
+ getFollowedChannels,
1477
+ getJoinedGroups,
1478
+ getAllContacts,
1479
+
1480
+ // ── NEW: Media Converter ──────────────────────────────────────────────
1481
+ convertMedia, convertToSticker,
1482
+
1483
+ // ── Group ─────────────────────────────────────────────────────────────
1484
+ groupTagAll, groupStatusV2, getGroupAdmins, isGroupAdmin,
1485
+ sendToAdminsOnly, bulkGroupAction, setGroupDisappearing,
1486
+ sendTagAll, sendMentionAll, sendGroupInvite, sendAdminInvite,
1487
+ updateGroupName, updateGroupDescription, updateGroupSetting,
1488
+ revokeGroupInvite, getGroupInviteLink, joinGroupViaLink, leaveGroup,
1489
+ getGroupParticipants, setGroupJoinApproval, getGroupJoinRequests,
1490
+ approveGroupJoinRequest, rejectGroupJoinRequest,
1491
+ setGroupMemberAddMode, updateGroupProfilePicture,
1492
+
1493
+ // ── Status ────────────────────────────────────────────────────────────
1615
1494
  sendStatus,
1616
1495
 
1617
- // Media
1618
- sendImage,
1619
- sendVideo,
1620
- sendAudio,
1621
- sendAudioPTT,
1622
- sendVoiceNote,
1623
- sendDocument,
1624
- sendGIF,
1625
- sendPTV,
1626
- sendViewOnce,
1627
- sendAlbum,
1628
- sendLocation,
1629
- sendLocationReply,
1630
- sendLiveLocation,
1631
- sendContact,
1632
- sendPoll,
1633
- sendEvent,
1634
- sendScheduledCall,
1635
- sendLinkPreview,
1636
- sendDisappearingToggle,
1637
-
1638
- // Sticker
1639
- sendStickerFromUrl,
1640
- sendStickerFromBuffer,
1641
- sendStickerWithMetadata,
1642
- sendStickerPack,
1643
- sendStickerMessage,
1644
-
1645
- // Interactive (full support)
1646
- sendButtonsMessage,
1647
- sendListMessage,
1648
- sendTemplateMessage,
1649
- sendInteractiveMessage,
1650
- sendInteractiveWithMedia,
1496
+ // ── Media ─────────────────────────────────────────────────────────────
1497
+ sendImage, sendVideo, sendAudio, sendAudioPTT, sendVoiceNote,
1498
+ sendDocument, sendDocumentPack,
1499
+ sendGIF, sendPTV, sendViewOnce, sendAlbum,
1500
+ sendLocation, sendLocationReply, sendLiveLocation,
1501
+ sendContact, sendPoll, sendEvent, sendScheduledCall,
1502
+ sendLinkPreview, sendDisappearingToggle,
1503
+
1504
+ // ── Sticker ───────────────────────────────────────────────────────────
1505
+ sendStickerFromUrl, sendStickerFromBuffer, sendStickerWithMetadata,
1506
+ sendStickerPack, sendStickerPackAlbum, sendStickerMessage,
1507
+
1508
+ // ── Interactive (full native flow support) ────────────────────────────
1509
+ sendButtonsMessage, sendListMessage, sendTemplateMessage,
1510
+ sendInteractiveMessage, sendInteractiveWithMedia,
1651
1511
  sendHighlyStructuredMessage,
1652
- sendProductMessage,
1653
- sendProductMessageWithButtons,
1654
- sendNewsletterMessage,
1655
- sendNewsletterReaction,
1656
- getNewsletterInfo,
1657
- followNewsletter,
1658
- unfollowNewsletter,
1659
- getNewsletterMetadata,
1660
- joinNewsletterByUrl,
1661
- sendImageWithButtons,
1662
- sendVideoWithButtons,
1663
- sendDocumentWithButtons,
1664
-
1665
- // Reply / quote
1666
- sendReply,
1667
- sendMediaReply,
1668
- sendQuotedText,
1669
- sendWithQuotedFake,
1670
- sendWithMentionAndReply,
1671
- forwardWithComment,
1672
-
1673
- // Mentions / typing
1674
- sendTextWithMentions,
1675
- sendTyping,
1676
- sendWithTyping,
1677
-
1678
- // Broadcast
1679
- broadcastMessage,
1680
- broadcastToGroups,
1681
- sendMultipleMessages,
1682
-
1683
- // Message actions
1684
- pinMessage,
1685
- keepMessage,
1686
- editMessage,
1687
- deleteMessage,
1688
- reactMessage,
1689
- forwardMessage,
1690
-
1691
- // Chat management
1692
- muteJid,
1693
- unmuteJid,
1694
- archiveChat,
1695
- unarchiveChat,
1696
- pinChat,
1697
- unpinChat,
1698
- markAsRead,
1699
- markAsUnread,
1700
- blockUser,
1701
- unblockUser,
1702
- starMessage,
1703
- unstarMessage,
1704
- deleteChat,
1705
- clearChat,
1706
- sendSeen,
1707
-
1708
- // Profile
1709
- getProfilePicture,
1710
- getUserStatus,
1711
- updateProfilePicture,
1712
- removeProfilePicture,
1713
- updateProfileName,
1714
- updateProfileStatus,
1715
- getContactInfo,
1716
- getBusinessProfile,
1717
- fetchBlocklist,
1718
- fetchAllGroups,
1719
- fetchMessageHistory,
1720
-
1721
- // Privacy
1722
- updatePrivacyLastSeen,
1723
- updatePrivacyProfilePic,
1724
- updatePrivacyStatus,
1725
- updatePrivacyReadReceipts,
1726
- updatePrivacyGroupsAdd,
1727
- updatePrivacyOnline,
1512
+ sendProductMessage, sendProductMessageWithButtons,
1513
+ sendNewsletterMessage, sendNewsletterReaction, getNewsletterInfo,
1514
+ followNewsletter, unfollowNewsletter, getNewsletterMetadata, joinNewsletterByUrl,
1515
+ sendImageWithButtons, sendVideoWithButtons, sendDocumentWithButtons,
1516
+
1517
+ // ── Reply / Quote ─────────────────────────────────────────────────────
1518
+ sendReply, sendMediaReply, sendQuotedText,
1519
+ sendWithQuotedFake, sendWithMentionAndReply, forwardWithComment,
1520
+
1521
+ // ── Mentions / Typing ─────────────────────────────────────────────────
1522
+ sendTextWithMentions, sendTyping, sendWithTyping,
1523
+
1524
+ // ── Broadcast ─────────────────────────────────────────────────────────
1525
+ broadcastMessage, broadcastToGroups, sendMultipleMessages,
1526
+
1527
+ // ── Message Actions ───────────────────────────────────────────────────
1528
+ pinMessage, keepMessage, editMessage, deleteMessage,
1529
+ reactMessage, forwardMessage,
1530
+
1531
+ // ── Chat Management ───────────────────────────────────────────────────
1532
+ muteJid, unmuteJid, archiveChat, unarchiveChat,
1533
+ pinChat, unpinChat, markAsRead, markAsUnread,
1534
+ blockUser, unblockUser, starMessage, unstarMessage,
1535
+ deleteChat, clearChat, sendSeen,
1536
+
1537
+ // ── Profile (safe fetch) ──────────────────────────────────────────────
1538
+ getProfilePicture, getUserStatus,
1539
+ updateProfilePicture, removeProfilePicture,
1540
+ updateProfileName, updateProfileStatus,
1541
+ getContactInfo, getBusinessProfile,
1542
+ fetchBlocklist, fetchAllGroups, fetchMessageHistory,
1543
+
1544
+ // ── Privacy ───────────────────────────────────────────────────────────
1545
+ updatePrivacyLastSeen, updatePrivacyProfilePic, updatePrivacyStatus,
1546
+ updatePrivacyReadReceipts, updatePrivacyGroupsAdd, updatePrivacyOnline,
1728
1547
  setDefaultDisappearing,
1729
1548
 
1730
- // Misc
1731
- sendDisappearingMessage,
1732
- isOnWhatsApp,
1733
- presenceSubscribe,
1734
- rejectAllCalls,
1549
+ // ── Misc ──────────────────────────────────────────────────────────────
1550
+ sendDisappearingMessage, isOnWhatsApp,
1551
+ presenceSubscribe, rejectAllCalls,
1735
1552
  };
1736
1553
  };
1737
1554