@ryuu-reinzz/haruka-lib 1.0.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/index.js +50 -0
- package/main/socket.js +693 -0
- package/main/sqliteAuth.js +110 -0
- package/package.json +22 -0
package/main/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import addProperty from './socket.js';
|
|
2
|
+
import useSQLiteAuthState from './sqliteAuth.js';
|
|
3
|
+
|
|
4
|
+
const haruka = console.log(`
|
|
5
|
+
╭──────────────────────────────────────╮
|
|
6
|
+
│ @ryuu-reinzz/haruka-lib │
|
|
7
|
+
│ Small helper for WhatsApp Baileys │
|
|
8
|
+
╰──────────────────────────────────────╯
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
haruka.extendSocketBot(socket, store, smsg, baileys)
|
|
12
|
+
→ extend socket with helper methods
|
|
13
|
+
|
|
14
|
+
haruka.useSQLiteAuthState()
|
|
15
|
+
→ SQLite-based auth state for your bot
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
Add socket:
|
|
19
|
+
import makeWASocket, {
|
|
20
|
+
proto,
|
|
21
|
+
generateWAMessageFromContent,
|
|
22
|
+
jidDecode,
|
|
23
|
+
downloadContentFromMessage,
|
|
24
|
+
prepareWAMessageMedia,
|
|
25
|
+
generateMessageID
|
|
26
|
+
} from "baileys";
|
|
27
|
+
const conn = makeWASocket({});
|
|
28
|
+
const baileys = {
|
|
29
|
+
proto,
|
|
30
|
+
generateWAMessageFromContent,
|
|
31
|
+
jidDecode,
|
|
32
|
+
downloadContentFromMessage,
|
|
33
|
+
prepareWAMessageMedia,
|
|
34
|
+
generateMessageID
|
|
35
|
+
}
|
|
36
|
+
import haruka from "@ryuu-reinzz/haruka-lib";
|
|
37
|
+
haruka.extendSocketBot(conn, store, smsg, baileys);
|
|
38
|
+
|
|
39
|
+
SQLite session:
|
|
40
|
+
const sessionPath = "./session"
|
|
41
|
+
if (!fs.existsSync(sessionPath)) fs.mkdirSync(sessionPath, { recursive: true });
|
|
42
|
+
const useSQLiteAuthState = haruka.useSQLiteAuthState;
|
|
43
|
+
const { state, saveCreds } = await useSQLiteAuthState(sessionPath + \"auth.db\");
|
|
44
|
+
|
|
45
|
+
Made by Ryuu
|
|
46
|
+
`);
|
|
47
|
+
haruka.useSQLiteAuthState = useSQLiteAuthState;
|
|
48
|
+
haruka.addProperty = addProperty;
|
|
49
|
+
|
|
50
|
+
export default haruka;
|
package/main/socket.js
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import fetch from "node-fetch";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
import stkPkg from 'wa-sticker-formatter';
|
|
8
|
+
const { Sticker } = stkPkg;
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {import('baileys').WASocket} socket
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export default function addProperty(socket, store, smsg, baileys) {
|
|
17
|
+
const {
|
|
18
|
+
proto,
|
|
19
|
+
generateWAMessageFromContent,
|
|
20
|
+
jidDecode,
|
|
21
|
+
downloadContentFromMessage,
|
|
22
|
+
prepareWAMessageMedia,
|
|
23
|
+
generateMessageID
|
|
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
|
|
101
|
+
})
|
|
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
|
+
type: options.type || 'full',
|
|
142
|
+
categories: options.categories || ['馃椏'],
|
|
143
|
+
quality: options.quality || 80
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const buffer = await sticker.build()
|
|
147
|
+
const result = await socket.sendMessage(jid, { sticker: buffer })
|
|
148
|
+
|
|
149
|
+
if (Buffer.isBuffer(options.sticker)) fs.unlinkSync(stickerPath)
|
|
150
|
+
|
|
151
|
+
return result
|
|
152
|
+
} catch (e) {
|
|
153
|
+
console.error('[sendSticker Error]', e)
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
sendButton: async (jid, content = {}, options = {}) => {
|
|
158
|
+
if (!socket.user?.id) {
|
|
159
|
+
throw new Error("User not authenticated");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const {
|
|
163
|
+
text = "",
|
|
164
|
+
caption = "",
|
|
165
|
+
title = "",
|
|
166
|
+
footer = "",
|
|
167
|
+
buttons = [],
|
|
168
|
+
hasMediaAttachment = false,
|
|
169
|
+
image = null,
|
|
170
|
+
video = null,
|
|
171
|
+
document = null,
|
|
172
|
+
mimetype = null,
|
|
173
|
+
jpegThumbnail = null,
|
|
174
|
+
location = null,
|
|
175
|
+
product = null,
|
|
176
|
+
businessOwnerJid = null,
|
|
177
|
+
} = content;
|
|
178
|
+
|
|
179
|
+
if (!Array.isArray(buttons) || buttons.length ===
|
|
180
|
+
0) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
"buttons must be a non-empty array");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const interactiveButtons = [];
|
|
186
|
+
|
|
187
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
188
|
+
const btn = buttons[i];
|
|
189
|
+
|
|
190
|
+
if (!btn || typeof btn !== "object") {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`button[${i}] must be an object`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (btn.name && btn.buttonParamsJson) {
|
|
196
|
+
interactiveButtons.push(btn);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (btn.id || btn.text || btn.displayText) {
|
|
201
|
+
interactiveButtons.push({
|
|
202
|
+
name: "quick_reply",
|
|
203
|
+
buttonParamsJson: JSON
|
|
204
|
+
.stringify({
|
|
205
|
+
display_text: btn
|
|
206
|
+
.text || btn
|
|
207
|
+
.displayText ||
|
|
208
|
+
`Button ${i + 1}`,
|
|
209
|
+
id: btn.id ||
|
|
210
|
+
`quick_${i + 1}`,
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (btn.buttonId && btn.buttonText
|
|
217
|
+
?.displayText) {
|
|
218
|
+
interactiveButtons.push({
|
|
219
|
+
name: "quick_reply",
|
|
220
|
+
buttonParamsJson: JSON
|
|
221
|
+
.stringify({
|
|
222
|
+
display_text: btn
|
|
223
|
+
.buttonText
|
|
224
|
+
.displayText,
|
|
225
|
+
id: btn.buttonId,
|
|
226
|
+
}),
|
|
227
|
+
});
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
throw new Error(
|
|
232
|
+
`button[${i}] has invalid shape`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let messageContent = {};
|
|
236
|
+
if (image) {
|
|
237
|
+
const mediaInput = {};
|
|
238
|
+
if (Buffer.isBuffer(image)) {
|
|
239
|
+
mediaInput.image = image;
|
|
240
|
+
} else if (typeof image === "object" && image
|
|
241
|
+
.url) {
|
|
242
|
+
mediaInput.image = {
|
|
243
|
+
url: image.url
|
|
244
|
+
};
|
|
245
|
+
} else if (typeof image === "string") {
|
|
246
|
+
mediaInput.image = {
|
|
247
|
+
url: image
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const preparedMedia =
|
|
252
|
+
await prepareWAMessageMedia(mediaInput, {
|
|
253
|
+
upload: socket.waUploadToServer,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
messageContent.header = {
|
|
257
|
+
title: title || "",
|
|
258
|
+
hasMediaAttachment: hasMediaAttachment,
|
|
259
|
+
imageMessage: preparedMedia
|
|
260
|
+
.imageMessage,
|
|
261
|
+
};
|
|
262
|
+
} else if (video) {
|
|
263
|
+
const mediaInput = {};
|
|
264
|
+
if (Buffer.isBuffer(video)) {
|
|
265
|
+
mediaInput.video = video;
|
|
266
|
+
} else if (typeof video === "object" && video
|
|
267
|
+
.url) {
|
|
268
|
+
mediaInput.video = {
|
|
269
|
+
url: video.url
|
|
270
|
+
};
|
|
271
|
+
} else if (typeof video === "string") {
|
|
272
|
+
mediaInput.video = {
|
|
273
|
+
url: video
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const preparedMedia =
|
|
278
|
+
await prepareWAMessageMedia(mediaInput, {
|
|
279
|
+
upload: socket.waUploadToServer,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
messageContent.header = {
|
|
283
|
+
title: title || "",
|
|
284
|
+
hasMediaAttachment: hasMediaAttachment,
|
|
285
|
+
videoMessage: preparedMedia
|
|
286
|
+
.videoMessage,
|
|
287
|
+
};
|
|
288
|
+
} else if (document) {
|
|
289
|
+
const mediaInput = {
|
|
290
|
+
document: {}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
if (Buffer.isBuffer(document)) {
|
|
294
|
+
mediaInput.document = document;
|
|
295
|
+
} else if (typeof document === "object" &&
|
|
296
|
+
document.url) {
|
|
297
|
+
mediaInput.document = {
|
|
298
|
+
url: document.url
|
|
299
|
+
};
|
|
300
|
+
} else if (typeof document === "string") {
|
|
301
|
+
mediaInput.document = {
|
|
302
|
+
url: document
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (mimetype) {
|
|
307
|
+
if (typeof mediaInput.document ===
|
|
308
|
+
"object") {
|
|
309
|
+
mediaInput.document.mimetype = mimetype;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (jpegThumbnail) {
|
|
314
|
+
if (typeof mediaInput.document ===
|
|
315
|
+
"object") {
|
|
316
|
+
if (Buffer.isBuffer(jpegThumbnail)) {
|
|
317
|
+
mediaInput.document.jpegThumbnail =
|
|
318
|
+
jpegThumbnail;
|
|
319
|
+
} else if (typeof jpegThumbnail ===
|
|
320
|
+
"string") {
|
|
321
|
+
try {
|
|
322
|
+
const response = await fetch(
|
|
323
|
+
jpegThumbnail);
|
|
324
|
+
const arrayBuffer =
|
|
325
|
+
await response
|
|
326
|
+
.arrayBuffer();
|
|
327
|
+
mediaInput.document
|
|
328
|
+
.jpegThumbnail = Buffer
|
|
329
|
+
.from(arrayBuffer);
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const preparedMedia =
|
|
337
|
+
await prepareWAMessageMedia(mediaInput, {
|
|
338
|
+
upload: socket.waUploadToServer,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
messageContent.header = {
|
|
342
|
+
title: title || "",
|
|
343
|
+
hasMediaAttachment: hasMediaAttachment,
|
|
344
|
+
documentMessage: preparedMedia
|
|
345
|
+
.documentMessage,
|
|
346
|
+
};
|
|
347
|
+
} else if (location && typeof location ===
|
|
348
|
+
"object") {
|
|
349
|
+
messageContent.header = {
|
|
350
|
+
title: title || location.name ||
|
|
351
|
+
"Location",
|
|
352
|
+
hasMediaAttachment: hasMediaAttachment,
|
|
353
|
+
locationMessage: {
|
|
354
|
+
degreesLatitude: location
|
|
355
|
+
.degressLatitude || location
|
|
356
|
+
.degreesLatitude || 0,
|
|
357
|
+
degreesLongitude: location
|
|
358
|
+
.degressLongitude || location
|
|
359
|
+
.degreesLongitude || 0,
|
|
360
|
+
name: location.name || "",
|
|
361
|
+
address: location.address || "",
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
} else if (product && typeof product === "object") {
|
|
365
|
+
let productImageMessage = null;
|
|
366
|
+
if (product.productImage) {
|
|
367
|
+
const mediaInput = {};
|
|
368
|
+
if (Buffer.isBuffer(product.productImage)) {
|
|
369
|
+
mediaInput.image = product.productImage;
|
|
370
|
+
} else if (
|
|
371
|
+
typeof product.productImage ===
|
|
372
|
+
"object" &&
|
|
373
|
+
product.productImage.url
|
|
374
|
+
) {
|
|
375
|
+
mediaInput.image = {
|
|
376
|
+
url: product.productImage.url,
|
|
377
|
+
};
|
|
378
|
+
} else if (typeof product.productImage ===
|
|
379
|
+
"string") {
|
|
380
|
+
mediaInput.image = {
|
|
381
|
+
url: product.productImage,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const preparedMedia =
|
|
386
|
+
await prepareWAMessageMedia(
|
|
387
|
+
mediaInput, {
|
|
388
|
+
upload: socket.waUploadToServer,
|
|
389
|
+
});
|
|
390
|
+
productImageMessage = preparedMedia
|
|
391
|
+
.imageMessage;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
messageContent.header = {
|
|
395
|
+
title: title || product.title ||
|
|
396
|
+
"Product",
|
|
397
|
+
hasMediaAttachment: hasMediaAttachment,
|
|
398
|
+
productMessage: {
|
|
399
|
+
product: {
|
|
400
|
+
productImage: productImageMessage,
|
|
401
|
+
productId: product.productId ||
|
|
402
|
+
"",
|
|
403
|
+
title: product.title || "",
|
|
404
|
+
description: product
|
|
405
|
+
.description || "",
|
|
406
|
+
currencyCode: product
|
|
407
|
+
.currencyCode || "USD",
|
|
408
|
+
priceAmount1000: parseInt(
|
|
409
|
+
product.priceAmount1000
|
|
410
|
+
) || 0,
|
|
411
|
+
retailerId: product
|
|
412
|
+
.retailerId || "",
|
|
413
|
+
url: product.url || "",
|
|
414
|
+
productImageCount: product
|
|
415
|
+
.productImageCount || 1,
|
|
416
|
+
},
|
|
417
|
+
businessOwnerJid: businessOwnerJid ||
|
|
418
|
+
product.businessOwnerJid || socket
|
|
419
|
+
.user.id,
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
} else if (title) {
|
|
423
|
+
messageContent.header = {
|
|
424
|
+
title: title,
|
|
425
|
+
hasMediaAttachment: false,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const hasMedia = !!(image || video || document ||
|
|
430
|
+
location || product);
|
|
431
|
+
const bodyText = hasMedia ? caption : text ||
|
|
432
|
+
caption;
|
|
433
|
+
|
|
434
|
+
if (bodyText) {
|
|
435
|
+
messageContent.body = {
|
|
436
|
+
text: bodyText
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (footer) {
|
|
441
|
+
messageContent.footer = {
|
|
442
|
+
text: footer
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
messageContent.nativeFlowMessage = {
|
|
447
|
+
buttons: interactiveButtons,
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const payload = proto.Message.InteractiveMessage
|
|
451
|
+
.create(messageContent);
|
|
452
|
+
|
|
453
|
+
const msg = generateWAMessageFromContent(
|
|
454
|
+
jid, {
|
|
455
|
+
viewOnceMessage: {
|
|
456
|
+
message: {
|
|
457
|
+
interactiveMessage: payload,
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
}, {
|
|
461
|
+
userJid: socket.user.id,
|
|
462
|
+
quoted: options?.quoted || null,
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
const isGroup = jid.endsWith("@g.us");
|
|
466
|
+
const additionalNodes = [{
|
|
467
|
+
tag: "biz",
|
|
468
|
+
attrs: {},
|
|
469
|
+
content: [{
|
|
470
|
+
tag: "interactive",
|
|
471
|
+
attrs: {
|
|
472
|
+
type: "native_flow",
|
|
473
|
+
v: "1",
|
|
474
|
+
},
|
|
475
|
+
content: [{
|
|
476
|
+
tag: "native_flow",
|
|
477
|
+
attrs: {
|
|
478
|
+
v: "9",
|
|
479
|
+
name: "mixed",
|
|
480
|
+
},
|
|
481
|
+
}, ],
|
|
482
|
+
}, ],
|
|
483
|
+
}, ];
|
|
484
|
+
|
|
485
|
+
if (!isGroup) {
|
|
486
|
+
additionalNodes.push({
|
|
487
|
+
tag: "bot",
|
|
488
|
+
attrs: {
|
|
489
|
+
biz_bot: "1"
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
await socket.relayMessage(jid, msg.message, {
|
|
495
|
+
messageId: msg.key.id,
|
|
496
|
+
additionalNodes,
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
return msg;
|
|
500
|
+
},
|
|
501
|
+
sendAlbum: {
|
|
502
|
+
async value(jid, items = [], options = {}) {
|
|
503
|
+
if (!this.user?.id) {
|
|
504
|
+
throw new Error("User not authenticated");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const messageSecret = new Uint8Array(32);
|
|
508
|
+
crypto.getRandomValues(messageSecret);
|
|
509
|
+
|
|
510
|
+
const messageContent = {
|
|
511
|
+
messageContextInfo: { messageSecret },
|
|
512
|
+
albumMessage: {
|
|
513
|
+
expectedImageCount: items.filter((a) =>
|
|
514
|
+
a?.image).length,
|
|
515
|
+
expectedVideoCount: items.filter((a) =>
|
|
516
|
+
a?.video).length,
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const generationOptions = {
|
|
521
|
+
userJid: this.user.id,
|
|
522
|
+
upload: this.waUploadToServer,
|
|
523
|
+
quoted: options?.quoted || null,
|
|
524
|
+
ephemeralExpiration: options?.quoted
|
|
525
|
+
?.expiration ?? 0,
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const album = generateWAMessageFromContent(jid,
|
|
529
|
+
messageContent, generationOptions);
|
|
530
|
+
|
|
531
|
+
await this.relayMessage(album.key.remoteJid, album
|
|
532
|
+
.message, {
|
|
533
|
+
messageId: album.key.id,
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
await Promise.all(
|
|
537
|
+
items.map(async (content) => {
|
|
538
|
+
const mediaSecret =
|
|
539
|
+
new Uint8Array(32);
|
|
540
|
+
crypto.getRandomValues(
|
|
541
|
+
mediaSecret);
|
|
542
|
+
|
|
543
|
+
const mediaMsg =
|
|
544
|
+
await generateWAMessage(
|
|
545
|
+
album.key.remoteJid,
|
|
546
|
+
content, {
|
|
547
|
+
upload: this
|
|
548
|
+
.waUploadToServer,
|
|
549
|
+
ephemeralExpiration: options
|
|
550
|
+
?.quoted
|
|
551
|
+
?.expiration ??
|
|
552
|
+
0,
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
mediaMsg.message
|
|
556
|
+
.messageContextInfo = {
|
|
557
|
+
messageSecret: mediaSecret,
|
|
558
|
+
messageAssociation: {
|
|
559
|
+
associationType: 1,
|
|
560
|
+
parentMessageKey: album
|
|
561
|
+
.key,
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
return this.relayMessage(
|
|
566
|
+
mediaMsg.key.remoteJid,
|
|
567
|
+
mediaMsg.message, {
|
|
568
|
+
messageId: mediaMsg
|
|
569
|
+
.key.id,
|
|
570
|
+
});
|
|
571
|
+
})
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
return album;
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
sendOrder: async (jid, orderData, options = {}) => {
|
|
579
|
+
if (!socket.user?.id) {
|
|
580
|
+
throw new Error("User not authenticated");
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
let thumbnail = null;
|
|
584
|
+
if (orderData.thumbnail) {
|
|
585
|
+
if (Buffer.isBuffer(orderData.thumbnail)) {
|
|
586
|
+
thumbnail = orderData.thumbnail;
|
|
587
|
+
} else if (typeof orderData.thumbnail === "string") {
|
|
588
|
+
try {
|
|
589
|
+
if (orderData.thumbnail.startsWith("http")) {
|
|
590
|
+
const response = await fetch(orderData.thumbnail);
|
|
591
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
592
|
+
thumbnail = Buffer.from(arrayBuffer);
|
|
593
|
+
} else {
|
|
594
|
+
thumbnail = Buffer.from(orderData.thumbnail, "base64");
|
|
595
|
+
}
|
|
596
|
+
} catch (e) {
|
|
597
|
+
socket.logger?.warn(
|
|
598
|
+
{
|
|
599
|
+
err: e.message
|
|
600
|
+
},
|
|
601
|
+
"Failed to fetch/convert thumbnail"
|
|
602
|
+
);
|
|
603
|
+
thumbnail = null;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const orderMessage = proto.Message.OrderMessage.fromObject({
|
|
609
|
+
orderId: orderData.orderId || generateMessageID(),
|
|
610
|
+
thumbnail: thumbnail,
|
|
611
|
+
itemCount: orderData.itemCount || 1,
|
|
612
|
+
status: orderData.status || proto.Message.OrderMessage.OrderStatus.INQUIRY,
|
|
613
|
+
surface: orderData.surface || proto.Message.OrderMessage.OrderSurface.CATALOG,
|
|
614
|
+
message: orderData.message || "",
|
|
615
|
+
orderTitle: orderData.orderTitle || "Order",
|
|
616
|
+
sellerJid: orderData.sellerJid || socket.user.id,
|
|
617
|
+
token: orderData.token || "",
|
|
618
|
+
totalAmount1000: orderData.totalAmount1000 || 0,
|
|
619
|
+
totalCurrencyCode: orderData.totalCurrencyCode || "IDR",
|
|
620
|
+
contextInfo: {
|
|
621
|
+
...(options.contextInfo || {}),
|
|
622
|
+
...(options.mentions ?
|
|
623
|
+
{
|
|
624
|
+
mentionedJid: options.mentions,
|
|
625
|
+
} :
|
|
626
|
+
{}),
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
const msg = proto.Message.create({
|
|
631
|
+
orderMessage,
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const message = generateWAMessageFromContent(jid, msg, {
|
|
635
|
+
userJid: socket.user.id,
|
|
636
|
+
timestamp: options.timestamp || new Date(),
|
|
637
|
+
quoted: options.quoted || null,
|
|
638
|
+
ephemeralExpiration: options.ephemeralExpiration || 0,
|
|
639
|
+
messageId: options.messageId || null,
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
return await socket.relayMessage(message.key.remoteJid, message.message, {
|
|
643
|
+
messageId: message.key.id,
|
|
644
|
+
});
|
|
645
|
+
},
|
|
646
|
+
|
|
647
|
+
getPNFromLid: async (m, lidInput) => {
|
|
648
|
+
if (!lidInput) throw new Error("Misssing input");
|
|
649
|
+
try {
|
|
650
|
+
let chat = m.chat
|
|
651
|
+
if (chat.endsWith('@g.us')) {
|
|
652
|
+
|
|
653
|
+
const metadata = await socket.groupMetadata(chat);
|
|
654
|
+
if (!metadata || !metadata.participants) return lidInput;
|
|
655
|
+
|
|
656
|
+
const found = metadata.participants.find(entry => entry.id === lidInput);
|
|
657
|
+
return found ? found.phoneNumber : lidInput;
|
|
658
|
+
} else {
|
|
659
|
+
const { remoteJid, remoteJidAlt } = m.key
|
|
660
|
+
if (remoteJid === lidInput && remoteJidAlt.endsWith("@s.whatsapp.net")) {
|
|
661
|
+
return remoteJidAlt
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
} catch (e) {
|
|
665
|
+
console.error('Gagal ambil group metadata:', e.message);
|
|
666
|
+
return lidInput;
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
|
|
670
|
+
getLidFromPN: async (m, jidInput) => {
|
|
671
|
+
if (!jidInput) throw new Error("Misssing jid input");
|
|
672
|
+
try {
|
|
673
|
+
let chat = m.chat;
|
|
674
|
+
if (chat.endsWith('@g.us')) {
|
|
675
|
+
const metadata = await socket.groupMetadata(chat);
|
|
676
|
+
if (!metadata || !metadata.participants) return jidInput;
|
|
677
|
+
|
|
678
|
+
const found = metadata.participants.find(entry => entry.phoneNumber === jidInput);
|
|
679
|
+
return found ? found.id : jidInput;
|
|
680
|
+
} else {
|
|
681
|
+
const { remoteJid, remoteJidAlt } = m.key
|
|
682
|
+
if (!remoteJid || !remoteJidAlt) return null
|
|
683
|
+
if (remoteJidAlt === jidInput && remoteJid.endsWith("@lid")) {
|
|
684
|
+
return remoteJid
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
} catch (e) {
|
|
688
|
+
console.error('Gagal ambil group metadata:', e.message);
|
|
689
|
+
return jidInput;
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom SQLite Auth Store untuk Baileys
|
|
3
|
+
* by Ryuu
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
//import Database from "bun:sqlite";
|
|
7
|
+
//jika menggunakan bun runtime
|
|
8
|
+
import Database from "better-sqlite3";
|
|
9
|
+
/**
|
|
10
|
+
* Membuat atau mengambil auth state dari SQLite
|
|
11
|
+
* @param {string} dbPath - Lokasi file SQLite (contoh: "./auth.db")
|
|
12
|
+
*/
|
|
13
|
+
export async function useSQLiteAuthState(dbPath = "./auth.db", baileys) {
|
|
14
|
+
const { proto, BufferJSON, initAuthCreds } = baileys;
|
|
15
|
+
const db = new Database(dbPath);
|
|
16
|
+
db.pragma("journal_mode = WAL");
|
|
17
|
+
|
|
18
|
+
db.prepare(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS baileys_state (
|
|
20
|
+
key TEXT PRIMARY KEY,
|
|
21
|
+
value BLOB
|
|
22
|
+
)
|
|
23
|
+
`).run();
|
|
24
|
+
|
|
25
|
+
const load = (key) => {
|
|
26
|
+
const row = db.prepare("SELECT value FROM baileys_state WHERE key = ?").get(key);
|
|
27
|
+
if (!row) return null;
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(row.value.toString(), BufferJSON.reviver);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const save = (key, data) => {
|
|
36
|
+
const json = JSON.stringify(data, BufferJSON.replacer);
|
|
37
|
+
const buf = Buffer.from(json, "utf8");
|
|
38
|
+
db.prepare("REPLACE INTO baileys_state (key, value) VALUES (?, ?)").run(key, buf);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const creds = load("creds") || initAuthCreds();
|
|
42
|
+
|
|
43
|
+
const keys = {};
|
|
44
|
+
const categories = [
|
|
45
|
+
"pre-key",
|
|
46
|
+
"session",
|
|
47
|
+
"sender-key",
|
|
48
|
+
"app-state-sync-key",
|
|
49
|
+
"app-state-sync-version"
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
for (const category of categories) {
|
|
53
|
+
keys[category] = {};
|
|
54
|
+
const rows = db
|
|
55
|
+
.prepare("SELECT key, value FROM baileys_state WHERE key LIKE ?")
|
|
56
|
+
.all(`${category}:%`);
|
|
57
|
+
for (const row of rows) {
|
|
58
|
+
try {
|
|
59
|
+
keys[category][row.key.slice(category.length + 1)] = JSON.parse(row.value.toString());
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function saveCreds() {
|
|
65
|
+
save("creds", creds);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const set = (category, id, value) => {
|
|
69
|
+
const key = `${category}:${id}`;
|
|
70
|
+
save(key, value);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const get = (category, id) => {
|
|
74
|
+
const key = `${category}:${id}`;
|
|
75
|
+
return load(key);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const del = (category, id) => {
|
|
79
|
+
const key = `${category}:${id}`;
|
|
80
|
+
db.prepare("DELETE FROM baileys_state WHERE key = ?").run(key);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
state: {
|
|
85
|
+
creds,
|
|
86
|
+
keys: {
|
|
87
|
+
get: async (type, ids) => {
|
|
88
|
+
const data = {};
|
|
89
|
+
for (const id of ids) {
|
|
90
|
+
const value = load(`${type}:${id}`);
|
|
91
|
+
if (value) data[id] = value;
|
|
92
|
+
}
|
|
93
|
+
return data;
|
|
94
|
+
},
|
|
95
|
+
set: async (data) => {
|
|
96
|
+
for (const category in data) {
|
|
97
|
+
for (const id in data[category]) {
|
|
98
|
+
const value = data[category][id];
|
|
99
|
+
save(`${category}:${id}`, value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
saveCreds: async () => save("creds", creds)
|
|
106
|
+
};
|
|
107
|
+
setInterval(async () => {
|
|
108
|
+
await save("creds", creds);
|
|
109
|
+
}, 30_000);
|
|
110
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ryuu-reinzz/haruka-lib",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Library extra for bot WhatsApp",
|
|
5
|
+
"main": "main/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "RyuuReinzz",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"whatsapp-bot",
|
|
11
|
+
"baileys",
|
|
12
|
+
"haruka",
|
|
13
|
+
"nodejs",
|
|
14
|
+
"ryuu-kun",
|
|
15
|
+
":v"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"better-sqlite3": "^12.5.0",
|
|
19
|
+
"node-fetch": "^2.6.1",
|
|
20
|
+
"wa-sticker-formatter": "^4.4.4"
|
|
21
|
+
}
|
|
22
|
+
}
|