n4lyx 3.0.7 → 3.0.9

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