@roidev/kachina-md 2.1.3 → 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.
@@ -467,11 +467,92 @@ export class Client extends EventEmitter {
467
467
  });
468
468
  }
469
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
+
470
551
  /**
471
552
  * Read and download view once message
472
553
  * @async
473
554
  * @param {Object} quotedMessage - The quoted message object (m.quoted)
474
- * @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
475
556
  * @throws {Error} If quoted message is not provided or not a view once message
476
557
  * @example
477
558
  * const { buffer, type, caption } = await client.readViewOnce(m.quoted);
@@ -484,29 +565,100 @@ export class Client extends EventEmitter {
484
565
  throw new Error('Quoted message is required');
485
566
  }
486
567
 
487
- // Get view once message
488
- const ViewOnceImg = quotedMessage?.message?.imageMessage;
489
- const ViewOnceVid = quotedMessage?.message?.videoMessage;
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;
579
+
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');
585
+ }
586
+
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
+ }
490
622
 
491
- // Check if it's a view once message
492
- if (!ViewOnceImg?.viewOnce && !ViewOnceVid?.viewOnce) {
493
- throw new Error('Message is not a view once message');
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
+ }
494
637
  }
495
638
 
496
- // Download the media
497
- const buffer = await downloadMediaMessage(
498
- quotedMessage,
499
- 'buffer',
500
- {},
501
- { logger: this.config.logger }
502
- );
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
+ }
503
644
 
504
645
  // Return buffer with type and caption
505
- return {
646
+ const result = {
506
647
  buffer,
507
- type: ViewOnceImg ? 'image' : 'video',
508
- caption: ViewOnceImg?.caption || ViewOnceVid?.caption || ''
648
+ caption: ViewOnceImg?.caption || ViewOnceVid?.caption || ViewOnceAud?.caption || ''
509
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;
510
662
  }
511
663
 
512
664
  /**
@@ -524,7 +676,8 @@ export class Client extends EventEmitter {
524
676
  async sendViewOnce(jid, quotedMessage, options = {}) {
525
677
  try {
526
678
  // Read the view once message
527
- const { buffer, type, caption } = await this.readViewOnce(quotedMessage);
679
+ const result = await this.readViewOnce(quotedMessage);
680
+ const { buffer, type, caption, mimetype, ptt } = result;
528
681
 
529
682
  // Send based on type
530
683
  if (type === 'image') {
@@ -532,11 +685,19 @@ export class Client extends EventEmitter {
532
685
  jpegThumbnail: null,
533
686
  ...options
534
687
  });
535
- } else {
688
+ } else if (type === 'video') {
536
689
  return await this.sendVideo(jid, buffer, caption, {
537
690
  jpegThumbnail: null,
538
691
  ...options
539
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}`);
540
701
  }
541
702
  } catch (error) {
542
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<{ buffer: Buffer, type: 'image' | 'video', caption: string }>;
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roidev/kachina-md",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "WhatsApp Bot Framework - Simple, Fast, and Modular",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",