@innovatorssoft/innovators-bot2 1.2.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/README.md +718 -0
- package/example.jpg +0 -0
- package/example.js +710 -0
- package/index.js +1494 -0
- package/package.json +42 -0
- package/publish-dual.js +40 -0
package/index.js
ADDED
|
@@ -0,0 +1,1494 @@
|
|
|
1
|
+
const {
|
|
2
|
+
makeWASocket,
|
|
3
|
+
Browsers,
|
|
4
|
+
useMultiFileAuthState,
|
|
5
|
+
DisconnectReason,
|
|
6
|
+
fetchLatestBaileysVersion,
|
|
7
|
+
fetchLatestWaWebVersion,
|
|
8
|
+
downloadMediaMessage,
|
|
9
|
+
getCurrentSenderInfo,
|
|
10
|
+
// JID Utilities
|
|
11
|
+
parseJid,
|
|
12
|
+
plotJid,
|
|
13
|
+
normalizePhoneToJid,
|
|
14
|
+
// Anti-Delete
|
|
15
|
+
MessageStore,
|
|
16
|
+
createMessageStoreHandler,
|
|
17
|
+
createAntiDeleteHandler
|
|
18
|
+
} = require('@innovatorssoft/baileys');
|
|
19
|
+
|
|
20
|
+
const { Sticker, StickerTypes } = require('wa-sticker-formatter');
|
|
21
|
+
|
|
22
|
+
const { Boom } = require('@hapi/boom');
|
|
23
|
+
const { EventEmitter } = require('events');
|
|
24
|
+
const P = require('pino');
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const mime = require('mime');
|
|
28
|
+
const figlet = require('figlet');
|
|
29
|
+
const NodeCache = require('node-cache');
|
|
30
|
+
|
|
31
|
+
process.title = 'INNOVATORS Soft WhatsApp Server 92 322 4559543'
|
|
32
|
+
|
|
33
|
+
console.log(figlet.textSync('WELCOME To'))
|
|
34
|
+
console.log(figlet.textSync('INNOVATORS'))
|
|
35
|
+
console.log(figlet.textSync('SOFT'))
|
|
36
|
+
|
|
37
|
+
class Group {
|
|
38
|
+
constructor(client, groupData) {
|
|
39
|
+
this.client = client
|
|
40
|
+
this.id = groupData.id
|
|
41
|
+
this.subject = groupData.subject
|
|
42
|
+
this.creation = groupData.creation
|
|
43
|
+
this.owner = groupData.owner
|
|
44
|
+
this.desc = groupData.desc
|
|
45
|
+
this.participants = groupData.participants
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class WhatsAppClient extends EventEmitter {
|
|
50
|
+
constructor(config = {}) {
|
|
51
|
+
super()
|
|
52
|
+
this.sock = null
|
|
53
|
+
this.isConnected = false
|
|
54
|
+
this.sessionName = config.sessionName || 'auth_info_baileys'
|
|
55
|
+
this._connectionState = 'disconnected'
|
|
56
|
+
this.authmethod = config.authmethod || 'qr';
|
|
57
|
+
this._reconnectDelay = 5000;
|
|
58
|
+
this.pairingPhoneNumber = config.pairingPhoneNumber || null;
|
|
59
|
+
this.groupMetadataCache = new NodeCache({ stdTTL: 600, checkperiod: 120 });
|
|
60
|
+
this.messageStore = new MessageStore({
|
|
61
|
+
maxMessagesPerChat: config.maxMessagesPerChat || 1000,
|
|
62
|
+
ttl: config.messageTTL || 24 * 60 * 60 * 1000
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Helper method to resolve LID to PN (Phone Number) if available and normalize JID
|
|
68
|
+
* @param {string} jid - The JID to resolve (could be LID or PN)
|
|
69
|
+
* @returns {Promise<string>} The resolved and normalized PN if LID mapping exists, otherwise the normalized original JID
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
async _resolveLidToPn(jid) {
|
|
73
|
+
if (!jid) return jid;
|
|
74
|
+
|
|
75
|
+
// If it's a LID, try to resolve it to PN
|
|
76
|
+
if (jid.endsWith('@lid')) {
|
|
77
|
+
try {
|
|
78
|
+
const phoneNumber = await this.getPNForLID(jid);
|
|
79
|
+
if (phoneNumber) {
|
|
80
|
+
// Normalize the resolved PN by removing device ID
|
|
81
|
+
return this._normalizeJid(phoneNumber);
|
|
82
|
+
}
|
|
83
|
+
return jid; // Return original LID if no PN found
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('Error resolving LID to PN:', error);
|
|
86
|
+
return jid; // Return original JID on error
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// If it's already a PN or other format, normalize and return
|
|
91
|
+
return this._normalizeJid(jid);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Normalize JID by removing device ID suffix (e.g., :0)
|
|
96
|
+
* Converts 923014434335:0@s.whatsapp.net to 923014434335@s.whatsapp.net
|
|
97
|
+
* @param {string} jid - The JID to normalize
|
|
98
|
+
* @returns {string} Normalized JID
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
_normalizeJid(jid) {
|
|
102
|
+
if (!jid) return jid;
|
|
103
|
+
|
|
104
|
+
// Remove device ID (e.g., :0, :1, etc.) from the JID
|
|
105
|
+
// Pattern: number:deviceId@server becomes number@server
|
|
106
|
+
return jid.replace(/:\d+@/, '@');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async connect() {
|
|
110
|
+
try {
|
|
111
|
+
|
|
112
|
+
if (this._connectionState !== 'connecting') {
|
|
113
|
+
this._connectionState = 'connecting';
|
|
114
|
+
this.emit('connecting', 'Connecting to WhatsApp...');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const browserConfig = this.authmethod === 'pairing'
|
|
118
|
+
? Browsers.windows('chrome')
|
|
119
|
+
: ["Innovators Soft", "chrome", "1000.26100.275.0"];
|
|
120
|
+
|
|
121
|
+
const { version: baileysVersion, isLatest: baileysIsLatest } = await fetchLatestBaileysVersion();
|
|
122
|
+
const { version: waWebVersion, isLatest: waWebIsLatest } = await fetchLatestWaWebVersion();
|
|
123
|
+
|
|
124
|
+
console.log('Using Baileys Version:', baileysVersion, baileysIsLatest ? ' isLatest true' : ' isLatest false');
|
|
125
|
+
console.log('Using WhatsApp Web Version:', waWebVersion, waWebIsLatest ? ' isLatest true' : ' isLatest false');
|
|
126
|
+
|
|
127
|
+
const { state, saveCreds } = await useMultiFileAuthState(this.sessionName)
|
|
128
|
+
const logger = P({ level: 'silent' })
|
|
129
|
+
|
|
130
|
+
this.sock = makeWASocket({
|
|
131
|
+
auth: state,
|
|
132
|
+
logger,
|
|
133
|
+
markOnlineOnConnect: false,
|
|
134
|
+
syncFullHistory: true,
|
|
135
|
+
generateHighQualityLinkPreview: true,
|
|
136
|
+
linkPreviewImageThumbnailWidth: 192,
|
|
137
|
+
emitOwnEvents: true,
|
|
138
|
+
browser: browserConfig,
|
|
139
|
+
version: waWebVersion,
|
|
140
|
+
cachedGroupMetadata: async (jid) => {
|
|
141
|
+
const cached = this.groupMetadataCache.get(jid);
|
|
142
|
+
if (cached) {
|
|
143
|
+
return cached;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const metadata = await this.sock.groupMetadata(jid);
|
|
147
|
+
this.groupMetadataCache.set(jid, metadata);
|
|
148
|
+
return metadata;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(`Error fetching metadata for group ${jid}:`, error);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
this.store = this.sock.signalRepository.lidMapping;
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
this.sock.ev.on('connection.update', async (update) => {
|
|
161
|
+
const { connection, lastDisconnect, qr } = update;
|
|
162
|
+
|
|
163
|
+
if (qr && this.authmethod === 'qr') {
|
|
164
|
+
this.emit('qr', qr);
|
|
165
|
+
}
|
|
166
|
+
if (connection === 'open') {
|
|
167
|
+
if (this._connectionState !== 'connected') {
|
|
168
|
+
const user = getCurrentSenderInfo(this.sock.authState)
|
|
169
|
+
if (user) {
|
|
170
|
+
const userInfo = {
|
|
171
|
+
name: user.pushName || 'Unknown',
|
|
172
|
+
phone: user.phoneNumber,
|
|
173
|
+
platform: user.platform || 'Unknown',
|
|
174
|
+
isOnline: true
|
|
175
|
+
};
|
|
176
|
+
this.isConnected = true;
|
|
177
|
+
this._connectionState = 'connected';
|
|
178
|
+
this.emit('connected', userInfo);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (connection === 'close') {
|
|
183
|
+
const shouldReconnect = (lastDisconnect?.error instanceof Boom) ?
|
|
184
|
+
lastDisconnect.error?.output?.statusCode !== DisconnectReason.loggedOut : true;
|
|
185
|
+
|
|
186
|
+
if (this._connectionState !== 'disconnected') {
|
|
187
|
+
this.isConnected = false;
|
|
188
|
+
this._connectionState = 'disconnected';
|
|
189
|
+
this.emit('disconnected', lastDisconnect?.error);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (shouldReconnect) {
|
|
193
|
+
this.connect();
|
|
194
|
+
} else if (lastDisconnect?.error?.output?.statusCode === DisconnectReason.loggedOut) {
|
|
195
|
+
await this.reinitialize();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Handle pairing code request after connection is established but before login
|
|
200
|
+
if (this.authmethod === 'pairing' && connection === 'connecting' && !state.creds?.registered) {
|
|
201
|
+
const phoneNumber = this.pairingPhoneNumber;
|
|
202
|
+
|
|
203
|
+
if (phoneNumber) {
|
|
204
|
+
try {
|
|
205
|
+
// Wait a bit for the connection to initialize properly
|
|
206
|
+
setTimeout(async () => {
|
|
207
|
+
const customeCode = "INOVATOR";
|
|
208
|
+
try {
|
|
209
|
+
const code = await this.sock.requestPairingCode(phoneNumber, customeCode);
|
|
210
|
+
if (code) {
|
|
211
|
+
// Emit pairing code event so clients can handle it
|
|
212
|
+
this.emit('pairing-code', formatCode(code));
|
|
213
|
+
} else {
|
|
214
|
+
console.log("❌ Pairing code not found.");
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error requesting pairing code:', error);
|
|
218
|
+
this.emit('error', error);
|
|
219
|
+
}
|
|
220
|
+
}, 2000); // Wait 2 seconds before requesting pairing code
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error('Error setting timeout for pairing code:', error);
|
|
223
|
+
this.emit('error', error);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Handle incoming messages
|
|
231
|
+
* @emits WhatsAppClient#message - When a new message is received
|
|
232
|
+
* @param {object} update - The message update object
|
|
233
|
+
*/
|
|
234
|
+
|
|
235
|
+
this.sock.ev.on('messages.upsert', async (update) => {
|
|
236
|
+
// 📝 Store message for the Anti-Delete system
|
|
237
|
+
createMessageStoreHandler(this.messageStore)(update);
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
if (update.type !== 'notify' || !update.messages?.length) return;
|
|
241
|
+
const [message] = update.messages;
|
|
242
|
+
if (!message || message.key?.fromMe) return;
|
|
243
|
+
|
|
244
|
+
// 🚫 Ignore all protocol messages (history sync, security notifications, app state sync, deleted messages, etc.)
|
|
245
|
+
if (message.message?.protocolMessage) return;
|
|
246
|
+
|
|
247
|
+
const msg = message.message || {};
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
let jid = message.key.remoteJid;
|
|
251
|
+
|
|
252
|
+
// Prefer PN (Phone Number) JID over LID for better compatibility
|
|
253
|
+
// remoteJidAlt contains the alternate JID (PN if primary is LID, or vice versa)
|
|
254
|
+
if (typeof message.key.remoteJidAlt === 'string' &&
|
|
255
|
+
message.key.remoteJidAlt.endsWith('@s.whatsapp.net')
|
|
256
|
+
) {
|
|
257
|
+
jid = message.key.remoteJidAlt;
|
|
258
|
+
}
|
|
259
|
+
// Resolve the actual sender (preferring PN over LID)
|
|
260
|
+
const participant = message.key.participant || message.participant || null;
|
|
261
|
+
const participantAlt = message.key.participantAlt || null;
|
|
262
|
+
|
|
263
|
+
let sender = jid;
|
|
264
|
+
if (jid.endsWith('@g.us') || jid === 'status@broadcast') {
|
|
265
|
+
sender = (participantAlt && participantAlt.endsWith('@s.whatsapp.net'))
|
|
266
|
+
? participantAlt
|
|
267
|
+
: (participant || jid);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Resolve LID to PN for sender if needed
|
|
271
|
+
sender = await this._resolveLidToPn(sender);
|
|
272
|
+
|
|
273
|
+
const timestamp = new Date((message.messageTimestamp || Date.now()) * 1000);
|
|
274
|
+
|
|
275
|
+
const getText = () =>
|
|
276
|
+
msg.conversation ||
|
|
277
|
+
msg.extendedTextMessage?.text ||
|
|
278
|
+
msg.imageMessage?.caption ||
|
|
279
|
+
msg.videoMessage?.caption ||
|
|
280
|
+
'';
|
|
281
|
+
|
|
282
|
+
const getButtonText = () => {
|
|
283
|
+
if (msg.listResponseMessage)
|
|
284
|
+
return msg.listResponseMessage.title || msg.listResponseMessage.description || '';
|
|
285
|
+
if (msg.templateButtonReplyMessage)
|
|
286
|
+
return msg.templateButtonReplyMessage.selectedDisplayText || msg.templateButtonReplyMessage.selectedId || '';
|
|
287
|
+
if (msg.buttonsResponseMessage)
|
|
288
|
+
return msg.buttonsResponseMessage.selectedDisplayText || msg.buttonsResponseMessage.selectedButtonId || '';
|
|
289
|
+
if (msg.interactiveResponseMessage) {
|
|
290
|
+
const i = msg.interactiveResponseMessage;
|
|
291
|
+
return i.listResponse?.title ||
|
|
292
|
+
i.listResponse?.description ||
|
|
293
|
+
i.nativeFlowResponse?.response?.reply ||
|
|
294
|
+
i.reply ||
|
|
295
|
+
i.buttonReplyMessage?.displayText || '';
|
|
296
|
+
}
|
|
297
|
+
return '';
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const reply = async (text) => this.sock.sendMessage(jid, { text }, { quoted: message, ai: true });
|
|
301
|
+
|
|
302
|
+
// ✅ Handle status updates (stories)
|
|
303
|
+
if (jid === 'status@broadcast') {
|
|
304
|
+
this.emit('status', {
|
|
305
|
+
from: sender,
|
|
306
|
+
sender,
|
|
307
|
+
participant,
|
|
308
|
+
participantAlt,
|
|
309
|
+
body: getText(),
|
|
310
|
+
hasMedia: Boolean(msg.imageMessage || msg.videoMessage),
|
|
311
|
+
timestamp,
|
|
312
|
+
key: message.key,
|
|
313
|
+
raw: message,
|
|
314
|
+
// Reply to status
|
|
315
|
+
reply: async (text) => {
|
|
316
|
+
if (!sender) throw new Error('Missing participant JID');
|
|
317
|
+
return this.sock.sendMessage(sender, { text }, { quoted: message, ai: true });
|
|
318
|
+
},
|
|
319
|
+
// 👍 Like (react) to status
|
|
320
|
+
like: async (emoji = '❤️') => {
|
|
321
|
+
if (!sender) throw new Error('Missing participant JID');
|
|
322
|
+
|
|
323
|
+
// Read the message first
|
|
324
|
+
try {
|
|
325
|
+
await this.sock.readMessages([message.key]);
|
|
326
|
+
} catch (readError) {
|
|
327
|
+
console.error('Error reading status message:', readError);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Then send the reaction
|
|
331
|
+
return this.sock.sendMessage(sender, {
|
|
332
|
+
react: {
|
|
333
|
+
text: emoji,
|
|
334
|
+
key: message.key
|
|
335
|
+
}
|
|
336
|
+
}, { ai: true });
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ✅ Handle normal/chat messages
|
|
343
|
+
const buttonText = getButtonText();
|
|
344
|
+
const body = buttonText || getText() ||
|
|
345
|
+
msg.listResponseMessage?.singleSelectReply?.selectedRowId || '';
|
|
346
|
+
|
|
347
|
+
this.emit('message', {
|
|
348
|
+
from: jid,
|
|
349
|
+
sender,
|
|
350
|
+
participant,
|
|
351
|
+
participantAlt,
|
|
352
|
+
body,
|
|
353
|
+
hasMedia: Boolean(msg.imageMessage || msg.videoMessage || msg.audioMessage || msg.documentMessage),
|
|
354
|
+
isGroup: jid.endsWith('@g.us'),
|
|
355
|
+
timestamp,
|
|
356
|
+
isButtonResponse: Boolean(buttonText),
|
|
357
|
+
buttonId: msg?.listResponseMessage?.singleSelectReply?.selectedRowId ||
|
|
358
|
+
msg?.templateButtonReplyMessage?.selectedId ||
|
|
359
|
+
msg?.buttonsResponseMessage?.selectedButtonId || null,
|
|
360
|
+
buttonText,
|
|
361
|
+
raw: message,
|
|
362
|
+
reply,
|
|
363
|
+
});
|
|
364
|
+
} catch (err) {
|
|
365
|
+
console.error('Error processing message:', err);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// 🛡️ Anti-Delete System: Handle message revokes/deletions
|
|
370
|
+
const antiDeleteHandler = createAntiDeleteHandler(this.messageStore);
|
|
371
|
+
|
|
372
|
+
this.sock.ev.on('messages.update', (updates) => {
|
|
373
|
+
const deletedMessages = antiDeleteHandler(updates);
|
|
374
|
+
for (const info of deletedMessages) {
|
|
375
|
+
let jid = info.key.remoteJid;
|
|
376
|
+
if (typeof info.key.remoteJidAlt === 'string' && info.key.remoteJidAlt.endsWith('@s.whatsapp.net')) {
|
|
377
|
+
jid = info.key.remoteJidAlt;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
this.emit('message-deleted', {
|
|
381
|
+
jid: jid,
|
|
382
|
+
originalMessage: info.originalMessage,
|
|
383
|
+
key: info.key
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// 👍 Handle message reactions
|
|
389
|
+
this.sock.ev.on('messages.reaction', async (reactions) => {
|
|
390
|
+
try {
|
|
391
|
+
for (const reaction of reactions) {
|
|
392
|
+
// Get the chat JID, preferring PN over LID
|
|
393
|
+
let jid = reaction.key.remoteJid;
|
|
394
|
+
if (typeof reaction.key.remoteJidAlt === 'string' &&
|
|
395
|
+
reaction.key.remoteJidAlt.endsWith('@s.whatsapp.net')) {
|
|
396
|
+
jid = reaction.key.remoteJidAlt;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Resolve the sender (who reacted), preferring PN over LID
|
|
400
|
+
const participant = reaction.key.participant || null;
|
|
401
|
+
const participantAlt = reaction.key.participantAlt || null;
|
|
402
|
+
|
|
403
|
+
let sender = jid;
|
|
404
|
+
if (jid.endsWith('@g.us') || jid === 'status@broadcast') {
|
|
405
|
+
sender = (participantAlt && participantAlt.endsWith('@s.whatsapp.net'))
|
|
406
|
+
? participantAlt
|
|
407
|
+
: (participant || jid);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Resolve LID to PN for sender if needed
|
|
411
|
+
sender = await this._resolveLidToPn(sender);
|
|
412
|
+
|
|
413
|
+
// Emit the reaction event
|
|
414
|
+
this.emit('message-reaction', {
|
|
415
|
+
from: jid,
|
|
416
|
+
sender: sender,
|
|
417
|
+
participant: participant,
|
|
418
|
+
participantAlt: participantAlt,
|
|
419
|
+
emoji: reaction.reaction?.text || null,
|
|
420
|
+
isRemoved: !reaction.reaction?.text,
|
|
421
|
+
messageKey: reaction.key,
|
|
422
|
+
timestamp: new Date(),
|
|
423
|
+
raw: reaction
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error('Error processing message reaction:', error);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Handle incoming calls
|
|
432
|
+
this.sock.ev.on('call', async (call) => {
|
|
433
|
+
try {
|
|
434
|
+
// Extract phone number from LID if available
|
|
435
|
+
for (const callData of call) {
|
|
436
|
+
if (callData.chatId || callData.from) {
|
|
437
|
+
const jid = callData.chatId || callData.from;
|
|
438
|
+
|
|
439
|
+
// Resolve LID to PN using the helper method
|
|
440
|
+
const resolvedJid = await this._resolveLidToPn(jid);
|
|
441
|
+
callData.phoneNumber = resolvedJid.split(':')[0].split('@')[0];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
await this.emit('call', call);
|
|
446
|
+
} catch (error) {
|
|
447
|
+
console.error('Error in call handler:', error);
|
|
448
|
+
this.emit('error', error);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Handle LID/PN mapping updates
|
|
453
|
+
this.sock.ev.on('lid-mapping.update', async (update) => {
|
|
454
|
+
try {
|
|
455
|
+
// Store the mapping for future use
|
|
456
|
+
if (update && Object.keys(update).length > 0) {
|
|
457
|
+
// The update object contains PN -> LID mappings
|
|
458
|
+
// They are automatically stored in sock.signalRepository.lidMapping
|
|
459
|
+
this.emit('lid-mapping-update', update);
|
|
460
|
+
}
|
|
461
|
+
} catch (error) {
|
|
462
|
+
console.error('Error processing LID mapping update:', error);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Handle credential updates
|
|
467
|
+
this.sock.ev.on('creds.update', saveCreds);
|
|
468
|
+
|
|
469
|
+
// Handle group events to keep the cache updated
|
|
470
|
+
this.sock.ev.on('groups.upsert', (groups) => {
|
|
471
|
+
for (const group of groups) {
|
|
472
|
+
this.updateGroupMetadataCache(group.id, group);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
this.sock.ev.on('groups.update', (groups) => {
|
|
477
|
+
for (const group of groups) {
|
|
478
|
+
// Get existing cache and update it with new information
|
|
479
|
+
const cached = this.groupMetadataCache.get(group.id);
|
|
480
|
+
if (cached) {
|
|
481
|
+
// Merge the updated fields with existing cached data
|
|
482
|
+
const updated = { ...cached, ...group };
|
|
483
|
+
this.updateGroupMetadataCache(group.id, updated);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
this.sock.ev.on('group-participants.update', async (update) => {
|
|
489
|
+
// When participants change, refresh the group metadata
|
|
490
|
+
try {
|
|
491
|
+
const metadata = await this.sock.groupMetadata(update.id);
|
|
492
|
+
this.updateGroupMetadataCache(update.id, metadata);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
// Check if group no longer exists or bot was removed (404 item-not-found)
|
|
495
|
+
const isNotFound = error.data === 404 ||
|
|
496
|
+
error.message?.includes('item-not-found') ||
|
|
497
|
+
error.output?.statusCode === 404;
|
|
498
|
+
|
|
499
|
+
if (isNotFound) {
|
|
500
|
+
// Group no longer exists or bot was removed - clean up cache
|
|
501
|
+
this.clearGroupMetadataCache(update.id);
|
|
502
|
+
this.emit('group-left', {
|
|
503
|
+
id: update.id,
|
|
504
|
+
reason: 'Group not found or bot was removed'
|
|
505
|
+
});
|
|
506
|
+
} else {
|
|
507
|
+
console.error(`Error refreshing metadata for group ${update.id}:`, error);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
} catch (error) {
|
|
513
|
+
console.error('Error in connect:', error);
|
|
514
|
+
this.emit('error', error);
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Send a message to a chat
|
|
521
|
+
* @param {string} chatId - The ID of the chat to send the message to
|
|
522
|
+
* @param {string|object} message - The message content (string) or message object
|
|
523
|
+
* @param {object} options - Additional options for sending the message
|
|
524
|
+
* @returns {Promise<object>} The sent message info
|
|
525
|
+
* @throws {Error} If client is not connected or message sending fails
|
|
526
|
+
*/
|
|
527
|
+
async sendMessage(chatId, message, options = {}) {
|
|
528
|
+
if (!this.isConnected) {
|
|
529
|
+
throw new Error('Client is not connected');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
let messageContent = {};
|
|
533
|
+
|
|
534
|
+
// Handle different message types
|
|
535
|
+
if (typeof message === 'string') {
|
|
536
|
+
messageContent = { text: message };
|
|
537
|
+
} else if (message && typeof message === 'object') {
|
|
538
|
+
// Handle different message types
|
|
539
|
+
switch (message.type) {
|
|
540
|
+
case 'text':
|
|
541
|
+
messageContent = { text: message.text };
|
|
542
|
+
if (message.mentions) {
|
|
543
|
+
messageContent.mentions = message.mentions;
|
|
544
|
+
}
|
|
545
|
+
break;
|
|
546
|
+
|
|
547
|
+
case 'location':
|
|
548
|
+
messageContent = {
|
|
549
|
+
location: {
|
|
550
|
+
degreesLatitude: message.latitude,
|
|
551
|
+
degreesLongitude: message.longitude,
|
|
552
|
+
name: message.name,
|
|
553
|
+
address: message.address
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
break;
|
|
557
|
+
|
|
558
|
+
case 'contact':
|
|
559
|
+
messageContent = {
|
|
560
|
+
contacts: {
|
|
561
|
+
displayName: message.fullName,
|
|
562
|
+
contacts: [{
|
|
563
|
+
displayName: message.fullName,
|
|
564
|
+
vcard: `BEGIN:VCARD\nVERSION:3.0\n` +
|
|
565
|
+
`FN:${message.fullName}\n` +
|
|
566
|
+
(message.organization ? `ORG:${message.organization};\n` : '') +
|
|
567
|
+
(message.phoneNumber ? `TEL;type=CELL;type=VOICE;waid=${message.phoneNumber}:+${message.phoneNumber}\n` : '') +
|
|
568
|
+
'END:VCARD'
|
|
569
|
+
}]
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
break;
|
|
573
|
+
|
|
574
|
+
case 'reaction':
|
|
575
|
+
messageContent = {
|
|
576
|
+
react: {
|
|
577
|
+
text: message.emoji,
|
|
578
|
+
key: message.messageKey || message.message?.key || message.key
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
break;
|
|
582
|
+
|
|
583
|
+
default:
|
|
584
|
+
throw new Error('Invalid message type');
|
|
585
|
+
}
|
|
586
|
+
} else {
|
|
587
|
+
throw new Error('Invalid message content');
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
return await this.sock.sendMessage(chatId, messageContent, { ...options, ai: true });
|
|
592
|
+
} catch (error) {
|
|
593
|
+
console.error('Error sending message:', error);
|
|
594
|
+
throw error;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Send a media file to a chat
|
|
599
|
+
* @param {string} chatId - The ID of the chat to send the media to
|
|
600
|
+
* @param {string} filePath - Path to the media file
|
|
601
|
+
* @param {object} options - Additional options for the media message
|
|
602
|
+
* @returns {Promise<object>} The sent message info
|
|
603
|
+
* @throws {Error} If client is not connected or file not found
|
|
604
|
+
*/
|
|
605
|
+
async sendMedia(chatId, filePath, options = {}) {
|
|
606
|
+
if (!this.isConnected) {
|
|
607
|
+
throw new Error('Client is not connected');
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
try {
|
|
611
|
+
// Check if file exists
|
|
612
|
+
if (!fs.existsSync(filePath)) {
|
|
613
|
+
throw new Error('File not found: ' + filePath);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const fileExtension = path.extname(filePath).toLowerCase();
|
|
617
|
+
const caption = options.caption || '';
|
|
618
|
+
let mediaMessage = {};
|
|
619
|
+
|
|
620
|
+
// Handle different media types
|
|
621
|
+
switch (fileExtension) {
|
|
622
|
+
case '.gif':
|
|
623
|
+
case '.mp4':
|
|
624
|
+
mediaMessage = {
|
|
625
|
+
video: fs.readFileSync(filePath),
|
|
626
|
+
caption: caption,
|
|
627
|
+
gifPlayback: options.asGif || fileExtension === '.gif',
|
|
628
|
+
}
|
|
629
|
+
break;
|
|
630
|
+
|
|
631
|
+
// Handle audio files
|
|
632
|
+
case '.mp3':
|
|
633
|
+
case '.ogg':
|
|
634
|
+
case '.wav':
|
|
635
|
+
mediaMessage = {
|
|
636
|
+
audio: {
|
|
637
|
+
url: filePath
|
|
638
|
+
},
|
|
639
|
+
mimetype: 'audio/mp4',
|
|
640
|
+
};
|
|
641
|
+
break;
|
|
642
|
+
|
|
643
|
+
// Handle image files
|
|
644
|
+
case '.jpg':
|
|
645
|
+
case '.jpeg':
|
|
646
|
+
case '.png':
|
|
647
|
+
mediaMessage = {
|
|
648
|
+
image: fs.readFileSync(filePath),
|
|
649
|
+
caption: caption,
|
|
650
|
+
};
|
|
651
|
+
break;
|
|
652
|
+
|
|
653
|
+
default:
|
|
654
|
+
throw new Error('Unsupported file type: ' + fileExtension);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return await this.sock.sendMessage(chatId, mediaMessage, { ai: true });
|
|
658
|
+
} catch (error) {
|
|
659
|
+
console.error('Error sending media:', error);
|
|
660
|
+
throw error;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Send a document to a chat
|
|
666
|
+
* @param {string} chatId - The ID of the chat to send the document to
|
|
667
|
+
* @param {string} filePath - Path to the document file
|
|
668
|
+
* @param {string} [caption=''] - Optional caption for the document
|
|
669
|
+
* @returns {Promise<object>} The sent message info
|
|
670
|
+
* @throws {Error} If client is not connected or file not found
|
|
671
|
+
*/
|
|
672
|
+
async sendDocument(chatId, filePath, caption = '') {
|
|
673
|
+
if (!this.isConnected) {
|
|
674
|
+
throw new Error('Client is not connected');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
try {
|
|
678
|
+
// Check if file exists
|
|
679
|
+
if (!fs.existsSync(filePath)) {
|
|
680
|
+
throw new Error('File not found: ' + filePath);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
684
|
+
const fileName = path.basename(filePath);
|
|
685
|
+
const mimeType = mime.getType(filePath);
|
|
686
|
+
|
|
687
|
+
return await this.sock.sendMessage(chatId, {
|
|
688
|
+
document: fileBuffer,
|
|
689
|
+
caption: caption,
|
|
690
|
+
mimetype: mimeType,
|
|
691
|
+
fileName: fileName,
|
|
692
|
+
}, { ai: true });
|
|
693
|
+
} catch (error) {
|
|
694
|
+
console.error('Error sending document:', error);
|
|
695
|
+
throw error;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Send a message with interactive buttons
|
|
701
|
+
* @param {string} chatId - The ID of the chat to send the message to
|
|
702
|
+
* @param {object} options - Options for the button message
|
|
703
|
+
* @param {string} [options.text] - The text content of the message
|
|
704
|
+
* @param {string} [options.imagePath] - Optional path to an image to include
|
|
705
|
+
* @param {string} [options.caption] - Caption for the image
|
|
706
|
+
* @param {string} [options.title] - Title for the message
|
|
707
|
+
* @param {string} [options.footer] - Footer text for the message
|
|
708
|
+
* @param {Array} [options.interactiveButtons=[]] - Array of button objects
|
|
709
|
+
* @param {boolean} [options.hasMediaAttachment=false] - Whether the message has a media attachment
|
|
710
|
+
* @param {object} [extraOptions={}] - Additional options for the message
|
|
711
|
+
* @returns {Promise<object>} The sent message info
|
|
712
|
+
* @throws {Error} If client is not connected or message sending fails
|
|
713
|
+
*/
|
|
714
|
+
async sendButtons(chatId, options = {}, extraOptions = {}) {
|
|
715
|
+
if (!this.isConnected) {
|
|
716
|
+
throw new Error('Client is not connected');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const {
|
|
720
|
+
text,
|
|
721
|
+
imagePath,
|
|
722
|
+
caption,
|
|
723
|
+
title,
|
|
724
|
+
footer,
|
|
725
|
+
interactiveButtons = [],
|
|
726
|
+
hasMediaAttachment = false,
|
|
727
|
+
} = options;
|
|
728
|
+
|
|
729
|
+
let messageContent = {};
|
|
730
|
+
|
|
731
|
+
try {
|
|
732
|
+
if (imagePath) {
|
|
733
|
+
// Handle message with image
|
|
734
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
735
|
+
messageContent = {
|
|
736
|
+
image: imageBuffer,
|
|
737
|
+
caption: caption,
|
|
738
|
+
title: title,
|
|
739
|
+
footer: footer,
|
|
740
|
+
interactiveButtons: interactiveButtons,
|
|
741
|
+
hasMediaAttachment: hasMediaAttachment,
|
|
742
|
+
};
|
|
743
|
+
} else {
|
|
744
|
+
// Handle text-only message
|
|
745
|
+
messageContent = {
|
|
746
|
+
text: text,
|
|
747
|
+
title: title,
|
|
748
|
+
footer: footer,
|
|
749
|
+
interactiveButtons: interactiveButtons,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Send the message with buttons
|
|
754
|
+
return await this.sock.sendMessage(chatId, messageContent, { ...extraOptions, ai: true });
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.error('Error sending buttons:', error);
|
|
757
|
+
throw error;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Send an interactive list message
|
|
763
|
+
* @param {string} chatId - The ID of the chat to send the list to
|
|
764
|
+
* @param {object} listOptions - Options for the list message
|
|
765
|
+
* @param {string} listOptions.text - The text content of the message
|
|
766
|
+
* @param {string} listOptions.title - Title of the list
|
|
767
|
+
* @param {string} [listOptions.footer=''] - Optional footer text
|
|
768
|
+
* @param {string} [listOptions.buttonText='Tap here'] - Text for the button
|
|
769
|
+
* @param {Array<object>} listOptions.sections - Array of section objects
|
|
770
|
+
* @returns {Promise<object>} The sent message info
|
|
771
|
+
* @throws {Error} If client is not connected or message sending fails
|
|
772
|
+
*/
|
|
773
|
+
async SendList(chatId, listOptions) {
|
|
774
|
+
if (!this.isConnected) {
|
|
775
|
+
throw new Error('Client is not connected');
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
try {
|
|
779
|
+
const listMessage = {
|
|
780
|
+
text: listOptions.text,
|
|
781
|
+
title: listOptions.title,
|
|
782
|
+
footer: listOptions.footer || '',
|
|
783
|
+
buttonText: listOptions.buttonText || 'Tap here',
|
|
784
|
+
sections: listOptions.sections.map((section) => ({
|
|
785
|
+
title: section.title,
|
|
786
|
+
rows: section.rows.map((row) => ({
|
|
787
|
+
title: row.title,
|
|
788
|
+
rowId: row.id,
|
|
789
|
+
description: row.description,
|
|
790
|
+
})),
|
|
791
|
+
})),
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
return await this.sock.sendMessage(chatId, listMessage, { ai: true });
|
|
795
|
+
} catch (error) {
|
|
796
|
+
console.error('Error sending list message:', error);
|
|
797
|
+
throw error;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Send an external ad reply with a local image
|
|
802
|
+
* @param {string} number - The phone number to send the ad to
|
|
803
|
+
* @param {string} localImagePath - Path to the local image file
|
|
804
|
+
* @param {string} title - Title of the ad
|
|
805
|
+
* @param {string} body - Body of the ad
|
|
806
|
+
* @returns {Promise<void>}
|
|
807
|
+
* @throws {Error} If client is not connected or message sending fails
|
|
808
|
+
*/
|
|
809
|
+
|
|
810
|
+
async sendAdReply(number, msg, imgpath, title, body, sourceurl) {
|
|
811
|
+
if (!this.isConnected) {
|
|
812
|
+
throw new Error('Client is not connected');
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
try {
|
|
816
|
+
|
|
817
|
+
// Read the local image as Buffer
|
|
818
|
+
const bufferLocalFile = fs.readFileSync(imgpath);
|
|
819
|
+
|
|
820
|
+
// Send message with external ad reply using local image
|
|
821
|
+
await this.sock.sendMessage(number, {
|
|
822
|
+
text: msg,
|
|
823
|
+
contextInfo: {
|
|
824
|
+
externalAdReply: {
|
|
825
|
+
title: title || 'Ad Title',
|
|
826
|
+
body: body || 'Ad Description',
|
|
827
|
+
mediaType: 1, // Image
|
|
828
|
+
previewType: 0,
|
|
829
|
+
showAdAttribution: true,
|
|
830
|
+
renderLargerThumbnail: true,
|
|
831
|
+
thumbnail: bufferLocalFile,
|
|
832
|
+
sourceUrl: sourceurl || 'https://m.facebook.com/innovatorssoft',
|
|
833
|
+
mediaUrl: sourceurl || 'https://m.facebook.com/innovatorssoft'
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}, { ai: true });
|
|
837
|
+
|
|
838
|
+
} catch (err) {
|
|
839
|
+
console.error('Failed to send externalAdReply:', err);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Get all groups the bot is a member of
|
|
845
|
+
* @returns {Promise<Array<Group>>} Array of Group instances
|
|
846
|
+
* @throws {Error} If client is not connected or an error occurs
|
|
847
|
+
*/
|
|
848
|
+
async getAllGroups() {
|
|
849
|
+
if (!this.isConnected) {
|
|
850
|
+
throw new Error('Client is not connected');
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
try {
|
|
854
|
+
const groupsData = await this.sock.groupFetchAllParticipating();
|
|
855
|
+
return Object.values(groupsData).map(
|
|
856
|
+
(groupData) => new Group(this, groupData)
|
|
857
|
+
);
|
|
858
|
+
} catch (error) {
|
|
859
|
+
console.error('Error fetching groups:', error);
|
|
860
|
+
throw error;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Add or remove participants from a group
|
|
866
|
+
* @param {string} groupId - The ID of the group
|
|
867
|
+
* @param {Array<string>} participantIds - Array of participant IDs to modify
|
|
868
|
+
* @param {string} action - Action to perform ('add', 'remove', 'promote', 'demote')
|
|
869
|
+
* @returns {Promise<Array>} Array of results for each participant update
|
|
870
|
+
* @throws {Error} If client is not connected or an error occurs
|
|
871
|
+
*/
|
|
872
|
+
async changeGroupParticipants(groupId, participantIds, action) {
|
|
873
|
+
if (!this.isConnected) {
|
|
874
|
+
throw new Error('Client is not connected');
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const results = [];
|
|
878
|
+
|
|
879
|
+
try {
|
|
880
|
+
for (const participantId of participantIds) {
|
|
881
|
+
try {
|
|
882
|
+
let updateResult = await this.sock.groupParticipantsUpdate(
|
|
883
|
+
groupId,
|
|
884
|
+
[participantId],
|
|
885
|
+
action
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
// Baileys may return various formats:
|
|
889
|
+
// 1. Array: [{ status: 200, jid: '...' }]
|
|
890
|
+
// 2. Array with nested: [{ '123@s.whatsapp.net': { code: 200 } }]
|
|
891
|
+
// 3. Direct object: { status: 200, jid: '...' }
|
|
892
|
+
let participantResult = Array.isArray(updateResult) ? updateResult[0] : updateResult;
|
|
893
|
+
|
|
894
|
+
// Handle the { [jid]: { code: ... } } format
|
|
895
|
+
if (participantResult && typeof participantResult === 'object') {
|
|
896
|
+
// Check if the result has the participant JID as a key
|
|
897
|
+
const jidKey = Object.keys(participantResult).find(k => k.includes('@'));
|
|
898
|
+
if (jidKey && participantResult[jidKey]) {
|
|
899
|
+
const innerResult = participantResult[jidKey];
|
|
900
|
+
participantResult = {
|
|
901
|
+
status: innerResult.code || innerResult.status || 200,
|
|
902
|
+
jid: jidKey,
|
|
903
|
+
message: innerResult.message || (innerResult.code == 200 ? 'Success' : undefined),
|
|
904
|
+
...innerResult
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Normalize status to number
|
|
910
|
+
let status = participantResult?.status || participantResult?.code;
|
|
911
|
+
|
|
912
|
+
// If no status but no error, assume success
|
|
913
|
+
if (status === undefined && !participantResult?.error) {
|
|
914
|
+
status = 200;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (action === 'add' && (status === 403 || status === '403')) {
|
|
918
|
+
try {
|
|
919
|
+
await this.sendGroupInvitation(groupId, participantId, null);
|
|
920
|
+
participantResult.invitationSent = true;
|
|
921
|
+
participantResult.message = 'Invitation link sent due to privacy settings';
|
|
922
|
+
participantResult.status = 403;
|
|
923
|
+
} catch (invError) {
|
|
924
|
+
participantResult.invitationSent = false;
|
|
925
|
+
participantResult.error = 'Privacy restricted, and failed to send invitation link';
|
|
926
|
+
participantResult.status = 403;
|
|
927
|
+
}
|
|
928
|
+
} else if (participantResult) {
|
|
929
|
+
participantResult.status = status ? parseInt(status) : 200;
|
|
930
|
+
if (participantResult.status === 200) {
|
|
931
|
+
participantResult.message = participantResult.message || 'Successfully added to group';
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
results.push(participantResult || { status: 200, jid: participantId, message: 'Added successfully' });
|
|
936
|
+
} catch (participantError) {
|
|
937
|
+
|
|
938
|
+
const statusCode = participantError.output?.statusCode || participantError.data?.status;
|
|
939
|
+
|
|
940
|
+
if (action === 'add' && (statusCode === 403 || statusCode === '403')) {
|
|
941
|
+
try {
|
|
942
|
+
await this.sendGroupInvitation(groupId, participantId, null);
|
|
943
|
+
results.push({
|
|
944
|
+
status: 403,
|
|
945
|
+
jid: participantId,
|
|
946
|
+
invitationSent: true,
|
|
947
|
+
message: 'Invitation link sent due to privacy settings'
|
|
948
|
+
});
|
|
949
|
+
continue;
|
|
950
|
+
} catch (invError) {
|
|
951
|
+
console.error('Failed to send fallback invitation:', invError);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
results.push({
|
|
956
|
+
status: statusCode || 500,
|
|
957
|
+
jid: participantId,
|
|
958
|
+
error: participantError.message || 'Unknown error'
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return results;
|
|
963
|
+
} catch (error) {
|
|
964
|
+
throw error;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Get group name from metadata
|
|
970
|
+
* @param {string} groupId - The group JID
|
|
971
|
+
* @returns {Promise<string>} The group name
|
|
972
|
+
* @private
|
|
973
|
+
*/
|
|
974
|
+
async _getGroupName(groupId) {
|
|
975
|
+
try {
|
|
976
|
+
const metadata = await this.getGroupMetadata(groupId);
|
|
977
|
+
return metadata.subject || 'Group';
|
|
978
|
+
} catch (error) {
|
|
979
|
+
console.error('Error getting group name:', error);
|
|
980
|
+
return 'Group';
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Send a group invitation link to a user
|
|
986
|
+
* @param {string} groupId - The group JID
|
|
987
|
+
* @param {string} participantId - The participant JID to send invitation to
|
|
988
|
+
* @param {string} [customMessage] - Optional custom invitation message
|
|
989
|
+
* @returns {Promise<object>} The sent message info
|
|
990
|
+
* @throws {Error} If client is not connected or an error occurs
|
|
991
|
+
*/
|
|
992
|
+
|
|
993
|
+
async sendGroupInvitation(groupId, participantId, customMessage) {
|
|
994
|
+
if (!this.isConnected) {
|
|
995
|
+
throw new Error('Client is not connected');
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
try {
|
|
999
|
+
// Get group invite code
|
|
1000
|
+
const inviteCode = await this.sock.groupInviteCode(groupId);
|
|
1001
|
+
const groupName = await this._getGroupName(groupId);
|
|
1002
|
+
|
|
1003
|
+
// Create the invitation link
|
|
1004
|
+
const inviteLink = `https://chat.whatsapp.com/${inviteCode}`;
|
|
1005
|
+
|
|
1006
|
+
// Create the invitation message
|
|
1007
|
+
const message = customMessage
|
|
1008
|
+
? `${customMessage}\n\n${inviteLink}`
|
|
1009
|
+
: `📨 *Group Invitation*\n\n` +
|
|
1010
|
+
`You have been invited to join:\n` +
|
|
1011
|
+
`*${groupName}*\n\n` +
|
|
1012
|
+
`Click the link below to join:\n${inviteLink}`;
|
|
1013
|
+
|
|
1014
|
+
// Send as text message
|
|
1015
|
+
return await this.sock.sendMessage(participantId, {
|
|
1016
|
+
text: message
|
|
1017
|
+
}, { ai: true });
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
console.error('Error sending group invitation:', error);
|
|
1020
|
+
throw error;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Mark a message as read
|
|
1026
|
+
* @param {object|string} messageKey - The message key object or message ID
|
|
1027
|
+
* @returns {Promise<void>}
|
|
1028
|
+
* @throws {Error} If client is not connected or an error occurs
|
|
1029
|
+
*/
|
|
1030
|
+
async readMessage(messageKey) {
|
|
1031
|
+
if (!this.isConnected) {
|
|
1032
|
+
throw new Error('Client is not connected');
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
try {
|
|
1036
|
+
// If messageKey is a string (legacy), convert to key object format
|
|
1037
|
+
// Otherwise use it directly as a key object
|
|
1038
|
+
if (typeof messageKey === 'string') {
|
|
1039
|
+
// Legacy support: just ID string
|
|
1040
|
+
await this.sock.readMessages([{ id: messageKey }]);
|
|
1041
|
+
} else {
|
|
1042
|
+
// Proper key object with remoteJid, id, fromMe, etc.
|
|
1043
|
+
await this.sock.readMessages([messageKey]);
|
|
1044
|
+
}
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
console.error('Error marking message as read:', error);
|
|
1047
|
+
throw error;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Check if a phone number is registered on WhatsApp
|
|
1053
|
+
* @param {string} phoneNumber - The phone number to check (with country code, without '+')
|
|
1054
|
+
* @returns {Promise<boolean>} True if the number is on WhatsApp, false otherwise
|
|
1055
|
+
* @throws {Error} If an error occurs during the check
|
|
1056
|
+
*/
|
|
1057
|
+
async isNumberOnWhatsApp(phoneNumber) {
|
|
1058
|
+
if (!this.isConnected) {
|
|
1059
|
+
throw new Error('Client is not connected');
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
try {
|
|
1063
|
+
const result = await this.sock.onWhatsApp(phoneNumber);
|
|
1064
|
+
return result.length > 0 && result[0].exists === true;
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
console.error('Error checking if number is on WhatsApp:', error);
|
|
1067
|
+
throw error;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Get the profile picture URL for a contact or group
|
|
1073
|
+
* @param {string} id - The contact or group JID
|
|
1074
|
+
* @returns {Promise<string|undefined>} The profile picture URL, or undefined if not available
|
|
1075
|
+
*/
|
|
1076
|
+
async getProfilePicture(id) {
|
|
1077
|
+
if (!this.isConnected) {
|
|
1078
|
+
throw new Error('Client is not connected');
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
try {
|
|
1082
|
+
return await this.sock.profilePictureUrl(id);
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
// If the error is because the user has no profile picture, return undefined
|
|
1085
|
+
const isStatus = (err, code) => (
|
|
1086
|
+
(err && typeof err.message === 'string' && err.message.includes(String(code))) ||
|
|
1087
|
+
(err && (err.status === code || err.code === code || err.data === code)) ||
|
|
1088
|
+
(err && err.response && err.response.status === code) ||
|
|
1089
|
+
(err && err.output && err.output.statusCode === code)
|
|
1090
|
+
);
|
|
1091
|
+
if (isStatus(error, 404) || isStatus(error, 401)) {
|
|
1092
|
+
return undefined;
|
|
1093
|
+
}
|
|
1094
|
+
console.error('Error getting profile picture:', error);
|
|
1095
|
+
throw error;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Reject an incoming call
|
|
1100
|
+
* @param {string} callId - The ID of the call to reject
|
|
1101
|
+
* @param {object} callInfo - Additional call information
|
|
1102
|
+
* @returns {Promise<void>}
|
|
1103
|
+
*/
|
|
1104
|
+
async rejectCall(callId, callInfo) {
|
|
1105
|
+
if (!this.isConnected) {
|
|
1106
|
+
throw new Error('Client is not connected');
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
try {
|
|
1110
|
+
await this.sock.rejectCall(callId, callInfo);
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
console.error('Error rejecting call:', error);
|
|
1113
|
+
throw error;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Get the underlying socket instance
|
|
1119
|
+
* @returns {object} The socket instance
|
|
1120
|
+
*/
|
|
1121
|
+
getSock() {
|
|
1122
|
+
return this.sock;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Send typing indicator to a chat
|
|
1127
|
+
* @param {string} jid - The JID of the chat
|
|
1128
|
+
* @returns {Promise<void>}
|
|
1129
|
+
*/
|
|
1130
|
+
async sendStateTyping(jid) {
|
|
1131
|
+
if (!this.sock) throw new Error('Not connected to WhatsApp');
|
|
1132
|
+
await this.sock.sendPresenceUpdate("composing", jid);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Send recording indicator to a chat
|
|
1137
|
+
* @param {string} jid - The JID of the chat
|
|
1138
|
+
* @returns {Promise<void>}
|
|
1139
|
+
*/
|
|
1140
|
+
async sendStateRecording(jid) {
|
|
1141
|
+
if (!this.sock) throw new Error('Not connected to WhatsApp');
|
|
1142
|
+
await this.sock.sendPresenceUpdate("recording", jid);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Clear typing/recording indicator from a chat
|
|
1147
|
+
* @param {string} jid - The JID of the chat
|
|
1148
|
+
* @returns {Promise<void>}
|
|
1149
|
+
*/
|
|
1150
|
+
async clearState(jid) {
|
|
1151
|
+
if (!this.sock) throw new Error('Not connected to WhatsApp');
|
|
1152
|
+
await this.sock.sendPresenceUpdate("paused", jid);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Reinitialize the WhatsApp client by clearing the session and reconnecting
|
|
1157
|
+
* @returns {Promise<void>}
|
|
1158
|
+
*/
|
|
1159
|
+
async reinitialize() {
|
|
1160
|
+
try {
|
|
1161
|
+
//console.log('Starting session reinitialization...');
|
|
1162
|
+
|
|
1163
|
+
// Reset connection state
|
|
1164
|
+
this.isConnected = false;
|
|
1165
|
+
|
|
1166
|
+
// Clear the session data if it exists
|
|
1167
|
+
if (fs.existsSync(this.sessionName)) {
|
|
1168
|
+
await fs.promises.rm(this.sessionName, {
|
|
1169
|
+
recursive: true,
|
|
1170
|
+
force: true,
|
|
1171
|
+
});
|
|
1172
|
+
//console.log('Cleared existing session data');
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Clear any existing socket
|
|
1176
|
+
if (this.sock) {
|
|
1177
|
+
try {
|
|
1178
|
+
this.sock.ev.removeAllListeners();
|
|
1179
|
+
this.sock.end(undefined);
|
|
1180
|
+
} catch (e) {
|
|
1181
|
+
console.error('Error while cleaning up socket:', e);
|
|
1182
|
+
}
|
|
1183
|
+
this.sock = null;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Add a small delay before reconnecting
|
|
1187
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1188
|
+
|
|
1189
|
+
// Reconnect with a fresh session
|
|
1190
|
+
//console.log('Establishing new connection...');
|
|
1191
|
+
await this.connect(1); // Start with attempt 1
|
|
1192
|
+
this.emit('reinitialized');
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
console.error('Error during reinitialization:', error);
|
|
1195
|
+
this.emit('error', error);
|
|
1196
|
+
throw error;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Get LID (Local Identifier) for a phone number
|
|
1201
|
+
* @param {string} phoneNumber - Phone number in format: '1234567890@s.whatsapp.net'
|
|
1202
|
+
* @returns {Promise<string|undefined>} The LID for the phone number, or undefined if not found
|
|
1203
|
+
*/
|
|
1204
|
+
async getLIDForPN(phoneNumber) {
|
|
1205
|
+
if (!this.sock || !this.store) {
|
|
1206
|
+
throw new Error('Client is not connected');
|
|
1207
|
+
}
|
|
1208
|
+
try {
|
|
1209
|
+
return await this.store.getLIDForPN(phoneNumber);
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
console.error('Error getting LID for PN:', error);
|
|
1212
|
+
return undefined;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Get Phone Number for a LID (Local Identifier)
|
|
1218
|
+
* @param {string} lid - LID in format: '123456@lid'
|
|
1219
|
+
* @returns {Promise<string|undefined>} The phone number for the LID, or undefined if not found
|
|
1220
|
+
*/
|
|
1221
|
+
async getPNForLID(lid) {
|
|
1222
|
+
if (!this.sock || !this.store) {
|
|
1223
|
+
throw new Error('Client is not connected');
|
|
1224
|
+
}
|
|
1225
|
+
try {
|
|
1226
|
+
return await this.store.getPNForLID(lid);
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
console.error('Error getting PN for LID:', error);
|
|
1229
|
+
return undefined;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* Get multiple LIDs for multiple phone numbers
|
|
1235
|
+
* @param {Array<string>} phoneNumbers - Array of phone numbers
|
|
1236
|
+
* @returns {Promise<Array<string>>} Array of LIDs
|
|
1237
|
+
*/
|
|
1238
|
+
async getLIDsForPNs(phoneNumbers) {
|
|
1239
|
+
if (!this.sock || !this.store) {
|
|
1240
|
+
throw new Error('Client is not connected');
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
return await this.store.getLIDsForPNs(phoneNumbers);
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
console.error('Error getting LIDs for PNs:', error);
|
|
1246
|
+
return [];
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Get group metadata with caching
|
|
1252
|
+
* @param {string} jid - The group JID
|
|
1253
|
+
* @returns {Promise<object>} The group metadata
|
|
1254
|
+
*/
|
|
1255
|
+
async getGroupMetadata(jid) {
|
|
1256
|
+
if (!this.isConnected) {
|
|
1257
|
+
throw new Error('Client is not connected');
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Check cache first
|
|
1261
|
+
const cached = this.groupMetadataCache.get(jid);
|
|
1262
|
+
if (cached) {
|
|
1263
|
+
return cached;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// Fetch from WhatsApp if not cached
|
|
1267
|
+
try {
|
|
1268
|
+
const metadata = await this.sock.groupMetadata(jid);
|
|
1269
|
+
// Cache the result
|
|
1270
|
+
this.groupMetadataCache.set(jid, metadata);
|
|
1271
|
+
return metadata;
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
console.error(`Error fetching metadata for group ${jid}:`, error);
|
|
1274
|
+
throw error;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* Update group metadata in cache
|
|
1280
|
+
* @param {string} jid - The group JID
|
|
1281
|
+
* @param {object} metadata - The group metadata
|
|
1282
|
+
*/
|
|
1283
|
+
updateGroupMetadataCache(jid, metadata) {
|
|
1284
|
+
if (this.groupMetadataCache) {
|
|
1285
|
+
this.groupMetadataCache.set(jid, metadata);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Clear group metadata from cache
|
|
1291
|
+
* @param {string} jid - The group JID
|
|
1292
|
+
*/
|
|
1293
|
+
clearGroupMetadataCache(jid) {
|
|
1294
|
+
if (this.groupMetadataCache) {
|
|
1295
|
+
this.groupMetadataCache.del(jid);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Clear all group metadata from cache
|
|
1301
|
+
*/
|
|
1302
|
+
clearAllGroupMetadataCache() {
|
|
1303
|
+
if (this.groupMetadataCache) {
|
|
1304
|
+
this.groupMetadataCache.flushAll();
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Log out from WhatsApp
|
|
1310
|
+
* @returns {Promise<boolean>} True if logout was successful
|
|
1311
|
+
* @throws {Error} If logout fails
|
|
1312
|
+
*/
|
|
1313
|
+
async logout() {
|
|
1314
|
+
if (!this.sock) {
|
|
1315
|
+
this.emit('logout', 'Already logged out');
|
|
1316
|
+
return true;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
try {
|
|
1320
|
+
// Properly close the socket connection
|
|
1321
|
+
await this.sock.logout();
|
|
1322
|
+
await this.sock.end();
|
|
1323
|
+
this.sock = null;
|
|
1324
|
+
|
|
1325
|
+
// Remove session data if it exists
|
|
1326
|
+
if (fs.existsSync(this.sessionName)) {
|
|
1327
|
+
fs.rmSync(this.sessionName, {
|
|
1328
|
+
recursive: true,
|
|
1329
|
+
force: true,
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Update connection state and emit event
|
|
1334
|
+
this.isConnected = false;
|
|
1335
|
+
this.emit('logout', 'Logged out successfully');
|
|
1336
|
+
|
|
1337
|
+
return true;
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
console.error('Logout error:', error);
|
|
1340
|
+
this.emit('error', new Error(`Failed to logout: ${error.message}`));
|
|
1341
|
+
throw error;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* Download media from a message
|
|
1347
|
+
* @param {object} message - The message object containing media (must have raw property)
|
|
1348
|
+
* @returns {Promise<object|null>} Object with buffer, mimetype, and extension, or null if no media
|
|
1349
|
+
* @throws {Error} If client is not connected or download fails
|
|
1350
|
+
*/
|
|
1351
|
+
async downloadMedia(message) {
|
|
1352
|
+
if (!this.isConnected) {
|
|
1353
|
+
throw new Error('Client is not connected');
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// Check if message has media
|
|
1357
|
+
const hasMedia = Boolean(
|
|
1358
|
+
message.raw?.message?.imageMessage ||
|
|
1359
|
+
message.raw?.message?.videoMessage ||
|
|
1360
|
+
message.raw?.message?.audioMessage ||
|
|
1361
|
+
message.raw?.message?.documentMessage
|
|
1362
|
+
);
|
|
1363
|
+
|
|
1364
|
+
if (!hasMedia) {
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
try {
|
|
1369
|
+
// Use Baileys' downloadMediaMessage function with the raw message
|
|
1370
|
+
const buffer = await downloadMediaMessage(message.raw, 'buffer', {});
|
|
1371
|
+
|
|
1372
|
+
if (buffer) {
|
|
1373
|
+
// Get the message content to determine file type
|
|
1374
|
+
const messageContent = message.raw.message;
|
|
1375
|
+
let mimetype = 'application/octet-stream';
|
|
1376
|
+
|
|
1377
|
+
// Determine mimetype from the message type
|
|
1378
|
+
if (messageContent.imageMessage) {
|
|
1379
|
+
mimetype = messageContent.imageMessage.mimetype || 'image/jpeg';
|
|
1380
|
+
} else if (messageContent.videoMessage) {
|
|
1381
|
+
mimetype = messageContent.videoMessage.mimetype || 'video/mp4';
|
|
1382
|
+
} else if (messageContent.audioMessage) {
|
|
1383
|
+
mimetype = messageContent.audioMessage.mimetype || 'audio/ogg';
|
|
1384
|
+
} else if (messageContent.documentMessage) {
|
|
1385
|
+
mimetype = messageContent.documentMessage.mimetype || 'application/octet-stream';
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// Use mime.getExtension() to get the proper file extension
|
|
1389
|
+
const extension = mime.getExtension(mimetype) || 'bin';
|
|
1390
|
+
|
|
1391
|
+
return {
|
|
1392
|
+
buffer,
|
|
1393
|
+
mimetype,
|
|
1394
|
+
extension,
|
|
1395
|
+
size: buffer.length
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
return null;
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
console.error('Error downloading media:', error);
|
|
1402
|
+
throw error;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* Parse JID information
|
|
1408
|
+
* @param {string} jid - The JID to parse
|
|
1409
|
+
* @returns {object} JID info (isLid, user, etc.)
|
|
1410
|
+
*/
|
|
1411
|
+
parseJid(jid) {
|
|
1412
|
+
return parseJid(jid);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Normalize phone number to WhatsApp JID
|
|
1417
|
+
* @param {string} phone - Phone number
|
|
1418
|
+
* @returns {string} Normalized JID
|
|
1419
|
+
*/
|
|
1420
|
+
normalizePhoneToJid(phone) {
|
|
1421
|
+
return normalizePhoneToJid(phone);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* Plot JID (Convert between PN and LID if mapping is available)
|
|
1426
|
+
* @param {string} jid - JID to plot
|
|
1427
|
+
* @returns {string|undefined} Plotted JID
|
|
1428
|
+
*/
|
|
1429
|
+
plotJid(jid) {
|
|
1430
|
+
return plotJid(jid);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* Send a sticker with metadata
|
|
1435
|
+
* @param {string} chatId - Target chat JID
|
|
1436
|
+
* @param {Buffer} webpBuffer - WebP sticker buffer
|
|
1437
|
+
* @param {object} metadata - Sticker metadata (packName, author, etc.)
|
|
1438
|
+
* @returns {Promise<object>} Sent message info
|
|
1439
|
+
*/
|
|
1440
|
+
async sendSticker(chatId, buffer, metadata = {}) {
|
|
1441
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
1442
|
+
|
|
1443
|
+
try {
|
|
1444
|
+
const sticker = new Sticker(buffer, {
|
|
1445
|
+
pack: metadata.packName || 'Innovators',
|
|
1446
|
+
author: metadata.author || 'Innovators',
|
|
1447
|
+
type: metadata.type || StickerTypes.FULL,
|
|
1448
|
+
categories: metadata.categories || [],
|
|
1449
|
+
id: metadata.id || 'innovators-bot',
|
|
1450
|
+
quality: metadata.quality || 50
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
const stickerBuffer = await sticker.toBuffer();
|
|
1454
|
+
|
|
1455
|
+
return await this.sock.sendMessage(chatId, {
|
|
1456
|
+
sticker: stickerBuffer
|
|
1457
|
+
}, { ai: true });
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
console.error('Error generating sticker:', error);
|
|
1460
|
+
throw error;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/**
|
|
1465
|
+
* Add EXIF metadata to an existing WebP buffer
|
|
1466
|
+
* @param {Buffer} buffer - WebP buffer
|
|
1467
|
+
* @param {object} metadata - Metadata (packName, author)
|
|
1468
|
+
* @returns {Buffer} Buffer with EXIF
|
|
1469
|
+
*/
|
|
1470
|
+
async addExifToSticker(buffer, metadata = {}) {
|
|
1471
|
+
try {
|
|
1472
|
+
const sticker = new Sticker(buffer, {
|
|
1473
|
+
pack: metadata.packName || 'Innovators',
|
|
1474
|
+
author: metadata.author || 'Innovators'
|
|
1475
|
+
});
|
|
1476
|
+
return await sticker.toBuffer();
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
console.error('Error adding EXIF to sticker:', error);
|
|
1479
|
+
throw error;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function formatCode(code) {
|
|
1485
|
+
if (typeof code === 'string' && code.length === 8) {
|
|
1486
|
+
return code.slice(0, 4) + ' - ' + code.slice(4);
|
|
1487
|
+
}
|
|
1488
|
+
return code;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
module.exports = {
|
|
1492
|
+
WhatsAppClient: WhatsAppClient,
|
|
1493
|
+
Group: Group,
|
|
1494
|
+
}
|