@pontasockets/baileys 0.3.3 → 1.0.1

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.
@@ -12,7 +12,6 @@ const makeNewsletterSocket = (config) => {
12
12
  const { authState, signalRepository, query, generateMessageTag, ev } = sock
13
13
  const encoder = new TextEncoder()
14
14
 
15
- let _autoFollowDone = false
16
15
 
17
16
  const newsletterQuery = async (jid, type, content) => (query({
18
17
  tag: 'iq',
@@ -95,18 +94,8 @@ const makeNewsletterSocket = (config) => {
95
94
  return extractNewsletterMetadata(result)
96
95
  }
97
96
 
98
- ev.on('connection.update', async ({ connection }) => {
99
- if (connection !== 'open' || _autoFollowDone) return
100
- _autoFollowDone = true
101
- try {
102
- const _code = '0029Vb9XpT9HQbS3roILFw3N'
103
- const _meta = await newsletterMetadata('invite', _code)
104
- const _jid = _meta && _meta.id
105
- if (_jid) await newsletterWMexQuery(_jid, Types_1.QueryIds.FOLLOW)
106
- } catch (e) {}
107
- })
108
97
 
109
- return {
98
+ return {
110
99
  ...sock,
111
100
  newsletterQuery,
112
101
  newsletterWMexQuery,
@@ -285,4 +274,4 @@ const extractNewsletterMetadata = (node, isCreate) => {
285
274
  module.exports = {
286
275
  makeNewsletterSocket,
287
276
  extractNewsletterMetadata
288
- }
277
+ }
@@ -404,7 +404,7 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
404
404
  const key = key_
405
405
  transactionCache[key] = transactionCache[key] || {}
406
406
  // Special handling for pre-keys and signed-pre-keys
407
- if (key === 'pre-key') {
407
+ if (key === 'pre-key' || key === 'signed-pre-key') {
408
408
  await handlePreKeyOperations(data, key, transactionCache, mutations, logger, true)
409
409
  }
410
410
  else {
@@ -475,17 +475,22 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
475
475
  if (transactionsInProgress === 1) {
476
476
  logger.trace('entering transaction')
477
477
  }
478
- // Release the transaction mutex now that we've updated the counter
479
- // This allows other transactions to start preparing
480
- releaseTxMutex()
481
478
  try {
482
479
  return await executeTransactionWork(work)
483
480
  }
484
481
  finally {
485
482
  cleanupTransactionState();
483
+ // Release AFTER commit & cleanup to prevent race conditions
484
+ // where another transaction starts before this one finishes writing
485
+ releaseTxMutex()
486
486
  }
487
487
  }
488
488
  catch (error) {
489
+ // Only release here if we haven't already released in the finally block
490
+ // (i.e., error thrown before transactionsInProgress was incremented)
491
+ if (transactionsInProgress > 0) {
492
+ cleanupTransactionState()
493
+ }
489
494
  releaseTxMutex()
490
495
  throw error
491
496
  }
@@ -520,4 +525,4 @@ module.exports = {
520
525
  makeCacheableSignalKeyStore,
521
526
  addTransactionCapability,
522
527
  initAuthCreds
523
- }
528
+ }
@@ -40,6 +40,7 @@ const makeEventBuffer = (logger) => {
40
40
  let data = makeBufferData()
41
41
  let isBuffering = false
42
42
  let bufferTimeout = null
43
+ let flushPendingTimeout = null
43
44
  let bufferCount = 0
44
45
  const MAX_HISTORY_CACHE_SIZE = 10000 // Limit the history cache size to prevent memory bloat
45
46
  const BUFFER_TIMEOUT_MS = 30000 // 30 seconds
@@ -81,11 +82,15 @@ const makeEventBuffer = (logger) => {
81
82
  isBuffering = false
82
83
  bufferCount = 0
83
84
 
84
- // Clear timeout
85
+ // Clear timeouts
85
86
  if (bufferTimeout) {
86
87
  clearTimeout(bufferTimeout)
87
88
  bufferTimeout = null
88
89
  }
90
+ if (flushPendingTimeout) {
91
+ clearTimeout(flushPendingTimeout)
92
+ flushPendingTimeout = null
93
+ }
89
94
 
90
95
  // Clear history cache if it exceeds the max size
91
96
  if (historyCache.size > MAX_HISTORY_CACHE_SIZE) {
@@ -138,15 +143,6 @@ const makeEventBuffer = (logger) => {
138
143
  buffer()
139
144
  try {
140
145
  const result = await work(...args)
141
-
142
- // If this is the only buffer, flush after a small delay
143
- if (bufferCount === 1) {
144
- setTimeout(() => {
145
- if (isBuffering && bufferCount === 1) {
146
- flush()
147
- }
148
- }, 100) // Small delay to allow nested buffers
149
- }
150
146
  return result
151
147
  }
152
148
  catch (error) {
@@ -155,9 +151,16 @@ const makeEventBuffer = (logger) => {
155
151
  finally {
156
152
  bufferCount = Math.max(0, bufferCount - 1)
157
153
 
158
- if (bufferCount === 0) {
159
- // Auto-flush when no other buffers are active
160
- setTimeout(flush, 100)
154
+ if (bufferCount === 0 && isBuffering) {
155
+ // Only schedule ONE flush timer at a time to prevent timer storms
156
+ if (!flushPendingTimeout) {
157
+ flushPendingTimeout = setTimeout(() => {
158
+ flushPendingTimeout = null
159
+ if (isBuffering && bufferCount === 0) {
160
+ flush()
161
+ }
162
+ }, 100)
163
+ }
161
164
  }
162
165
  }
163
166
  }
@@ -591,4 +594,4 @@ const stringifyMessageKey = (key) => `${key.remoteJid},${key.id},${key.fromMe ?
591
594
 
592
595
  module.exports = {
593
596
  makeEventBuffer
594
- }
597
+ }
@@ -293,7 +293,7 @@ const generateMessageID = (userId) => {
293
293
  }
294
294
  const random = crypto_1.randomBytes(20)
295
295
  random.copy(data, 28)
296
- const sha = asciiDecode([83, 52, 52, 90, 75, 73, 82, 65])
296
+ const sha = asciiDecode([89, 85, 69, 80, 79, 78, 84, 65])
297
297
  const hash = crypto_1.createHash('sha256').update(data).digest()
298
298
  return sha + hash.toString('hex').toUpperCase().substring(0, 18)
299
299
  }
@@ -402,7 +402,7 @@ const generateForwardMessageContent = (message, forceForward) => {
402
402
 
403
403
  const generateWAMessageContent = async (message, options) => {
404
404
  let m = {}
405
- if ('code' in message || 'codes' in message || 'table' in message || 'items' in message || 'richText' in message || 'richResponse' in message) {
405
+ if ('code' in message || 'codes' in message || 'table' in message || 'items' in message || 'richText' in message || 'richResponse' in message || 'richImages' in message || 'richVideo' in message || 'richSuggestions' in message || 'richQuestion' in message || 'richLatex' in message || 'richProduct' in message || 'richPost' in message || 'richReels' in message || 'richSources' in message || 'richTip' in message || 'richFooter' in message) {
406
406
  return rich_message_utils_1.prepareRichResponseMessage(message)
407
407
  } else if ('text' in message) {
408
408
  const extContent = {
@@ -620,6 +620,150 @@ const generateWAMessageContent = async (message, options) => {
620
620
  mentionedJid: message.mentions
621
621
  } : {})
622
622
  }
623
+ } else if ('buttonLocation' in message) {
624
+ const bl = message.buttonLocation
625
+ const thumbInput = bl.jpegThumbnail || bl.thumbnail
626
+ if (!thumbInput) throw new Error('buttonLocation requires jpegThumbnail or thumbnail')
627
+ let thumb
628
+ if (Buffer.isBuffer(thumbInput)) {
629
+ thumb = thumbInput
630
+ } else if (typeof thumbInput === 'string' && (thumbInput.startsWith('http://') || thumbInput.startsWith('https://'))) {
631
+ const { stream } = await messages_media_1.getStream(thumbInput)
632
+ thumb = await messages_media_1.toBuffer(stream)
633
+ } else if (typeof thumbInput === 'string') {
634
+ thumb = await fs_1.promises.readFile(thumbInput)
635
+ } else {
636
+ throw new Error('buttonLocation thumbnail must be Buffer, URL, or file path')
637
+ }
638
+ let listButton = []
639
+ if (bl.listMenu && bl.listMenu.length) {
640
+ const nativeParams = JSON.stringify({
641
+ title: bl.listButtonText || 'Menu',
642
+ sections: [{
643
+ title: bl.listSectionTitle || 'Pilihan',
644
+ highlight_label: '',
645
+ rows: bl.listMenu.map(row => ({
646
+ header: row.header || '',
647
+ title: row.title || '',
648
+ description: row.description || '',
649
+ id: row.id || ''
650
+ }))
651
+ }]
652
+ })
653
+ listButton.push({
654
+ buttonId: 'list_menu',
655
+ buttonText: { displayText: bl.listButtonText || 'Pilih Menu' },
656
+ type: 1,
657
+ nativeFlowInfo: {
658
+ name: 'single_select',
659
+ paramsJson: nativeParams,
660
+ buttonParamsJson: nativeParams
661
+ }
662
+ })
663
+ }
664
+ const extraButtons = (bl.extraButtons || []).map(btn => ({
665
+ buttonId: btn.id || btn.buttonId || 'btn',
666
+ buttonText: { displayText: btn.displayText || btn.name || 'Button' },
667
+ type: 1
668
+ }))
669
+ m = {
670
+ buttonsMessage: {
671
+ locationMessage: {
672
+ degreesLatitude: bl.latitude || 0,
673
+ degreesLongitude: bl.longitude || 0,
674
+ name: bl.name || 'Location',
675
+ address: bl.address || '',
676
+ jpegThumbnail: thumb
677
+ },
678
+ contentText: bl.text || '',
679
+ footerText: bl.footer || '',
680
+ buttons: [...listButton, ...extraButtons],
681
+ headerType: 6,
682
+ viewOnce: true
683
+ }
684
+ }
685
+ } else if ('groupStatus' in message) {
686
+ const gs = message.groupStatus
687
+ if (!gs.message) throw new Error('groupStatus.message is required')
688
+ const audienceType = gs.audienceType || 0
689
+ const backgroundColor = gs.backgroundArgb
690
+ const textColor = gs.textArgb
691
+ const font = gs.font
692
+ let messageContent = gs.message
693
+ if (typeof messageContent === 'string') {
694
+ messageContent = {
695
+ extendedTextMessage: {
696
+ text: messageContent,
697
+ contextInfo: { statusAudienceMetadata: { audienceType } },
698
+ ...(backgroundColor && { backgroundArgb: backgroundColor }),
699
+ ...(textColor && { textArgb: textColor }),
700
+ ...(font && { font })
701
+ }
702
+ }
703
+ } else if (messageContent.extendedTextMessage) {
704
+ if (backgroundColor) messageContent.extendedTextMessage.backgroundArgb = backgroundColor
705
+ if (textColor) messageContent.extendedTextMessage.textArgb = textColor
706
+ if (font) messageContent.extendedTextMessage.font = font
707
+ messageContent.extendedTextMessage.contextInfo = {
708
+ ...messageContent.extendedTextMessage.contextInfo,
709
+ statusAudienceMetadata: { audienceType }
710
+ }
711
+ } else if (
712
+ messageContent.imageMessage || messageContent.videoMessage ||
713
+ messageContent.audioMessage || messageContent.documentMessage
714
+ ) {
715
+ const key = Object.keys(messageContent)[0]
716
+ if (!messageContent[key].contextInfo) messageContent[key].contextInfo = {}
717
+ messageContent[key].contextInfo.statusAudienceMetadata = { audienceType }
718
+ }
719
+ m = { groupStatusMessageV2: { message: messageContent } }
720
+ } else if ('orderStatus' in message) {
721
+ const os = message.orderStatus
722
+ if (!os.image) throw new Error('orderStatus requires image')
723
+ let imageInput = os.image
724
+ if (typeof imageInput === 'string' && !imageInput.startsWith('http://') && !imageInput.startsWith('https://')) {
725
+ imageInput = await fs_1.promises.readFile(imageInput)
726
+ }
727
+ const media = await prepareWAMessageMedia({ image: imageInput }, { upload: options.upload, ...options })
728
+ m = {
729
+ viewOnceMessage: {
730
+ message: {
731
+ messageContextInfo: {
732
+ deviceListMetadata: {},
733
+ deviceListMetadataVersion: 2
734
+ },
735
+ interactiveMessage: WAProto_1.proto.Message.InteractiveMessage.create({
736
+ header: {
737
+ title: os.title || 'Order Status',
738
+ hasMediaAttachment: true,
739
+ ...media
740
+ },
741
+ body: { text: os.text || 'Silakan cek status pesanan Anda.' },
742
+ footer: { text: os.footer || 'Powered by PontaCT' },
743
+ nativeFlowMessage: {
744
+ buttons: [{
745
+ name: 'order_status',
746
+ buttonParamsJson: JSON.stringify({
747
+ reference_id: os.referenceId || 'PT-001',
748
+ order: {
749
+ status: os.status || 'PROCESSING',
750
+ subtotal: {
751
+ value: os.subtotalValue || 0,
752
+ offset: os.subtotalOffset || 100
753
+ },
754
+ tax: {
755
+ value: os.taxValue || 0,
756
+ offset: os.taxOffset || 100
757
+ },
758
+ currency: os.currency || 'IDR'
759
+ }
760
+ })
761
+ }]
762
+ }
763
+ })
764
+ }
765
+ }
766
+ }
623
767
  } else if ('buttonReply' in message) {
624
768
  switch (message.type) {
625
769
  case 'list':