@areumtecnologia/wasockets 1.1.5 → 2.0.0-alpha.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.
- package/README.md +867 -27
- package/WAProto/index.d.ts +17 -0
- package/lib/Defaults/connection.js +34 -2
- package/lib/Defaults/index.d.ts +6 -0
- package/lib/Plugins/index.d.ts +33 -0
- package/lib/Plugins/index.js +125 -0
- package/lib/Socket/business.js +33 -0
- package/lib/Socket/chats.js +173 -1
- package/lib/Socket/community.js +126 -69
- package/lib/Socket/groups.js +103 -26
- package/lib/Socket/index.js +27 -3
- package/lib/Socket/messages-recv.js +42 -0
- package/lib/Socket/messages-send.js +174 -2
- package/lib/Socket/newsletter.js +125 -3
- package/lib/Store/index.d.ts +4 -0
- package/lib/Store/make-in-memory-store.js +294 -25
- package/lib/Types/index.d.ts +199 -0
- package/lib/Utils/chat-utils.js +51 -0
- package/lib/Utils/index.d.ts +33 -0
- package/lib/Utils/index.js +2 -1
- package/lib/Utils/messages.js +48 -3
- package/lib/Utils/reconnect.js +136 -0
- package/lib/Utils/validate-connection.js +5 -5
- package/lib/WABinary/index.d.ts +17 -0
- package/lib/index.d.ts +274 -0
- package/lib/index.js +1 -0
- package/package.json +19 -5
|
@@ -105,6 +105,41 @@ const makeMessagesSocket = (config) => {
|
|
|
105
105
|
// Initialize message retry manager if enabled
|
|
106
106
|
const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null
|
|
107
107
|
|
|
108
|
+
// Pending message queue for transient disconnections
|
|
109
|
+
const pendingMessagesQueue = []
|
|
110
|
+
let isDrainingQueue = false
|
|
111
|
+
|
|
112
|
+
const drainPendingMessages = async () => {
|
|
113
|
+
if (isDrainingQueue || !pendingMessagesQueue.length) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
isDrainingQueue = true
|
|
117
|
+
logger.debug({ queueLength: pendingMessagesQueue.length }, 'draining pending messages queue')
|
|
118
|
+
try {
|
|
119
|
+
while (pendingMessagesQueue.length && suki.ws?.isOpen) {
|
|
120
|
+
const item = pendingMessagesQueue.shift()
|
|
121
|
+
if (Date.now() - item.queuedAt > (config.messageQueueTtlMs || 300000)) {
|
|
122
|
+
item.reject(new Boom('Message queue TTL expired', { statusCode: 408 }))
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const result = await sendMessage(item.jid, item.content, item.options)
|
|
127
|
+
item.resolve(result)
|
|
128
|
+
} catch (err) {
|
|
129
|
+
item.reject(err)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
isDrainingQueue = false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ev.on('connection.update', ({ connection }) => {
|
|
138
|
+
if (connection === 'open') {
|
|
139
|
+
drainPendingMessages()
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
108
143
|
// Prevent race conditions in Signal session encryption by user
|
|
109
144
|
const encryptionMutex = makeKeyedMutex()
|
|
110
145
|
|
|
@@ -510,6 +545,15 @@ const makeMessagesSocket = (config) => {
|
|
|
510
545
|
return didFetchNewSession
|
|
511
546
|
}
|
|
512
547
|
|
|
548
|
+
const repairSession = async (jid) => {
|
|
549
|
+
logger.info({ jid }, 'repairing Signal session')
|
|
550
|
+
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
|
|
551
|
+
peerSessionsCache.delete(signalId)
|
|
552
|
+
await signalRepository.deleteSession([jid])
|
|
553
|
+
await assertSessions([jid], true)
|
|
554
|
+
logger.info({ jid }, 'signal session repaired successfully')
|
|
555
|
+
}
|
|
556
|
+
|
|
513
557
|
const sendPeerDataOperationMessage = async (pdoMessage) => {
|
|
514
558
|
//TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
|
|
515
559
|
if (!authState.creds.me?.id) {
|
|
@@ -736,7 +780,7 @@ const makeMessagesSocket = (config) => {
|
|
|
736
780
|
return
|
|
737
781
|
}
|
|
738
782
|
|
|
739
|
-
if (messages.pinInChatMessage || messages.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage) {
|
|
783
|
+
if (messages.pinInChatMessage || messages.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage || message.encEventResponseMessage || message.commentMessage) {
|
|
740
784
|
extraAttrs['decrypt-fail'] = 'hide'
|
|
741
785
|
}
|
|
742
786
|
|
|
@@ -1140,9 +1184,15 @@ const makeMessagesSocket = (config) => {
|
|
|
1140
1184
|
else if (message.reactionMessage) {
|
|
1141
1185
|
return 'reaction'
|
|
1142
1186
|
}
|
|
1143
|
-
else if (message.eventMessage) {
|
|
1187
|
+
else if (message.eventMessage || message.eventResponseMessage || message.encEventResponseMessage) {
|
|
1144
1188
|
return 'event'
|
|
1145
1189
|
}
|
|
1190
|
+
else if (message.pinInChatMessage) {
|
|
1191
|
+
return 'pin'
|
|
1192
|
+
}
|
|
1193
|
+
else if (message.commentMessage) {
|
|
1194
|
+
return 'comment'
|
|
1195
|
+
}
|
|
1146
1196
|
else if (getMediaType(message)) {
|
|
1147
1197
|
return 'media'
|
|
1148
1198
|
}
|
|
@@ -1389,6 +1439,8 @@ const makeMessagesSocket = (config) => {
|
|
|
1389
1439
|
...suki,
|
|
1390
1440
|
getPrivacyTokens,
|
|
1391
1441
|
assertSessions,
|
|
1442
|
+
repairSession,
|
|
1443
|
+
drainPendingMessages,
|
|
1392
1444
|
profilePictureUrl,
|
|
1393
1445
|
relayMessage,
|
|
1394
1446
|
sendReceipt,
|
|
@@ -1609,7 +1661,105 @@ const makeMessagesSocket = (config) => {
|
|
|
1609
1661
|
|
|
1610
1662
|
return msg
|
|
1611
1663
|
},
|
|
1664
|
+
reactToStatus: async (key, reaction = '❤️') => {
|
|
1665
|
+
const author = key.participant || key.remoteJid
|
|
1666
|
+
const targetJid = STORIES_JID
|
|
1667
|
+
return relayMessage(
|
|
1668
|
+
targetJid,
|
|
1669
|
+
{
|
|
1670
|
+
reactionMessage: {
|
|
1671
|
+
key,
|
|
1672
|
+
text: reaction,
|
|
1673
|
+
senderTimestampMs: Date.now()
|
|
1674
|
+
}
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
messageId: generateMessageID(authState.creds.me.id),
|
|
1678
|
+
statusJidList: [jidNormalizedUser(author)],
|
|
1679
|
+
additionalNodes: [
|
|
1680
|
+
{
|
|
1681
|
+
tag: 'meta',
|
|
1682
|
+
attrs: {},
|
|
1683
|
+
content: [
|
|
1684
|
+
{
|
|
1685
|
+
tag: 'target',
|
|
1686
|
+
attrs: {
|
|
1687
|
+
chat: targetJid,
|
|
1688
|
+
author: jidNormalizedUser(author)
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
]
|
|
1692
|
+
}
|
|
1693
|
+
]
|
|
1694
|
+
}
|
|
1695
|
+
)
|
|
1696
|
+
},
|
|
1697
|
+
sendEventResponse: async (jid, eventCreationMessageKey, response, extraGuestCount = 0) => {
|
|
1698
|
+
const responseType = typeof response === 'string'
|
|
1699
|
+
? (proto.Message.EventResponseMessage?.EventResponseType?.[response] ?? 1)
|
|
1700
|
+
: response
|
|
1701
|
+
|
|
1702
|
+
return sendMessage(jid, {
|
|
1703
|
+
eventResponse: {
|
|
1704
|
+
eventKey: eventCreationMessageKey,
|
|
1705
|
+
response: responseType,
|
|
1706
|
+
extraGuestCount,
|
|
1707
|
+
timestampMs: Date.now()
|
|
1708
|
+
}
|
|
1709
|
+
})
|
|
1710
|
+
},
|
|
1711
|
+
cancelEvent: async (jid, eventKey) => {
|
|
1712
|
+
return sendMessage(jid, {
|
|
1713
|
+
event: {
|
|
1714
|
+
isCanceled: true
|
|
1715
|
+
},
|
|
1716
|
+
edit: eventKey
|
|
1717
|
+
})
|
|
1718
|
+
},
|
|
1719
|
+
sendAlbumMessage: async (jid, medias, options = {}) => {
|
|
1720
|
+
return sendMessage(jid, { album: medias }, options)
|
|
1721
|
+
},
|
|
1722
|
+
groupSendMentionAll: async (jid, content, options = {}) => {
|
|
1723
|
+
const metadata = await suki.groupMetadata(jid)
|
|
1724
|
+
let participants = metadata.participants || []
|
|
1725
|
+
if (options.adminsOnly) {
|
|
1726
|
+
participants = participants.filter(p => !!p.admin)
|
|
1727
|
+
}
|
|
1728
|
+
if (options.excludeMe && authState.creds.me?.id) {
|
|
1729
|
+
const myPn = jidNormalizedUser(authState.creds.me.id)
|
|
1730
|
+
participants = participants.filter(p => jidNormalizedUser(p.id) !== myPn && (!p.lid || jidNormalizedUser(p.lid) !== myPn))
|
|
1731
|
+
}
|
|
1732
|
+
const mentionJids = participants.map(p => p.id || p.lid).filter(Boolean)
|
|
1733
|
+
const msgContent = typeof content === 'string' ? { text: content } : { ...content }
|
|
1734
|
+
msgContent.mentions = [...new Set([...(msgContent.mentions || []), ...mentionJids])]
|
|
1735
|
+
return sendMessage(jid, msgContent, options)
|
|
1736
|
+
},
|
|
1612
1737
|
sendMessage: async (jid, content, options = {}) => {
|
|
1738
|
+
if (config.enableMessageQueue && (!suki.ws || !suki.ws.isOpen)) {
|
|
1739
|
+
logger.info({ jid }, 'WebSocket connection not open, queuing message')
|
|
1740
|
+
return new Promise((resolve, reject) => {
|
|
1741
|
+
pendingMessagesQueue.push({
|
|
1742
|
+
jid,
|
|
1743
|
+
content,
|
|
1744
|
+
options,
|
|
1745
|
+
queuedAt: Date.now(),
|
|
1746
|
+
resolve,
|
|
1747
|
+
reject
|
|
1748
|
+
})
|
|
1749
|
+
})
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
const sendContext = { jid, content, options }
|
|
1753
|
+
if (suki.plugins) {
|
|
1754
|
+
const allowed = await suki.plugins.runMiddlewares('beforeSendMessage', sendContext)
|
|
1755
|
+
if (allowed === false) {
|
|
1756
|
+
logger.info({ jid }, 'Message sending cancelled by plugin middleware')
|
|
1757
|
+
return
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
content = sendContext.content
|
|
1761
|
+
options = sendContext.options
|
|
1762
|
+
|
|
1613
1763
|
const userJid = authState.creds.me.id
|
|
1614
1764
|
const additionalAttributes = {}
|
|
1615
1765
|
|
|
@@ -1620,6 +1770,24 @@ const makeMessagesSocket = (config) => {
|
|
|
1620
1770
|
}
|
|
1621
1771
|
}
|
|
1622
1772
|
|
|
1773
|
+
if (isJidGroup(jid) && typeof content === 'object' && (content?.mentionAll || options?.mentionAll)) {
|
|
1774
|
+
try {
|
|
1775
|
+
const metadata = await suki.groupMetadata(jid)
|
|
1776
|
+
let participants = metadata.participants || []
|
|
1777
|
+
if (options.adminsOnly || content.adminsOnly) {
|
|
1778
|
+
participants = participants.filter(p => !!p.admin)
|
|
1779
|
+
}
|
|
1780
|
+
if ((options.excludeMe || content.excludeMe) && authState.creds.me?.id) {
|
|
1781
|
+
const myPn = jidNormalizedUser(authState.creds.me.id)
|
|
1782
|
+
participants = participants.filter(p => jidNormalizedUser(p.id) !== myPn && (!p.lid || jidNormalizedUser(p.lid) !== myPn))
|
|
1783
|
+
}
|
|
1784
|
+
const mentionJids = participants.map(p => p.id || p.lid).filter(Boolean)
|
|
1785
|
+
content.mentions = [...new Set([...(content.mentions || []), ...mentionJids])]
|
|
1786
|
+
} catch (err) {
|
|
1787
|
+
logger.warn({ err, jid }, 'Failed to resolve group participants for mentionAll')
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1623
1791
|
if (typeof content === 'object' &&
|
|
1624
1792
|
'disappearingMessagesInChat' in content &&
|
|
1625
1793
|
typeof content['disappearingMessagesInChat'] !== 'undefined' &&
|
|
@@ -1722,6 +1890,10 @@ const makeMessagesSocket = (config) => {
|
|
|
1722
1890
|
})
|
|
1723
1891
|
}
|
|
1724
1892
|
|
|
1893
|
+
if (suki.plugins) {
|
|
1894
|
+
await suki.plugins.runMiddlewares('afterSendMessage', { jid, content, options, fullMsg })
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1725
1897
|
return fullMsg
|
|
1726
1898
|
}
|
|
1727
1899
|
}
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true })
|
|
4
4
|
|
|
5
|
+
const { proto } = require("../../WAProto")
|
|
5
6
|
const {
|
|
6
7
|
QueryIds,
|
|
7
8
|
XWAPaths
|
|
8
9
|
} = require("../Types")
|
|
9
10
|
const { generateProfilePicture } = require("../Utils")
|
|
10
|
-
const { getBinaryNodeChild } = require("../WABinary")
|
|
11
|
+
const { getBinaryNodeChild, getBinaryNodeChildren } = require("../WABinary")
|
|
11
12
|
const { makeGroupsSocket } = require("./groups")
|
|
12
13
|
const { executeWMexQuery: genericExecuteWMexQuery } = require("./mex")
|
|
13
14
|
|
|
@@ -132,13 +133,14 @@ const makeNewsletterSocket = (config) => {
|
|
|
132
133
|
return await newsletterUpdate(jid, { picture: '' })
|
|
133
134
|
},
|
|
134
135
|
newsletterReactMessage: async (jid, serverId, reaction) => {
|
|
136
|
+
const targetServerId = typeof serverId === 'object' ? (serverId.server_id || serverId.id) : serverId
|
|
135
137
|
await query({
|
|
136
138
|
tag: 'message',
|
|
137
139
|
attrs: {
|
|
138
140
|
to: jid,
|
|
139
141
|
...(reaction ? {} : { edit: '7' }),
|
|
140
142
|
type: 'reaction',
|
|
141
|
-
server_id:
|
|
143
|
+
server_id: targetServerId?.toString(),
|
|
142
144
|
id: generateMessageTag()
|
|
143
145
|
},
|
|
144
146
|
content: [
|
|
@@ -149,7 +151,87 @@ const makeNewsletterSocket = (config) => {
|
|
|
149
151
|
]
|
|
150
152
|
})
|
|
151
153
|
},
|
|
152
|
-
|
|
154
|
+
newsletterFetchReactions: async (jid, serverId) => {
|
|
155
|
+
const targetServerId = typeof serverId === 'object' ? (serverId.server_id || serverId.id) : serverId
|
|
156
|
+
const result = await query({
|
|
157
|
+
tag: 'iq',
|
|
158
|
+
attrs: {
|
|
159
|
+
id: generateMessageTag(),
|
|
160
|
+
type: 'get',
|
|
161
|
+
xmlns: 'newsletter',
|
|
162
|
+
to: jid
|
|
163
|
+
},
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
tag: 'reactions',
|
|
167
|
+
attrs: {
|
|
168
|
+
server_id: targetServerId?.toString()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const reactionsNode = getBinaryNodeChild(result, 'reactions')
|
|
175
|
+
if (!reactionsNode) return []
|
|
176
|
+
const reactionChildren = getBinaryNodeChildren(reactionsNode, 'reaction')
|
|
177
|
+
return reactionChildren.map(r => ({
|
|
178
|
+
code: r.attrs.code,
|
|
179
|
+
count: parseInt(r.attrs.count || '0', 10)
|
|
180
|
+
}))
|
|
181
|
+
},
|
|
182
|
+
newsletterFetchMessage: async (jid, serverId) => {
|
|
183
|
+
const targetServerId = typeof serverId === 'object' ? (serverId.server_id || serverId.id) : serverId
|
|
184
|
+
const result = await query({
|
|
185
|
+
tag: 'iq',
|
|
186
|
+
attrs: {
|
|
187
|
+
id: generateMessageTag(),
|
|
188
|
+
type: 'get',
|
|
189
|
+
xmlns: 'newsletter',
|
|
190
|
+
to: jid
|
|
191
|
+
},
|
|
192
|
+
content: [
|
|
193
|
+
{
|
|
194
|
+
tag: 'message',
|
|
195
|
+
attrs: {
|
|
196
|
+
server_id: targetServerId?.toString()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
const messageNode = getBinaryNodeChild(result, 'message')
|
|
203
|
+
if (!messageNode) return null
|
|
204
|
+
|
|
205
|
+
const plaintextNode = getBinaryNodeChild(messageNode, 'plaintext')
|
|
206
|
+
let messageProto
|
|
207
|
+
if (plaintextNode?.content) {
|
|
208
|
+
try {
|
|
209
|
+
const buffer = Buffer.isBuffer(plaintextNode.content)
|
|
210
|
+
? plaintextNode.content
|
|
211
|
+
: Buffer.from(plaintextNode.content)
|
|
212
|
+
messageProto = proto.Message.decode(buffer)
|
|
213
|
+
} catch (e) {
|
|
214
|
+
// ignore decode error
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const reactionsNode = getBinaryNodeChild(messageNode, 'reactions')
|
|
219
|
+
const reactions = reactionsNode
|
|
220
|
+
? getBinaryNodeChildren(reactionsNode, 'reaction').map(r => ({
|
|
221
|
+
code: r.attrs.code,
|
|
222
|
+
count: parseInt(r.attrs.count || '0', 10)
|
|
223
|
+
}))
|
|
224
|
+
: []
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
server_id: messageNode.attrs.server_id,
|
|
228
|
+
messageTimestamp: +messageNode.attrs.t,
|
|
229
|
+
views: messageNode.attrs.views ? +messageNode.attrs.views : undefined,
|
|
230
|
+
message: messageProto,
|
|
231
|
+
reactions
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
newsletterFetchMessages: async (jid, count = 20, since, after) => {
|
|
153
235
|
const messageUpdateAttrs = {
|
|
154
236
|
count: count.toString()
|
|
155
237
|
}
|
|
@@ -178,8 +260,48 @@ const makeNewsletterSocket = (config) => {
|
|
|
178
260
|
]
|
|
179
261
|
})
|
|
180
262
|
|
|
263
|
+
const messageUpdatesNode = getBinaryNodeChild(result, 'message_updates')
|
|
264
|
+
const messageNodes = getBinaryNodeChildren(messageUpdatesNode, 'message')
|
|
265
|
+
|
|
266
|
+
const messages = messageNodes.map(node => {
|
|
267
|
+
const plaintextNode = getBinaryNodeChild(node, 'plaintext')
|
|
268
|
+
let messageProto
|
|
269
|
+
if (plaintextNode?.content) {
|
|
270
|
+
try {
|
|
271
|
+
const buffer = Buffer.isBuffer(plaintextNode.content)
|
|
272
|
+
? plaintextNode.content
|
|
273
|
+
: Buffer.from(plaintextNode.content)
|
|
274
|
+
messageProto = proto.Message.decode(buffer)
|
|
275
|
+
} catch (e) {
|
|
276
|
+
// ignore decode errors
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const reactionsNode = getBinaryNodeChild(node, 'reactions')
|
|
281
|
+
const reactions = reactionsNode
|
|
282
|
+
? getBinaryNodeChildren(reactionsNode, 'reaction').map(r => ({
|
|
283
|
+
code: r.attrs.code,
|
|
284
|
+
count: parseInt(r.attrs.count || '0', 10)
|
|
285
|
+
}))
|
|
286
|
+
: []
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
server_id: node.attrs.server_id,
|
|
290
|
+
messageTimestamp: +node.attrs.t,
|
|
291
|
+
views: node.attrs.views ? +node.attrs.views : undefined,
|
|
292
|
+
message: messageProto,
|
|
293
|
+
reactions
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
result.messages = messages
|
|
181
298
|
return result
|
|
182
299
|
},
|
|
300
|
+
newsletterToggleMute: async (jid, mute = true) => {
|
|
301
|
+
return mute
|
|
302
|
+
? executeWMexQuery({ newsletter_id: jid }, QueryIds.MUTE, XWAPaths.MUTE_V2)
|
|
303
|
+
: executeWMexQuery({ newsletter_id: jid }, QueryIds.UNMUTE, XWAPaths.UNMUTE_V2)
|
|
304
|
+
},
|
|
183
305
|
subscribeNewsletterUpdates: async (jid) => {
|
|
184
306
|
const result = await query({
|
|
185
307
|
tag: 'iq',
|