@zervonex/baileys 0.0.0-stage → 3.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.
- package/LICENSE +21 -0
- package/README.md +3203 -2
- package/WAProto/WAProtoCompile.js +291 -0
- package/WAProto/fetcher.js +69 -0
- package/WAProto/index.js +88 -0
- package/WAProto/parser.js +313 -0
- package/engine-requirements.js +10 -0
- package/lib/Builders/AIRich.js +590 -0
- package/lib/Builders/base.js +130 -0
- package/lib/Builders/builders.js +448 -0
- package/lib/Builders/index.js +3 -0
- package/lib/Defaults/index.js +225 -0
- package/lib/Defaults/phonenumber-mcc.json +223 -0
- package/lib/Signal/index.js +2 -0
- package/lib/Signal/libsignal.js +523 -0
- package/lib/Signal/lid-mapping.js +283 -0
- package/lib/Socket/Client/index.js +2 -0
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.js +84 -0
- package/lib/Socket/business.js +152 -0
- package/lib/Socket/chats.js +855 -0
- package/lib/Socket/communities.js +147 -0
- package/lib/Socket/groups.js +294 -0
- package/lib/Socket/index.js +15 -0
- package/lib/Socket/messages-recv.js +904 -0
- package/lib/Socket/messages-send.js +693 -0
- package/lib/Socket/mex.js +34 -0
- package/lib/Socket/newsletter.js +752 -0
- package/lib/Socket/nexus-handler.js +55 -0
- package/lib/Socket/registration.js +155 -0
- package/lib/Socket/socket.js +798 -0
- package/lib/Socket/spam-report.js +65 -0
- package/lib/Socket/username.js +82 -0
- package/lib/Store/index.js +6 -0
- package/lib/Store/make-cache-manager-store.js +81 -0
- package/lib/Store/make-in-memory-store.js +425 -0
- package/lib/Store/make-ordered-dictionary.js +82 -0
- package/lib/Store/object-repository.js +31 -0
- package/lib/Store/reporting-info-store.js +38 -0
- package/lib/Types/Auth.js +2 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.js +8 -0
- package/lib/Types/Contact.js +2 -0
- package/lib/Types/Events.js +2 -0
- package/lib/Types/GroupMetadata.js +2 -0
- package/lib/Types/Label.js +25 -0
- package/lib/Types/LabelAssociation.js +7 -0
- package/lib/Types/Message.js +15 -0
- package/lib/Types/Newsletter.js +100 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.js +14 -0
- package/lib/Types/State.js +26 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.js +30 -0
- package/lib/Utils/auth-utils.js +142 -0
- package/lib/Utils/baileys-event-stream.js +33 -0
- package/lib/Utils/bot-capabilities.js +40 -0
- package/lib/Utils/bot-planning-replay.js +44 -0
- package/lib/Utils/browser-utils.js +26 -0
- package/lib/Utils/business.js +75 -0
- package/lib/Utils/chat-utils.js +316 -0
- package/lib/Utils/companion-reg-client-utils.js +20 -0
- package/lib/Utils/consumer-application.js +38 -0
- package/lib/Utils/crypto.js +52 -0
- package/lib/Utils/decode-wa-message.js +197 -0
- package/lib/Utils/event-buffer.js +273 -0
- package/lib/Utils/extras.js +186 -0
- package/lib/Utils/generics.js +195 -0
- package/lib/Utils/history.js +51 -0
- package/lib/Utils/identity-change-handler.js +19 -0
- package/lib/Utils/index.js +39 -0
- package/lib/Utils/key-store.js +3 -0
- package/lib/Utils/link-preview.js +51 -0
- package/lib/Utils/logger.js +2 -0
- package/lib/Utils/lt-hash.js +3 -0
- package/lib/Utils/make-mutex.js +16 -0
- package/lib/Utils/message-composer.js +59 -0
- package/lib/Utils/message-retry-manager.js +48 -0
- package/lib/Utils/messages-media.js +520 -0
- package/lib/Utils/messages.js +742 -0
- package/lib/Utils/meta-ai-msmsg.js +122 -0
- package/lib/Utils/meta-compositing.js +75 -0
- package/lib/Utils/modern-messages.js +235 -0
- package/lib/Utils/native-flow.js +55 -0
- package/lib/Utils/noise-handler.js +161 -0
- package/lib/Utils/pre-key-manager.js +76 -0
- package/lib/Utils/process-message.js +399 -0
- package/lib/Utils/reporting-utils.js +171 -0
- package/lib/Utils/signal.js +167 -0
- package/lib/Utils/spam-report-utils.js +142 -0
- package/lib/Utils/sync-action-utils.js +33 -0
- package/lib/Utils/tc-token-utils.js +102 -0
- package/lib/Utils/use-multi-file-auth-state.js +307 -0
- package/lib/Utils/validate-connection.js +181 -0
- package/lib/WABinary/constants.js +1321 -0
- package/lib/WABinary/decode.js +152 -0
- package/lib/WABinary/encode.js +140 -0
- package/lib/WABinary/generic-utils.js +179 -0
- package/lib/WABinary/index.js +6 -0
- package/lib/WABinary/jid-utils.js +235 -0
- package/lib/WABinary/types.js +4 -0
- package/lib/WAM/BinaryInfo.js +10 -0
- package/lib/WAM/constants.js +22853 -0
- package/lib/WAM/encode.js +150 -0
- package/lib/WAM/index.js +3 -0
- package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +30 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +48 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +23 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +28 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +15 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +24 -0
- package/lib/WAUSync/Protocols/USyncLIDProtocol.js +9 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +21 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +19 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +19 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +22 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +16 -0
- package/lib/WAUSync/Protocols/index.js +12 -0
- package/lib/WAUSync/USyncQuery.js +100 -0
- package/lib/WAUSync/USyncUser.js +20 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/index.js +68 -0
- package/package.json +216 -5
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { KEY_BUNDLE_TYPE } from '../Defaults/index.js'
|
|
2
|
+
import { assertNodeErrorFree, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildUInt, getServerFromDomainType, jidDecode, S_WHATSAPP_NET, WAJIDDomains } from '../WABinary/index.js'
|
|
3
|
+
import { Curve, generateSignalPubKey } from './crypto.js'
|
|
4
|
+
import { encodeBigEndian } from './generics.js'
|
|
5
|
+
|
|
6
|
+
const chunk = (array, size) => {
|
|
7
|
+
const chunks = []
|
|
8
|
+
for (let i = 0; i < array.length; i += size) chunks.push(array.slice(i, i + size))
|
|
9
|
+
return chunks
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const createSignalIdentity = (wid, accountSignatureKey) => ({
|
|
13
|
+
identifier: { name: wid, deviceId: 0 },
|
|
14
|
+
identifierKey: generateSignalPubKey(accountSignatureKey),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export const getPreKeys = async ({ get }, min, limit) => {
|
|
18
|
+
const idList = []
|
|
19
|
+
for (let id = min; id < limit; id++) idList.push(id.toString())
|
|
20
|
+
return get('pre-key', idList)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const generateOrGetPreKeys = (creds, range) => {
|
|
24
|
+
const available = creds.nextPreKeyId - creds.firstUnuploadedPreKeyId
|
|
25
|
+
const remaining = range - available
|
|
26
|
+
const lastPreKeyId = creds.nextPreKeyId + remaining - 1
|
|
27
|
+
const newPreKeys = {}
|
|
28
|
+
if (remaining > 0) {
|
|
29
|
+
for (let i = creds.nextPreKeyId; i <= lastPreKeyId; i++) newPreKeys[i] = Curve.generateKeyPair()
|
|
30
|
+
}
|
|
31
|
+
return { newPreKeys, lastPreKeyId, preKeysRange: [creds.firstUnuploadedPreKeyId, range] }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const xmppSignedPreKey = key => ({
|
|
35
|
+
tag: 'skey',
|
|
36
|
+
attrs: {},
|
|
37
|
+
content: [
|
|
38
|
+
{ tag: 'id', attrs: {}, content: encodeBigEndian(key.keyId, 3) },
|
|
39
|
+
{ tag: 'value', attrs: {}, content: key.keyPair.public },
|
|
40
|
+
{ tag: 'signature', attrs: {}, content: key.signature },
|
|
41
|
+
],
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export const xmppPreKey = (pair, id) => ({
|
|
45
|
+
tag: 'key',
|
|
46
|
+
attrs: {},
|
|
47
|
+
content: [
|
|
48
|
+
{ tag: 'id', attrs: {}, content: encodeBigEndian(id, 3) },
|
|
49
|
+
{ tag: 'value', attrs: {}, content: pair.public },
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const isValidUInt = n => typeof n === 'number' && Number.isInteger(n) && n >= 0
|
|
54
|
+
|
|
55
|
+
export const extractE2ESessionFromRetryReceipt = receipt => {
|
|
56
|
+
const keysNode = getBinaryNodeChild(receipt, 'keys')
|
|
57
|
+
if (!keysNode) return null
|
|
58
|
+
|
|
59
|
+
const typeBuf = getBinaryNodeChildBuffer(keysNode, 'type')
|
|
60
|
+
if (!typeBuf || typeBuf.length !== 1 || typeBuf[0] !== KEY_BUNDLE_TYPE[0]) return null
|
|
61
|
+
|
|
62
|
+
const identity = getBinaryNodeChildBuffer(keysNode, 'identity')
|
|
63
|
+
const skey = getBinaryNodeChild(keysNode, 'skey')
|
|
64
|
+
if (!identity || identity.length !== 32 || !skey) return null
|
|
65
|
+
|
|
66
|
+
const registrationId = getBinaryNodeChildUInt(receipt, 'registration', 4)
|
|
67
|
+
if (!isValidUInt(registrationId)) return null
|
|
68
|
+
|
|
69
|
+
const signedPubKey = getBinaryNodeChildBuffer(skey, 'value')
|
|
70
|
+
const signedSig = getBinaryNodeChildBuffer(skey, 'signature')
|
|
71
|
+
const signedKeyId = getBinaryNodeChildUInt(skey, 'id', 3)
|
|
72
|
+
if (!signedPubKey || signedPubKey.length !== 32 || !signedSig || !isValidUInt(signedKeyId)) return null
|
|
73
|
+
|
|
74
|
+
const preKeyNode = getBinaryNodeChild(keysNode, 'key')
|
|
75
|
+
let preKey
|
|
76
|
+
if (preKeyNode) {
|
|
77
|
+
const preKeyPub = getBinaryNodeChildBuffer(preKeyNode, 'value')
|
|
78
|
+
const preKeyId = getBinaryNodeChildUInt(preKeyNode, 'id', 3)
|
|
79
|
+
if (!preKeyPub || preKeyPub.length !== 32 || !isValidUInt(preKeyId)) return null
|
|
80
|
+
preKey = { keyId: preKeyId, publicKey: generateSignalPubKey(preKeyPub) }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
registrationId,
|
|
85
|
+
identityKey: generateSignalPubKey(identity),
|
|
86
|
+
signedPreKey: { keyId: signedKeyId, publicKey: generateSignalPubKey(signedPubKey), signature: signedSig },
|
|
87
|
+
preKey,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const parseAndInjectE2ESessions = async (node, repository) => {
|
|
92
|
+
const extractKey = key => key ? {
|
|
93
|
+
keyId: getBinaryNodeChildUInt(key, 'id', 3),
|
|
94
|
+
publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')),
|
|
95
|
+
signature: getBinaryNodeChildBuffer(key, 'signature'),
|
|
96
|
+
} : undefined
|
|
97
|
+
|
|
98
|
+
const nodes = getBinaryNodeChildren(getBinaryNodeChild(node, 'list'), 'user').filter(node => {
|
|
99
|
+
try { assertNodeErrorFree(node); return true } catch { return false }
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
for (const nodesChunk of chunk(nodes, 100)) {
|
|
103
|
+
await Promise.all(nodesChunk.map(node => repository.injectE2ESession({
|
|
104
|
+
jid: node.attrs.jid,
|
|
105
|
+
session: {
|
|
106
|
+
registrationId: getBinaryNodeChildUInt(node, 'registration', 4),
|
|
107
|
+
identityKey: generateSignalPubKey(getBinaryNodeChildBuffer(node, 'identity')),
|
|
108
|
+
signedPreKey: extractKey(getBinaryNodeChild(node, 'skey')),
|
|
109
|
+
preKey: extractKey(getBinaryNodeChild(node, 'key')),
|
|
110
|
+
},
|
|
111
|
+
})))
|
|
112
|
+
await new Promise(resolve => setImmediate(resolve)) // yield between chunks to avoid blocking on large batches
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const extractDeviceJids = (result, myJid, myLid, excludeZeroDevices) => {
|
|
117
|
+
const { user: myUser, device: myDevice } = jidDecode(myJid)
|
|
118
|
+
const extracted = []
|
|
119
|
+
for (const { devices, id } of result) {
|
|
120
|
+
const decoded = jidDecode(id)
|
|
121
|
+
const { user, server } = decoded
|
|
122
|
+
let { domainType } = decoded
|
|
123
|
+
const deviceList = devices?.deviceList
|
|
124
|
+
if (!Array.isArray(deviceList)) continue
|
|
125
|
+
for (const { id: device, keyIndex, isHosted } of deviceList) {
|
|
126
|
+
if ((!excludeZeroDevices || device !== 0) &&
|
|
127
|
+
((myUser !== user && myLid !== user) || myDevice !== device) &&
|
|
128
|
+
(device === 0 || !!keyIndex)) {
|
|
129
|
+
if (isHosted) domainType = domainType === WAJIDDomains.LID ? WAJIDDomains.HOSTED_LID : WAJIDDomains.HOSTED
|
|
130
|
+
extracted.push({ user, device, domainType, server: getServerFromDomainType(server, domainType) })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return extracted
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const getNextPreKeys = async ({ creds, keys }, count) => {
|
|
138
|
+
const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(creds, count)
|
|
139
|
+
await keys.set({ 'pre-key': newPreKeys })
|
|
140
|
+
const preKeys = await getPreKeys(keys, preKeysRange[0], preKeysRange[0] + preKeysRange[1])
|
|
141
|
+
return {
|
|
142
|
+
update: {
|
|
143
|
+
nextPreKeyId: Math.max(lastPreKeyId + 1, creds.nextPreKeyId),
|
|
144
|
+
firstUnuploadedPreKeyId: Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId + 1),
|
|
145
|
+
},
|
|
146
|
+
preKeys,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const getNextPreKeysNode = async (state, count) => {
|
|
151
|
+
const { creds } = state
|
|
152
|
+
const { update, preKeys } = await getNextPreKeys(state, count)
|
|
153
|
+
return {
|
|
154
|
+
update,
|
|
155
|
+
node: {
|
|
156
|
+
tag: 'iq',
|
|
157
|
+
attrs: { xmlns: 'encrypt', type: 'set', to: S_WHATSAPP_NET },
|
|
158
|
+
content: [
|
|
159
|
+
{ tag: 'registration', attrs: {}, content: encodeBigEndian(creds.registrationId) },
|
|
160
|
+
{ tag: 'type', attrs: {}, content: KEY_BUNDLE_TYPE },
|
|
161
|
+
{ tag: 'identity', attrs: {}, content: creds.signedIdentityKey.public },
|
|
162
|
+
{ tag: 'list', attrs: {}, content: Object.keys(preKeys).map(k => xmppPreKey(preKeys[+k], +k)) },
|
|
163
|
+
xmppSignedPreKey(creds.signedPreKey),
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { getBinaryNodeChild, getBinaryNodeChildString, isLidUser, isHostedLidUser, S_WHATSAPP_NET } from '../WABinary/index.js'
|
|
2
|
+
import { extractMessageContent } from './messages.js'
|
|
3
|
+
|
|
4
|
+
export const SPAM_FLOWS = {
|
|
5
|
+
ACCOUNT_INFO_REPORT: 'account_info_report',
|
|
6
|
+
ACCOUNT_INFO_BLOCK: 'account_info_block',
|
|
7
|
+
ONE_TO_ONE_SPAM_BANNER_REPORT: '1_1_spam_banner_report',
|
|
8
|
+
MESSAGE_MENU: 'message_menu',
|
|
9
|
+
OVERFLOW_MENU_REPORT: 'overflow_menu_report',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const toBuffer = data => {
|
|
13
|
+
if (!data) return null
|
|
14
|
+
if (Buffer.isBuffer(data)) return data
|
|
15
|
+
if (data instanceof Uint8Array) return Buffer.from(data)
|
|
16
|
+
if (typeof data === 'string') return Buffer.from(data, 'base64')
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const resolveReportJid = async (jid, lidMapping) => {
|
|
21
|
+
if (!jid) throw new Error('reportSpam: jid required')
|
|
22
|
+
if (isLidUser(jid) || isHostedLidUser(jid)) return jid
|
|
23
|
+
if (lidMapping?.getLIDForPN) {
|
|
24
|
+
const lid = await lidMapping.getLIDForPN(jid)
|
|
25
|
+
if (lid) return lid
|
|
26
|
+
}
|
|
27
|
+
return jid
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const normalizeReportingTag = tag => {
|
|
31
|
+
const buf = toBuffer(tag)
|
|
32
|
+
return buf?.length ? buf : null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const extractMessageText = msg => {
|
|
36
|
+
if (!msg?.message) return ''
|
|
37
|
+
const content = extractMessageContent(msg.message)
|
|
38
|
+
if (!content) return ''
|
|
39
|
+
if (typeof content.conversation === 'string') return content.conversation
|
|
40
|
+
if (content.extendedTextMessage?.text) return content.extendedTextMessage.text
|
|
41
|
+
if (content.imageMessage?.caption) return content.imageMessage.caption
|
|
42
|
+
if (content.videoMessage?.caption) return content.videoMessage.caption
|
|
43
|
+
return ''
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const extractReportingInfoFromStanza = stanza => {
|
|
47
|
+
const reporting = getBinaryNodeChild(stanza, 'reporting')
|
|
48
|
+
if (!reporting) return null
|
|
49
|
+
|
|
50
|
+
let tagNode = getBinaryNodeChild(reporting, 'reporting_tag')
|
|
51
|
+
if (!tagNode) {
|
|
52
|
+
const validation = getBinaryNodeChild(reporting, 'reporting_validation')
|
|
53
|
+
tagNode = validation ? getBinaryNodeChild(validation, 'reporting_tag') : undefined
|
|
54
|
+
}
|
|
55
|
+
if (!tagNode) return null
|
|
56
|
+
|
|
57
|
+
const raw = tagNode.content
|
|
58
|
+
const reportingTag = toBuffer(
|
|
59
|
+
raw instanceof Uint8Array || Buffer.isBuffer(raw) ? raw
|
|
60
|
+
: typeof raw === 'string' ? raw
|
|
61
|
+
: null
|
|
62
|
+
)
|
|
63
|
+
if (!reportingTag?.length) return null
|
|
64
|
+
|
|
65
|
+
const stanzaId = tagNode.attrs?.id || stanza.attrs?.id
|
|
66
|
+
if (!stanzaId) return null
|
|
67
|
+
|
|
68
|
+
const tsRaw = tagNode.attrs?.ts_s || stanza.attrs?.t
|
|
69
|
+
return { stanzaId, timestamp: tsRaw ? Number(tsRaw) : 0, reportingTag }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const buildSpamReportMessageNodes = messages => messages.slice(0, 5).map(m => {
|
|
73
|
+
const reportingTag = normalizeReportingTag(m.reportingTag)
|
|
74
|
+
if (!reportingTag) throw new Error(`reportSpam: missing reportingTag for ${m.stanzaId}`)
|
|
75
|
+
return {
|
|
76
|
+
tag: 'message',
|
|
77
|
+
attrs: {
|
|
78
|
+
t: String(m.timestamp),
|
|
79
|
+
id: m.stanzaId,
|
|
80
|
+
reported_push_name: m.pushName || '',
|
|
81
|
+
type: m.messageType || 'text',
|
|
82
|
+
from: m.fromJid,
|
|
83
|
+
},
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
tag: 'raw',
|
|
87
|
+
attrs: { local_message_type: '0', v: '2' },
|
|
88
|
+
content: m.text ?? '',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
tag: 'reporting',
|
|
92
|
+
attrs: {},
|
|
93
|
+
content: [{
|
|
94
|
+
tag: 'reporting_validation',
|
|
95
|
+
attrs: {},
|
|
96
|
+
content: [{
|
|
97
|
+
tag: 'reporting_tag',
|
|
98
|
+
attrs: { id: m.stanzaId, ts_s: String(m.timestamp) },
|
|
99
|
+
content: reportingTag,
|
|
100
|
+
}],
|
|
101
|
+
}],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
export const buildSpamReportIq = (jid, messages, spamFlow = SPAM_FLOWS.ACCOUNT_INFO_REPORT) => {
|
|
108
|
+
if (!messages?.length) throw new Error('reportSpam: at least one message with reporting_tag required')
|
|
109
|
+
return {
|
|
110
|
+
tag: 'iq',
|
|
111
|
+
attrs: { type: 'set', xmlns: 'spam', to: S_WHATSAPP_NET },
|
|
112
|
+
content: [{
|
|
113
|
+
tag: 'spam_list',
|
|
114
|
+
attrs: { jid, spam_flow: spamFlow },
|
|
115
|
+
content: buildSpamReportMessageNodes(messages),
|
|
116
|
+
}],
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const parseSpamReportResponse = result => {
|
|
121
|
+
if (!result) return { success: false, error: 'empty response' }
|
|
122
|
+
if (result.attrs?.type === 'error') {
|
|
123
|
+
const errNode = getBinaryNodeChild(result, 'error')
|
|
124
|
+
const code = errNode?.attrs?.code
|
|
125
|
+
const text = errNode?.content?.toString?.() || errNode?.attrs?.text
|
|
126
|
+
return { success: false, error: `spam IQ error${code ? ` ${code}` : ''}${text ? `: ${text}` : ''}` }
|
|
127
|
+
}
|
|
128
|
+
const reportNode = getBinaryNodeChild(result, 'report')
|
|
129
|
+
const reportId = reportNode?.attrs?.id || getBinaryNodeChildString(reportNode, 'id')
|
|
130
|
+
if (reportId) return { success: true, reportId: String(reportId) }
|
|
131
|
+
if (result.attrs?.type === 'result') return { success: true }
|
|
132
|
+
return { success: false, error: 'unexpected spam IQ response' }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const buildSimpleReportNode = (jid, messageKeys) => ({
|
|
136
|
+
tag: 'report',
|
|
137
|
+
attrs: { jid, type: 'spam' },
|
|
138
|
+
content: (messageKeys || []).map(key => ({
|
|
139
|
+
tag: 'message',
|
|
140
|
+
attrs: { id: key.id || key, from: key.fromMe ? undefined : jid },
|
|
141
|
+
})),
|
|
142
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isLidUser, isPnUser } from '../WABinary/index.js'
|
|
2
|
+
|
|
3
|
+
export const processContactAction = (action, id, logger) => {
|
|
4
|
+
const results = []
|
|
5
|
+
if (!id) {
|
|
6
|
+
logger?.warn({ hasFullName: !!action.fullName, hasLidJid: !!action.lidJid, hasPnJid: !!action.pnJid }, 'contactAction sync: missing id in index')
|
|
7
|
+
return results
|
|
8
|
+
}
|
|
9
|
+
const lidJid = action.lidJid
|
|
10
|
+
const idIsPn = isPnUser(id)
|
|
11
|
+
const phoneNumber = idIsPn ? id : action.pnJid || undefined
|
|
12
|
+
results.push({
|
|
13
|
+
event: 'contacts.upsert',
|
|
14
|
+
data: [{
|
|
15
|
+
id,
|
|
16
|
+
name: action.fullName || action.firstName || action.username || undefined,
|
|
17
|
+
username: action.username || undefined,
|
|
18
|
+
lid: lidJid || undefined,
|
|
19
|
+
phoneNumber
|
|
20
|
+
}]
|
|
21
|
+
})
|
|
22
|
+
if (lidJid && isLidUser(lidJid) && idIsPn) {
|
|
23
|
+
results.push({ event: 'lid-mapping.update', data: { lid: lidJid, pn: id } })
|
|
24
|
+
}
|
|
25
|
+
return results
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const emitSyncActionResults = (ev, results) => {
|
|
29
|
+
for (const result of results) {
|
|
30
|
+
if (result.event === 'contacts.upsert') ev.emit('contacts.upsert', result.data)
|
|
31
|
+
else ev.emit('lid-mapping.update', result.data)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidMetaAI, isLidUser, isPnUser, jidNormalizedUser } from '../WABinary/index.js';
|
|
3
|
+
import { unixTimestampSeconds } from './generics.js';
|
|
4
|
+
import { TC_TOKEN_INDEX_KEY } from './key-store.js';
|
|
5
|
+
|
|
6
|
+
const BOT_PHONE_REGEX = /^1313555\d{4}$|^131655500\d{2}$/;
|
|
7
|
+
const TC_TOKEN_BUCKET_DURATION = 604800; // 7 days in seconds
|
|
8
|
+
const TC_TOKEN_NUM_BUCKETS = 4; // ~28-day rolling window
|
|
9
|
+
|
|
10
|
+
const currentBucket = () => Math.floor(Math.floor(Date.now() / 1000) / TC_TOKEN_BUCKET_DURATION);
|
|
11
|
+
const generateTcToken = () => Buffer.concat([Buffer.from([0x04, 0x01, (Math.floor(unixTimestampSeconds() / TC_TOKEN_BUCKET_DURATION) - 2900) & 0xff]), randomBytes(8)]);
|
|
12
|
+
|
|
13
|
+
export const isRegularUser = jid => {
|
|
14
|
+
if (!jid) return false;
|
|
15
|
+
const user = jid.split('@')[0] ?? '';
|
|
16
|
+
if (user === '0' || BOT_PHONE_REGEX.test(user) || isJidMetaAI(jid)) return false;
|
|
17
|
+
return !!(isPnUser(jid) || isLidUser(jid) || isHostedPnUser(jid) || isHostedLidUser(jid) || jid.endsWith('@c.us'));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const isTcTokenExpired = timestamp => {
|
|
21
|
+
if (timestamp == null) return true;
|
|
22
|
+
const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp;
|
|
23
|
+
return isNaN(ts) || ts < (currentBucket() - (TC_TOKEN_NUM_BUCKETS - 1)) * TC_TOKEN_BUCKET_DURATION;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const shouldSendNewTcToken = senderTimestamp => {
|
|
27
|
+
if (senderTimestamp === undefined) return true;
|
|
28
|
+
return currentBucket() > Math.floor(senderTimestamp / TC_TOKEN_BUCKET_DURATION);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const resolveTcTokenJid = async (jid, getLIDForPN) => isLidUser(jid) ? jid : (await getLIDForPN(jid) || null);
|
|
32
|
+
|
|
33
|
+
export const resolveIssuanceJid = async (jid, issueToLid, getLIDForPN, getPNForLID) => {
|
|
34
|
+
if (issueToLid) return isLidUser(jid) ? jid : (await getLIDForPN(jid)) ?? jid;
|
|
35
|
+
if (!isLidUser(jid)) return jid;
|
|
36
|
+
return getPNForLID ? (await getPNForLID(jid)) ?? jid : jid;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const readTcTokenIndex = async keys => {
|
|
40
|
+
for (const key of [TC_TOKEN_INDEX_KEY, 'index']) {
|
|
41
|
+
const entry = (await keys.get('tctoken', [key]))[key];
|
|
42
|
+
if (!entry?.token?.length) continue;
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(Buffer.from(entry.token).toString());
|
|
45
|
+
if (!Array.isArray(parsed)) continue;
|
|
46
|
+
return parsed.filter(j => typeof j === 'string' && j.length > 0 && j !== TC_TOKEN_INDEX_KEY && j !== 'index');
|
|
47
|
+
} catch { continue; }
|
|
48
|
+
}
|
|
49
|
+
return [];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const buildMergedTcTokenIndexWrite = async (keys, addedJids) => {
|
|
53
|
+
const merged = new Set(await readTcTokenIndex(keys));
|
|
54
|
+
for (const jid of addedJids) if (jid && jid !== TC_TOKEN_INDEX_KEY && jid !== 'index') merged.add(jid);
|
|
55
|
+
return { [TC_TOKEN_INDEX_KEY]: { token: Buffer.from(JSON.stringify([...merged])) } };
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const preSeedTcToken = async ({ authState, jid, getLIDForPN, logger }) => {
|
|
59
|
+
const tcTokenJid = await resolveTcTokenJid(jid, getLIDForPN);
|
|
60
|
+
const existing = await authState.keys.get('tctoken', [tcTokenJid]);
|
|
61
|
+
if (existing[tcTokenJid]?.token?.length) return { token: existing[tcTokenJid].token, storageJid: tcTokenJid };
|
|
62
|
+
const token = generateTcToken();
|
|
63
|
+
const timestamp = String(unixTimestampSeconds());
|
|
64
|
+
const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
|
|
65
|
+
await authState.keys.set({ tctoken: { [tcTokenJid]: { ...(existing[tcTokenJid] || {}), token, timestamp }, ...indexWrite } });
|
|
66
|
+
logger?.debug({ jid, tcTokenJid }, 'pre-seeded locally generated tctoken');
|
|
67
|
+
return { token, storageJid: tcTokenJid };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const buildTcTokenFromJid = async ({ authState, jid, baseContent = [], getLIDForPN }) => {
|
|
71
|
+
try {
|
|
72
|
+
const storageJid = await resolveTcTokenJid(jid, getLIDForPN);
|
|
73
|
+
const entry = (await authState.keys.get('tctoken', [storageJid]))?.[storageJid];
|
|
74
|
+
const { token: tcTokenBuffer, timestamp, senderTimestamp } = entry ?? {};
|
|
75
|
+
if (!tcTokenBuffer?.length || timestamp === undefined || isTcTokenExpired(timestamp)) {
|
|
76
|
+
if (tcTokenBuffer) {
|
|
77
|
+
const cleared = senderTimestamp !== undefined ? { token: Buffer.alloc(0), senderTimestamp } : null;
|
|
78
|
+
await authState.keys.set({ tctoken: { [storageJid]: cleared } });
|
|
79
|
+
}
|
|
80
|
+
return baseContent.length > 0 ? baseContent : undefined;
|
|
81
|
+
}
|
|
82
|
+
baseContent.push({ tag: 'tctoken', attrs: { t: String(timestamp) }, content: tcTokenBuffer });
|
|
83
|
+
return baseContent;
|
|
84
|
+
} catch { return baseContent.length > 0 ? baseContent : undefined; }
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const storeTcTokensFromIqResult = async ({ result, fallbackJid, keys, getLIDForPN, onNewJidStored }) => {
|
|
88
|
+
const tokensNode = getBinaryNodeChild(result, 'tokens');
|
|
89
|
+
if (!tokensNode) return;
|
|
90
|
+
for (const tokenNode of getBinaryNodeChildren(tokensNode, 'token')) {
|
|
91
|
+
if (tokenNode.attrs.type !== 'trusted_contact' || !(tokenNode.content instanceof Uint8Array)) continue;
|
|
92
|
+
const rawJid = jidNormalizedUser(fallbackJid || tokenNode.attrs.jid);
|
|
93
|
+
if (!isRegularUser(rawJid)) continue;
|
|
94
|
+
const storageJid = await resolveTcTokenJid(rawJid, getLIDForPN);
|
|
95
|
+
const existingEntry = (await keys.get('tctoken', [storageJid]))[storageJid];
|
|
96
|
+
const existingTs = existingEntry?.timestamp ? Number(existingEntry.timestamp) : 0;
|
|
97
|
+
const incomingTs = tokenNode.attrs.t ? Number(tokenNode.attrs.t) : 0;
|
|
98
|
+
if (!incomingTs || (existingTs > 0 && existingTs > incomingTs)) continue;
|
|
99
|
+
await keys.set({ tctoken: { [storageJid]: { ...existingEntry, token: Buffer.from(tokenNode.content), timestamp: tokenNode.attrs.t } } });
|
|
100
|
+
onNewJidStored?.(storageJid);
|
|
101
|
+
}
|
|
102
|
+
};
|