@zervonex/baileys 0.0.0-stage → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3203 -2
  3. package/WAProto/WAProtoCompile.js +291 -0
  4. package/WAProto/fetcher.js +69 -0
  5. package/WAProto/index.js +88 -0
  6. package/WAProto/parser.js +313 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Builders/AIRich.js +590 -0
  9. package/lib/Builders/base.js +130 -0
  10. package/lib/Builders/builders.js +448 -0
  11. package/lib/Builders/index.js +3 -0
  12. package/lib/Defaults/index.js +225 -0
  13. package/lib/Defaults/phonenumber-mcc.json +223 -0
  14. package/lib/Signal/index.js +2 -0
  15. package/lib/Signal/libsignal.js +523 -0
  16. package/lib/Signal/lid-mapping.js +283 -0
  17. package/lib/Socket/Client/index.js +2 -0
  18. package/lib/Socket/Client/types.js +10 -0
  19. package/lib/Socket/Client/websocket.js +84 -0
  20. package/lib/Socket/business.js +152 -0
  21. package/lib/Socket/chats.js +855 -0
  22. package/lib/Socket/communities.js +147 -0
  23. package/lib/Socket/groups.js +294 -0
  24. package/lib/Socket/index.js +15 -0
  25. package/lib/Socket/messages-recv.js +904 -0
  26. package/lib/Socket/messages-send.js +693 -0
  27. package/lib/Socket/mex.js +34 -0
  28. package/lib/Socket/newsletter.js +752 -0
  29. package/lib/Socket/nexus-handler.js +55 -0
  30. package/lib/Socket/registration.js +155 -0
  31. package/lib/Socket/socket.js +798 -0
  32. package/lib/Socket/spam-report.js +65 -0
  33. package/lib/Socket/username.js +82 -0
  34. package/lib/Store/index.js +6 -0
  35. package/lib/Store/make-cache-manager-store.js +81 -0
  36. package/lib/Store/make-in-memory-store.js +425 -0
  37. package/lib/Store/make-ordered-dictionary.js +82 -0
  38. package/lib/Store/object-repository.js +31 -0
  39. package/lib/Store/reporting-info-store.js +38 -0
  40. package/lib/Types/Auth.js +2 -0
  41. package/lib/Types/Bussines.js +2 -0
  42. package/lib/Types/Call.js +2 -0
  43. package/lib/Types/Chat.js +8 -0
  44. package/lib/Types/Contact.js +2 -0
  45. package/lib/Types/Events.js +2 -0
  46. package/lib/Types/GroupMetadata.js +2 -0
  47. package/lib/Types/Label.js +25 -0
  48. package/lib/Types/LabelAssociation.js +7 -0
  49. package/lib/Types/Message.js +15 -0
  50. package/lib/Types/Newsletter.js +100 -0
  51. package/lib/Types/Product.js +2 -0
  52. package/lib/Types/Signal.js +2 -0
  53. package/lib/Types/Socket.js +14 -0
  54. package/lib/Types/State.js +26 -0
  55. package/lib/Types/USync.js +2 -0
  56. package/lib/Types/index.js +30 -0
  57. package/lib/Utils/auth-utils.js +142 -0
  58. package/lib/Utils/baileys-event-stream.js +33 -0
  59. package/lib/Utils/bot-capabilities.js +40 -0
  60. package/lib/Utils/bot-planning-replay.js +44 -0
  61. package/lib/Utils/browser-utils.js +26 -0
  62. package/lib/Utils/business.js +75 -0
  63. package/lib/Utils/chat-utils.js +316 -0
  64. package/lib/Utils/companion-reg-client-utils.js +20 -0
  65. package/lib/Utils/consumer-application.js +38 -0
  66. package/lib/Utils/crypto.js +52 -0
  67. package/lib/Utils/decode-wa-message.js +197 -0
  68. package/lib/Utils/event-buffer.js +273 -0
  69. package/lib/Utils/extras.js +186 -0
  70. package/lib/Utils/generics.js +195 -0
  71. package/lib/Utils/history.js +51 -0
  72. package/lib/Utils/identity-change-handler.js +19 -0
  73. package/lib/Utils/index.js +39 -0
  74. package/lib/Utils/key-store.js +3 -0
  75. package/lib/Utils/link-preview.js +51 -0
  76. package/lib/Utils/logger.js +2 -0
  77. package/lib/Utils/lt-hash.js +3 -0
  78. package/lib/Utils/make-mutex.js +16 -0
  79. package/lib/Utils/message-composer.js +59 -0
  80. package/lib/Utils/message-retry-manager.js +48 -0
  81. package/lib/Utils/messages-media.js +520 -0
  82. package/lib/Utils/messages.js +742 -0
  83. package/lib/Utils/meta-ai-msmsg.js +122 -0
  84. package/lib/Utils/meta-compositing.js +75 -0
  85. package/lib/Utils/modern-messages.js +235 -0
  86. package/lib/Utils/native-flow.js +55 -0
  87. package/lib/Utils/noise-handler.js +161 -0
  88. package/lib/Utils/pre-key-manager.js +76 -0
  89. package/lib/Utils/process-message.js +399 -0
  90. package/lib/Utils/reporting-utils.js +171 -0
  91. package/lib/Utils/signal.js +167 -0
  92. package/lib/Utils/spam-report-utils.js +142 -0
  93. package/lib/Utils/sync-action-utils.js +33 -0
  94. package/lib/Utils/tc-token-utils.js +102 -0
  95. package/lib/Utils/use-multi-file-auth-state.js +307 -0
  96. package/lib/Utils/validate-connection.js +181 -0
  97. package/lib/WABinary/constants.js +1321 -0
  98. package/lib/WABinary/decode.js +152 -0
  99. package/lib/WABinary/encode.js +140 -0
  100. package/lib/WABinary/generic-utils.js +179 -0
  101. package/lib/WABinary/index.js +6 -0
  102. package/lib/WABinary/jid-utils.js +235 -0
  103. package/lib/WABinary/types.js +4 -0
  104. package/lib/WAM/BinaryInfo.js +10 -0
  105. package/lib/WAM/constants.js +22853 -0
  106. package/lib/WAM/encode.js +150 -0
  107. package/lib/WAM/index.js +3 -0
  108. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +30 -0
  109. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +48 -0
  110. package/lib/WAUSync/Protocols/USyncContactProtocol.js +23 -0
  111. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +28 -0
  112. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +15 -0
  113. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +24 -0
  114. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +9 -0
  115. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +21 -0
  116. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +19 -0
  117. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +19 -0
  118. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +22 -0
  119. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +16 -0
  120. package/lib/WAUSync/Protocols/index.js +12 -0
  121. package/lib/WAUSync/USyncQuery.js +100 -0
  122. package/lib/WAUSync/USyncUser.js +20 -0
  123. package/lib/WAUSync/index.js +3 -0
  124. package/lib/index.js +68 -0
  125. package/package.json +216 -5
@@ -0,0 +1,122 @@
1
+ import { hkdf, aesDecryptGCM } from './crypto.js'
2
+ import { unpadRandomMax16 } from './generics.js'
3
+ import { jidNormalizedUser } from '../WABinary/index.js'
4
+ import { proto } from '../../WAProto/index.js'
5
+
6
+ const BOT_MESSAGE_INFO = 'Bot Message'
7
+ const KEY_LENGTH = 32
8
+ const AUTH_TAG_LENGTH = 16
9
+ const MSG_ID_HEX_RE = /^[0-9A-Fa-f]{32}$/
10
+
11
+ const toBuffer = value => {
12
+ if (Buffer.isBuffer(value)) return value
13
+ if (value instanceof Uint8Array) return Buffer.from(value.buffer, value.byteOffset, value.byteLength)
14
+ return Buffer.from(value)
15
+ }
16
+
17
+ const buildMessageIdRepresentations = messageId => {
18
+ const ascii = Buffer.from(messageId)
19
+ const binary = MSG_ID_HEX_RE.test(messageId) ? Buffer.from(messageId, 'hex') : ascii
20
+ return [{ label: 'msgIdAscii', value: ascii }, ...(binary.equals(ascii) ? [] : [{ label: 'msgIdBinary', value: binary }])]
21
+ }
22
+
23
+ const pushUnique = (items, seen, item) => {
24
+ const key = JSON.stringify([item.messageId, item.idSource, item.idSources, item.infoSource, item.aadSource, item.info.toString('hex'), item.aad.toString('hex')])
25
+ if (!seen.has(key)) { seen.add(key); items.push(item) }
26
+ }
27
+
28
+ const getCandidateIds = messageKey => {
29
+ const ordered = [
30
+ messageKey?.botType === 'full' ? { source: 'stanzaId', messageId: messageKey?.stanzaId } : { source: 'botEditTargetId', messageId: messageKey?.botEditTargetId },
31
+ { source: 'targetId', messageId: messageKey?.targetId },
32
+ { source: 'metaTargetId', messageId: messageKey?.metaTargetId },
33
+ { source: 'stanzaId', messageId: messageKey?.stanzaId },
34
+ ]
35
+ const candidates = Array.isArray(messageKey?.targetIdCandidates) ? messageKey.targetIdCandidates : []
36
+ for (let i = 0; i < candidates.length; i++) ordered.push({ source: `targetIdCandidates[${i}]`, messageId: candidates[i] })
37
+ const grouped = new Map()
38
+ for (const c of ordered) {
39
+ if (!c.messageId) continue
40
+ const id = String(c.messageId)
41
+ const existing = grouped.get(id)
42
+ if (existing) { if (!existing.idSources.includes(c.source)) existing.idSources.push(c.source) }
43
+ else grouped.set(id, { messageId: id, idSource: c.source, idSources: [c.source] })
44
+ }
45
+ return Array.from(grouped.values())
46
+ }
47
+
48
+ const getJidCandidates = messageKey => {
49
+ const ordered = [
50
+ { source: 'meId', jid: messageKey?.meId },
51
+ { source: 'conversationJid', jid: messageKey?.conversationJid },
52
+ { source: 'senderJid', jid: messageKey?.senderJid },
53
+ { source: 'meLidNormalized', jid: jidNormalizedUser(messageKey?.meLid) },
54
+ ]
55
+ const seen = new Set()
56
+ const result = []
57
+ for (const c of ordered) {
58
+ if (!c.jid) continue
59
+ const jid = String(c.jid)
60
+ if (!seen.has(jid)) { seen.add(jid); result.push({ source: c.source, jid, value: Buffer.from(jid) }) }
61
+ }
62
+ return result
63
+ }
64
+
65
+ const buildMsmsgDecryptionStrategies = messageKey => {
66
+ const botJid = String(messageKey?.participant || '')
67
+ const botJidBuffer = Buffer.from(botJid)
68
+ const targetIds = getCandidateIds(messageKey)
69
+ const jidCandidates = getJidCandidates(messageKey)
70
+ const primaryJid = jidCandidates[0]
71
+ const alternateJid = jidCandidates.find(c => c.source !== primaryJid?.source && c.jid !== botJid)
72
+ const strategies = [], seen = new Set()
73
+ for (const idCandidate of targetIds) {
74
+ for (const idForm of buildMessageIdRepresentations(idCandidate.messageId)) {
75
+ pushUnique(strategies, seen, { mode: '2step', idSource: idCandidate.idSource, idSources: idCandidate.idSources, infoSource: `${idForm.label}+meId+botJid`, aadSource: `${idForm.label}+0+botJid`, authTagLayout: 'trailing', messageId: idCandidate.messageId, info: Buffer.concat([idForm.value, primaryJid.value, botJidBuffer, Buffer.alloc(0)]), aad: Buffer.concat([idForm.value, Buffer.from([0]), botJidBuffer]), attemptLabel: `${idCandidate.idSource}:${idForm.label}:primary` })
76
+ if (alternateJid) pushUnique(strategies, seen, { mode: '2step', idSource: idCandidate.idSource, idSources: idCandidate.idSources, infoSource: `${idForm.label}+${alternateJid.source}+botJid`, aadSource: `${idForm.label}+0+${alternateJid.source}`, authTagLayout: 'trailing', messageId: idCandidate.messageId, info: Buffer.concat([idForm.value, alternateJid.value, botJidBuffer, Buffer.alloc(0)]), aad: Buffer.concat([idForm.value, Buffer.from([0]), alternateJid.value]), attemptLabel: `${idCandidate.idSource}:${idForm.label}:${alternateJid.source}` })
77
+ }
78
+ }
79
+ return strategies.slice(0, 12)
80
+ }
81
+
82
+ const assertRequired = (value, label) => {
83
+ if (!value || (Buffer.isBuffer(value) && value.length === 0) || (value instanceof Uint8Array && value.byteLength === 0)) throw new Error(`Missing required ${label} for msmsg decryption`)
84
+ }
85
+
86
+ const decryptWithStrategy = (messageSecret, msMsg, strategy) => {
87
+ const baseSecret = Buffer.from(hkdf(toBuffer(messageSecret), KEY_LENGTH, { info: BOT_MESSAGE_INFO }))
88
+ const key = Buffer.from(hkdf(baseSecret, KEY_LENGTH, { info: strategy.info }))
89
+ const payload = toBuffer(msMsg.encPayload)
90
+ const ciphertextWithTag = Buffer.concat([payload.slice(0, -AUTH_TAG_LENGTH), payload.slice(-AUTH_TAG_LENGTH)])
91
+ return Buffer.from(aesDecryptGCM(ciphertextWithTag, key, toBuffer(msMsg.encIv), strategy.aad))
92
+ }
93
+
94
+ export const decodeDecryptedMsmsgMessage = decrypted => {
95
+ const messageBuffer = toBuffer(decrypted)
96
+ try {
97
+ const unpadded = Buffer.from(unpadRandomMax16(messageBuffer))
98
+ const decoded = proto.Message.decode(unpadded)
99
+ if (Object.keys(decoded).some(key => key !== 'messageContextInfo' && decoded[key] != null)) return decoded
100
+ } catch { }
101
+ return proto.Message.decode(messageBuffer)
102
+ }
103
+
104
+ export const decryptMsmsgBotMessage = async (messageSecret, messageKey, msMsg) => {
105
+ assertRequired(messageSecret, 'messageSecret')
106
+ assertRequired(messageKey?.participant, 'participant')
107
+ assertRequired(messageKey?.meId, 'meId')
108
+ assertRequired(msMsg?.encIv, 'encIv')
109
+ assertRequired(msMsg?.encPayload, 'encPayload')
110
+ if (getCandidateIds(messageKey).length === 0) throw new Error('Missing required target message id for msmsg decryption')
111
+ const strategies = buildMsmsgDecryptionStrategies(messageKey)
112
+ const attemptedStrategies = []
113
+ let lastError
114
+ for (const strategy of strategies) {
115
+ try { return decryptWithStrategy(messageSecret, msMsg, strategy) }
116
+ catch (error) { attemptedStrategies.push({ idSource: strategy.idSource, idSources: strategy.idSources, infoSource: strategy.infoSource, aadSource: strategy.aadSource, messageId: strategy.messageId }); lastError = error }
117
+ }
118
+ const error = new Error('Failed to decrypt msmsg with bounded deterministic strategies')
119
+ error.attemptedStrategies = attemptedStrategies
120
+ error.cause = lastError
121
+ throw error
122
+ }
@@ -0,0 +1,75 @@
1
+ import { proto } from '../../WAProto/index.js'
2
+ import { BOT_RENDERING_CONFIG_METADATA } from '../Defaults/index.js'
3
+ import { BotCapabilityType, buildBotCapabilityMetadata } from './bot-capabilities.js'
4
+ import { delay } from './generics.js'
5
+
6
+ export const PlanningStepStatus = { IN_PROGRESS: 0, DONE: 1, FAILED: 2 }
7
+
8
+ export const buildProgressIndicator = (description, steps = [], estimatedMs) => {
9
+ const stepsMetadata = steps.map(s => {
10
+ const m = { statusTitle: s.title, status: s.status ?? PlanningStepStatus.IN_PROGRESS }
11
+ if (s.body) m.statusBody = s.body
12
+ if (s.isReasoning) m.isReasoning = true
13
+ if (s.isEnhancedSearch) m.isEnhancedSearch = true
14
+ return m
15
+ })
16
+ const indicator = { stepsMetadata }
17
+ if (description) indicator.progressDescription = description
18
+ if (estimatedMs != null) indicator.estimatedCompletionTime = estimatedMs
19
+ return indicator
20
+ }
21
+
22
+ export const buildCompositingPlaceholder = ({ description = 'Thinking…', steps = [], estimatedMs, placeholderText = '', verificationMetadata } = {}) => {
23
+ const progressIndicatorMetadata = buildProgressIndicator(description, steps, estimatedMs)
24
+ const unifiedData = new TextEncoder().encode(JSON.stringify({
25
+ response_id: crypto.randomUUID(),
26
+ sections: placeholderText ? [{ view_model: { primitive: { text: placeholderText, inline_entities: [], __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } }] : []
27
+ }))
28
+ const capabilities = [BotCapabilityType.PROGRESS_INDICATOR, ...(steps.length ? [BotCapabilityType.AGENTIC_PLANNING] : [])]
29
+ return {
30
+ messageContextInfo: {
31
+ botMetadata: {
32
+ capabilityMetadata: buildBotCapabilityMetadata(capabilities),
33
+ pluginMetadata: {},
34
+ progressIndicatorMetadata,
35
+ botRenderingConfigMetadata: BOT_RENDERING_CONFIG_METADATA,
36
+ ...(verificationMetadata ? { verificationMetadata } : {}),
37
+ }
38
+ },
39
+ botForwardedMessage: {
40
+ message: {
41
+ richResponseMessage: {
42
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
43
+ unifiedResponse: { data: unifiedData },
44
+ submessages: [],
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ export const buildPlainPlaceholder = (description = 'Thinking…', steps = [], placeholderText = '') => {
52
+ const lines = steps.map(s => {
53
+ const icon = s.status === PlanningStepStatus.DONE ? '✓' : s.status === PlanningStepStatus.FAILED ? '✗' : '○'
54
+ return `${icon} ${s.title}`
55
+ }).join('\n')
56
+ let text = `_${description}_`
57
+ if (lines) text += `\n\n${lines}`
58
+ if (placeholderText) text += `\n\n${placeholderText}`
59
+ return { text }
60
+ }
61
+
62
+ export const metaTyping = async (sock, jid, { description = 'Thinking…', steps = [], estimatedMs, placeholderText = '' } = {}) => {
63
+ await sock.sendPresenceUpdate('composing', jid)
64
+ const pending = steps.map(s => ({ ...s, status: PlanningStepStatus.IN_PROGRESS }))
65
+ return sock.sendMessage(jid, buildPlainPlaceholder(description, pending, placeholderText))
66
+ }
67
+
68
+ export const sendMetaComposited = async (sock, jid, content, { thinkingMs = 2000, description = 'Thinking…', steps = [], placeholderText = '', sendOptions = {} } = {}) => {
69
+ const placeholder = await metaTyping(sock, jid, { description, steps, estimatedMs: thinkingMs, placeholderText })
70
+ try { await delay(thinkingMs); if (placeholder?.key) await sock.sendMessage(jid, { delete: placeholder.key }) } catch (_) { }
71
+ await sock.sendPresenceUpdate('paused', jid)
72
+ return sock.sendMessage(jid, content, sendOptions)
73
+ }
74
+
75
+ export const buildSteps = (titles, status = PlanningStepStatus.IN_PROGRESS) => titles.map(title => ({ title, status }))
@@ -0,0 +1,235 @@
1
+ import { proto } from '../../WAProto/index.js'
2
+
3
+ const requireObject = (value, name) => {
4
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new TypeError(`${name} must be an object`)
5
+ return value
6
+ }
7
+
8
+ const requireKey = (value, name = 'key') => {
9
+ requireObject(value, name)
10
+ if (!value.id) throw new TypeError(`${name}.id is required`)
11
+ return value
12
+ }
13
+
14
+ const enumValue = (enumObject, value, fallback) => {
15
+ if (typeof value === 'number') return value
16
+ if (typeof value === 'string') { const key = value.trim().toUpperCase(); if (key in enumObject) return enumObject[key] }
17
+ return fallback
18
+ }
19
+
20
+ export const STATUS_NOTIFICATION_TYPES = Object.freeze({ ...proto.Message.StatusNotificationMessage.StatusNotificationType, STATUS_GROUP_STATUS_REPLY: 4 })
21
+
22
+ const toSeconds = value => value instanceof Date ? Math.floor(value.getTime() / 1000) : value
23
+ const toMilliseconds = value => value instanceof Date ? value.getTime() : value
24
+
25
+ const makeInnerCommentMessage = content => {
26
+ const data = requireObject(content, 'comment.content')
27
+ if (data.raw && typeof data.raw === 'object') return data.raw
28
+ if (typeof data.text === 'string') return { extendedTextMessage: proto.Message.ExtendedTextMessage.create({ text: data.text, contextInfo: data.contextInfo }) }
29
+ const keys = Object.keys(data)
30
+ if (keys.some(key => key.endsWith('Message'))) return data
31
+ throw new TypeError('comment.content supports text, raw, or protobuf message fields')
32
+ }
33
+
34
+ export const makeQuestionMessage = input => {
35
+ const data = requireObject(input, 'question')
36
+ if (typeof data.text !== 'string' || !data.text.length) throw new TypeError('question.text is required')
37
+ return { questionMessage: proto.Message.FutureProofMessage.create({ message: { extendedTextMessage: proto.Message.ExtendedTextMessage.create({ text: data.text, contextInfo: { ...(data.contextInfo || {}), isQuestion: true } }) } }) }
38
+ }
39
+
40
+ export const makeQuestionResponseMessage = input => {
41
+ const data = requireObject(input, 'questionResponse')
42
+ requireKey(data.key, 'questionResponse.key')
43
+ return { questionResponseMessage: proto.Message.QuestionResponseMessage.create({ key: data.key, text: data.text ?? '' }) }
44
+ }
45
+
46
+ export const makeQuestionReplyMessage = input => {
47
+ const data = requireObject(input, 'questionReply')
48
+ if (typeof data.text !== 'string' || !data.text.length) throw new TypeError('questionReply.text is required')
49
+ if (data.serverQuestionId === undefined || data.serverQuestionId === null) throw new TypeError('questionReply.serverQuestionId is required')
50
+ const quoted = { serverQuestionId: Number(data.serverQuestionId) }
51
+ if (data.quotedQuestion) quoted.quotedQuestion = data.quotedQuestion
52
+ if (data.quotedResponse) quoted.quotedResponse = data.quotedResponse
53
+ return { questionReplyMessage: proto.Message.FutureProofMessage.create({ message: { extendedTextMessage: proto.Message.ExtendedTextMessage.create({ text: data.text, contextInfo: { ...(data.contextInfo || {}), questionReplyQuotedMessage: quoted } }) } }) }
54
+ }
55
+
56
+ export const makeStatusQuestionAnswerMessage = input => {
57
+ const data = requireObject(input, 'statusQuestionAnswer')
58
+ requireKey(data.key, 'statusQuestionAnswer.key')
59
+ return { statusQuestionAnswerMessage: proto.Message.StatusQuestionAnswerMessage.create({ key: data.key, text: data.text ?? '' }) }
60
+ }
61
+
62
+ export const makeStatusQuotedMessage = input => {
63
+ const data = requireObject(input, 'statusQuoted')
64
+ requireKey(data.originalStatusId, 'statusQuoted.originalStatusId')
65
+ const type = enumValue(proto.Message.StatusQuotedMessage.StatusQuotedMessageType, data.type, proto.Message.StatusQuotedMessage.StatusQuotedMessageType.QUESTION_ANSWER)
66
+ return { statusQuotedMessage: proto.Message.StatusQuotedMessage.create({ type, text: data.text, thumbnail: data.thumbnail, originalStatusId: data.originalStatusId }) }
67
+ }
68
+
69
+ export const makeStatusStickerInteractionMessage = input => {
70
+ const data = requireObject(input, 'statusStickerInteraction')
71
+ requireKey(data.key, 'statusStickerInteraction.key')
72
+ if (!data.stickerKey) throw new TypeError('statusStickerInteraction.stickerKey is required')
73
+ const type = enumValue(proto.Message.StatusStickerInteractionMessage.StatusStickerType, data.type, proto.Message.StatusStickerInteractionMessage.StatusStickerType.REACTION)
74
+ return { statusStickerInteractionMessage: proto.Message.StatusStickerInteractionMessage.create({ key: data.key, stickerKey: data.stickerKey, type }) }
75
+ }
76
+
77
+ export const makeStatusNotificationMessage = input => {
78
+ const data = requireObject(input, 'statusNotification')
79
+ requireKey(data.responseMessageKey, 'statusNotification.responseMessageKey')
80
+ requireKey(data.originalMessageKey, 'statusNotification.originalMessageKey')
81
+ const type = enumValue(STATUS_NOTIFICATION_TYPES, data.type, STATUS_NOTIFICATION_TYPES.UNKNOWN)
82
+ return { statusNotificationMessage: proto.Message.StatusNotificationMessage.create({ responseMessageKey: data.responseMessageKey, originalMessageKey: data.originalMessageKey, type }) }
83
+ }
84
+
85
+ export const makeNewsletterAdminInviteMessage = input => {
86
+ const data = requireObject(input, 'newsletterAdminInvite')
87
+ if (!data.newsletterJid) throw new TypeError('newsletterAdminInvite.newsletterJid is required')
88
+ return { newsletterAdminInviteMessage: proto.Message.NewsletterAdminInviteMessage.create({ newsletterJid: data.newsletterJid, newsletterName: data.newsletterName, jpegThumbnail: data.jpegThumbnail, caption: data.caption, inviteExpiration: data.inviteExpiration, contextInfo: data.contextInfo }) }
89
+ }
90
+
91
+ export const makeNewsletterFollowerInviteMessage = input => {
92
+ const data = requireObject(input, 'newsletterFollowerInvite')
93
+ if (!data.newsletterJid) throw new TypeError('newsletterFollowerInvite.newsletterJid is required')
94
+ return { newsletterFollowerInviteMessageV2: proto.Message.NewsletterFollowerInviteMessage.create({ newsletterJid: data.newsletterJid, newsletterName: data.newsletterName, jpegThumbnail: data.jpegThumbnail, caption: data.caption, contextInfo: data.contextInfo }) }
95
+ }
96
+
97
+ export const makePollAddOptionMessage = input => {
98
+ const data = requireObject(input, 'pollAddOption')
99
+ requireKey(data.pollCreationMessageKey, 'pollAddOption.pollCreationMessageKey')
100
+ const addOption = typeof data.option === 'string' ? { optionName: data.option } : data.addOption || data.option
101
+ if (!addOption?.optionName) throw new TypeError('pollAddOption.option is required')
102
+ return { pollAddOptionMessage: proto.Message.PollAddOptionMessage.create({ pollCreationMessageKey: data.pollCreationMessageKey, addOption, metadata: data.metadata }) }
103
+ }
104
+
105
+ export const makeCommentMessage = input => {
106
+ const data = requireObject(input, 'comment')
107
+ requireKey(data.targetMessageKey, 'comment.targetMessageKey')
108
+ const message = data.message || (data.content ? makeInnerCommentMessage(data.content) : null)
109
+ requireObject(message, 'comment.message')
110
+ return { commentMessage: proto.Message.CommentMessage.create({ message, targetMessageKey: data.targetMessageKey }) }
111
+ }
112
+
113
+ export const makeEventInviteMessage = input => {
114
+ const data = requireObject(input, 'eventInvite')
115
+ if (!data.eventId) throw new TypeError('eventInvite.eventId is required')
116
+ return { eventInviteMessage: proto.Message.EventInviteMessage.create({ contextInfo: data.contextInfo, eventId: data.eventId, eventTitle: data.eventTitle, jpegThumbnail: data.jpegThumbnail, startTime: toSeconds(data.startTime), caption: data.caption, isCanceled: data.isCanceled, endTime: toSeconds(data.endTime), callLink: data.callLink }) }
117
+ }
118
+
119
+ export const makeScheduledCallCreationMessage = input => {
120
+ const data = requireObject(input, 'scheduledCall')
121
+ if (!data.scheduledTimestampMs) throw new TypeError('scheduledCall.scheduledTimestampMs is required')
122
+ const callType = enumValue(proto.Message.ScheduledCallCreationMessage.CallType, data.callType, proto.Message.ScheduledCallCreationMessage.CallType.UNKNOWN)
123
+ return { scheduledCallCreationMessage: proto.Message.ScheduledCallCreationMessage.create({ scheduledTimestampMs: toMilliseconds(data.scheduledTimestampMs), callType, title: data.title }) }
124
+ }
125
+
126
+ export const makeScheduledCallEditMessage = input => {
127
+ const data = requireObject(input, 'scheduledCallEdit')
128
+ requireKey(data.key, 'scheduledCallEdit.key')
129
+ const editType = enumValue(proto.Message.ScheduledCallEditMessage.EditType, data.editType, proto.Message.ScheduledCallEditMessage.EditType.CANCEL)
130
+ return { scheduledCallEditMessage: proto.Message.ScheduledCallEditMessage.create({ key: data.key, editType }) }
131
+ }
132
+
133
+ export const makeGroupStatusReactionMessage = input => {
134
+ const data = requireObject(input, 'groupStatusReaction')
135
+ requireKey(data.key, 'groupStatusReaction.key')
136
+ return { groupStatusMessageV2: proto.Message.FutureProofMessage.create({ message: { reactionMessage: proto.Message.ReactionMessage.create({ key: data.key, text: data.text ?? '', groupingKey: data.groupingKey, senderTimestampMs: data.senderTimestampMs || Date.now() }) } }) }
137
+ }
138
+
139
+ export const makeNewsletterStatusAttribution = input => {
140
+ const data = requireObject(input, 'newsletterStatus')
141
+ if (!data.newsletterJid) throw new TypeError('newsletterStatus.newsletterJid is required')
142
+ if (data.messageId === undefined || data.messageId === null) throw new TypeError('newsletterStatus.messageId is required')
143
+ return proto.StatusAttribution.create({ type: proto.StatusAttribution.Type.NEWSLETTER_STATUS, actionUrl: data.actionUrl, statusReshare: { source: proto.StatusAttribution.StatusReshare.Source.CHANNEL_RESHARE, metadata: { duration: data.duration, channelJid: data.newsletterJid, channelMessageId: Number(data.messageId), hasMultipleReshares: data.hasMultipleReshares ?? false } } })
144
+ }
145
+
146
+ export const makeGroupStatusAttribution = input => {
147
+ const data = requireObject(input, 'groupStatusAttribution')
148
+ if (!data.authorJid) throw new TypeError('groupStatusAttribution.authorJid is required')
149
+ return proto.StatusAttribution.create({ type: proto.StatusAttribution.Type.GROUP_STATUS, actionUrl: data.actionUrl, groupStatus: { authorJid: data.authorJid } })
150
+ }
151
+
152
+ export const STATUS_AUDIENCE_DEFAULT_LIST_NAME = 'Close friends'
153
+ export const STATUS_AUDIENCE_DEFAULT_EMOJI = '⭐'
154
+
155
+ export const makeStatusAudienceMetadata = input => {
156
+ const data = typeof input === 'string' ? { listName: input } : requireObject(input, 'statusAudience')
157
+ const audienceType = enumValue(proto.ContextInfo.StatusAudienceMetadata.AudienceType, data.audienceType, proto.ContextInfo.StatusAudienceMetadata.AudienceType.CLOSE_FRIENDS)
158
+ const listName = data.listName === undefined ? STATUS_AUDIENCE_DEFAULT_LIST_NAME : String(data.listName)
159
+ const listEmoji = data.listEmoji === undefined ? STATUS_AUDIENCE_DEFAULT_EMOJI : String(data.listEmoji)
160
+ return proto.ContextInfo.StatusAudienceMetadata.create({ audienceType, listName, listEmoji })
161
+ }
162
+
163
+ export const makeMessageAssociation = input => {
164
+ const data = requireObject(input, 'messageAssociation')
165
+ const parentMessageKey = data.parentMessageKey ?? data.key
166
+ requireKey(parentMessageKey, 'messageAssociation.parentMessageKey')
167
+ const associationType = enumValue(proto.MessageAssociation.AssociationType, data.associationType ?? data.type, proto.MessageAssociation.AssociationType.UNKNOWN)
168
+ return proto.MessageAssociation.create({ associationType, parentMessageKey, messageIndex: data.messageIndex })
169
+ }
170
+
171
+ export const makeStatusAddYoursAssociation = input => {
172
+ const data = input && typeof input === 'object' && !Array.isArray(input) && input.id ? { key: input } : requireObject(input, 'addYours')
173
+ const parentMessageKey = data.key ?? data.parentMessageKey
174
+ requireKey(parentMessageKey, 'addYours.key')
175
+ const associationType = enumValue(proto.MessageAssociation.AssociationType, data.type ?? data.associationType, proto.MessageAssociation.AssociationType.STATUS_ADD_YOURS)
176
+ return proto.MessageAssociation.create({ associationType, parentMessageKey, messageIndex: data.messageIndex })
177
+ }
178
+
179
+ export const makeStatusMentionMessage = input => {
180
+ const data = requireObject(input, 'statusMention')
181
+ requireKey(data.key, 'statusMention.key')
182
+ const field = data.group ? 'groupStatusMentionMessage' : 'statusMentionMessage'
183
+ return { [field]: proto.Message.FutureProofMessage.create({ message: { protocolMessage: proto.Message.ProtocolMessage.create({ key: data.key, type: proto.Message.ProtocolMessage.Type.STATUS_MENTION_MESSAGE }) } }) }
184
+ }
185
+
186
+ const modernBuilders = [
187
+ ['question', makeQuestionMessage],
188
+ ['questionReply', makeQuestionReplyMessage],
189
+ ['questionResponse', makeQuestionResponseMessage],
190
+ ['statusQuestionAnswer', makeStatusQuestionAnswerMessage],
191
+ ['statusQuoted', makeStatusQuotedMessage],
192
+ ['statusStickerInteraction', makeStatusStickerInteractionMessage],
193
+ ['statusNotification', makeStatusNotificationMessage],
194
+ ['newsletterAdminInvite', makeNewsletterAdminInviteMessage],
195
+ ['newsletterFollowerInvite', makeNewsletterFollowerInviteMessage],
196
+ ['pollAddOption', makePollAddOptionMessage],
197
+ ['comment', makeCommentMessage],
198
+ ['eventInvite', makeEventInviteMessage],
199
+ ['scheduledCall', makeScheduledCallCreationMessage],
200
+ ['scheduledCallEdit', makeScheduledCallEditMessage],
201
+ ['groupStatusReaction', makeGroupStatusReactionMessage],
202
+ ['statusMention', makeStatusMentionMessage]
203
+ ]
204
+
205
+ export const prepareModernMessageContent = content => {
206
+ if (!content || typeof content !== 'object' || Array.isArray(content)) return content
207
+ let output = { ...content }
208
+ const attributions = Array.isArray(output.contextInfo?.statusAttributions) ? [...output.contextInfo.statusAttributions] : []
209
+ if (output.newsletterStatus) { attributions.push(makeNewsletterStatusAttribution(output.newsletterStatus)); delete output.newsletterStatus }
210
+ if (output.groupStatusAttribution) { attributions.push(makeGroupStatusAttribution(output.groupStatusAttribution)); delete output.groupStatusAttribution }
211
+ if (Array.isArray(output.statusAttributions)) { attributions.push(...output.statusAttributions); delete output.statusAttributions }
212
+ const carried = []
213
+ const audience = output.statusAudience
214
+ if (audience !== undefined && audience !== null) {
215
+ carried.push('statusAudience')
216
+ output.contextInfo = { ...(output.contextInfo || {}), statusAudienceMetadata: makeStatusAudienceMetadata(audience) }
217
+ delete output.statusAudience
218
+ }
219
+ if (attributions.length) output.contextInfo = { ...(output.contextInfo || {}), statusAttributions: attributions }
220
+ if (output.addYours !== undefined && output.addYours !== null) {
221
+ carried.push('addYours')
222
+ output.messageAssociation = makeStatusAddYoursAssociation(output.addYours)
223
+ delete output.addYours
224
+ } else if (output.messageAssociation !== undefined && output.messageAssociation !== null) {
225
+ carried.push('messageAssociation')
226
+ output.messageAssociation = makeMessageAssociation(output.messageAssociation)
227
+ }
228
+ for (const [key, builder] of modernBuilders) {
229
+ if (output[key] !== undefined && output[key] !== null) {
230
+ if (carried.length) throw new TypeError(`${carried.join(' and ')} cannot be combined with ${key}: ${key} builds its own message and keeps no room for them. Build it with the low-level makers and generateWAMessageFromContent instead.`)
231
+ return { raw: true, ...builder(output[key]) }
232
+ }
233
+ }
234
+ return output
235
+ }
@@ -0,0 +1,55 @@
1
+ const parseJsonObject = v => {
2
+ if (!v) return {}
3
+ if (typeof v === 'object') return v
4
+ try { const p = JSON.parse(v); return p && typeof p === 'object' ? p : {} } catch { return {} }
5
+ }
6
+
7
+ export const parseNativeFlowResponse = message => {
8
+ const content = message?.message || message
9
+ const response = content?.interactiveResponseMessage?.nativeFlowResponseMessage || content?.nativeFlowResponseMessage
10
+ if (!response) return null
11
+ const params = parseJsonObject(response.paramsJson)
12
+ const actionPayload = params.flow_action_payload || params.flowActionPayload || {}
13
+ const data = params.data || params.response || params.form_data || params.formData || actionPayload.data || {}
14
+ return {
15
+ name: response.name || '',
16
+ version: response.version,
17
+ paramsJson: response.paramsJson || '',
18
+ params,
19
+ isFlow: response.name === 'galaxy_message' || response.name === 'flow' || !!(params.flow_id || params.flow_token || params.flow_message_version),
20
+ flowId: params.flow_id || params.flowId,
21
+ flowToken: params.flow_token || params.flowToken,
22
+ screen: params.screen || actionPayload.screen,
23
+ action: params.flow_action || params.flowAction || actionPayload.action,
24
+ actionPayload,
25
+ data,
26
+ buttonId: params.id || params.button_id || params.buttonId,
27
+ displayText: params.display_text || params.displayText,
28
+ raw: response,
29
+ }
30
+ }
31
+
32
+ export const parseWhatsAppFlowResponse = message => {
33
+ const response = parseNativeFlowResponse(message)
34
+ return response?.isFlow ? response : null
35
+ }
36
+
37
+ export const buildWhatsAppFlowButton = ({ flowId, flowToken, cta = 'Open form', action = 'navigate', actionPayload, mode = 'published', flowMessageVersion = '3', metadata, name = 'galaxy_message' } = {}) => {
38
+ if (!flowId) throw new TypeError('flowId is required')
39
+ if (!flowToken) throw new TypeError('flowToken is required')
40
+ return {
41
+ name,
42
+ flow_message_version: String(flowMessageVersion),
43
+ flow_token: String(flowToken),
44
+ flow_id: String(flowId),
45
+ flow_cta: String(cta),
46
+ flow_action: action,
47
+ ...(actionPayload ? { flow_action_payload: actionPayload } : {}),
48
+ ...(metadata ? { flow_metadata: metadata } : {}),
49
+ }
50
+ }
51
+
52
+ export const makeWhatsAppFlowButton = flow => ({
53
+ name: flow?.nativeFlowName || 'galaxy_message',
54
+ buttonParamsJson: JSON.stringify(buildWhatsAppFlowButton(flow)),
55
+ })
@@ -0,0 +1,161 @@
1
+ import { Boom } from '@hapi/boom'
2
+ import { proto } from '../../WAProto/index.js'
3
+ import { NOISE_MODE, WA_CERT_DETAILS } from '../Defaults/index.js'
4
+ import { decodeBinaryNode } from '../WABinary/index.js'
5
+ import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, sha256 } from './crypto.js'
6
+
7
+ const IV_LENGTH = 12
8
+ const EMPTY_BUFFER = Buffer.alloc(0)
9
+
10
+ class TransportState {
11
+ constructor(encKey, decKey) {
12
+ this.encKey = encKey
13
+ this.decKey = decKey
14
+ this.readCounter = 0
15
+ this.writeCounter = 0
16
+ this.iv = new Uint8Array(IV_LENGTH)
17
+ }
18
+
19
+ #writeIV(c) {
20
+ this.iv[8] = (c >>> 24) & 0xff
21
+ this.iv[9] = (c >>> 16) & 0xff
22
+ this.iv[10] = (c >>> 8) & 0xff
23
+ this.iv[11] = c & 0xff
24
+ }
25
+
26
+ encrypt(plaintext) { this.#writeIV(this.writeCounter++); return aesEncryptGCM(plaintext, this.encKey, this.iv, EMPTY_BUFFER) }
27
+ decrypt(ciphertext) { this.#writeIV(this.readCounter++); return aesDecryptGCM(ciphertext, this.decKey, this.iv, EMPTY_BUFFER) }
28
+ }
29
+
30
+ const buildIntroHeader = (NOISE_HEADER, routingInfo) => {
31
+ if (!routingInfo) return Buffer.from(NOISE_HEADER)
32
+ const buf = Buffer.alloc(7 + routingInfo.byteLength + NOISE_HEADER.length)
33
+ buf.write('ED', 0, 'utf8')
34
+ buf.writeUint8(0, 2)
35
+ buf.writeUint8(1, 3)
36
+ buf.writeUint8(routingInfo.byteLength >> 16, 4)
37
+ buf.writeUint16BE(routingInfo.byteLength & 65535, 5)
38
+ buf.set(routingInfo, 7)
39
+ buf.set(NOISE_HEADER, 7 + routingInfo.byteLength)
40
+ return buf
41
+ }
42
+
43
+ export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, logger, routingInfo }) => {
44
+ logger = logger.child({ class: 'ns' })
45
+
46
+ const data = Buffer.from(NOISE_MODE)
47
+ let hash = data.byteLength === 32 ? data : sha256(data)
48
+ let salt = hash
49
+ let encKey = hash
50
+ let decKey = hash
51
+ let counter = 0
52
+ let sentIntro = false
53
+
54
+ let inBytes = Buffer.alloc(0)
55
+ let transport = null
56
+ let isWaitingForTransport = false
57
+ let pendingOnFrame = null
58
+
59
+ const introHeader = buildIntroHeader(NOISE_HEADER, routingInfo)
60
+
61
+ const generateIV = c => {
62
+ const iv = new ArrayBuffer(IV_LENGTH)
63
+ new DataView(iv).setUint32(8, c)
64
+ return new Uint8Array(iv)
65
+ }
66
+
67
+ const authenticate = d => { if (!transport) hash = sha256(Buffer.concat([hash, d])) }
68
+
69
+ const localHKDF = d => {
70
+ const key = hkdf(Buffer.from(d), 64, { salt, info: '' })
71
+ return [key.subarray(0, 32), key.subarray(32)]
72
+ }
73
+
74
+ const mixIntoKey = d => {
75
+ const [write, read] = localHKDF(d)
76
+ salt = write; encKey = read; decKey = read; counter = 0
77
+ }
78
+
79
+ const encrypt = plaintext => {
80
+ if (transport) return transport.encrypt(plaintext)
81
+ const result = aesEncryptGCM(plaintext, encKey, generateIV(counter++), hash)
82
+ authenticate(result)
83
+ return result
84
+ }
85
+
86
+ const decrypt = ciphertext => {
87
+ if (transport) return transport.decrypt(ciphertext)
88
+ const result = aesDecryptGCM(ciphertext, decKey, generateIV(counter++), hash)
89
+ authenticate(ciphertext)
90
+ return result
91
+ }
92
+
93
+ const processData = async onFrame => {
94
+ while (inBytes.length >= 3) {
95
+ const size = (inBytes[0] << 16) | (inBytes[1] << 8) | inBytes[2]
96
+ if (inBytes.length < size + 3) return
97
+ let frame = inBytes.subarray(3, size + 3)
98
+ inBytes = inBytes.subarray(size + 3)
99
+ if (transport) frame = await decodeBinaryNode(transport.decrypt(frame))
100
+ onFrame(frame)
101
+ }
102
+ }
103
+
104
+ const finishInit = async () => {
105
+ isWaitingForTransport = true
106
+ const [write, read] = localHKDF(new Uint8Array(0))
107
+ transport = new TransportState(write, read)
108
+ isWaitingForTransport = false
109
+ if (pendingOnFrame) { await processData(pendingOnFrame); pendingOnFrame = null }
110
+ }
111
+
112
+ authenticate(NOISE_HEADER)
113
+ authenticate(publicKey)
114
+
115
+ return {
116
+ encrypt,
117
+ decrypt,
118
+ authenticate,
119
+ mixIntoKey,
120
+ finishInit,
121
+ processHandshake: ({ serverHello }, noiseKey) => {
122
+ authenticate(serverHello.ephemeral)
123
+ mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral))
124
+
125
+ const decStaticContent = decrypt(serverHello.static)
126
+ mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))
127
+
128
+ const { intermediate: certIntermediate, leaf } = proto.CertChain.decode(decrypt(serverHello.payload))
129
+
130
+ if (!leaf?.details || !leaf?.signature) throw new Boom('invalid noise leaf certificate', { statusCode: 400 })
131
+ if (!certIntermediate?.details || !certIntermediate?.signature) throw new Boom('invalid noise intermediate certificate', { statusCode: 400 })
132
+
133
+ const details = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details)
134
+
135
+ if (!Curve.verify(details.key, leaf.details, leaf.signature)) throw new Boom('noise certificate signature invalid', { statusCode: 400 })
136
+ if (!Curve.verify(WA_CERT_DETAILS.PUBLIC_KEY, certIntermediate.details, certIntermediate.signature)) throw new Boom('noise intermediate certificate signature invalid', { statusCode: 400 })
137
+ if (details.issuerSerial !== WA_CERT_DETAILS.SERIAL) throw new Boom('certification match failed', { statusCode: 400 })
138
+
139
+ const keyEnc = encrypt(noiseKey.public)
140
+ mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello.ephemeral))
141
+ return keyEnc
142
+ },
143
+ encodeFrame: data => {
144
+ if (transport) data = transport.encrypt(data)
145
+ const dataLen = data.byteLength
146
+ const introSize = sentIntro ? 0 : introHeader.length
147
+ const frame = Buffer.allocUnsafe(introSize + 3 + dataLen)
148
+ if (!sentIntro) { frame.set(introHeader); sentIntro = true }
149
+ frame[introSize] = (dataLen >>> 16) & 0xff
150
+ frame[introSize + 1] = (dataLen >>> 8) & 0xff
151
+ frame[introSize + 2] = dataLen & 0xff
152
+ frame.set(data, introSize + 3)
153
+ return frame
154
+ },
155
+ decodeFrame: async (newData, onFrame) => {
156
+ if (isWaitingForTransport) { inBytes = Buffer.concat([inBytes, newData]); pendingOnFrame = onFrame; return }
157
+ inBytes = inBytes.length === 0 ? Buffer.from(newData) : Buffer.concat([inBytes, newData])
158
+ await processData(onFrame)
159
+ },
160
+ }
161
+ }