@areumtecnologia/wasockets 2.2.3 → 2.3.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 (31) hide show
  1. package/README.md +98 -25
  2. package/lib/Client/Builders/Buttons.js +127 -0
  3. package/lib/Client/Builders/EventBuilder.js +122 -0
  4. package/lib/Client/Builders/InteractiveMessage.js +150 -0
  5. package/lib/Client/Builders/Lists.js +78 -0
  6. package/lib/Client/Builders/PollBuilder.js +84 -0
  7. package/lib/Client/Builders/index.js +21 -0
  8. package/lib/Client/Constants.js +54 -0
  9. package/lib/Client/Context/CallContext.js +96 -0
  10. package/lib/Client/Context/MediaObject.js +155 -0
  11. package/lib/Client/Context/MessageContext.js +213 -0
  12. package/lib/Client/Modules/BusinessManager.js +155 -0
  13. package/lib/Client/Modules/ChannelManager.js +144 -0
  14. package/lib/Client/Modules/ChatManager.js +113 -0
  15. package/lib/Client/Modules/CommunityManager.js +136 -0
  16. package/lib/Client/Modules/GroupManager.js +194 -0
  17. package/lib/Client/Modules/LabelManager.js +123 -0
  18. package/lib/Client/Modules/MessageManager.js +198 -0
  19. package/lib/Client/Modules/PresenceManager.js +68 -0
  20. package/lib/Client/Modules/PrivacyManager.js +98 -0
  21. package/lib/Client/Modules/ProfileManager.js +92 -0
  22. package/lib/Client/Modules/UserManager.js +92 -0
  23. package/lib/Client/Modules/VoipManager.js +117 -0
  24. package/lib/Client/Modules/index.js +29 -0
  25. package/lib/Client/Normalizers/EventNormalizer.js +99 -0
  26. package/lib/Client/Normalizers/JidNormalizer.js +96 -0
  27. package/lib/Client/Normalizers/MessageNormalizer.js +422 -0
  28. package/lib/Client/WaClient.js +438 -0
  29. package/lib/Client/index.d.ts +547 -0
  30. package/lib/Client/index.js +76 -0
  31. package/package.json +29 -23
@@ -0,0 +1,422 @@
1
+ "use strict"
2
+
3
+ Object.defineProperty(exports, "__esModule", { value: true })
4
+
5
+ class MessageNormalizer {
6
+ /**
7
+ * Recursively unwraps all container wrappers (ephemeral, viewOnce, associatedChild, etc.)
8
+ * @param {Object} rawMessageContent - raw message node from proto
9
+ * @returns {Object} Unwrapped message object
10
+ */
11
+ static unwrapMessage(rawMessageContent) {
12
+ if (!rawMessageContent) return null
13
+
14
+ let msg = rawMessageContent
15
+ let unwrapped = true
16
+
17
+ // Loop to peel away multiple layers of wrappers if nested
18
+ while (unwrapped && msg) {
19
+ unwrapped = false
20
+
21
+ if (msg.ephemeralMessage?.message) {
22
+ msg = msg.ephemeralMessage.message
23
+ unwrapped = true
24
+ continue
25
+ }
26
+ if (msg.viewOnceMessage?.message) {
27
+ msg = msg.viewOnceMessage.message
28
+ unwrapped = true
29
+ continue
30
+ }
31
+ if (msg.viewOnceMessageV2?.message) {
32
+ msg = msg.viewOnceMessageV2.message
33
+ unwrapped = true
34
+ continue
35
+ }
36
+ if (msg.viewOnceMessageV2Extension?.message) {
37
+ msg = msg.viewOnceMessageV2Extension.message
38
+ unwrapped = true
39
+ continue
40
+ }
41
+ if (msg.documentWithCaptionMessage?.message) {
42
+ msg = msg.documentWithCaptionMessage.message
43
+ unwrapped = true
44
+ continue
45
+ }
46
+ if (msg.associatedChildMessage?.message) {
47
+ msg = msg.associatedChildMessage.message
48
+ unwrapped = true
49
+ continue
50
+ }
51
+ }
52
+
53
+ return msg
54
+ }
55
+
56
+ /**
57
+ * Identifies the primary friendly content type of the message (28 distinct types)
58
+ * @param {Object} messageContent - Raw or unwrapped message content
59
+ * @returns {string} Normalized friendly type
60
+ */
61
+ static extractType(messageContent) {
62
+ const msg = this.unwrapMessage(messageContent)
63
+ if (!msg) return 'unknown'
64
+
65
+ // 1. Text & Media Basics
66
+ if (msg.conversation || msg.extendedTextMessage) return 'text'
67
+ if (msg.imageMessage) return 'image'
68
+ if (msg.albumMessage) return 'album'
69
+ if (msg.videoMessage) return msg.videoMessage.gifPlayback ? 'gif' : 'video'
70
+ if (msg.audioMessage) return msg.audioMessage.ptt ? 'voice' : 'audio'
71
+ if (msg.documentMessage) return 'document'
72
+ if (msg.stickerMessage) return 'sticker'
73
+ if (msg.locationMessage) return 'location'
74
+ if (msg.liveLocationMessage) return 'live_location'
75
+ if (msg.contactMessage || msg.contactsArrayMessage) return 'contact'
76
+
77
+ // 2. Polls & Events
78
+ if (msg.pollCreationMessage || msg.pollCreationMessageV2 || msg.pollCreationMessageV3) return 'poll_creation'
79
+ if (msg.pollUpdateMessage) return 'poll_update'
80
+ if (msg.eventMessage) return 'event'
81
+ if (msg.eventResponseMessage || msg.encEventResponseMessage) return 'event_response'
82
+
83
+ // 3. Reactions & Pins
84
+ if (msg.reactionMessage) return 'reaction'
85
+ if (msg.pinInChatMessage) return 'pin_in_chat'
86
+ if (msg.unpinInChatMessage) return 'unpin_in_chat'
87
+
88
+ // 4. Replies from Buttons, Lists & Questions
89
+ if (msg.buttonsResponseMessage) return 'button_reply'
90
+ if (msg.listResponseMessage) return 'list_reply'
91
+ if (msg.templateButtonReplyMessage) return 'template_button_reply'
92
+ if (msg.questionReplyMessage) return 'question_reply'
93
+
94
+ // 5. Specific Interactive Flow Buttons
95
+ const firstBtnName = msg.interactiveMessage?.interactiveButtons?.[0]?.name
96
+ if (firstBtnName) {
97
+ switch (firstBtnName) {
98
+ case 'review_and_pay': return 'review_and_pay'
99
+ case 'review_order': return 'review_order'
100
+ case 'quick_reply': return 'quick_reply'
101
+ case 'cta_url': return 'cta_url'
102
+ case 'cta_copy': return 'cta_copy'
103
+ case 'cta_call': return 'cta_call'
104
+ case 'voice_call': return 'voice_call'
105
+ case 'payment_info': return 'payment_info'
106
+ }
107
+ }
108
+
109
+ // 6. Native Flow Responses
110
+ if (msg.interactiveResponseMessage?.nativeFlowResponseMessage) {
111
+ const flowName = msg.interactiveResponseMessage.nativeFlowResponseMessage.name
112
+ if (flowName === 'menu_options') return 'native_flow_menu_options_response'
113
+ return 'native_flow_response'
114
+ }
115
+
116
+ // 7. Interactive Containers
117
+ if (msg.interactiveMessage?.interactiveButtons) return 'interactive_buttons'
118
+ if (msg.interactiveMessage?.interactiveList) return 'interactive_list'
119
+ if (msg.interactiveResponseMessage) return 'interactive_response'
120
+ if (msg.interactiveMessage) return 'interactive_message'
121
+
122
+ // 8. Protocol
123
+ if (msg.protocolMessage) return 'protocol'
124
+
125
+ // Fallback to first non-metadata proto key
126
+ const keys = Object.keys(msg).filter(k => k !== 'messageContextInfo' && k !== 'senderKeyDistributionMessage')
127
+ return keys[0] ? keys[0].replace('Message', '').toLowerCase() : 'unknown'
128
+ }
129
+
130
+ /**
131
+ * Extracts readable text content across diverse message structures
132
+ * @param {Object} messageContent - Raw or unwrapped message content
133
+ * @returns {string} Text body
134
+ */
135
+ static extractBody(messageContent) {
136
+ const msg = this.unwrapMessage(messageContent)
137
+ if (!msg) return ''
138
+
139
+ // Direct Text
140
+ if (msg.conversation) return msg.conversation
141
+ if (msg.extendedTextMessage?.text) return msg.extendedTextMessage.text
142
+
143
+ // Media Captions
144
+ if (msg.imageMessage?.caption) return msg.imageMessage.caption
145
+ if (msg.videoMessage?.caption) return msg.videoMessage.caption
146
+ if (msg.documentMessage?.caption) return msg.documentMessage.caption
147
+
148
+ // Button / List selections
149
+ if (msg.buttonsResponseMessage?.selectedDisplayText) return msg.buttonsResponseMessage.selectedDisplayText
150
+ if (msg.buttonsResponseMessage?.selectedButtonId) return msg.buttonsResponseMessage.selectedButtonId
151
+ if (msg.listResponseMessage?.title) return msg.listResponseMessage.title
152
+ if (msg.templateButtonReplyMessage?.selectedDisplayText) return msg.templateButtonReplyMessage.selectedDisplayText
153
+ if (msg.templateButtonReplyMessage?.selectedId) return msg.templateButtonReplyMessage.selectedId
154
+
155
+ // Interactive / Native Flows
156
+ if (msg.interactiveResponseMessage?.body?.text) return msg.interactiveResponseMessage.body.text
157
+ if (msg.interactiveResponseMessage?.nativeFlowResponseMessage?.paramsJson) {
158
+ return msg.interactiveResponseMessage.nativeFlowResponseMessage.paramsJson
159
+ }
160
+ if (msg.interactiveMessage?.body?.text) return msg.interactiveMessage.body.text
161
+
162
+ // Polls, Reactions, Questions & Locations
163
+ if (msg.pollCreationMessage?.name) return msg.pollCreationMessage.name
164
+ if (msg.pollCreationMessageV2?.name) return msg.pollCreationMessageV2.name
165
+ if (msg.pollCreationMessageV3?.name) return msg.pollCreationMessageV3.name
166
+ if (msg.reactionMessage?.text) return msg.reactionMessage.text
167
+ if (msg.questionReplyMessage?.message?.extendedTextMessage?.text) return msg.questionReplyMessage.message.extendedTextMessage.text
168
+ if (msg.questionReplyMessage?.answer) return msg.questionReplyMessage.answer
169
+ if (msg.locationMessage?.name || msg.locationMessage?.address) {
170
+ return [msg.locationMessage.name, msg.locationMessage.address].filter(Boolean).join(' - ')
171
+ }
172
+
173
+ return ''
174
+ }
175
+
176
+ /**
177
+ * Extracts and safely parses interactive button/list responses
178
+ * @param {Object} messageContent - Unwrapped message content
179
+ * @returns {Object|null} { id, text, params, flowName, type }
180
+ */
181
+ static extractInteractiveReply(messageContent) {
182
+ const msg = this.unwrapMessage(messageContent)
183
+ if (!msg) return null
184
+
185
+ // 1. Classic Button Response
186
+ if (msg.buttonsResponseMessage) {
187
+ return {
188
+ id: msg.buttonsResponseMessage.selectedButtonId || '',
189
+ text: msg.buttonsResponseMessage.selectedDisplayText || '',
190
+ type: 'button'
191
+ }
192
+ }
193
+
194
+ // 2. List Dropdown Selection
195
+ if (msg.listResponseMessage) {
196
+ const row = msg.listResponseMessage.singleSelectReply
197
+ return {
198
+ id: row?.selectedRowId || '',
199
+ text: msg.listResponseMessage.title || '',
200
+ description: row?.description || '',
201
+ type: 'list'
202
+ }
203
+ }
204
+
205
+ // 3. Native Flow Interactive Response
206
+ if (msg.interactiveResponseMessage?.nativeFlowResponseMessage) {
207
+ const flow = msg.interactiveResponseMessage.nativeFlowResponseMessage
208
+ let params = {}
209
+ if (flow.paramsJson) {
210
+ try {
211
+ params = JSON.parse(flow.paramsJson)
212
+ } catch {
213
+ params = { raw: flow.paramsJson }
214
+ }
215
+ }
216
+
217
+ const id = params?.id || params?.selected_id || params?.row_id || flow.name || ''
218
+ return {
219
+ id,
220
+ text: flow.paramsJson || '',
221
+ params,
222
+ flowName: flow.name || '',
223
+ type: 'native_flow'
224
+ }
225
+ }
226
+
227
+ // 4. Template Button Reply
228
+ if (msg.templateButtonReplyMessage) {
229
+ return {
230
+ id: msg.templateButtonReplyMessage.selectedId || '',
231
+ text: msg.templateButtonReplyMessage.selectedDisplayText || '',
232
+ type: 'template_button'
233
+ }
234
+ }
235
+
236
+ // 5. Question Reply (Channels)
237
+ if (msg.questionReplyMessage) {
238
+ return {
239
+ id: msg.questionReplyMessage.selectedButtonId || '',
240
+ text: msg.questionReplyMessage.selectedDisplayText || msg.questionReplyMessage.answer || '',
241
+ type: 'question_reply'
242
+ }
243
+ }
244
+
245
+ return null
246
+ }
247
+
248
+ /**
249
+ * Extracts and parses contacts from vCards
250
+ * @param {Object} messageContent - Unwrapped message content
251
+ * @returns {Array<{ name: string, number: string, org?: string, vcard: string }>}
252
+ */
253
+ static extractContacts(messageContent) {
254
+ const msg = this.unwrapMessage(messageContent)
255
+ if (!msg) return []
256
+
257
+ const parseVcard = (vcard = '') => {
258
+ const nameMatch = vcard.match(/FN:(.+)/i)
259
+ // Tenta waid= primeiro, se não encontrar pega TEL
260
+ const waidMatch = vcard.match(/waid=(\d+)/i)
261
+ const telMatch = vcard.match(/TEL[^:]*:([+\d\s().-]+)/i)
262
+ const orgMatch = vcard.match(/ORG:([^;\n\r]+)/i)
263
+
264
+ let number = ''
265
+ if (waidMatch) {
266
+ number = waidMatch[1].replace(/\D/g, '')
267
+ } else if (telMatch) {
268
+ number = telMatch[1].replace(/\D/g, '')
269
+ }
270
+
271
+ return {
272
+ name: (nameMatch ? nameMatch[1] : '').replace(/\\/g, '').trim(),
273
+ number,
274
+ org: orgMatch ? orgMatch[1].trim() : undefined,
275
+ vcard
276
+ }
277
+ }
278
+
279
+ if (msg.contactMessage) {
280
+ return [parseVcard(msg.contactMessage.vcard)]
281
+ }
282
+
283
+ if (msg.contactsArrayMessage?.contacts) {
284
+ return msg.contactsArrayMessage.contacts.map(c => parseVcard(c.vcard))
285
+ }
286
+
287
+ return []
288
+ }
289
+
290
+ /**
291
+ * Extracts coordinates and metadata from location messages
292
+ * @param {Object} messageContent - Unwrapped message content
293
+ * @returns {Object|null}
294
+ */
295
+ static extractLocation(messageContent) {
296
+ const msg = this.unwrapMessage(messageContent)
297
+ if (!msg) return null
298
+
299
+ const loc = msg.locationMessage || msg.liveLocationMessage
300
+ if (!loc) return null
301
+
302
+ return {
303
+ latitude: Number(loc.degreesLatitude || 0),
304
+ longitude: Number(loc.degreesLongitude || 0),
305
+ name: loc.name || '',
306
+ address: loc.address || '',
307
+ url: loc.url || '',
308
+ accuracyInMeters: loc.accuracyInMeters ? Number(loc.accuracyInMeters) : undefined,
309
+ speedInMps: loc.speedInMps ? Number(loc.speedInMps) : undefined,
310
+ isLive: Boolean(msg.liveLocationMessage)
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Extracts reaction details
316
+ * @param {Object} messageContent - Unwrapped message content
317
+ * @returns {Object|null}
318
+ */
319
+ static extractReaction(messageContent) {
320
+ const msg = this.unwrapMessage(messageContent)
321
+ if (!msg?.reactionMessage) return null
322
+
323
+ const reaction = msg.reactionMessage
324
+ return {
325
+ emoji: reaction.text || '',
326
+ reactedMessageId: reaction.key?.id || '',
327
+ targetKey: reaction.key || null,
328
+ isRemoval: !reaction.text,
329
+ senderTimestamp: reaction.senderTimestampMs ? new Date(Number(reaction.senderTimestampMs)) : new Date()
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Extracts poll creation structure
335
+ * @param {Object} messageContent - Unwrapped message content
336
+ * @param {Object} [rawMessage]
337
+ * @returns {Object|null}
338
+ */
339
+ static extractPollCreation(messageContent, rawMessage) {
340
+ const msg = this.unwrapMessage(messageContent)
341
+ const poll = msg?.pollCreationMessage || msg?.pollCreationMessageV2 || msg?.pollCreationMessageV3
342
+ if (!poll) return null
343
+
344
+ return {
345
+ id: rawMessage?.key?.id || '',
346
+ name: poll.name || '',
347
+ selectableOptionsCount: Number(poll.selectableOptionsCount || 1),
348
+ allowsMultipleAnswers: Number(poll.selectableOptionsCount || 1) > 1,
349
+ options: (poll.options || []).map(opt => ({
350
+ name: opt.optionName || '',
351
+ hash: opt.optionHash ? Buffer.from(opt.optionHash).toString('hex') : null
352
+ }))
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Extracts media node if present
358
+ * @param {Object} messageContent - Unwrapped message content
359
+ * @returns {Object|null}
360
+ */
361
+ static extractMediaNode(messageContent) {
362
+ const msg = this.unwrapMessage(messageContent)
363
+ if (!msg) return null
364
+
365
+ return (
366
+ msg.imageMessage ||
367
+ msg.videoMessage ||
368
+ msg.audioMessage ||
369
+ msg.documentMessage ||
370
+ msg.stickerMessage ||
371
+ null
372
+ )
373
+ }
374
+
375
+ /**
376
+ * Extracts structured media details
377
+ * @param {Object} messageContent - Unwrapped message content
378
+ * @returns {Object|null}
379
+ */
380
+ static extractMediaDetails(messageContent) {
381
+ const node = this.extractMediaNode(messageContent)
382
+ if (!node) return null
383
+
384
+ return {
385
+ mimetype: node.mimetype || '',
386
+ fileName: node.fileName || '',
387
+ caption: node.caption || '',
388
+ duration: node.seconds ? Number(node.seconds) : null,
389
+ isPtt: Boolean(node.ptt),
390
+ isGif: Boolean(node.gifPlayback),
391
+ isViewOnce: Boolean(node.viewOnce)
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Extracts quoted message context info
397
+ * @param {Object} messageContent - Unwrapped message content
398
+ * @returns {Object|null}
399
+ */
400
+ static extractContextInfo(messageContent) {
401
+ const msg = this.unwrapMessage(messageContent)
402
+ if (!msg) return null
403
+
404
+ return (
405
+ msg.extendedTextMessage?.contextInfo ||
406
+ msg.imageMessage?.contextInfo ||
407
+ msg.videoMessage?.contextInfo ||
408
+ msg.audioMessage?.contextInfo ||
409
+ msg.documentMessage?.contextInfo ||
410
+ msg.stickerMessage?.contextInfo ||
411
+ msg.buttonsResponseMessage?.contextInfo ||
412
+ msg.listResponseMessage?.contextInfo ||
413
+ msg.interactiveResponseMessage?.contextInfo ||
414
+ msg.templateButtonReplyMessage?.contextInfo ||
415
+ msg.messageContextInfo ||
416
+ null
417
+ )
418
+ }
419
+ }
420
+
421
+ exports.MessageNormalizer = MessageNormalizer
422
+ exports.default = MessageNormalizer