@areumtecnologia/wasockets 2.3.0 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/lib/Client/Context/MediaObject.js +6 -0
- package/lib/Client/Context/MessageContext.js +160 -45
- package/lib/Client/Normalizers/EventNormalizer.js +19 -1
- package/lib/Client/Normalizers/JidNormalizer.js +5 -2
- package/lib/Client/Normalizers/MessageNormalizer.js +311 -23
- package/lib/Client/index.d.ts +92 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -224,18 +224,33 @@ client.on(Events.LABELS_UPDATE, (labels) => { /* Criação ou associação de et
|
|
|
224
224
|
Ao escutar `Events.MESSAGE_RECEIVED`, você recebe um objeto `ctx` que já contém todos os dados mastigados e métodos de ação vinculados diretamente àquela mensagem:
|
|
225
225
|
|
|
226
226
|
#### Propriedades de `ctx`:
|
|
227
|
-
- `ctx.id`: ID único da mensagem.
|
|
227
|
+
- `ctx.id`: ID único da mensagem (se for mensagem editada, preserva o ID original rastreável).
|
|
228
228
|
- `ctx.chatId`: JID do chat (usuário, grupo ou canal).
|
|
229
|
-
- `ctx.
|
|
230
|
-
- `ctx.
|
|
231
|
-
- `ctx.
|
|
229
|
+
- `ctx.fromMe` / `ctx.isFromMe`: Booleano se a mensagem foi enviada pelo próprio bot.
|
|
230
|
+
- `ctx.from`: JID de origem (remetente em recebidas, ou JID do bot em enviadas).
|
|
231
|
+
- `ctx.to`: JID de destino (bot em recebidas, ou JID do chat/destinatário em enviadas).
|
|
232
|
+
- `ctx.fromPushName` / `ctx.senderName`: Nome de exibição formatado ou pushName ('Você' se `fromMe`).
|
|
233
|
+
- `ctx.fromNumber` / `ctx.senderNumber`: Apenas os números de telefone do remetente (sem sufixos ou `@s.whatsapp.net`).
|
|
234
|
+
- `ctx.sender`: JID de quem enviou a mensagem no chat.
|
|
232
235
|
- `ctx.isGroup`: Booleano indicando se é grupo (`@g.us`).
|
|
233
236
|
- `ctx.isChannel`: Booleano indicando se é newsletter/canal (`@newsletter`).
|
|
234
|
-
- `ctx.
|
|
235
|
-
- `ctx.body`: Texto principal da mensagem (já
|
|
236
|
-
- `ctx.type`: Tipo normalizado (`text`, `image`, `video`, `
|
|
237
|
-
- `ctx.
|
|
238
|
-
- `ctx.
|
|
237
|
+
- `ctx.isBroadcast`: Booleano se é lista de transmissão ou status.
|
|
238
|
+
- `ctx.body`: Texto principal da mensagem (já desempacotado recursivamente de `viewOnce`, `ephemeral`, botões, listas, etc.).
|
|
239
|
+
- `ctx.type`: Tipo normalizado e amigável (28 tipos identificados: `text`, `image`, `video`, `voice`, `document`, `sticker`, `native_flow_response`, `poll_creation`, `event`, etc.).
|
|
240
|
+
- `ctx.isEdited`: Booleano indicando se a mensagem é uma edição de mensagem prévia (`protocolMessage.editedMessage`).
|
|
241
|
+
- `ctx.editTimestamp`: Instância de `Date` informando quando a mensagem foi editada (ou `null`).
|
|
242
|
+
- `ctx.interactiveReply`: Resposta estruturada de botões, listas e fluxos nativos (`{ id, text, params, flowName, type }`).
|
|
243
|
+
- `ctx.contacts`: Array com contatos e vCards parseados (`[{ name, number, org, vcard }]`), com suporte a `waid` e fallback `TEL`.
|
|
244
|
+
- `ctx.location`: Coordenadas e endereço (`{ latitude, longitude, name, address, url, isLive }`).
|
|
245
|
+
- `ctx.reaction`: Detalhes de reação (`{ emoji, reactedMessageId, targetKey, isRemoval, senderTimestamp }`).
|
|
246
|
+
- `ctx.poll`: Dados completos de enquetes (`{ name, options, selectableOptionsCount, allowsMultipleAnswers }`).
|
|
247
|
+
- `ctx.mediaDetails`: Metadados da mídia (`{ mimetype, fileName, caption, duration, isPtt, isGif, isViewOnce }`).
|
|
248
|
+
- `ctx.media`: Instância de `MediaObject` para conversão rápida para buffer, base64, salvamento em disco ou Sharp.
|
|
249
|
+
- `ctx.isMentioningMe`: Booleano indicando se o bot foi mencionado diretamente no texto (suporta Phone JID e LID JID).
|
|
250
|
+
- `ctx.mentions` / `ctx.mentionedJids`: Array de JIDs mencionados na mensagem.
|
|
251
|
+
- `ctx.isForwarded` & `ctx.forwardingScore`: Rastreamento de encaminhamento.
|
|
252
|
+
- `ctx.isReply`: Booleano indicando se esta mensagem está citando outra.
|
|
253
|
+
- `ctx.quoted`: Instância recursiva de `MessageContext` da mensagem citada/respondida (com controle de profundidade).
|
|
239
254
|
- `ctx.raw`: O nó bruto original do WhatsApp/Baileys para casos de baixo nível.
|
|
240
255
|
|
|
241
256
|
#### Ações Disponíveis em `ctx`:
|
|
@@ -19,8 +19,14 @@ class MediaObject {
|
|
|
19
19
|
this.mimetype = this.mediaNode.mimetype || this._defaultMimetype(this.mediaType)
|
|
20
20
|
this.fileLength = Number(this.mediaNode.fileLength || 0)
|
|
21
21
|
this.fileName = this.mediaNode.fileName || ''
|
|
22
|
+
this.caption = this.mediaNode.caption || ''
|
|
22
23
|
this.seconds = Number(this.mediaNode.seconds || 0)
|
|
24
|
+
this.duration = this.seconds || null
|
|
25
|
+
|
|
23
26
|
this.isVoice = Boolean(this.mediaNode.ptt)
|
|
27
|
+
this.isPtt = this.isVoice
|
|
28
|
+
this.isGif = Boolean(this.mediaNode.gifPlayback)
|
|
29
|
+
this.isViewOnce = Boolean(this.mediaNode.viewOnce)
|
|
24
30
|
this.isAnimated = Boolean(this.mediaNode.gifPlayback || this.mediaNode.isAnimated)
|
|
25
31
|
this.extension = this._inferExtension()
|
|
26
32
|
|
|
@@ -10,58 +10,173 @@ class MessageContext {
|
|
|
10
10
|
/**
|
|
11
11
|
* @param {import('../WaClient').WaClient} client
|
|
12
12
|
* @param {Object} rawMessage - proto.IWebMessageInfo
|
|
13
|
+
* @param {number} [depth=0] - Recursion depth guard for quoted messages
|
|
13
14
|
*/
|
|
14
|
-
constructor(client, rawMessage) {
|
|
15
|
+
constructor(client, rawMessage, depth = 0) {
|
|
15
16
|
this.client = client
|
|
16
|
-
this.raw = rawMessage
|
|
17
|
+
this.raw = rawMessage || {}
|
|
18
|
+
|
|
19
|
+
// 1. Detection of Edited Message (protocolMessage.editedMessage)
|
|
20
|
+
const editedMsg = rawMessage?.message?.protocolMessage?.editedMessage
|
|
21
|
+
this.isEdited = Boolean(editedMsg)
|
|
22
|
+
|
|
23
|
+
const effectiveKey = (this.isEdited && rawMessage.message.protocolMessage.key)
|
|
24
|
+
? { ...rawMessage.key, ...rawMessage.message.protocolMessage.key }
|
|
25
|
+
: (rawMessage?.key || {})
|
|
26
|
+
|
|
27
|
+
const effectiveMessage = editedMsg || rawMessage?.message || null
|
|
28
|
+
|
|
29
|
+
// 2. Single-pass message unwrapping (eliminates repeated while-loops)
|
|
30
|
+
const unwrapped = MessageNormalizer.unwrapMessage(effectiveMessage)
|
|
31
|
+
|
|
32
|
+
// 3. Identity and Routing
|
|
33
|
+
this.id = effectiveKey.id || rawMessage?.key?.id || ''
|
|
34
|
+
this.chatId = effectiveKey.remoteJid || rawMessage?.key?.remoteJid || ''
|
|
35
|
+
this.fromMe = Boolean(effectiveKey.fromMe)
|
|
17
36
|
|
|
18
|
-
const key = rawMessage.key || {}
|
|
19
|
-
this.id = key.id || ''
|
|
20
|
-
this.chatId = key.remoteJid || ''
|
|
21
|
-
this.isFromMe = Boolean(key.fromMe)
|
|
22
37
|
this.isGroup = JidNormalizer.isGroup(this.chatId)
|
|
23
38
|
this.isChannel = JidNormalizer.isNewsletter(this.chatId)
|
|
24
39
|
this.isBroadcast = JidNormalizer.isBroadcast(this.chatId)
|
|
25
40
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.
|
|
45
|
-
|
|
46
|
-
//
|
|
41
|
+
// Bot JID resolution
|
|
42
|
+
const sockUser = client?.sock?.user || {}
|
|
43
|
+
const clientJid = sockUser.id ? JidNormalizer.toJid(sockUser.id) : ''
|
|
44
|
+
const clientLid = sockUser.lid ? JidNormalizer.toJid(sockUser.lid) : ''
|
|
45
|
+
|
|
46
|
+
// Origin and Destination (from / to)
|
|
47
|
+
if (this.fromMe) {
|
|
48
|
+
this.from = clientJid || this.chatId
|
|
49
|
+
this.to = this.chatId
|
|
50
|
+
this.fromPushName = sockUser.name || sockUser.notify || 'Você'
|
|
51
|
+
} else {
|
|
52
|
+
this.from = this.isGroup
|
|
53
|
+
? (effectiveKey.participant || rawMessage?.participant || this.chatId)
|
|
54
|
+
: this.chatId
|
|
55
|
+
this.to = clientJid || this.chatId
|
|
56
|
+
this.fromPushName = rawMessage?.pushName || JidNormalizer.toPhoneNumber(this.from) || 'Desconhecido'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.fromNumber = JidNormalizer.toPhoneNumber(this.from)
|
|
60
|
+
|
|
61
|
+
// Timestamps
|
|
62
|
+
const rawTs = Number(rawMessage?.messageTimestamp || Math.floor(Date.now() / 1000))
|
|
63
|
+
this.timestamp = new Date(rawTs > 10000000000 ? rawTs : rawTs * 1000)
|
|
64
|
+
this.editTimestamp = this.isEdited
|
|
65
|
+
? new Date(Number(rawMessage.message.protocolMessage.timestampMs || Date.now()))
|
|
66
|
+
: null
|
|
67
|
+
|
|
68
|
+
// 4. Content Decoupling & Typed Parsers (Single-pass unwrapped nodes)
|
|
69
|
+
this.type = MessageNormalizer.extractType(unwrapped)
|
|
70
|
+
this.body = MessageNormalizer.extractBody(unwrapped)
|
|
71
|
+
this.interactiveReply = MessageNormalizer.extractInteractiveReply(unwrapped)
|
|
72
|
+
this.contacts = MessageNormalizer.extractContacts(unwrapped)
|
|
73
|
+
this.location = MessageNormalizer.extractLocation(unwrapped)
|
|
74
|
+
this.reaction = MessageNormalizer.extractReaction(unwrapped)
|
|
75
|
+
this.poll = MessageNormalizer.extractPollCreation(unwrapped, rawMessage)
|
|
76
|
+
|
|
77
|
+
// 5. Media Normalization (Centralized directly into MediaObject)
|
|
78
|
+
this.mediaNode = MessageNormalizer.extractMediaNode(unwrapped)
|
|
79
|
+
this.media = this.mediaNode ? new MediaObject(this.mediaNode, this.type) : null
|
|
80
|
+
|
|
81
|
+
// 6. Context Info, Mentions & Forwarding
|
|
82
|
+
this.contextInfo = MessageNormalizer.extractContextInfo(unwrapped)
|
|
83
|
+
this.mentions = this.contextInfo?.mentionedJid || []
|
|
84
|
+
this.isForwarded = Boolean(this.contextInfo?.isForwarded)
|
|
85
|
+
this.forwardingScore = Number(this.contextInfo?.forwardingScore || 0)
|
|
86
|
+
|
|
87
|
+
// Checks if the bot was directly mentioned (by PN or LID)
|
|
88
|
+
this.isMentioningMe = Boolean(
|
|
89
|
+
(clientJid && this.mentions.includes(clientJid)) ||
|
|
90
|
+
(clientLid && this.mentions.includes(clientLid))
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// 7. Quoted Message Parsing (Recursion with depth limit 1)
|
|
94
|
+
this.isReply = Boolean(this.contextInfo?.quotedMessage)
|
|
47
95
|
this.quoted = null
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
96
|
+
|
|
97
|
+
if (this.isReply && this.contextInfo?.quotedMessage && depth === 0) {
|
|
98
|
+
const quotedParticipant = this.contextInfo.participant || this.chatId
|
|
99
|
+
const quotedRaw = {
|
|
100
|
+
key: {
|
|
101
|
+
remoteJid: this.chatId,
|
|
102
|
+
id: this.contextInfo.stanzaId,
|
|
103
|
+
fromMe: clientJid && JidNormalizer.toJid(quotedParticipant) === clientJid,
|
|
104
|
+
participant: quotedParticipant
|
|
105
|
+
},
|
|
106
|
+
message: this.contextInfo.quotedMessage,
|
|
107
|
+
pushName: this.contextInfo.pushName
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
this.quoted = new MessageContext(this.client, quotedRaw, depth + 1)
|
|
111
|
+
} catch {
|
|
112
|
+
this.quoted = {
|
|
113
|
+
id: this.contextInfo.stanzaId,
|
|
114
|
+
sender: quotedParticipant,
|
|
115
|
+
senderNumber: JidNormalizer.toPhoneNumber(quotedParticipant),
|
|
116
|
+
type: MessageNormalizer.extractType(this.contextInfo.quotedMessage),
|
|
117
|
+
body: MessageNormalizer.extractBody(this.contextInfo.quotedMessage),
|
|
118
|
+
raw: this.contextInfo.quotedMessage
|
|
119
|
+
}
|
|
57
120
|
}
|
|
58
121
|
}
|
|
59
122
|
}
|
|
60
123
|
|
|
124
|
+
// ==========================================
|
|
125
|
+
// Zero-Cost Memory Aliases & Compatibility Getters
|
|
126
|
+
// ==========================================
|
|
127
|
+
|
|
128
|
+
/** Alias for this.fromMe */
|
|
129
|
+
get isFromMe() {
|
|
130
|
+
return this.fromMe
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Alias for this.from */
|
|
134
|
+
get sender() {
|
|
135
|
+
return this.from
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Alias for this.fromNumber */
|
|
139
|
+
get senderNumber() {
|
|
140
|
+
return this.fromNumber
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Alias for this.fromPushName */
|
|
144
|
+
get senderName() {
|
|
145
|
+
return this.fromPushName
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Alias for this.mentions */
|
|
149
|
+
get mentionedJids() {
|
|
150
|
+
return this.mentions
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Boolean check whether message contains media */
|
|
154
|
+
get hasMedia() {
|
|
155
|
+
return Boolean(this.media)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Backward-compatible media details accessor backed by MediaObject */
|
|
159
|
+
get mediaDetails() {
|
|
160
|
+
if (!this.media) return null
|
|
161
|
+
return {
|
|
162
|
+
mimetype: this.media.mimetype,
|
|
163
|
+
fileName: this.media.fileName,
|
|
164
|
+
caption: this.media.caption,
|
|
165
|
+
duration: this.media.duration,
|
|
166
|
+
isPtt: this.media.isPtt,
|
|
167
|
+
isGif: this.media.isGif,
|
|
168
|
+
isViewOnce: this.media.isViewOnce
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ==========================================
|
|
173
|
+
// Context Action Methods
|
|
174
|
+
// ==========================================
|
|
175
|
+
|
|
61
176
|
/**
|
|
62
177
|
* Responde citando esta mensagem automaticamente
|
|
63
|
-
* @param {string|Object} content - Texto ou
|
|
64
|
-
* @param {Object} [options={}]
|
|
178
|
+
* @param {string|Object} content - Texto ou payload de mensagem
|
|
179
|
+
* @param {Object} [options={}]
|
|
65
180
|
*/
|
|
66
181
|
async reply(content, options = {}) {
|
|
67
182
|
const payload = typeof content === 'string' ? { text: content } : content
|
|
@@ -72,8 +187,8 @@ class MessageContext {
|
|
|
72
187
|
}
|
|
73
188
|
|
|
74
189
|
/**
|
|
75
|
-
* Reage à mensagem com um emoji
|
|
76
|
-
* @param {string} emoji
|
|
190
|
+
* Reage à mensagem com um emoji (ou string vazia para remover)
|
|
191
|
+
* @param {string} emoji
|
|
77
192
|
*/
|
|
78
193
|
async react(emoji) {
|
|
79
194
|
return this.client.sendMessage(this.chatId, {
|
|
@@ -104,13 +219,13 @@ class MessageContext {
|
|
|
104
219
|
* Marca esta mensagem como lida (ticks azuis)
|
|
105
220
|
*/
|
|
106
221
|
async markRead() {
|
|
107
|
-
if (!this.client
|
|
222
|
+
if (!this.client?.sock?.readMessages) return
|
|
108
223
|
return this.client.sock.readMessages([this.raw.key])
|
|
109
224
|
}
|
|
110
225
|
|
|
111
226
|
/**
|
|
112
227
|
* Encaminha esta mensagem para outro destinatário
|
|
113
|
-
* @param {string} targetJid
|
|
228
|
+
* @param {string} targetJid
|
|
114
229
|
* @param {Object} [options={}]
|
|
115
230
|
*/
|
|
116
231
|
async forward(targetJid, options = {}) {
|
|
@@ -122,10 +237,10 @@ class MessageContext {
|
|
|
122
237
|
|
|
123
238
|
/**
|
|
124
239
|
* Fixa a mensagem no chat com duração configurável
|
|
125
|
-
* @param {number} [durationInSeconds=86400]
|
|
240
|
+
* @param {number} [durationInSeconds=86400]
|
|
126
241
|
*/
|
|
127
242
|
async pin(durationInSeconds = 86400) {
|
|
128
|
-
if (!this.client
|
|
243
|
+
if (!this.client?.sock?.pinInChatMessage) {
|
|
129
244
|
throw new Error('Método pinInChatMessage indisponível no socket.')
|
|
130
245
|
}
|
|
131
246
|
return this.client.sock.pinInChatMessage(this.chatId, this.raw.key, 1, durationInSeconds)
|
|
@@ -135,7 +250,7 @@ class MessageContext {
|
|
|
135
250
|
* Desafixa a mensagem do topo da conversa
|
|
136
251
|
*/
|
|
137
252
|
async unpin() {
|
|
138
|
-
if (!this.client
|
|
253
|
+
if (!this.client?.sock?.pinInChatMessage) {
|
|
139
254
|
throw new Error('Método pinInChatMessage indisponível no socket.')
|
|
140
255
|
}
|
|
141
256
|
return this.client.sock.pinInChatMessage(this.chatId, this.raw.key, 2)
|
|
@@ -20,9 +20,18 @@ class EventNormalizer {
|
|
|
20
20
|
if (!Array.isArray(messages)) return
|
|
21
21
|
|
|
22
22
|
for (const rawMsg of messages) {
|
|
23
|
-
// Ignore stub or protocol messages if empty
|
|
23
|
+
// Ignore stub or protocol messages if completely empty
|
|
24
24
|
if (!rawMsg.message && !rawMsg.messageStubType) continue
|
|
25
25
|
|
|
26
|
+
// Handle protocol revoke messages explicitly (delete for everyone)
|
|
27
|
+
const isRevoke = rawMsg.message?.protocolMessage?.type === 0
|
|
28
|
+
if (isRevoke) {
|
|
29
|
+
client.emit(Events.MESSAGE_DELETE, {
|
|
30
|
+
keys: [rawMsg.message.protocolMessage.key || rawMsg.key]
|
|
31
|
+
})
|
|
32
|
+
continue // Do not emit as a new received/sent message
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
const ctx = new MessageContext(client, rawMsg)
|
|
27
36
|
|
|
28
37
|
if (ctx.isFromMe) {
|
|
@@ -30,6 +39,15 @@ class EventNormalizer {
|
|
|
30
39
|
} else {
|
|
31
40
|
client.emit(Events.MESSAGE_RECEIVED, ctx, rawMsg)
|
|
32
41
|
}
|
|
42
|
+
|
|
43
|
+
// If message is an edit, also notify on MESSAGE_UPDATE
|
|
44
|
+
if (ctx.isEdited) {
|
|
45
|
+
client.emit(Events.MESSAGE_UPDATE, [{
|
|
46
|
+
key: ctx.raw.key,
|
|
47
|
+
update: { message: ctx.raw.message },
|
|
48
|
+
ctx
|
|
49
|
+
}])
|
|
50
|
+
}
|
|
33
51
|
}
|
|
34
52
|
})
|
|
35
53
|
|
|
@@ -4,7 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true })
|
|
|
4
4
|
|
|
5
5
|
class JidNormalizer {
|
|
6
6
|
/**
|
|
7
|
-
* Sanitizes and normalizes any phone number or JID string into a canonical JID
|
|
7
|
+
* Sanitizes and normalizes any phone number or JID string into a canonical JID,
|
|
8
|
+
* stripping device specifiers (e.g., ":1@s.whatsapp.net" -> "@s.whatsapp.net")
|
|
8
9
|
* @param {string} input - Phone number (e.g., "+55 (11) 99999-9999") or existing JID
|
|
9
10
|
* @returns {string} Normalized JID (e.g., "5511999999999@s.whatsapp.net")
|
|
10
11
|
*/
|
|
@@ -13,7 +14,9 @@ class JidNormalizer {
|
|
|
13
14
|
const cleaned = input.trim()
|
|
14
15
|
|
|
15
16
|
if (cleaned.includes('@')) {
|
|
16
|
-
|
|
17
|
+
const [userPart, server] = cleaned.split('@')
|
|
18
|
+
const cleanUser = userPart.split(':')[0]
|
|
19
|
+
return `${cleanUser}@${server}`
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
const digits = cleaned.replace(/\D/g, '')
|
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true })
|
|
|
4
4
|
|
|
5
5
|
class MessageNormalizer {
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Recursively unwraps all container wrappers (ephemeral, viewOnce, associatedChild, etc.)
|
|
8
8
|
* @param {Object} rawMessageContent - raw message node from proto
|
|
9
9
|
* @returns {Object} Unwrapped message object
|
|
10
10
|
*/
|
|
@@ -12,81 +12,347 @@ class MessageNormalizer {
|
|
|
12
12
|
if (!rawMessageContent) return null
|
|
13
13
|
|
|
14
14
|
let msg = rawMessageContent
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
msg.
|
|
28
|
-
|
|
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
|
+
}
|
|
29
51
|
}
|
|
30
52
|
|
|
31
53
|
return msg
|
|
32
54
|
}
|
|
33
55
|
|
|
34
56
|
/**
|
|
35
|
-
* Identifies the primary content type of the message
|
|
36
|
-
* @param {Object} messageContent -
|
|
37
|
-
* @returns {string} Normalized type
|
|
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
|
|
38
60
|
*/
|
|
39
61
|
static extractType(messageContent) {
|
|
40
62
|
const msg = this.unwrapMessage(messageContent)
|
|
41
63
|
if (!msg) return 'unknown'
|
|
42
64
|
|
|
65
|
+
// 1. Text & Media Basics
|
|
43
66
|
if (msg.conversation || msg.extendedTextMessage) return 'text'
|
|
44
67
|
if (msg.imageMessage) return 'image'
|
|
68
|
+
if (msg.albumMessage) return 'album'
|
|
45
69
|
if (msg.videoMessage) return msg.videoMessage.gifPlayback ? 'gif' : 'video'
|
|
46
70
|
if (msg.audioMessage) return msg.audioMessage.ptt ? 'voice' : 'audio'
|
|
47
71
|
if (msg.documentMessage) return 'document'
|
|
48
72
|
if (msg.stickerMessage) return 'sticker'
|
|
73
|
+
if (msg.locationMessage) return 'location'
|
|
74
|
+
if (msg.liveLocationMessage) return 'live_location'
|
|
49
75
|
if (msg.contactMessage || msg.contactsArrayMessage) return 'contact'
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
52
84
|
if (msg.reactionMessage) return 'reaction'
|
|
53
|
-
if (msg.
|
|
54
|
-
if (msg.
|
|
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'
|
|
55
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
|
|
56
123
|
if (msg.protocolMessage) return 'protocol'
|
|
57
124
|
|
|
125
|
+
// Fallback to first non-metadata proto key
|
|
58
126
|
const keys = Object.keys(msg).filter(k => k !== 'messageContextInfo' && k !== 'senderKeyDistributionMessage')
|
|
59
127
|
return keys[0] ? keys[0].replace('Message', '').toLowerCase() : 'unknown'
|
|
60
128
|
}
|
|
61
129
|
|
|
62
130
|
/**
|
|
63
131
|
* Extracts readable text content across diverse message structures
|
|
64
|
-
* @param {Object} messageContent -
|
|
132
|
+
* @param {Object} messageContent - Raw or unwrapped message content
|
|
65
133
|
* @returns {string} Text body
|
|
66
134
|
*/
|
|
67
135
|
static extractBody(messageContent) {
|
|
68
136
|
const msg = this.unwrapMessage(messageContent)
|
|
69
137
|
if (!msg) return ''
|
|
70
138
|
|
|
139
|
+
// Direct Text
|
|
71
140
|
if (msg.conversation) return msg.conversation
|
|
72
141
|
if (msg.extendedTextMessage?.text) return msg.extendedTextMessage.text
|
|
142
|
+
|
|
143
|
+
// Media Captions
|
|
73
144
|
if (msg.imageMessage?.caption) return msg.imageMessage.caption
|
|
74
145
|
if (msg.videoMessage?.caption) return msg.videoMessage.caption
|
|
75
146
|
if (msg.documentMessage?.caption) return msg.documentMessage.caption
|
|
147
|
+
|
|
148
|
+
// Button / List selections
|
|
76
149
|
if (msg.buttonsResponseMessage?.selectedDisplayText) return msg.buttonsResponseMessage.selectedDisplayText
|
|
150
|
+
if (msg.buttonsResponseMessage?.selectedButtonId) return msg.buttonsResponseMessage.selectedButtonId
|
|
77
151
|
if (msg.listResponseMessage?.title) return msg.listResponseMessage.title
|
|
152
|
+
if (msg.templateButtonReplyMessage?.selectedDisplayText) return msg.templateButtonReplyMessage.selectedDisplayText
|
|
78
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
|
|
79
157
|
if (msg.interactiveResponseMessage?.nativeFlowResponseMessage?.paramsJson) {
|
|
80
158
|
return msg.interactiveResponseMessage.nativeFlowResponseMessage.paramsJson
|
|
81
159
|
}
|
|
160
|
+
if (msg.interactiveMessage?.body?.text) return msg.interactiveMessage.body.text
|
|
161
|
+
|
|
162
|
+
// Polls, Reactions, Questions & Locations
|
|
82
163
|
if (msg.pollCreationMessage?.name) return msg.pollCreationMessage.name
|
|
83
164
|
if (msg.pollCreationMessageV2?.name) return msg.pollCreationMessageV2.name
|
|
84
165
|
if (msg.pollCreationMessageV3?.name) return msg.pollCreationMessageV3.name
|
|
85
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
|
+
}
|
|
86
172
|
|
|
87
173
|
return ''
|
|
88
174
|
}
|
|
89
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
|
+
|
|
90
356
|
/**
|
|
91
357
|
* Extracts media node if present
|
|
92
358
|
* @param {Object} messageContent - Unwrapped message content
|
|
@@ -106,6 +372,26 @@ class MessageNormalizer {
|
|
|
106
372
|
)
|
|
107
373
|
}
|
|
108
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
|
+
|
|
109
395
|
/**
|
|
110
396
|
* Extracts quoted message context info
|
|
111
397
|
* @param {Object} messageContent - Unwrapped message content
|
|
@@ -125,6 +411,8 @@ class MessageNormalizer {
|
|
|
125
411
|
msg.buttonsResponseMessage?.contextInfo ||
|
|
126
412
|
msg.listResponseMessage?.contextInfo ||
|
|
127
413
|
msg.interactiveResponseMessage?.contextInfo ||
|
|
414
|
+
msg.templateButtonReplyMessage?.contextInfo ||
|
|
415
|
+
msg.messageContextInfo ||
|
|
128
416
|
null
|
|
129
417
|
)
|
|
130
418
|
}
|
package/lib/Client/index.d.ts
CHANGED
|
@@ -58,8 +58,13 @@ export declare class MediaObject {
|
|
|
58
58
|
mimetype: string
|
|
59
59
|
fileLength: number
|
|
60
60
|
fileName: string
|
|
61
|
+
caption: string
|
|
61
62
|
seconds: number
|
|
63
|
+
duration: number | null
|
|
62
64
|
isVoice: boolean
|
|
65
|
+
isPtt: boolean
|
|
66
|
+
isGif: boolean
|
|
67
|
+
isViewOnce: boolean
|
|
63
68
|
isAnimated: boolean
|
|
64
69
|
extension: string
|
|
65
70
|
|
|
@@ -92,13 +97,66 @@ export declare class CallContext {
|
|
|
92
97
|
playAudio(audioPathOrBuffer: string | Buffer): Promise<any>
|
|
93
98
|
}
|
|
94
99
|
|
|
100
|
+
export interface InteractiveReplyData {
|
|
101
|
+
id: string
|
|
102
|
+
text: string
|
|
103
|
+
params?: any
|
|
104
|
+
flowName?: string
|
|
105
|
+
description?: string
|
|
106
|
+
type: 'button' | 'list' | 'native_flow' | 'template_button' | 'question_reply' | string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ContactData {
|
|
110
|
+
name: string
|
|
111
|
+
number: string
|
|
112
|
+
org?: string
|
|
113
|
+
vcard: string
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface LocationData {
|
|
117
|
+
latitude: number
|
|
118
|
+
longitude: number
|
|
119
|
+
name: string
|
|
120
|
+
address: string
|
|
121
|
+
url: string
|
|
122
|
+
accuracyInMeters?: number
|
|
123
|
+
speedInMps?: number
|
|
124
|
+
isLive: boolean
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ReactionData {
|
|
128
|
+
emoji: string
|
|
129
|
+
reactedMessageId: string
|
|
130
|
+
targetKey: any
|
|
131
|
+
isRemoval: boolean
|
|
132
|
+
senderTimestamp: Date
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface PollData {
|
|
136
|
+
id: string
|
|
137
|
+
name: string
|
|
138
|
+
selectableOptionsCount: number
|
|
139
|
+
allowsMultipleAnswers: boolean
|
|
140
|
+
options: Array<{ name: string; hash: string | null }>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface MediaDetailsData {
|
|
144
|
+
mimetype: string
|
|
145
|
+
fileName: string
|
|
146
|
+
caption: string
|
|
147
|
+
duration: number | null
|
|
148
|
+
isPtt: boolean
|
|
149
|
+
isGif: boolean
|
|
150
|
+
isViewOnce: boolean
|
|
151
|
+
}
|
|
152
|
+
|
|
95
153
|
export interface QuotedMessageContext {
|
|
96
154
|
id: string
|
|
97
155
|
sender: string
|
|
98
156
|
senderNumber: string
|
|
99
157
|
type: string
|
|
100
158
|
body: string
|
|
101
|
-
raw: proto.IMessage
|
|
159
|
+
raw: proto.IMessage | any
|
|
102
160
|
}
|
|
103
161
|
|
|
104
162
|
export declare class MessageContext {
|
|
@@ -106,24 +164,48 @@ export declare class MessageContext {
|
|
|
106
164
|
raw: proto.IWebMessageInfo
|
|
107
165
|
id: string
|
|
108
166
|
chatId: string
|
|
167
|
+
fromMe: boolean
|
|
109
168
|
isFromMe: boolean
|
|
110
169
|
isGroup: boolean
|
|
111
170
|
isChannel: boolean
|
|
112
171
|
isBroadcast: boolean
|
|
172
|
+
|
|
173
|
+
from: string
|
|
174
|
+
to: string
|
|
175
|
+
fromPushName: string
|
|
176
|
+
fromNumber: string
|
|
113
177
|
sender: string
|
|
114
178
|
senderNumber: string
|
|
115
179
|
senderName: string
|
|
180
|
+
|
|
181
|
+
timestamp: Date
|
|
182
|
+
isEdited: boolean
|
|
183
|
+
editTimestamp: Date | null
|
|
184
|
+
|
|
116
185
|
type: string
|
|
117
186
|
body: string
|
|
187
|
+
interactiveReply: InteractiveReplyData | null
|
|
188
|
+
contacts: ContactData[]
|
|
189
|
+
location: LocationData | null
|
|
190
|
+
reaction: ReactionData | null
|
|
191
|
+
poll: PollData | null
|
|
192
|
+
|
|
118
193
|
mediaNode: any | null
|
|
119
194
|
hasMedia: boolean
|
|
120
195
|
media: MediaObject | null
|
|
121
|
-
|
|
196
|
+
mediaDetails: MediaDetailsData | null
|
|
197
|
+
|
|
122
198
|
contextInfo: proto.IContextInfo | null
|
|
199
|
+
mentions: string[]
|
|
123
200
|
mentionedJids: string[]
|
|
124
|
-
|
|
201
|
+
isForwarded: boolean
|
|
202
|
+
forwardingScore: number
|
|
203
|
+
isMentioningMe: boolean
|
|
204
|
+
|
|
205
|
+
isReply: boolean
|
|
206
|
+
quoted: MessageContext | QuotedMessageContext | null
|
|
125
207
|
|
|
126
|
-
constructor(client: WaClient, rawMessage: proto.IWebMessageInfo)
|
|
208
|
+
constructor(client: WaClient, rawMessage: proto.IWebMessageInfo, depth?: number)
|
|
127
209
|
|
|
128
210
|
reply(content: string | AnyMessageContent, options?: MiscMessageGenerationOptions): Promise<proto.WebMessageInfo | undefined>
|
|
129
211
|
react(emoji: string): Promise<proto.WebMessageInfo | undefined>
|
|
@@ -397,7 +479,13 @@ export declare class MessageNormalizer {
|
|
|
397
479
|
static unwrapMessage(rawMessageContent: any): any
|
|
398
480
|
static extractType(messageContent: any): string
|
|
399
481
|
static extractBody(messageContent: any): string
|
|
482
|
+
static extractInteractiveReply(messageContent: any): InteractiveReplyData | null
|
|
483
|
+
static extractContacts(messageContent: any): ContactData[]
|
|
484
|
+
static extractLocation(messageContent: any): LocationData | null
|
|
485
|
+
static extractReaction(messageContent: any): ReactionData | null
|
|
486
|
+
static extractPollCreation(messageContent: any, rawMessage?: any): PollData | null
|
|
400
487
|
static extractMediaNode(messageContent: any): any | null
|
|
488
|
+
static extractMediaDetails(messageContent: any): MediaDetailsData | null
|
|
401
489
|
static extractContextInfo(messageContent: any): proto.IContextInfo | null
|
|
402
490
|
}
|
|
403
491
|
|