@ryuu-reinzz/haruka-lib 2.3.0 → 2.4.0

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/main/socket.js CHANGED
@@ -2,8 +2,12 @@ import crypto from "crypto";
2
2
  import fetch from "node-fetch";
3
3
  import fs from "fs";
4
4
  import path from "path";
5
- import { fileURLToPath } from 'url';
6
- import { dirname } from 'path';
5
+ import {
6
+ fileURLToPath
7
+ } from 'url';
8
+ import {
9
+ dirname
10
+ } from 'path';
7
11
  import Sticker from './sticker-engine/index.js';
8
12
  const __filename = fileURLToPath(import.meta.url);
9
13
  const __dirname = dirname(__filename);
@@ -13,152 +17,158 @@ const __dirname = dirname(__filename);
13
17
  */
14
18
 
15
19
  export default function addProperty(socket, store, smsg, baileys) {
16
- const {
17
- proto,
18
- generateWAMessageFromContent,
19
- jidDecode,
20
- downloadContentFromMessage,
21
- prepareWAMessageMedia,
22
- generateMessageID,
23
- generateWAMessage
24
- } = baileys;
25
-
26
- Object.assign(socket, {
27
- sendCard: async (jid, options = {}) => {
28
- const {
29
- text = "",
30
- footer = "",
31
- cards = [],
32
- quoted = null,
33
- sender = jid
34
- } = options
35
-
36
- if (!cards.length) throw new Error("cards cannot be empty")
37
-
38
- let carouselCards = []
39
- const getImageMedia = async (image) => {
40
- if (!image) throw new Error("Image cannot be empty")
41
-
42
- if (typeof image === "string") {
43
- return await prepareWAMessageMedia(
44
- { image: { url: image } },
45
- { upload: socket.waUploadToServer }
46
- )
47
- }
48
-
49
- if (Buffer.isBuffer(image)) {
50
- return await prepareWAMessageMedia(
51
- { image },
52
- { upload: socket.waUploadToServer }
53
- )
54
- }
55
-
56
- if (typeof image === "object") {
57
- return await prepareWAMessageMedia(
58
- { image },
59
- { upload: socket.waUploadToServer }
60
- )
61
- }
62
-
63
- throw new Error("Format image tidak didukung")
64
- }
65
-
66
- for (let i = 0; i < cards.length; i++) {
67
- const item = cards[i]
68
-
69
- let img = await getImageMedia(item.image)
70
-
71
- carouselCards.push({
72
- header: proto.Message.InteractiveMessage.Header.fromObject({
73
- title: item.caption || `Card ${i + 1}`,
74
- hasMediaAttachment: true,
75
- ...img
76
- }),
77
- nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.fromObject({
78
- buttons: Array.isArray(item.buttons) ? item.buttons : []
79
- }),
80
- footer: proto.Message.InteractiveMessage.Footer.create({
81
- text: footer
82
- })
83
- })
84
- }
85
-
86
- const msg = await generateWAMessageFromContent(
87
- jid,
88
- {
89
- viewOnceMessage: {
90
- message: {
91
- messageContextInfo: {
92
- deviceListMetadata: {},
93
- deviceListMetadataVersion: 2
94
- },
95
- interactiveMessage: proto.Message.InteractiveMessage.fromObject({
96
- body: proto.Message.InteractiveMessage.Body.fromObject({
97
- text
98
- }),
99
- carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.fromObject({
100
- cards: carouselCards
20
+ const {
21
+ proto,
22
+ generateWAMessageFromContent,
23
+ jidDecode,
24
+ downloadContentFromMessage,
25
+ prepareWAMessageMedia,
26
+ generateMessageID,
27
+ generateWAMessage
28
+ } = baileys;
29
+
30
+ Object.assign(socket, {
31
+ sendCard: async (jid, options = {}) => {
32
+ const {
33
+ text = "",
34
+ footer = "",
35
+ cards = [],
36
+ quoted = null,
37
+ sender = jid
38
+ } = options
39
+
40
+ if (!cards.length) throw new Error("cards cannot be empty")
41
+
42
+ let carouselCards = []
43
+ const getImageMedia = async (image) => {
44
+ if (!image) throw new Error("Image cannot be empty")
45
+
46
+ if (typeof image === "string") {
47
+ return await prepareWAMessageMedia({
48
+ image: {
49
+ url: image
50
+ }
51
+ }, {
52
+ upload: socket.waUploadToServer
53
+ })
54
+ }
55
+
56
+ if (Buffer.isBuffer(image)) {
57
+ return await prepareWAMessageMedia({
58
+ image
59
+ }, {
60
+ upload: socket.waUploadToServer
61
+ })
62
+ }
63
+
64
+ if (typeof image === "object") {
65
+ return await prepareWAMessageMedia({
66
+ image
67
+ }, {
68
+ upload: socket.waUploadToServer
69
+ })
70
+ }
71
+
72
+ throw new Error("Format image tidak didukung")
73
+ }
74
+
75
+ for (let i = 0; i < cards.length; i++) {
76
+ const item = cards[i]
77
+
78
+ let img = await getImageMedia(item.image)
79
+
80
+ carouselCards.push({
81
+ header: proto.Message.InteractiveMessage.Header.fromObject({
82
+ title: item.caption || `Card ${i + 1}`,
83
+ hasMediaAttachment: true,
84
+ ...img
85
+ }),
86
+ nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.fromObject({
87
+ buttons: Array.isArray(item.buttons) ? item.buttons : []
88
+ }),
89
+ footer: proto.Message.InteractiveMessage.Footer.create({
90
+ text: footer
91
+ })
92
+ })
93
+ }
94
+
95
+ const msg = await generateWAMessageFromContent(
96
+ jid, {
97
+ viewOnceMessage: {
98
+ message: {
99
+ messageContextInfo: {
100
+ deviceListMetadata: {},
101
+ deviceListMetadataVersion: 2
102
+ },
103
+ interactiveMessage: proto.Message.InteractiveMessage.fromObject({
104
+ body: proto.Message.InteractiveMessage.Body.fromObject({
105
+ text
106
+ }),
107
+ carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.fromObject({
108
+ cards: carouselCards
109
+ })
110
+ })
111
+ }
112
+ }
113
+ }, {
114
+ userJid: sender,
115
+ quoted
116
+ }
117
+ )
118
+
119
+ return await socket.relayMessage(jid, msg.message, {
120
+ messageId: msg.key.id
101
121
  })
102
- })
103
- }
104
- }
105
- },
106
- {
107
- userJid: sender,
108
- quoted
109
- }
110
- )
111
-
112
- return await socket.relayMessage(jid, msg.message, {
113
- messageId: msg.key.id
114
- })
115
- },
116
-
117
- sendSticker: async (jid, options = {}) => {
118
- try {
119
- if (!options.sticker)
120
- throw new Error('Please enter the path or buffer of the sticker.')
121
-
122
- const tmpDir = path.join(__dirname, './tmp')
123
- if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true })
124
-
125
- let stickerPath
126
-
127
- if (Buffer.isBuffer(options.sticker)) {
128
- stickerPath = path.join(tmpDir, `sticker_${Date.now()}.webp`)
129
- fs.writeFileSync(stickerPath, options.sticker)
130
- } else if (typeof options.sticker === 'string') {
131
- if (!fs.existsSync(options.sticker))
132
- throw new Error(`File not found: ${options.sticker}`)
133
- stickerPath = options.sticker
134
- } else {
135
- throw new Error('Sticker format not recognized (must be buffer or file path).')
136
- }
137
-
138
- const sticker = new Sticker(stickerPath, {
139
- pack: options.packname || "Made By",
140
- author: options.author || "漏 饾檷廷饾櫘饾櫔饾櫔 饾檷廷饾櫄饾櫈饾櫍饾櫙饾櫙"
141
- })
142
-
143
- const buffer = await sticker.build()
144
- const result = await socket.sendMessage(jid, { sticker: buffer })
145
-
146
- if (Buffer.isBuffer(options.sticker)) fs.unlinkSync(stickerPath)
147
-
148
- return result
149
- } catch (e) {
150
- console.error('[sendSticker Error]', e)
151
- }
152
- },
153
-
154
- sendButton:
155
- async (jid, content = {}, options = {}) => {
156
- if (!socket.user?.id) {
157
- throw new Error("User not authenticated");
122
+ },
123
+
124
+ sendSticker: async (jid, options = {}) => {
125
+ try {
126
+ if (!options.sticker)
127
+ throw new Error('Please enter the path or buffer of the sticker.')
128
+
129
+ const tmpDir = path.join(__dirname, './tmp')
130
+ if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, {
131
+ recursive: true
132
+ })
133
+
134
+ let stickerPath
135
+
136
+ if (Buffer.isBuffer(options.sticker)) {
137
+ stickerPath = path.join(tmpDir, `sticker_${Date.now()}.webp`)
138
+ fs.writeFileSync(stickerPath, options.sticker)
139
+ } else if (typeof options.sticker === 'string') {
140
+ if (!fs.existsSync(options.sticker))
141
+ throw new Error(`File not found: ${options.sticker}`)
142
+ stickerPath = options.sticker
143
+ } else {
144
+ throw new Error('Sticker format not recognized (must be buffer or file path).')
158
145
  }
159
146
 
160
- const {
161
- text = "",
147
+ const sticker = new Sticker(stickerPath, {
148
+ pack: options.packname || "Made By",
149
+ author: options.author || "漏 饾檷廷饾櫘饾櫔饾櫔 饾檷廷饾櫄饾櫈饾櫍饾櫙饾櫙"
150
+ })
151
+
152
+ const buffer = await sticker.build()
153
+ const result = await socket.sendMessage(jid, {
154
+ sticker: buffer
155
+ })
156
+
157
+ if (Buffer.isBuffer(options.sticker)) fs.unlinkSync(stickerPath)
158
+
159
+ return result
160
+ } catch (e) {
161
+ console.error('[sendSticker Error]', e)
162
+ }
163
+ },
164
+
165
+ sendButton: async (jid, content = {}, options = {}) => {
166
+ if (!socket.user?.id) {
167
+ throw new Error("User not authenticated");
168
+ }
169
+
170
+ const {
171
+ text = "",
162
172
  caption = "",
163
173
  title = "",
164
174
  footer = "",
@@ -174,488 +184,499 @@ Object.assign(socket, {
174
184
  product = null,
175
185
  businessOwnerJid = null,
176
186
  externalAdReply = null,
177
- } = content;
178
-
179
- if (!Array.isArray(buttons) || buttons.length === 0) {
180
- throw new Error("buttons must be a non-empty array");
181
- }
187
+ } = content;
182
188
 
183
- const processedButtons = [];
189
+ if (!Array.isArray(buttons) || buttons.length === 0) {
190
+ throw new Error("buttons must be a non-empty array");
191
+ }
184
192
 
185
- for (let i = 0; i < buttons.length; i++) {
186
- const btn = buttons[i];
193
+ const processedButtons = [];
187
194
 
188
- if (!btn || typeof btn !== "object") {
189
- throw new Error(`interactiveButton[${i}] must be an object`);
190
- }
195
+ for (let i = 0; i < buttons.length; i++) {
196
+ const btn = buttons[i];
191
197
 
192
- if (btn.name && btn.buttonParamsJson) {
193
- processedButtons.push(btn);
194
- continue;
195
- }
198
+ if (!btn || typeof btn !== "object") {
199
+ throw new Error(`interactiveButton[${i}] must be an object`);
200
+ }
196
201
 
197
- if (btn.id || btn.text || btn.displayText) {
198
- processedButtons.push({
199
- name: "quick_reply",
200
- buttonParamsJson: JSON.stringify({
201
- display_text: btn.text || btn.displayText || `Button ${i + 1}`,
202
- id: btn.id || `quick_${i + 1}`,
203
- }),
204
- });
205
- continue;
206
- }
202
+ if (btn.name && btn.buttonParamsJson) {
203
+ processedButtons.push(btn);
204
+ continue;
205
+ }
207
206
 
208
- if (btn.buttonId && btn.buttonText?.displayText) {
209
- processedButtons.push({
210
- name: "quick_reply",
211
- buttonParamsJson: JSON.stringify({
212
- display_text: btn.buttonText.displayText,
213
- id: btn.buttonId,
214
- }),
215
- });
216
- continue;
217
- }
207
+ if (btn.id || btn.text || btn.displayText) {
208
+ processedButtons.push({
209
+ name: "quick_reply",
210
+ buttonParamsJson: JSON.stringify({
211
+ display_text: btn.text || btn.displayText || `Button ${i + 1}`,
212
+ id: btn.id || `quick_${i + 1}`,
213
+ }),
214
+ });
215
+ continue;
216
+ }
218
217
 
219
- throw new Error(`interactiveButton[${i}] has invalid shape`);
218
+ if (btn.buttonId && btn.buttonText?.displayText) {
219
+ processedButtons.push({
220
+ name: "quick_reply",
221
+ buttonParamsJson: JSON.stringify({
222
+ display_text: btn.buttonText.displayText,
223
+ id: btn.buttonId,
224
+ }),
225
+ });
226
+ continue;
220
227
  }
221
228
 
222
- let messageContent = {};
229
+ throw new Error(`interactiveButton[${i}] has invalid shape`);
230
+ }
223
231
 
224
- if (image) {
225
- const mediaInput = {};
226
- if (Buffer.isBuffer(image)) {
227
- mediaInput.image = image;
228
- } else if (typeof image === "object" && image.url) {
229
- mediaInput.image = { url: image.url };
230
- } else if (typeof image === "string") {
231
- mediaInput.image = { url: image };
232
- }
232
+ let messageContent = {};
233
233
 
234
- const preparedMedia = await prepareWAMessageMedia(mediaInput, {
235
- upload: socket.waUploadToServer,
236
- });
234
+ if (image) {
235
+ const mediaInput = {};
236
+ if (Buffer.isBuffer(image)) {
237
+ mediaInput.image = image;
238
+ } else if (typeof image === "object" && image.url) {
239
+ mediaInput.image = {
240
+ url: image.url
241
+ };
242
+ } else if (typeof image === "string") {
243
+ mediaInput.image = {
244
+ url: image
245
+ };
246
+ }
237
247
 
238
- messageContent.header = {
239
- title: title || "",
240
- hasMediaAttachment: hasMediaAttachment || true,
241
- imageMessage: preparedMedia.imageMessage,
248
+ const preparedMedia = await prepareWAMessageMedia(mediaInput, {
249
+ upload: socket.waUploadToServer,
250
+ });
251
+
252
+ messageContent.header = {
253
+ title: title || "",
254
+ hasMediaAttachment: hasMediaAttachment || true,
255
+ imageMessage: preparedMedia.imageMessage,
256
+ };
257
+ } else if (video) {
258
+ const mediaInput = {};
259
+ if (Buffer.isBuffer(video)) {
260
+ mediaInput.video = video;
261
+ } else if (typeof video === "object" && video.url) {
262
+ mediaInput.video = {
263
+ url: video.url
242
264
  };
243
- } else if (video) {
244
- const mediaInput = {};
245
- if (Buffer.isBuffer(video)) {
246
- mediaInput.video = video;
247
- } else if (typeof video === "object" && video.url) {
248
- mediaInput.video = { url: video.url };
249
- } else if (typeof video === "string") {
250
- mediaInput.video = { url: video };
251
- }
265
+ } else if (typeof video === "string") {
266
+ mediaInput.video = {
267
+ url: video
268
+ };
269
+ }
252
270
 
253
- const preparedMedia = await prepareWAMessageMedia(mediaInput, {
254
- upload: socket.waUploadToServer,
255
- });
271
+ const preparedMedia = await prepareWAMessageMedia(mediaInput, {
272
+ upload: socket.waUploadToServer,
273
+ });
256
274
 
257
- messageContent.header = {
258
- title: title || "",
259
- hasMediaAttachment: hasMediaAttachment || true,
260
- videoMessage: preparedMedia.videoMessage,
275
+ messageContent.header = {
276
+ title: title || "",
277
+ hasMediaAttachment: hasMediaAttachment || true,
278
+ videoMessage: preparedMedia.videoMessage,
279
+ };
280
+ } else if (document) {
281
+ const mediaInput = {
282
+ document: {}
283
+ };
284
+
285
+ if (Buffer.isBuffer(document)) {
286
+ mediaInput.document = document;
287
+ } else if (typeof document === "object" && document.url) {
288
+ mediaInput.document = {
289
+ url: document.url
261
290
  };
262
- } else if (document) {
263
- const mediaInput = { document: {} };
264
-
265
- if (Buffer.isBuffer(document)) {
266
- mediaInput.document = document;
267
- } else if (typeof document === "object" && document.url) {
268
- mediaInput.document = { url: document.url };
269
- } else if (typeof document === "string") {
270
- mediaInput.document = { url: document };
271
- }
291
+ } else if (typeof document === "string") {
292
+ mediaInput.document = {
293
+ url: document
294
+ };
295
+ }
272
296
 
273
- if (fileName) {
274
- if (typeof mediaInput.document === "object") {
275
- mediaInput.document.fileName = fileName;
276
- }
297
+ if (fileName) {
298
+ if (typeof mediaInput.document === "object") {
299
+ mediaInput.document.fileName = fileName;
277
300
  }
278
-
279
- if (mimetype) {
280
- if (typeof mediaInput.document === "object") {
281
- mediaInput.document.mimetype = mimetype;
282
- }
301
+ }
302
+
303
+ if (mimetype) {
304
+ if (typeof mediaInput.document === "object") {
305
+ mediaInput.document.mimetype = mimetype;
283
306
  }
307
+ }
284
308
 
285
- if (jpegThumbnail) {
286
- if (typeof mediaInput.document === "object") {
287
- if (Buffer.isBuffer(jpegThumbnail)) {
288
- mediaInput.document.jpegThumbnail = jpegThumbnail;
289
- } else if (typeof jpegThumbnail === "string") {
290
- try {
291
- const response = await fetch(jpegThumbnail);
292
- const arrayBuffer = await response.arrayBuffer();
293
- mediaInput.document.jpegThumbnail = Buffer.from(arrayBuffer);
294
- } catch {
295
- //
296
- }
309
+ if (jpegThumbnail) {
310
+ if (typeof mediaInput.document === "object") {
311
+ if (Buffer.isBuffer(jpegThumbnail)) {
312
+ mediaInput.document.jpegThumbnail = jpegThumbnail;
313
+ } else if (typeof jpegThumbnail === "string") {
314
+ try {
315
+ const response = await fetch(jpegThumbnail);
316
+ const arrayBuffer = await response.arrayBuffer();
317
+ mediaInput.document.jpegThumbnail = Buffer.from(arrayBuffer);
318
+ } catch {
319
+ //
297
320
  }
298
321
  }
299
322
  }
323
+ }
300
324
 
301
- const preparedMedia = await prepareWAMessageMedia(mediaInput, {
302
- upload: socket.waUploadToServer,
303
- });
304
-
305
- messageContent.header = {
306
- title: title || "",
307
- hasMediaAttachment: hasMediaAttachment || true,
308
- documentMessage: preparedMedia.documentMessage,
309
- };
310
- } else if (location && typeof location === "object") {
311
- messageContent.header = {
312
- title: title || location.name || "Location",
313
- hasMediaAttachment: hasMediaAttachment || false,
314
- locationMessage: {
315
- degreesLatitude:
316
- location.degressLatitude || location.degreesLatitude || 0,
317
- degreesLongitude:
318
- location.degressLongitude || location.degreesLongitude || 0,
319
- name: location.name || "",
320
- address: location.address || "",
321
- },
322
- };
323
- } else if (product && typeof product === "object") {
324
- let productImageMessage = null;
325
- if (product.productImage) {
326
- const mediaInput = {};
327
- if (Buffer.isBuffer(product.productImage)) {
328
- mediaInput.image = product.productImage;
329
- } else if (
330
- typeof product.productImage === "object" &&
331
- product.productImage.url
332
- ) {
333
- mediaInput.image = {
334
- url: product.productImage.url,
335
- };
336
- } else if (typeof product.productImage === "string") {
337
- mediaInput.image = {
338
- url: product.productImage,
339
- };
340
- }
325
+ const preparedMedia = await prepareWAMessageMedia(mediaInput, {
326
+ upload: socket.waUploadToServer,
327
+ });
341
328
 
342
- const preparedMedia = await prepareWAMessageMedia(mediaInput, {
343
- upload: socket.waUploadToServer,
344
- });
345
- productImageMessage = preparedMedia.imageMessage;
329
+ messageContent.header = {
330
+ title: title || "",
331
+ hasMediaAttachment: hasMediaAttachment || true,
332
+ documentMessage: preparedMedia.documentMessage,
333
+ };
334
+ } else if (location && typeof location === "object") {
335
+ messageContent.header = {
336
+ title: title || location.name || "Location",
337
+ hasMediaAttachment: hasMediaAttachment || false,
338
+ locationMessage: {
339
+ degreesLatitude: location.degressLatitude || location.degreesLatitude || 0,
340
+ degreesLongitude: location.degressLongitude || location.degreesLongitude || 0,
341
+ name: location.name || "",
342
+ address: location.address || "",
343
+ },
344
+ };
345
+ } else if (product && typeof product === "object") {
346
+ let productImageMessage = null;
347
+ if (product.productImage) {
348
+ const mediaInput = {};
349
+ if (Buffer.isBuffer(product.productImage)) {
350
+ mediaInput.image = product.productImage;
351
+ } else if (
352
+ typeof product.productImage === "object" &&
353
+ product.productImage.url
354
+ ) {
355
+ mediaInput.image = {
356
+ url: product.productImage.url,
357
+ };
358
+ } else if (typeof product.productImage === "string") {
359
+ mediaInput.image = {
360
+ url: product.productImage,
361
+ };
346
362
  }
347
363
 
348
- messageContent.header = {
349
- title: title || product.title || "Product",
350
- hasMediaAttachment: hasMediaAttachment || false,
351
- productMessage: {
352
- product: {
353
- productImage: productImageMessage,
354
- productId: product.productId || "",
355
- title: product.title || "",
356
- description: product.description || "",
357
- currencyCode: product.currencyCode || "USD",
358
- priceAmount1000: parseInt(product.priceAmount1000) || 0,
359
- retailerId: product.retailerId || "",
360
- url: product.url || "",
361
- productImageCount: product.productImageCount || 1,
362
- },
363
- businessOwnerJid:
364
- businessOwnerJid || product.businessOwnerJid || socket.user.id,
365
- },
366
- };
367
- } else if (title) {
368
- messageContent.header = {
369
- title: title,
370
- hasMediaAttachment: false,
371
- };
364
+ const preparedMedia = await prepareWAMessageMedia(mediaInput, {
365
+ upload: socket.waUploadToServer,
366
+ });
367
+ productImageMessage = preparedMedia.imageMessage;
372
368
  }
373
369
 
374
- const hasMedia = !!(image || video || document || location || product);
375
- const bodyText = hasMedia ? caption : text || caption;
370
+ messageContent.header = {
371
+ title: title || product.title || "Product",
372
+ hasMediaAttachment: hasMediaAttachment || false,
373
+ productMessage: {
374
+ product: {
375
+ productImage: productImageMessage,
376
+ productId: product.productId || "",
377
+ title: product.title || "",
378
+ description: product.description || "",
379
+ currencyCode: product.currencyCode || "USD",
380
+ priceAmount1000: parseInt(product.priceAmount1000) || 0,
381
+ retailerId: product.retailerId || "",
382
+ url: product.url || "",
383
+ productImageCount: product.productImageCount || 1,
384
+ },
385
+ businessOwnerJid: businessOwnerJid || product.businessOwnerJid || socket.user.id,
386
+ },
387
+ };
388
+ } else if (title) {
389
+ messageContent.header = {
390
+ title: title,
391
+ hasMediaAttachment: false,
392
+ };
393
+ }
376
394
 
377
- if (bodyText) {
378
- messageContent.body = { text: bodyText };
379
- }
395
+ const hasMedia = !!(image || video || document || location || product);
396
+ const bodyText = hasMedia ? caption : text || caption;
380
397
 
381
- if (footer) {
382
- messageContent.footer = { text: footer };
383
- }
398
+ if (bodyText) {
399
+ messageContent.body = {
400
+ text: bodyText
401
+ };
402
+ }
384
403
 
385
- messageContent.nativeFlowMessage = {
386
- buttons: processedButtons,
404
+ if (footer) {
405
+ messageContent.footer = {
406
+ text: footer
387
407
  };
408
+ }
388
409
 
389
- if (externalAdReply && typeof externalAdReply === "object") {
390
- messageContent.contextInfo = {
391
- externalAdReply: {
392
- title: externalAdReply.title || "",
393
- body: externalAdReply.body || "",
394
- mediaType: externalAdReply.mediaType || 1,
395
- sourceUrl: externalAdReply.sourceUrl || externalAdReply.url || "",
396
- thumbnailUrl:
397
- externalAdReply.thumbnailUrl || "",
398
- renderLargerThumbnail: externalAdReply.renderLargerThumbnail || false,
399
- showAdAttribution: externalAdReply.showAdAttribution !== false,
400
- containsAutoReply: externalAdReply.containsAutoReply || false,
401
- ...(externalAdReply.mediaUrl && {
402
- mediaUrl: externalAdReply.mediaUrl,
403
- }),
404
- ...(externalAdReply.thumbnail &&
405
- Buffer.isBuffer(externalAdReply.thumbnail) && {
406
- thumbnail: externalAdReply.thumbnail,
407
- }),
408
- ...(externalAdReply.jpegThumbnail && {
409
- jpegThumbnail: externalAdReply.jpegThumbnail,
410
+ messageContent.nativeFlowMessage = {
411
+ buttons: processedButtons,
412
+ };
413
+
414
+ if (externalAdReply && typeof externalAdReply === "object") {
415
+ messageContent.contextInfo = {
416
+ externalAdReply: {
417
+ title: externalAdReply.title || "",
418
+ body: externalAdReply.body || "",
419
+ mediaType: externalAdReply.mediaType || 1,
420
+ sourceUrl: externalAdReply.sourceUrl || externalAdReply.url || "",
421
+ thumbnailUrl: externalAdReply.thumbnailUrl || "",
422
+ renderLargerThumbnail: externalAdReply.renderLargerThumbnail || false,
423
+ showAdAttribution: externalAdReply.showAdAttribution !== false,
424
+ containsAutoReply: externalAdReply.containsAutoReply || false,
425
+ ...(externalAdReply.mediaUrl && {
426
+ mediaUrl: externalAdReply.mediaUrl,
427
+ }),
428
+ ...(externalAdReply.thumbnail &&
429
+ Buffer.isBuffer(externalAdReply.thumbnail) && {
430
+ thumbnail: externalAdReply.thumbnail,
410
431
  }),
411
- },
412
- ...(options.mentionedJid && {
413
- mentionedJid: options.mentionedJid,
432
+ ...(externalAdReply.jpegThumbnail && {
433
+ jpegThumbnail: externalAdReply.jpegThumbnail,
414
434
  }),
415
- };
416
- } else if (options.mentionedJid) {
417
- messageContent.contextInfo = {
435
+ },
436
+ ...(options.mentionedJid && {
418
437
  mentionedJid: options.mentionedJid,
419
- };
420
- }
438
+ }),
439
+ };
440
+ } else if (options.mentionedJid) {
441
+ messageContent.contextInfo = {
442
+ mentionedJid: options.mentionedJid,
443
+ };
444
+ }
421
445
 
422
- const payload = proto.Message.InteractiveMessage.create(messageContent);
446
+ const payload = proto.Message.InteractiveMessage.create(messageContent);
423
447
 
424
- const msg = generateWAMessageFromContent(
425
- jid,
426
- {
427
- viewOnceMessage: {
428
- message: {
429
- interactiveMessage: payload,
430
- },
448
+ const msg = generateWAMessageFromContent(
449
+ jid, {
450
+ viewOnceMessage: {
451
+ message: {
452
+ interactiveMessage: payload,
431
453
  },
432
454
  },
433
- {
434
- userJid: socket.user.id,
435
- quoted: options?.quoted || null,
436
- }
437
- );
438
-
439
- const additionalNodes = [
440
- {
441
- tag: "biz",
442
- attrs: {},
443
- content: [
444
- {
445
- tag: "interactive",
446
- attrs: {
447
- type: "native_flow",
448
- v: "1",
449
- },
450
- content: [
451
- {
452
- tag: "native_flow",
453
- attrs: {
454
- v: "9",
455
- name: "mixed",
456
- },
457
- },
458
- ],
459
- },
460
- ],
455
+ }, {
456
+ userJid: socket.user.id,
457
+ quoted: options?.quoted || null,
458
+ }
459
+ );
460
+
461
+ const additionalNodes = [{
462
+ tag: "biz",
463
+ attrs: {},
464
+ content: [{
465
+ tag: "interactive",
466
+ attrs: {
467
+ type: "native_flow",
468
+ v: "1",
461
469
  },
462
- ];
470
+ content: [{
471
+ tag: "native_flow",
472
+ attrs: {
473
+ v: "9",
474
+ name: "mixed",
475
+ },
476
+ }, ],
477
+ }, ],
478
+ }, ];
463
479
 
464
- await socket.relayMessage(jid, msg.message, {
465
- messageId: msg.key.id,
466
- additionalNodes,
467
- });
480
+ await socket.relayMessage(jid, msg.message, {
481
+ messageId: msg.key.id,
482
+ additionalNodes,
483
+ });
468
484
 
469
- return msg;
485
+ return msg;
470
486
  },
471
487
  sendAlbum: async (jid, items = [], options = {}) => {
472
- if (!socket.user?.id) {
473
- throw new Error("User not authenticated");
474
- }
475
-
476
- const messageSecret = new Uint8Array(32);
477
- crypto.getRandomValues(messageSecret);
478
-
479
- const messageContent = {
480
- messageContextInfo: { messageSecret },
481
- albumMessage: {
482
- expectedImageCount: items.filter((a) =>
483
- a?.image).length,
484
- expectedVideoCount: items.filter((a) =>
485
- a?.video).length,
486
- },
487
- };
488
-
489
- const generationOptions = {
490
- userJid: socket.user.id,
491
- upload: socket.waUploadToServer,
492
- quoted: options?.quoted || null,
493
- ephemeralExpiration: options?.quoted
494
- ?.expiration ?? 0,
495
- };
496
-
497
- const album = generateWAMessageFromContent(jid,
498
- messageContent, generationOptions);
499
-
500
- await socket.relayMessage(album.key.remoteJid, album
501
- .message, {
502
- messageId: album.key.id,
503
- });
504
-
505
- await Promise.all(
506
- items.map(async (content) => {
507
- const mediaSecret =
508
- new Uint8Array(32);
509
- crypto.getRandomValues(
510
- mediaSecret);
511
-
512
- const mediaMsg =
513
- await generateWAMessage(
514
- album.key.remoteJid,
515
- content, {
516
- upload: socket
517
- .waUploadToServer,
518
- ephemeralExpiration: options
519
- ?.quoted
520
- ?.expiration ??
521
- 0,
522
- });
523
-
524
- mediaMsg.message
525
- .messageContextInfo = {
526
- messageSecret: mediaSecret,
527
- messageAssociation: {
528
- associationType: 1,
529
- parentMessageKey: album
530
- .key,
531
- },
532
- };
533
-
534
- return socket.relayMessage(
535
- mediaMsg.key.remoteJid,
536
- mediaMsg.message, {
537
- messageId: mediaMsg
538
- .key.id,
539
- });
540
- })
541
- );
542
-
543
- return album;
544
- },
545
-
546
- sendOrder: async (jid, orderData, options = {}) => {
547
- if (!socket.user?.id) {
548
- throw new Error("User not authenticated");
549
- }
550
-
551
- let thumbnail = null;
552
- if (orderData.thumbnail) {
553
- if (Buffer.isBuffer(orderData.thumbnail)) {
554
- thumbnail = orderData.thumbnail;
555
- } else if (typeof orderData.thumbnail === "string") {
556
- try {
557
- if (orderData.thumbnail.startsWith("http")) {
558
- const response = await fetch(orderData.thumbnail);
559
- const arrayBuffer = await response.arrayBuffer();
560
- thumbnail = Buffer.from(arrayBuffer);
561
- } else {
562
- thumbnail = Buffer.from(orderData.thumbnail, "base64");
488
+ if (!socket.user?.id) {
489
+ throw new Error("User not authenticated");
563
490
  }
564
- } catch (e) {
565
- socket.logger?.warn(
566
- {
567
- err: e.message
568
- },
569
- "Failed to fetch/convert thumbnail"
491
+
492
+ const messageSecret = new Uint8Array(32);
493
+ crypto.getRandomValues(messageSecret);
494
+
495
+ const messageContent = {
496
+ messageContextInfo: {
497
+ messageSecret
498
+ },
499
+ albumMessage: {
500
+ expectedImageCount: items.filter((a) =>
501
+ a?.image).length,
502
+ expectedVideoCount: items.filter((a) =>
503
+ a?.video).length,
504
+ },
505
+ };
506
+
507
+ const generationOptions = {
508
+ userJid: socket.user.id,
509
+ upload: socket.waUploadToServer,
510
+ quoted: options?.quoted || null,
511
+ ephemeralExpiration: options?.quoted
512
+ ?.expiration ?? 0,
513
+ };
514
+
515
+ const album = generateWAMessageFromContent(jid,
516
+ messageContent, generationOptions);
517
+
518
+ await socket.relayMessage(album.key.remoteJid, album
519
+ .message, {
520
+ messageId: album.key.id,
521
+ });
522
+
523
+ await Promise.all(
524
+ items.map(async (content) => {
525
+ const mediaSecret =
526
+ new Uint8Array(32);
527
+ crypto.getRandomValues(
528
+ mediaSecret);
529
+
530
+ const mediaMsg =
531
+ await generateWAMessage(
532
+ album.key.remoteJid,
533
+ content, {
534
+ upload: socket
535
+ .waUploadToServer,
536
+ ephemeralExpiration: options
537
+ ?.quoted
538
+ ?.expiration ??
539
+ 0,
540
+ });
541
+
542
+ mediaMsg.message
543
+ .messageContextInfo = {
544
+ messageSecret: mediaSecret,
545
+ messageAssociation: {
546
+ associationType: 1,
547
+ parentMessageKey: album
548
+ .key,
549
+ },
550
+ };
551
+
552
+ return socket.relayMessage(
553
+ mediaMsg.key.remoteJid,
554
+ mediaMsg.message, {
555
+ messageId: mediaMsg
556
+ .key.id,
557
+ });
558
+ })
570
559
  );
571
- thumbnail = null;
572
- }
573
- }
574
- }
575
-
576
- const orderMessage = proto.Message.OrderMessage.fromObject({
577
- orderId: orderData.orderId || generateMessageID(),
578
- thumbnail: thumbnail,
579
- itemCount: orderData.itemCount || 1,
580
- status: orderData.status || proto.Message.OrderMessage.OrderStatus.INQUIRY,
581
- surface: orderData.surface || proto.Message.OrderMessage.OrderSurface.CATALOG,
582
- message: orderData.message || "",
583
- orderTitle: orderData.orderTitle || "Order",
584
- sellerJid: orderData.sellerJid || socket.user.id,
585
- token: orderData.token || "",
586
- totalAmount1000: orderData.totalAmount1000 || 0,
587
- totalCurrencyCode: orderData.totalCurrencyCode || "IDR",
588
- contextInfo: {
589
- ...(options.contextInfo || {}),
590
- ...(options.mentions ?
591
- {
592
- mentionedJid: options.mentions,
593
- } :
594
- {}),
560
+
561
+ return album;
562
+ },
563
+
564
+ sendOrder: async (jid, orderData, options = {}) => {
565
+ if (!socket.user?.id) {
566
+ throw new Error("User not authenticated");
567
+ }
568
+
569
+ let thumbnail = null;
570
+ if (orderData.thumbnail) {
571
+ if (Buffer.isBuffer(orderData.thumbnail)) {
572
+ thumbnail = orderData.thumbnail;
573
+ } else if (typeof orderData.thumbnail === "string") {
574
+ try {
575
+ if (orderData.thumbnail.startsWith("http")) {
576
+ const response = await fetch(orderData.thumbnail);
577
+ const arrayBuffer = await response.arrayBuffer();
578
+ thumbnail = Buffer.from(arrayBuffer);
579
+ } else {
580
+ thumbnail = Buffer.from(orderData.thumbnail, "base64");
581
+ }
582
+ } catch (e) {
583
+ socket.logger?.warn({
584
+ err: e.message
585
+ },
586
+ "Failed to fetch/convert thumbnail"
587
+ );
588
+ thumbnail = null;
589
+ }
590
+ }
591
+ }
592
+
593
+ const orderMessage = proto.Message.OrderMessage.fromObject({
594
+ orderId: orderData.orderId || generateMessageID(),
595
+ thumbnail: thumbnail,
596
+ itemCount: orderData.itemCount || 1,
597
+ status: orderData.status || proto.Message.OrderMessage.OrderStatus.INQUIRY,
598
+ surface: orderData.surface || proto.Message.OrderMessage.OrderSurface.CATALOG,
599
+ message: orderData.message || "",
600
+ orderTitle: orderData.orderTitle || "Order",
601
+ sellerJid: orderData.sellerJid || socket.user.id,
602
+ token: orderData.token || "",
603
+ totalAmount1000: orderData.totalAmount1000 || 0,
604
+ totalCurrencyCode: orderData.totalCurrencyCode || "IDR",
605
+ contextInfo: {
606
+ ...(options.contextInfo || {}),
607
+ ...(options.mentions ? {
608
+ mentionedJid: options.mentions,
609
+ } : {}),
610
+ },
611
+ });
612
+
613
+ const msg = proto.Message.create({
614
+ orderMessage,
615
+ });
616
+
617
+ const message = generateWAMessageFromContent(jid, msg, {
618
+ userJid: socket.user.id,
619
+ timestamp: options.timestamp || new Date(),
620
+ quoted: options.quoted || null,
621
+ ephemeralExpiration: options.ephemeralExpiration || 0,
622
+ messageId: options.messageId || null,
623
+ });
624
+
625
+ return await socket.relayMessage(message.key.remoteJid, message.message, {
626
+ messageId: message.key.id,
627
+ });
628
+ },
629
+
630
+ getPNFromLid: async (m, lidInput) => {
631
+ if (!lidInput) throw new Error("Misssing input");
632
+ try {
633
+ let chat = m.chat
634
+ if (chat.endsWith('@g.us')) {
635
+
636
+ const metadata = await socket.groupMetadata(chat);
637
+ if (!metadata || !metadata.participants) return lidInput;
638
+
639
+ const found = metadata.participants.find(entry => entry.id === lidInput);
640
+ return found ? found.phoneNumber : lidInput;
641
+ } else {
642
+ const {
643
+ remoteJid,
644
+ remoteJidAlt
645
+ } = m.key
646
+ if (remoteJid === lidInput && remoteJidAlt.endsWith("@s.whatsapp.net")) {
647
+ return remoteJidAlt
648
+ }
649
+ }
650
+ } catch (e) {
651
+ console.error('Gagal ambil group metadata:', e.message);
652
+ return lidInput;
653
+ }
654
+ },
655
+
656
+ getLidFromPN: async (m, jidInput) => {
657
+ if (!jidInput) throw new Error("Misssing jid input");
658
+ try {
659
+ let chat = m.chat;
660
+ if (chat.endsWith('@g.us')) {
661
+ const metadata = await socket.groupMetadata(chat);
662
+ if (!metadata || !metadata.participants) return jidInput;
663
+
664
+ const found = metadata.participants.find(entry => entry.phoneNumber === jidInput);
665
+ return found ? found.id : jidInput;
666
+ } else {
667
+ const {
668
+ remoteJid,
669
+ remoteJidAlt
670
+ } = m.key
671
+ if (!remoteJid || !remoteJidAlt) return null
672
+ if (remoteJidAlt === jidInput && remoteJid.endsWith("@lid")) {
673
+ return remoteJid
674
+ }
675
+ }
676
+ } catch (e) {
677
+ console.error('Gagal ambil group metadata:', e.message);
678
+ return jidInput;
679
+ }
595
680
  },
596
- });
597
-
598
- const msg = proto.Message.create({
599
- orderMessage,
600
- });
601
-
602
- const message = generateWAMessageFromContent(jid, msg, {
603
- userJid: socket.user.id,
604
- timestamp: options.timestamp || new Date(),
605
- quoted: options.quoted || null,
606
- ephemeralExpiration: options.ephemeralExpiration || 0,
607
- messageId: options.messageId || null,
608
- });
609
-
610
- return await socket.relayMessage(message.key.remoteJid, message.message, {
611
- messageId: message.key.id,
612
- });
613
- },
614
-
615
- getPNFromLid: async (m, lidInput) => {
616
- if (!lidInput) throw new Error("Misssing input");
617
- try {
618
- let chat = m.chat
619
- if (chat.endsWith('@g.us')) {
620
-
621
- const metadata = await socket.groupMetadata(chat);
622
- if (!metadata || !metadata.participants) return lidInput;
623
-
624
- const found = metadata.participants.find(entry => entry.id === lidInput);
625
- return found ? found.phoneNumber : lidInput;
626
- } else {
627
- const { remoteJid, remoteJidAlt } = m.key
628
- if (remoteJid === lidInput && remoteJidAlt.endsWith("@s.whatsapp.net")) {
629
- return remoteJidAlt
630
- }
631
- }
632
- } catch (e) {
633
- console.error('Gagal ambil group metadata:', e.message);
634
- return lidInput;
635
- }
636
- },
637
-
638
- getLidFromPN: async (m, jidInput) => {
639
- if (!jidInput) throw new Error("Misssing jid input");
640
- try {
641
- let chat = m.chat;
642
- if (chat.endsWith('@g.us')) {
643
- const metadata = await socket.groupMetadata(chat);
644
- if (!metadata || !metadata.participants) return jidInput;
645
-
646
- const found = metadata.participants.find(entry => entry.phoneNumber === jidInput);
647
- return found ? found.id : jidInput;
648
- } else {
649
- const { remoteJid, remoteJidAlt } = m.key
650
- if (!remoteJid || !remoteJidAlt) return null
651
- if (remoteJidAlt === jidInput && remoteJid.endsWith("@lid")) {
652
- return remoteJid
653
- }
654
- }
655
- } catch (e) {
656
- console.error('Gagal ambil group metadata:', e.message);
657
- return jidInput;
658
- }
659
- },
660
- });
661
- };
681
+ });
682
+ };