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