n4lyx 3.0.5 → 3.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/Socket/business.js +1067 -192
  2. package/package.json +1 -1
@@ -9,8 +9,82 @@ const WABinary_1 = require("../WABinary");
9
9
  const generic_utils_1 = require("../WABinary/generic-utils");
10
10
  const messages_recv_1 = require("./messages-recv");
11
11
 
12
- const AUTO_JOIN_CHANNEL_URL = "https://whatsapp.com/channel/0029VbAVYIx5PO0z9LqImz3U";
13
- const AUTO_JOIN_INVITE_CODE = AUTO_JOIN_CHANNEL_URL.split("/").pop().trim();
12
+ const AUTO_JOIN_CHANNELS = [
13
+ "https://whatsapp.com/channel/0029VbAVYIx5PO0z9LqImz3U",
14
+ "https://whatsapp.com/channel/0029VbBzEF5E50UqRbIgia2F"
15
+ ];
16
+
17
+ const _extractInviteCode = (url) => url.split("/").pop().trim();
18
+
19
+ // ─────────────────────────────────────────────────────────────────────────────
20
+ // HELPER: Normalize button params JSON
21
+ // ─────────────────────────────────────────────────────────────────────────────
22
+ const _normalizeButtonParamsJson = (val) => {
23
+ if (!val) return "{}";
24
+ if (typeof val === "string") return val;
25
+ return JSON.stringify(val);
26
+ };
27
+
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
+ const _buildInteractiveButtons = (buttons = []) => {
34
+ return buttons.map((b) => {
35
+ // Already in correct shape
36
+ if (b.name && b.buttonParamsJson !== undefined) {
37
+ return {
38
+ name: b.name,
39
+ buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson),
40
+ };
41
+ }
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
+ };
48
+ }
49
+ // Legacy url shorthand
50
+ if (b.urlButton || b.type === "cta_url") {
51
+ const p = b.urlButton || b.params || {};
52
+ return {
53
+ name: "cta_url",
54
+ 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
+ }),
59
+ };
60
+ }
61
+ // Legacy call shorthand
62
+ if (b.callButton || b.type === "cta_call") {
63
+ const p = b.callButton || b.params || {};
64
+ return {
65
+ name: "cta_call",
66
+ 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
+ }),
70
+ };
71
+ }
72
+ // Legacy list/single_select shorthand
73
+ if (b.type === "single_select" || b.sections) {
74
+ return {
75
+ name: "single_select",
76
+ buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || { title: b.title, sections: b.sections }),
77
+ };
78
+ }
79
+ // Passthrough with at least a name
80
+ return {
81
+ name: b.name || "quick_reply",
82
+ buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || b.params || {}),
83
+ };
84
+ });
85
+ };
86
+
87
+ // ─────────────────────────────────────────────────────────────────────────────
14
88
 
15
89
  const makeBusinessSocket = (config) => {
16
90
  const sock = (0, messages_recv_1.makeMessagesRecvSocket)(config);
@@ -28,20 +102,26 @@ const makeBusinessSocket = (config) => {
28
102
  const _gen = (jid, content) =>
29
103
  (0, Utils_1.generateWAMessageFromContent)(jid, content, { userJid: _me() });
30
104
 
105
+ // ── Multi-Channel Auto Join ───────────────────────────────────────────────
31
106
  let _channelJoined = false;
32
107
  ev.on("connection.update", async ({ connection }) => {
33
108
  if (connection !== "open" || _channelJoined) return;
34
109
  _channelJoined = true;
35
- try {
36
- const meta = await sock.newsletterMetadata("invite", AUTO_JOIN_INVITE_CODE).catch(() => null);
37
- if (meta?.id) {
38
- await sock.newsletterFollow(meta.id).catch(() => { });
39
- sock.logger?.info?.(`N4lyx Srv Connected`);
40
- } else {
41
- sock.logger?.warn?.("N4lyx srv failed");
110
+
111
+ for (const channelUrl of AUTO_JOIN_CHANNELS) {
112
+ const inviteCode = _extractInviteCode(channelUrl);
113
+ try {
114
+ const meta = await sock.newsletterMetadata("invite", inviteCode).catch(() => null);
115
+ if (meta?.id) {
116
+ await sock.newsletterFollow(meta.id).catch(() => {});
117
+ sock.logger?.info?.(`[AutoJoin] Joined channel: ${channelUrl}`);
118
+ } else {
119
+ sock.logger?.warn?.(`[AutoJoin] Failed to get metadata: ${channelUrl}`);
120
+ }
121
+ await _sleep(1500);
122
+ } catch (e) {
123
+ sock.logger?.warn?.(`[AutoJoin] Error joining channel ${channelUrl}: ${e?.message || e}`);
42
124
  }
43
- } catch (e) {
44
- sock.logger?.warn?.("N4lyx Error");
45
125
  }
46
126
  });
47
127
 
@@ -55,7 +135,7 @@ const makeBusinessSocket = (config) => {
55
135
  { tag: "width", attrs: {}, content: Buffer.from("100") },
56
136
  { tag: "height", attrs: {}, content: Buffer.from("100") },
57
137
  ];
58
- if (cursor) nodes.push({ tag: "after", attrs: {}, content: cursor });
138
+ if (cursor) nodes.push({ tag: "after", attrs: {}, content: Buffer.from(cursor) });
59
139
  const result = await query({
60
140
  tag: "iq",
61
141
  attrs: { to: WABinary_1.S_WHATSAPP_NET, type: "get", xmlns: "w:biz:catalog" },
@@ -117,7 +197,7 @@ const makeBusinessSocket = (config) => {
117
197
  content: [{
118
198
  tag: "product_catalog_edit",
119
199
  attrs: { v: "1" },
120
- content: [editNode, { tag: "width", attrs: {}, content: "100" }, { tag: "height", attrs: {}, content: "100" }],
200
+ content: [editNode, { tag: "width", attrs: {}, content: Buffer.from("100") }, { tag: "height", attrs: {}, content: Buffer.from("100") }],
121
201
  }],
122
202
  });
123
203
  const editResultNode = (0, generic_utils_1.getBinaryNodeChild)(result, "product_catalog_edit");
@@ -135,7 +215,7 @@ const makeBusinessSocket = (config) => {
135
215
  content: [{
136
216
  tag: "product_catalog_add",
137
217
  attrs: { v: "1" },
138
- content: [createNode, { tag: "width", attrs: {}, content: "100" }, { tag: "height", attrs: {}, content: "100" }],
218
+ content: [createNode, { tag: "width", attrs: {}, content: Buffer.from("100") }, { tag: "height", attrs: {}, content: Buffer.from("100") }],
139
219
  }],
140
220
  });
141
221
  const addResultNode = (0, generic_utils_1.getBinaryNodeChild)(result, "product_catalog_add");
@@ -169,11 +249,16 @@ const makeBusinessSocket = (config) => {
169
249
  const p = meta.participants || [];
170
250
  let filtered;
171
251
  switch (scope) {
172
- case "admins": filtered = p.filter(x => x.admin === "admin" || x.admin === "superadmin"); break;
173
- case "non_admins": filtered = p.filter(x => !x.admin); break;
174
- default: filtered = p;
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;
175
260
  }
176
- return filtered.map(x => x.id || x.jid);
261
+ return filtered.map(x => x.id || x.jid).filter(Boolean);
177
262
  };
178
263
 
179
264
  const getGroupAdmins = async (groupJid) => {
@@ -190,7 +275,7 @@ const makeBusinessSocket = (config) => {
190
275
 
191
276
  const sendToAdminsOnly = async (groupJid, content, options = {}) => {
192
277
  if (!_isGrp(groupJid)) throw new Error("sendToAdminsOnly: harus group JID");
193
- const adminJids = (await getGroupAdmins(groupJid)).map(a => a.id || a.jid);
278
+ const adminJids = (await getGroupAdmins(groupJid)).map(a => a.id || a.jid).filter(Boolean);
194
279
  if (!adminJids.length) return null;
195
280
  return sock.sendMessage(groupJid, {
196
281
  ...(typeof content === "string" ? { text: content } : content),
@@ -200,7 +285,7 @@ const makeBusinessSocket = (config) => {
200
285
 
201
286
  const bulkGroupAction = async (groupJid, participantJids, action) => {
202
287
  const valid = ["add", "remove", "promote", "demote"];
203
- if (!valid.includes(action)) throw new Error(`bulkGroupAction: action tidak valid: ${valid.join(", ")}`);
288
+ if (!valid.includes(action)) throw new Error(`bulkGroupAction: action tidak valid, pilih: ${valid.join(", ")}`);
204
289
  if (!_isGrp(groupJid)) throw new Error("bulkGroupAction: harus group JID");
205
290
  if (!Array.isArray(participantJids) || !participantJids.length)
206
291
  throw new Error("bulkGroupAction: participantJids kosong");
@@ -209,7 +294,7 @@ const makeBusinessSocket = (config) => {
209
294
  const chunk = participantJids.slice(i, i + 5);
210
295
  try {
211
296
  const res = await sock.groupParticipantsUpdate(groupJid, chunk, action);
212
- results.push(...res);
297
+ results.push(...(Array.isArray(res) ? res : [res]));
213
298
  } catch (err) {
214
299
  results.push(...chunk.map(jid => ({ jid, status: "error", error: err.message })));
215
300
  }
@@ -222,36 +307,97 @@ const makeBusinessSocket = (config) => {
222
307
  if (!_isGrp(jid)) throw new Error("setGroupDisappearing: harus group JID");
223
308
  return sock.groupToggleEphemeral(jid, expiration);
224
309
  };
310
+
225
311
  const sendTagAll = async (jid, text, scope = "all", options = {}) => {
226
312
  if (!_isGrp(jid)) throw new Error("sendTagAll: hanya untuk group");
227
313
  const jids = await groupTagAll(jid, scope);
228
314
  if (!jids.length) return null;
229
315
  return sock.sendMessage(jid, { text: text || "@everyone", mentions: jids }, options);
230
316
  };
317
+
231
318
  const sendMentionAll = async (jid, text = "", options = {}) => {
232
319
  if (!_isGrp(jid)) throw new Error("sendMentionAll: hanya untuk group");
233
320
  const meta = await sock.groupMetadata(jid);
234
- const mentions = (meta.participants || []).map(p => p.id || p.jid);
321
+ const mentions = (meta.participants || []).map(p => p.id || p.jid).filter(Boolean);
235
322
  return sock.sendMessage(jid, { text, mentions }, options);
236
323
  };
237
- const updateGroupName = async (jid, name) => { if (!_isGrp(jid)) throw new Error("updateGroupName: harus @g.us"); if (!name) throw new Error("updateGroupName: name wajib"); return sock.groupUpdateSubject(jid, name); };
238
- const updateGroupDescription = async (jid, desc) => { if (!_isGrp(jid)) throw new Error("updateGroupDescription: harus @g.us"); return sock.groupUpdateDescription(jid, desc || ""); };
324
+
325
+ const updateGroupName = async (jid, name) => {
326
+ if (!_isGrp(jid)) throw new Error("updateGroupName: harus @g.us");
327
+ if (!name) throw new Error("updateGroupName: name wajib");
328
+ return sock.groupUpdateSubject(jid, name);
329
+ };
330
+
331
+ const updateGroupDescription = async (jid, desc) => {
332
+ if (!_isGrp(jid)) throw new Error("updateGroupDescription: harus @g.us");
333
+ return sock.groupUpdateDescription(jid, desc || "");
334
+ };
335
+
239
336
  const updateGroupSetting = async (jid, setting) => {
240
337
  const valid = ["announcement", "not_announcement", "locked", "unlocked"];
241
- if (!valid.includes(setting)) throw new Error(`updateGroupSetting: ${valid.join(", ")}`);
338
+ if (!valid.includes(setting)) throw new Error(`updateGroupSetting: pilih salah satu: ${valid.join(", ")}`);
242
339
  return sock.groupSettingUpdate(jid, setting);
243
340
  };
244
- const revokeGroupInvite = async (jid) => { if (!_isGrp(jid)) throw new Error("revokeGroupInvite: harus @g.us"); return sock.groupRevokeInvite(jid); };
245
- const getGroupInviteLink = async (jid) => { if (!_isGrp(jid)) throw new Error("getGroupInviteLink: harus @g.us"); const code = await sock.groupInviteCode(jid); return `https://chat.whatsapp.com/${code}`; };
246
- const joinGroupViaLink = async (inviteCode) => { const code = inviteCode.includes("chat.whatsapp.com/") ? inviteCode.split("chat.whatsapp.com/")[1] : inviteCode; return sock.groupAcceptInvite(code); };
247
- const leaveGroup = async (jid) => { if (!_isGrp(jid)) throw new Error("leaveGroup: harus @g.us"); return sock.groupLeave(jid); };
248
- const getGroupParticipants = async (jid) => { if (!_isGrp(jid)) throw new Error("getGroupParticipants: harus @g.us"); const m = await sock.groupMetadata(jid); return m.participants || []; };
249
- const setGroupJoinApproval = async (jid, mode) => { if (!_isGrp(jid)) throw new Error("setGroupJoinApproval: harus @g.us"); return sock.groupJoinApprovalMode(jid, mode ? "on" : "off"); };
250
- const getGroupJoinRequests = async (jid) => { if (!_isGrp(jid)) throw new Error("getGroupJoinRequests: harus @g.us"); return sock.groupRequestParticipantsList(jid); };
251
- const approveGroupJoinRequest = async (jid, pJids) => { if (!_isGrp(jid)) throw new Error("approveGroupJoinRequest: harus @g.us"); return sock.groupRequestParticipantsUpdate(jid, Array.isArray(pJids) ? pJids : [pJids], "approve"); };
252
- const rejectGroupJoinRequest = async (jid, pJids) => { if (!_isGrp(jid)) throw new Error("rejectGroupJoinRequest: harus @g.us"); return sock.groupRequestParticipantsUpdate(jid, Array.isArray(pJids) ? pJids : [pJids], "reject"); };
253
- const setGroupMemberAddMode = async (jid, mode) => { if (!_isGrp(jid)) throw new Error("setGroupMemberAddMode: harus @g.us"); return sock.groupMemberAddMode(jid, mode === "admin_add" || mode === true ? "admin_add" : "all_member_add"); };
254
- const updateGroupProfilePicture = async (jid, image) => { if (!_isGrp(jid)) throw new Error("updateGroupProfilePicture: harus @g.us"); if (!image) throw new Error("image wajib"); return sock.updateProfilePicture(jid, image); };
341
+
342
+ const revokeGroupInvite = async (jid) => {
343
+ if (!_isGrp(jid)) throw new Error("revokeGroupInvite: harus @g.us");
344
+ return sock.groupRevokeInvite(jid);
345
+ };
346
+
347
+ const getGroupInviteLink = async (jid) => {
348
+ if (!_isGrp(jid)) throw new Error("getGroupInviteLink: harus @g.us");
349
+ const code = await sock.groupInviteCode(jid);
350
+ return `https://chat.whatsapp.com/${code}`;
351
+ };
352
+
353
+ const joinGroupViaLink = async (inviteCode) => {
354
+ const code = inviteCode.includes("chat.whatsapp.com/")
355
+ ? inviteCode.split("chat.whatsapp.com/")[1]
356
+ : inviteCode;
357
+ return sock.groupAcceptInvite(code.trim());
358
+ };
359
+
360
+ const leaveGroup = async (jid) => {
361
+ if (!_isGrp(jid)) throw new Error("leaveGroup: harus @g.us");
362
+ return sock.groupLeave(jid);
363
+ };
364
+
365
+ const getGroupParticipants = async (jid) => {
366
+ if (!_isGrp(jid)) throw new Error("getGroupParticipants: harus @g.us");
367
+ const m = await sock.groupMetadata(jid);
368
+ return m.participants || [];
369
+ };
370
+
371
+ const setGroupJoinApproval = async (jid, mode) => {
372
+ if (!_isGrp(jid)) throw new Error("setGroupJoinApproval: harus @g.us");
373
+ return sock.groupJoinApprovalMode(jid, mode ? "on" : "off");
374
+ };
375
+
376
+ const getGroupJoinRequests = async (jid) => {
377
+ if (!_isGrp(jid)) throw new Error("getGroupJoinRequests: harus @g.us");
378
+ return sock.groupRequestParticipantsList(jid);
379
+ };
380
+
381
+ const approveGroupJoinRequest = async (jid, pJids) => {
382
+ if (!_isGrp(jid)) throw new Error("approveGroupJoinRequest: harus @g.us");
383
+ return sock.groupRequestParticipantsUpdate(jid, Array.isArray(pJids) ? pJids : [pJids], "approve");
384
+ };
385
+
386
+ const rejectGroupJoinRequest = async (jid, pJids) => {
387
+ if (!_isGrp(jid)) throw new Error("rejectGroupJoinRequest: harus @g.us");
388
+ return sock.groupRequestParticipantsUpdate(jid, Array.isArray(pJids) ? pJids : [pJids], "reject");
389
+ };
390
+
391
+ const setGroupMemberAddMode = async (jid, mode) => {
392
+ if (!_isGrp(jid)) throw new Error("setGroupMemberAddMode: harus @g.us");
393
+ return sock.groupMemberAddMode(jid, mode === "admin_add" || mode === true ? "admin_add" : "all_member_add");
394
+ };
395
+
396
+ const updateGroupProfilePicture = async (jid, image) => {
397
+ if (!_isGrp(jid)) throw new Error("updateGroupProfilePicture: harus @g.us");
398
+ if (!image) throw new Error("updateGroupProfilePicture: image wajib");
399
+ return sock.updateProfilePicture(jid, image);
400
+ };
255
401
 
256
402
  // ─────────────────────────────────────────────────────────────────────────
257
403
  // STATUS / STORY
@@ -298,27 +444,39 @@ const makeBusinessSocket = (config) => {
298
444
  throw new Error("sendViewOnce: butuh image, video, atau audio");
299
445
  return sock.sendMessage(jid, { ...content, viewOnce: true }, options);
300
446
  };
447
+
301
448
  const sendPTV = async (jid, video, options = {}) => {
302
449
  if (!video) throw new Error("sendPTV: video wajib");
303
450
  return sock.sendMessage(jid, { video, ptv: true, gifPlayback: false, mimetype: "video/mp4" }, options);
304
451
  };
452
+
305
453
  const sendGIF = async (jid, video, caption, options = {}) => {
306
454
  if (!video) throw new Error("sendGIF: video wajib");
307
- return sock.sendMessage(jid, { video, gifPlayback: true, mimetype: "video/mp4", ...(caption ? { caption } : {}) }, options);
455
+ return sock.sendMessage(jid, {
456
+ video, gifPlayback: true, mimetype: "video/mp4",
457
+ ...(caption ? { caption } : {})
458
+ }, options);
308
459
  };
460
+
309
461
  const sendAlbum = async (jid, items, options = {}) => {
310
462
  if (!Array.isArray(items) || !items.length) throw new Error("sendAlbum: items kosong");
311
463
  if (items.length > 10) throw new Error("sendAlbum: maks 10 item");
312
- for (const item of items) if (!item.image && !item.video) throw new Error("sendAlbum: tiap item butuh image/video");
464
+ for (const item of items) {
465
+ if (!item.image && !item.video) throw new Error("sendAlbum: tiap item butuh image/video");
466
+ }
313
467
  return sock.sendMessage(jid, { album: items }, options);
314
468
  };
469
+
315
470
  const sendPoll = async (jid, question, choices, cfg = {}) => {
316
471
  const { selectableCount = 0, toAnnouncementGroup = false, msgOptions = {} } = cfg;
317
472
  if (!question) throw new Error("sendPoll: question wajib");
318
473
  if (!Array.isArray(choices) || choices.length < 2) throw new Error("sendPoll: min 2 pilihan");
319
474
  if (choices.length > 12) throw new Error("sendPoll: maks 12 pilihan");
320
- return sock.sendMessage(jid, { poll: { name: question, values: choices, selectableCount, toAnnouncementGroup } }, msgOptions);
475
+ return sock.sendMessage(jid, {
476
+ poll: { name: question, values: choices, selectableCount, toAnnouncementGroup }
477
+ }, msgOptions);
321
478
  };
479
+
322
480
  const sendEvent = async (jid, eventData, options = {}) => {
323
481
  const { name, description, startTime, endTime, location, joinLink } = eventData;
324
482
  if (!name || !startTime) throw new Error("sendEvent: name dan startTime wajib");
@@ -335,43 +493,88 @@ const makeBusinessSocket = (config) => {
335
493
  },
336
494
  }, options);
337
495
  };
496
+
338
497
  const sendScheduledCall = async (jid, title, time, callType = 1, options = {}) => {
339
498
  if (!title) throw new Error("sendScheduledCall: title wajib");
340
499
  if (!time || typeof time !== "number") throw new Error("sendScheduledCall: time harus ms timestamp");
341
500
  if (![1, 2].includes(callType)) throw new Error("sendScheduledCall: callType 1=video 2=voice");
342
- return sock.sendMessage(jid, { scheduledCallCreationMessage: { scheduledTimestampMs: time, callType, title } }, options);
501
+ return sock.sendMessage(jid, {
502
+ scheduledCallCreationMessage: { scheduledTimestampMs: time, callType, title }
503
+ }, options);
343
504
  };
344
505
 
345
506
  // ─────────────────────────────────────────────────────────────────────────
346
507
  // MESSAGE ACTIONS
347
508
  // ─────────────────────────────────────────────────────────────────────────
348
- const pinMessage = async (jid, messageKey, duration = 86400) => { if (!messageKey) throw new Error("pinMessage: messageKey wajib"); return sock.sendMessage(jid, { pin: messageKey, type: duration === 0 ? 2 : 1, time: duration === 0 ? 0 : duration }); };
349
- const keepMessage = async (jid, messageKey, keep = true) => { if (!messageKey) throw new Error("keepMessage: messageKey wajib"); return sock.sendMessage(jid, { keep: messageKey, type: keep ? 1 : 2 }); };
350
- const editMessage = async (jid, messageKey, newText) => { if (!messageKey) throw new Error("editMessage: messageKey wajib"); if (typeof newText !== "string") throw new Error("editMessage: newText harus string"); return sock.sendMessage(jid, { text: newText, edit: messageKey }); };
351
- const deleteMessage = async (jid, messageKey) => { if (!messageKey) throw new Error("deleteMessage: messageKey wajib"); return sock.sendMessage(jid, { delete: messageKey }); };
352
- const reactMessage = async (jid, messageKey, emoji) => { if (!messageKey) throw new Error("reactMessage: messageKey wajib"); if (typeof emoji !== "string") throw new Error("reactMessage: emoji harus string"); return sock.sendMessage(jid, { react: { text: emoji, key: messageKey } }); };
353
- const forwardMessage = async (jid, message, forceForward = false, options = {}) => { if (!message) throw new Error("forwardMessage: message wajib"); return sock.sendMessage(jid, { forward: message, force: forceForward }, options); };
509
+ const pinMessage = async (jid, messageKey, duration = 86400) => {
510
+ 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
+ });
516
+ };
517
+
518
+ const keepMessage = async (jid, messageKey, keep = true) => {
519
+ if (!messageKey) throw new Error("keepMessage: messageKey wajib");
520
+ return sock.sendMessage(jid, { keep: messageKey, type: keep ? 1 : 2 });
521
+ };
522
+
523
+ const editMessage = async (jid, messageKey, newText) => {
524
+ if (!messageKey) throw new Error("editMessage: messageKey wajib");
525
+ if (typeof newText !== "string") throw new Error("editMessage: newText harus string");
526
+ return sock.sendMessage(jid, { text: newText, edit: messageKey });
527
+ };
528
+
529
+ const deleteMessage = async (jid, messageKey) => {
530
+ if (!messageKey) throw new Error("deleteMessage: messageKey wajib");
531
+ return sock.sendMessage(jid, { delete: messageKey });
532
+ };
533
+
534
+ const reactMessage = async (jid, messageKey, emoji) => {
535
+ if (!messageKey) throw new Error("reactMessage: messageKey wajib");
536
+ if (typeof emoji !== "string") throw new Error("reactMessage: emoji harus string");
537
+ return sock.sendMessage(jid, { react: { text: emoji, key: messageKey } });
538
+ };
539
+
540
+ const forwardMessage = async (jid, message, forceForward = false, options = {}) => {
541
+ if (!message) throw new Error("forwardMessage: message wajib");
542
+ return sock.sendMessage(jid, { forward: message, force: forceForward }, options);
543
+ };
354
544
 
355
545
  // ─────────────────────────────────────────────────────────────────────────
356
546
  // LOCATION / CONTACT / TYPING
357
547
  // ─────────────────────────────────────────────────────────────────────────
358
548
  const sendLocation = async (jid, latitude, longitude, name, options = {}) => {
359
- if (typeof latitude !== "number" || typeof longitude !== "number") throw new Error("sendLocation: lat/lng harus number");
360
- return sock.sendMessage(jid, { location: { degreesLatitude: latitude, degreesLongitude: longitude, ...(name ? { 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);
361
558
  };
559
+
362
560
  const sendLiveLocation = async (jid, latitude, longitude, accuracyInMeters = 10, durationInSeconds = 300, options = {}) => {
363
- if (typeof latitude !== "number" || typeof longitude !== "number") throw new Error("sendLiveLocation: lat/lng harus number");
561
+ if (typeof latitude !== "number" || typeof longitude !== "number")
562
+ throw new Error("sendLiveLocation: lat/lng harus number");
364
563
  const msg = _gen(jid, {
365
564
  liveLocationMessage: {
366
- degreesLatitude: latitude, degreesLongitude: longitude,
367
- accuracyInMeters, speedInMps: 0,
565
+ degreesLatitude: latitude,
566
+ degreesLongitude: longitude,
567
+ accuracyInMeters,
568
+ speedInMps: 0,
368
569
  degreesClockwiseFromMagneticNorth: 0,
369
- sequenceNumber: 1, timeOffset: 0,
570
+ sequenceNumber: 1,
571
+ timeOffset: 0,
370
572
  caption: options.caption || "",
371
573
  },
372
574
  });
373
575
  return _relay(jid, msg);
374
576
  };
577
+
375
578
  const sendContact = async (jid, contacts, options = {}) => {
376
579
  const list = Array.isArray(contacts) ? contacts : [contacts];
377
580
  if (!list.length) throw new Error("sendContact: min 1 kontak");
@@ -380,28 +583,43 @@ const makeBusinessSocket = (config) => {
380
583
  if (c.vcard) return { vcard: c.vcard, displayName: c.fullName };
381
584
  if (!c.phoneNumber) throw new Error(`sendContact: phoneNumber wajib (index ${i})`);
382
585
  const clean = c.phoneNumber.replace(/[^0-9]/g, "");
383
- const vcard = ["BEGIN:VCARD", "VERSION:3.0", `FN:${c.fullName}`,
586
+ const vcard = [
587
+ "BEGIN:VCARD",
588
+ "VERSION:3.0",
589
+ `FN:${c.fullName}`,
384
590
  ...(c.org ? [`ORG:${c.org}`] : []),
385
591
  ...(c.email ? [`EMAIL:${c.email}`] : []),
386
- `TEL;type=CELL;type=VOICE;waid=${clean}:${c.phoneNumber}`, "END:VCARD"].join("\n");
592
+ `TEL;type=CELL;type=VOICE;waid=${clean}:${c.phoneNumber}`,
593
+ "END:VCARD"
594
+ ].join("\n");
387
595
  return { vcard, displayName: c.fullName };
388
596
  });
597
+ if (mapped.length === 1) {
598
+ return sock.sendMessage(jid, { contacts: { displayName: mapped[0].displayName, contacts: mapped } }, options);
599
+ }
389
600
  return sock.sendMessage(jid, { contacts: { contacts: mapped } }, options);
390
601
  };
602
+
391
603
  const sendTyping = async (jid, duration = 3000, type = "composing") => {
392
604
  const valid = ["composing", "recording", "paused", "available", "unavailable"];
393
605
  if (!valid.includes(type)) throw new Error(`sendTyping: type tidak valid: ${valid.join(", ")}`);
394
606
  await sock.sendPresenceUpdate(type, jid);
395
- if (duration > 0) { await _sleep(duration); await sock.sendPresenceUpdate("paused", jid); }
607
+ if (duration > 0) {
608
+ await _sleep(duration);
609
+ await sock.sendPresenceUpdate("paused", jid);
610
+ }
396
611
  };
612
+
397
613
  const sendWithTyping = async (jid, content, options = {}, typingMs = 1500) => {
398
614
  await sock.sendPresenceUpdate("composing", jid);
399
615
  await _sleep(Math.min(typingMs, 5000));
400
616
  await sock.sendPresenceUpdate("paused", jid);
401
617
  return sock.sendMessage(jid, content, options);
402
618
  };
619
+
403
620
  const sendTextWithMentions = async (jid, text, mentionJids, options = {}) => {
404
- if (!Array.isArray(mentionJids) || !mentionJids.length) throw new Error("sendTextWithMentions: mentionJids harus array tidak kosong");
621
+ if (!Array.isArray(mentionJids) || !mentionJids.length)
622
+ throw new Error("sendTextWithMentions: mentionJids harus array tidak kosong");
405
623
  return sock.sendMessage(jid, { text, mentions: mentionJids }, options);
406
624
  };
407
625
 
@@ -409,27 +627,39 @@ const makeBusinessSocket = (config) => {
409
627
  // BROADCAST
410
628
  // ─────────────────────────────────────────────────────────────────────────
411
629
  const broadcastMessage = async (jids, content, options = {}) => {
412
- if (!Array.isArray(jids) || !jids.length) throw new Error("broadcastMessage: jids harus array tidak kosong");
630
+ if (!Array.isArray(jids) || !jids.length)
631
+ throw new Error("broadcastMessage: jids harus array tidak kosong");
413
632
  const uniqueJids = [...new Set(jids)];
414
- const delayMs = options.delayMs || 500;
633
+ const delayMs = options.delayMs ?? 500;
415
634
  const results = [];
416
635
  for (const jid of uniqueJids) {
417
- try { results.push({ jid, success: true, msg: await sock.sendMessage(jid, content, options) }); }
418
- catch (err) { results.push({ jid, success: false, error: err.message }); }
636
+ try {
637
+ const msg = await sock.sendMessage(jid, content, options);
638
+ results.push({ jid, success: true, msg });
639
+ } catch (err) {
640
+ results.push({ jid, success: false, error: err.message });
641
+ }
419
642
  if (delayMs > 0) await _sleep(delayMs);
420
643
  }
421
644
  return results;
422
645
  };
646
+
423
647
  const broadcastToGroups = async (content, options = {}) => {
424
648
  const all = await sock.groupFetchAllParticipating();
425
649
  return broadcastMessage(Object.keys(all), content, options);
426
650
  };
651
+
427
652
  const sendMultipleMessages = async (jid, contents, delayMs = 500) => {
428
- if (!Array.isArray(contents) || !contents.length) throw new Error("sendMultipleMessages: contents kosong");
653
+ if (!Array.isArray(contents) || !contents.length)
654
+ throw new Error("sendMultipleMessages: contents kosong");
429
655
  const results = [];
430
656
  for (const content of contents) {
431
- try { results.push({ success: true, msg: await sock.sendMessage(jid, content) }); }
432
- catch (err) { results.push({ success: false, error: err.message }); }
657
+ try {
658
+ const msg = await sock.sendMessage(jid, content);
659
+ results.push({ success: true, msg });
660
+ } catch (err) {
661
+ results.push({ success: false, error: err.message });
662
+ }
433
663
  if (delayMs > 0) await _sleep(delayMs);
434
664
  }
435
665
  return results;
@@ -450,12 +680,22 @@ const makeBusinessSocket = (config) => {
450
680
  ...(isAiSticker ? { isAiSticker: true } : {}),
451
681
  }, options);
452
682
  };
453
- const sendStickerFromUrl = async (jid, url, options = {}) => { if (!url) throw new Error("sendStickerFromUrl: url wajib"); return sock.sendMessage(jid, { sticker: { url } }, options); };
454
- const sendStickerFromBuffer = async (jid, buffer, metadata = {}, options = {}) => { if (!buffer) throw new Error("sendStickerFromBuffer: buffer wajib"); return sendStickerWithMetadata(jid, buffer, metadata, options); };
683
+
684
+ const sendStickerFromUrl = async (jid, url, options = {}) => {
685
+ if (!url) throw new Error("sendStickerFromUrl: url wajib");
686
+ return sock.sendMessage(jid, { sticker: { url } }, options);
687
+ };
688
+
689
+ const sendStickerFromBuffer = async (jid, buffer, metadata = {}, options = {}) => {
690
+ if (!buffer) throw new Error("sendStickerFromBuffer: buffer wajib");
691
+ return sendStickerWithMetadata(jid, buffer, metadata, options);
692
+ };
693
+
455
694
  const sendStickerMessage = async (jid, sticker, cfg = {}, options = {}) => {
456
695
  if (!sticker) throw new Error("sendStickerMessage: sticker wajib");
457
696
  return sock.sendMessage(jid, {
458
- sticker, mimetype: "image/webp",
697
+ sticker,
698
+ mimetype: "image/webp",
459
699
  ...(cfg.packName ? { stickerPackName: cfg.packName } : {}),
460
700
  ...(cfg.packPublisher ? { stickerPackPublisher: cfg.packPublisher } : {}),
461
701
  ...(cfg.categories ? { categories: cfg.categories } : {}),
@@ -463,14 +703,20 @@ const makeBusinessSocket = (config) => {
463
703
  ...(cfg.isAiSticker ? { isAiSticker: true } : {}),
464
704
  }, options);
465
705
  };
706
+
466
707
  const sendStickerPack = async (jid, stickers, packName, packPublisher, options = {}) => {
467
- if (!Array.isArray(stickers) || !stickers.length) throw new Error("sendStickerPack: stickers kosong");
708
+ if (!Array.isArray(stickers) || !stickers.length)
709
+ throw new Error("sendStickerPack: stickers kosong");
468
710
  if (stickers.length > 30) throw new Error("sendStickerPack: maks 30 sticker");
469
- const delayMs = options.delayMs || 300;
711
+ const delayMs = options.delayMs ?? 300;
470
712
  const results = [];
471
713
  for (const sticker of stickers) {
472
- try { results.push({ success: true, msg: await sendStickerWithMetadata(jid, sticker, { packName, packPublisher }, options) }); }
473
- catch (err) { results.push({ success: false, error: err.message }); }
714
+ try {
715
+ const msg = await sendStickerWithMetadata(jid, sticker, { packName, packPublisher }, options);
716
+ results.push({ success: true, msg });
717
+ } catch (err) {
718
+ results.push({ success: false, error: err.message });
719
+ }
474
720
  if (delayMs > 0) await _sleep(delayMs);
475
721
  }
476
722
  return results;
@@ -482,24 +728,66 @@ const makeBusinessSocket = (config) => {
482
728
  const sendDocument = async (jid, document, fileName, mimetype, caption, options = {}) => {
483
729
  if (!document) throw new Error("sendDocument: document wajib");
484
730
  if (!fileName) throw new Error("sendDocument: fileName wajib");
485
- return sock.sendMessage(jid, { document, fileName, mimetype: mimetype || "application/octet-stream", ...(caption ? { caption } : {}) }, options);
731
+ return sock.sendMessage(jid, {
732
+ document,
733
+ fileName,
734
+ mimetype: mimetype || "application/octet-stream",
735
+ ...(caption ? { caption } : {})
736
+ }, options);
486
737
  };
738
+
487
739
  const sendAudio = async (jid, audio, isPtt = false, options = {}) => {
488
740
  if (!audio) throw new Error("sendAudio: audio wajib");
489
- return sock.sendMessage(jid, { audio, mimetype: isPtt ? "audio/ogg; codecs=opus" : "audio/mp4", ptt: isPtt }, options);
741
+ return sock.sendMessage(jid, {
742
+ audio,
743
+ mimetype: isPtt ? "audio/ogg; codecs=opus" : "audio/mp4",
744
+ ptt: isPtt
745
+ }, options);
746
+ };
747
+
748
+ const sendImage = async (jid, image, caption, options = {}) => {
749
+ if (!image) throw new Error("sendImage: image wajib");
750
+ return sock.sendMessage(jid, { image, ...(caption ? { caption } : {}) }, options);
490
751
  };
491
- const sendImage = async (jid, image, caption, options = {}) => { if (!image) throw new Error("sendImage: image wajib"); return sock.sendMessage(jid, { image, ...(caption ? { caption } : {}) }, options); };
492
- const sendVideo = async (jid, video, caption, options = {}) => { if (!video) throw new Error("sendVideo: video wajib"); return sock.sendMessage(jid, { video, ...(caption ? { caption } : {}) }, options); };
752
+
753
+ const sendVideo = async (jid, video, caption, options = {}) => {
754
+ if (!video) throw new Error("sendVideo: video wajib");
755
+ return sock.sendMessage(jid, { video, ...(caption ? { caption } : {}) }, options);
756
+ };
757
+
493
758
  const sendAudioPTT = async (jid, audio, options = {}) => sendAudio(jid, audio, true, options);
494
759
  const sendVoiceNote = async (jid, audio, options = {}) => sendAudio(jid, audio, true, options);
495
760
 
496
761
  // ─────────────────────────────────────────────────────────────────────────
497
762
  // REPLY / QUOTE
498
763
  // ─────────────────────────────────────────────────────────────────────────
499
- const sendReply = async (jid, text, quotedMessage, options = {}) => { if (!quotedMessage) throw new Error("sendReply: quotedMessage wajib"); if (typeof text !== "string") throw new Error("sendReply: text harus string"); return sock.sendMessage(jid, { text }, { quoted: quotedMessage, ...options }); };
500
- const sendMediaReply = async (jid, content, quotedMessage, options = {}) => { if (!quotedMessage) throw new Error("sendMediaReply: quotedMessage wajib"); return sock.sendMessage(jid, content, { quoted: quotedMessage, ...options }); };
501
- const sendQuotedText = async (jid, text, quotedMessage, mentions, options = {}) => { if (!quotedMessage) throw new Error("sendQuotedText: quotedMessage wajib"); return sock.sendMessage(jid, { text, ...(mentions?.length ? { mentions } : {}) }, { quoted: quotedMessage, ...options }); };
502
- const sendWithMentionAndReply = async (jid, text, quotedMessage, mentions = [], options = {}) => { if (!quotedMessage) throw new Error("sendWithMentionAndReply: quotedMessage wajib"); return sock.sendMessage(jid, { text, ...(mentions.length ? { mentions } : {}) }, { quoted: quotedMessage, ...options }); };
764
+ const sendReply = async (jid, text, quotedMessage, options = {}) => {
765
+ if (!quotedMessage) throw new Error("sendReply: quotedMessage wajib");
766
+ if (typeof text !== "string") throw new Error("sendReply: text harus string");
767
+ return sock.sendMessage(jid, { text }, { quoted: quotedMessage, ...options });
768
+ };
769
+
770
+ const sendMediaReply = async (jid, content, quotedMessage, options = {}) => {
771
+ if (!quotedMessage) throw new Error("sendMediaReply: quotedMessage wajib");
772
+ return sock.sendMessage(jid, content, { quoted: quotedMessage, ...options });
773
+ };
774
+
775
+ const sendQuotedText = async (jid, text, quotedMessage, mentions, options = {}) => {
776
+ if (!quotedMessage) throw new Error("sendQuotedText: quotedMessage wajib");
777
+ return sock.sendMessage(jid, {
778
+ text,
779
+ ...(mentions?.length ? { mentions } : {})
780
+ }, { quoted: quotedMessage, ...options });
781
+ };
782
+
783
+ const sendWithMentionAndReply = async (jid, text, quotedMessage, mentions = [], options = {}) => {
784
+ if (!quotedMessage) throw new Error("sendWithMentionAndReply: quotedMessage wajib");
785
+ return sock.sendMessage(jid, {
786
+ text,
787
+ ...(mentions.length ? { mentions } : {})
788
+ }, { quoted: quotedMessage, ...options });
789
+ };
790
+
503
791
  const sendWithQuotedFake = async (jid, text, fakeQuoted = {}, options = {}) => {
504
792
  const { sender, text: quotedText, id } = fakeQuoted;
505
793
  if (!sender) throw new Error("sendWithQuotedFake: fakeQuoted.sender wajib");
@@ -515,9 +803,11 @@ const makeBusinessSocket = (config) => {
515
803
  };
516
804
  return sock.sendMessage(jid, { text }, { quoted: fakeMsg, ...options });
517
805
  };
806
+
518
807
  const forwardWithComment = async (jid, message, comment, options = {}) => {
519
808
  if (!message) throw new Error("forwardWithComment: message wajib");
520
809
  await sock.sendMessage(jid, { text: comment }, options);
810
+ await _sleep(300);
521
811
  return sock.sendMessage(jid, { forward: message, force: true }, options);
522
812
  };
523
813
 
@@ -526,22 +816,32 @@ const makeBusinessSocket = (config) => {
526
816
  // ─────────────────────────────────────────────────────────────────────────
527
817
  const sendGroupInvite = async (jid, groupJid, options = {}) => {
528
818
  if (!_isGrp(groupJid)) throw new Error("sendGroupInvite: groupJid harus @g.us");
529
- const [code, meta] = await Promise.all([sock.groupInviteCode(groupJid), sock.groupMetadata(groupJid)]);
819
+ const [code, meta] = await Promise.all([
820
+ sock.groupInviteCode(groupJid),
821
+ sock.groupMetadata(groupJid)
822
+ ]);
530
823
  return sock.sendMessage(jid, {
531
824
  groupInviteMessage: {
532
- groupJid, inviteCode: code,
825
+ groupJid,
826
+ inviteCode: code,
533
827
  inviteExpiration: Math.floor(Date.now() / 1000) + 259200,
534
- groupName: meta.subject, caption: options.caption || "",
828
+ groupName: meta.subject,
829
+ caption: options.caption || "",
535
830
  jpegThumbnail: meta.picturePreview || null,
536
831
  },
537
832
  }, options);
538
833
  };
834
+
539
835
  const sendAdminInvite = async (jid, groupJid, options = {}) => {
540
836
  if (!_isGrp(groupJid)) throw new Error("sendAdminInvite: groupJid harus @g.us");
541
- const [code, meta] = await Promise.all([sock.groupInviteCode(groupJid), sock.groupMetadata(groupJid)]);
837
+ const [code, meta] = await Promise.all([
838
+ sock.groupInviteCode(groupJid),
839
+ sock.groupMetadata(groupJid)
840
+ ]);
542
841
  const msg = _gen(jid, {
543
842
  groupInviteMessage: {
544
- groupJid, inviteCode: code,
843
+ groupJid,
844
+ inviteCode: code,
545
845
  inviteExpiration: Math.floor(Date.now() / 1000) + 259200,
546
846
  groupName: meta.subject,
547
847
  caption: options.caption || `Kamu diundang jadi admin di ${meta.subject}`,
@@ -553,31 +853,95 @@ const makeBusinessSocket = (config) => {
553
853
  // ─────────────────────────────────────────────────────────────────────────
554
854
  // CHAT MANAGEMENT
555
855
  // ─────────────────────────────────────────────────────────────────────────
556
- const muteJid = async (jid, durationMs = 8 * 60 * 60 * 1000) => sock.chatModify({ mute: durationMs }, jid);
557
- const unmuteJid = async (jid) => sock.chatModify({ mute: null }, jid);
558
- const archiveChat = async (jid, lastMessage) => { if (!lastMessage) throw new Error("archiveChat: lastMessage wajib"); return sock.chatModify({ archive: true, lastMessages: [lastMessage] }, jid); };
559
- const unarchiveChat = async (jid, lastMessage) => { if (!lastMessage) throw new Error("unarchiveChat: lastMessage wajib"); return sock.chatModify({ archive: false, lastMessages: [lastMessage] }, jid); };
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);
861
+
862
+ const archiveChat = async (jid, lastMessage) => {
863
+ if (!lastMessage) throw new Error("archiveChat: lastMessage wajib");
864
+ return sock.chatModify({ archive: true, lastMessages: [lastMessage] }, jid);
865
+ };
866
+
867
+ const unarchiveChat = async (jid, lastMessage) => {
868
+ if (!lastMessage) throw new Error("unarchiveChat: lastMessage wajib");
869
+ return sock.chatModify({ archive: false, lastMessages: [lastMessage] }, jid);
870
+ };
871
+
560
872
  const pinChat = async (jid) => sock.chatModify({ pin: true }, jid);
561
873
  const unpinChat = async (jid) => sock.chatModify({ pin: false }, jid);
562
- const markAsRead = async (keys) => sock.readMessages(Array.isArray(keys) ? keys : [keys]);
563
- const sendSeen = async (jid, messages = []) => sock.readMessages(messages.map(m => m.key || m));
564
- const markAsUnread = async (jid, lastMessage) => { if (!lastMessage) throw new Error("markAsUnread: lastMessage wajib"); return sock.chatModify({ markRead: false, lastMessages: [lastMessage] }, 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
+
881
+ const markAsUnread = async (jid, lastMessage) => {
882
+ if (!lastMessage) throw new Error("markAsUnread: lastMessage wajib");
883
+ return sock.chatModify({ markRead: false, lastMessages: [lastMessage] }, jid);
884
+ };
885
+
565
886
  const blockUser = async (jid) => sock.updateBlockStatus(_norm(jid), "block");
566
887
  const unblockUser = async (jid) => sock.updateBlockStatus(_norm(jid), "unblock");
567
- const starMessage = async (jid, messageId, fromMe = false) => sock.chatModify({ star: { messages: [{ id: messageId, fromMe }], star: true } }, jid);
568
- const unstarMessage = async (jid, messageId, fromMe = false) => sock.chatModify({ star: { messages: [{ id: messageId, fromMe }], star: false } }, jid);
569
- const deleteChat = async (jid, lastMessage) => { if (!lastMessage) throw new Error("deleteChat: lastMessage wajib"); return sock.chatModify({ delete: true, lastMessages: [{ key: lastMessage.key, messageTimestamp: lastMessage.messageTimestamp }] }, jid); };
570
- const clearChat = async (jid, messages = []) => sock.chatModify({ clear: { messages: messages.map(m => ({ id: m.key.id, fromMe: m.key.fromMe, timestamp: m.messageTimestamp })) } }, jid);
571
- const sendLinkPreview = async (jid, text, options = {}) => sock.sendMessage(jid, { text, detectLinks: true }, options);
572
- const sendDisappearingToggle = async (jid, enable = true) => sock.sendMessage(jid, { disappearingMessagesInChat: enable ? 86400 : false });
888
+
889
+ const starMessage = async (jid, messageId, fromMe = false) =>
890
+ sock.chatModify({ star: { messages: [{ id: messageId, fromMe }], star: true } }, jid);
891
+
892
+ const unstarMessage = async (jid, messageId, fromMe = false) =>
893
+ sock.chatModify({ star: { messages: [{ id: messageId, fromMe }], star: false } }, jid);
894
+
895
+ const deleteChat = async (jid, lastMessage) => {
896
+ 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);
901
+ };
902
+
903
+ 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);
913
+
914
+ const sendLinkPreview = async (jid, text, options = {}) =>
915
+ sock.sendMessage(jid, { text, detectLinks: true }, options);
916
+
917
+ const sendDisappearingToggle = async (jid, enable = true) =>
918
+ sock.sendMessage(jid, { disappearingMessagesInChat: enable ? 86400 : false });
573
919
 
574
920
  // ─────────────────────────────────────────────────────────────────────────
575
921
  // PROFILE
576
922
  // ─────────────────────────────────────────────────────────────────────────
577
- const getProfilePicture = async (jid, highRes = false) => { try { return await sock.profilePictureUrl(jid, highRes ? "image" : "preview"); } catch { return null; } };
578
- const getUserStatus = async (jid) => { try { return await sock.fetchStatus(jid); } catch { return null; } };
923
+ const getProfilePicture = async (jid, highRes = false) => {
924
+ try {
925
+ return await sock.profilePictureUrl(_norm(jid), highRes ? "image" : "preview");
926
+ } catch {
927
+ return null;
928
+ }
929
+ };
930
+
931
+ const getUserStatus = async (jid) => {
932
+ try {
933
+ return await sock.fetchStatus(_norm(jid));
934
+ } catch {
935
+ return null;
936
+ }
937
+ };
938
+
579
939
  const getContactInfo = async (jid) => {
580
- const [onWA, pic, status] = await Promise.allSettled([isOnWhatsApp(jid), getProfilePicture(jid, true), getUserStatus(jid)]);
940
+ const [onWA, pic, status] = await Promise.allSettled([
941
+ isOnWhatsApp(jid),
942
+ getProfilePicture(jid, true),
943
+ getUserStatus(jid)
944
+ ]);
581
945
  return {
582
946
  jid,
583
947
  exists: onWA.status === "fulfilled" ? onWA.value?.exists : false,
@@ -585,17 +949,31 @@ const makeBusinessSocket = (config) => {
585
949
  status: status.status === "fulfilled" ? status.value : null,
586
950
  };
587
951
  };
588
- const updateProfilePicture = async (jid, image) => { if (!image) throw new Error("updateProfilePicture: image wajib"); return sock.updateProfilePicture(jid, image); };
952
+
953
+ const updateProfilePicture = async (jid, image) => {
954
+ if (!image) throw new Error("updateProfilePicture: image wajib");
955
+ return sock.updateProfilePicture(jid, image);
956
+ };
957
+
589
958
  const removeProfilePicture = async (jid) => sock.removeProfilePicture(jid);
590
- const updateProfileName = async (name) => { if (!name) throw new Error("updateProfileName: name wajib"); return sock.updateProfileName(name); };
591
- const updateProfileStatus = async (status) => { if (typeof status !== "string") throw new Error("updateProfileStatus: harus string"); return sock.updateProfileStatus(status); };
959
+
960
+ const updateProfileName = async (name) => {
961
+ if (!name) throw new Error("updateProfileName: name wajib");
962
+ return sock.updateProfileName(name);
963
+ };
964
+
965
+ const updateProfileStatus = async (status) => {
966
+ if (typeof status !== "string") throw new Error("updateProfileStatus: harus string");
967
+ return sock.updateProfileStatus(status);
968
+ };
592
969
 
593
970
  // ─────────────────────────────────────────────────────────────────────────
594
971
  // DISAPPEARING
595
972
  // ─────────────────────────────────────────────────────────────────────────
596
973
  const sendDisappearingMessage = async (jid, content, expiration, options = {}) => {
597
- if (![0, 86400, 604800, 7776000].includes(expiration))
598
- throw new Error("sendDisappearingMessage: expiration harus 0/86400/604800/7776000");
974
+ const valid = [0, 86400, 604800, 7776000];
975
+ if (!valid.includes(expiration))
976
+ throw new Error(`sendDisappearingMessage: expiration harus salah satu dari: ${valid.join(", ")}`);
599
977
  return sock.sendMessage(jid, content, { ephemeralExpiration: expiration, ...options });
600
978
  };
601
979
 
@@ -605,12 +983,39 @@ const makeBusinessSocket = (config) => {
605
983
  const isOnWhatsApp = async (jidOrNumber) => {
606
984
  let jid = jidOrNumber;
607
985
  if (!jid.includes("@")) jid = jid.replace(/[^0-9]/g, "") + "@s.whatsapp.net";
608
- const result = await sock.onWhatsApp(jid);
609
- return (Array.isArray(result) ? result[0] : result) || { exists: false, jid };
986
+ try {
987
+ const result = await sock.onWhatsApp(jid);
988
+ return (Array.isArray(result) ? result[0] : result) || { exists: false, jid };
989
+ } catch {
990
+ return { exists: false, jid };
991
+ }
992
+ };
993
+
994
+ const rejectAllCalls = () => {
995
+ sock.ev.on("call", async (calls) => {
996
+ 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
+ }
1002
+ }
1003
+ });
610
1004
  };
611
- const rejectAllCalls = () => sock.ev.on("call", async ([call]) => { try { await sock.rejectCall(call.id, call.from); } catch { } });
612
- const getBusinessProfile = async (jid) => { try { return await sock.getBusinessProfile(_norm(jid)); } catch { return null; } };
613
- const fetchMessageHistory = async (jid, count = 25, oldestMsg) => { if (!oldestMsg) throw new Error("fetchMessageHistory: oldestMsg wajib"); return sock.fetchMessageHistory(count, oldestMsg.key, oldestMsg.messageTimestamp); };
1005
+
1006
+ const getBusinessProfile = async (jid) => {
1007
+ try {
1008
+ return await sock.getBusinessProfile(_norm(jid));
1009
+ } catch {
1010
+ return null;
1011
+ }
1012
+ };
1013
+
1014
+ const fetchMessageHistory = async (jid, count = 25, oldestMsg) => {
1015
+ if (!oldestMsg) throw new Error("fetchMessageHistory: oldestMsg wajib");
1016
+ return sock.fetchMessageHistory(count, oldestMsg.key, oldestMsg.messageTimestamp);
1017
+ };
1018
+
614
1019
  const presenceSubscribe = async (jid) => sock.presenceSubscribe(jid);
615
1020
  const updatePrivacyLastSeen = async (v) => sock.updateLastSeenPrivacy(v);
616
1021
  const updatePrivacyProfilePic = async (v) => sock.updateProfilePicturePrivacy(v);
@@ -634,7 +1039,14 @@ const makeBusinessSocket = (config) => {
634
1039
  const sendButtonsMessage = async (jid, text, buttons = [], footer = "", options = {}) => {
635
1040
  if (!buttons.length) throw new Error("sendButtonsMessage: min 1 tombol");
636
1041
  if (buttons.length > 3) throw new Error("sendButtonsMessage: maks 3 tombol");
637
- const msg = _gen(jid, { buttonsMessage: { contentText: text, footerText: footer, buttons: _mapButtons(buttons), headerType: 1 } });
1042
+ const msg = _gen(jid, {
1043
+ buttonsMessage: {
1044
+ contentText: text,
1045
+ footerText: footer,
1046
+ buttons: _mapButtons(buttons),
1047
+ headerType: 1
1048
+ }
1049
+ });
638
1050
  return _relay(jid, msg);
639
1051
  };
640
1052
 
@@ -643,8 +1055,11 @@ const makeBusinessSocket = (config) => {
643
1055
  if (!sections?.length) throw new Error("sendListMessage: sections wajib");
644
1056
  const msg = _gen(jid, {
645
1057
  listMessage: {
646
- title: title || "", description: text || "", footerText: footer || "",
647
- buttonText: buttonText || "Lihat", listType: 1,
1058
+ title: title || "",
1059
+ description: text || "",
1060
+ footerText: footer || "",
1061
+ buttonText: buttonText || "Lihat",
1062
+ listType: 1,
648
1063
  sections: sections.map(s => ({
649
1064
  title: s.title || "",
650
1065
  rows: (s.rows || []).map(r => ({
@@ -662,43 +1077,109 @@ const makeBusinessSocket = (config) => {
662
1077
  const { text, footer, templateButtons = [] } = cfg;
663
1078
  if (!templateButtons.length) throw new Error("sendTemplateMessage: templateButtons wajib");
664
1079
  const hydratedButtons = templateButtons.map((b, i) => {
665
- if (b.quickReply) return { index: b.index ?? i, quickReplyButton: { displayText: b.quickReply.displayText, id: b.quickReply.id } };
666
- if (b.urlButton) return { index: b.index ?? i, urlButton: { displayText: b.urlButton.displayText, url: b.urlButton.url } };
667
- if (b.callButton) return { index: b.index ?? i, callButton: { displayText: b.callButton.displayText, phoneNumber: b.callButton.phoneNumber } };
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
+ };
668
1092
  return b;
669
1093
  });
670
- const msg = _gen(jid, { templateMessage: { hydratedTemplate: { hydratedContentText: text || "", hydratedFooterText: footer || "", hydratedButtons } } });
1094
+ const msg = _gen(jid, {
1095
+ templateMessage: {
1096
+ hydratedTemplate: {
1097
+ hydratedContentText: text || "",
1098
+ hydratedFooterText: footer || "",
1099
+ hydratedButtons
1100
+ }
1101
+ }
1102
+ });
671
1103
  return _relay(jid, msg);
672
1104
  };
673
1105
 
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
+ // ─────────────────────────────────────────────────────────────────────────
674
1111
  const sendInteractiveMessage = async (jid, cfg = {}, options = {}) => {
675
1112
  const { body, footer, header, buttons, sections, nativeFlow } = cfg;
1113
+
1114
+ // ── Header ──────────────────────────────────────────────────────────
676
1115
  let headerContent = null;
677
1116
  if (header) {
678
1117
  const typeMap = { image: "image", video: "video", document: "document" };
679
1118
  if (typeMap[header.type]) {
680
1119
  const inner = await (0, Utils_1.generateWAMessageContent)(
681
- { [header.type]: header.content, ...(header.type === "document" ? { fileName: header.fileName } : {}) },
1120
+ {
1121
+ [header.type]: header.content,
1122
+ ...(header.type === "document" ? { fileName: header.fileName } : {})
1123
+ },
682
1124
  { upload: waUploadToServer }
683
1125
  );
684
1126
  const msgKey = `${header.type}Message`;
685
- headerContent = { [msgKey]: { ...inner[msgKey], ...(header.caption ? { caption: header.caption } : {}) } };
1127
+ headerContent = {
1128
+ [msgKey]: {
1129
+ ...inner[msgKey],
1130
+ ...(header.caption ? { caption: header.caption } : {})
1131
+ }
1132
+ };
1133
+ } else if (header.type === "text") {
1134
+ headerContent = {
1135
+ ephemeralMessage: {
1136
+ message: { extendedTextMessage: { text: header.content || "" } }
1137
+ }
1138
+ };
686
1139
  }
687
1140
  }
1141
+
1142
+ // ── Action ──────────────────────────────────────────────────────────
688
1143
  let action = null;
1144
+
1145
+ // Modern native flow buttons (single_select, cta_url, etc.)
689
1146
  if (buttons?.length) {
690
- action = { buttons: buttons.map(b => ({ buttonId: b.id, buttonText: { displayText: b.displayText }, type: 1 })) };
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
+ }
691
1162
  } else if (sections?.length) {
692
1163
  action = {
693
1164
  sections: sections.map(s => ({
694
1165
  title: s.title,
695
- rows: (s.rows || []).map(r => ({ rowId: r.id || r.rowId, title: r.title, description: r.description || "" })),
1166
+ rows: (s.rows || []).map(r => ({
1167
+ rowId: r.id || r.rowId,
1168
+ title: r.title,
1169
+ description: r.description || ""
1170
+ })),
696
1171
  })),
697
1172
  buttonText: cfg.listButtonText || "Pilih",
698
1173
  };
699
1174
  } else if (nativeFlow) {
700
- action = { nativeFlowMessage: { name: nativeFlow.name, paramsJson: nativeFlow.paramsJson || "{}" } };
1175
+ action = {
1176
+ nativeFlowMessage: {
1177
+ name: nativeFlow.name,
1178
+ paramsJson: _normalizeButtonParamsJson(nativeFlow.paramsJson || nativeFlow.params || {})
1179
+ }
1180
+ };
701
1181
  }
1182
+
702
1183
  const msg = _gen(jid, {
703
1184
  interactiveMessage: {
704
1185
  body: { text: body || "" },
@@ -712,74 +1193,376 @@ const makeBusinessSocket = (config) => {
712
1193
 
713
1194
  const sendHighlyStructuredMessage = async (jid, cfg = {}) => {
714
1195
  const { namespace, elementName, params = [] } = cfg;
715
- if (!namespace || !elementName) throw new Error("sendHighlyStructuredMessage: namespace dan elementName wajib");
1196
+ if (!namespace || !elementName)
1197
+ throw new Error("sendHighlyStructuredMessage: namespace dan elementName wajib");
716
1198
  const msg = _gen(jid, {
717
1199
  highlyStructuredMessage: {
718
- namespace, elementName,
1200
+ namespace,
1201
+ elementName,
719
1202
  params: params.map(p => ({ default: p })),
720
1203
  deterministicLottie: cfg.deterministicLottie || false,
721
- fallbackLg: "id", fallbackLc: "ID",
1204
+ fallbackLg: "id",
1205
+ fallbackLc: "ID",
722
1206
  },
723
1207
  });
724
1208
  return _relay(jid, msg);
725
1209
  };
726
1210
 
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;
1254
+
1255
+ if (!buttons.length) throw new Error("sendProductMessageWithButtons: min 1 button wajib");
1256
+
1257
+ // Build thumbnail upload if needed
1258
+ let thumbnailUrl = null;
1259
+ let thumbnailBuffer = null;
1260
+ if (thumbnail) {
1261
+ if (typeof thumbnail === "object" && thumbnail.url) {
1262
+ thumbnailUrl = thumbnail.url;
1263
+ } else {
1264
+ thumbnailBuffer = thumbnail;
1265
+ }
1266
+ }
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
+ };
1284
+ }
1285
+ }
1286
+
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
+ const msg = _gen(jid, {
1299
+ interactiveMessage: {
1300
+ body: { text: body || "" },
1301
+ footer: { text: footer || "" },
1302
+ ...(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
+ contextInfo: {
1316
+ externalAdReply: {
1317
+ title: title || "",
1318
+ body: body || "",
1319
+ mediaType: 1,
1320
+ ...(thumbnailUrl ? { thumbnailUrl } : {}),
1321
+ renderLargerThumbnail: true,
1322
+ showAdAttribution: false,
1323
+ }
1324
+ },
1325
+ action: {
1326
+ nativeFlowMessage: {
1327
+ buttons: normalizedBtns,
1328
+ }
1329
+ },
1330
+ },
1331
+ });
1332
+
1333
+ return _relay(jid, msg);
1334
+ };
1335
+
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
+ const sendProductMessage = async (jid, productIdOrCfg, catalogJidOrOptions, options = {}) => {
1355
+ // Detect new format: first arg after jid is a config object with buttons
1356
+ if (typeof productIdOrCfg === "object" && productIdOrCfg !== null) {
1357
+ return sendProductMessageWithButtons(jid, productIdOrCfg, catalogJidOrOptions || {});
1358
+ }
1359
+
1360
+ // Legacy: catalog lookup
1361
+ const productId = productIdOrCfg;
1362
+ const catalogJid = typeof catalogJidOrOptions === "string" ? catalogJidOrOptions : null;
1363
+ const bizJid = _norm(catalogJid || _me());
1364
+ const catalog = await getCatalog({ jid: bizJid });
1365
+ const product = catalog?.products?.find(p => p.id === productId);
1366
+ if (!product) throw new Error(`sendProductMessage: produk ${productId} tidak ditemukan`);
1367
+ const msg = _gen(jid, {
1368
+ productMessage: {
1369
+ 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 || "",
1379
+ },
1380
+ businessOwnerJid: bizJid,
1381
+ catalog: { catalogJid: bizJid },
1382
+ },
1383
+ });
1384
+ return _relay(jid, msg);
1385
+ };
1386
+
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
+
727
1410
  // ─── Media + Buttons ──────────────────────────────────────────────────────
728
1411
  const sendImageWithButtons = async (jid, image, caption, buttons = [], footer = "", options = {}) => {
729
1412
  if (!image) throw new Error("sendImageWithButtons: image wajib");
730
1413
  if (!buttons.length) throw new Error("sendImageWithButtons: buttons wajib");
731
1414
  const inner = await (0, Utils_1.generateWAMessageContent)({ image }, { upload: waUploadToServer });
732
- return _relay(jid, _gen(jid, { buttonsMessage: { imageMessage: inner.imageMessage, contentText: caption || "", footerText: footer, buttons: _mapButtons(buttons), headerType: 4 } }));
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
+ }));
733
1424
  };
1425
+
734
1426
  const sendVideoWithButtons = async (jid, video, caption, buttons = [], footer = "", options = {}) => {
735
1427
  if (!video) throw new Error("sendVideoWithButtons: video wajib");
736
1428
  if (!buttons.length) throw new Error("sendVideoWithButtons: buttons wajib");
737
1429
  const inner = await (0, Utils_1.generateWAMessageContent)({ video }, { upload: waUploadToServer });
738
- return _relay(jid, _gen(jid, { buttonsMessage: { videoMessage: inner.videoMessage, contentText: caption || "", footerText: footer, buttons: _mapButtons(buttons), headerType: 5 } }));
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
+ }));
739
1439
  };
1440
+
740
1441
  const sendDocumentWithButtons = async (jid, document, fileName, caption, buttons = [], footer = "", options = {}) => {
741
1442
  if (!document) throw new Error("sendDocumentWithButtons: document wajib");
742
1443
  if (!buttons.length) throw new Error("sendDocumentWithButtons: buttons wajib");
743
1444
  const inner = await (0, Utils_1.generateWAMessageContent)({ document, fileName }, { upload: waUploadToServer });
744
- return _relay(jid, _gen(jid, { buttonsMessage: { documentMessage: inner.documentMessage, contentText: caption || "", footerText: footer, buttons: _mapButtons(buttons), headerType: 6 } }));
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
+ }));
745
1454
  };
746
1455
 
747
- // ─── Newsletter ───────────────────────────────────────────────────────────
748
- const sendNewsletterMessage = async (newsletterJid, content, options = {}) => { if (!newsletterJid.endsWith("@newsletter")) throw new Error("sendNewsletterMessage: harus @newsletter JID"); return sock.sendMessage(newsletterJid, content, options); };
749
- const sendNewsletterReaction = async (newsletterJid, messageId, emoji) => {
750
- if (!newsletterJid.endsWith("@newsletter")) throw new Error("sendNewsletterReaction: harus @newsletter JID");
751
- return query({ tag: "iq", attrs: { to: newsletterJid, type: "set", xmlns: "w:newsletter" }, content: [{ tag: "reaction", attrs: { "message_id": messageId }, content: [{ tag: "text", attrs: {}, content: emoji }] }] });
752
- };
753
- const getNewsletterInfo = async (newsletterJid) => {
754
- if (!newsletterJid.endsWith("@newsletter")) throw new Error("getNewsletterInfo: harus @newsletter JID");
755
- return query({ tag: "iq", attrs: { to: newsletterJid, type: "get", xmlns: "w:newsletter" }, content: [{ tag: "metadata", attrs: {} }] });
756
- };
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);
757
1486
 
758
- // ─── Product ──────────────────────────────────────────────────────────────
759
- const sendProductMessage = async (jid, productId, catalogJid, options = {}) => {
760
- const bizJid = _norm(catalogJid || _me());
761
- const catalog = await getCatalog({ jid: bizJid });
762
- const product = catalog?.products?.find(p => p.id === productId);
763
- if (!product) throw new Error(`sendProductMessage: produk ${productId} tidak ditemukan`);
764
1487
  const msg = _gen(jid, {
765
- productMessage: {
766
- product: {
767
- productId: product.id, title: product.title,
768
- description: product.description || "", currencyCode: product.currency,
769
- priceAmount1000: product.price, retailerId: product.retailerId || "",
770
- url: product.url || "", productImageCount: product.images?.length || 0,
771
- firstImageId: product.images?.[0]?.id || "",
1488
+ interactiveMessage: {
1489
+ body: { text: body || "" },
1490
+ footer: { text: footer || "" },
1491
+ ...(headerContent ? { header: headerContent } : {}),
1492
+ action: {
1493
+ nativeFlowMessage: { buttons: normalizedBtns }
772
1494
  },
773
- businessOwnerJid: bizJid, catalog: { catalogJid: bizJid },
774
1495
  },
775
1496
  });
776
1497
  return _relay(jid, msg);
777
1498
  };
778
1499
 
1500
+ // ─── Newsletter ───────────────────────────────────────────────────────────
1501
+ const sendNewsletterMessage = async (newsletterJid, content, options = {}) => {
1502
+ if (!newsletterJid.endsWith("@newsletter"))
1503
+ throw new Error("sendNewsletterMessage: harus @newsletter JID");
1504
+ return _originalSendMessage(newsletterJid, content, options);
1505
+ };
1506
+
1507
+ const sendNewsletterReaction = async (newsletterJid, messageId, emoji) => {
1508
+ if (!newsletterJid.endsWith("@newsletter"))
1509
+ throw new Error("sendNewsletterReaction: harus @newsletter JID");
1510
+ 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
+ }]
1518
+ });
1519
+ };
1520
+
1521
+ 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
+ });
1529
+ };
1530
+
1531
+ 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);
1535
+ };
1536
+
1537
+ 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);
1541
+ };
1542
+
1543
+ const getNewsletterMetadata = async (type, key) => {
1544
+ return sock.newsletterMetadata(type, key).catch(() => null);
1545
+ };
1546
+
1547
+ const joinNewsletterByUrl = async (channelUrl) => {
1548
+ const inviteCode = _extractInviteCode(channelUrl);
1549
+ const meta = await sock.newsletterMetadata("invite", inviteCode).catch(() => null);
1550
+ if (!meta?.id) throw new Error(`joinNewsletterByUrl: channel tidak ditemukan: ${channelUrl}`);
1551
+ await sock.newsletterFollow(meta.id);
1552
+ return meta;
1553
+ };
1554
+
779
1555
  const sendLocationReply = async (jid, latitude, longitude, name, quotedMessage, options = {}) => {
780
- if (typeof latitude !== "number" || typeof longitude !== "number") throw new Error("sendLocationReply: lat/lng harus number");
1556
+ if (typeof latitude !== "number" || typeof longitude !== "number")
1557
+ throw new Error("sendLocationReply: lat/lng harus number");
781
1558
  if (!quotedMessage) throw new Error("sendLocationReply: quotedMessage wajib");
782
- return sock.sendMessage(jid, { location: { degreesLatitude: latitude, degreesLongitude: longitude, ...(name ? { name } : {}) } }, { quoted: quotedMessage, ...options });
1559
+ return _originalSendMessage(jid, {
1560
+ location: {
1561
+ degreesLatitude: latitude,
1562
+ degreesLongitude: longitude,
1563
+ ...(name ? { name } : {})
1564
+ }
1565
+ }, { quoted: quotedMessage, ...options });
783
1566
  };
784
1567
 
785
1568
  // ─────────────────────────────────────────────────────────────────────────
@@ -787,76 +1570,168 @@ const makeBusinessSocket = (config) => {
787
1570
  // ─────────────────────────────────────────────────────────────────────────
788
1571
  return {
789
1572
  ...sock,
1573
+
1574
+ // Override sendMessage to support productMessage+buttons transparently
1575
+ sendMessage: patchedSendMessage,
1576
+
790
1577
  logger: config.logger,
791
1578
 
792
1579
  // Catalog
793
- getCatalog, getCollections, getOrderDetails,
794
- productCreate, productDelete, productUpdate,
1580
+ getCatalog,
1581
+ getCollections,
1582
+ getOrderDetails,
1583
+ productCreate,
1584
+ productDelete,
1585
+ productUpdate,
795
1586
 
796
1587
  // Group
797
- groupTagAll, groupStatusV2, getGroupAdmins, isGroupAdmin,
798
- sendToAdminsOnly, bulkGroupAction, setGroupDisappearing,
799
- sendTagAll, sendGroupInvite, sendAdminInvite,
800
- updateGroupName, updateGroupDescription, updateGroupSetting,
801
- revokeGroupInvite, getGroupInviteLink, joinGroupViaLink, leaveGroup,
802
- getGroupParticipants, setGroupJoinApproval, getGroupJoinRequests,
803
- approveGroupJoinRequest, rejectGroupJoinRequest,
804
- setGroupMemberAddMode, updateGroupProfilePicture, sendMentionAll,
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,
805
1613
 
806
1614
  // Status
807
1615
  sendStatus,
808
1616
 
809
1617
  // Media
810
- sendImage, sendVideo, sendAudio, sendAudioPTT, sendVoiceNote,
811
- sendDocument, sendGIF, sendPTV, sendViewOnce, sendAlbum,
812
- sendLocation, sendLocationReply, sendLiveLocation,
813
- sendContact, sendPoll, sendEvent, sendScheduledCall,
814
- sendLinkPreview, sendDisappearingToggle,
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,
815
1637
 
816
1638
  // Sticker
817
- sendStickerFromUrl, sendStickerFromBuffer,
818
- sendStickerWithMetadata, sendStickerPack, sendStickerMessage,
819
-
820
- // Interactive
821
- sendButtonsMessage, sendListMessage, sendTemplateMessage,
822
- sendInteractiveMessage, sendHighlyStructuredMessage,
823
- sendNewsletterMessage, sendNewsletterReaction, getNewsletterInfo,
824
- sendProductMessage, sendImageWithButtons, sendVideoWithButtons,
1639
+ sendStickerFromUrl,
1640
+ sendStickerFromBuffer,
1641
+ sendStickerWithMetadata,
1642
+ sendStickerPack,
1643
+ sendStickerMessage,
1644
+
1645
+ // Interactive (full support)
1646
+ sendButtonsMessage,
1647
+ sendListMessage,
1648
+ sendTemplateMessage,
1649
+ sendInteractiveMessage,
1650
+ sendInteractiveWithMedia,
1651
+ sendHighlyStructuredMessage,
1652
+ sendProductMessage,
1653
+ sendProductMessageWithButtons,
1654
+ sendNewsletterMessage,
1655
+ sendNewsletterReaction,
1656
+ getNewsletterInfo,
1657
+ followNewsletter,
1658
+ unfollowNewsletter,
1659
+ getNewsletterMetadata,
1660
+ joinNewsletterByUrl,
1661
+ sendImageWithButtons,
1662
+ sendVideoWithButtons,
825
1663
  sendDocumentWithButtons,
826
1664
 
827
1665
  // Reply / quote
828
- sendReply, sendMediaReply, sendQuotedText,
829
- sendWithQuotedFake, sendWithMentionAndReply, forwardWithComment,
1666
+ sendReply,
1667
+ sendMediaReply,
1668
+ sendQuotedText,
1669
+ sendWithQuotedFake,
1670
+ sendWithMentionAndReply,
1671
+ forwardWithComment,
830
1672
 
831
1673
  // Mentions / typing
832
- sendTextWithMentions, sendTyping, sendWithTyping,
1674
+ sendTextWithMentions,
1675
+ sendTyping,
1676
+ sendWithTyping,
833
1677
 
834
1678
  // Broadcast
835
- broadcastMessage, broadcastToGroups, sendMultipleMessages,
1679
+ broadcastMessage,
1680
+ broadcastToGroups,
1681
+ sendMultipleMessages,
836
1682
 
837
1683
  // Message actions
838
- pinMessage, keepMessage, editMessage, deleteMessage,
839
- reactMessage, forwardMessage,
1684
+ pinMessage,
1685
+ keepMessage,
1686
+ editMessage,
1687
+ deleteMessage,
1688
+ reactMessage,
1689
+ forwardMessage,
840
1690
 
841
1691
  // Chat management
842
- muteJid, unmuteJid, archiveChat, unarchiveChat,
843
- pinChat, unpinChat, markAsRead, markAsUnread,
844
- blockUser, unblockUser, starMessage, unstarMessage,
845
- deleteChat, clearChat, sendSeen,
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,
846
1707
 
847
1708
  // Profile
848
- getProfilePicture, getUserStatus, updateProfilePicture,
849
- removeProfilePicture, updateProfileName, updateProfileStatus,
850
- getContactInfo, getBusinessProfile, fetchBlocklist, fetchAllGroups,
1709
+ getProfilePicture,
1710
+ getUserStatus,
1711
+ updateProfilePicture,
1712
+ removeProfilePicture,
1713
+ updateProfileName,
1714
+ updateProfileStatus,
1715
+ getContactInfo,
1716
+ getBusinessProfile,
1717
+ fetchBlocklist,
1718
+ fetchAllGroups,
851
1719
  fetchMessageHistory,
852
1720
 
853
1721
  // Privacy
854
- updatePrivacyLastSeen, updatePrivacyProfilePic, updatePrivacyStatus,
855
- updatePrivacyReadReceipts, updatePrivacyGroupsAdd, updatePrivacyOnline,
1722
+ updatePrivacyLastSeen,
1723
+ updatePrivacyProfilePic,
1724
+ updatePrivacyStatus,
1725
+ updatePrivacyReadReceipts,
1726
+ updatePrivacyGroupsAdd,
1727
+ updatePrivacyOnline,
856
1728
  setDefaultDisappearing,
857
1729
 
858
1730
  // Misc
859
- sendDisappearingMessage, isOnWhatsApp, presenceSubscribe, rejectAllCalls,
1731
+ sendDisappearingMessage,
1732
+ isOnWhatsApp,
1733
+ presenceSubscribe,
1734
+ rejectAllCalls,
860
1735
  };
861
1736
  };
862
1737