@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.
- package/README.md +1 -1
- package/WAProto/WAProto.proto +75 -1256
- package/WAProto/index.d.ts +19729 -36122
- package/WAProto/index.js +132193 -178538
- package/engine-requirements.js +15 -7
- package/lib/Defaults/index.js +14 -22
- package/lib/Signal/Group/group_cipher.js +5 -5
- package/lib/Signal/Group/keyhelper.js +4 -4
- package/lib/Signal/Group/sender-chain-key.js +2 -2
- package/lib/Signal/Group/sender-key-distribution-message.js +1 -1
- package/lib/Signal/Group/sender-key-message.js +3 -3
- package/lib/Signal/Group/sender-key-record.js +1 -1
- package/lib/Signal/Group/sender-message-key.js +5 -5
- package/lib/Signal/libsignal.js +28 -39
- package/lib/Signal/lid-mapping.js +8 -28
- package/lib/Socket/aigroups.js +5 -5
- package/lib/Socket/business.js +11 -3
- package/lib/Socket/chats.js +319 -668
- package/lib/Socket/communities.js +89 -85
- package/lib/Socket/graphql.js +228 -435
- package/lib/Socket/groups.js +211 -132
- package/lib/Socket/index.js +7 -9
- package/lib/Socket/interactive-handler.js +60 -117
- package/lib/Socket/interop.js +175 -56
- package/lib/Socket/luxu.js +387 -0
- package/lib/Socket/managed-account.js +48 -79
- package/lib/Socket/messages-recv.js +340 -898
- package/lib/Socket/messages-send.js +160 -462
- package/lib/Socket/mex.js +53 -40
- package/lib/Socket/newsletter.js +295 -291
- package/lib/Socket/privacy.js +265 -77
- package/lib/Socket/registration.js +157 -164
- package/lib/Socket/socket.js +199 -181
- package/lib/Socket/text-router.js +83 -0
- package/lib/Socket/username.js +227 -144
- package/lib/Store/keyed-db.js +108 -0
- package/lib/Store/make-cache-manager-store.js +2 -2
- package/lib/Store/make-in-memory-store.js +141 -25
- package/lib/Types/Auth.js +3 -3
- package/lib/Types/Label.js +1 -1
- package/lib/Types/LabelAssociation.js +1 -1
- package/lib/Types/Mex.js +112 -0
- package/lib/Types/Newsletter.js +65 -96
- package/lib/Types/State.js +4 -4
- package/lib/Types/index.js +1 -1
- package/lib/Utils/auth-utils.js +57 -52
- package/lib/Utils/browser-utils.js +1 -1
- package/lib/Utils/business.js +6 -2
- package/lib/Utils/chat-utils.js +38 -102
- package/lib/Utils/command-loader.d.ts +31 -0
- package/lib/Utils/command-loader.js +100 -0
- package/lib/Utils/companion-reg-client-utils.js +42 -0
- package/lib/Utils/crypto.js +84 -54
- package/lib/Utils/curve25519-js.js +275 -0
- package/lib/Utils/decode-wa-message.js +83 -316
- package/lib/Utils/event-buffer.js +61 -37
- package/lib/Utils/generics.js +103 -157
- package/lib/Utils/group-history.js +12 -3
- package/lib/Utils/history.js +15 -15
- package/lib/Utils/index.js +5 -1
- package/lib/Utils/link-preview.js +8 -3
- package/lib/Utils/lt-hash.js +35 -2
- package/lib/Utils/make-mutex.js +1 -1
- package/lib/Utils/message-retry-manager.js +92 -27
- package/lib/Utils/messages-media.js +130 -159
- package/lib/Utils/messages.js +566 -144
- package/lib/Utils/meta-ai-msmsg.js +202 -62
- package/lib/Utils/native-bridge.js +82 -0
- package/lib/Utils/noise-handler.js +21 -19
- package/lib/Utils/offline-node-processor.js +8 -3
- package/lib/Utils/pre-key-manager.js +23 -13
- package/lib/Utils/process-message.js +106 -197
- package/lib/Utils/reporting-utils.js +2 -2
- package/lib/Utils/session-pool.d.ts +16 -0
- package/lib/Utils/session-pool.js +94 -0
- package/lib/Utils/signal.js +63 -28
- package/lib/Utils/stanza-ack.js +12 -39
- package/lib/Utils/sticker.d.ts +17 -0
- package/lib/Utils/sticker.js +145 -0
- package/lib/Utils/sync-action-utils.js +7 -4
- package/lib/Utils/tc-token-utils.js +25 -20
- package/lib/Utils/use-multi-file-auth-state.js +14 -30
- package/lib/Utils/validate-connection.js +19 -59
- package/lib/Utils/view-once-cache.d.ts +9 -0
- package/lib/Utils/view-once-cache.js +79 -0
- package/lib/WABinary/decode.js +16 -50
- package/lib/WABinary/encode.js +224 -54
- package/lib/WABinary/generic-utils.js +97 -7
- package/lib/WABinary/jid-utils.js +14 -27
- package/lib/WAM/encode.js +7 -7
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +1 -58
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +1 -7
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +5 -13
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +4 -1
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +7 -20
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +1 -1
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +2 -2
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +2 -7
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +1 -1
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +1 -2
- package/lib/WAUSync/Protocols/index.js +0 -1
- package/lib/WAUSync/USyncQuery.js +6 -77
- package/lib/WAUSync/USyncUser.js +8 -16
- package/lib/WAUSync/index.js +0 -1
- package/lib/antiban.js +854 -608
- package/lib/index.js +1 -1
- package/package.json +20 -16
package/lib/Utils/signal.js
CHANGED
|
@@ -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:
|
|
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
|
|
67
|
+
const xmppPreKey = (pair, id) => ({
|
|
69
68
|
tag: 'key',
|
|
70
|
-
attrs:
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
]
|
package/lib/Utils/stanza-ack.js
CHANGED
|
@@ -1,50 +1,24 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
Object.defineProperty(exports, '__esModule', { value: true })
|
|
3
3
|
exports.buildAckStanza = buildAckStanza
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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:
|
|
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
|
|
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
|
}
|