@zervonex/baileys 0.0.0-stage → 3.0.2
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 +3206 -3
- 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 +643 -0
- package/lib/Builders/base.js +130 -0
- package/lib/Builders/builders.js +449 -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 +981 -0
- package/lib/Socket/messages-send.js +708 -0
- package/lib/Socket/mex.js +34 -0
- package/lib/Socket/newsletter.js +781 -0
- package/lib/Socket/registration.js +155 -0
- package/lib/Socket/socket.js +797 -0
- package/lib/Socket/spam-report.js +65 -0
- package/lib/Socket/username.js +82 -0
- package/lib/Socket/zervonex-handler.js +55 -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 +317 -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 +38 -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 +752 -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 +135 -0
- package/lib/Utils/sync-action-utils.js +33 -0
- package/lib/Utils/tc-token-utils.js +154 -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 +67 -0
- package/package.json +220 -5
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Boom } from '@hapi/boom'
|
|
2
|
+
import { createHash, randomBytes } from 'crypto'
|
|
3
|
+
import { createRequire } from 'module'
|
|
4
|
+
import { proto, fetchProtoBundle } from '../../WAProto/index.js'
|
|
5
|
+
import { DisconnectReason } from '../Types/index.js'
|
|
6
|
+
import { getAllBinaryNodeChildren, jidDecode } from '../WABinary/index.js'
|
|
7
|
+
import { sha256 } from './crypto.js'
|
|
8
|
+
|
|
9
|
+
export const BufferJSON = {
|
|
10
|
+
replacer: (k, value) => {
|
|
11
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array || value?.type === 'Buffer') return { type: 'Buffer', data: Buffer.from(value?.data || value).toString('base64') }
|
|
12
|
+
return value
|
|
13
|
+
},
|
|
14
|
+
reviver: (_, value) => {
|
|
15
|
+
if (typeof value === 'object' && value !== null && value.type === 'Buffer' && typeof value.data === 'string') return Buffer.from(value.data, 'base64')
|
|
16
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
17
|
+
const keys = Object.keys(value)
|
|
18
|
+
if (keys.length > 0 && keys.every(k => !isNaN(parseInt(k, 10)))) {
|
|
19
|
+
const values = Object.values(value)
|
|
20
|
+
if (values.every(v => typeof v === 'number')) return Buffer.from(values)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return value
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const getKeyAuthor = (key, meId = 'me') => (key?.fromMe ? meId : key?.participantAlt || key?.remoteJidAlt || key?.participant || key?.remoteJid) || ''
|
|
28
|
+
export const isStringNullOrEmpty = value => value == null || value === ''
|
|
29
|
+
|
|
30
|
+
export const writeRandomPadMax16 = msg => { const pad = randomBytes(1); const padLength = (pad[0] & 0x0f) + 1; return Buffer.concat([msg, Buffer.alloc(padLength, padLength)]) }
|
|
31
|
+
export const unpadRandomMax16 = e => { const t = new Uint8Array(e); if (!t.length) throw new Error('unpadPkcs7 given empty bytes'); const r = t[t.length - 1]; if (r > t.length) throw new Error(`unpad given ${t.length} bytes, but pad is ${r}`); return new Uint8Array(t.buffer, t.byteOffset, t.length - r) }
|
|
32
|
+
|
|
33
|
+
export const generateParticipantHashV2 = participants => { participants.sort(); return '2:' + sha256(Buffer.from(participants.join(''))).toString('base64').slice(0, 6) }
|
|
34
|
+
|
|
35
|
+
export const encodeWAMessage = message => {
|
|
36
|
+
try { return writeRandomPadMax16(proto.Message.encode(message?.$type === proto.Message ? message : proto.Message.fromObject(message)).finish()) }
|
|
37
|
+
catch (error) {
|
|
38
|
+
try { return writeRandomPadMax16(proto.Message.encode(message).finish()) }
|
|
39
|
+
catch (e) { console.warn('Message encoding failed:', error.message, e.message); throw new Error(`Failed to encode message: ${error.message}`) }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const generateRegistrationId = () => Uint16Array.from(randomBytes(2))[0] & 16383
|
|
44
|
+
|
|
45
|
+
export const encodeBigEndian = (e, t = 4) => { let r = e; const a = new Uint8Array(t); for (let i = t - 1; i >= 0; i--) { a[i] = 255 & r; r >>>= 8 }; return a }
|
|
46
|
+
|
|
47
|
+
export const toNumber = t => typeof t === 'object' && t ? ('toNumber' in t ? t.toNumber() : t.low) : t || 0
|
|
48
|
+
export const unixTimestampSeconds = (date = new Date()) => Math.floor(date.getTime() / 1000)
|
|
49
|
+
|
|
50
|
+
export const debouncedTimeout = (intervalMs = 1000, task) => {
|
|
51
|
+
let timeout
|
|
52
|
+
return {
|
|
53
|
+
start: (newIntervalMs, newTask) => { task = newTask || task; intervalMs = newIntervalMs || intervalMs; if (timeout) clearTimeout(timeout); timeout = setTimeout(() => task?.(), intervalMs) },
|
|
54
|
+
cancel: () => { if (timeout) clearTimeout(timeout); timeout = undefined },
|
|
55
|
+
setTask: newTask => (task = newTask),
|
|
56
|
+
setInterval: newInterval => (intervalMs = newInterval)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const delay = ms => delayCancellable(ms).delay
|
|
61
|
+
export const delayCancellable = ms => {
|
|
62
|
+
const stack = new Error().stack
|
|
63
|
+
let timeout, reject
|
|
64
|
+
const delay = new Promise((resolve, _reject) => { timeout = setTimeout(resolve, ms); reject = _reject })
|
|
65
|
+
return { delay, cancel: () => { clearTimeout(timeout); reject(new Boom('Cancelled', { statusCode: 500, data: { stack } })) } }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const promiseTimeout = async (ms, promise) => {
|
|
69
|
+
if (!ms) return new Promise(promise)
|
|
70
|
+
const stack = new Error().stack
|
|
71
|
+
const { delay, cancel } = delayCancellable(ms)
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
delay.then(() => reject(new Boom('Timed Out', { statusCode: DisconnectReason.timedOut, data: { stack } }))).catch(reject)
|
|
74
|
+
promise(resolve, reject)
|
|
75
|
+
}).finally(cancel)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const generateMessageIDV2 = userId => {
|
|
79
|
+
const data = Buffer.alloc(8 + 20 + 16)
|
|
80
|
+
data.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 1000)))
|
|
81
|
+
if (userId) { const id = jidDecode(userId); if (id?.user) { data.write(id.user, 8); data.write('@c.us', 8 + id.user.length) } }
|
|
82
|
+
randomBytes(16).copy(data, 28)
|
|
83
|
+
return 'ZERVONEX' + createHash('sha256').update(data).digest().toString('hex').toUpperCase().substring(0, 18)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const generateMessageID = () => 'ZERVONEX' + randomBytes(18).toString('hex').toUpperCase()
|
|
87
|
+
|
|
88
|
+
export const bindWaitForEvent = (ev, event) => async (check, timeoutMs) => {
|
|
89
|
+
let listener, closeListener
|
|
90
|
+
await promiseTimeout(timeoutMs, (resolve, reject) => {
|
|
91
|
+
closeListener = ({ connection, lastDisconnect }) => { if (connection === 'close') reject(lastDisconnect?.error || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })) }
|
|
92
|
+
ev.on('connection.update', closeListener)
|
|
93
|
+
listener = async update => { if (await check(update)) resolve() }
|
|
94
|
+
ev.on(event, listener)
|
|
95
|
+
}).finally(() => { ev.off(event, listener); ev.off('connection.update', closeListener) })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const bindWaitForConnectionUpdate = ev => bindWaitForEvent(ev, 'connection.update')
|
|
99
|
+
|
|
100
|
+
export const fetchLatestWaWebVersion = async () => {
|
|
101
|
+
const { version } = await fetchProtoBundle()
|
|
102
|
+
return { version: [2, 3000, parseInt(version)], isLatest: true }
|
|
103
|
+
}
|
|
104
|
+
export const fetchLatestBaileysVersion = fetchLatestWaWebVersion
|
|
105
|
+
|
|
106
|
+
export const generateMdTagPrefix = () => { const b = randomBytes(4); return `${b.readUInt16BE()}.${b.readUInt16BE(2)}-` }
|
|
107
|
+
|
|
108
|
+
const STATUS_MAP = { sender: proto.WebMessageInfo.Status.SERVER_ACK, played: proto.WebMessageInfo.Status.PLAYED, read: proto.WebMessageInfo.Status.READ, 'read-self': proto.WebMessageInfo.Status.READ }
|
|
109
|
+
export const getStatusFromReceiptType = type => typeof type === 'undefined' ? proto.WebMessageInfo.Status.DELIVERY_ACK : STATUS_MAP[type]
|
|
110
|
+
|
|
111
|
+
const CODE_MAP = { conflict: DisconnectReason.connectionReplaced }
|
|
112
|
+
export const getErrorCodeFromStreamError = node => { const [reasonNode] = getAllBinaryNodeChildren(node); let reason = reasonNode?.tag || 'unknown'; const statusCode = +(node.attrs.code || CODE_MAP[reason] || DisconnectReason.badSession); if (statusCode === DisconnectReason.restartRequired) reason = 'restart required'; return { reason, statusCode } }
|
|
113
|
+
|
|
114
|
+
export const getCallStatusFromNode = ({ tag, attrs }) => {
|
|
115
|
+
switch (tag) {
|
|
116
|
+
case 'offer': case 'offer_notice': return 'offer'
|
|
117
|
+
case 'terminate': return attrs.reason === 'timeout' ? 'timeout' : 'terminate'
|
|
118
|
+
case 'reject': return 'reject'
|
|
119
|
+
case 'accept': return 'accept'
|
|
120
|
+
default: return 'ringing'
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const UNEXPECTED_SERVER_CODE_TEXT = 'Unexpected server response: '
|
|
125
|
+
export const getCodeFromWSError = error => {
|
|
126
|
+
if (error?.message?.includes(UNEXPECTED_SERVER_CODE_TEXT)) { const code = +error.message.slice(UNEXPECTED_SERVER_CODE_TEXT.length); if (!Number.isNaN(code) && code >= 400) return code }
|
|
127
|
+
if (error?.code?.startsWith('E') || error?.message?.includes('timed out')) return 408
|
|
128
|
+
return 500
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const isWABusinessPlatform = platform => platform === 'smbi' || platform === 'smba'
|
|
132
|
+
export const trimUndefined = obj => { for (const key in obj) if (typeof obj[key] === 'undefined') delete obj[key]; return obj }
|
|
133
|
+
|
|
134
|
+
const CROCKFORD_CHARACTERS = '123456789ABCDEFGHJKLMNPQRSTVWXYZ'
|
|
135
|
+
export const bytesToCrockford = buffer => {
|
|
136
|
+
let value = 0, bitCount = 0
|
|
137
|
+
const crockford = []
|
|
138
|
+
for (const element of buffer) { value = (value << 8) | (element & 0xff); bitCount += 8; while (bitCount >= 5) { crockford.push(CROCKFORD_CHARACTERS.charAt((value >>> (bitCount - 5)) & 31)); bitCount -= 5 } }
|
|
139
|
+
if (bitCount > 0) crockford.push(CROCKFORD_CHARACTERS.charAt((value << (5 - bitCount)) & 31))
|
|
140
|
+
return crockford.join('')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const encodeNewsletterMessage = message => proto.Message.encode(message).finish()
|
|
144
|
+
|
|
145
|
+
const _require = createRequire(import.meta.url)
|
|
146
|
+
const Prism = _require('prismjs')
|
|
147
|
+
const loadPrismLanguages = _require('prismjs/components/index')
|
|
148
|
+
let _prismReady = false
|
|
149
|
+
const ensurePrism = () => { if (_prismReady) return; _prismReady = true; try { loadPrismLanguages() } catch { } }
|
|
150
|
+
|
|
151
|
+
const PRISM_TYPE_MAP = {
|
|
152
|
+
keyword: 1, 'keyword control': 1, builtin: 1, important: 1, bold: 1,
|
|
153
|
+
function: 2, 'function-definition': 2, method: 2, 'method-definition': 2,
|
|
154
|
+
string: 3, 'template-string': 3, 'string interpolation': 3, char: 3, regex: 3,
|
|
155
|
+
number: 4, constant: 4, boolean: 4, unit: 4,
|
|
156
|
+
comment: 5, 'block-comment': 5, prolog: 5, doctype: 5
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const flattenPrism = (tokens, out = []) => {
|
|
160
|
+
for (const t of tokens) {
|
|
161
|
+
if (typeof t === 'string') { if (t) out.push({ codeContent: t, highlightType: 0 }); continue }
|
|
162
|
+
const type = PRISM_TYPE_MAP[t.type] ?? 0
|
|
163
|
+
const content = Array.isArray(t.content) ? flattenPrism(t.content, []).map(x => x.codeContent).join('') : String(t.content)
|
|
164
|
+
if (content) out.push({ codeContent: content, highlightType: type })
|
|
165
|
+
}
|
|
166
|
+
return out
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const TYPE_NAMES = ['DEFAULT', 'KEYWORD', 'METHOD', 'STR', 'NUMBER', 'COMMENT']
|
|
170
|
+
export const tokenizeCode = (code, language = 'javascript') => {
|
|
171
|
+
ensurePrism()
|
|
172
|
+
const grammar = Prism.languages[language.toLowerCase()] ?? Prism.languages.javascript
|
|
173
|
+
const raw = flattenPrism(Prism.tokenize(code, grammar))
|
|
174
|
+
const merged = []
|
|
175
|
+
for (const t of raw) { const prev = merged[merged.length - 1]; if (prev && prev.highlightType === t.highlightType) prev.codeContent += t.codeContent; else merged.push({ ...t }) }
|
|
176
|
+
return { codeBlocks: merged, unified_codeBlock: merged.map(t => ({ content: t.codeContent, type: TYPE_NAMES[t.highlightType] ?? 'DEFAULT' })) }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const waitAllPromises = async value => {
|
|
180
|
+
if (value instanceof Promise) return waitAllPromises(await value)
|
|
181
|
+
if (Array.isArray(value)) return Promise.all(value.map(waitAllPromises))
|
|
182
|
+
if (value && typeof value === 'object' && value.constructor === Object) { const keys = Object.keys(value); const vals = await Promise.all(keys.map(k => waitAllPromises(value[k]))); return Object.fromEntries(keys.map((k, i) => [k, vals[i]])) }
|
|
183
|
+
return value
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const extractIE = (text, extraEntities = []) => {
|
|
187
|
+
const entities = [...extraEntities]
|
|
188
|
+
let out = text
|
|
189
|
+
out = out.replace(/\[\[([^:]+):([^\]]+)\]\]/g, (_, key, url) => { entities.push({ __typename: 'GenAITextInlineEntity', key, metadata: { __typename: 'GenAILatexItem', latex_expression: '.', font_height: 83.333, padding: 15, latex_image: { __typename: 'GenAIMediaItem', mime_type: 'image/png', url, url_fallback: url, width: 400, height: 400, expiration_timestamp_ms: Date.now() + 86400000 } } }); return `{{${key}}}.{{/${key}}}` })
|
|
190
|
+
out = out.replace(/\[([^\]]+)\]\(>([^)]+)\)/g, (_, display, deeplink) => { const key = `dl_${entities.length}`; entities.push({ __typename: 'GenAITextInlineEntity', key, metadata: { __typename: 'GenAIDeeplinkInlineEntityMetadata', deeplink, display_text: display } }); return `{{${key}}}${display}{{/${key}}}` })
|
|
191
|
+
out = out.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, (_, display, url) => { const key = `lnk_${entities.length}`; entities.push({ __typename: 'GenAITextInlineEntity', key, metadata: { __typename: 'GenAIHyperlinkInlineEntityMetadata', url, display_text: display } }); return `{{${key}}}${display}{{/${key}}}` })
|
|
192
|
+
return { text: out, inline_entities: entities }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const generateVerificationMetadata = () => ({ proofs: [{ version: 1, useCase: 1, signature: randomBytes(32), certificateChain: [randomBytes(64)], certificateChainSki: [] }] })
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { promisify } from 'util'
|
|
2
|
+
import { inflate } from 'zlib'
|
|
3
|
+
import { proto } from '../../WAProto/index.js'
|
|
4
|
+
import { WAMessageStubType } from '../Types/index.js'
|
|
5
|
+
import { isJidUser } from '../WABinary/index.js'
|
|
6
|
+
import { toNumber } from './generics.js'
|
|
7
|
+
import { normalizeMessageContent } from './messages.js'
|
|
8
|
+
import { downloadContentFromMessage } from './messages-media.js'
|
|
9
|
+
|
|
10
|
+
const inflatePromise = promisify(inflate)
|
|
11
|
+
|
|
12
|
+
export const downloadHistory = async (msg, options) => {
|
|
13
|
+
const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options })
|
|
14
|
+
const chunks = []
|
|
15
|
+
for await (const chunk of stream) chunks.push(chunk)
|
|
16
|
+
return proto.HistorySync.decode(await inflatePromise(Buffer.concat(chunks)))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const processHistoryMessage = item => {
|
|
20
|
+
const messages = [], contacts = [], chats = []
|
|
21
|
+
switch (item.syncType) {
|
|
22
|
+
case proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP:
|
|
23
|
+
case proto.HistorySync.HistorySyncType.RECENT:
|
|
24
|
+
case proto.HistorySync.HistorySyncType.FULL:
|
|
25
|
+
case proto.HistorySync.HistorySyncType.ON_DEMAND:
|
|
26
|
+
for (const chat of (item.conversations ?? [])) {
|
|
27
|
+
contacts.push({ id: chat.id, name: chat.name || undefined, lid: chat.lidJid || undefined, phoneNumber: chat.pnJid || undefined, jid: isJidUser(chat.id) ? chat.id : undefined })
|
|
28
|
+
const msgs = chat.messages || []
|
|
29
|
+
delete chat.messages; delete chat.archived; delete chat.muteEndTime; delete chat.pinned
|
|
30
|
+
for (const item of msgs) {
|
|
31
|
+
const { message } = item
|
|
32
|
+
messages.push(message)
|
|
33
|
+
if (!chat.messages?.length) chat.messages = [{ message }]
|
|
34
|
+
if (!message.key.fromMe && !chat.lastMessageRecvTimestamp) chat.lastMessageRecvTimestamp = toNumber(message.messageTimestamp)
|
|
35
|
+
if ((message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_BSP || message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_FB) && message.messageStubParameters?.[0]) contacts.push({ id: message.key.participant || message.key.remoteJid, verifiedName: message.messageStubParameters[0] })
|
|
36
|
+
}
|
|
37
|
+
if (isJidUser(chat.id) && chat.readOnly && chat.archived) delete chat.readOnly
|
|
38
|
+
chats.push({ ...chat })
|
|
39
|
+
}
|
|
40
|
+
break
|
|
41
|
+
case proto.HistorySync.HistorySyncType.PUSH_NAME:
|
|
42
|
+
for (const c of (item.pushnames ?? [])) contacts.push({ id: c.id, notify: c.pushname })
|
|
43
|
+
for (const c of (item.inlineContacts ?? [])) { if (c.id) contacts.push({ id: c.id, name: c.fullName || c.firstName || undefined, notify: c.pushName || undefined, lid: c.lidJid || undefined, phoneNumber: c.pnJid || undefined }) }
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
return { chats, contacts, messages, syncType: item.syncType, progress: item.progress }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const downloadAndProcessHistorySyncNotification = async (msg, options) => processHistoryMessage(await downloadHistory(msg, options))
|
|
50
|
+
|
|
51
|
+
export const getHistoryMsg = message => message ? normalizeMessageContent(message)?.protocolMessage?.historySyncNotification : undefined
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { areJidsSameUser, getBinaryNodeChild, jidDecode } from '../WABinary/index.js'
|
|
2
|
+
import { isStringNullOrEmpty } from './generics.js'
|
|
3
|
+
|
|
4
|
+
export async function handleIdentityChange(node, ctx) {
|
|
5
|
+
const from = node.attrs.from
|
|
6
|
+
if (!from) return { action: 'invalid_notification' }
|
|
7
|
+
if (!getBinaryNodeChild(node, 'identity')) return { action: 'no_identity_node' }
|
|
8
|
+
ctx.logger.info({ jid: from }, 'identity changed')
|
|
9
|
+
const decoded = jidDecode(from)
|
|
10
|
+
if (decoded?.device && decoded.device !== 0) { ctx.logger.debug({ jid: from, device: decoded.device }, 'ignoring identity change from companion device'); return { action: 'skipped_companion_device', device: decoded.device } }
|
|
11
|
+
if (ctx.meId && (areJidsSameUser(from, ctx.meId) || (ctx.meLid && areJidsSameUser(from, ctx.meLid)))) { ctx.logger.info({ jid: from }, 'self primary identity changed'); return { action: 'skipped_self_primary' } }
|
|
12
|
+
if (ctx.debounceCache.get(from)) { ctx.logger.debug({ jid: from }, 'skipping identity assert (debounced)'); return { action: 'debounced' } }
|
|
13
|
+
ctx.debounceCache.set(from, true)
|
|
14
|
+
if (!(await ctx.validateSession(from)).exists) { ctx.logger.debug({ jid: from }, 'no old session, skipping session refresh'); return { action: 'skipped_no_session' } }
|
|
15
|
+
ctx.logger.debug({ jid: from }, 'old session exists, will refresh session')
|
|
16
|
+
if (!isStringNullOrEmpty(node.attrs.offline)) { ctx.logger.debug({ jid: from }, 'skipping session refresh during offline processing'); return { action: 'skipped_offline' } }
|
|
17
|
+
try { await ctx.assertSessions([from], true); return { action: 'session_refreshed' } }
|
|
18
|
+
catch (error) { ctx.logger.warn({ error, jid: from }, 'failed to assert sessions after identity change'); return { action: 'session_refresh_failed', error } }
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export * from './generics.js'
|
|
2
|
+
export * from './decode-wa-message.js'
|
|
3
|
+
export * from './messages.js'
|
|
4
|
+
export * from './messages-media.js'
|
|
5
|
+
export * from './validate-connection.js'
|
|
6
|
+
export * from './crypto.js'
|
|
7
|
+
export * from './signal.js'
|
|
8
|
+
export * from './make-mutex.js'
|
|
9
|
+
export * from './logger.js'
|
|
10
|
+
export * from './noise-handler.js'
|
|
11
|
+
export * from './history.js'
|
|
12
|
+
export * from './chat-utils.js'
|
|
13
|
+
export * from './lt-hash.js'
|
|
14
|
+
export * from './auth-utils.js'
|
|
15
|
+
export * from './baileys-event-stream.js'
|
|
16
|
+
export * from './use-multi-file-auth-state.js'
|
|
17
|
+
export * from './link-preview.js'
|
|
18
|
+
export * from './event-buffer.js'
|
|
19
|
+
export * from './process-message.js'
|
|
20
|
+
export * from './message-retry-manager.js'
|
|
21
|
+
export * from './browser-utils.js'
|
|
22
|
+
export * from './identity-change-handler.js'
|
|
23
|
+
export * from './reporting-utils.js'
|
|
24
|
+
export * from './companion-reg-client-utils.js'
|
|
25
|
+
export * from './tc-token-utils.js'
|
|
26
|
+
export * from './meta-ai-msmsg.js'
|
|
27
|
+
export * from './spam-report-utils.js'
|
|
28
|
+
export * from './modern-messages.js'
|
|
29
|
+
export * from './extras.js'
|
|
30
|
+
export * from './native-flow.js'
|
|
31
|
+
export * from './consumer-application.js'
|
|
32
|
+
export * from './bot-capabilities.js'
|
|
33
|
+
export * from './meta-compositing.js'
|
|
34
|
+
export * from './bot-planning-replay.js'
|
|
35
|
+
export * from './message-composer.js'
|
|
36
|
+
export * from './business.js'
|
|
37
|
+
export * from './pre-key-manager.js'
|
|
38
|
+
export * from './sync-action-utils.js'
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { unfurl } from 'unfurl.js'
|
|
2
|
+
import { prepareWAMessageMedia } from './messages.js'
|
|
3
|
+
import { extractImageThumb, getHttpStream } from './messages-media.js'
|
|
4
|
+
|
|
5
|
+
const THUMBNAIL_WIDTH = 192
|
|
6
|
+
const TIMEOUT = 8_000
|
|
7
|
+
const MAX_INFLIGHT = 1000
|
|
8
|
+
const _inflight = new Map()
|
|
9
|
+
|
|
10
|
+
const _normalize = text => { const t = text.trim(); return /^https?:\/\//i.test(t) ? t : `https://${t}` }
|
|
11
|
+
const _extractImage = data => data?.open_graph?.images?.[0]?.url ?? data?.twitter_card?.images?.[0]?.url ?? data?.oEmbed?.thumbnails?.[0]?.url ?? data?.favicon ?? null
|
|
12
|
+
const _previewType = image => image ? 5 : 0
|
|
13
|
+
const _compressedThumb = async (url, opts) => (await extractImageThumb(await getHttpStream(url, opts.fetchOpts), opts.thumbnailWidth ?? THUMBNAIL_WIDTH)).buffer
|
|
14
|
+
|
|
15
|
+
const _resolveThumbnail = async (image, opts) => {
|
|
16
|
+
if (!image) return {}
|
|
17
|
+
if (opts.uploadImage) {
|
|
18
|
+
try {
|
|
19
|
+
const { imageMessage } = await prepareWAMessageMedia({ image: { url: image } }, { upload: opts.uploadImage, mediaTypeOverride: 'thumbnail-link', options: opts.fetchOpts })
|
|
20
|
+
const jpeg = imageMessage?.jpegThumbnail ? Buffer.from(imageMessage.jpegThumbnail) : await _compressedThumb(image, opts).catch(() => undefined)
|
|
21
|
+
return { jpegThumbnail: jpeg, highQualityThumbnail: imageMessage ?? undefined }
|
|
22
|
+
} catch { }
|
|
23
|
+
}
|
|
24
|
+
try { return { jpegThumbnail: await _compressedThumb(image, opts) } } catch { return {} }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const _fetchMeta = async (url, opts) => {
|
|
28
|
+
try {
|
|
29
|
+
const data = await unfurl(url, { timeout: opts.fetchOpts?.timeout ?? TIMEOUT })
|
|
30
|
+
const title = (data?.open_graph?.title !== data?.title ? data?.open_graph?.title : data?.title) ?? data?.oEmbed?.title
|
|
31
|
+
if (!title) return undefined
|
|
32
|
+
return { url: data?.open_graph?.url ?? url, title, description: data?.open_graph?.description ?? data?.description ?? data?.oEmbed?.author_name ?? '', image: _extractImage(data) }
|
|
33
|
+
} catch (err) { opts.logger?.warn({ err: err?.message || err, url }, 'unfurl failed'); return undefined }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const _buildResult = async (meta, text, opts) => ({
|
|
37
|
+
'canonical-url': meta.url, 'matched-text': text,
|
|
38
|
+
title: meta.title, description: meta.description,
|
|
39
|
+
originalThumbnailUrl: meta.image, previewType: _previewType(meta.image),
|
|
40
|
+
...await _resolveThumbnail(meta.image, opts)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const getUrlInfo = (text, opts = {}) => {
|
|
44
|
+
const url = _normalize(text)
|
|
45
|
+
if (_inflight.has(url)) return _inflight.get(url)
|
|
46
|
+
if (_inflight.size >= MAX_INFLIGHT) return Promise.resolve(undefined)
|
|
47
|
+
const o = { fetchOpts: { timeout: TIMEOUT, ...opts.fetchOpts }, thumbnailWidth: opts.thumbnailWidth ?? THUMBNAIL_WIDTH, uploadImage: opts.uploadImage, logger: opts.logger }
|
|
48
|
+
const promise = (async () => { const meta = await _fetchMeta(url, o); return meta ? _buildResult(meta, text, o) : undefined })().finally(() => _inflight.delete(url))
|
|
49
|
+
_inflight.set(url, promise)
|
|
50
|
+
return promise
|
|
51
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const makeMutex = () => {
|
|
2
|
+
let task = Promise.resolve()
|
|
3
|
+
return {
|
|
4
|
+
mutex(code) {
|
|
5
|
+
task = task.catch(() => { }).then(code)
|
|
6
|
+
return task
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const makeKeyedMutex = () => {
|
|
12
|
+
const map = {}
|
|
13
|
+
return {
|
|
14
|
+
mutex(key, code) { if (!map[key]) map[key] = makeMutex(); return map[key].mutex(code) }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { generateMessageIDV2, tokenizeCode } from './generics.js'
|
|
2
|
+
|
|
3
|
+
const buildRichContextInfo = (quoted, options = {}) => {
|
|
4
|
+
const ctx = { forwardingScore: 1, isForwarded: true, forwardedAiBotMessageInfo: { botJid: options.botJid || '867051314767696@bot' }, forwardOrigin: 4, ...(options.mentions ? { mentionedJid: options.mentions } : {}) }
|
|
5
|
+
if (quoted?.key) { ctx.stanzaId = quoted.key.id; ctx.participant = quoted.key.participant || quoted.sender || quoted.key.remoteJid; ctx.quotedMessage = quoted.message }
|
|
6
|
+
return ctx
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const buildBotForwardedMessage = (submessages, contextInfo, unifiedResponse) => {
|
|
10
|
+
const rich = { messageType: 1, submessages, contextInfo }
|
|
11
|
+
if (unifiedResponse) rich.unifiedResponse = unifiedResponse
|
|
12
|
+
return { botForwardedMessage: { message: { richResponseMessage: rich } } }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const wrap = (sub, quoted, options) => ({ message: buildBotForwardedMessage(sub, buildRichContextInfo(quoted, options)), messageId: generateMessageIDV2() })
|
|
16
|
+
const text = msg => ({ messageType: 2, messageText: msg })
|
|
17
|
+
const table = (title, rows) => ({ messageType: 4, tableMetadata: { title, rows } })
|
|
18
|
+
|
|
19
|
+
export const generateTableContent = (title, headers, rows, quoted, options = {}) => {
|
|
20
|
+
const { footer, headerText } = options
|
|
21
|
+
const sub = [...(headerText ? [text(headerText)] : []), table(title, [{ items: headers, isHeading: true }, ...rows.map(row => ({ items: row.map(String) }))]), ...(footer ? [text(footer)] : [])]
|
|
22
|
+
return wrap(sub, quoted, options)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const generateListContent = (title, items, quoted, options = {}) => {
|
|
26
|
+
const { footer, headerText } = options
|
|
27
|
+
const sub = [...(headerText ? [text(headerText)] : []), table(title, items.map(item => ({ items: Array.isArray(item) ? item.map(String) : [String(item)] }))), ...(footer ? [text(footer)] : [])]
|
|
28
|
+
return wrap(sub, quoted, options)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const generateCodeBlockContent = (code, quoted, options = {}) => {
|
|
32
|
+
const { title, footer, language = 'javascript' } = options
|
|
33
|
+
const sub = [...(title ? [text(title)] : []), { messageType: 5, codeMetadata: { codeLanguage: language, codeBlocks: tokenizeCode(code, language) } }, ...(footer ? [text(footer)] : [])]
|
|
34
|
+
return wrap(sub, quoted, options)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const generateLatexContent = (quoted, options = {}) => {
|
|
38
|
+
const { text: txt, expressions = [], headerText, footer } = options
|
|
39
|
+
const latexExpressions = expressions.map(({ latexExpression, url, width, height, fontHeight, imageTopPadding, imageLeadingPadding, imageBottomPadding, imageTrailingPadding }) => {
|
|
40
|
+
const e = { latexExpression, url, width, height }
|
|
41
|
+
if (fontHeight !== undefined) e.fontHeight = fontHeight
|
|
42
|
+
if (imageTopPadding !== undefined) e.imageTopPadding = imageTopPadding
|
|
43
|
+
if (imageLeadingPadding !== undefined) e.imageLeadingPadding = imageLeadingPadding
|
|
44
|
+
if (imageBottomPadding !== undefined) e.imageBottomPadding = imageBottomPadding
|
|
45
|
+
if (imageTrailingPadding !== undefined) e.imageTrailingPadding = imageTrailingPadding
|
|
46
|
+
return e
|
|
47
|
+
})
|
|
48
|
+
const sub = [...(headerText ? [text(headerText)] : []), { messageType: 8, latexMetadata: { text: txt || '', expressions: latexExpressions } }, ...(footer ? [text(footer)] : [])]
|
|
49
|
+
return wrap(sub, quoted, options)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const captureUnifiedResponse = msg => {
|
|
53
|
+
const rich = msg?.botForwardedMessage?.message?.richResponseMessage
|
|
54
|
+
if (!rich?.unifiedResponse?.data) return null
|
|
55
|
+
return { unifiedResponse: { data: rich.unifiedResponse.data }, submessages: rich.submessages || [], contextInfo: rich.contextInfo || {} }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const generateUnifiedResponseContent = (quoted, captured) => wrap(captured.submessages, quoted, { unifiedResponse: captured.unifiedResponse })
|
|
59
|
+
export const generateRichMessageContent = (submessages, quoted, options) => wrap(submessages, quoted, options)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { LRUCache } from 'lru-cache'
|
|
2
|
+
|
|
3
|
+
const RECENT_MESSAGES_SIZE = 512
|
|
4
|
+
const RECREATE_SESSION_TIMEOUT = 60 * 60 * 1000
|
|
5
|
+
const PHONE_REQUEST_DELAY = 3000
|
|
6
|
+
|
|
7
|
+
export class MessageRetryManager {
|
|
8
|
+
constructor(logger, maxMsgRetryCount) {
|
|
9
|
+
this.logger = logger
|
|
10
|
+
this.maxMsgRetryCount = maxMsgRetryCount
|
|
11
|
+
this.recentMessagesMap = new LRUCache({ max: RECENT_MESSAGES_SIZE })
|
|
12
|
+
this.sessionRecreateHistory = new LRUCache({ ttl: RECREATE_SESSION_TIMEOUT * 2, ttlAutopurge: true })
|
|
13
|
+
this.retryCounters = new LRUCache({ ttl: 15 * 60 * 1000, ttlAutopurge: true, updateAgeOnGet: true })
|
|
14
|
+
this.pendingPhoneRequests = {}
|
|
15
|
+
this.statistics = { totalRetries: 0, successfulRetries: 0, failedRetries: 0, mediaRetries: 0, sessionRecreations: 0, phoneRequests: 0 }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
keyToString(key) { return `${key.to}:${key.id}` }
|
|
19
|
+
|
|
20
|
+
addRecentMessage(to, id, message) { this.recentMessagesMap.set(this.keyToString({ to, id }), { message, timestamp: Date.now() }) }
|
|
21
|
+
getRecentMessage(to, id) { return this.recentMessagesMap.get(this.keyToString({ to, id })) }
|
|
22
|
+
|
|
23
|
+
shouldRecreateSession(jid, retryCount, hasSession) {
|
|
24
|
+
if (!hasSession) { this.sessionRecreateHistory.set(jid, Date.now()); this.statistics.sessionRecreations++; return { reason: "we don't have a Signal session with them", recreate: true } }
|
|
25
|
+
if (retryCount < 2) return { reason: '', recreate: false }
|
|
26
|
+
const now = Date.now()
|
|
27
|
+
const prevTime = this.sessionRecreateHistory.get(jid)
|
|
28
|
+
if (!prevTime || now - prevTime > RECREATE_SESSION_TIMEOUT) { this.sessionRecreateHistory.set(jid, now); this.statistics.sessionRecreations++; return { reason: 'retry count > 1 and over an hour since last recreation', recreate: true } }
|
|
29
|
+
return { reason: '', recreate: false }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
incrementRetryCount(messageId) { this.retryCounters.set(messageId, (this.retryCounters.get(messageId) || 0) + 1); this.statistics.totalRetries++; return this.retryCounters.get(messageId) }
|
|
33
|
+
getRetryCount(messageId) { return this.retryCounters.get(messageId) || 0 }
|
|
34
|
+
hasExceededMaxRetries(messageId) { return this.getRetryCount(messageId) >= this.maxMsgRetryCount }
|
|
35
|
+
|
|
36
|
+
markRetrySuccess(messageId) { this.statistics.successfulRetries++; this.retryCounters.delete(messageId); this.cancelPendingPhoneRequest(messageId) }
|
|
37
|
+
markRetryFailed(messageId) { this.statistics.failedRetries++; this.retryCounters.delete(messageId) }
|
|
38
|
+
|
|
39
|
+
schedulePhoneRequest(messageId, callback, delay = PHONE_REQUEST_DELAY) {
|
|
40
|
+
this.cancelPendingPhoneRequest(messageId)
|
|
41
|
+
this.pendingPhoneRequests[messageId] = setTimeout(() => { delete this.pendingPhoneRequests[messageId]; this.statistics.phoneRequests++; callback() }, delay)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
cancelPendingPhoneRequest(messageId) {
|
|
45
|
+
const timeout = this.pendingPhoneRequests[messageId]
|
|
46
|
+
if (timeout) { clearTimeout(timeout); delete this.pendingPhoneRequests[messageId] }
|
|
47
|
+
}
|
|
48
|
+
}
|