@zervonex/baileys 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3204 -3203
- package/WAProto/fetcher.js +1 -1
- package/WAProto/index.js +87 -87
- package/lib/Builders/AIRich.js +642 -589
- package/lib/Builders/builders.js +5 -4
- package/lib/Defaults/index.js +2 -1
- package/lib/Socket/index.js +14 -14
- package/lib/Socket/messages-recv.js +980 -903
- package/lib/Socket/messages-send.js +707 -692
- package/lib/Socket/newsletter.js +781 -751
- package/lib/Socket/socket.js +0 -1
- package/lib/Socket/{nexus-handler.js → zervonex-handler.js} +54 -54
- package/lib/Utils/chat-utils.js +1 -0
- package/lib/Utils/index.js +38 -39
- package/lib/Utils/messages.js +30 -20
- package/lib/Utils/spam-report-utils.js +134 -141
- package/lib/Utils/tc-token-utils.js +125 -73
- package/lib/index.js +51 -52
- package/package.json +11 -7
- package/lib/Utils/key-store.js +0 -3
package/lib/Socket/socket.js
CHANGED
|
@@ -530,7 +530,6 @@ export const makeSocket = (config) => {
|
|
|
530
530
|
// ─── Pairing ─────────────────────────────────────────────────────────────────
|
|
531
531
|
|
|
532
532
|
const requestPairingCode = async (phoneNumber, customPairingCode) => {
|
|
533
|
-
if (authState.creds.pairingCode && !customPairingCode) return authState.creds.pairingCode
|
|
534
533
|
await waitForSocketOpen()
|
|
535
534
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
536
535
|
const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5))
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import crypto from 'crypto'
|
|
2
|
-
import * as Builders from '../Builders/index.js'
|
|
3
|
-
import { buildDispatch, buildPlugins, norm, handleGroupStatus, handleStatusMention, handlePollResult, PRIMITIVE_SHORTCUTS, HANDLER_SHORTCUTS, attachAIRich } from '../Builders/index.js'
|
|
4
|
-
import { makeUsernameSocket } from './username.js'
|
|
5
|
-
|
|
6
|
-
export const makeMessageBuilderSocket = (config) => {
|
|
7
|
-
const sock = makeUsernameSocket(config)
|
|
8
|
-
const _send = sock.sendMessage.bind(sock)
|
|
9
|
-
|
|
10
|
-
const ALL_BUILDERS = Object.values(Builders).filter(B => typeof B === 'function' && (B.dispatchKeys || B.shortcuts || B.name))
|
|
11
|
-
|
|
12
|
-
const DISPATCH = buildDispatch(ALL_BUILDERS, {
|
|
13
|
-
groupStatus: (sock, jid, content, opts, config) => handleGroupStatus(sock, jid, norm(content.groupStatus), config),
|
|
14
|
-
statusMentionMessage: (sock, jid, content, opts, config) => handleStatusMention(sock, jid, norm(content.statusMentionMessage), config),
|
|
15
|
-
pollResultMessage: (sock, jid, content) => handlePollResult(sock, jid, norm(content.pollResultMessage))
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
const PLUGINS = buildPlugins(ALL_BUILDERS, { ...PRIMITIVE_SHORTCUTS, ...HANDLER_SHORTCUTS })
|
|
19
|
-
|
|
20
|
-
const result = { ...sock }
|
|
21
|
-
|
|
22
|
-
for (const [name, factory] of Object.entries(PLUGINS)) result[name] = factory(result, config)
|
|
23
|
-
|
|
24
|
-
result.sendMessage = async (jid, content, options = {}) => {
|
|
25
|
-
// aiRich shorthand — multiple aliases supported
|
|
26
|
-
const aiData = content.aiRich ?? content.airich ?? content.richResponse ?? content.AIRich
|
|
27
|
-
if (aiData) return result.sendRichFromObject(jid, aiData, options)
|
|
28
|
-
|
|
29
|
-
// dispatch to registered builder handlers
|
|
30
|
-
for (const [key, handler] of Object.entries(DISPATCH)) {
|
|
31
|
-
if (key in content) return handler(result, jid, content, options, config)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// bare nativeFlow array/object — wrap into interactiveMessage before sending
|
|
35
|
-
if (content.nativeFlow && !content.interactiveMessage) {
|
|
36
|
-
const buttons = Array.isArray(content.nativeFlow) ? content.nativeFlow : [content.nativeFlow]
|
|
37
|
-
const { generateWAMessageFromContent } = await import('../Utils/index.js')
|
|
38
|
-
const msg = await generateWAMessageFromContent(jid, {
|
|
39
|
-
interactiveMessage: { body: { text: content.text || '' }, footer: { text: content.footer || '' }, header: { hasMediaAttachment: false }, nativeFlowMessage: { buttons } }
|
|
40
|
-
}, { userJid: sock.user?.id })
|
|
41
|
-
await result.relayMessage(jid, msg.message, { messageId: msg.key.id, aiLabel: !!(content.ai ?? content.aiLabel ?? options.aiLabel ?? options.ai ?? config.aiLabel ?? config.ai) })
|
|
42
|
-
return msg
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const { ai, aiLabel: contentAiLabel, ...cleanContent } = content
|
|
46
|
-
const isAi = !!(ai ?? contentAiLabel ?? options.aiLabel ?? options.ai ?? config.aiLabel)
|
|
47
|
-
if (isAi) {
|
|
48
|
-
const secret = cleanContent.messageContextInfo?.messageSecret || crypto.randomBytes(32)
|
|
49
|
-
cleanContent.messageContextInfo = { ...(cleanContent.messageContextInfo || {}), messageSecret: secret, supportPayload: JSON.stringify({ version: 2, is_ai_message: true, should_show_system_message: true, ticket_id: crypto.randomBytes(16).toString('hex') }) }
|
|
50
|
-
}
|
|
51
|
-
return _send(jid, isAi ? cleanContent : content, { ...options, aiLabel: isAi })
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return attachAIRich(result, config)
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import * as Builders from '../Builders/index.js'
|
|
3
|
+
import { buildDispatch, buildPlugins, norm, handleGroupStatus, handleStatusMention, handlePollResult, PRIMITIVE_SHORTCUTS, HANDLER_SHORTCUTS, attachAIRich } from '../Builders/index.js'
|
|
4
|
+
import { makeUsernameSocket } from './username.js'
|
|
5
|
+
|
|
6
|
+
export const makeMessageBuilderSocket = (config) => {
|
|
7
|
+
const sock = makeUsernameSocket(config)
|
|
8
|
+
const _send = sock.sendMessage.bind(sock)
|
|
9
|
+
|
|
10
|
+
const ALL_BUILDERS = Object.values(Builders).filter(B => typeof B === 'function' && (B.dispatchKeys || B.shortcuts || B.name))
|
|
11
|
+
|
|
12
|
+
const DISPATCH = buildDispatch(ALL_BUILDERS, {
|
|
13
|
+
groupStatus: (sock, jid, content, opts, config) => handleGroupStatus(sock, jid, norm(content.groupStatus), config),
|
|
14
|
+
statusMentionMessage: (sock, jid, content, opts, config) => handleStatusMention(sock, jid, norm(content.statusMentionMessage), config),
|
|
15
|
+
pollResultMessage: (sock, jid, content) => handlePollResult(sock, jid, norm(content.pollResultMessage))
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const PLUGINS = buildPlugins(ALL_BUILDERS, { ...PRIMITIVE_SHORTCUTS, ...HANDLER_SHORTCUTS })
|
|
19
|
+
|
|
20
|
+
const result = { ...sock }
|
|
21
|
+
|
|
22
|
+
for (const [name, factory] of Object.entries(PLUGINS)) result[name] = factory(result, config)
|
|
23
|
+
|
|
24
|
+
result.sendMessage = async (jid, content, options = {}) => {
|
|
25
|
+
// aiRich shorthand — multiple aliases supported
|
|
26
|
+
const aiData = content.aiRich ?? content.airich ?? content.richResponse ?? content.AIRich
|
|
27
|
+
if (aiData) return result.sendRichFromObject(jid, aiData, options)
|
|
28
|
+
|
|
29
|
+
// dispatch to registered builder handlers
|
|
30
|
+
for (const [key, handler] of Object.entries(DISPATCH)) {
|
|
31
|
+
if (key in content) return handler(result, jid, content, options, config)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// bare nativeFlow array/object — wrap into interactiveMessage before sending
|
|
35
|
+
if (content.nativeFlow && !content.interactiveMessage) {
|
|
36
|
+
const buttons = Array.isArray(content.nativeFlow) ? content.nativeFlow : [content.nativeFlow]
|
|
37
|
+
const { generateWAMessageFromContent } = await import('../Utils/index.js')
|
|
38
|
+
const msg = await generateWAMessageFromContent(jid, {
|
|
39
|
+
interactiveMessage: { body: { text: content.text || '' }, footer: { text: content.footer || '' }, header: { hasMediaAttachment: false }, nativeFlowMessage: { buttons } }
|
|
40
|
+
}, { userJid: sock.user?.id })
|
|
41
|
+
await result.relayMessage(jid, msg.message, { messageId: msg.key.id, aiLabel: !!(content.ai ?? content.aiLabel ?? options.aiLabel ?? options.ai ?? config.aiLabel ?? config.ai) })
|
|
42
|
+
return msg
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { ai, aiLabel: contentAiLabel, ...cleanContent } = content
|
|
46
|
+
const isAi = !!(ai ?? contentAiLabel ?? options.aiLabel ?? options.ai ?? config.aiLabel)
|
|
47
|
+
if (isAi) {
|
|
48
|
+
const secret = cleanContent.messageContextInfo?.messageSecret || crypto.randomBytes(32)
|
|
49
|
+
cleanContent.messageContextInfo = { ...(cleanContent.messageContextInfo || {}), messageSecret: secret, supportPayload: JSON.stringify({ version: 2, is_ai_message: true, should_show_system_message: true, ticket_id: crypto.randomBytes(16).toString('hex') }) }
|
|
50
|
+
}
|
|
51
|
+
return _send(jid, isAi ? cleanContent : content, { ...options, aiLabel: isAi })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return attachAIRich(result, config)
|
|
55
55
|
}
|
package/lib/Utils/chat-utils.js
CHANGED
|
@@ -102,6 +102,7 @@ export const decodeSyncdMutations = async (msgMutations, initialState, getAppSta
|
|
|
102
102
|
const record = 'record' in msgMutation && msgMutation.record ? msgMutation.record : msgMutation
|
|
103
103
|
let key
|
|
104
104
|
try { key = await getKey(record.keyId.id) } catch (err) { if (isMissingKeyError(err)) throw err; continue }
|
|
105
|
+
if (!record?.value?.blob) continue
|
|
105
106
|
const content = record.value.blob
|
|
106
107
|
const encContent = content.subarray(0, -32)
|
|
107
108
|
const ogValueMac = content.subarray(-32)
|
package/lib/Utils/index.js
CHANGED
|
@@ -1,39 +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 './
|
|
24
|
-
export * from './
|
|
25
|
-
export * from './
|
|
26
|
-
export * from './
|
|
27
|
-
export * from './
|
|
28
|
-
export * from './
|
|
29
|
-
export * from './
|
|
30
|
-
export * from './
|
|
31
|
-
export * from './
|
|
32
|
-
export * from './
|
|
33
|
-
export * from './
|
|
34
|
-
export * from './
|
|
35
|
-
export * from './
|
|
36
|
-
export * from './
|
|
37
|
-
export * from './
|
|
38
|
-
export * from './
|
|
39
|
-
export * from './sync-action-utils.js'
|
|
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'
|
package/lib/Utils/messages.js
CHANGED
|
@@ -95,8 +95,9 @@ const compressWebp = async (buf, { animated, quality = 80, stickerType = Sticker
|
|
|
95
95
|
return { buffer: converted.length <= STICKER_MAX_BYTES ? converted : await shrinkToLimit(converted, { animated: false }), isAnimated: false }
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
const shrinkToLimit = async (buf, { animated } = {}) => {
|
|
98
|
+
const shrinkToLimit = async (buf, { animated } = {}, _depth = 0) => {
|
|
99
99
|
if (buf.length <= STICKER_MAX_BYTES) return buf
|
|
100
|
+
if (_depth >= 2) return buf
|
|
100
101
|
const ratio = STICKER_MAX_BYTES / buf.length
|
|
101
102
|
const q = Math.max(10, Math.min(60, Math.floor(ratio * 80 * 0.85)))
|
|
102
103
|
const out = (await compressWebp(buf, { animated, quality: q })).buffer
|
|
@@ -134,11 +135,15 @@ const tgsToWebp = async (tgsBuffer, { quality = 80, fps = 30 } = {}) => {
|
|
|
134
135
|
const rgba = Buffer.from(api.HEAPU8.buffer, bufPtr, W * H * 4)
|
|
135
136
|
await sharp(Buffer.from(rgba), { raw: { width: W, height: H, channels: 4 } }).png().toFile(path.join(tgsTmpDir, `f${String(i).padStart(4, '0')}.png`))
|
|
136
137
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
const encode = async (outFps, q) => { const outPath = path.join(tgsTmpDir, `out_${outFps}_${q}.webp`); await ffmpegRun(['-y', '-framerate', String(fps), '-i', path.join(tgsTmpDir, 'f%04d.png'), '-vcodec', 'libwebp', '-vf', `scale=512:512,fps=${outFps}`, '-lossless', '0', '-compression_level', '6', '-q:v', String(q), '-loop', '0', '-preset', 'default', '-an', '-vsync', '0', outPath]); return readFileSync(outPath) }
|
|
139
|
+
let out = await encode(fps, quality)
|
|
140
|
+
if (out.length <= STICKER_MAX_BYTES) return out
|
|
141
|
+
const targetFps = Math.max(8, Math.floor(fps * (STICKER_MAX_BYTES / out.length) * 0.9))
|
|
142
|
+
out = await encode(targetFps, quality)
|
|
143
|
+
if (out.length <= STICKER_MAX_BYTES) return out
|
|
144
|
+
const targetQ = Math.max(10, Math.floor(quality * (STICKER_MAX_BYTES / out.length) * 0.9))
|
|
145
|
+
return encode(targetFps, targetQ)
|
|
146
|
+
} finally { api.lottie_destroy(handle); rmSync(tgsTmpDir, { recursive: true, force: true }) }
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
// ─── EXPORTS: URL / LINK PREVIEW ─────────────────────────────────────────────
|
|
@@ -158,11 +163,12 @@ export const getContentType = (content) => {
|
|
|
158
163
|
return Object.keys(content).find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage')
|
|
159
164
|
}
|
|
160
165
|
|
|
161
|
-
export const normalizeMessageContent = (content) => {
|
|
166
|
+
export const normalizeMessageContent = (content, logger) => {
|
|
162
167
|
if (!content) return
|
|
163
168
|
for (let i = 0; i < 5; i++) {
|
|
164
169
|
const inner = WRAPPER_KEYS.reduce((found, k) => found || content?.[k], null)
|
|
165
170
|
if (!inner) break
|
|
171
|
+
if (!inner.message) { logger?.warn({ i, keys: Object.keys(inner) }, 'normalizeMessageContent: inner wrapper has no message field'); break }
|
|
166
172
|
content = inner.message
|
|
167
173
|
}
|
|
168
174
|
return content
|
|
@@ -183,6 +189,18 @@ export const extractMessageContent = (content) => {
|
|
|
183
189
|
return content
|
|
184
190
|
}
|
|
185
191
|
|
|
192
|
+
const normalizeExternalAdReplyThumbnail = async (ext) => {
|
|
193
|
+
if (!ext?.thumbnail || !Buffer.isBuffer(ext.thumbnail) || ext.thumbnail.length <= 50_000) return ext
|
|
194
|
+
let quality = 90
|
|
195
|
+
while (quality >= 10) {
|
|
196
|
+
const thumbnail = await sharp(ext.thumbnail, { sequentialRead: true }).resize(200, 200, { fit: 'inside' }).jpeg({ quality }).toBuffer().catch(() => null)
|
|
197
|
+
if (!thumbnail) return ext
|
|
198
|
+
if (thumbnail.length <= 50_000) return { ...ext, thumbnail }
|
|
199
|
+
quality = Math.max(10, Math.floor(quality * (50_000 / thumbnail.length) * 0.85))
|
|
200
|
+
}
|
|
201
|
+
return ext
|
|
202
|
+
}
|
|
203
|
+
|
|
186
204
|
export const generateForwardMessageContent = (message, forceForward) => {
|
|
187
205
|
let content = normalizeMessageContent(message.message)
|
|
188
206
|
if (!content) throw new Boom('no content in message', { statusCode: 400 })
|
|
@@ -486,15 +504,7 @@ const MESSAGE_HANDLERS = {
|
|
|
486
504
|
// Interactive overlays: keyed by the input field, each returns a complete replacement for m.
|
|
487
505
|
// Looked up dynamically — adding a new overlay type = one new entry, nothing else.
|
|
488
506
|
const applyContextInfoAndMentions = async (interactive, msg) => {
|
|
489
|
-
if (msg.contextInfo) {
|
|
490
|
-
const ctx = { ...(interactive.contextInfo || {}), ...msg.contextInfo }
|
|
491
|
-
if (ctx.externalAdReply?.thumbnail && Buffer.isBuffer(ctx.externalAdReply.thumbnail) && ctx.externalAdReply.thumbnail.length > 50_000) {
|
|
492
|
-
try {
|
|
493
|
-
ctx.externalAdReply.thumbnail = await sharp(ctx.externalAdReply.thumbnail).resize(200, 200, { fit: 'cover', position: 'center' }).jpeg({ quality: 60 }).toBuffer()
|
|
494
|
-
} catch { }
|
|
495
|
-
}
|
|
496
|
-
interactive.contextInfo = ctx
|
|
497
|
-
}
|
|
507
|
+
if (msg.contextInfo) { const ctx = { ...(interactive.contextInfo || {}), ...msg.contextInfo }; if (ctx.externalAdReply) ctx.externalAdReply = await normalizeExternalAdReplyThumbnail(ctx.externalAdReply); interactive.contextInfo = ctx }
|
|
498
508
|
if (msg.mentions?.length) interactive.contextInfo = { ...(interactive.contextInfo || {}), mentionedJid: msg.mentions }
|
|
499
509
|
}
|
|
500
510
|
|
|
@@ -582,14 +592,14 @@ export const generateWAMessageContent = async (message, options = {}) => {
|
|
|
582
592
|
}
|
|
583
593
|
|
|
584
594
|
// Apply overlays: any overlay key present in message replaces m
|
|
585
|
-
for (const key of overlayKeys) { if (key in message && message[key]) { m = OVERLAY_HANDLERS[key](m, message); break } }
|
|
595
|
+
for (const key of overlayKeys) { if (key in message && message[key]) { m = await OVERLAY_HANDLERS[key](m, message); break } }
|
|
586
596
|
|
|
587
597
|
// Merge contextInfo / mentions onto the primary content key — deep-merges externalAdReply to prevent thumbnail/field loss
|
|
588
598
|
const finalKey = Object.keys(m)[0]
|
|
589
599
|
if ((message.contextInfo || message.mentions?.length) && finalKey && m[finalKey] && typeof m[finalKey] === 'object') {
|
|
590
|
-
const existing = m[finalKey].contextInfo || {}
|
|
591
|
-
const
|
|
592
|
-
m[finalKey].contextInfo = { ...existing, ...incoming, ...(
|
|
600
|
+
const existing = m[finalKey].contextInfo || {}; const incoming = message.contextInfo || {}
|
|
601
|
+
const mergedExt = (existing.externalAdReply || incoming.externalAdReply) ? await normalizeExternalAdReplyThumbnail({ ...(existing.externalAdReply || {}), ...(incoming.externalAdReply || {}) }) : undefined
|
|
602
|
+
m[finalKey].contextInfo = { ...existing, ...incoming, ...(mergedExt ? { externalAdReply: mergedExt } : {}), ...(message.mentions?.length ? { mentionedJid: message.mentions } : {}) }
|
|
593
603
|
}
|
|
594
604
|
|
|
595
605
|
if ('viewOnce' in message && message.viewOnce || 'viewOnceMessage' in message) m = { viewOnceMessage: { message: m } }
|
|
@@ -1,142 +1,135 @@
|
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (!
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
})),
|
|
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
|
+
const tokenNode = getBinaryNodeChild(reporting, 'reporting_token')
|
|
50
|
+
const tagNode = getBinaryNodeChild(reporting, 'reporting_tag') ||
|
|
51
|
+
getBinaryNodeChild(getBinaryNodeChild(reporting, 'reporting_validation'), 'reporting_tag')
|
|
52
|
+
if (!tagNode?.content) return null
|
|
53
|
+
const raw = tagNode.content
|
|
54
|
+
const reportingTag = toBuffer(raw instanceof Uint8Array || Buffer.isBuffer(raw) ? raw : typeof raw === 'string' ? raw : null)
|
|
55
|
+
if (!reportingTag?.length) return null
|
|
56
|
+
const stanzaId = tagNode.attrs?.id || stanza.attrs?.id
|
|
57
|
+
if (!stanzaId) return null
|
|
58
|
+
const tsRaw = tagNode.attrs?.ts_s || stanza.attrs?.t
|
|
59
|
+
const reportingToken = tokenNode?.content ? toBuffer(tokenNode.content) : null
|
|
60
|
+
const tokenVersion = tokenNode?.attrs?.v ? parseInt(tokenNode.attrs.v) : null
|
|
61
|
+
const validationPolicy = reporting.attrs?.validation_policy || null
|
|
62
|
+
return { stanzaId, timestamp: tsRaw ? Number(tsRaw) : 0, reportingTag, reportingToken, tokenVersion, validationPolicy }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const buildSpamReportMessageNodes = messages => messages.slice(0, 5).map(m => {
|
|
66
|
+
const reportingTag = normalizeReportingTag(m.reportingTag)
|
|
67
|
+
if (!reportingTag) throw new Error(`reportSpam: missing reportingTag for ${m.stanzaId}`)
|
|
68
|
+
return {
|
|
69
|
+
tag: 'message',
|
|
70
|
+
attrs: {
|
|
71
|
+
t: String(m.timestamp),
|
|
72
|
+
id: m.stanzaId,
|
|
73
|
+
reported_push_name: m.pushName || '',
|
|
74
|
+
type: m.messageType || 'text',
|
|
75
|
+
from: m.fromJid,
|
|
76
|
+
},
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
tag: 'raw',
|
|
80
|
+
attrs: { local_message_type: '0', v: '2' },
|
|
81
|
+
content: m.text ?? '',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
tag: 'reporting',
|
|
85
|
+
attrs: {},
|
|
86
|
+
content: [{
|
|
87
|
+
tag: 'reporting_validation',
|
|
88
|
+
attrs: {},
|
|
89
|
+
content: [{
|
|
90
|
+
tag: 'reporting_tag',
|
|
91
|
+
attrs: { id: m.stanzaId, ts_s: String(m.timestamp) },
|
|
92
|
+
content: reportingTag,
|
|
93
|
+
}],
|
|
94
|
+
}],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
export const buildSpamReportIq = (jid, messages, spamFlow = SPAM_FLOWS.ACCOUNT_INFO_REPORT) => {
|
|
101
|
+
if (!messages?.length) throw new Error('reportSpam: at least one message with reporting_tag required')
|
|
102
|
+
return {
|
|
103
|
+
tag: 'iq',
|
|
104
|
+
attrs: { type: 'set', xmlns: 'spam', to: S_WHATSAPP_NET },
|
|
105
|
+
content: [{
|
|
106
|
+
tag: 'spam_list',
|
|
107
|
+
attrs: { jid, spam_flow: spamFlow },
|
|
108
|
+
content: buildSpamReportMessageNodes(messages),
|
|
109
|
+
}],
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const parseSpamReportResponse = result => {
|
|
114
|
+
if (!result) return { success: false, error: 'empty response' }
|
|
115
|
+
if (result.attrs?.type === 'error') {
|
|
116
|
+
const errNode = getBinaryNodeChild(result, 'error')
|
|
117
|
+
const code = errNode?.attrs?.code
|
|
118
|
+
const text = errNode?.content?.toString?.() || errNode?.attrs?.text
|
|
119
|
+
return { success: false, error: `spam IQ error${code ? ` ${code}` : ''}${text ? `: ${text}` : ''}` }
|
|
120
|
+
}
|
|
121
|
+
const reportNode = getBinaryNodeChild(result, 'report')
|
|
122
|
+
const reportId = reportNode?.attrs?.id || getBinaryNodeChildString(reportNode, 'id')
|
|
123
|
+
if (reportId) return { success: true, reportId: String(reportId) }
|
|
124
|
+
if (result.attrs?.type === 'result') return { success: true }
|
|
125
|
+
return { success: false, error: 'unexpected spam IQ response' }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const buildSimpleReportNode = (jid, messageKeys) => ({
|
|
129
|
+
tag: 'report',
|
|
130
|
+
attrs: { jid, type: 'spam' },
|
|
131
|
+
content: (messageKeys || []).map(key => ({
|
|
132
|
+
tag: 'message',
|
|
133
|
+
attrs: { id: key.id || key, from: key.fromMe ? undefined : jid },
|
|
134
|
+
})),
|
|
142
135
|
})
|