@badzz88/baileys 8.5.4 → 8.5.5

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.
Files changed (107) hide show
  1. package/README.md +1 -1
  2. package/WAProto/WAProto.proto +75 -1256
  3. package/WAProto/index.d.ts +19729 -36122
  4. package/WAProto/index.js +132193 -178538
  5. package/engine-requirements.js +15 -7
  6. package/lib/Defaults/index.js +14 -22
  7. package/lib/Signal/Group/group_cipher.js +5 -5
  8. package/lib/Signal/Group/keyhelper.js +4 -4
  9. package/lib/Signal/Group/sender-chain-key.js +2 -2
  10. package/lib/Signal/Group/sender-key-distribution-message.js +1 -1
  11. package/lib/Signal/Group/sender-key-message.js +3 -3
  12. package/lib/Signal/Group/sender-key-record.js +1 -1
  13. package/lib/Signal/Group/sender-message-key.js +5 -5
  14. package/lib/Signal/libsignal.js +28 -39
  15. package/lib/Signal/lid-mapping.js +8 -28
  16. package/lib/Socket/aigroups.js +5 -5
  17. package/lib/Socket/business.js +11 -3
  18. package/lib/Socket/chats.js +319 -668
  19. package/lib/Socket/communities.js +89 -85
  20. package/lib/Socket/graphql.js +228 -435
  21. package/lib/Socket/groups.js +211 -132
  22. package/lib/Socket/index.js +7 -9
  23. package/lib/Socket/interactive-handler.js +60 -117
  24. package/lib/Socket/interop.js +175 -56
  25. package/lib/Socket/luxu.js +387 -0
  26. package/lib/Socket/managed-account.js +48 -79
  27. package/lib/Socket/messages-recv.js +340 -898
  28. package/lib/Socket/messages-send.js +160 -462
  29. package/lib/Socket/mex.js +53 -40
  30. package/lib/Socket/newsletter.js +295 -291
  31. package/lib/Socket/privacy.js +265 -77
  32. package/lib/Socket/registration.js +157 -164
  33. package/lib/Socket/socket.js +199 -181
  34. package/lib/Socket/text-router.js +83 -0
  35. package/lib/Socket/username.js +227 -144
  36. package/lib/Store/keyed-db.js +108 -0
  37. package/lib/Store/make-cache-manager-store.js +2 -2
  38. package/lib/Store/make-in-memory-store.js +141 -25
  39. package/lib/Types/Auth.js +3 -3
  40. package/lib/Types/Label.js +1 -1
  41. package/lib/Types/LabelAssociation.js +1 -1
  42. package/lib/Types/Mex.js +112 -0
  43. package/lib/Types/Newsletter.js +65 -96
  44. package/lib/Types/State.js +4 -4
  45. package/lib/Types/index.js +1 -1
  46. package/lib/Utils/auth-utils.js +57 -52
  47. package/lib/Utils/browser-utils.js +1 -1
  48. package/lib/Utils/business.js +6 -2
  49. package/lib/Utils/chat-utils.js +38 -102
  50. package/lib/Utils/command-loader.d.ts +31 -0
  51. package/lib/Utils/command-loader.js +100 -0
  52. package/lib/Utils/companion-reg-client-utils.js +42 -0
  53. package/lib/Utils/crypto.js +84 -54
  54. package/lib/Utils/curve25519-js.js +275 -0
  55. package/lib/Utils/decode-wa-message.js +83 -316
  56. package/lib/Utils/event-buffer.js +61 -37
  57. package/lib/Utils/generics.js +103 -157
  58. package/lib/Utils/group-history.js +12 -3
  59. package/lib/Utils/history.js +15 -15
  60. package/lib/Utils/index.js +5 -1
  61. package/lib/Utils/link-preview.js +8 -3
  62. package/lib/Utils/lt-hash.js +35 -2
  63. package/lib/Utils/make-mutex.js +1 -1
  64. package/lib/Utils/message-retry-manager.js +92 -27
  65. package/lib/Utils/messages-media.js +130 -159
  66. package/lib/Utils/messages.js +566 -144
  67. package/lib/Utils/meta-ai-msmsg.js +202 -62
  68. package/lib/Utils/native-bridge.js +82 -0
  69. package/lib/Utils/noise-handler.js +21 -19
  70. package/lib/Utils/offline-node-processor.js +8 -3
  71. package/lib/Utils/pre-key-manager.js +23 -13
  72. package/lib/Utils/process-message.js +106 -197
  73. package/lib/Utils/reporting-utils.js +2 -2
  74. package/lib/Utils/session-pool.d.ts +16 -0
  75. package/lib/Utils/session-pool.js +94 -0
  76. package/lib/Utils/signal.js +63 -28
  77. package/lib/Utils/stanza-ack.js +12 -39
  78. package/lib/Utils/sticker.d.ts +17 -0
  79. package/lib/Utils/sticker.js +145 -0
  80. package/lib/Utils/sync-action-utils.js +7 -4
  81. package/lib/Utils/tc-token-utils.js +25 -20
  82. package/lib/Utils/use-multi-file-auth-state.js +14 -30
  83. package/lib/Utils/validate-connection.js +19 -59
  84. package/lib/Utils/view-once-cache.d.ts +9 -0
  85. package/lib/Utils/view-once-cache.js +79 -0
  86. package/lib/WABinary/decode.js +16 -50
  87. package/lib/WABinary/encode.js +224 -54
  88. package/lib/WABinary/generic-utils.js +97 -7
  89. package/lib/WABinary/jid-utils.js +14 -27
  90. package/lib/WAM/encode.js +7 -7
  91. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +1 -58
  92. package/lib/WAUSync/Protocols/USyncContactProtocol.js +1 -7
  93. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +5 -13
  94. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +4 -1
  95. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +7 -20
  96. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +1 -1
  97. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +2 -2
  98. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +2 -7
  99. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +1 -1
  100. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +1 -2
  101. package/lib/WAUSync/Protocols/index.js +0 -1
  102. package/lib/WAUSync/USyncQuery.js +6 -77
  103. package/lib/WAUSync/USyncUser.js +8 -16
  104. package/lib/WAUSync/index.js +0 -1
  105. package/lib/antiban.js +854 -608
  106. package/lib/index.js +1 -1
  107. package/package.json +20 -16
@@ -4,6 +4,7 @@ exports.getNextPreKeysNode =
4
4
  exports.getNextPreKeys =
5
5
  exports.extractDeviceJids =
6
6
  exports.parseAndInjectE2ESessions =
7
+ exports.extractE2ESessionFromRetryReceipt =
7
8
  exports.xmppPreKey =
8
9
  exports.xmppSignedPreKey =
9
10
  exports.generateOrGetPreKeys =
@@ -53,11 +54,9 @@ const generateOrGetPreKeys = (creds, range) => {
53
54
  }
54
55
  }
55
56
  exports.generateOrGetPreKeys = generateOrGetPreKeys
56
-
57
-
58
- const xmppSignedPreKey = (key, cipherSuite) => ({
57
+ const xmppSignedPreKey = key => ({
59
58
  tag: 'skey',
60
- attrs: cipherSuite !== undefined ? { key_cipher_suite: cipherSuite } : {},
59
+ attrs: {},
61
60
  content: [
62
61
  { tag: 'id', attrs: {}, content: (0, generics_1.encodeBigEndian)(key.keyId, 3) },
63
62
  { tag: 'value', attrs: {}, content: key.keyPair.public },
@@ -65,36 +64,72 @@ const xmppSignedPreKey = (key, cipherSuite) => ({
65
64
  ]
66
65
  })
67
66
  exports.xmppSignedPreKey = xmppSignedPreKey
68
- const xmppPreKey = (pair, id, cipherSuite) => ({
67
+ const xmppPreKey = (pair, id) => ({
69
68
  tag: 'key',
70
- attrs: cipherSuite !== undefined ? { key_cipher_suite: cipherSuite } : {},
69
+ attrs: {},
71
70
  content: [
72
71
  { tag: 'id', attrs: {}, content: (0, generics_1.encodeBigEndian)(id, 3) },
73
72
  { tag: 'value', attrs: {}, content: pair.public }
74
73
  ]
75
74
  })
76
75
  exports.xmppPreKey = xmppPreKey
77
- const parseAndInjectE2ESessions = async (node, repository) => {
78
- const extractKey = key => {
79
- if (!key) return undefined
80
- const cipherSuite = key.attrs?.key_cipher_suite
81
- return {
82
- keyId: (0, WABinary_1.getBinaryNodeChildUInt)(key, 'id', 3),
83
- publicKey: (0, crypto_1.generateSignalPubKey)((0, WABinary_1.getBinaryNodeChildBuffer)(key, 'value')),
84
- signature: (0, WABinary_1.getBinaryNodeChildBuffer)(key, 'signature'),
85
-
86
- ...(cipherSuite !== undefined ? { cipherSuite } : {})
76
+ const isValidUInt = n => typeof n === 'number' && Number.isInteger(n)
77
+ const extractE2ESessionFromRetryReceipt = receipt => {
78
+ const keysNode = (0, WABinary_1.getBinaryNodeChild)(receipt, 'keys')
79
+ if (!keysNode) return null
80
+ const typeBuf = (0, WABinary_1.getBinaryNodeChildBuffer)(keysNode, 'type')
81
+ if (!typeBuf || typeBuf.length !== 1 || typeBuf[0] !== Defaults_1.KEY_BUNDLE_TYPE[0]) return null
82
+ const identity = (0, WABinary_1.getBinaryNodeChildBuffer)(keysNode, 'identity')
83
+ const skey = (0, WABinary_1.getBinaryNodeChild)(keysNode, 'skey')
84
+ if (!identity || identity.length !== 32 || !skey) return null
85
+ const registrationId = (0, WABinary_1.getBinaryNodeChildUInt)(receipt, 'registration', 4)
86
+ if (!isValidUInt(registrationId)) return null
87
+ const signedPubKey = (0, WABinary_1.getBinaryNodeChildBuffer)(skey, 'value')
88
+ const signedSig = (0, WABinary_1.getBinaryNodeChildBuffer)(skey, 'signature')
89
+ const signedKeyId = (0, WABinary_1.getBinaryNodeChildUInt)(skey, 'id', 3)
90
+ if (!signedPubKey || signedPubKey.length !== 32 || !signedSig || !isValidUInt(signedKeyId)) {
91
+ return null
92
+ }
93
+ const preKeyNode = (0, WABinary_1.getBinaryNodeChild)(keysNode, 'key')
94
+ let preKey
95
+ if (preKeyNode) {
96
+ const preKeyPub = (0, WABinary_1.getBinaryNodeChildBuffer)(preKeyNode, 'value')
97
+ const preKeyId = (0, WABinary_1.getBinaryNodeChildUInt)(preKeyNode, 'id', 3)
98
+ if (!preKeyPub || preKeyPub.length !== 32 || !isValidUInt(preKeyId)) {
99
+ return null
87
100
  }
101
+ preKey = { keyId: preKeyId, publicKey: (0, crypto_1.generateSignalPubKey)(preKeyPub) }
102
+ }
103
+ return {
104
+ registrationId,
105
+ identityKey: (0, crypto_1.generateSignalPubKey)(identity),
106
+ signedPreKey: {
107
+ keyId: signedKeyId,
108
+ publicKey: (0, crypto_1.generateSignalPubKey)(signedPubKey),
109
+ signature: signedSig
110
+ },
111
+ preKey
88
112
  }
113
+ }
114
+ exports.extractE2ESessionFromRetryReceipt = extractE2ESessionFromRetryReceipt
115
+ const parseAndInjectE2ESessions = async (node, repository) => {
116
+ const extractKey = key =>
117
+ key
118
+ ? {
119
+ keyId: (0, WABinary_1.getBinaryNodeChildUInt)(key, 'id', 3),
120
+ publicKey: (0, crypto_1.generateSignalPubKey)((0, WABinary_1.getBinaryNodeChildBuffer)(key, 'value')),
121
+ signature: (0, WABinary_1.getBinaryNodeChildBuffer)(key, 'signature')
122
+ }
123
+ : undefined
89
124
  const nodes = (0, WABinary_1.getBinaryNodeChildren)((0, WABinary_1.getBinaryNodeChild)(node, 'list'), 'user')
90
125
  for (const node of nodes) {
91
126
  ;(0, WABinary_1.assertNodeErrorFree)(node)
92
127
  }
93
-
94
-
95
-
96
-
97
-
128
+ // Most of the work in repository.injectE2ESession is CPU intensive, not IO
129
+ // So Promise.all doesn't really help here,
130
+ // but blocks even loop if we're using it inside keys.transaction, and it makes it "sync" actually
131
+ // This way we chunk it in smaller parts and between those parts we can yield to the event loop
132
+ // It's rare case when you need to E2E sessions for so many users, but it's possible
98
133
  const chunkSize = 100
99
134
  const chunks = chunk(nodes, chunkSize)
100
135
  for (const nodesChunk of chunks) {
@@ -129,9 +164,9 @@ const extractDeviceJids = (result, myJid, myLid, excludeZeroDevices) => {
129
164
  if (!Array.isArray(deviceList)) continue
130
165
  for (const { id: device, keyIndex, isHosted } of deviceList) {
131
166
  if (
132
- (!excludeZeroDevices || device !== 0) &&
133
- ((myUser !== user && myLid !== user) || myDevice !== device) &&
134
- (device === 0 || !!keyIndex)
167
+ (!excludeZeroDevices || device !== 0) && // if zero devices are not-excluded, or device is non zero
168
+ ((myUser !== user && myLid !== user) || myDevice !== device) && // either different user or if me user, not this device
169
+ (device === 0 || !!keyIndex) // ensure that "key-index" is specified for "non-zero" devices, produces a bad req otherwise
135
170
  ) {
136
171
  if (isHosted) {
137
172
  domainType =
@@ -151,7 +186,10 @@ const extractDeviceJids = (result, myJid, myLid, excludeZeroDevices) => {
151
186
  return extracted
152
187
  }
153
188
  exports.extractDeviceJids = extractDeviceJids
154
-
189
+ /**
190
+ * get the next N keys for upload or processing
191
+ * @param count number of pre-keys to get or generate
192
+ */
155
193
  const getNextPreKeys = async ({ creds, keys }, count) => {
156
194
  const { newPreKeys, lastPreKeyId, preKeysRange } = (0, exports.generateOrGetPreKeys)(creds, count)
157
195
  const update = {
@@ -177,9 +215,6 @@ const getNextPreKeysNode = async (state, count) => {
177
215
  { tag: 'registration', attrs: {}, content: (0, generics_1.encodeBigEndian)(creds.registrationId) },
178
216
  { tag: 'type', attrs: {}, content: Defaults_1.KEY_BUNDLE_TYPE },
179
217
  { tag: 'identity', attrs: {}, content: creds.signedIdentityKey.public },
180
-
181
-
182
-
183
218
  { tag: 'list', attrs: {}, content: Object.keys(preKeys).map(k => (0, exports.xmppPreKey)(preKeys[+k], +k)) },
184
219
  (0, exports.xmppSignedPreKey)(creds.signedPreKey)
185
220
  ]
@@ -1,50 +1,24 @@
1
1
  'use strict'
2
2
  Object.defineProperty(exports, '__esModule', { value: true })
3
3
  exports.buildAckStanza = buildAckStanza
4
-
5
-
6
- const CALL_STANZA_TAGS = new Set([
7
- 'offer',
8
- 'offer_notice',
9
- 'terminate',
10
- 'accept',
11
- 'reject',
12
- 'preaccept',
13
- 'transport',
14
- 'video',
15
- 'duration',
16
- 'mute_v2',
17
- 'lobby',
18
- 'heartbeat',
19
- 'relaylatency',
20
- 'link_query',
21
- 'group_update',
22
- 'accept_ack',
23
- 'enc-rekey',
24
- 'enc_rekey',
25
- 'peer_state',
26
- 'group_info',
27
- 'video_state',
28
- 'video_state_ack',
29
- 'flow_control',
30
- 'waiting_room_request'
31
- ])
32
-
4
+ /**
5
+ * Builds an ACK stanza for a received node.
6
+ * Pure function -- no I/O, no side effects.
7
+ *
8
+ * Mirrors WhatsApp Web's ACK construction:
9
+ * - WAWebHandleMsgSendAck.sendAck / sendNack
10
+ * - WAWebCreateNackFromStanza.createNackFromStanza
11
+ */
33
12
  function buildAckStanza(node, errorCode, meId) {
34
13
  const { tag, attrs } = node
35
- const isCallStanza = CALL_STANZA_TAGS.has(tag)
36
14
  const stanza = {
37
15
  tag: 'ack',
38
16
  attrs: {
39
17
  id: attrs.id,
40
18
  to: attrs.from,
41
- class: isCallStanza ? 'call' : tag
19
+ class: tag
42
20
  }
43
21
  }
44
-
45
- if (isCallStanza) {
46
- stanza.attrs.type = tag
47
- }
48
22
  if (errorCode) {
49
23
  stanza.attrs.error = errorCode.toString()
50
24
  }
@@ -54,12 +28,11 @@ function buildAckStanza(node, errorCode, meId) {
54
28
  if (attrs.recipient) {
55
29
  stanza.attrs.recipient = attrs.recipient
56
30
  }
57
-
58
-
59
- if (attrs.type && !isCallStanza) {
31
+ // WA Web always includes type when present: `n.type || DROP_ATTR`
32
+ if (attrs.type) {
60
33
  stanza.attrs.type = attrs.type
61
34
  }
62
-
35
+ // WA Web WAWebHandleMsgSendAck.sendAck/sendNack always include `from` for message-class ACKs
63
36
  if (tag === 'message' && meId) {
64
37
  stanza.attrs.from = meId
65
38
  }
@@ -0,0 +1,17 @@
1
+ export interface StickerMetaOptions {
2
+ packName?: string
3
+ packPublisher?: string
4
+ categories?: string[]
5
+ }
6
+
7
+ export function addExifToWebp(webpBuffer: Buffer, options?: StickerMetaOptions): Promise<Buffer>
8
+
9
+ export function imageToWebpSticker(
10
+ imageBuffer: Buffer,
11
+ options?: StickerMetaOptions & { quality?: number }
12
+ ): Promise<Buffer>
13
+
14
+ export function videoToWebpSticker(
15
+ videoBuffer: Buffer,
16
+ options?: StickerMetaOptions & { fps?: number; seconds?: number }
17
+ ): Promise<Buffer>
@@ -0,0 +1,145 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+ exports.imageToWebpSticker = exports.videoToWebpSticker = exports.addExifToWebp = void 0
4
+ const os = require('os')
5
+ const path = require('path')
6
+ const fs = require('fs')
7
+ const crypto = require('crypto')
8
+ const { exec } = require('child_process')
9
+
10
+ /**
11
+ * Builds and writes the EXIF metadata WhatsApp expects on sticker webp files
12
+ * (pack name / author, shown when a user long-presses a sticker).
13
+ * Converts the file to the "extended" (VP8X) WebP container if it isn't already,
14
+ * since that's the container format that actually carries an EXIF chunk.
15
+ * Returns the new buffer with EXIF attached.
16
+ */
17
+ const addExifToWebp = async (webpBuffer, { packName = '', packPublisher = '', categories = [] } = {}) => {
18
+ const json = {
19
+ 'sticker-pack-id': crypto.randomBytes(16).toString('hex'),
20
+ 'sticker-pack-name': packName,
21
+ 'sticker-pack-publisher': packPublisher,
22
+ emojis: categories.length ? categories : ['😀']
23
+ }
24
+ const jsonBuffer = Buffer.from(JSON.stringify(json), 'utf-8')
25
+ // TIFF header + IFD entry pointing at a single custom tag (0x5741 "AW") holding
26
+ // the JSON payload -- this is the exact structure WhatsApp's client parses.
27
+ const exifAttr = Buffer.from([
28
+ 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16,
29
+ 0x00, 0x00, 0x00
30
+ ])
31
+ exifAttr.writeUIntLE(jsonBuffer.length, 14, 4)
32
+ const exifPayload = Buffer.concat([exifAttr, jsonBuffer])
33
+ const pad = buf => (buf.length % 2 === 1 ? Buffer.concat([buf, Buffer.from([0x00])]) : buf)
34
+ const makeChunk = (tag, data) => {
35
+ const sizeField = Buffer.alloc(4)
36
+ sizeField.writeUInt32LE(data.length, 0)
37
+ return Buffer.concat([Buffer.from(tag, 'ascii'), sizeField, pad(data)])
38
+ }
39
+ const exifChunk = makeChunk('EXIF', exifPayload)
40
+ if (webpBuffer.slice(0, 4).toString('ascii') !== 'RIFF' || webpBuffer.slice(8, 12).toString('ascii') !== 'WEBP') {
41
+ throw new Error('addExifToWebp expects a valid WebP buffer')
42
+ }
43
+ let offset = 12
44
+ let vp8xChunk = null
45
+ let vp8xOffset = -1
46
+ const otherChunks = []
47
+ while (offset < webpBuffer.length) {
48
+ const tag = webpBuffer.slice(offset, offset + 4).toString('ascii')
49
+ const size = webpBuffer.readUInt32LE(offset + 4)
50
+ const dataStart = offset + 8
51
+ const dataEnd = dataStart + size
52
+ const chunkTotal = 8 + size + (size % 2)
53
+ if (tag === 'VP8X') {
54
+ vp8xChunk = webpBuffer.slice(offset, offset + chunkTotal)
55
+ vp8xOffset = offset
56
+ } else if (tag !== 'EXIF') {
57
+ // drop any pre-existing EXIF chunk, keep everything else (VP8/VP8L/ANIM/ANMF/ICCP/...)
58
+ otherChunks.push(webpBuffer.slice(offset, offset + chunkTotal))
59
+ }
60
+ offset += chunkTotal
61
+ }
62
+ let flags
63
+ let canvasWidth
64
+ let canvasHeight
65
+ if (vp8xChunk) {
66
+ flags = vp8xChunk.readUInt8(8)
67
+ canvasWidth = vp8xChunk.readUIntLE(12, 3) + 1
68
+ canvasHeight = vp8xChunk.readUIntLE(15, 3) + 1
69
+ } else {
70
+ flags = 0
71
+ let sharp
72
+ try {
73
+ sharp = require('sharp')
74
+ } catch (error) {
75
+ throw new Error('addExifToWebp needs the optional "sharp" dependency to read image dimensions')
76
+ }
77
+ const meta = await sharp(webpBuffer).metadata()
78
+ canvasWidth = meta.width
79
+ canvasHeight = meta.height
80
+ }
81
+ flags |= 0x08 // set the "has EXIF metadata" bit
82
+ const vp8xData = Buffer.alloc(10)
83
+ vp8xData.writeUInt8(flags, 0)
84
+ vp8xData.writeUIntLE(canvasWidth - 1, 4, 3)
85
+ vp8xData.writeUIntLE(canvasHeight - 1, 7, 3)
86
+ const newVp8xChunk = makeChunk('VP8X', vp8xData)
87
+ const body = Buffer.concat([newVp8xChunk, ...otherChunks, exifChunk])
88
+ const fileSize = Buffer.alloc(4)
89
+ fileSize.writeUInt32LE(4 + body.length, 0) // 4 = 'WEBP'
90
+ return Buffer.concat([Buffer.from('RIFF', 'ascii'), fileSize, Buffer.from('WEBP', 'ascii'), body])
91
+ }
92
+ exports.addExifToWebp = addExifToWebp
93
+
94
+ /**
95
+ * Converts a static image buffer to a WhatsApp sticker-ready webp buffer using `sharp`.
96
+ * @param {Buffer} imageBuffer
97
+ * @param {{ packName?: string, packPublisher?: string, categories?: string[], quality?: number }} [options]
98
+ * @returns {Promise<Buffer>}
99
+ */
100
+ const imageToWebpSticker = async (imageBuffer, options = {}) => {
101
+ let sharp
102
+ try {
103
+ sharp = require('sharp')
104
+ } catch (error) {
105
+ throw new Error('imageToWebpSticker requires the optional "sharp" dependency to be installed')
106
+ }
107
+ const webp = await sharp(imageBuffer)
108
+ .resize(512, 512, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
109
+ .webp({ quality: options.quality ?? 80 })
110
+ .toBuffer()
111
+ return addExifToWebp(webp, options)
112
+ }
113
+ exports.imageToWebpSticker = imageToWebpSticker
114
+
115
+ /**
116
+ * Converts a video buffer to an animated WhatsApp sticker-ready webp buffer.
117
+ * Requires the `ffmpeg` binary to be available on PATH.
118
+ * @param {Buffer} videoBuffer
119
+ * @param {{ packName?: string, packPublisher?: string, categories?: string[], fps?: number, seconds?: number }} [options]
120
+ * @returns {Promise<Buffer>}
121
+ */
122
+ const videoToWebpSticker = async (videoBuffer, options = {}) => {
123
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'xazbails-sticker-'))
124
+ const inputPath = path.join(tmpDir, 'input.mp4')
125
+ const outputPath = path.join(tmpDir, 'output.webp')
126
+ fs.writeFileSync(inputPath, videoBuffer)
127
+ const fps = options.fps ?? 10
128
+ const seconds = options.seconds ?? 5
129
+ const cmd =
130
+ `ffmpeg -y -i ${inputPath} -t ${seconds} -vf ` +
131
+ `"fps=${fps},scale=512:512:force_original_aspect_ratio=decrease,` +
132
+ `pad=512:512:(ow-iw)/2:(oh-ih)/2:color=0x00000000,split[a][b];` +
133
+ `[a]palettegen=reserve_transparent=1[p];[b][p]paletteuse" ` +
134
+ `-loop 0 -preset default -an -vsync 0 ${outputPath}`
135
+ try {
136
+ await new Promise((resolve, reject) => {
137
+ exec(cmd, err => (err ? reject(err) : resolve()))
138
+ })
139
+ const webp = fs.readFileSync(outputPath)
140
+ return await addExifToWebp(webp, options)
141
+ } finally {
142
+ fs.rmSync(tmpDir, { recursive: true, force: true })
143
+ }
144
+ }
145
+ exports.videoToWebpSticker = videoToWebpSticker
@@ -2,7 +2,10 @@
2
2
  Object.defineProperty(exports, '__esModule', { value: true })
3
3
  exports.emitSyncActionResults = exports.processContactAction = void 0
4
4
  const WABinary_1 = require('../WABinary')
5
-
5
+ /**
6
+ * Process contactAction and return events to emit.
7
+ * Pure function - no side effects.
8
+ */
6
9
  const processContactAction = (action, id, logger) => {
7
10
  const results = []
8
11
  if (!id) {
@@ -14,9 +17,9 @@ const processContactAction = (action, id, logger) => {
14
17
  }
15
18
  const lidJid = action.lidJid
16
19
  const idIsPn = (0, WABinary_1.isPnUser)(id)
17
-
20
+ // PN is in index[1], not in contactAction.pnJid which is usually null
18
21
  const phoneNumber = idIsPn ? id : action.pnJid || undefined
19
-
22
+ // Always emit contacts.upsert
20
23
  results.push({
21
24
  event: 'contacts.upsert',
22
25
  data: [
@@ -29,7 +32,7 @@ const processContactAction = (action, id, logger) => {
29
32
  }
30
33
  ]
31
34
  })
32
-
35
+ // Emit lid-mapping.update if we have valid LID-PN pair
33
36
  if (lidJid && (0, WABinary_1.isLidUser)(lidJid) && idIsPn) {
34
37
  results.push({
35
38
  event: 'lid-mapping.update',
@@ -10,16 +10,21 @@ exports.resolveIssuanceJid = resolveIssuanceJid
10
10
  exports.buildTcTokenFromJid = buildTcTokenFromJid
11
11
  exports.storeTcTokensFromIqResult = storeTcTokensFromIqResult
12
12
  const WABinary_1 = require('../WABinary')
13
-
14
-
13
+ // Same phone-number pattern as WABinary's isJidBot, applied against the user
14
+ // part so the check is invariant to @c.us ↔ @s.whatsapp.net normalization.
15
15
  const BOT_PHONE_REGEX = /^1313555\d{4}$|^131655500\d{2}$/
16
-
16
+ /**
17
+ * Mirrors WA Web's `Wid.isRegularUser()` (user ∧ ¬PSA ∧ ¬Bot). Used to gate tctoken
18
+ * storage against malformed notifications — WA Web filters server-side but we
19
+ * defend here for parity with `WAWebSetTcTokenChatAction.handleIncomingTcToken`.
20
+ * Works for both pre- and post-normalized JIDs (`@c.us` vs `@s.whatsapp.net`).
21
+ */
17
22
  function isRegularUser(jid) {
18
23
  if (!jid) return false
19
24
  const user = jid.split('@')[0] ?? ''
20
- if (user === '0') return false
21
- if (BOT_PHONE_REGEX.test(user)) return false
22
- if ((0, WABinary_1.isJidMetaAI)(jid)) return false
25
+ if (user === '0') return false // PSA
26
+ if (BOT_PHONE_REGEX.test(user)) return false // Bot by phone pattern
27
+ if ((0, WABinary_1.isJidMetaAI)(jid)) return false // MetaAI (@bot server)
23
28
  return !!(
24
29
  (0, WABinary_1.isPnUser)(jid) ||
25
30
  (0, WABinary_1.isLidUser)(jid) ||
@@ -28,11 +33,11 @@ function isRegularUser(jid) {
28
33
  jid.endsWith('@c.us')
29
34
  )
30
35
  }
31
- const TC_TOKEN_BUCKET_DURATION = 604800
32
- const TC_TOKEN_NUM_BUCKETS = 4
33
-
36
+ const TC_TOKEN_BUCKET_DURATION = 604800 // 7 days
37
+ const TC_TOKEN_NUM_BUCKETS = 4 // ~28-day rolling window
38
+ /** Sentinel key under `tctoken` store holding a JSON array of tracked storage JIDs for cross-session pruning. */
34
39
  exports.TC_TOKEN_INDEX_KEY = '__index'
35
-
40
+ /** Read the persisted tctoken JID index and return its entries (never contains the sentinel key itself). */
36
41
  async function readTcTokenIndex(keys) {
37
42
  const data = await keys.get('tctoken', [exports.TC_TOKEN_INDEX_KEY])
38
43
  const entry = data[exports.TC_TOKEN_INDEX_KEY]
@@ -45,7 +50,7 @@ async function readTcTokenIndex(keys) {
45
50
  return []
46
51
  }
47
52
  }
48
-
53
+ /** Build a SignalDataSet fragment that writes the merged index (persisted ∪ added) under the sentinel key. */
49
54
  async function buildMergedTcTokenIndexWrite(keys, addedJids) {
50
55
  const persisted = await readTcTokenIndex(keys)
51
56
  const merged = new Set(persisted)
@@ -56,10 +61,10 @@ async function buildMergedTcTokenIndexWrite(keys, addedJids) {
56
61
  [exports.TC_TOKEN_INDEX_KEY]: { token: Buffer.from(JSON.stringify([...merged])) }
57
62
  }
58
63
  }
59
-
64
+ // WA Web has separate sender/receiver AB props for these but they're identical today
60
65
  function isTcTokenExpired(timestamp) {
61
66
  if (timestamp === null || timestamp === undefined) return true
62
- const ts = typeof timestamp === 'string' ? parseInt(timestamp, 10) : timestamp
67
+ const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp
63
68
  if (isNaN(ts)) return true
64
69
  const now = Math.floor(Date.now() / 1000)
65
70
  const currentBucket = Math.floor(now / TC_TOKEN_BUCKET_DURATION)
@@ -74,13 +79,13 @@ function shouldSendNewTcToken(senderTimestamp) {
74
79
  const senderBucket = Math.floor(senderTimestamp / TC_TOKEN_BUCKET_DURATION)
75
80
  return currentBucket > senderBucket
76
81
  }
77
-
82
+ /** Resolve JID to LID for tctoken storage (WA Web stores under LID) */
78
83
  async function resolveTcTokenJid(jid, getLIDForPN) {
79
84
  if ((0, WABinary_1.isLidUser)(jid)) return jid
80
85
  const lid = await getLIDForPN(jid)
81
86
  return lid ?? jid
82
87
  }
83
-
88
+ /** Resolve target JID for issuing privacy token based on AB prop 14303 */
84
89
  async function resolveIssuanceJid(jid, issueToLid, getLIDForPN, getPNForLID) {
85
90
  if (issueToLid) {
86
91
  if ((0, WABinary_1.isLidUser)(jid)) return jid
@@ -102,9 +107,9 @@ async function buildTcTokenFromJid({ authState, jid, baseContent = [], getLIDFor
102
107
  const tcTokenBuffer = entry?.token
103
108
  if (!tcTokenBuffer?.length || isTcTokenExpired(entry?.timestamp)) {
104
109
  if (tcTokenBuffer) {
105
-
106
-
107
-
110
+ // Preserve senderTimestamp so shouldSendNewTcToken() keeps its dedupe state
111
+ // after we drop the unusable peer token. Only wipe the record entirely when
112
+ // there's nothing worth keeping.
108
113
  const cleared =
109
114
  entry?.senderTimestamp !== undefined
110
115
  ? { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp }
@@ -131,7 +136,7 @@ async function storeTcTokensFromIqResult({ result, fallbackJid, keys, getLIDForP
131
136
  if (tokenNode.attrs.type !== 'trusted_contact' || !(tokenNode.content instanceof Uint8Array)) {
132
137
  continue
133
138
  }
134
-
139
+ // In notifications tokenNode.attrs.jid is your own device JID, not the sender's
135
140
  const rawJid = (0, WABinary_1.jidNormalizedUser)(fallbackJid || tokenNode.attrs.jid)
136
141
  if (!isRegularUser(rawJid)) continue
137
142
  const storageJid = await resolveTcTokenJid(rawJid, getLIDForPN)
@@ -139,7 +144,7 @@ async function storeTcTokensFromIqResult({ result, fallbackJid, keys, getLIDForP
139
144
  const existingEntry = existingTcData[storageJid]
140
145
  const existingTs = existingEntry?.timestamp ? Number(existingEntry.timestamp) : 0
141
146
  const incomingTs = tokenNode.attrs.t ? Number(tokenNode.attrs.t) : 0
142
-
147
+ // timestamp-less tokens would be immediately expired
143
148
  if (!incomingTs) continue
144
149
  if (existingTs > 0 && existingTs > incomingTs) continue
145
150
  await keys.set({
@@ -7,12 +7,12 @@ const path_1 = require('path')
7
7
  const index_js_1 = require('../../WAProto/index.js')
8
8
  const auth_utils_1 = require('./auth-utils')
9
9
  const generics_1 = require('./generics')
10
-
11
-
12
-
13
-
10
+ // We need to lock files due to the fact that we are using async functions to read and write files
11
+ // https://github.com/WhiskeySockets/Baileys/issues/794
12
+ // https://github.com/nodejs/node/issues/26338
13
+ // Use a Map to store mutexes for each file path
14
14
  const fileLocks = new Map()
15
-
15
+ // Get or create a mutex for a specific file path
16
16
  const getFileLock = path => {
17
17
  let mutex = fileLocks.get(path)
18
18
  if (!mutex) {
@@ -21,37 +21,21 @@ const getFileLock = path => {
21
21
  }
22
22
  return mutex
23
23
  }
24
-
24
+ /**
25
+ * stores the full authentication state in a single folder.
26
+ * Far more efficient than singlefileauthstate
27
+ *
28
+ * Again, I wouldn't endorse this for any production level use other than perhaps a bot.
29
+ * Would recommend writing an auth state for use with a proper SQL or No-SQL DB
30
+ * */
25
31
  const useMultiFileAuthState = async folder => {
26
-
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
33
  const writeData = async (data, file) => {
28
34
  const filePath = (0, path_1.join)(folder, fixFileName(file))
29
35
  const mutex = getFileLock(filePath)
30
36
  return mutex.acquire().then(async release => {
31
- const tmpPath = `${filePath}.tmp`
32
-
33
-
34
-
35
-
36
-
37
- const existingStat = await (0, promises_1.stat)(filePath).catch(() => undefined)
38
37
  try {
39
- await (0, promises_1.writeFile)(
40
- tmpPath,
41
- JSON.stringify(data, generics_1.BufferJSON.replacer),
42
- existingStat ? { mode: existingStat.mode & 0o777 } : undefined
43
- )
44
- try {
45
- await (0, promises_1.rename)(tmpPath, filePath)
46
- } catch {
47
-
48
-
49
- await (0, promises_1.writeFile)(
50
- filePath,
51
- JSON.stringify(data, generics_1.BufferJSON.replacer),
52
- existingStat ? { mode: existingStat.mode & 0o777 } : undefined
53
- )
54
- }
38
+ await (0, promises_1.writeFile)(filePath, JSON.stringify(data, generics_1.BufferJSON.replacer))
55
39
  } finally {
56
40
  release()
57
41
  }