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.
- package/lib/Socket/business.js +1067 -192
- package/package.json +1 -1
package/lib/Socket/business.js
CHANGED
|
@@ -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
|
|
13
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
sock.
|
|
40
|
-
|
|
41
|
-
|
|
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":
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
238
|
-
const
|
|
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
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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, {
|
|
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)
|
|
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, {
|
|
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, {
|
|
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) => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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")
|
|
360
|
-
|
|
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")
|
|
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,
|
|
367
|
-
|
|
565
|
+
degreesLatitude: latitude,
|
|
566
|
+
degreesLongitude: longitude,
|
|
567
|
+
accuracyInMeters,
|
|
568
|
+
speedInMps: 0,
|
|
368
569
|
degreesClockwiseFromMagneticNorth: 0,
|
|
369
|
-
sequenceNumber: 1,
|
|
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 = [
|
|
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}`,
|
|
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) {
|
|
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)
|
|
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)
|
|
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
|
|
633
|
+
const delayMs = options.delayMs ?? 500;
|
|
415
634
|
const results = [];
|
|
416
635
|
for (const jid of uniqueJids) {
|
|
417
|
-
try {
|
|
418
|
-
|
|
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)
|
|
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 {
|
|
432
|
-
|
|
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
|
-
|
|
454
|
-
const
|
|
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,
|
|
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)
|
|
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
|
|
711
|
+
const delayMs = options.delayMs ?? 300;
|
|
470
712
|
const results = [];
|
|
471
713
|
for (const sticker of stickers) {
|
|
472
|
-
try {
|
|
473
|
-
|
|
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, {
|
|
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, {
|
|
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
|
-
|
|
492
|
-
const sendVideo = async (jid, video, 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 = {}) => {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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([
|
|
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,
|
|
825
|
+
groupJid,
|
|
826
|
+
inviteCode: code,
|
|
533
827
|
inviteExpiration: Math.floor(Date.now() / 1000) + 259200,
|
|
534
|
-
groupName: meta.subject,
|
|
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([
|
|
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,
|
|
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) =>
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const
|
|
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
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
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
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const
|
|
572
|
-
|
|
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) => {
|
|
578
|
-
|
|
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([
|
|
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
|
-
|
|
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
|
-
|
|
591
|
-
const
|
|
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
|
-
|
|
598
|
-
|
|
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
|
-
|
|
609
|
-
|
|
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
|
-
|
|
612
|
-
const getBusinessProfile = async (jid) => {
|
|
613
|
-
|
|
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, {
|
|
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 || "",
|
|
647
|
-
|
|
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 {
|
|
666
|
-
|
|
667
|
-
|
|
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, {
|
|
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
|
-
{
|
|
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 = {
|
|
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
|
-
|
|
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 => ({
|
|
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 = {
|
|
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)
|
|
1196
|
+
if (!namespace || !elementName)
|
|
1197
|
+
throw new Error("sendHighlyStructuredMessage: namespace dan elementName wajib");
|
|
716
1198
|
const msg = _gen(jid, {
|
|
717
1199
|
highlyStructuredMessage: {
|
|
718
|
-
namespace,
|
|
1200
|
+
namespace,
|
|
1201
|
+
elementName,
|
|
719
1202
|
params: params.map(p => ({ default: p })),
|
|
720
1203
|
deterministicLottie: cfg.deterministicLottie || false,
|
|
721
|
-
fallbackLg: "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, {
|
|
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, {
|
|
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, {
|
|
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
|
-
//
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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")
|
|
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
|
|
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,
|
|
794
|
-
|
|
1580
|
+
getCatalog,
|
|
1581
|
+
getCollections,
|
|
1582
|
+
getOrderDetails,
|
|
1583
|
+
productCreate,
|
|
1584
|
+
productDelete,
|
|
1585
|
+
productUpdate,
|
|
795
1586
|
|
|
796
1587
|
// Group
|
|
797
|
-
groupTagAll,
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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,
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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,
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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,
|
|
829
|
-
|
|
1666
|
+
sendReply,
|
|
1667
|
+
sendMediaReply,
|
|
1668
|
+
sendQuotedText,
|
|
1669
|
+
sendWithQuotedFake,
|
|
1670
|
+
sendWithMentionAndReply,
|
|
1671
|
+
forwardWithComment,
|
|
830
1672
|
|
|
831
1673
|
// Mentions / typing
|
|
832
|
-
sendTextWithMentions,
|
|
1674
|
+
sendTextWithMentions,
|
|
1675
|
+
sendTyping,
|
|
1676
|
+
sendWithTyping,
|
|
833
1677
|
|
|
834
1678
|
// Broadcast
|
|
835
|
-
broadcastMessage,
|
|
1679
|
+
broadcastMessage,
|
|
1680
|
+
broadcastToGroups,
|
|
1681
|
+
sendMultipleMessages,
|
|
836
1682
|
|
|
837
1683
|
// Message actions
|
|
838
|
-
pinMessage,
|
|
839
|
-
|
|
1684
|
+
pinMessage,
|
|
1685
|
+
keepMessage,
|
|
1686
|
+
editMessage,
|
|
1687
|
+
deleteMessage,
|
|
1688
|
+
reactMessage,
|
|
1689
|
+
forwardMessage,
|
|
840
1690
|
|
|
841
1691
|
// Chat management
|
|
842
|
-
muteJid,
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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,
|
|
849
|
-
|
|
850
|
-
|
|
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,
|
|
855
|
-
|
|
1722
|
+
updatePrivacyLastSeen,
|
|
1723
|
+
updatePrivacyProfilePic,
|
|
1724
|
+
updatePrivacyStatus,
|
|
1725
|
+
updatePrivacyReadReceipts,
|
|
1726
|
+
updatePrivacyGroupsAdd,
|
|
1727
|
+
updatePrivacyOnline,
|
|
856
1728
|
setDefaultDisappearing,
|
|
857
1729
|
|
|
858
1730
|
// Misc
|
|
859
|
-
sendDisappearingMessage,
|
|
1731
|
+
sendDisappearingMessage,
|
|
1732
|
+
isOnWhatsApp,
|
|
1733
|
+
presenceSubscribe,
|
|
1734
|
+
rejectAllCalls,
|
|
860
1735
|
};
|
|
861
1736
|
};
|
|
862
1737
|
|