@roidev/kachina-md 2.1.2 ā 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client/Client.js +220 -72
- package/lib/index.d.ts +7 -1
- package/package.json +1 -1
package/lib/client/Client.js
CHANGED
|
@@ -3,7 +3,8 @@ import makeWASocket, {
|
|
|
3
3
|
useMultiFileAuthState,
|
|
4
4
|
makeCacheableSignalKeyStore,
|
|
5
5
|
fetchLatestBaileysVersion,
|
|
6
|
-
downloadMediaMessage
|
|
6
|
+
downloadMediaMessage,
|
|
7
|
+
Browsers
|
|
7
8
|
} from 'baileys';
|
|
8
9
|
import { Boom } from '@hapi/boom';
|
|
9
10
|
import pino from 'pino';
|
|
@@ -109,7 +110,7 @@ export class Client extends EventEmitter {
|
|
|
109
110
|
keys: makeCacheableSignalKeyStore(state.keys, this.config.logger)
|
|
110
111
|
},
|
|
111
112
|
logger: this.config.logger,
|
|
112
|
-
browser:
|
|
113
|
+
browser: Browsers.macOS('safari'),
|
|
113
114
|
getMessage: async (key) => {
|
|
114
115
|
if (this.store) {
|
|
115
116
|
const msg = await this.store.loadMessage(key.remoteJid, key.id);
|
|
@@ -125,67 +126,56 @@ export class Client extends EventEmitter {
|
|
|
125
126
|
|
|
126
127
|
this.sock.ev.on('creds.update', saveCreds);
|
|
127
128
|
|
|
128
|
-
// Handle pairing code request (
|
|
129
|
+
// Handle pairing code request (before connection.update listener)
|
|
129
130
|
if (this.config.loginMethod === 'pairing' && !state.creds.registered) {
|
|
130
131
|
if (!this.config.phoneNumber) {
|
|
131
132
|
throw new Error('Phone number is required for pairing method. Example: { phoneNumber: "628123456789" }');
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
const phoneNumber = this.config.phoneNumber.replace(/[^0-9]/g, '');
|
|
135
|
-
|
|
136
136
|
if (phoneNumber.length < 10) {
|
|
137
137
|
throw new Error('Invalid phone number format. Use country code without +. Example: 628123456789');
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
console.log('š Initiating pairing code process...');
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (isLastAttempt) {
|
|
176
|
-
console.error('ā Max pairing retries reached. Please try again later.');
|
|
177
|
-
throw error; // Re-throw on last attempt
|
|
178
|
-
} else {
|
|
179
|
-
console.log(`ā³ Retrying in 2 seconds...`);
|
|
180
|
-
}
|
|
142
|
+
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
143
|
+
let retryCount = 0;
|
|
144
|
+
const maxRetries = 3;
|
|
145
|
+
let pairingSuccess = false;
|
|
146
|
+
|
|
147
|
+
while (retryCount < maxRetries && !pairingSuccess) {
|
|
148
|
+
try {
|
|
149
|
+
await delay(3000);
|
|
150
|
+
const code = await this.sock.requestPairingCode(phoneNumber);
|
|
151
|
+
this.emit('pairing.code', code);
|
|
152
|
+
|
|
153
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
154
|
+
console.log('ā WhatsApp Pairing Code ā');
|
|
155
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤');
|
|
156
|
+
console.log(`ā Code: ${chalk.bgYellowBright.white(code)} ā`);
|
|
157
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
158
|
+
console.log('\nSteps to pair:');
|
|
159
|
+
console.log('1. Open WhatsApp on your phone');
|
|
160
|
+
console.log('2. Go to Settings > Linked Devices');
|
|
161
|
+
console.log('3. Tap "Link a Device"');
|
|
162
|
+
console.log('4. Enter the code above\n');
|
|
163
|
+
|
|
164
|
+
pairingSuccess = true;
|
|
165
|
+
break;
|
|
166
|
+
} catch (err) {
|
|
167
|
+
retryCount++;
|
|
168
|
+
console.error(`Pairing attempt ${retryCount}/${maxRetries} failed:`, err.message);
|
|
169
|
+
this.emit('pairing.error', err);
|
|
170
|
+
|
|
171
|
+
if (retryCount >= maxRetries) {
|
|
172
|
+
console.error('ā Max pairing retries reached. Please try again later.');
|
|
173
|
+
throw err;
|
|
181
174
|
}
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
175
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
});
|
|
176
|
+
await delay(2000);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
189
179
|
}
|
|
190
180
|
|
|
191
181
|
this.sock.ev.on('connection.update', async (update) => {
|
|
@@ -233,6 +223,7 @@ export class Client extends EventEmitter {
|
|
|
233
223
|
async handleConnectionUpdate(update) {
|
|
234
224
|
const { connection, lastDisconnect, qr } = update;
|
|
235
225
|
|
|
226
|
+
// Handle QR code for QR login method
|
|
236
227
|
if (qr && this.config.loginMethod === 'qr') {
|
|
237
228
|
qrcode.generate(qr, { small: true });
|
|
238
229
|
}
|
|
@@ -253,15 +244,11 @@ export class Client extends EventEmitter {
|
|
|
253
244
|
this.user = this.sock.user;
|
|
254
245
|
this.emit('ready', this.user);
|
|
255
246
|
|
|
256
|
-
// Log success message
|
|
257
247
|
if (this.config.loginMethod === 'pairing') {
|
|
258
248
|
console.log('\nā Successfully connected via pairing code!\n');
|
|
259
249
|
}
|
|
260
250
|
} else if (connection === 'connecting') {
|
|
261
251
|
this.emit('connecting');
|
|
262
|
-
if (this.config.loginMethod === 'pairing') {
|
|
263
|
-
console.log('Waiting for pairing code confirmation...');
|
|
264
|
-
}
|
|
265
252
|
}
|
|
266
253
|
}
|
|
267
254
|
|
|
@@ -480,11 +467,92 @@ export class Client extends EventEmitter {
|
|
|
480
467
|
});
|
|
481
468
|
}
|
|
482
469
|
|
|
470
|
+
/**
|
|
471
|
+
* Helper function to unwrap view once message from various structures
|
|
472
|
+
* @private
|
|
473
|
+
* @param {Object} quotedMessage - The quoted message object
|
|
474
|
+
* @returns {Object|null} Unwrapped message or null if not view once
|
|
475
|
+
*/
|
|
476
|
+
_unwrapViewOnceMessage(quotedMessage) {
|
|
477
|
+
const quotedMsg = quotedMessage?.message;
|
|
478
|
+
const quotedRaw = quotedMessage?.raw?.message?.extendedTextMessage?.contextInfo?.quotedMessage;
|
|
479
|
+
|
|
480
|
+
let innerMsg = null;
|
|
481
|
+
let isViewOnce = false;
|
|
482
|
+
|
|
483
|
+
// Helper to extract from wrapper
|
|
484
|
+
const extractFromWrapper = (wrapper) => {
|
|
485
|
+
if (wrapper?.viewOnceMessageV2?.message) {
|
|
486
|
+
return { msg: wrapper.viewOnceMessageV2.message, isVO: true };
|
|
487
|
+
}
|
|
488
|
+
if (wrapper?.viewOnceMessageV2Extension?.message) {
|
|
489
|
+
return { msg: wrapper.viewOnceMessageV2Extension.message, isVO: true };
|
|
490
|
+
}
|
|
491
|
+
if (wrapper?.viewOnceMessage?.message) {
|
|
492
|
+
return { msg: wrapper.viewOnceMessage.message, isVO: true };
|
|
493
|
+
}
|
|
494
|
+
return null;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// Try multiple paths to find view once message
|
|
498
|
+
// Path 1: Direct from quotedRaw
|
|
499
|
+
if (quotedRaw) {
|
|
500
|
+
const extracted = extractFromWrapper(quotedRaw);
|
|
501
|
+
if (extracted) {
|
|
502
|
+
innerMsg = extracted.msg;
|
|
503
|
+
isViewOnce = extracted.isVO;
|
|
504
|
+
} else if (quotedRaw.ephemeralMessage?.message) {
|
|
505
|
+
const ephExtracted = extractFromWrapper(quotedRaw.ephemeralMessage.message);
|
|
506
|
+
if (ephExtracted) {
|
|
507
|
+
innerMsg = ephExtracted.msg;
|
|
508
|
+
isViewOnce = ephExtracted.isVO;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Path 2: From quotedMsg
|
|
514
|
+
if (!innerMsg && quotedMsg) {
|
|
515
|
+
const extracted = extractFromWrapper(quotedMsg);
|
|
516
|
+
if (extracted) {
|
|
517
|
+
innerMsg = extracted.msg;
|
|
518
|
+
isViewOnce = extracted.isVO;
|
|
519
|
+
} else if (quotedMsg.ephemeralMessage?.message) {
|
|
520
|
+
const ephExtracted = extractFromWrapper(quotedMsg.ephemeralMessage.message);
|
|
521
|
+
if (ephExtracted) {
|
|
522
|
+
innerMsg = ephExtracted.msg;
|
|
523
|
+
isViewOnce = ephExtracted.isVO;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Path 3: Already unwrapped (has viewOnce flag)
|
|
529
|
+
if (!innerMsg && quotedMsg) {
|
|
530
|
+
if (quotedMsg.imageMessage?.viewOnce ||
|
|
531
|
+
quotedMsg.videoMessage?.viewOnce ||
|
|
532
|
+
quotedMsg.audioMessage?.viewOnce) {
|
|
533
|
+
innerMsg = quotedMsg;
|
|
534
|
+
isViewOnce = true;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Path 4: Check raw message directly
|
|
539
|
+
if (!innerMsg && quotedRaw) {
|
|
540
|
+
if (quotedRaw.imageMessage?.viewOnce ||
|
|
541
|
+
quotedRaw.videoMessage?.viewOnce ||
|
|
542
|
+
quotedRaw.audioMessage?.viewOnce) {
|
|
543
|
+
innerMsg = quotedRaw;
|
|
544
|
+
isViewOnce = true;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return isViewOnce ? innerMsg : null;
|
|
549
|
+
}
|
|
550
|
+
|
|
483
551
|
/**
|
|
484
552
|
* Read and download view once message
|
|
485
553
|
* @async
|
|
486
554
|
* @param {Object} quotedMessage - The quoted message object (m.quoted)
|
|
487
|
-
* @returns {Promise<{buffer: Buffer, type: 'image'|'video', caption: string}>} Object containing media buffer, type, and caption
|
|
555
|
+
* @returns {Promise<{buffer: Buffer, type: 'image'|'video'|'audio', caption: string, mimetype?: string, ptt?: boolean}>} Object containing media buffer, type, and caption
|
|
488
556
|
* @throws {Error} If quoted message is not provided or not a view once message
|
|
489
557
|
* @example
|
|
490
558
|
* const { buffer, type, caption } = await client.readViewOnce(m.quoted);
|
|
@@ -497,29 +565,100 @@ export class Client extends EventEmitter {
|
|
|
497
565
|
throw new Error('Quoted message is required');
|
|
498
566
|
}
|
|
499
567
|
|
|
500
|
-
//
|
|
501
|
-
const
|
|
502
|
-
|
|
568
|
+
// Try to unwrap view once message
|
|
569
|
+
const innerMsg = this._unwrapViewOnceMessage(quotedMessage);
|
|
570
|
+
|
|
571
|
+
if (!innerMsg) {
|
|
572
|
+
throw new Error('Message is not a view once message or has already been opened');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Extract media messages
|
|
576
|
+
const ViewOnceImg = innerMsg.imageMessage;
|
|
577
|
+
const ViewOnceVid = innerMsg.videoMessage;
|
|
578
|
+
const ViewOnceAud = innerMsg.audioMessage;
|
|
503
579
|
|
|
504
|
-
//
|
|
505
|
-
|
|
506
|
-
|
|
580
|
+
// Get the media message
|
|
581
|
+
const mediaMsg = ViewOnceImg || ViewOnceVid || ViewOnceAud;
|
|
582
|
+
|
|
583
|
+
if (!mediaMsg) {
|
|
584
|
+
throw new Error('No media found in view once message');
|
|
507
585
|
}
|
|
508
586
|
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
587
|
+
// Check if media key exists (if not, it's expired/opened)
|
|
588
|
+
if (!mediaMsg.mediaKey || mediaMsg.mediaKey.length === 0) {
|
|
589
|
+
throw new Error('View once message has been opened or expired. Reply to view once BEFORE it is opened.');
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Download the media with multiple fallback methods
|
|
593
|
+
let buffer = null;
|
|
594
|
+
const errors = [];
|
|
595
|
+
|
|
596
|
+
// Method 1: Try using quotedMessage.download() if available
|
|
597
|
+
if (typeof quotedMessage.download === 'function') {
|
|
598
|
+
try {
|
|
599
|
+
buffer = await quotedMessage.download();
|
|
600
|
+
this.config.logger?.debug('Downloaded view once using m.quoted.download()');
|
|
601
|
+
} catch (err) {
|
|
602
|
+
errors.push(`Method 1 (m.quoted.download): ${err.message}`);
|
|
603
|
+
this.config.logger?.debug('Download via m.quoted.download failed:', err.message);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Method 2: Try downloadMediaMessage with innerMsg
|
|
608
|
+
if (!buffer) {
|
|
609
|
+
try {
|
|
610
|
+
buffer = await downloadMediaMessage(
|
|
611
|
+
{ message: innerMsg, key: quotedMessage.key },
|
|
612
|
+
'buffer',
|
|
613
|
+
{},
|
|
614
|
+
{ logger: this.config.logger }
|
|
615
|
+
);
|
|
616
|
+
this.config.logger?.debug('Downloaded view once using downloadMediaMessage with innerMsg');
|
|
617
|
+
} catch (err) {
|
|
618
|
+
errors.push(`Method 2 (downloadMediaMessage with innerMsg): ${err.message}`);
|
|
619
|
+
this.config.logger?.debug('Download via downloadMediaMessage (innerMsg) failed:', err.message);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Method 3: Try downloadMediaMessage with original quotedMessage
|
|
624
|
+
if (!buffer) {
|
|
625
|
+
try {
|
|
626
|
+
buffer = await downloadMediaMessage(
|
|
627
|
+
quotedMessage,
|
|
628
|
+
'buffer',
|
|
629
|
+
{},
|
|
630
|
+
{ logger: this.config.logger }
|
|
631
|
+
);
|
|
632
|
+
this.config.logger?.debug('Downloaded view once using downloadMediaMessage with quotedMessage');
|
|
633
|
+
} catch (err) {
|
|
634
|
+
errors.push(`Method 3 (downloadMediaMessage with quotedMessage): ${err.message}`);
|
|
635
|
+
this.config.logger?.debug('Download via downloadMediaMessage (quotedMessage) failed:', err.message);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (!buffer) {
|
|
640
|
+
const errorMsg = `Failed to download view once media. Tried ${errors.length} methods:\n${errors.join('\n')}`;
|
|
641
|
+
this.config.logger?.error(errorMsg);
|
|
642
|
+
throw new Error(errorMsg);
|
|
643
|
+
}
|
|
516
644
|
|
|
517
645
|
// Return buffer with type and caption
|
|
518
|
-
|
|
646
|
+
const result = {
|
|
519
647
|
buffer,
|
|
520
|
-
|
|
521
|
-
caption: ViewOnceImg?.caption || ViewOnceVid?.caption || ''
|
|
648
|
+
caption: ViewOnceImg?.caption || ViewOnceVid?.caption || ViewOnceAud?.caption || ''
|
|
522
649
|
};
|
|
650
|
+
|
|
651
|
+
if (ViewOnceImg) {
|
|
652
|
+
result.type = 'image';
|
|
653
|
+
} else if (ViewOnceVid) {
|
|
654
|
+
result.type = 'video';
|
|
655
|
+
} else if (ViewOnceAud) {
|
|
656
|
+
result.type = 'audio';
|
|
657
|
+
result.mimetype = ViewOnceAud.mimetype || 'audio/mpeg';
|
|
658
|
+
result.ptt = !!ViewOnceAud.ptt;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return result;
|
|
523
662
|
}
|
|
524
663
|
|
|
525
664
|
/**
|
|
@@ -537,7 +676,8 @@ export class Client extends EventEmitter {
|
|
|
537
676
|
async sendViewOnce(jid, quotedMessage, options = {}) {
|
|
538
677
|
try {
|
|
539
678
|
// Read the view once message
|
|
540
|
-
const
|
|
679
|
+
const result = await this.readViewOnce(quotedMessage);
|
|
680
|
+
const { buffer, type, caption, mimetype, ptt } = result;
|
|
541
681
|
|
|
542
682
|
// Send based on type
|
|
543
683
|
if (type === 'image') {
|
|
@@ -545,11 +685,19 @@ export class Client extends EventEmitter {
|
|
|
545
685
|
jpegThumbnail: null,
|
|
546
686
|
...options
|
|
547
687
|
});
|
|
548
|
-
} else {
|
|
688
|
+
} else if (type === 'video') {
|
|
549
689
|
return await this.sendVideo(jid, buffer, caption, {
|
|
550
690
|
jpegThumbnail: null,
|
|
551
691
|
...options
|
|
552
692
|
});
|
|
693
|
+
} else if (type === 'audio') {
|
|
694
|
+
return await this.sendAudio(jid, buffer, {
|
|
695
|
+
mimetype: mimetype || 'audio/mpeg',
|
|
696
|
+
ptt: ptt || false,
|
|
697
|
+
...options
|
|
698
|
+
});
|
|
699
|
+
} else {
|
|
700
|
+
throw new Error(`Unsupported view once media type: ${type}`);
|
|
553
701
|
}
|
|
554
702
|
} catch (error) {
|
|
555
703
|
throw new Error(`Failed to send view once: ${error.message}`);
|
package/lib/index.d.ts
CHANGED
|
@@ -51,7 +51,13 @@ export class Client extends EventEmitter {
|
|
|
51
51
|
sendReact(jid: string, messageKey: any, emoji: string): Promise<any>;
|
|
52
52
|
|
|
53
53
|
// View once methods
|
|
54
|
-
readViewOnce(quotedMessage: any): Promise<{
|
|
54
|
+
readViewOnce(quotedMessage: any): Promise<{
|
|
55
|
+
buffer: Buffer;
|
|
56
|
+
type: 'image' | 'video' | 'audio';
|
|
57
|
+
caption: string;
|
|
58
|
+
mimetype?: string;
|
|
59
|
+
ptt?: boolean;
|
|
60
|
+
}>;
|
|
55
61
|
sendViewOnce(jid: string, quotedMessage: any, options?: any): Promise<any>;
|
|
56
62
|
|
|
57
63
|
// Group methods
|