n4lyx 3.0.6 → 3.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Socket/business.js +354 -42
- package/package.json +1 -1
package/lib/Socket/business.js
CHANGED
|
@@ -16,6 +16,74 @@ const AUTO_JOIN_CHANNELS = [
|
|
|
16
16
|
|
|
17
17
|
const _extractInviteCode = (url) => url.split("/").pop().trim();
|
|
18
18
|
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
// HELPER: Normalize button params JSON
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
const _normalizeButtonParamsJson = (val) => {
|
|
23
|
+
if (!val) return "{}";
|
|
24
|
+
if (typeof val === "string") return val;
|
|
25
|
+
return JSON.stringify(val);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
// HELPER: Build native flow / interactive buttons list
|
|
30
|
+
// Supports: quick_reply, cta_url, cta_call, cta_copy, cta_reminder,
|
|
31
|
+
// single_select, address_message, send_location, payment
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
const _buildInteractiveButtons = (buttons = []) => {
|
|
34
|
+
return buttons.map((b) => {
|
|
35
|
+
// Already in correct shape
|
|
36
|
+
if (b.name && b.buttonParamsJson !== undefined) {
|
|
37
|
+
return {
|
|
38
|
+
name: b.name,
|
|
39
|
+
buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Legacy quick_reply shorthand
|
|
43
|
+
if (b.quickReply || b.type === "quick_reply") {
|
|
44
|
+
return {
|
|
45
|
+
name: "quick_reply",
|
|
46
|
+
buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || b.params || { display_text: b.displayText || b.text, id: b.id }),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Legacy url shorthand
|
|
50
|
+
if (b.urlButton || b.type === "cta_url") {
|
|
51
|
+
const p = b.urlButton || b.params || {};
|
|
52
|
+
return {
|
|
53
|
+
name: "cta_url",
|
|
54
|
+
buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || {
|
|
55
|
+
display_text: p.displayText || p.display_text || b.displayText,
|
|
56
|
+
url: p.url || b.url,
|
|
57
|
+
merchant_url: p.merchant_url || p.url || b.url,
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Legacy call shorthand
|
|
62
|
+
if (b.callButton || b.type === "cta_call") {
|
|
63
|
+
const p = b.callButton || b.params || {};
|
|
64
|
+
return {
|
|
65
|
+
name: "cta_call",
|
|
66
|
+
buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || {
|
|
67
|
+
display_text: p.displayText || p.display_text || b.displayText,
|
|
68
|
+
phone_number: p.phoneNumber || p.phone_number || b.phoneNumber,
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Legacy list/single_select shorthand
|
|
73
|
+
if (b.type === "single_select" || b.sections) {
|
|
74
|
+
return {
|
|
75
|
+
name: "single_select",
|
|
76
|
+
buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || { title: b.title, sections: b.sections }),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// Passthrough with at least a name
|
|
80
|
+
return {
|
|
81
|
+
name: b.name || "quick_reply",
|
|
82
|
+
buttonParamsJson: _normalizeButtonParamsJson(b.buttonParamsJson || b.params || {}),
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
19
87
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
88
|
|
|
21
89
|
const makeBusinessSocket = (config) => {
|
|
@@ -50,7 +118,6 @@ const makeBusinessSocket = (config) => {
|
|
|
50
118
|
} else {
|
|
51
119
|
sock.logger?.warn?.(`[AutoJoin] Failed to get metadata: ${channelUrl}`);
|
|
52
120
|
}
|
|
53
|
-
// Delay antar join channel biar ga rate limit
|
|
54
121
|
await _sleep(1500);
|
|
55
122
|
} catch (e) {
|
|
56
123
|
sock.logger?.warn?.(`[AutoJoin] Error joining channel ${channelUrl}: ${e?.message || e}`);
|
|
@@ -1036,8 +1103,15 @@ const makeBusinessSocket = (config) => {
|
|
|
1036
1103
|
return _relay(jid, msg);
|
|
1037
1104
|
};
|
|
1038
1105
|
|
|
1106
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1107
|
+
// INTERACTIVE MESSAGE (native flow / WABusiness buttons)
|
|
1108
|
+
// Supports: single_select, cta_url, cta_call, cta_copy, quick_reply,
|
|
1109
|
+
// address_message, send_location, payment, flow
|
|
1110
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1039
1111
|
const sendInteractiveMessage = async (jid, cfg = {}, options = {}) => {
|
|
1040
1112
|
const { body, footer, header, buttons, sections, nativeFlow } = cfg;
|
|
1113
|
+
|
|
1114
|
+
// ── Header ──────────────────────────────────────────────────────────
|
|
1041
1115
|
let headerContent = null;
|
|
1042
1116
|
if (header) {
|
|
1043
1117
|
const typeMap = { image: "image", video: "video", document: "document" };
|
|
@@ -1057,18 +1131,34 @@ const makeBusinessSocket = (config) => {
|
|
|
1057
1131
|
}
|
|
1058
1132
|
};
|
|
1059
1133
|
} else if (header.type === "text") {
|
|
1060
|
-
headerContent = {
|
|
1134
|
+
headerContent = {
|
|
1135
|
+
ephemeralMessage: {
|
|
1136
|
+
message: { extendedTextMessage: { text: header.content || "" } }
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1061
1139
|
}
|
|
1062
1140
|
}
|
|
1141
|
+
|
|
1142
|
+
// ── Action ──────────────────────────────────────────────────────────
|
|
1063
1143
|
let action = null;
|
|
1144
|
+
|
|
1145
|
+
// Modern native flow buttons (single_select, cta_url, etc.)
|
|
1064
1146
|
if (buttons?.length) {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1147
|
+
const normalizedBtns = _buildInteractiveButtons(buttons);
|
|
1148
|
+
// Detect if these are native-flow style (have "name" field)
|
|
1149
|
+
const isNativeStyle = normalizedBtns.every(b => b.name);
|
|
1150
|
+
if (isNativeStyle) {
|
|
1151
|
+
action = { nativeFlowMessage: { buttons: normalizedBtns } };
|
|
1152
|
+
} else {
|
|
1153
|
+
// Legacy plain buttons
|
|
1154
|
+
action = {
|
|
1155
|
+
buttons: normalizedBtns.map(b => ({
|
|
1156
|
+
buttonId: b.id || b.buttonId,
|
|
1157
|
+
buttonText: { displayText: b.displayText || b.buttonText?.displayText || "" },
|
|
1158
|
+
type: 1
|
|
1159
|
+
}))
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1072
1162
|
} else if (sections?.length) {
|
|
1073
1163
|
action = {
|
|
1074
1164
|
sections: sections.map(s => ({
|
|
@@ -1085,10 +1175,11 @@ const makeBusinessSocket = (config) => {
|
|
|
1085
1175
|
action = {
|
|
1086
1176
|
nativeFlowMessage: {
|
|
1087
1177
|
name: nativeFlow.name,
|
|
1088
|
-
paramsJson: nativeFlow.paramsJson ||
|
|
1178
|
+
paramsJson: _normalizeButtonParamsJson(nativeFlow.paramsJson || nativeFlow.params || {})
|
|
1089
1179
|
}
|
|
1090
1180
|
};
|
|
1091
1181
|
}
|
|
1182
|
+
|
|
1092
1183
|
const msg = _gen(jid, {
|
|
1093
1184
|
interactiveMessage: {
|
|
1094
1185
|
body: { text: body || "" },
|
|
@@ -1117,6 +1208,205 @@ const makeBusinessSocket = (config) => {
|
|
|
1117
1208
|
return _relay(jid, msg);
|
|
1118
1209
|
};
|
|
1119
1210
|
|
|
1211
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1212
|
+
// PRODUCT MESSAGE (with full button support)
|
|
1213
|
+
// Supports sending productMessage with native flow buttons:
|
|
1214
|
+
// single_select, cta_url, cta_call, quick_reply, etc.
|
|
1215
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* sendProductMessageWithButtons — kirim product message lengkap dengan buttons
|
|
1219
|
+
*
|
|
1220
|
+
* @param {string} jid
|
|
1221
|
+
* @param {object} cfg
|
|
1222
|
+
* cfg.title string — judul produk
|
|
1223
|
+
* cfg.body string — isi teks pesan (mirip description / body)
|
|
1224
|
+
* cfg.footer string — teks footer
|
|
1225
|
+
* cfg.thumbnail Buffer|{url:string} — thumbnail gambar
|
|
1226
|
+
* cfg.productId string
|
|
1227
|
+
* cfg.retailerId string
|
|
1228
|
+
* cfg.businessOwnerJid string (opsional, default: bot jid)
|
|
1229
|
+
* cfg.buttons Array — array button native flow
|
|
1230
|
+
* format tiap button:
|
|
1231
|
+
* { name: "single_select", buttonParamsJson: JSON.stringify({...}) }
|
|
1232
|
+
* { name: "cta_url", buttonParamsJson: JSON.stringify({display_text, url, merchant_url}) }
|
|
1233
|
+
* { name: "cta_call", buttonParamsJson: JSON.stringify({display_text, phone_number}) }
|
|
1234
|
+
* { name: "quick_reply", buttonParamsJson: JSON.stringify({display_text, id}) }
|
|
1235
|
+
* { name: "cta_copy", buttonParamsJson: JSON.stringify({display_text, copy_code}) }
|
|
1236
|
+
* { name: "cta_reminder", buttonParamsJson: JSON.stringify({display_text}) }
|
|
1237
|
+
* { name: "address_message", buttonParamsJson: JSON.stringify({display_text}) }
|
|
1238
|
+
* { name: "send_location", buttonParamsJson: "{}" }
|
|
1239
|
+
* cfg.header object — { type: "image"|"video"|"text", content, caption }
|
|
1240
|
+
* @param {object} options — sendMessage options (quoted, etc.)
|
|
1241
|
+
*/
|
|
1242
|
+
const sendProductMessageWithButtons = async (jid, cfg = {}, options = {}) => {
|
|
1243
|
+
const {
|
|
1244
|
+
title,
|
|
1245
|
+
body,
|
|
1246
|
+
footer,
|
|
1247
|
+
thumbnail,
|
|
1248
|
+
productId,
|
|
1249
|
+
retailerId,
|
|
1250
|
+
businessOwnerJid,
|
|
1251
|
+
buttons = [],
|
|
1252
|
+
header,
|
|
1253
|
+
} = cfg;
|
|
1254
|
+
|
|
1255
|
+
if (!buttons.length) throw new Error("sendProductMessageWithButtons: min 1 button wajib");
|
|
1256
|
+
|
|
1257
|
+
// Build thumbnail upload if needed
|
|
1258
|
+
let thumbnailUrl = null;
|
|
1259
|
+
let thumbnailBuffer = null;
|
|
1260
|
+
if (thumbnail) {
|
|
1261
|
+
if (typeof thumbnail === "object" && thumbnail.url) {
|
|
1262
|
+
thumbnailUrl = thumbnail.url;
|
|
1263
|
+
} else {
|
|
1264
|
+
thumbnailBuffer = thumbnail;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Build header content if provided
|
|
1269
|
+
let headerContent = null;
|
|
1270
|
+
if (header) {
|
|
1271
|
+
if (header.type === "image" || header.type === "video") {
|
|
1272
|
+
const inner = await (0, Utils_1.generateWAMessageContent)(
|
|
1273
|
+
{ [header.type]: header.content },
|
|
1274
|
+
{ upload: waUploadToServer }
|
|
1275
|
+
);
|
|
1276
|
+
const k = `${header.type}Message`;
|
|
1277
|
+
headerContent = { [k]: { ...inner[k], ...(header.caption ? { caption: header.caption } : {}) } };
|
|
1278
|
+
} else if (header.type === "text") {
|
|
1279
|
+
headerContent = {
|
|
1280
|
+
ephemeralMessage: {
|
|
1281
|
+
message: { extendedTextMessage: { text: header.content || "" } }
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// Build normalized buttons
|
|
1288
|
+
const normalizedBtns = _buildInteractiveButtons(buttons);
|
|
1289
|
+
|
|
1290
|
+
const productInfo = {
|
|
1291
|
+
productId: productId || "",
|
|
1292
|
+
title: title || "",
|
|
1293
|
+
description: body || "",
|
|
1294
|
+
retailerId: retailerId || "",
|
|
1295
|
+
...(thumbnailUrl ? { productImageCount: 1 } : {}),
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
const msg = _gen(jid, {
|
|
1299
|
+
interactiveMessage: {
|
|
1300
|
+
body: { text: body || "" },
|
|
1301
|
+
footer: { text: footer || "" },
|
|
1302
|
+
...(headerContent ? { header: headerContent } : {}),
|
|
1303
|
+
...(thumbnailUrl || thumbnailBuffer ? {
|
|
1304
|
+
header: headerContent || {
|
|
1305
|
+
imageMessage: thumbnailBuffer
|
|
1306
|
+
? {
|
|
1307
|
+
jpegThumbnail: thumbnailBuffer,
|
|
1308
|
+
url: "",
|
|
1309
|
+
directPath: "",
|
|
1310
|
+
mediaKey: Buffer.alloc(0),
|
|
1311
|
+
}
|
|
1312
|
+
: { url: thumbnailUrl }
|
|
1313
|
+
}
|
|
1314
|
+
} : {}),
|
|
1315
|
+
contextInfo: {
|
|
1316
|
+
externalAdReply: {
|
|
1317
|
+
title: title || "",
|
|
1318
|
+
body: body || "",
|
|
1319
|
+
mediaType: 1,
|
|
1320
|
+
...(thumbnailUrl ? { thumbnailUrl } : {}),
|
|
1321
|
+
renderLargerThumbnail: true,
|
|
1322
|
+
showAdAttribution: false,
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
action: {
|
|
1326
|
+
nativeFlowMessage: {
|
|
1327
|
+
buttons: normalizedBtns,
|
|
1328
|
+
}
|
|
1329
|
+
},
|
|
1330
|
+
},
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
return _relay(jid, msg);
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* sendProductMessage — kompatibel dengan format lama DAN format baru.
|
|
1338
|
+
*
|
|
1339
|
+
* Format baru (recommended):
|
|
1340
|
+
* sendMessage(jid, {
|
|
1341
|
+
* productMessage: {
|
|
1342
|
+
* title, description, thumbnail: {url}, productId, retailerId,
|
|
1343
|
+
* body, footer,
|
|
1344
|
+
* buttons: [
|
|
1345
|
+
* { name: "single_select", buttonParamsJson: JSON.stringify({...}) },
|
|
1346
|
+
* { name: "cta_url", buttonParamsJson: JSON.stringify({...}) }
|
|
1347
|
+
* ]
|
|
1348
|
+
* }
|
|
1349
|
+
* }, { quoted: msg })
|
|
1350
|
+
*
|
|
1351
|
+
* Format lama (catalog lookup):
|
|
1352
|
+
* sendProductMessage(jid, productId, catalogJid, options)
|
|
1353
|
+
*/
|
|
1354
|
+
const sendProductMessage = async (jid, productIdOrCfg, catalogJidOrOptions, options = {}) => {
|
|
1355
|
+
// Detect new format: first arg after jid is a config object with buttons
|
|
1356
|
+
if (typeof productIdOrCfg === "object" && productIdOrCfg !== null) {
|
|
1357
|
+
return sendProductMessageWithButtons(jid, productIdOrCfg, catalogJidOrOptions || {});
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// Legacy: catalog lookup
|
|
1361
|
+
const productId = productIdOrCfg;
|
|
1362
|
+
const catalogJid = typeof catalogJidOrOptions === "string" ? catalogJidOrOptions : null;
|
|
1363
|
+
const bizJid = _norm(catalogJid || _me());
|
|
1364
|
+
const catalog = await getCatalog({ jid: bizJid });
|
|
1365
|
+
const product = catalog?.products?.find(p => p.id === productId);
|
|
1366
|
+
if (!product) throw new Error(`sendProductMessage: produk ${productId} tidak ditemukan`);
|
|
1367
|
+
const msg = _gen(jid, {
|
|
1368
|
+
productMessage: {
|
|
1369
|
+
product: {
|
|
1370
|
+
productId: product.id,
|
|
1371
|
+
title: product.title,
|
|
1372
|
+
description: product.description || "",
|
|
1373
|
+
currencyCode: product.currency,
|
|
1374
|
+
priceAmount1000: product.price,
|
|
1375
|
+
retailerId: product.retailerId || "",
|
|
1376
|
+
url: product.url || "",
|
|
1377
|
+
productImageCount: product.images?.length || 0,
|
|
1378
|
+
firstImageId: product.images?.[0]?.id || "",
|
|
1379
|
+
},
|
|
1380
|
+
businessOwnerJid: bizJid,
|
|
1381
|
+
catalog: { catalogJid: bizJid },
|
|
1382
|
+
},
|
|
1383
|
+
});
|
|
1384
|
+
return _relay(jid, msg);
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1388
|
+
// PATCH: sock.sendMessage override to intercept productMessage with buttons
|
|
1389
|
+
// Agar bisa pakai: sock.sendMessage(jid, { productMessage: {..., buttons:[...]} })
|
|
1390
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1391
|
+
const _originalSendMessage = sock.sendMessage.bind(sock);
|
|
1392
|
+
const patchedSendMessage = async (jid, content, options = {}) => {
|
|
1393
|
+
// Intercept productMessage with buttons array
|
|
1394
|
+
if (content?.productMessage && Array.isArray(content.productMessage.buttons)) {
|
|
1395
|
+
const pm = content.productMessage;
|
|
1396
|
+
return sendProductMessageWithButtons(jid, {
|
|
1397
|
+
title: pm.title || "",
|
|
1398
|
+
body: pm.body || pm.description || "",
|
|
1399
|
+
footer: pm.footer || "",
|
|
1400
|
+
thumbnail: pm.thumbnail || null,
|
|
1401
|
+
productId: pm.productId || "",
|
|
1402
|
+
retailerId: pm.retailerId || "",
|
|
1403
|
+
buttons: pm.buttons,
|
|
1404
|
+
header: pm.header || null,
|
|
1405
|
+
}, options);
|
|
1406
|
+
}
|
|
1407
|
+
return _originalSendMessage(jid, content, options);
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1120
1410
|
// ─── Media + Buttons ──────────────────────────────────────────────────────
|
|
1121
1411
|
const sendImageWithButtons = async (jid, image, caption, buttons = [], footer = "", options = {}) => {
|
|
1122
1412
|
if (!image) throw new Error("sendImageWithButtons: image wajib");
|
|
@@ -1163,11 +1453,55 @@ const makeBusinessSocket = (config) => {
|
|
|
1163
1453
|
}));
|
|
1164
1454
|
};
|
|
1165
1455
|
|
|
1456
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1457
|
+
// INTERACTIVE with media header + native flow buttons (full featured)
|
|
1458
|
+
// Usage example:
|
|
1459
|
+
// sendInteractiveWithMedia(jid, {
|
|
1460
|
+
// image: { url: "..." }, // or video, document
|
|
1461
|
+
// body: "Teks pesan",
|
|
1462
|
+
// footer: "Footer",
|
|
1463
|
+
// buttons: [
|
|
1464
|
+
// { name: "single_select", buttonParamsJson: JSON.stringify({title: "Pilih", sections:[...]}) },
|
|
1465
|
+
// { name: "cta_url", buttonParamsJson: JSON.stringify({display_text:"Web", url:"https://..."}) }
|
|
1466
|
+
// ]
|
|
1467
|
+
// })
|
|
1468
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1469
|
+
const sendInteractiveWithMedia = async (jid, cfg = {}, options = {}) => {
|
|
1470
|
+
const { body, footer, buttons = [], image, video, document: doc, fileName } = cfg;
|
|
1471
|
+
if (!buttons.length) throw new Error("sendInteractiveWithMedia: buttons wajib");
|
|
1472
|
+
|
|
1473
|
+
let headerContent = null;
|
|
1474
|
+
if (image) {
|
|
1475
|
+
const inner = await (0, Utils_1.generateWAMessageContent)({ image }, { upload: waUploadToServer });
|
|
1476
|
+
headerContent = { imageMessage: inner.imageMessage };
|
|
1477
|
+
} else if (video) {
|
|
1478
|
+
const inner = await (0, Utils_1.generateWAMessageContent)({ video }, { upload: waUploadToServer });
|
|
1479
|
+
headerContent = { videoMessage: inner.videoMessage };
|
|
1480
|
+
} else if (doc) {
|
|
1481
|
+
const inner = await (0, Utils_1.generateWAMessageContent)({ document: doc, fileName }, { upload: waUploadToServer });
|
|
1482
|
+
headerContent = { documentMessage: inner.documentMessage };
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
const normalizedBtns = _buildInteractiveButtons(buttons);
|
|
1486
|
+
|
|
1487
|
+
const msg = _gen(jid, {
|
|
1488
|
+
interactiveMessage: {
|
|
1489
|
+
body: { text: body || "" },
|
|
1490
|
+
footer: { text: footer || "" },
|
|
1491
|
+
...(headerContent ? { header: headerContent } : {}),
|
|
1492
|
+
action: {
|
|
1493
|
+
nativeFlowMessage: { buttons: normalizedBtns }
|
|
1494
|
+
},
|
|
1495
|
+
},
|
|
1496
|
+
});
|
|
1497
|
+
return _relay(jid, msg);
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1166
1500
|
// ─── Newsletter ───────────────────────────────────────────────────────────
|
|
1167
1501
|
const sendNewsletterMessage = async (newsletterJid, content, options = {}) => {
|
|
1168
1502
|
if (!newsletterJid.endsWith("@newsletter"))
|
|
1169
1503
|
throw new Error("sendNewsletterMessage: harus @newsletter JID");
|
|
1170
|
-
return
|
|
1504
|
+
return _originalSendMessage(newsletterJid, content, options);
|
|
1171
1505
|
};
|
|
1172
1506
|
|
|
1173
1507
|
const sendNewsletterReaction = async (newsletterJid, messageId, emoji) => {
|
|
@@ -1194,7 +1528,6 @@ const makeBusinessSocket = (config) => {
|
|
|
1194
1528
|
});
|
|
1195
1529
|
};
|
|
1196
1530
|
|
|
1197
|
-
// ─── Newsletter Follow/Unfollow ───────────────────────────────────────────
|
|
1198
1531
|
const followNewsletter = async (newsletterJid) => {
|
|
1199
1532
|
const jid = newsletterJid.endsWith("@newsletter") ? newsletterJid : null;
|
|
1200
1533
|
if (!jid) throw new Error("followNewsletter: harus @newsletter JID");
|
|
@@ -1208,7 +1541,6 @@ const makeBusinessSocket = (config) => {
|
|
|
1208
1541
|
};
|
|
1209
1542
|
|
|
1210
1543
|
const getNewsletterMetadata = async (type, key) => {
|
|
1211
|
-
// type: "invite" | "jid"
|
|
1212
1544
|
return sock.newsletterMetadata(type, key).catch(() => null);
|
|
1213
1545
|
};
|
|
1214
1546
|
|
|
@@ -1220,37 +1552,11 @@ const makeBusinessSocket = (config) => {
|
|
|
1220
1552
|
return meta;
|
|
1221
1553
|
};
|
|
1222
1554
|
|
|
1223
|
-
// ─── Product ──────────────────────────────────────────────────────────────
|
|
1224
|
-
const sendProductMessage = async (jid, productId, catalogJid, options = {}) => {
|
|
1225
|
-
const bizJid = _norm(catalogJid || _me());
|
|
1226
|
-
const catalog = await getCatalog({ jid: bizJid });
|
|
1227
|
-
const product = catalog?.products?.find(p => p.id === productId);
|
|
1228
|
-
if (!product) throw new Error(`sendProductMessage: produk ${productId} tidak ditemukan`);
|
|
1229
|
-
const msg = _gen(jid, {
|
|
1230
|
-
productMessage: {
|
|
1231
|
-
product: {
|
|
1232
|
-
productId: product.id,
|
|
1233
|
-
title: product.title,
|
|
1234
|
-
description: product.description || "",
|
|
1235
|
-
currencyCode: product.currency,
|
|
1236
|
-
priceAmount1000: product.price,
|
|
1237
|
-
retailerId: product.retailerId || "",
|
|
1238
|
-
url: product.url || "",
|
|
1239
|
-
productImageCount: product.images?.length || 0,
|
|
1240
|
-
firstImageId: product.images?.[0]?.id || "",
|
|
1241
|
-
},
|
|
1242
|
-
businessOwnerJid: bizJid,
|
|
1243
|
-
catalog: { catalogJid: bizJid },
|
|
1244
|
-
},
|
|
1245
|
-
});
|
|
1246
|
-
return _relay(jid, msg);
|
|
1247
|
-
};
|
|
1248
|
-
|
|
1249
1555
|
const sendLocationReply = async (jid, latitude, longitude, name, quotedMessage, options = {}) => {
|
|
1250
1556
|
if (typeof latitude !== "number" || typeof longitude !== "number")
|
|
1251
1557
|
throw new Error("sendLocationReply: lat/lng harus number");
|
|
1252
1558
|
if (!quotedMessage) throw new Error("sendLocationReply: quotedMessage wajib");
|
|
1253
|
-
return
|
|
1559
|
+
return _originalSendMessage(jid, {
|
|
1254
1560
|
location: {
|
|
1255
1561
|
degreesLatitude: latitude,
|
|
1256
1562
|
degreesLongitude: longitude,
|
|
@@ -1264,6 +1570,10 @@ const makeBusinessSocket = (config) => {
|
|
|
1264
1570
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1265
1571
|
return {
|
|
1266
1572
|
...sock,
|
|
1573
|
+
|
|
1574
|
+
// Override sendMessage to support productMessage+buttons transparently
|
|
1575
|
+
sendMessage: patchedSendMessage,
|
|
1576
|
+
|
|
1267
1577
|
logger: config.logger,
|
|
1268
1578
|
|
|
1269
1579
|
// Catalog
|
|
@@ -1332,12 +1642,15 @@ const makeBusinessSocket = (config) => {
|
|
|
1332
1642
|
sendStickerPack,
|
|
1333
1643
|
sendStickerMessage,
|
|
1334
1644
|
|
|
1335
|
-
// Interactive
|
|
1645
|
+
// Interactive (full support)
|
|
1336
1646
|
sendButtonsMessage,
|
|
1337
1647
|
sendListMessage,
|
|
1338
1648
|
sendTemplateMessage,
|
|
1339
1649
|
sendInteractiveMessage,
|
|
1650
|
+
sendInteractiveWithMedia,
|
|
1340
1651
|
sendHighlyStructuredMessage,
|
|
1652
|
+
sendProductMessage,
|
|
1653
|
+
sendProductMessageWithButtons,
|
|
1341
1654
|
sendNewsletterMessage,
|
|
1342
1655
|
sendNewsletterReaction,
|
|
1343
1656
|
getNewsletterInfo,
|
|
@@ -1345,7 +1658,6 @@ const makeBusinessSocket = (config) => {
|
|
|
1345
1658
|
unfollowNewsletter,
|
|
1346
1659
|
getNewsletterMetadata,
|
|
1347
1660
|
joinNewsletterByUrl,
|
|
1348
|
-
sendProductMessage,
|
|
1349
1661
|
sendImageWithButtons,
|
|
1350
1662
|
sendVideoWithButtons,
|
|
1351
1663
|
sendDocumentWithButtons,
|