@innovatorssoft/innovators-bot2 2.0.5 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +4 -0
- package/README.md +358 -36
- package/example.js +649 -204
- package/example.mp4 +0 -0
- package/index.js +654 -117
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -4,7 +4,6 @@ const {
|
|
|
4
4
|
useMultiFileAuthState,
|
|
5
5
|
DisconnectReason,
|
|
6
6
|
fetchLatestBaileysVersion,
|
|
7
|
-
fetchLatestWaWebVersion,
|
|
8
7
|
downloadMediaMessage,
|
|
9
8
|
getCurrentSenderInfo,
|
|
10
9
|
// JID Utilities
|
|
@@ -21,11 +20,17 @@ const {
|
|
|
21
20
|
generateCombinedButtons,
|
|
22
21
|
generateCopyCodeButton,
|
|
23
22
|
generateUrlButtonMessage,
|
|
24
|
-
generateQuickReplyButtons
|
|
23
|
+
generateQuickReplyButtons,
|
|
24
|
+
StatusHelper,
|
|
25
|
+
STATUS_BACKGROUNDS,
|
|
26
|
+
STATUS_FONTS,
|
|
27
|
+
renderLatexToPng,
|
|
28
|
+
uploadUnencryptedToWA,
|
|
29
|
+
RichSubMessageType,
|
|
30
|
+
getAggregateVotesInPollMessage
|
|
25
31
|
} = require('@innovatorssoft/baileys');
|
|
26
32
|
|
|
27
33
|
const { Sticker, StickerTypes } = require('wa-sticker-formatter');
|
|
28
|
-
|
|
29
34
|
const { Boom } = require('@hapi/boom');
|
|
30
35
|
const { EventEmitter } = require('events');
|
|
31
36
|
const P = require('pino');
|
|
@@ -35,7 +40,7 @@ const mime = require('mime');
|
|
|
35
40
|
const figlet = require('figlet');
|
|
36
41
|
const NodeCache = require('node-cache');
|
|
37
42
|
|
|
38
|
-
process.title = 'INNOVATORS Soft WhatsApp Server
|
|
43
|
+
process.title = 'INNOVATORS Soft WhatsApp Server +447498792682'
|
|
39
44
|
|
|
40
45
|
console.log(figlet.textSync('WELCOME To'))
|
|
41
46
|
console.log(figlet.textSync('INNOVATORS'))
|
|
@@ -79,6 +84,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
79
84
|
this._storeChangeCount = 0;
|
|
80
85
|
this._pairingCodeTimer = null;
|
|
81
86
|
this._lastStoreSave = null;
|
|
87
|
+
this.ai = config.ai === undefined ? true : config.ai;
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
/**
|
|
@@ -124,6 +130,32 @@ class WhatsAppClient extends EventEmitter {
|
|
|
124
130
|
return jid.replace(/:\d+@/, '@');
|
|
125
131
|
}
|
|
126
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Internal helper to handle mentions and the "mention all" flag
|
|
135
|
+
* @param {string[]} mentions - Array of JIDs or keywords like 'all'/'@all'
|
|
136
|
+
* @param {boolean} mentionAll - Explicit mentionAll flag
|
|
137
|
+
* @returns {object} Object containing processed mentions and mentionAll flag
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
_handleMentions(mentions, mentionAll) {
|
|
141
|
+
let processedMentions = mentions;
|
|
142
|
+
let finalMentionAll = mentionAll;
|
|
143
|
+
|
|
144
|
+
if (mentions && Array.isArray(mentions)) {
|
|
145
|
+
processedMentions = mentions
|
|
146
|
+
.filter(jid => jid !== 'all' && jid !== '@all')
|
|
147
|
+
.map(jid => this._normalizeJid(jid));
|
|
148
|
+
if (mentions.includes('all') || mentions.includes('@all')) {
|
|
149
|
+
finalMentionAll = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
mentions: processedMentions,
|
|
155
|
+
mentionAll: finalMentionAll
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
127
159
|
async connect() {
|
|
128
160
|
try {
|
|
129
161
|
if (this._connectionState === 'connecting' && this.sock) {
|
|
@@ -136,10 +168,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
136
168
|
}
|
|
137
169
|
|
|
138
170
|
const { version: baileysVersion, isLatest: baileysIsLatest } = await fetchLatestBaileysVersion();
|
|
139
|
-
const { version: waWebVersion, isLatest: waWebIsLatest } = await fetchLatestWaWebVersion();
|
|
140
|
-
|
|
141
171
|
console.log('Using Baileys Version:', baileysVersion, baileysIsLatest ? ' isLatest true' : ' isLatest false');
|
|
142
|
-
console.log('Using WhatsApp Web Version:', waWebVersion, waWebIsLatest ? ' isLatest true' : ' isLatest false');
|
|
143
172
|
|
|
144
173
|
const { state, saveCreds } = await useMultiFileAuthState(this.sessionName)
|
|
145
174
|
const logger = P({ level: 'silent' })
|
|
@@ -148,13 +177,20 @@ class WhatsAppClient extends EventEmitter {
|
|
|
148
177
|
auth: state,
|
|
149
178
|
logger,
|
|
150
179
|
markOnlineOnConnect: false,
|
|
151
|
-
syncFullHistory:
|
|
152
|
-
getMessage: async (key) =>
|
|
180
|
+
syncFullHistory: false,
|
|
181
|
+
getMessage: async (key) => {
|
|
182
|
+
const msg = this.messageStore.getOriginalMessage(key);
|
|
183
|
+
if (!msg) {
|
|
184
|
+
console.log(`Message not found for key: ${JSON.stringify(key)}`);
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
return msg.message;
|
|
188
|
+
},
|
|
153
189
|
generateHighQualityLinkPreview: true,
|
|
154
190
|
linkPreviewImageThumbnailWidth: 192,
|
|
155
191
|
emitOwnEvents: true,
|
|
156
192
|
browser: Browsers.android('Innovators Soft'),
|
|
157
|
-
version:
|
|
193
|
+
version: baileysVersion,
|
|
158
194
|
cachedGroupMetadata: async (jid) => {
|
|
159
195
|
const cached = this.groupMetadataCache.get(jid);
|
|
160
196
|
if (cached) {
|
|
@@ -291,6 +327,10 @@ class WhatsAppClient extends EventEmitter {
|
|
|
291
327
|
// Save message store to file immediately
|
|
292
328
|
await this.saveMessageStore();
|
|
293
329
|
|
|
330
|
+
/*console.log('-'.repeat(50));
|
|
331
|
+
console.dir(update, { depth: null });
|
|
332
|
+
console.log('-'.repeat(50));*/
|
|
333
|
+
|
|
294
334
|
try {
|
|
295
335
|
if (update.type !== 'notify' || !update.messages?.length) return;
|
|
296
336
|
const [message] = update.messages;
|
|
@@ -301,19 +341,14 @@ class WhatsAppClient extends EventEmitter {
|
|
|
301
341
|
|
|
302
342
|
const msg = message.message || {};
|
|
303
343
|
|
|
344
|
+
let jid = this._normalizeJid(message.key.remoteJid);
|
|
304
345
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
// remoteJidAlt contains the alternate JID (PN if primary is LID, or vice versa)
|
|
309
|
-
if (typeof message.key.remoteJidAlt === 'string' &&
|
|
310
|
-
message.key.remoteJidAlt.endsWith('@s.whatsapp.net')
|
|
311
|
-
) {
|
|
312
|
-
jid = message.key.remoteJidAlt;
|
|
313
|
-
}
|
|
346
|
+
// Keep the original technical JID as the primary identifier for Signal sessions
|
|
347
|
+
// remoteJidAlt can be used if needed, but not to replace the primary jid for technical replies
|
|
348
|
+
const jidAlt = this._normalizeJid(message.key.remoteJidAlt) || null;
|
|
314
349
|
// Resolve the actual sender (preferring PN over LID)
|
|
315
|
-
const participant = message.key.participant || message.participant || null;
|
|
316
|
-
const participantAlt = message.key.participantAlt || null;
|
|
350
|
+
const participant = this._normalizeJid(message.key.participant || message.participant) || null;
|
|
351
|
+
const participantAlt = this._normalizeJid(message.key.participantAlt) || null;
|
|
317
352
|
|
|
318
353
|
let sender = jid;
|
|
319
354
|
if (jid.endsWith('@g.us') || jid === 'status@broadcast') {
|
|
@@ -352,7 +387,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
352
387
|
return '';
|
|
353
388
|
};
|
|
354
389
|
|
|
355
|
-
const reply = async (text) => this.sock.sendMessage(jid, { text }, { quoted: message, ai:
|
|
390
|
+
const reply = async (text) => this.sock.sendMessage(jid, { text }, { quoted: message, ai: this.ai });
|
|
356
391
|
|
|
357
392
|
// ✅ Handle status updates (stories)
|
|
358
393
|
if (jid === 'status@broadcast') {
|
|
@@ -369,7 +404,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
369
404
|
// Reply to status
|
|
370
405
|
reply: async (text) => {
|
|
371
406
|
if (!sender) throw new Error('Missing participant JID');
|
|
372
|
-
return this.sock.sendMessage(sender, { text }, { quoted: message, ai:
|
|
407
|
+
return this.sock.sendMessage(sender, { text }, { quoted: message, ai: this.ai });
|
|
373
408
|
},
|
|
374
409
|
// 👍 Like (react) to status
|
|
375
410
|
like: async (emoji = '❤️') => {
|
|
@@ -388,7 +423,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
388
423
|
text: emoji,
|
|
389
424
|
key: message.key
|
|
390
425
|
}
|
|
391
|
-
}, { ai:
|
|
426
|
+
}, { ai: this.ai });
|
|
392
427
|
}
|
|
393
428
|
});
|
|
394
429
|
return;
|
|
@@ -401,6 +436,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
401
436
|
|
|
402
437
|
this.emit('message', {
|
|
403
438
|
from: jid,
|
|
439
|
+
fromAlt: jidAlt,
|
|
404
440
|
sender,
|
|
405
441
|
participant,
|
|
406
442
|
participantAlt,
|
|
@@ -424,20 +460,128 @@ class WhatsAppClient extends EventEmitter {
|
|
|
424
460
|
// 🛡️ Anti-Delete System: Handle message revokes/deletions
|
|
425
461
|
const antiDeleteHandler = createAntiDeleteHandler(this.messageStore);
|
|
426
462
|
|
|
427
|
-
this.sock.ev.on('messages.update', (updates) => {
|
|
463
|
+
this.sock.ev.on('messages.update', async (updates) => {
|
|
428
464
|
const deletedMessages = antiDeleteHandler(updates);
|
|
429
465
|
for (const info of deletedMessages) {
|
|
430
|
-
let jid = info.key.remoteJid;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
466
|
+
let jid = this._normalizeJid(info.key.remoteJid);
|
|
467
|
+
// Use original remoteJid for technical identification
|
|
468
|
+
const jidAlt = this._normalizeJid(info.key.remoteJidAlt) || null;
|
|
434
469
|
|
|
435
470
|
this.emit('message-deleted', {
|
|
436
471
|
jid: jid,
|
|
472
|
+
jidAlt: jidAlt,
|
|
437
473
|
originalMessage: info.originalMessage,
|
|
438
474
|
key: info.key
|
|
439
475
|
});
|
|
440
476
|
}
|
|
477
|
+
|
|
478
|
+
// Handle Poll Updates
|
|
479
|
+
for (const updateObj of updates) {
|
|
480
|
+
const { key, update } = updateObj;
|
|
481
|
+
if (update.pollUpdates) {
|
|
482
|
+
try {
|
|
483
|
+
const pollCreation = this.messageStore.getOriginalMessage(key);
|
|
484
|
+
if (pollCreation) {
|
|
485
|
+
// Initialize pollUpdates array on the stored message if it doesn't exist
|
|
486
|
+
if (!pollCreation.pollUpdates) {
|
|
487
|
+
pollCreation.pollUpdates = [];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Merge new updates by voter JID to ensure only the latest vote per voter is stored
|
|
491
|
+
for (const newUp of update.pollUpdates) {
|
|
492
|
+
const voterJid = newUp.pollUpdateMessageKey?.participant || (newUp.pollUpdateMessageKey?.fromMe ? 'me' : null);
|
|
493
|
+
if (voterJid) {
|
|
494
|
+
const normVoterJid = voterJid === 'me' ? 'me' : this._normalizeJid(voterJid);
|
|
495
|
+
const index = pollCreation.pollUpdates.findIndex(existing => {
|
|
496
|
+
const existingVoter = existing.pollUpdateMessageKey?.participant || (existing.pollUpdateMessageKey?.fromMe ? 'me' : null);
|
|
497
|
+
const normExisting = existingVoter === 'me' ? 'me' : this._normalizeJid(existingVoter);
|
|
498
|
+
return normExisting === normVoterJid;
|
|
499
|
+
});
|
|
500
|
+
if (index !== -1) {
|
|
501
|
+
pollCreation.pollUpdates[index] = newUp; // Replace with latest vote update from this voter
|
|
502
|
+
} else {
|
|
503
|
+
pollCreation.pollUpdates.push(newUp); // Add new voter's update
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const pollUpdate = getAggregateVotesInPollMessage({
|
|
509
|
+
message: pollCreation.message || pollCreation,
|
|
510
|
+
pollUpdates: pollCreation.pollUpdates,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// Resolve JID from LID to PN for voters in the poll update
|
|
514
|
+
const resolvedPollUpdate = await Promise.all(
|
|
515
|
+
pollUpdate.map(async (option) => {
|
|
516
|
+
const resolvedVoters = await Promise.all(
|
|
517
|
+
(option.voters || []).map(async (v) => {
|
|
518
|
+
if (v === 'me') return 'me';
|
|
519
|
+
return await this._resolveLidToPn(v);
|
|
520
|
+
})
|
|
521
|
+
);
|
|
522
|
+
return {
|
|
523
|
+
...option,
|
|
524
|
+
voters: resolvedVoters
|
|
525
|
+
};
|
|
526
|
+
})
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
let jid = this._normalizeJid(key.remoteJid);
|
|
530
|
+
jid = await this._resolveLidToPn(jid);
|
|
531
|
+
const jidAlt = this._normalizeJid(key.remoteJidAlt) || null;
|
|
532
|
+
|
|
533
|
+
// Clone key to resolve LIDs to PNs without mutating the original reference if it's read-only
|
|
534
|
+
const resolvedKey = { ...key };
|
|
535
|
+
if (key.remoteJid) {
|
|
536
|
+
resolvedKey.remoteJid = await this._resolveLidToPn(key.remoteJid);
|
|
537
|
+
}
|
|
538
|
+
if (key.participant) {
|
|
539
|
+
resolvedKey.participant = await this._resolveLidToPn(key.participant);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Clone pollCreation to resolve LIDs to PNs without mutating the original store reference
|
|
543
|
+
const resolvedPollCreation = { ...pollCreation };
|
|
544
|
+
if (pollCreation.participant) {
|
|
545
|
+
resolvedPollCreation.participant = await this._resolveLidToPn(pollCreation.participant);
|
|
546
|
+
}
|
|
547
|
+
if (pollCreation.key) {
|
|
548
|
+
resolvedPollCreation.key = { ...pollCreation.key };
|
|
549
|
+
if (pollCreation.key.remoteJid) {
|
|
550
|
+
resolvedPollCreation.key.remoteJid = await this._resolveLidToPn(pollCreation.key.remoteJid);
|
|
551
|
+
}
|
|
552
|
+
if (pollCreation.key.participant) {
|
|
553
|
+
resolvedPollCreation.key.participant = await this._resolveLidToPn(pollCreation.key.participant);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Extract and resolve voter JID(s) from the pollUpdates
|
|
558
|
+
const voters = await Promise.all(
|
|
559
|
+
(update.pollUpdates || []).map(async (u) => {
|
|
560
|
+
const voterJid = u.pollUpdateMessageKey?.participant || (u.pollUpdateMessageKey?.fromMe ? 'me' : null);
|
|
561
|
+
if (voterJid && voterJid !== 'me') {
|
|
562
|
+
return await this._resolveLidToPn(this._normalizeJid(voterJid));
|
|
563
|
+
}
|
|
564
|
+
return voterJid;
|
|
565
|
+
})
|
|
566
|
+
).then(arr => arr.filter(Boolean));
|
|
567
|
+
|
|
568
|
+
this.emit('poll-votes-update', {
|
|
569
|
+
jid: jid,
|
|
570
|
+
jidAlt: jidAlt,
|
|
571
|
+
voter: voters[0] || null,
|
|
572
|
+
voters: voters,
|
|
573
|
+
key: resolvedKey,
|
|
574
|
+
pollUpdate: resolvedPollUpdate,
|
|
575
|
+
pollCreationMessage: resolvedPollCreation
|
|
576
|
+
});
|
|
577
|
+
} else {
|
|
578
|
+
console.log('[PollVotes] Could not find poll creation message in store for key:', key.id);
|
|
579
|
+
}
|
|
580
|
+
} catch (err) {
|
|
581
|
+
console.error('[PollVotes ERROR] Error processing poll updates:', err);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
441
585
|
});
|
|
442
586
|
|
|
443
587
|
// 👍 Handle message reactions
|
|
@@ -446,17 +590,16 @@ class WhatsAppClient extends EventEmitter {
|
|
|
446
590
|
|
|
447
591
|
try {
|
|
448
592
|
for (const reaction of reactions) {
|
|
593
|
+
if (reaction.key?.fromMe) continue;
|
|
449
594
|
|
|
450
595
|
// Get the chat JID, preferring PN over LID
|
|
451
|
-
let jid = reaction.key.remoteJid;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
jid = reaction.key.remoteJidAlt;
|
|
455
|
-
}
|
|
596
|
+
let jid = this._normalizeJid(reaction.key.remoteJid);
|
|
597
|
+
// Use original remoteJid for technical identification
|
|
598
|
+
const jidAlt = this._normalizeJid(reaction.key.remoteJidAlt) || null;
|
|
456
599
|
|
|
457
600
|
// Resolve the sender (who reacted), preferring PN over LID
|
|
458
|
-
const participant = reaction.key.participant || null;
|
|
459
|
-
const participantAlt = reaction.key.participantAlt || null;
|
|
601
|
+
const participant = this._normalizeJid(reaction.key.participant) || null;
|
|
602
|
+
const participantAlt = this._normalizeJid(reaction.key.participantAlt) || null;
|
|
460
603
|
|
|
461
604
|
let sender = jid;
|
|
462
605
|
if (jid.endsWith('@g.us') || jid === 'status@broadcast') {
|
|
@@ -471,6 +614,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
471
614
|
// Emit the reaction event
|
|
472
615
|
this.emit('message-reaction', {
|
|
473
616
|
from: jid,
|
|
617
|
+
fromAlt: jidAlt,
|
|
474
618
|
sender: sender,
|
|
475
619
|
participant: participant,
|
|
476
620
|
participantAlt: participantAlt,
|
|
@@ -611,75 +755,101 @@ class WhatsAppClient extends EventEmitter {
|
|
|
611
755
|
* @throws {Error} If client is not connected or message sending fails
|
|
612
756
|
*/
|
|
613
757
|
async sendMessage(chatId, message, options = {}) {
|
|
758
|
+
chatId = this._normalizeJid(chatId);
|
|
614
759
|
if (!this.isConnected) {
|
|
615
760
|
throw new Error('Client is not connected');
|
|
616
761
|
}
|
|
617
762
|
|
|
618
763
|
let messageContent = {};
|
|
619
764
|
|
|
620
|
-
//
|
|
621
|
-
|
|
765
|
+
// Check if poll is provided in message or options
|
|
766
|
+
let pollData = null;
|
|
767
|
+
if (message && typeof message === 'object' && message.poll) {
|
|
768
|
+
pollData = message.poll;
|
|
769
|
+
} else if (options && options.poll) {
|
|
770
|
+
pollData = options.poll;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (pollData) {
|
|
774
|
+
messageContent = {
|
|
775
|
+
poll: {
|
|
776
|
+
name: pollData.name,
|
|
777
|
+
values: pollData.values || pollData.options || [],
|
|
778
|
+
selectableCount: pollData.selectableCount !== undefined ? pollData.selectableCount : (pollData.selectableOptionsCount !== undefined ? pollData.selectableOptionsCount : 1),
|
|
779
|
+
toAnnouncementGroup: pollData.toAnnouncementGroup || false
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
} else if (typeof message === 'string') {
|
|
622
783
|
messageContent = { text: message };
|
|
623
784
|
} else if (message && typeof message === 'object') {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
785
|
+
if (message.richResponse) {
|
|
786
|
+
if (Array.isArray(message.richResponse)) {
|
|
787
|
+
// Route array of submessages to sendRichMessage instead
|
|
788
|
+
return await this.sendRichMessage(chatId, message.richResponse, options.quoted || null, { ...options, useMarkdown: true });
|
|
789
|
+
}
|
|
790
|
+
messageContent = { richResponse: message.richResponse };
|
|
791
|
+
} else {
|
|
792
|
+
// Handle different message types
|
|
793
|
+
switch (message.type) {
|
|
794
|
+
case 'text':
|
|
795
|
+
messageContent = { text: message.text };
|
|
796
|
+
const { mentions: textMentions, mentionAll: textMentionAll } = this._handleMentions(message.mentions, message.mentionAll);
|
|
797
|
+
if (textMentions) messageContent.mentions = textMentions;
|
|
798
|
+
if (textMentionAll !== undefined) messageContent.mentionAll = textMentionAll;
|
|
799
|
+
break;
|
|
800
|
+
|
|
801
|
+
case 'location':
|
|
802
|
+
messageContent = {
|
|
803
|
+
location: {
|
|
804
|
+
degreesLatitude: message.latitude,
|
|
805
|
+
degreesLongitude: message.longitude,
|
|
806
|
+
name: message.name,
|
|
807
|
+
address: message.address
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
break;
|
|
643
811
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
displayName: message.fullName,
|
|
648
|
-
contacts: [{
|
|
812
|
+
case 'contact':
|
|
813
|
+
messageContent = {
|
|
814
|
+
contacts: {
|
|
649
815
|
displayName: message.fullName,
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
816
|
+
contacts: [{
|
|
817
|
+
displayName: message.fullName,
|
|
818
|
+
vcard: `BEGIN:VCARD\nVERSION:3.0\n` +
|
|
819
|
+
`FN:${message.fullName}\n` +
|
|
820
|
+
(message.organization ? `ORG:${message.organization};\n` : '') +
|
|
821
|
+
(message.phoneNumber ? `TEL;type=CELL;type=VOICE;waid=${message.phoneNumber}:+${message.phoneNumber}\n` : '') +
|
|
822
|
+
'END:VCARD'
|
|
823
|
+
}]
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
break;
|
|
827
|
+
|
|
828
|
+
case 'reaction':
|
|
829
|
+
messageContent = {
|
|
830
|
+
react: {
|
|
831
|
+
text: message.emoji,
|
|
832
|
+
key: message.messageKey || message.message?.key || message.key
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
break;
|
|
668
836
|
|
|
669
|
-
|
|
670
|
-
|
|
837
|
+
default:
|
|
838
|
+
throw new Error('Invalid message type');
|
|
839
|
+
}
|
|
671
840
|
}
|
|
672
841
|
} else {
|
|
673
842
|
throw new Error('Invalid message content');
|
|
674
843
|
}
|
|
675
844
|
|
|
676
845
|
try {
|
|
677
|
-
return await this.sock.sendMessage(chatId, messageContent, {
|
|
846
|
+
return await this.sock.sendMessage(chatId, messageContent, { ai: this.ai, ...options });
|
|
678
847
|
} catch (error) {
|
|
679
848
|
console.error('Error sending message:', error);
|
|
680
849
|
throw error;
|
|
681
850
|
}
|
|
682
851
|
}
|
|
852
|
+
|
|
683
853
|
/**
|
|
684
854
|
* Send a media file to a chat
|
|
685
855
|
* @param {string} chatId - The ID of the chat to send the media to
|
|
@@ -689,17 +859,45 @@ class WhatsAppClient extends EventEmitter {
|
|
|
689
859
|
* @throws {Error} If client is not connected or file not found
|
|
690
860
|
*/
|
|
691
861
|
async sendMedia(chatId, filePath, options = {}) {
|
|
862
|
+
chatId = this._normalizeJid(chatId);
|
|
692
863
|
if (!this.isConnected) {
|
|
693
864
|
throw new Error('Client is not connected');
|
|
694
865
|
}
|
|
695
866
|
|
|
696
867
|
try {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
868
|
+
let fileBuffer;
|
|
869
|
+
let fileExtension;
|
|
870
|
+
let isUrl = false;
|
|
871
|
+
|
|
872
|
+
try {
|
|
873
|
+
const parsedUrl = new URL(filePath);
|
|
874
|
+
isUrl = parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
|
|
875
|
+
} catch (_) { }
|
|
876
|
+
|
|
877
|
+
if (isUrl) {
|
|
878
|
+
const response = await fetch(filePath);
|
|
879
|
+
if (!response.ok) {
|
|
880
|
+
throw new Error(`Failed to fetch media from URL: ${response.statusText}`);
|
|
881
|
+
}
|
|
882
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
883
|
+
fileBuffer = Buffer.from(arrayBuffer);
|
|
884
|
+
const contentType = response.headers.get('content-type');
|
|
885
|
+
if (contentType) {
|
|
886
|
+
const cleanMime = contentType.split(';')[0].trim();
|
|
887
|
+
fileExtension = '.' + mime.getExtension(cleanMime);
|
|
888
|
+
} else {
|
|
889
|
+
const parsedUrl = new URL(filePath);
|
|
890
|
+
fileExtension = path.extname(parsedUrl.pathname).toLowerCase();
|
|
891
|
+
}
|
|
892
|
+
} else {
|
|
893
|
+
// Check if file exists
|
|
894
|
+
if (!fs.existsSync(filePath)) {
|
|
895
|
+
throw new Error('File not found: ' + filePath);
|
|
896
|
+
}
|
|
897
|
+
fileBuffer = fs.readFileSync(filePath);
|
|
898
|
+
fileExtension = path.extname(filePath).toLowerCase();
|
|
700
899
|
}
|
|
701
900
|
|
|
702
|
-
const fileExtension = path.extname(filePath).toLowerCase();
|
|
703
901
|
const caption = options.caption || '';
|
|
704
902
|
let mediaMessage = {};
|
|
705
903
|
|
|
@@ -708,7 +906,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
708
906
|
case '.gif':
|
|
709
907
|
case '.mp4':
|
|
710
908
|
mediaMessage = {
|
|
711
|
-
video:
|
|
909
|
+
video: fileBuffer,
|
|
712
910
|
caption: caption,
|
|
713
911
|
gifPlayback: options.asGif || fileExtension === '.gif',
|
|
714
912
|
}
|
|
@@ -719,9 +917,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
719
917
|
case '.ogg':
|
|
720
918
|
case '.wav':
|
|
721
919
|
mediaMessage = {
|
|
722
|
-
audio:
|
|
723
|
-
url: filePath
|
|
724
|
-
},
|
|
920
|
+
audio: fileBuffer,
|
|
725
921
|
mimetype: 'audio/mp4',
|
|
726
922
|
};
|
|
727
923
|
break;
|
|
@@ -731,7 +927,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
731
927
|
case '.jpeg':
|
|
732
928
|
case '.png':
|
|
733
929
|
mediaMessage = {
|
|
734
|
-
image:
|
|
930
|
+
image: fileBuffer,
|
|
735
931
|
caption: caption,
|
|
736
932
|
};
|
|
737
933
|
break;
|
|
@@ -740,11 +936,11 @@ class WhatsAppClient extends EventEmitter {
|
|
|
740
936
|
throw new Error('Unsupported file type: ' + fileExtension);
|
|
741
937
|
}
|
|
742
938
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
939
|
+
const { mentions: mediaMentions, mentionAll: mediaMentionAll } = this._handleMentions(options.mentions, options.mentionAll);
|
|
940
|
+
if (mediaMentions) mediaMessage.mentions = mediaMentions;
|
|
941
|
+
if (mediaMentionAll !== undefined) mediaMessage.mentionAll = mediaMentionAll;
|
|
746
942
|
|
|
747
|
-
return await this.sock.sendMessage(chatId, mediaMessage, { ai:
|
|
943
|
+
return await this.sock.sendMessage(chatId, mediaMessage, { ai: this.ai });
|
|
748
944
|
} catch (error) {
|
|
749
945
|
console.error('Error sending media:', error);
|
|
750
946
|
throw error;
|
|
@@ -760,19 +956,57 @@ class WhatsAppClient extends EventEmitter {
|
|
|
760
956
|
* @throws {Error} If client is not connected or file not found
|
|
761
957
|
*/
|
|
762
958
|
async sendDocument(chatId, filePath, caption = '') {
|
|
959
|
+
chatId = this._normalizeJid(chatId);
|
|
763
960
|
if (!this.isConnected) {
|
|
764
961
|
throw new Error('Client is not connected');
|
|
765
962
|
}
|
|
766
963
|
|
|
767
964
|
try {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
965
|
+
let fileBuffer;
|
|
966
|
+
let fileName;
|
|
967
|
+
let mimeType;
|
|
968
|
+
let isUrl = false;
|
|
969
|
+
|
|
970
|
+
try {
|
|
971
|
+
const parsedUrl = new URL(filePath);
|
|
972
|
+
isUrl = parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
|
|
973
|
+
} catch (_) { }
|
|
974
|
+
|
|
975
|
+
if (isUrl) {
|
|
976
|
+
const response = await fetch(filePath);
|
|
977
|
+
if (!response.ok) {
|
|
978
|
+
throw new Error(`Failed to fetch document from URL: ${response.statusText}`);
|
|
979
|
+
}
|
|
980
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
981
|
+
fileBuffer = Buffer.from(arrayBuffer);
|
|
982
|
+
|
|
983
|
+
const contentType = response.headers.get('content-type');
|
|
984
|
+
if (contentType) {
|
|
985
|
+
mimeType = contentType.split(';')[0].trim();
|
|
986
|
+
} else {
|
|
987
|
+
mimeType = mime.getType(filePath) || 'application/octet-stream';
|
|
988
|
+
}
|
|
772
989
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
990
|
+
const contentDisposition = response.headers.get('content-disposition');
|
|
991
|
+
if (contentDisposition) {
|
|
992
|
+
const match = contentDisposition.match(/filename="?([^"]+)"?/);
|
|
993
|
+
if (match && match[1]) {
|
|
994
|
+
fileName = match[1];
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
if (!fileName) {
|
|
999
|
+
const parsedUrl = new URL(filePath);
|
|
1000
|
+
fileName = path.basename(parsedUrl.pathname) || 'document';
|
|
1001
|
+
}
|
|
1002
|
+
} else {
|
|
1003
|
+
if (!fs.existsSync(filePath)) {
|
|
1004
|
+
throw new Error('File not found: ' + filePath);
|
|
1005
|
+
}
|
|
1006
|
+
fileBuffer = fs.readFileSync(filePath);
|
|
1007
|
+
fileName = path.basename(filePath);
|
|
1008
|
+
mimeType = mime.getType(filePath) || 'application/octet-stream';
|
|
1009
|
+
}
|
|
776
1010
|
|
|
777
1011
|
const messageContent = {
|
|
778
1012
|
document: fileBuffer,
|
|
@@ -783,12 +1017,15 @@ class WhatsAppClient extends EventEmitter {
|
|
|
783
1017
|
|
|
784
1018
|
if (typeof caption === 'object' && caption !== null) {
|
|
785
1019
|
if (caption.caption) messageContent.caption = caption.caption;
|
|
786
|
-
|
|
1020
|
+
|
|
1021
|
+
const { mentions: docMentions, mentionAll: docMentionAll } = this._handleMentions(caption.mentions, caption.mentionAll);
|
|
1022
|
+
if (docMentions) messageContent.mentions = docMentions;
|
|
1023
|
+
if (docMentionAll !== undefined) messageContent.mentionAll = docMentionAll;
|
|
787
1024
|
}
|
|
788
1025
|
|
|
789
1026
|
return await this.sock.sendMessage(chatId, {
|
|
790
1027
|
...messageContent,
|
|
791
|
-
}, { ai:
|
|
1028
|
+
}, { ai: this.ai });
|
|
792
1029
|
} catch (error) {
|
|
793
1030
|
console.error('Error sending document:', error);
|
|
794
1031
|
throw error;
|
|
@@ -811,6 +1048,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
811
1048
|
* @throws {Error} If client is not connected or message sending fails
|
|
812
1049
|
*/
|
|
813
1050
|
async sendButtons(chatId, options = {}, extraOptions = {}) {
|
|
1051
|
+
chatId = this._normalizeJid(chatId);
|
|
814
1052
|
if (!this.isConnected) {
|
|
815
1053
|
throw new Error('Client is not connected');
|
|
816
1054
|
}
|
|
@@ -874,7 +1112,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
874
1112
|
}
|
|
875
1113
|
|
|
876
1114
|
// Send the message with buttons
|
|
877
|
-
return await this.sock.sendMessage(chatId, messageContent, {
|
|
1115
|
+
return await this.sock.sendMessage(chatId, messageContent, { ai: this.ai, ...extraOptions });
|
|
878
1116
|
} catch (error) {
|
|
879
1117
|
console.error('Error sending buttons:', error);
|
|
880
1118
|
throw error;
|
|
@@ -893,7 +1131,9 @@ class WhatsAppClient extends EventEmitter {
|
|
|
893
1131
|
* @returns {Promise<object>} The sent message info
|
|
894
1132
|
* @throws {Error} If client is not connected or message sending fails
|
|
895
1133
|
*/
|
|
1134
|
+
|
|
896
1135
|
async SendList(chatId, listOptions) {
|
|
1136
|
+
chatId = this._normalizeJid(chatId);
|
|
897
1137
|
if (!this.isConnected) {
|
|
898
1138
|
throw new Error('Client is not connected');
|
|
899
1139
|
}
|
|
@@ -914,7 +1154,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
914
1154
|
})),
|
|
915
1155
|
};
|
|
916
1156
|
|
|
917
|
-
return await this.sock.sendMessage(chatId, listMessage, { ai:
|
|
1157
|
+
return await this.sock.sendMessage(chatId, listMessage, { ai: this.ai });
|
|
918
1158
|
} catch (error) {
|
|
919
1159
|
console.error('Error sending list message:', error);
|
|
920
1160
|
throw error;
|
|
@@ -929,9 +1169,10 @@ class WhatsAppClient extends EventEmitter {
|
|
|
929
1169
|
* @param {object} options - { footer }
|
|
930
1170
|
*/
|
|
931
1171
|
async sendQuickReplyV2(jid, text, buttons, options = {}) {
|
|
1172
|
+
jid = this._normalizeJid(jid);
|
|
932
1173
|
if (!this.isConnected) throw new Error('Client is not connected');
|
|
933
1174
|
const message = generateQuickReplyButtons(text, buttons, options);
|
|
934
|
-
return await this.sock.sendMessage(jid, message, { ai:
|
|
1175
|
+
return await this.sock.sendMessage(jid, message, { ai: this.ai });
|
|
935
1176
|
}
|
|
936
1177
|
|
|
937
1178
|
/**
|
|
@@ -940,9 +1181,10 @@ class WhatsAppClient extends EventEmitter {
|
|
|
940
1181
|
* @param {object} options - Button options
|
|
941
1182
|
*/
|
|
942
1183
|
async sendInteractiveButtonV2(jid, options) {
|
|
1184
|
+
jid = this._normalizeJid(jid);
|
|
943
1185
|
if (!this.isConnected) throw new Error('Client is not connected');
|
|
944
1186
|
const message = generateInteractiveButtonMessage(options);
|
|
945
|
-
return await this.sock.sendMessage(jid, message, { ai:
|
|
1187
|
+
return await this.sock.sendMessage(jid, message, { ai: this.ai });
|
|
946
1188
|
}
|
|
947
1189
|
|
|
948
1190
|
/**
|
|
@@ -953,9 +1195,10 @@ class WhatsAppClient extends EventEmitter {
|
|
|
953
1195
|
* @param {object} options - { title, footer }
|
|
954
1196
|
*/
|
|
955
1197
|
async sendUrlButtonV2(jid, text, buttons, options = {}) {
|
|
1198
|
+
jid = this._normalizeJid(jid);
|
|
956
1199
|
if (!this.isConnected) throw new Error('Client is not connected');
|
|
957
1200
|
const message = generateUrlButtonMessage(text, buttons, options);
|
|
958
|
-
return await this.sock.sendMessage(jid, message, { ai:
|
|
1201
|
+
return await this.sock.sendMessage(jid, message, { ai: this.ai });
|
|
959
1202
|
}
|
|
960
1203
|
|
|
961
1204
|
/**
|
|
@@ -966,9 +1209,10 @@ class WhatsAppClient extends EventEmitter {
|
|
|
966
1209
|
* @param {string} buttonText - Text on the copy button
|
|
967
1210
|
*/
|
|
968
1211
|
async sendCopyCodeV2(jid, text, code, buttonText) {
|
|
1212
|
+
jid = this._normalizeJid(jid);
|
|
969
1213
|
if (!this.isConnected) throw new Error('Client is not connected');
|
|
970
1214
|
const message = generateCopyCodeButton(text, code, buttonText);
|
|
971
|
-
return await this.sock.sendMessage(jid, message, { ai:
|
|
1215
|
+
return await this.sock.sendMessage(jid, message, { ai: this.ai });
|
|
972
1216
|
}
|
|
973
1217
|
|
|
974
1218
|
/**
|
|
@@ -979,9 +1223,10 @@ class WhatsAppClient extends EventEmitter {
|
|
|
979
1223
|
* @param {object} options - { title, footer }
|
|
980
1224
|
*/
|
|
981
1225
|
async sendCombinedButtonsV2(jid, text, buttons, options = {}) {
|
|
1226
|
+
jid = this._normalizeJid(jid);
|
|
982
1227
|
if (!this.isConnected) throw new Error('Client is not connected');
|
|
983
1228
|
const message = generateCombinedButtons(text, buttons, options);
|
|
984
|
-
return await this.sock.sendMessage(jid, message, { ai:
|
|
1229
|
+
return await this.sock.sendMessage(jid, message, { ai: this.ai });
|
|
985
1230
|
}
|
|
986
1231
|
|
|
987
1232
|
/**
|
|
@@ -990,9 +1235,21 @@ class WhatsAppClient extends EventEmitter {
|
|
|
990
1235
|
* @param {object} options - List options (title, buttonText, description, footer, sections)
|
|
991
1236
|
*/
|
|
992
1237
|
async sendListV2(jid, options) {
|
|
1238
|
+
jid = this._normalizeJid(jid);
|
|
993
1239
|
if (!this.isConnected) throw new Error('Client is not connected');
|
|
994
1240
|
const message = generateInteractiveListMessage(options);
|
|
995
|
-
return await this.sock.
|
|
1241
|
+
return await this.sock.relayMessage(jid, message, { ai: this.ai });
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* Send Buttons Cards Message
|
|
1246
|
+
* @param {string} jid - Target JID
|
|
1247
|
+
* @param {object} options - Cards options (text, title, subtile, footer, cards)
|
|
1248
|
+
*/
|
|
1249
|
+
async sendcards(jid, options) {
|
|
1250
|
+
jid = this._normalizeJid(jid);
|
|
1251
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
1252
|
+
return await this.sock.sendMessage(jid, options, { ai: this.ai });
|
|
996
1253
|
}
|
|
997
1254
|
/**
|
|
998
1255
|
* Send an external ad reply with a local image
|
|
@@ -1005,6 +1262,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
1005
1262
|
*/
|
|
1006
1263
|
|
|
1007
1264
|
async sendAdReply(number, msg, imgpath, title, body, sourceurl) {
|
|
1265
|
+
number = this._normalizeJid(number);
|
|
1008
1266
|
if (!this.isConnected) {
|
|
1009
1267
|
throw new Error('Client is not connected');
|
|
1010
1268
|
}
|
|
@@ -1030,7 +1288,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
1030
1288
|
mediaUrl: sourceurl || 'https://m.facebook.com/innovatorssoft'
|
|
1031
1289
|
}
|
|
1032
1290
|
}
|
|
1033
|
-
}, { ai:
|
|
1291
|
+
}, { ai: this.ai });
|
|
1034
1292
|
|
|
1035
1293
|
} catch (err) {
|
|
1036
1294
|
console.error('Failed to send externalAdReply:', err);
|
|
@@ -1074,7 +1332,8 @@ class WhatsAppClient extends EventEmitter {
|
|
|
1074
1332
|
const results = [];
|
|
1075
1333
|
|
|
1076
1334
|
try {
|
|
1077
|
-
for (
|
|
1335
|
+
for (let participantId of participantIds) {
|
|
1336
|
+
participantId = this._normalizeJid(participantId);
|
|
1078
1337
|
try {
|
|
1079
1338
|
let updateResult = await this.sock.groupParticipantsUpdate(
|
|
1080
1339
|
groupId,
|
|
@@ -1211,7 +1470,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
1211
1470
|
// Send as text message
|
|
1212
1471
|
return await this.sock.sendMessage(participantId, {
|
|
1213
1472
|
text: message
|
|
1214
|
-
}, { ai:
|
|
1473
|
+
}, { ai: this.ai });
|
|
1215
1474
|
} catch (error) {
|
|
1216
1475
|
console.error('Error sending group invitation:', error);
|
|
1217
1476
|
throw error;
|
|
@@ -1678,6 +1937,8 @@ class WhatsAppClient extends EventEmitter {
|
|
|
1678
1937
|
* @param {object} metadata - Sticker metadata (packName, author, etc.)
|
|
1679
1938
|
* @returns {Promise<object>} Sent message info
|
|
1680
1939
|
*/
|
|
1940
|
+
|
|
1941
|
+
// send sticker
|
|
1681
1942
|
async sendSticker(chatId, buffer, metadata = {}) {
|
|
1682
1943
|
if (!this.isConnected) throw new Error('Client is not connected');
|
|
1683
1944
|
|
|
@@ -1695,7 +1956,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
1695
1956
|
|
|
1696
1957
|
return await this.sock.sendMessage(chatId, {
|
|
1697
1958
|
sticker: stickerBuffer
|
|
1698
|
-
}, { ai:
|
|
1959
|
+
}, { ai: this.ai });
|
|
1699
1960
|
} catch (error) {
|
|
1700
1961
|
console.error('Error generating sticker:', error);
|
|
1701
1962
|
throw error;
|
|
@@ -2370,12 +2631,34 @@ class WhatsAppClient extends EventEmitter {
|
|
|
2370
2631
|
}
|
|
2371
2632
|
}
|
|
2372
2633
|
|
|
2634
|
+
/**
|
|
2635
|
+
* Decrypt and aggregate poll votes
|
|
2636
|
+
* @param {object} key - The message key of the poll creation message
|
|
2637
|
+
* @param {Array} pollUpdates - The poll updates array from messages.update event
|
|
2638
|
+
* @returns {Promise<Array>} The aggregated poll votes
|
|
2639
|
+
* @throws {Error} If poll creation message is not found
|
|
2640
|
+
*/
|
|
2641
|
+
async decryptPollVotes(key, pollUpdates) {
|
|
2642
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
2643
|
+
|
|
2644
|
+
const pollCreation = this.messageStore.getOriginalMessage(key);
|
|
2645
|
+
if (!pollCreation) {
|
|
2646
|
+
throw new Error('Poll creation message not found in store');
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
return await getAggregateVotesInPollMessage({
|
|
2650
|
+
message: pollCreation.message || pollCreation,
|
|
2651
|
+
pollUpdates: pollUpdates,
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2373
2655
|
/**
|
|
2374
2656
|
* Update profile picture privacy setting
|
|
2375
2657
|
* @param {string} value - 'all' | 'contacts' | 'contact_blacklist' | 'none'
|
|
2376
2658
|
* @returns {Promise<void>}
|
|
2377
2659
|
* @throws {Error} If client is not connected or update fails
|
|
2378
2660
|
*/
|
|
2661
|
+
|
|
2379
2662
|
async updateProfilePicturePrivacy(value) {
|
|
2380
2663
|
if (!this.isConnected) {
|
|
2381
2664
|
throw new Error('Client is not connected');
|
|
@@ -2520,6 +2803,255 @@ class WhatsAppClient extends EventEmitter {
|
|
|
2520
2803
|
throw error;
|
|
2521
2804
|
}
|
|
2522
2805
|
}
|
|
2806
|
+
|
|
2807
|
+
/**
|
|
2808
|
+
* Post a status update (story) with various media types and styles
|
|
2809
|
+
* @param {object} options Options for the status update
|
|
2810
|
+
* @param {string} [options.text] Text content for a text status
|
|
2811
|
+
* @param {string|number[]} [options.backgroundColor] Background color for text status (hex or array of rgb)
|
|
2812
|
+
* @param {number} [options.font] Font type for text status (1-5)
|
|
2813
|
+
* @param {string} [options.textColor] Text color (hex)
|
|
2814
|
+
* @param {string} [options.imagePath] Path to image file for image status
|
|
2815
|
+
* @param {string} [options.videoPath] Path to video file for video status
|
|
2816
|
+
* @param {string} [options.audioPath] Path to audio file for voice note status
|
|
2817
|
+
* @param {Buffer} [options.imageBuffer] Buffer containing image data
|
|
2818
|
+
* @param {Buffer} [options.videoBuffer] Buffer containing video data
|
|
2819
|
+
* @param {Buffer} [options.audioBuffer] Buffer containing audio data
|
|
2820
|
+
* @param {string} [options.caption] Caption for image or video status
|
|
2821
|
+
* @param {boolean} [options.isGif=false] Whether the video should be played as a GIF
|
|
2822
|
+
* @param {Array<string>} [contacts=[]] List of JIDs who should receive the status (important for Multi-Device)
|
|
2823
|
+
* @returns {Promise<object>} The sent message info
|
|
2824
|
+
* @throws {Error} If client is not connected or options are invalid
|
|
2825
|
+
*/
|
|
2826
|
+
async sendStatus(options = {}, contacts = []) {
|
|
2827
|
+
if (!this.isConnected) {
|
|
2828
|
+
throw new Error('Client is not connected');
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
try {
|
|
2832
|
+
let statusMessage;
|
|
2833
|
+
|
|
2834
|
+
// Voice Note Status
|
|
2835
|
+
if (options.audioPath || options.audioBuffer) {
|
|
2836
|
+
const buffer = options.audioBuffer || fs.readFileSync(options.audioPath);
|
|
2837
|
+
statusMessage = StatusHelper.voiceNote(buffer);
|
|
2838
|
+
}
|
|
2839
|
+
// Image Status
|
|
2840
|
+
else if (options.imagePath || options.imageBuffer) {
|
|
2841
|
+
const buffer = options.imageBuffer || fs.readFileSync(options.imagePath);
|
|
2842
|
+
statusMessage = StatusHelper.image(buffer, options.caption || '');
|
|
2843
|
+
}
|
|
2844
|
+
// Video / GIF Status
|
|
2845
|
+
else if (options.videoPath || options.videoBuffer) {
|
|
2846
|
+
const buffer = options.videoBuffer || fs.readFileSync(options.videoPath);
|
|
2847
|
+
if (options.isGif) {
|
|
2848
|
+
statusMessage = StatusHelper.gif(buffer, options.caption || '');
|
|
2849
|
+
} else {
|
|
2850
|
+
statusMessage = StatusHelper.video(buffer, options.caption || '');
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
// Text Status
|
|
2854
|
+
else if (options.text) {
|
|
2855
|
+
statusMessage = StatusHelper.text(
|
|
2856
|
+
options.text,
|
|
2857
|
+
options.backgroundColor || STATUS_BACKGROUNDS.solid.purple,
|
|
2858
|
+
options.font || STATUS_FONTS.SANS_SERIF,
|
|
2859
|
+
options.textColor
|
|
2860
|
+
);
|
|
2861
|
+
} else {
|
|
2862
|
+
throw new Error('Invalid status options: Provide text, image, video, or audio.');
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
// Send using the new StatusHelper
|
|
2866
|
+
return await StatusHelper.send(this.sock, statusMessage, contacts);
|
|
2867
|
+
|
|
2868
|
+
} catch (error) {
|
|
2869
|
+
console.error('Error sending status:', error);
|
|
2870
|
+
throw error;
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
/**
|
|
2875
|
+
* Send a formatted table (header row + data rows)
|
|
2876
|
+
* @param {string} jid
|
|
2877
|
+
* @param {string} title
|
|
2878
|
+
* @param {Array<string>} headers
|
|
2879
|
+
* @param {Array<Array<string>>} rows
|
|
2880
|
+
* @param {object} [quoted=null]
|
|
2881
|
+
* @param {object} [options={}]
|
|
2882
|
+
*/
|
|
2883
|
+
async sendTable(jid, title, headers, rows, quoted = null, options = {}) {
|
|
2884
|
+
jid = this._normalizeJid(jid);
|
|
2885
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
2886
|
+
return await this.sock.sendTable(jid, title, headers, rows, quoted, { ai: this.ai, ...options });
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
/**
|
|
2890
|
+
* Send a bulleted / single-column list (Rich AI)
|
|
2891
|
+
* @param {string} jid
|
|
2892
|
+
* @param {string} title
|
|
2893
|
+
* @param {Array} items
|
|
2894
|
+
* @param {object} [quoted=null]
|
|
2895
|
+
* @param {object} [options={}]
|
|
2896
|
+
*/
|
|
2897
|
+
async sendRichList(jid, title, items, quoted = null, options = {}) {
|
|
2898
|
+
jid = this._normalizeJid(jid);
|
|
2899
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
2900
|
+
return await this.sock.sendList(jid, title, items, quoted, { ai: this.ai, ...options });
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
/**
|
|
2904
|
+
* Send a syntax-highlighted code block
|
|
2905
|
+
* @param {string} jid
|
|
2906
|
+
* @param {string} code
|
|
2907
|
+
* @param {object} [quoted=null]
|
|
2908
|
+
* @param {object} [options={}]
|
|
2909
|
+
*/
|
|
2910
|
+
async sendCodeBlock(jid, code, quoted = null, options = {}) {
|
|
2911
|
+
jid = this._normalizeJid(jid);
|
|
2912
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
2913
|
+
return await this.sock.sendCodeBlock(jid, code, quoted, { ai: this.ai, ...options });
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
/**
|
|
2917
|
+
* Send LaTeX expressions as text
|
|
2918
|
+
* @param {string} jid
|
|
2919
|
+
* @param {object} [quoted=null]
|
|
2920
|
+
* @param {object} [options={}]
|
|
2921
|
+
*/
|
|
2922
|
+
async sendLatex(jid, quoted = null, options = {}) {
|
|
2923
|
+
jid = this._normalizeJid(jid);
|
|
2924
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
2925
|
+
if (quoted && !quoted.key && (quoted.expressions || quoted.text || quoted.headerText || quoted.footer)) {
|
|
2926
|
+
options = quoted;
|
|
2927
|
+
quoted = null;
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
const submessages = [];
|
|
2931
|
+
if (options.headerText) {
|
|
2932
|
+
submessages.push({ messageType: 2, messageText: options.headerText });
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
const latexExpressions = (options.expressions || []).map(expr => {
|
|
2936
|
+
const entry = {
|
|
2937
|
+
latexExpression: expr.latexExpression,
|
|
2938
|
+
url: expr.url,
|
|
2939
|
+
width: expr.width,
|
|
2940
|
+
height: expr.height
|
|
2941
|
+
};
|
|
2942
|
+
if (expr.fontHeight !== undefined) entry.fontHeight = expr.fontHeight;
|
|
2943
|
+
if (expr.imageTopPadding !== undefined) entry.imageTopPadding = expr.imageTopPadding;
|
|
2944
|
+
if (expr.imageLeadingPadding !== undefined) entry.imageLeadingPadding = expr.imageLeadingPadding;
|
|
2945
|
+
if (expr.imageBottomPadding !== undefined) entry.imageBottomPadding = expr.imageBottomPadding;
|
|
2946
|
+
if (expr.imageTrailingPadding !== undefined) entry.imageTrailingPadding = expr.imageTrailingPadding;
|
|
2947
|
+
return entry;
|
|
2948
|
+
});
|
|
2949
|
+
|
|
2950
|
+
submessages.push({ messageType: 8, latexMetadata: { text: options.text || '', expressions: latexExpressions } });
|
|
2951
|
+
|
|
2952
|
+
if (options.footer) {
|
|
2953
|
+
submessages.push({ messageType: 2, messageText: options.footer });
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
return await this.sock.sendRichMessage(jid, submessages, quoted, { ai: this.ai, ...options });
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
/**
|
|
2960
|
+
* Render a LaTeX expression to a PNG image using the online CodeCogs API, upload, and send.
|
|
2961
|
+
* @param {string} jid
|
|
2962
|
+
* @param {object} [quoted=null]
|
|
2963
|
+
* @param {object|string} [options={}] LaTeX string OR options object: { formula/latex/text/expressions, caption }
|
|
2964
|
+
*/
|
|
2965
|
+
async sendLatexImage(jid, quoted = null, options = {}) {
|
|
2966
|
+
jid = this._normalizeJid(jid);
|
|
2967
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
2968
|
+
if (quoted && !quoted.key && (quoted.expressions || quoted.text || quoted.headerText || quoted.footer || typeof quoted === 'string' || (Array.isArray(quoted) && quoted.length > 0))) {
|
|
2969
|
+
options = quoted;
|
|
2970
|
+
quoted = null;
|
|
2971
|
+
}
|
|
2972
|
+
const latexOptions = typeof options === 'string' ? options : { ai: this.ai, ...options };
|
|
2973
|
+
return await this.sock.sendLatexImage(jid, quoted, latexOptions);
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
/**
|
|
2977
|
+
* Render multiple LaTeX expressions as an album message.
|
|
2978
|
+
* @param {string} jid
|
|
2979
|
+
* @param {object} [quoted=null]
|
|
2980
|
+
* @param {object|string} [options={}] LaTeX string OR options object: { expressions, caption }
|
|
2981
|
+
*/
|
|
2982
|
+
async sendLatexInlineImage(jid, quoted = null, options = {}) {
|
|
2983
|
+
jid = this._normalizeJid(jid);
|
|
2984
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
2985
|
+
if (quoted && !quoted.key && (quoted.expressions || quoted.text || quoted.headerText || quoted.footer || typeof quoted === 'string' || (Array.isArray(quoted) && quoted.length > 0))) {
|
|
2986
|
+
options = quoted;
|
|
2987
|
+
quoted = null;
|
|
2988
|
+
}
|
|
2989
|
+
const latexOptions = typeof options === 'string' ? options : { ai: this.ai, ...options };
|
|
2990
|
+
return await this.sock.sendLatexInlineImage(jid, quoted, latexOptions);
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
/**
|
|
2994
|
+
* Send a rich markdown text message
|
|
2995
|
+
* @param {string} jid
|
|
2996
|
+
* @param {string} text
|
|
2997
|
+
* @param {object} [quoted=null]
|
|
2998
|
+
*/
|
|
2999
|
+
async sendMarkdown(jid, text, quoted = null) {
|
|
3000
|
+
jid = this._normalizeJid(jid);
|
|
3001
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
3002
|
+
return await this.sock.sendMarkdown(jid, text, quoted);
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
/**
|
|
3006
|
+
* Send a fully custom rich message by assembling raw submessage objects
|
|
3007
|
+
* @param {string} jid
|
|
3008
|
+
* @param {Array<object>} messages
|
|
3009
|
+
* @param {object} [quoted=null]
|
|
3010
|
+
* @param {object} [options={}]
|
|
3011
|
+
*/
|
|
3012
|
+
async sendRichMessage(jid, messages, quoted = null, options = {}) {
|
|
3013
|
+
jid = this._normalizeJid(jid);
|
|
3014
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
3015
|
+
return await this.sock.sendRichMessage(jid, messages, quoted, { ai: this.ai, ...options });
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
/**
|
|
3019
|
+
* Capture a unified response from an incoming Meta AI message
|
|
3020
|
+
* @param {object} message
|
|
3021
|
+
*/
|
|
3022
|
+
captureUnifiedResponse(message) {
|
|
3023
|
+
if (!this.sock) throw new Error('Client is not initialized');
|
|
3024
|
+
return this.sock.captureUnifiedResponse(message);
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
/**
|
|
3028
|
+
* Send a captured unified response
|
|
3029
|
+
* @param {string} jid
|
|
3030
|
+
* @param {object} [quoted=null]
|
|
3031
|
+
* @param {object} captured
|
|
3032
|
+
*/
|
|
3033
|
+
|
|
3034
|
+
async sendUnifiedResponse(jid, quoted = null, captured) {
|
|
3035
|
+
jid = this._normalizeJid(jid);
|
|
3036
|
+
if (!this.isConnected) throw new Error('Client is not connected');
|
|
3037
|
+
return await this.sock.sendUnifiedResponse(jid, quoted, captured, { ai: this.ai });
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
async sendGroupStatus(jid, content = {}, options = {}) {
|
|
3041
|
+
if (!this.isConnected) {
|
|
3042
|
+
throw new Error('Client is not connected');
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
if (!jid || typeof jid !== 'string' || !jid.endsWith('@g.us')) {
|
|
3046
|
+
throw new Error('Invalid group JID. Expected a JID ending with @g.us');
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
if (!content || typeof content !== 'object') {
|
|
3050
|
+
throw new Error('Invalid content. Expected an object');
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
return await this.sock.sendMessage(jid, { ...content, groupStatus: true }, options);
|
|
3054
|
+
}
|
|
2523
3055
|
}
|
|
2524
3056
|
|
|
2525
3057
|
function formatCode(code) {
|
|
@@ -2532,4 +3064,9 @@ function formatCode(code) {
|
|
|
2532
3064
|
module.exports = {
|
|
2533
3065
|
WhatsAppClient: WhatsAppClient,
|
|
2534
3066
|
Group: Group,
|
|
3067
|
+
STATUS_BACKGROUNDS,
|
|
3068
|
+
STATUS_FONTS,
|
|
3069
|
+
renderLatexToPng,
|
|
3070
|
+
uploadUnencryptedToWA,
|
|
3071
|
+
RichSubMessageType
|
|
2535
3072
|
}
|