@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,307 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, stat, unlink, writeFile, readdir } from 'fs/promises'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import { Keyv } from 'keyv'
|
|
4
|
+
import { proto } from '../../WAProto/index.js'
|
|
5
|
+
import { initAuthCreds } from './auth-utils.js'
|
|
6
|
+
import { BufferJSON } from './generics.js'
|
|
7
|
+
import { PROTOCOL_ADAPTERS } from '../WABinary/index.js'
|
|
8
|
+
|
|
9
|
+
const CURRENT_VERSION = 1
|
|
10
|
+
const DEFAULT_PREKEY_RETENTION = 150
|
|
11
|
+
const DEFAULT_CLEANUP_THRESHOLD = 50
|
|
12
|
+
const CLEANUP_INTERVAL_MS = 10 * 60 * 1000
|
|
13
|
+
|
|
14
|
+
const bootstrapCreds = (raw) => {
|
|
15
|
+
const c = raw ?? initAuthCreds()
|
|
16
|
+
if (!c.__version || c.__version < CURRENT_VERSION) c.__version = CURRENT_VERSION
|
|
17
|
+
return c
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const patchAppStateKey = (type, v) =>
|
|
21
|
+
type === 'app-state-sync-key' && v ? proto.Message.AppStateSyncKeyData.fromObject(v) : v
|
|
22
|
+
|
|
23
|
+
// ── File-based ────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export const useMultiFileAuthState = async (folder, options = {}) => {
|
|
26
|
+
const { preKeyRetention = DEFAULT_PREKEY_RETENTION, cleanupThreshold = DEFAULT_CLEANUP_THRESHOLD, logger } = options
|
|
27
|
+
|
|
28
|
+
const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-')
|
|
29
|
+
const filePath = (file) => join(folder, fixFileName(file))
|
|
30
|
+
const tmpPath = (file) => filePath(file) + '.tmp'
|
|
31
|
+
|
|
32
|
+
const folderInfo = await stat(folder).catch(() => null)
|
|
33
|
+
if (folderInfo) {
|
|
34
|
+
if (!folderInfo.isDirectory()) throw new Error(`Path exists but is not a directory: ${folder}`)
|
|
35
|
+
} else {
|
|
36
|
+
await mkdir(folder, { recursive: true })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const writeData = async (data, file) => {
|
|
40
|
+
const fp = filePath(file)
|
|
41
|
+
const tp = tmpPath(file)
|
|
42
|
+
const payload = JSON.stringify(data, BufferJSON.replacer)
|
|
43
|
+
await writeFile(tp, payload)
|
|
44
|
+
try {
|
|
45
|
+
await rename(tp, fp)
|
|
46
|
+
} catch {
|
|
47
|
+
await writeFile(fp, payload)
|
|
48
|
+
await unlink(tp).catch(() => { })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const readData = async (file) => {
|
|
53
|
+
const raw = await readFile(filePath(file), { encoding: 'utf-8' }).catch(() => null)
|
|
54
|
+
if (!raw) return null
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(raw, BufferJSON.reviver)
|
|
57
|
+
if (parsed && typeof parsed === 'object' && '__checksum' in parsed && 'data' in parsed) return parsed.data
|
|
58
|
+
return parsed
|
|
59
|
+
} catch (err) {
|
|
60
|
+
logger?.warn({ file, err: err.message }, 'failed to read auth file — reinitializing')
|
|
61
|
+
await unlink(filePath(file)).catch(() => { })
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const removeData = (file) => unlink(filePath(file)).catch(() => { })
|
|
67
|
+
|
|
68
|
+
let creds = bootstrapCreds(await readData('creds.json'))
|
|
69
|
+
let cleanupRunning = false
|
|
70
|
+
let lastCleanupAt = 0
|
|
71
|
+
let lastCleanedPreKeyId = creds.nextPreKeyId
|
|
72
|
+
|
|
73
|
+
const cleanOldPreKeys = async () => {
|
|
74
|
+
const now = Date.now()
|
|
75
|
+
if (cleanupRunning || now - lastCleanupAt < CLEANUP_INTERVAL_MS) return
|
|
76
|
+
cleanupRunning = true
|
|
77
|
+
try {
|
|
78
|
+
const minId = creds.nextPreKeyId - preKeyRetention
|
|
79
|
+
if (minId <= 0) return
|
|
80
|
+
const targets = (await readdir(folder))
|
|
81
|
+
.map(f => f.match(/^pre-key-(\d+)\.json(\.tmp)?$/))
|
|
82
|
+
.filter(m => m && parseInt(m[1], 10) < minId)
|
|
83
|
+
.map(m => join(folder, m[0]))
|
|
84
|
+
if (!targets.length) return
|
|
85
|
+
await Promise.all(targets.map(f => unlink(f).catch(() => { })))
|
|
86
|
+
lastCleanupAt = Date.now()
|
|
87
|
+
lastCleanedPreKeyId = creds.nextPreKeyId
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger?.warn({ err }, 'prekey cleanup failed')
|
|
90
|
+
} finally {
|
|
91
|
+
cleanupRunning = false
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
cleanOldPreKeys().catch(() => { })
|
|
96
|
+
|
|
97
|
+
const getStats = async () => {
|
|
98
|
+
const files = await readdir(folder).catch(() => [])
|
|
99
|
+
const preKeyFiles = files.filter(f => /^pre-key-\d+\.json$/.test(f))
|
|
100
|
+
return {
|
|
101
|
+
totalFiles: files.length,
|
|
102
|
+
preKeyCount: preKeyFiles.length,
|
|
103
|
+
nextPreKeyId: creds.nextPreKeyId,
|
|
104
|
+
lastCleanupAt: lastCleanupAt ? new Date(lastCleanupAt).toISOString() : null,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
state: {
|
|
110
|
+
creds,
|
|
111
|
+
keys: {
|
|
112
|
+
get: async (type, ids) => {
|
|
113
|
+
const data = {}
|
|
114
|
+
await Promise.all(ids.map(async id => {
|
|
115
|
+
let value = await readData(`${type}-${id}.json`)
|
|
116
|
+
if (type === 'app-state-sync-key' && value) value = proto.Message.AppStateSyncKeyData.fromObject(value)
|
|
117
|
+
data[id] = value
|
|
118
|
+
}))
|
|
119
|
+
return data
|
|
120
|
+
},
|
|
121
|
+
set: async (data) => {
|
|
122
|
+
const tasks = []
|
|
123
|
+
for (const category in data) {
|
|
124
|
+
for (const id in data[category]) {
|
|
125
|
+
const value = data[category][id]
|
|
126
|
+
tasks.push(value ? writeData(value, `${category}-${id}.json`) : removeData(`${category}-${id}.json`))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
await Promise.all(tasks)
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
saveCreds: async () => {
|
|
134
|
+
if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) cleanOldPreKeys().catch(() => { })
|
|
135
|
+
return writeData(creds, 'creds.json')
|
|
136
|
+
},
|
|
137
|
+
getStats,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── Keyv-backed ───────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
const ser = (v) => v == null ? null : JSON.parse(JSON.stringify(v, BufferJSON.replacer))
|
|
144
|
+
|
|
145
|
+
const unwrapNestedValue = (obj) => {
|
|
146
|
+
let cur = obj
|
|
147
|
+
while (cur && typeof cur === 'object' && !Array.isArray(cur) && Object.keys(cur).length === 1 && 'value' in cur) cur = cur.value
|
|
148
|
+
return cur
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const deser = (raw, logger) => {
|
|
152
|
+
if (raw == null) return null
|
|
153
|
+
let parsed
|
|
154
|
+
if (typeof raw === 'string') {
|
|
155
|
+
try { parsed = JSON.parse(raw) } catch (err) {
|
|
156
|
+
logger?.warn?.({ err: err.message }, '[Auth] failed to parse stored value — treating as missing')
|
|
157
|
+
return null
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
parsed = raw
|
|
161
|
+
}
|
|
162
|
+
parsed = unwrapNestedValue(parsed)
|
|
163
|
+
if (parsed == null) return null
|
|
164
|
+
if (typeof parsed === 'object' && '__checksum' in parsed && 'data' in parsed) parsed = parsed.data
|
|
165
|
+
return JSON.parse(JSON.stringify(parsed), BufferJSON.reviver)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const COLLECTION_OPTION_KEY = {
|
|
169
|
+
'@keyv/mongo': 'collection',
|
|
170
|
+
'@keyv/postgres': 'table',
|
|
171
|
+
'@keyv/mysql': 'table',
|
|
172
|
+
'@keyv/sqlite': 'table',
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const _adapterCache = new Map()
|
|
176
|
+
export const clearAdapterCache = () => _adapterCache.clear()
|
|
177
|
+
|
|
178
|
+
const resolveAdapter = async (connectionString, collectionName) => {
|
|
179
|
+
const cacheKey = collectionName ? `${connectionString}::${collectionName}` : connectionString
|
|
180
|
+
if (_adapterCache.has(cacheKey)) return _adapterCache.get(cacheKey)
|
|
181
|
+
|
|
182
|
+
let protocol
|
|
183
|
+
try { protocol = new URL(connectionString).protocol } catch { throw new Error(`[Auth] Invalid connection string: "${connectionString}"`) }
|
|
184
|
+
|
|
185
|
+
const pkg = PROTOCOL_ADAPTERS[protocol]
|
|
186
|
+
if (!pkg) throw new Error(`[Auth] Unsupported protocol "${protocol}" — supported: redis, mongodb, postgresql, mysql, sqlite, etcd, memcache. Pass a pre-built KeyvStoreAdapter instance for anything else.`)
|
|
187
|
+
|
|
188
|
+
let mod
|
|
189
|
+
try { mod = await import(pkg) } catch (e) {
|
|
190
|
+
if (e.code === 'ERR_MODULE_NOT_FOUND') throw new Error(`[Auth] "${pkg}" is required for this backend — install it: npm i ${pkg}`)
|
|
191
|
+
throw e
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const Adapter = mod.default ?? mod[Object.keys(mod)[0]]
|
|
195
|
+
const optionKey = COLLECTION_OPTION_KEY[pkg]
|
|
196
|
+
const adapter = (collectionName && optionKey)
|
|
197
|
+
? new Adapter({ url: connectionString, uri: connectionString, [optionKey]: collectionName })
|
|
198
|
+
: new Adapter(connectionString)
|
|
199
|
+
adapter.setMaxListeners?.(0)
|
|
200
|
+
_adapterCache.set(cacheKey, adapter)
|
|
201
|
+
return adapter
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const isAdapterLike = (v) =>
|
|
205
|
+
v != null &&
|
|
206
|
+
typeof v === 'object' &&
|
|
207
|
+
typeof v.get === 'function' &&
|
|
208
|
+
typeof v.set === 'function' &&
|
|
209
|
+
typeof v.delete === 'function' &&
|
|
210
|
+
typeof v.clear === 'function'
|
|
211
|
+
|
|
212
|
+
const resolveStore = async (backend, collectionName) => {
|
|
213
|
+
if (backend == null) return null
|
|
214
|
+
if (typeof backend === 'string') return resolveAdapter(backend, collectionName)
|
|
215
|
+
if (backend instanceof Keyv) return backend.opts?.store ?? backend.store ?? null
|
|
216
|
+
if (isAdapterLike(backend)) return backend
|
|
217
|
+
throw new Error('[Auth] backend must be a connection string, Keyv instance, or KeyvStoreAdapter')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const useKeyvAuthState = async (sessionId, backend, opts = {}) => {
|
|
221
|
+
const { preKeyRetention = DEFAULT_PREKEY_RETENTION, cleanupThreshold = DEFAULT_CLEANUP_THRESHOLD, logger, collectionName } = opts
|
|
222
|
+
|
|
223
|
+
const store = await resolveStore(backend, collectionName)
|
|
224
|
+
const keyv = store ? new Keyv({ store, namespace: sessionId }) : new Keyv({ namespace: sessionId })
|
|
225
|
+
keyv.on('error', (err) => logger?.warn?.({ err: err?.message }, '[Auth] store error'))
|
|
226
|
+
|
|
227
|
+
const keyName = (type, id) => `${type}-${id}`
|
|
228
|
+
|
|
229
|
+
let creds = bootstrapCreds(deser(await keyv.get('creds'), logger))
|
|
230
|
+
let cleanupRunning = false
|
|
231
|
+
let lastCleanupAt = 0
|
|
232
|
+
let lastCleanedPreKeyId = creds.nextPreKeyId
|
|
233
|
+
|
|
234
|
+
const cleanOldPreKeys = async () => {
|
|
235
|
+
const now = Date.now()
|
|
236
|
+
if (cleanupRunning || now - lastCleanupAt < CLEANUP_INTERVAL_MS) return
|
|
237
|
+
cleanupRunning = true
|
|
238
|
+
try {
|
|
239
|
+
const minId = creds.nextPreKeyId - preKeyRetention
|
|
240
|
+
if (minId <= 0) return
|
|
241
|
+
const ids = []
|
|
242
|
+
for (let id = Math.max(0, lastCleanedPreKeyId - preKeyRetention); id < minId; id++) ids.push(keyName('pre-key', id))
|
|
243
|
+
if (!ids.length) return
|
|
244
|
+
await Promise.all(ids.map(k => keyv.delete(k).catch(() => { })))
|
|
245
|
+
lastCleanupAt = Date.now()
|
|
246
|
+
lastCleanedPreKeyId = creds.nextPreKeyId
|
|
247
|
+
} catch (e) {
|
|
248
|
+
logger?.warn?.({ err: e.message }, '[Auth] prekey cleanup failed')
|
|
249
|
+
} finally {
|
|
250
|
+
cleanupRunning = false
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
cleanOldPreKeys().catch(() => { })
|
|
255
|
+
|
|
256
|
+
const keys = {
|
|
257
|
+
get: async (type, ids) => {
|
|
258
|
+
const data = {}
|
|
259
|
+
if (!ids.length) return data
|
|
260
|
+
const rawKeys = ids.map(id => keyName(type, id))
|
|
261
|
+
const rawValues = await keyv.getMany(rawKeys)
|
|
262
|
+
const rewrites = []
|
|
263
|
+
ids.forEach((id, i) => {
|
|
264
|
+
data[id] = patchAppStateKey(type, deser(rawValues[i], logger))
|
|
265
|
+
})
|
|
266
|
+
if (rewrites.length) await Promise.all(rewrites).catch(() => { })
|
|
267
|
+
return data
|
|
268
|
+
},
|
|
269
|
+
set: async (data) => {
|
|
270
|
+
const setEntries = []
|
|
271
|
+
const delKeys = []
|
|
272
|
+
for (const type in data) {
|
|
273
|
+
for (const id in data[type]) {
|
|
274
|
+
const value = data[type][id]
|
|
275
|
+
const key = keyName(type, id)
|
|
276
|
+
if (value != null) setEntries.push({ key, value: ser(value) })
|
|
277
|
+
else delKeys.push(key)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
await Promise.all([
|
|
281
|
+
setEntries.length ? keyv.setMany(setEntries) : null,
|
|
282
|
+
delKeys.length ? keyv.deleteMany(delKeys) : null,
|
|
283
|
+
])
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const saveCreds = async () => {
|
|
288
|
+
if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) cleanOldPreKeys().catch(() => { })
|
|
289
|
+
await keyv.set('creds', ser(creds))
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const clearSession = async () => {
|
|
293
|
+
const s = keyv.opts?.store ?? keyv.store
|
|
294
|
+
if (s && typeof s.deleteMany === 'function' && typeof s.iterator === 'function') {
|
|
295
|
+
const toDelete = []
|
|
296
|
+
for await (const [key] of s.iterator(sessionId)) toDelete.push(key)
|
|
297
|
+
if (toDelete.length) await s.deleteMany(toDelete)
|
|
298
|
+
} else {
|
|
299
|
+
await keyv.clear()
|
|
300
|
+
}
|
|
301
|
+
keyv.removeAllListeners?.()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { state: { creds, keys }, saveCreds, clearSession }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export { Keyv }
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Boom } from '@hapi/boom'
|
|
2
|
+
import { createHash } from 'crypto'
|
|
3
|
+
import { proto } from '../../WAProto/index.js'
|
|
4
|
+
import { KEY_BUNDLE_TYPE, WA_ADV_ACCOUNT_SIG_PREFIX, WA_ADV_DEVICE_SIG_PREFIX, WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX } from '../Defaults/index.js'
|
|
5
|
+
import { getBinaryNodeChild, jidDecode, S_WHATSAPP_NET } from '../WABinary/index.js'
|
|
6
|
+
import { Curve, hmacSign } from './crypto.js'
|
|
7
|
+
import { encodeBigEndian } from './generics.js'
|
|
8
|
+
import { createSignalIdentity } from './signal.js'
|
|
9
|
+
|
|
10
|
+
const PLATFORM_MAP = {
|
|
11
|
+
'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
|
|
12
|
+
Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN32,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const getUserAgent = (config) => ({
|
|
16
|
+
appVersion: {
|
|
17
|
+
primary: config.version[0],
|
|
18
|
+
secondary: config.version[1],
|
|
19
|
+
tertiary: config.version[2],
|
|
20
|
+
},
|
|
21
|
+
platform: config.browser[1].toLocaleLowerCase().includes('android')
|
|
22
|
+
? proto.ClientPayload.UserAgent.Platform.MACOS
|
|
23
|
+
: proto.ClientPayload.UserAgent.Platform.WEB,
|
|
24
|
+
releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
|
|
25
|
+
osVersion: '0.1',
|
|
26
|
+
device: 'Desktop',
|
|
27
|
+
osBuildNumber: '0.1',
|
|
28
|
+
localeLanguageIso6391: 'en',
|
|
29
|
+
mnc: '000',
|
|
30
|
+
mcc: '000',
|
|
31
|
+
localeCountryIso31661Alpha2: config.countryCode,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const getWebInfo = (config) => {
|
|
35
|
+
let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER
|
|
36
|
+
if (config.syncFullHistory && PLATFORM_MAP[config.browser[0]] && config.browser[1] === 'Desktop')
|
|
37
|
+
webSubPlatform = PLATFORM_MAP[config.browser[0]]
|
|
38
|
+
return { webSubPlatform }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const getClientPayload = (config) => {
|
|
42
|
+
const payload = {
|
|
43
|
+
connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
|
|
44
|
+
connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
|
|
45
|
+
userAgent: getUserAgent(config),
|
|
46
|
+
}
|
|
47
|
+
if (!config.browser[1].toLowerCase().includes('android')) payload.webInfo = getWebInfo(config)
|
|
48
|
+
return payload
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const getPlatformType = (platform) => {
|
|
52
|
+
const platformType = platform.toUpperCase()
|
|
53
|
+
if (platformType === 'ANDROID') return proto.DeviceProps.PlatformType.ANDROID_PHONE
|
|
54
|
+
return proto.DeviceProps.PlatformType[platformType] ?? proto.DeviceProps.PlatformType.CHROME
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const generateLoginNode = (userJid, config) => {
|
|
58
|
+
const { user, device } = jidDecode(userJid)
|
|
59
|
+
return proto.ClientPayload.fromObject({
|
|
60
|
+
...getClientPayload(config),
|
|
61
|
+
passive: true,
|
|
62
|
+
pull: true,
|
|
63
|
+
username: +user,
|
|
64
|
+
device,
|
|
65
|
+
lidDbMigrated: false, // hard-set; behaviour under true is unknown
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const generateRegistrationNode = ({ registrationId, signedPreKey, signedIdentityKey }, config) => {
|
|
70
|
+
const appVersionBuf = createHash('md5').update(config.version.join('.')).digest()
|
|
71
|
+
const companionProto = proto.DeviceProps.encode({
|
|
72
|
+
os: config.browser[0],
|
|
73
|
+
platformType: getPlatformType(config.browser[1]),
|
|
74
|
+
requireFullSync: config.syncFullHistory,
|
|
75
|
+
historySyncConfig: {
|
|
76
|
+
storageQuotaMb: 569150,
|
|
77
|
+
inlineInitialPayloadInE2EeMsg: true,
|
|
78
|
+
recentSyncDaysLimit: undefined,
|
|
79
|
+
supportCallLogHistory: false,
|
|
80
|
+
supportBotUserAgentChatHistory: true,
|
|
81
|
+
supportCagReactionsAndPolls: true,
|
|
82
|
+
supportBizHostedMsg: true,
|
|
83
|
+
supportRecentSyncChunkMessageCountTuning: true,
|
|
84
|
+
supportHostedGroupMsg: true,
|
|
85
|
+
supportFbidBotChatHistory: true,
|
|
86
|
+
supportAddOnHistorySyncMigration: undefined,
|
|
87
|
+
supportMessageAssociation: true,
|
|
88
|
+
supportGroupHistory: false,
|
|
89
|
+
onDemandReady: undefined,
|
|
90
|
+
supportGuestChat: undefined,
|
|
91
|
+
},
|
|
92
|
+
version: { primary: 10, secondary: 15, tertiary: 7 },
|
|
93
|
+
}).finish()
|
|
94
|
+
|
|
95
|
+
return proto.ClientPayload.fromObject({
|
|
96
|
+
...getClientPayload(config),
|
|
97
|
+
passive: false,
|
|
98
|
+
pull: false,
|
|
99
|
+
devicePairingData: {
|
|
100
|
+
buildHash: appVersionBuf,
|
|
101
|
+
deviceProps: companionProto,
|
|
102
|
+
eRegid: encodeBigEndian(registrationId),
|
|
103
|
+
eKeytype: KEY_BUNDLE_TYPE,
|
|
104
|
+
eIdent: signedIdentityKey.public,
|
|
105
|
+
eSkeyId: encodeBigEndian(signedPreKey.keyId, 3),
|
|
106
|
+
eSkeyVal: signedPreKey.keyPair.public,
|
|
107
|
+
eSkeySig: signedPreKey.signature,
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const configureSuccessfulPairing = (stanza, { advSecretKey, signedIdentityKey, signalIdentities }) => {
|
|
113
|
+
const msgId = stanza.attrs.id
|
|
114
|
+
const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success')
|
|
115
|
+
const deviceIdentityNode = getBinaryNodeChild(pairSuccessNode, 'device-identity')
|
|
116
|
+
const platformNode = getBinaryNodeChild(pairSuccessNode, 'platform')
|
|
117
|
+
const deviceNode = getBinaryNodeChild(pairSuccessNode, 'device')
|
|
118
|
+
const businessNode = getBinaryNodeChild(pairSuccessNode, 'biz')
|
|
119
|
+
|
|
120
|
+
if (!deviceIdentityNode || !deviceNode)
|
|
121
|
+
throw new Boom('Missing device-identity or device in pair success node', { data: stanza })
|
|
122
|
+
|
|
123
|
+
const jid = deviceNode.attrs.jid
|
|
124
|
+
const lid = deviceNode.attrs.lid
|
|
125
|
+
const bizName = businessNode?.attrs.name
|
|
126
|
+
|
|
127
|
+
const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content)
|
|
128
|
+
const hmacPrefix = (accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED)
|
|
129
|
+
? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
|
|
130
|
+
: Buffer.from([])
|
|
131
|
+
|
|
132
|
+
if (Buffer.compare(hmac, hmacSign(Buffer.concat([hmacPrefix, details]), Buffer.from(advSecretKey, 'base64'))) !== 0)
|
|
133
|
+
throw new Boom('Invalid account signature')
|
|
134
|
+
|
|
135
|
+
const account = proto.ADVSignedDeviceIdentity.decode(details)
|
|
136
|
+
const { accountSignatureKey, accountSignature, details: deviceDetails } = account
|
|
137
|
+
const deviceIdentity = proto.ADVDeviceIdentity.decode(deviceDetails)
|
|
138
|
+
|
|
139
|
+
const accountSignaturePrefix = deviceIdentity.deviceType === proto.ADVEncryptionType.HOSTED
|
|
140
|
+
? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
|
|
141
|
+
: WA_ADV_ACCOUNT_SIG_PREFIX
|
|
142
|
+
|
|
143
|
+
if (!Curve.verify(accountSignatureKey, Buffer.concat([accountSignaturePrefix, deviceDetails, signedIdentityKey.public]), accountSignature))
|
|
144
|
+
throw new Boom('Failed to verify account signature')
|
|
145
|
+
|
|
146
|
+
account.deviceSignature = Curve.sign(
|
|
147
|
+
signedIdentityKey.private,
|
|
148
|
+
Buffer.concat([WA_ADV_DEVICE_SIG_PREFIX, deviceDetails, signedIdentityKey.public, accountSignatureKey])
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
const identity = createSignalIdentity(lid, accountSignatureKey)
|
|
152
|
+
const accountEnc = encodeSignedDeviceIdentity(account, false)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
creds: {
|
|
156
|
+
account,
|
|
157
|
+
me: { id: jid, name: bizName, lid },
|
|
158
|
+
signalIdentities: [...(signalIdentities ?? []), identity],
|
|
159
|
+
platform: platformNode?.attrs.name,
|
|
160
|
+
},
|
|
161
|
+
reply: {
|
|
162
|
+
tag: 'iq',
|
|
163
|
+
attrs: { to: S_WHATSAPP_NET, type: 'result', id: msgId },
|
|
164
|
+
content: [{
|
|
165
|
+
tag: 'pair-device-sign',
|
|
166
|
+
attrs: {},
|
|
167
|
+
content: [{
|
|
168
|
+
tag: 'device-identity',
|
|
169
|
+
attrs: { 'key-index': deviceIdentity.keyIndex.toString() },
|
|
170
|
+
content: accountEnc,
|
|
171
|
+
}],
|
|
172
|
+
}],
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const encodeSignedDeviceIdentity = (account, includeSignatureKey) => {
|
|
178
|
+
account = { ...account }
|
|
179
|
+
if (!includeSignatureKey || !account.accountSignatureKey?.length) account.accountSignatureKey = null
|
|
180
|
+
return proto.ADVSignedDeviceIdentity.encode(account).finish()
|
|
181
|
+
}
|