@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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3203 -2
  3. package/WAProto/WAProtoCompile.js +291 -0
  4. package/WAProto/fetcher.js +69 -0
  5. package/WAProto/index.js +88 -0
  6. package/WAProto/parser.js +313 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Builders/AIRich.js +590 -0
  9. package/lib/Builders/base.js +130 -0
  10. package/lib/Builders/builders.js +448 -0
  11. package/lib/Builders/index.js +3 -0
  12. package/lib/Defaults/index.js +225 -0
  13. package/lib/Defaults/phonenumber-mcc.json +223 -0
  14. package/lib/Signal/index.js +2 -0
  15. package/lib/Signal/libsignal.js +523 -0
  16. package/lib/Signal/lid-mapping.js +283 -0
  17. package/lib/Socket/Client/index.js +2 -0
  18. package/lib/Socket/Client/types.js +10 -0
  19. package/lib/Socket/Client/websocket.js +84 -0
  20. package/lib/Socket/business.js +152 -0
  21. package/lib/Socket/chats.js +855 -0
  22. package/lib/Socket/communities.js +147 -0
  23. package/lib/Socket/groups.js +294 -0
  24. package/lib/Socket/index.js +15 -0
  25. package/lib/Socket/messages-recv.js +904 -0
  26. package/lib/Socket/messages-send.js +693 -0
  27. package/lib/Socket/mex.js +34 -0
  28. package/lib/Socket/newsletter.js +752 -0
  29. package/lib/Socket/nexus-handler.js +55 -0
  30. package/lib/Socket/registration.js +155 -0
  31. package/lib/Socket/socket.js +798 -0
  32. package/lib/Socket/spam-report.js +65 -0
  33. package/lib/Socket/username.js +82 -0
  34. package/lib/Store/index.js +6 -0
  35. package/lib/Store/make-cache-manager-store.js +81 -0
  36. package/lib/Store/make-in-memory-store.js +425 -0
  37. package/lib/Store/make-ordered-dictionary.js +82 -0
  38. package/lib/Store/object-repository.js +31 -0
  39. package/lib/Store/reporting-info-store.js +38 -0
  40. package/lib/Types/Auth.js +2 -0
  41. package/lib/Types/Bussines.js +2 -0
  42. package/lib/Types/Call.js +2 -0
  43. package/lib/Types/Chat.js +8 -0
  44. package/lib/Types/Contact.js +2 -0
  45. package/lib/Types/Events.js +2 -0
  46. package/lib/Types/GroupMetadata.js +2 -0
  47. package/lib/Types/Label.js +25 -0
  48. package/lib/Types/LabelAssociation.js +7 -0
  49. package/lib/Types/Message.js +15 -0
  50. package/lib/Types/Newsletter.js +100 -0
  51. package/lib/Types/Product.js +2 -0
  52. package/lib/Types/Signal.js +2 -0
  53. package/lib/Types/Socket.js +14 -0
  54. package/lib/Types/State.js +26 -0
  55. package/lib/Types/USync.js +2 -0
  56. package/lib/Types/index.js +30 -0
  57. package/lib/Utils/auth-utils.js +142 -0
  58. package/lib/Utils/baileys-event-stream.js +33 -0
  59. package/lib/Utils/bot-capabilities.js +40 -0
  60. package/lib/Utils/bot-planning-replay.js +44 -0
  61. package/lib/Utils/browser-utils.js +26 -0
  62. package/lib/Utils/business.js +75 -0
  63. package/lib/Utils/chat-utils.js +316 -0
  64. package/lib/Utils/companion-reg-client-utils.js +20 -0
  65. package/lib/Utils/consumer-application.js +38 -0
  66. package/lib/Utils/crypto.js +52 -0
  67. package/lib/Utils/decode-wa-message.js +197 -0
  68. package/lib/Utils/event-buffer.js +273 -0
  69. package/lib/Utils/extras.js +186 -0
  70. package/lib/Utils/generics.js +195 -0
  71. package/lib/Utils/history.js +51 -0
  72. package/lib/Utils/identity-change-handler.js +19 -0
  73. package/lib/Utils/index.js +39 -0
  74. package/lib/Utils/key-store.js +3 -0
  75. package/lib/Utils/link-preview.js +51 -0
  76. package/lib/Utils/logger.js +2 -0
  77. package/lib/Utils/lt-hash.js +3 -0
  78. package/lib/Utils/make-mutex.js +16 -0
  79. package/lib/Utils/message-composer.js +59 -0
  80. package/lib/Utils/message-retry-manager.js +48 -0
  81. package/lib/Utils/messages-media.js +520 -0
  82. package/lib/Utils/messages.js +742 -0
  83. package/lib/Utils/meta-ai-msmsg.js +122 -0
  84. package/lib/Utils/meta-compositing.js +75 -0
  85. package/lib/Utils/modern-messages.js +235 -0
  86. package/lib/Utils/native-flow.js +55 -0
  87. package/lib/Utils/noise-handler.js +161 -0
  88. package/lib/Utils/pre-key-manager.js +76 -0
  89. package/lib/Utils/process-message.js +399 -0
  90. package/lib/Utils/reporting-utils.js +171 -0
  91. package/lib/Utils/signal.js +167 -0
  92. package/lib/Utils/spam-report-utils.js +142 -0
  93. package/lib/Utils/sync-action-utils.js +33 -0
  94. package/lib/Utils/tc-token-utils.js +102 -0
  95. package/lib/Utils/use-multi-file-auth-state.js +307 -0
  96. package/lib/Utils/validate-connection.js +181 -0
  97. package/lib/WABinary/constants.js +1321 -0
  98. package/lib/WABinary/decode.js +152 -0
  99. package/lib/WABinary/encode.js +140 -0
  100. package/lib/WABinary/generic-utils.js +179 -0
  101. package/lib/WABinary/index.js +6 -0
  102. package/lib/WABinary/jid-utils.js +235 -0
  103. package/lib/WABinary/types.js +4 -0
  104. package/lib/WAM/BinaryInfo.js +10 -0
  105. package/lib/WAM/constants.js +22853 -0
  106. package/lib/WAM/encode.js +150 -0
  107. package/lib/WAM/index.js +3 -0
  108. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +30 -0
  109. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +48 -0
  110. package/lib/WAUSync/Protocols/USyncContactProtocol.js +23 -0
  111. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +28 -0
  112. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +15 -0
  113. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +24 -0
  114. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +9 -0
  115. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +21 -0
  116. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +19 -0
  117. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +19 -0
  118. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +22 -0
  119. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +16 -0
  120. package/lib/WAUSync/Protocols/index.js +12 -0
  121. package/lib/WAUSync/USyncQuery.js +100 -0
  122. package/lib/WAUSync/USyncUser.js +20 -0
  123. package/lib/WAUSync/index.js +3 -0
  124. package/lib/index.js +68 -0
  125. 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
+ };