@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.
@@ -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
  }
@@ -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: serverId,
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
- newsletterFetchMessages: async (jid, count, since, after) => {
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',
@@ -0,0 +1,4 @@
1
+ import { InMemoryStore, InMemoryStoreConfig } from '../index'
2
+
3
+ export declare function makeInMemoryStore(config?: InMemoryStoreConfig): InMemoryStore
4
+ export declare function makeCacheManagerAuthState(store: any, sessionKey: string): Promise<any>