sauruspro-bail 0.0.0-stage → 5.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 (160) hide show
  1. package/README.md +128 -2
  2. package/WAProto/GenerateStatics.sh +3 -0
  3. package/WAProto/WAProto.proto +6754 -0
  4. package/WAProto/fix-imports.js +85 -0
  5. package/WAProto/index.d.ts +79257 -0
  6. package/WAProto/index.js +242946 -0
  7. package/docs/CHANGELOG.md +43 -0
  8. package/docs/NEWSLETTER-STATUS.md +120 -0
  9. package/docs/SPR.md +51 -0
  10. package/engine-requirements.js +10 -0
  11. package/lib/Defaults/index.js +135 -0
  12. package/lib/Signal/Group/ciphertext-message.js +12 -0
  13. package/lib/Signal/Group/group-session-builder.js +30 -0
  14. package/lib/Signal/Group/group_cipher.js +82 -0
  15. package/lib/Signal/Group/index.js +12 -0
  16. package/lib/Signal/Group/keyhelper.js +18 -0
  17. package/lib/Signal/Group/sender-chain-key.js +26 -0
  18. package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
  19. package/lib/Signal/Group/sender-key-message.js +66 -0
  20. package/lib/Signal/Group/sender-key-name.js +48 -0
  21. package/lib/Signal/Group/sender-key-record.js +41 -0
  22. package/lib/Signal/Group/sender-key-state.js +84 -0
  23. package/lib/Signal/Group/sender-message-key.js +26 -0
  24. package/lib/Signal/libsignal.js +431 -0
  25. package/lib/Signal/lid-mapping.js +277 -0
  26. package/lib/Socket/Client/index.js +3 -0
  27. package/lib/Socket/Client/types.js +11 -0
  28. package/lib/Socket/Client/websocket.js +54 -0
  29. package/lib/Socket/MessageBuilder.js +3729 -0
  30. package/lib/Socket/business.js +379 -0
  31. package/lib/Socket/chats.js +1198 -0
  32. package/lib/Socket/communities.js +431 -0
  33. package/lib/Socket/dinonic-handler.d.ts +27 -0
  34. package/lib/Socket/dinonic-handler.js +882 -0
  35. package/lib/Socket/graphql.js +716 -0
  36. package/lib/Socket/groups.js +374 -0
  37. package/lib/Socket/index.js +96 -0
  38. package/lib/Socket/interop.js +463 -0
  39. package/lib/Socket/luxu.js +387 -0
  40. package/lib/Socket/message-builder.js +556 -0
  41. package/lib/Socket/messages-recv.js +1942 -0
  42. package/lib/Socket/messages-send.js +1541 -0
  43. package/lib/Socket/mex.js +54 -0
  44. package/lib/Socket/newsletter.js +641 -0
  45. package/lib/Socket/privacy.js +318 -0
  46. package/lib/Socket/socket.js +1115 -0
  47. package/lib/Socket/username.js +236 -0
  48. package/lib/Store/index.js +10 -0
  49. package/lib/Store/keyed-db.js +108 -0
  50. package/lib/Store/make-cache-manager-store.js +85 -0
  51. package/lib/Store/make-in-memory-store.js +244 -0
  52. package/lib/Store/make-ordered-dictionary.js +75 -0
  53. package/lib/Store/object-repository.js +32 -0
  54. package/lib/Types/Auth.js +2 -0
  55. package/lib/Types/Bussines.js +2 -0
  56. package/lib/Types/Call.js +2 -0
  57. package/lib/Types/Chat.js +8 -0
  58. package/lib/Types/Contact.js +2 -0
  59. package/lib/Types/Events.js +2 -0
  60. package/lib/Types/GroupMetadata.js +2 -0
  61. package/lib/Types/Label.js +25 -0
  62. package/lib/Types/LabelAssociation.js +7 -0
  63. package/lib/Types/Message.js +11 -0
  64. package/lib/Types/Mex.js +111 -0
  65. package/lib/Types/Product.js +2 -0
  66. package/lib/Types/Signal.js +2 -0
  67. package/lib/Types/Socket.js +3 -0
  68. package/lib/Types/State.js +56 -0
  69. package/lib/Types/USync.js +2 -0
  70. package/lib/Types/index.js +26 -0
  71. package/lib/Utils/MessageBuilder.d.ts +215 -0
  72. package/lib/Utils/MessageBuilder.js +3991 -0
  73. package/lib/Utils/a2ui.d.ts +202 -0
  74. package/lib/Utils/a2ui.js +732 -0
  75. package/lib/Utils/airich-registry.js +3 -0
  76. package/lib/Utils/auth-utils.js +302 -0
  77. package/lib/Utils/baileys-event-stream.js +58 -0
  78. package/lib/Utils/bot-signature.d.ts +22 -0
  79. package/lib/Utils/bot-signature.js +124 -0
  80. package/lib/Utils/browser-utils.js +48 -0
  81. package/lib/Utils/business.js +231 -0
  82. package/lib/Utils/chat-utils.js +872 -0
  83. package/lib/Utils/companion-reg-client-utils.js +35 -0
  84. package/lib/Utils/crypto.js +118 -0
  85. package/lib/Utils/decode-wa-message.js +350 -0
  86. package/lib/Utils/dinonic-spr.d.ts +26 -0
  87. package/lib/Utils/dinonic-spr.js +521 -0
  88. package/lib/Utils/event-buffer.js +622 -0
  89. package/lib/Utils/extras.d.ts +449 -0
  90. package/lib/Utils/extras.js +1377 -0
  91. package/lib/Utils/generics.js +403 -0
  92. package/lib/Utils/history.js +134 -0
  93. package/lib/Utils/html-app.d.ts +39 -0
  94. package/lib/Utils/html-app.js +291 -0
  95. package/lib/Utils/identity-change-handler.js +50 -0
  96. package/lib/Utils/index.js +38 -0
  97. package/lib/Utils/link-preview.js +85 -0
  98. package/lib/Utils/logger.js +3 -0
  99. package/lib/Utils/lt-hash.js +8 -0
  100. package/lib/Utils/make-mutex.js +33 -0
  101. package/lib/Utils/message-composer.js +273 -0
  102. package/lib/Utils/message-retry-manager.js +265 -0
  103. package/lib/Utils/messages-media.js +870 -0
  104. package/lib/Utils/messages.js +1394 -0
  105. package/lib/Utils/metaai.d.ts +438 -0
  106. package/lib/Utils/metaai.js +911 -0
  107. package/lib/Utils/modern-messages.d.ts +150 -0
  108. package/lib/Utils/modern-messages.js +496 -0
  109. package/lib/Utils/native-flow.d.ts +14 -0
  110. package/lib/Utils/native-flow.js +118 -0
  111. package/lib/Utils/newsletter-status.d.ts +163 -0
  112. package/lib/Utils/newsletter-status.js +694 -0
  113. package/lib/Utils/noise-handler.js +201 -0
  114. package/lib/Utils/offline-node-processor.js +40 -0
  115. package/lib/Utils/payment-messages.d.ts +59 -0
  116. package/lib/Utils/payment-messages.js +156 -0
  117. package/lib/Utils/pre-key-manager.js +106 -0
  118. package/lib/Utils/process-message.js +630 -0
  119. package/lib/Utils/reporting-utils.js +258 -0
  120. package/lib/Utils/scheduled-message.d.ts +82 -0
  121. package/lib/Utils/scheduled-message.js +113 -0
  122. package/lib/Utils/signal.js +201 -0
  123. package/lib/Utils/stanza-ack.js +38 -0
  124. package/lib/Utils/status-stickers.d.ts +78 -0
  125. package/lib/Utils/status-stickers.js +261 -0
  126. package/lib/Utils/sticker.js +133 -0
  127. package/lib/Utils/sync-action-utils.js +49 -0
  128. package/lib/Utils/tc-token-utils.js +163 -0
  129. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  130. package/lib/Utils/use-sqlite-auth-state.js +162 -0
  131. package/lib/Utils/validate-connection.js +203 -0
  132. package/lib/WABinary/constants.js +1301 -0
  133. package/lib/WABinary/decode.js +262 -0
  134. package/lib/WABinary/encode.js +220 -0
  135. package/lib/WABinary/generic-utils.js +204 -0
  136. package/lib/WABinary/index.js +6 -0
  137. package/lib/WABinary/jid-utils.js +98 -0
  138. package/lib/WABinary/types.js +2 -0
  139. package/lib/WAM/BinaryInfo.js +10 -0
  140. package/lib/WAM/constants.js +22853 -0
  141. package/lib/WAM/encode.js +150 -0
  142. package/lib/WAM/index.js +4 -0
  143. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +53 -0
  144. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
  145. package/lib/WAUSync/Protocols/USyncContactProtocol.js +54 -0
  146. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
  147. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
  148. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +58 -0
  149. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +30 -0
  150. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +32 -0
  151. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +28 -0
  152. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +39 -0
  153. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +38 -0
  154. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +27 -0
  155. package/lib/WAUSync/Protocols/index.js +12 -0
  156. package/lib/WAUSync/USyncQuery.js +151 -0
  157. package/lib/WAUSync/USyncUser.js +56 -0
  158. package/lib/WAUSync/index.js +3 -0
  159. package/lib/index.js +67 -0
  160. package/package.json +147 -5
@@ -0,0 +1,694 @@
1
+ import { proto } from '../../WAProto/index.js';
2
+ import { encodeNewsletterMessage, generateMessageIDV2 } from './generics.js';
3
+ import { generateWAMessage, normalizeMessageContent } from './messages.js';
4
+ import { prepareModernMessageContent, makeNewsletterStatusAttribution } from './modern-messages.js';
5
+ import { WAMessageStatus } from '../Types/Message.js';
6
+ import { getBinaryNodeChild, getBinaryNodeChildren, S_WHATSAPP_NET } from '../WABinary/index.js';
7
+ import { isJidNewsletter } from '../WABinary/jid-utils.js';
8
+
9
+ const NEWSLETTER_STATUS_MEDIA_TYPES = new Set(['audio', 'gif', 'image', 'video'])
10
+ const NEWSLETTER_STATUS_WEB_MEDIA_TYPES = new Set(['image', 'video'])
11
+ const NEWSLETTER_STATUS_INTERACTIONS = new Set(['question', 'question_response', 'question_reshare'])
12
+ const NEWSLETTER_STATUS_CONTENT_TYPES = new Set(['text', 'media', 'reaction'])
13
+ const NEWSLETTER_STATUS_CALLBACK_EVENTS = ['CB:ack', 'CB:status', 'CB:iq', 'CB:notification', 'CB:message']
14
+ const NEWSLETTER_STATUS_EDIT_REACTION_REVOKE = '7'
15
+ const NEWSLETTER_STATUS_EDIT_ADMIN_REVOKE = '8'
16
+ const NEWSLETTER_STATUS_ACK_CLASS = 'status'
17
+ const NEWSLETTER_STATUS_ACK_TIMEOUT_MS = 20000
18
+ const NEWSLETTER_STATUS_SERVER_ID_TIMEOUT_MS = 8000
19
+
20
+ export const getNewsletterStatusMediaType = message => {
21
+ const content = normalizeMessageContent(message)
22
+ if (content?.imageMessage) return 'image'
23
+ if (content?.videoMessage) return content.videoMessage.gifPlayback ? 'gif' : 'video'
24
+ if (content?.audioMessage) return 'audio'
25
+ return undefined
26
+ }
27
+
28
+ const assertNewsletterJid = jid => {
29
+ if (!isJidNewsletter(jid)) throw new TypeError('Newsletter status target must be a @newsletter JID')
30
+ }
31
+
32
+ const assertInteraction = interactionType => {
33
+ if (interactionType !== undefined && !NEWSLETTER_STATUS_INTERACTIONS.has(interactionType)) {
34
+ throw new TypeError(`Unsupported newsletter status interaction: ${interactionType}`)
35
+ }
36
+ }
37
+
38
+ const assertMediaType = mediaType => {
39
+ if (mediaType !== undefined && !NEWSLETTER_STATUS_MEDIA_TYPES.has(mediaType)) {
40
+ throw new TypeError(`Unsupported newsletter status media type: ${mediaType}`)
41
+ }
42
+ }
43
+
44
+ const isPresent = value => value !== undefined && value !== null && value !== ''
45
+
46
+ export const NEWSLETTER_SERVER_ID_MIN = 99
47
+ export const NEWSLETTER_SERVER_ID_MAX = 2147476647
48
+
49
+ export const toNewsletterServerId = (value, label) => {
50
+ const parsed = typeof value === 'number' ? value : Number(String(value).trim())
51
+ if (!Number.isInteger(parsed) || parsed < NEWSLETTER_SERVER_ID_MIN || parsed > NEWSLETTER_SERVER_ID_MAX) {
52
+ throw new TypeError(`${JSON.stringify(value)} is not a newsletter server id${label ? ` for ${label}` : ''}; the server accepts ${NEWSLETTER_SERVER_ID_MIN}..${NEWSLETTER_SERVER_ID_MAX}, and you get one from newsletterFetchMessages or a sent status`)
53
+ }
54
+ return String(parsed)
55
+ }
56
+
57
+ const toServerId = (value, label) => toNewsletterServerId(value, label)
58
+
59
+ const buildMetaNode = ({ interactionType, parentServerId, responseServerId, aiContent }) => {
60
+ assertInteraction(interactionType)
61
+
62
+ const attrs = {}
63
+ if (interactionType) {
64
+ attrs.interaction_type = interactionType
65
+ if (interactionType === 'question_reshare') {
66
+ if (!isPresent(parentServerId)) throw new TypeError('question_reshare requires parentServerId')
67
+ if (!isPresent(responseServerId)) throw new TypeError('question_reshare requires responseServerId')
68
+ attrs.parent_server_id = toServerId(parentServerId, 'parentServerId')
69
+ attrs.response_server_id = String(responseServerId)
70
+ }
71
+ else if (interactionType === 'question_response' && isPresent(responseServerId)) {
72
+ attrs.response_server_id = String(responseServerId)
73
+ }
74
+ }
75
+
76
+ if (!interactionType && !aiContent) return undefined
77
+
78
+ return {
79
+ tag: 'meta',
80
+ attrs,
81
+ content: aiContent ? [{ tag: 'ai_content', attrs: {}, content: undefined }] : undefined
82
+ }
83
+ }
84
+
85
+ export const withNewsletterStatusAttribution = content => ({
86
+ ...content,
87
+ contextInfo: {
88
+ statusAttributions: [{ type: proto.StatusAttribution.Type.NEWSLETTER_STATUS }],
89
+ featureEligibilities: { canBeReshared: true },
90
+ ...(content?.contextInfo || {})
91
+ }
92
+ })
93
+
94
+ const snapshotCallbackNode = (event, node) => ({
95
+ event,
96
+ tag: node?.tag,
97
+ attrs: node?.attrs || {},
98
+ children: Array.isArray(node?.content)
99
+ ? node.content.map(child => ({ tag: child?.tag, attrs: child?.attrs || {} }))
100
+ : []
101
+ })
102
+
103
+ const collectStatusCallbacks = sock => {
104
+ const frames = []
105
+ const listeners = []
106
+ if (!sock?.ws?.on) {
107
+ return { frames, stop: () => {} }
108
+ }
109
+
110
+ for (const event of NEWSLETTER_STATUS_CALLBACK_EVENTS) {
111
+ const listener = node => {
112
+ if (frames.length >= 40) return
113
+ frames.push(snapshotCallbackNode(event, node))
114
+ }
115
+ sock.ws.on(event, listener)
116
+ listeners.push([event, listener])
117
+ }
118
+
119
+ return {
120
+ frames,
121
+ stop: () => {
122
+ for (const [event, listener] of listeners) {
123
+ sock.ws?.off?.(event, listener)
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ const readIntAttr = (attrs, key) => {
130
+ if (!isPresent(attrs?.[key])) return undefined
131
+ const parsed = Number.parseInt(String(attrs[key]), 10)
132
+ return Number.isFinite(parsed) ? parsed : undefined
133
+ }
134
+
135
+ export const parseNewsletterStatusAck = (node, { jid, messageId } = {}) => {
136
+ if (!node || typeof node !== 'object') throw new TypeError('Newsletter status ack node is required')
137
+ if (node.tag !== 'ack') {
138
+ const error = new Error(`Newsletter status expected <ack>, got <${node.tag}>`)
139
+ error.data = node
140
+ throw error
141
+ }
142
+
143
+ const attrs = node.attrs || {}
144
+ if (attrs.class !== undefined && attrs.class !== NEWSLETTER_STATUS_ACK_CLASS) {
145
+ const error = new Error(`Newsletter status ack has class "${attrs.class}", expected "${NEWSLETTER_STATUS_ACK_CLASS}"`)
146
+ error.data = node
147
+ throw error
148
+ }
149
+ if (isPresent(messageId) && isPresent(attrs.id) && attrs.id !== messageId) {
150
+ const error = new Error(`Newsletter status ack id mismatch: ${attrs.id} != ${messageId}`)
151
+ error.data = node
152
+ throw error
153
+ }
154
+ if (isPresent(jid) && isPresent(attrs.from) && attrs.from !== jid) {
155
+ const error = new Error(`Newsletter status ack from mismatch: ${attrs.from} != ${jid}`)
156
+ error.data = node
157
+ throw error
158
+ }
159
+
160
+ return {
161
+ class: attrs.class,
162
+ from: attrs.from,
163
+ id: attrs.id,
164
+ t: readIntAttr(attrs, 't'),
165
+ serverId: readIntAttr(attrs, 'server_id'),
166
+ edit: attrs.edit,
167
+ error: isPresent(attrs.error) ? String(attrs.error) : undefined,
168
+ applicationError: readIntAttr(attrs, 'application_error'),
169
+ backoff: readIntAttr(attrs, 'backoff'),
170
+ node
171
+ }
172
+ }
173
+
174
+ /**
175
+ * The ack carries from, class, id and t, and nothing else. The server id lands
176
+ * separately, in the <status> stanza the server sends back to the publisher --
177
+ * WASmaxInStatusDeliverFromNewsletterMixin reads from, server_id, t and
178
+ * is_sender off exactly that tag. Reacting to or revoking your own status needs
179
+ * that id, so wait a moment for the echo rather than returning undefined.
180
+ */
181
+ export const waitForNewsletterStatusServerId = (sock, { jid, messageId, timeoutMs = 8000 }) => {
182
+ let settle
183
+ const promise = new Promise(resolve => {
184
+ let done = false
185
+ const finish = value => {
186
+ if (done) return
187
+ done = true
188
+ clearTimeout(timer)
189
+ sock.ws.off('CB:status', onStatus)
190
+ resolve(value)
191
+ }
192
+ const onStatus = node => {
193
+ if (node?.tag !== 'status') return
194
+ const attrs = node.attrs || {}
195
+ if (isPresent(jid) && isPresent(attrs.from) && attrs.from !== jid) return
196
+ if (isPresent(messageId) && isPresent(attrs.id) && attrs.id !== messageId) return
197
+ const serverId = readIntAttr(attrs, 'server_id')
198
+ if (serverId !== undefined) finish({ serverId, node })
199
+ }
200
+ const timer = setTimeout(() => finish(undefined), timeoutMs)
201
+ sock.ws.on('CB:status', onStatus)
202
+ settle = finish
203
+ })
204
+ promise.cancel = () => settle(undefined)
205
+ return promise
206
+ }
207
+
208
+ const assertStatusServerResponse = (response, { jid, messageId, callbacks = [] }) => {
209
+ if (!response) {
210
+ const suffix = callbacks.length
211
+ ? `; callbacks=${JSON.stringify(callbacks)}`
212
+ : '; no ack/status/iq/notification/message callback observed'
213
+ const error = new Error(`Newsletter status server ACK timed out for ${messageId}${suffix}`)
214
+ error.data = { messageId, callbacks }
215
+ throw error
216
+ }
217
+
218
+ const ack = parseNewsletterStatusAck(response, { jid, messageId })
219
+ if (ack.error) {
220
+ const details = [
221
+ ack.applicationError !== undefined ? `application_error=${ack.applicationError}` : undefined,
222
+ ack.backoff !== undefined ? `backoff=${ack.backoff}` : undefined
223
+ ].filter(Boolean).join(', ')
224
+ const hint = ack.error === '403' || ack.error === '401' || ack.error === 'not-authorized' || ack.error === 'forbidden'
225
+ ? '; the channel may be missing the CHANNEL_STATUS_PRODUCER capability, check newsletterCanPostStatus(jid)'
226
+ : ''
227
+ const error = new Error(`Newsletter status rejected by server (${ack.error})${details ? `: ${details}` : ''}${hint}`)
228
+ error.data = ack
229
+ throw error
230
+ }
231
+
232
+ return ack
233
+ }
234
+
235
+ export const buildNewsletterAdminProfileStatusMessage = message => {
236
+ if (!message || typeof message !== 'object') throw new TypeError('Newsletter status message is required')
237
+ return proto.Message.create({
238
+ newsletterAdminProfileStatusMessage: proto.Message.FutureProofMessage.create({
239
+ message
240
+ })
241
+ })
242
+ }
243
+
244
+ const resolveStatusPayload = ({ message, payload }) => {
245
+ if (payload !== undefined) {
246
+ if (!(payload instanceof Uint8Array)) throw new TypeError('Newsletter status payload must be a Uint8Array')
247
+ return payload
248
+ }
249
+ if (!message || typeof message !== 'object') throw new TypeError('Newsletter status message is required')
250
+ return encodeNewsletterMessage(message)
251
+ }
252
+
253
+ export const buildNewsletterStatusNode = ({
254
+ jid,
255
+ message,
256
+ payload,
257
+ messageId,
258
+ mediaType,
259
+ mediaId,
260
+ mediaHandle,
261
+ parentServerId,
262
+ responseServerId,
263
+ interactionType,
264
+ aiContent
265
+ }) => {
266
+ assertNewsletterJid(jid)
267
+ if (!messageId) throw new TypeError('Newsletter status messageId is required')
268
+ assertMediaType(mediaType)
269
+ assertInteraction(interactionType)
270
+
271
+ const handle = isPresent(mediaHandle) ? mediaHandle : mediaId
272
+ if (isPresent(handle) && !mediaType) {
273
+ throw new TypeError('mediaId requires a media newsletter status')
274
+ }
275
+ if (mediaType && !isPresent(handle)) {
276
+ throw new TypeError('Native newsletter status media requires the media handle returned by the newsletter upload')
277
+ }
278
+ if (interactionType === 'question_reshare' && !mediaType) {
279
+ throw new TypeError('question_reshare requires media')
280
+ }
281
+ if (interactionType === 'question_response') {
282
+ if (mediaType) throw new TypeError('question_response is published as a text status')
283
+ if (!isPresent(parentServerId)) throw new TypeError('question_response requires parentServerId')
284
+ }
285
+
286
+ const attrs = {
287
+ to: jid,
288
+ id: messageId,
289
+ type: mediaType ? 'media' : 'text'
290
+ }
291
+ if (mediaType) attrs.media_id = String(handle)
292
+ if (interactionType === 'question_response') {
293
+ attrs.server_id = toServerId(parentServerId, 'parentServerId')
294
+ }
295
+
296
+ const content = [{
297
+ tag: 'plaintext',
298
+ attrs: mediaType ? { mediatype: mediaType } : {},
299
+ content: resolveStatusPayload({ message, payload })
300
+ }]
301
+
302
+ const metaNode = buildMetaNode({ interactionType, parentServerId, responseServerId, aiContent })
303
+ if (metaNode) content.push(metaNode)
304
+
305
+ return { tag: 'status', attrs, content }
306
+ }
307
+
308
+ export const buildNewsletterStatusReactionNode = ({ jid, messageId, parentServerId, reaction }) => {
309
+ assertNewsletterJid(jid)
310
+ if (!messageId) throw new TypeError('Newsletter status reaction messageId is required')
311
+ if (!isPresent(parentServerId)) {
312
+ throw new TypeError('Newsletter status reaction requires parentServerId')
313
+ }
314
+
315
+ const isRevoke = !isPresent(reaction)
316
+ const attrs = {
317
+ to: jid,
318
+ id: messageId,
319
+ server_id: toServerId(parentServerId, 'parentServerId'),
320
+ type: 'reaction'
321
+ }
322
+ if (isRevoke) attrs.edit = NEWSLETTER_STATUS_EDIT_REACTION_REVOKE
323
+
324
+ return {
325
+ tag: 'status',
326
+ attrs,
327
+ content: [{
328
+ tag: 'reaction',
329
+ attrs: isRevoke ? {} : { code: reaction },
330
+ content: undefined
331
+ }]
332
+ }
333
+ }
334
+
335
+ export const buildNewsletterStatusRevokeNode = ({ jid, statusId }) => {
336
+ assertNewsletterJid(jid)
337
+ if (!statusId) throw new TypeError('Newsletter status revoke requires the status id')
338
+
339
+ return {
340
+ tag: 'status',
341
+ attrs: {
342
+ to: jid,
343
+ id: statusId,
344
+ type: 'text',
345
+ edit: NEWSLETTER_STATUS_EDIT_ADMIN_REVOKE
346
+ },
347
+ content: [{ tag: 'plaintext', attrs: {}, content: undefined }]
348
+ }
349
+ }
350
+
351
+ const sendStatusNode = async (sock, node, { jid, messageId, timeoutMs }) => {
352
+ const diagnostics = collectStatusCallbacks(sock)
353
+ const responsePromise = sock.waitForMessage(messageId, timeoutMs)
354
+
355
+ try {
356
+ await sock.sendNode(node)
357
+ const response = await responsePromise
358
+ return assertStatusServerResponse(response, { jid, messageId, callbacks: diagnostics.frames })
359
+ }
360
+ finally {
361
+ diagnostics.stop()
362
+ }
363
+ }
364
+
365
+ export const makeNewsletterStatusSender = (sock, config) => async (jid, content, options = {}) => {
366
+ assertNewsletterJid(jid)
367
+ if (!content || typeof content !== 'object' || Array.isArray(content)) {
368
+ throw new TypeError('Newsletter status content must be an object')
369
+ }
370
+
371
+ const userJid = sock.authState?.creds?.me?.id
372
+ if (!userJid) throw new TypeError('Not authenticated')
373
+
374
+ const {
375
+ mediaId,
376
+ mediaHandle: requestedMediaHandle,
377
+ parentServerId,
378
+ responseServerId,
379
+ interactionType: requestedInteractionType,
380
+ aiContent,
381
+ statusAttribution = true,
382
+ messageId: requestedMessageId,
383
+ ackTimeoutMs,
384
+ resolveServerId,
385
+ serverIdTimeoutMs,
386
+ transport,
387
+ ...messageOptions
388
+ } = options
389
+
390
+ if (transport !== undefined) {
391
+ config?.logger?.warn?.({ transport }, 'newsletter status "transport" option is obsolete, the verified status-publish transport is always used')
392
+ }
393
+
394
+ let uploadedMediaHandle
395
+ const upload = async (...args) => {
396
+ const result = await sock.waUploadToServer(...args)
397
+ if (args[1]?.newsletter) {
398
+ uploadedMediaHandle = result?.handle ?? result?.media_id ?? result?.mediaId ?? result?.fbid ?? uploadedMediaHandle
399
+ }
400
+ return result
401
+ }
402
+
403
+ const preparedContent = prepareModernMessageContent(content)
404
+ const fullMsg = await generateWAMessage(jid, statusAttribution ? withNewsletterStatusAttribution(preparedContent) : preparedContent, {
405
+ logger: config?.logger,
406
+ userJid,
407
+ upload,
408
+ mediaCache: config?.mediaCache,
409
+ options: config?.options,
410
+ ...messageOptions,
411
+ messageId: requestedMessageId || generateMessageIDV2(userJid)
412
+ })
413
+
414
+ const normalized = normalizeMessageContent(fullMsg.message)
415
+ const mediaType = getNewsletterStatusMediaType(fullMsg.message)
416
+ if (!mediaType && (normalized?.documentMessage || normalized?.stickerMessage)) {
417
+ throw new TypeError('Native newsletter status supports text, image, video, gif, and audio')
418
+ }
419
+ if (mediaType && !NEWSLETTER_STATUS_WEB_MEDIA_TYPES.has(mediaType)) {
420
+ config?.logger?.warn?.({ mediaType }, 'newsletter status media type is not published by WhatsApp Web itself, the server may reject it')
421
+ }
422
+
423
+ const interactionType = requestedInteractionType || (content.question ? 'question' : undefined)
424
+ if (interactionType === 'question' && !mediaType) {
425
+ config?.logger?.warn?.('WhatsApp Web only publishes question newsletter statuses on top of media, a text question status may be rejected')
426
+ }
427
+
428
+ const node = buildNewsletterStatusNode({
429
+ jid,
430
+ message: fullMsg.message,
431
+ messageId: fullMsg.key.id,
432
+ mediaType,
433
+ mediaHandle: requestedMediaHandle ?? mediaId ?? uploadedMediaHandle,
434
+ parentServerId,
435
+ responseServerId,
436
+ interactionType,
437
+ aiContent
438
+ })
439
+
440
+ const echo = resolveServerId === false
441
+ ? null
442
+ : waitForNewsletterStatusServerId(sock, {
443
+ jid,
444
+ messageId: fullMsg.key.id,
445
+ timeoutMs: serverIdTimeoutMs ?? NEWSLETTER_STATUS_SERVER_ID_TIMEOUT_MS
446
+ })
447
+
448
+ let ack
449
+ try {
450
+ ack = await sendStatusNode(sock, node, {
451
+ jid,
452
+ messageId: fullMsg.key.id,
453
+ timeoutMs: ackTimeoutMs ?? NEWSLETTER_STATUS_ACK_TIMEOUT_MS
454
+ })
455
+ }
456
+ catch (error) {
457
+ echo?.cancel()
458
+ throw error
459
+ }
460
+
461
+ const delivered = echo ? await echo : undefined
462
+
463
+ fullMsg.status = WAMessageStatus.SERVER_ACK
464
+ fullMsg.newsletterStatusServerId = ack.serverId ?? delivered?.serverId
465
+ fullMsg.newsletterStatusAck = ack
466
+ fullMsg.newsletterStatusResponse = ack.node
467
+ if (delivered) fullMsg.newsletterStatusDelivered = delivered.node
468
+ return fullMsg
469
+ }
470
+
471
+ export const makeNewsletterStatusReactionSender = sock => async (jid, parentServerId, reaction, options = {}) => {
472
+ assertNewsletterJid(jid)
473
+ const userJid = sock.authState?.creds?.me?.id
474
+ if (!userJid) throw new TypeError('Not authenticated')
475
+
476
+ const messageId = options.messageId || generateMessageIDV2(userJid)
477
+ const node = buildNewsletterStatusReactionNode({ jid, messageId, parentServerId, reaction })
478
+ const ack = await sendStatusNode(sock, node, {
479
+ jid,
480
+ messageId,
481
+ timeoutMs: options.ackTimeoutMs ?? NEWSLETTER_STATUS_ACK_TIMEOUT_MS
482
+ })
483
+
484
+ return {
485
+ key: {
486
+ remoteJid: jid,
487
+ fromMe: true,
488
+ id: messageId
489
+ },
490
+ status: WAMessageStatus.SERVER_ACK,
491
+ newsletterStatusServerId: ack.serverId,
492
+ newsletterStatusAck: ack,
493
+ newsletterStatusResponse: ack.node
494
+ }
495
+ }
496
+
497
+ export const makeNewsletterStatusRevokeSender = sock => async (jid, statusId, options = {}) => {
498
+ assertNewsletterJid(jid)
499
+ const userJid = sock.authState?.creds?.me?.id
500
+ if (!userJid) throw new TypeError('Not authenticated')
501
+
502
+ const node = buildNewsletterStatusRevokeNode({ jid, statusId })
503
+ const ack = await sendStatusNode(sock, node, {
504
+ jid,
505
+ messageId: statusId,
506
+ timeoutMs: options.ackTimeoutMs ?? NEWSLETTER_STATUS_ACK_TIMEOUT_MS
507
+ })
508
+
509
+ return {
510
+ key: {
511
+ remoteJid: jid,
512
+ fromMe: true,
513
+ id: statusId
514
+ },
515
+ status: WAMessageStatus.SERVER_ACK,
516
+ newsletterStatusAck: ack,
517
+ newsletterStatusResponse: ack.node
518
+ }
519
+ }
520
+
521
+ const decodeStatusPayload = plaintext => {
522
+ const bytes = plaintext?.content
523
+ if (!bytes || Array.isArray(bytes)) return undefined
524
+ return proto.Message.decode(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes))
525
+ }
526
+
527
+ const parseStatusAdminProfile = meta => {
528
+ const adminProfile = meta && getBinaryNodeChild(meta, 'admin_profile')
529
+ if (!adminProfile) return undefined
530
+
531
+ const name = getBinaryNodeChild(adminProfile, 'name')
532
+ const picture = getBinaryNodeChild(adminProfile, 'picture')
533
+ const content = name?.content
534
+ return {
535
+ id: adminProfile.attrs?.id,
536
+ name: typeof content === 'string'
537
+ ? content
538
+ : content instanceof Uint8Array
539
+ ? Buffer.from(content).toString('utf-8')
540
+ : undefined,
541
+ pictureId: picture?.attrs?.id,
542
+ pictureDirectPath: picture?.attrs?.direct_path
543
+ }
544
+ }
545
+
546
+ const parseStatusNode = status => {
547
+ const plaintext = getBinaryNodeChild(status, 'plaintext')
548
+ const reaction = getBinaryNodeChild(status, 'reaction')
549
+ const meta = getBinaryNodeChild(status, 'meta')
550
+ const reactions = getBinaryNodeChild(status, 'reactions')
551
+ const viewsCount = getBinaryNodeChild(status, 'views_count')
552
+ const responsesCount = getBinaryNodeChild(status, 'responses_count')
553
+ return {
554
+ id: status.attrs?.id,
555
+ serverId: readIntAttr(status.attrs, 'server_id'),
556
+ t: readIntAttr(status.attrs, 't'),
557
+ isSender: status.attrs?.is_sender === 'true',
558
+ type: status.attrs?.type,
559
+ edit: status.attrs?.edit,
560
+ mediaType: plaintext?.attrs?.mediatype,
561
+ interactionType: meta?.attrs?.interaction_type,
562
+ reaction: reaction ? reaction.attrs?.code ?? '' : undefined,
563
+ adminProfile: parseStatusAdminProfile(meta),
564
+ paidPartnership: meta ? !!getBinaryNodeChild(meta, 'paid_partnership') : false,
565
+ aiContent: meta ? !!getBinaryNodeChild(meta, 'ai_content') : false,
566
+ editTimestamp: readIntAttr(meta?.attrs, 'msg_edit_t'),
567
+ originalTimestamp: readIntAttr(meta?.attrs, 'original_msg_t'),
568
+ reactionCounts: reactions
569
+ ? getBinaryNodeChildren(reactions, 'reaction').map(entry => ({
570
+ code: entry.attrs?.code,
571
+ count: readIntAttr(entry.attrs, 'count')
572
+ }))
573
+ : undefined,
574
+ viewsCount: readIntAttr(viewsCount?.attrs, 'count'),
575
+ responsesCount: readIntAttr(responsesCount?.attrs, 'count'),
576
+ message: decodeStatusPayload(plaintext),
577
+ node: status
578
+ }
579
+ }
580
+
581
+ export const parseNewsletterStatusUpdatesResponse = node => {
582
+ const updates = getBinaryNodeChild(node, 'status_updates')
583
+ if (!updates) {
584
+ const children = Array.isArray(node?.content) ? node.content.map(child => child?.tag) : []
585
+ const error = new Error(`Newsletter status updates response has no <status_updates> child, got [${children.join(', ')}]`)
586
+ error.data = node
587
+ throw error
588
+ }
589
+
590
+ return parseNewsletterStatusesResponse(updates)
591
+ }
592
+
593
+ export const makeNewsletterStatusUpdatesFetcher = sock => async (jid, options = {}) => {
594
+ assertNewsletterJid(jid)
595
+
596
+ const { count = 20, since, before, after } = options
597
+ const attrs = { count: String(count) }
598
+ if (isPresent(since)) attrs.since = String(since)
599
+ if (isPresent(before)) attrs.before = toServerId(before, 'before')
600
+ else if (isPresent(after)) attrs.after = toServerId(after, 'after')
601
+
602
+ const result = await sock.query({
603
+ tag: 'iq',
604
+ attrs: {
605
+ to: jid,
606
+ xmlns: 'newsletter',
607
+ type: 'get'
608
+ },
609
+ content: [{ tag: 'status_updates', attrs, content: undefined }]
610
+ })
611
+
612
+ return parseNewsletterStatusUpdatesResponse(result)
613
+ }
614
+
615
+ export const parseNewsletterStatusesResponse = node => {
616
+ const statuses = getBinaryNodeChild(node, 'statuses')
617
+ if (!statuses) {
618
+ const children = Array.isArray(node?.content) ? node.content.map(child => child?.tag) : []
619
+ const error = new Error(`Newsletter statuses response has no <statuses> child, got [${children.join(', ')}]`)
620
+ error.data = node
621
+ throw error
622
+ }
623
+
624
+ return {
625
+ jid: statuses.attrs?.jid,
626
+ t: readIntAttr(statuses.attrs, 't'),
627
+ statuses: getBinaryNodeChildren(statuses, 'status').map(parseStatusNode)
628
+ }
629
+ }
630
+
631
+ export const makeNewsletterStatusFetcher = sock => async (jid, options = {}) => {
632
+ assertNewsletterJid(jid)
633
+
634
+ const { count = 20, before, after, viewRole } = options
635
+ const attrs = { type: 'jid', jid }
636
+ if (isPresent(viewRole)) attrs.view_role = String(viewRole).toLowerCase()
637
+ attrs.count = String(count)
638
+ if (isPresent(before)) attrs.before = toServerId(before, 'before')
639
+ else if (isPresent(after)) attrs.after = toServerId(after, 'after')
640
+
641
+ const result = await sock.query({
642
+ tag: 'iq',
643
+ attrs: {
644
+ to: S_WHATSAPP_NET,
645
+ xmlns: 'newsletter',
646
+ type: 'get'
647
+ },
648
+ content: [{ tag: 'statuses', attrs, content: undefined }]
649
+ })
650
+
651
+ return parseNewsletterStatusesResponse(result)
652
+ }
653
+
654
+ export const sendNewsletterStatus = (sock, inputOrJid, content, options = {}) => {
655
+ let jid;
656
+ let actualContent;
657
+ let actualOptions;
658
+
659
+ if (typeof inputOrJid === "object" && inputOrJid !== null && ("jid" in inputOrJid || "content" in inputOrJid)) {
660
+ const { jid: targetJid, content: targetContent, ...rest } = inputOrJid;
661
+ jid = targetJid;
662
+ actualContent = targetContent;
663
+ actualOptions = { ...rest, ...content };
664
+ } else {
665
+ jid = inputOrJid;
666
+ actualContent = content;
667
+ actualOptions = options;
668
+ }
669
+
670
+ const sender = makeNewsletterStatusSender(sock, sock?.config);
671
+ return sender(jid, actualContent, actualOptions);
672
+ };
673
+
674
+ export const sendNewsletterStatusReaction = (sock, jid, parentServerId, reaction, options) => {
675
+ return makeNewsletterStatusReactionSender(sock)(jid, parentServerId, reaction, options);
676
+ };
677
+
678
+ export const revokeNewsletterStatus = (sock, jid, serverId, options) => {
679
+ return makeNewsletterStatusRevokeSender(sock)(jid, serverId, options);
680
+ };
681
+
682
+ export const getNewsletterStatuses = (sock, jid, options) => {
683
+ return makeNewsletterStatusFetcher(sock)(jid, options);
684
+ };
685
+
686
+ export const getNewsletterStatusUpdates = (sock, jid, options) => {
687
+ return makeNewsletterStatusUpdatesFetcher(sock)(jid, options);
688
+ };
689
+
690
+ export { NEWSLETTER_STATUS_CONTENT_TYPES, NEWSLETTER_STATUS_MEDIA_TYPES, NEWSLETTER_STATUS_WEB_MEDIA_TYPES };
691
+
692
+ // compat dengan API sauruspro sebelumnya
693
+ export { makeNewsletterStatusAttribution };
694
+ export const prepareNewsletterStatusContent = prepareModernMessageContent;