@pontalabs/baileys 1.0.6 → 1.0.8

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 (185) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +839 -267
  3. package/WAProto/GenerateStatics.sh +4 -3
  4. package/WAProto/WAProto.proto +511 -1215
  5. package/WAProto/index.d.ts +55 -14017
  6. package/WAProto/index.js +29 -97689
  7. package/lib/Defaults/index.d.ts +74 -72
  8. package/lib/Defaults/index.js +108 -94
  9. package/lib/Signal/Group/ciphertext-message.d.ts +7 -8
  10. package/lib/Signal/Group/ciphertext-message.js +16 -9
  11. package/lib/Signal/Group/group-session-builder.d.ts +13 -11
  12. package/lib/Signal/Group/group-session-builder.js +61 -19
  13. package/lib/Signal/Group/group_cipher.d.ts +16 -14
  14. package/lib/Signal/Group/group_cipher.js +70 -41
  15. package/lib/Signal/Group/index.d.ts +11 -12
  16. package/lib/Signal/Group/index.js +61 -12
  17. package/lib/Signal/Group/keyhelper.d.ts +14 -9
  18. package/lib/Signal/Group/keyhelper.js +58 -10
  19. package/lib/Signal/Group/sender-chain-key.d.ts +13 -13
  20. package/lib/Signal/Group/sender-chain-key.js +34 -13
  21. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -16
  22. package/lib/Signal/Group/sender-key-distribution-message.js +36 -28
  23. package/lib/Signal/Group/sender-key-message.d.ts +18 -18
  24. package/lib/Signal/Group/sender-key-message.js +45 -38
  25. package/lib/Signal/Group/sender-key-name.d.ts +15 -14
  26. package/lib/Signal/Group/sender-key-name.js +31 -20
  27. package/lib/Signal/Group/sender-key-record.d.ts +25 -24
  28. package/lib/Signal/Group/sender-key-record.js +35 -18
  29. package/lib/Signal/Group/sender-key-state.d.ts +34 -29
  30. package/lib/Signal/Group/sender-key-state.js +100 -37
  31. package/lib/Signal/Group/sender-message-key.d.ts +10 -11
  32. package/lib/Signal/Group/sender-message-key.js +22 -15
  33. package/lib/Signal/libsignal.d.ts +8 -5
  34. package/lib/Signal/libsignal.js +142 -288
  35. package/lib/Signal/lid-mapping.d.ts +28 -23
  36. package/lib/Signal/lid-mapping.js +184 -277
  37. package/lib/Socket/Client/index.d.ts +2 -3
  38. package/lib/Socket/Client/index.js +22 -3
  39. package/lib/Socket/Client/types.d.ts +15 -15
  40. package/lib/Socket/Client/types.js +15 -8
  41. package/lib/Socket/Client/websocket.d.ts +12 -12
  42. package/lib/Socket/Client/websocket.js +37 -29
  43. package/lib/Socket/business.d.ts +178 -208
  44. package/lib/Socket/business.js +127 -91
  45. package/lib/Socket/chats.d.ts +93 -120
  46. package/lib/Socket/chats.js +652 -727
  47. package/lib/Socket/community.js +10 -2
  48. package/lib/Socket/groups.d.ts +111 -143
  49. package/lib/Socket/groups.js +186 -165
  50. package/lib/Socket/index.d.ts +183 -252
  51. package/lib/Socket/index.js +18 -38
  52. package/lib/Socket/messages-recv.d.ts +167 -196
  53. package/lib/Socket/messages-recv.js +1567 -1443
  54. package/lib/Socket/messages-send.d.ts +158 -192
  55. package/lib/Socket/messages-send.js +1208 -1102
  56. package/lib/Socket/newsletter.d.ts +138 -163
  57. package/lib/Socket/newsletter.js +249 -153
  58. package/lib/Socket/socket.d.ts +36 -50
  59. package/lib/Socket/socket.js +820 -749
  60. package/lib/Store/index.d.ts +4 -4
  61. package/lib/Store/index.js +24 -4
  62. package/lib/Store/make-in-memory-store.d.ts +104 -44
  63. package/lib/Store/make-in-memory-store.js +213 -199
  64. package/lib/Store/make-ordered-dictionary.d.ts +12 -13
  65. package/lib/Store/make-ordered-dictionary.js +45 -38
  66. package/lib/Store/object-repository.d.ts +10 -11
  67. package/lib/Store/object-repository.js +16 -9
  68. package/lib/Types/Auth.d.ts +97 -93
  69. package/lib/Types/Auth.js +3 -2
  70. package/lib/Types/Bussines.js +3 -2
  71. package/lib/Types/Call.d.ts +13 -14
  72. package/lib/Types/Call.js +3 -2
  73. package/lib/Types/Chat.d.ts +97 -78
  74. package/lib/Types/Chat.js +9 -8
  75. package/lib/Types/Contact.d.ts +9 -12
  76. package/lib/Types/Contact.js +3 -2
  77. package/lib/Types/Events.d.ts +176 -206
  78. package/lib/Types/Events.js +3 -2
  79. package/lib/Types/GroupMetadata.d.ts +48 -53
  80. package/lib/Types/GroupMetadata.js +3 -2
  81. package/lib/Types/Label.d.ts +14 -13
  82. package/lib/Types/Label.js +30 -24
  83. package/lib/Types/LabelAssociation.d.ts +20 -15
  84. package/lib/Types/LabelAssociation.js +12 -6
  85. package/lib/Types/Message.d.ts +412 -248
  86. package/lib/Types/Message.js +19 -17
  87. package/lib/Types/Product.d.ts +71 -58
  88. package/lib/Types/Product.js +3 -2
  89. package/lib/Types/Signal.d.ts +82 -71
  90. package/lib/Types/Signal.js +3 -2
  91. package/lib/Types/Socket.d.ts +81 -76
  92. package/lib/Types/Socket.js +3 -3
  93. package/lib/Types/State.d.ts +19 -75
  94. package/lib/Types/State.js +14 -56
  95. package/lib/Types/USync.d.ts +8 -8
  96. package/lib/Types/USync.js +3 -2
  97. package/lib/Types/index.d.ts +62 -47
  98. package/lib/Types/index.js +50 -26
  99. package/lib/Utils/auth-utils.d.ts +10 -13
  100. package/lib/Utils/auth-utils.js +472 -213
  101. package/lib/Utils/business.d.ts +20 -14
  102. package/lib/Utils/business.js +134 -110
  103. package/lib/Utils/chat-utils.d.ts +70 -88
  104. package/lib/Utils/chat-utils.js +403 -466
  105. package/lib/Utils/crypto.d.ts +46 -27
  106. package/lib/Utils/crypto.js +188 -117
  107. package/lib/Utils/decode-wa-message.d.ts +45 -58
  108. package/lib/Utils/decode-wa-message.js +311 -212
  109. package/lib/Utils/event-buffer.d.ts +17 -14
  110. package/lib/Utils/event-buffer.js +296 -321
  111. package/lib/Utils/generics.d.ts +92 -66
  112. package/lib/Utils/generics.js +436 -270
  113. package/lib/Utils/history.d.ts +23 -24
  114. package/lib/Utils/history.js +76 -106
  115. package/lib/Utils/index.d.ts +21 -24
  116. package/lib/Utils/index.js +41 -24
  117. package/lib/Utils/link-preview.d.ts +14 -12
  118. package/lib/Utils/link-preview.js +75 -40
  119. package/lib/Utils/logger.d.ts +11 -10
  120. package/lib/Utils/logger.js +7 -3
  121. package/lib/Utils/lt-hash.d.ts +14 -8
  122. package/lib/Utils/lt-hash.js +53 -3
  123. package/lib/Utils/make-mutex.d.ts +7 -7
  124. package/lib/Utils/make-mutex.js +44 -28
  125. package/lib/Utils/message-retry-manager.d.ts +88 -115
  126. package/lib/Utils/message-retry-manager.js +160 -265
  127. package/lib/Utils/messages-media.d.ts +107 -105
  128. package/lib/Utils/messages-media.js +619 -548
  129. package/lib/Utils/messages.d.ts +66 -52
  130. package/lib/Utils/messages.js +1558 -1730
  131. package/lib/Utils/noise-handler.d.ts +18 -18
  132. package/lib/Utils/noise-handler.js +130 -176
  133. package/lib/Utils/process-message.d.ts +32 -43
  134. package/lib/Utils/process-message.js +282 -484
  135. package/lib/Utils/rich-message-utils.js +724 -1220
  136. package/lib/Utils/signal.d.ts +32 -37
  137. package/lib/Utils/signal.js +105 -140
  138. package/lib/Utils/use-multi-file-auth-state.d.ts +5 -5
  139. package/lib/Utils/use-multi-file-auth-state.js +120 -70
  140. package/lib/Utils/use-single-file-auth-state.d.ts +13 -11
  141. package/lib/Utils/use-single-file-auth-state.js +66 -92
  142. package/lib/Utils/validate-connection.d.ts +13 -11
  143. package/lib/Utils/validate-connection.js +113 -128
  144. package/lib/WABinary/constants.d.ts +27 -25
  145. package/lib/WABinary/constants.js +1292 -1451
  146. package/lib/WABinary/decode.d.ts +9 -7
  147. package/lib/WABinary/decode.js +189 -163
  148. package/lib/WABinary/encode.d.ts +3 -3
  149. package/lib/WABinary/encode.js +155 -110
  150. package/lib/WABinary/generic-utils.d.ts +27 -14
  151. package/lib/WABinary/generic-utils.js +105 -177
  152. package/lib/WABinary/index.d.ts +5 -6
  153. package/lib/WABinary/index.js +25 -6
  154. package/lib/WABinary/jid-utils.d.ts +54 -41
  155. package/lib/WABinary/jid-utils.js +152 -83
  156. package/lib/WABinary/types.d.ts +13 -10
  157. package/lib/WABinary/types.js +3 -2
  158. package/lib/WAM/BinaryInfo.d.ts +15 -8
  159. package/lib/WAM/BinaryInfo.js +14 -7
  160. package/lib/WAM/constants.d.ts +37 -30
  161. package/lib/WAM/constants.js +11983 -19465
  162. package/lib/WAM/encode.d.ts +3 -3
  163. package/lib/WAM/encode.js +109 -95
  164. package/lib/WAM/index.d.ts +3 -4
  165. package/lib/WAM/index.js +23 -4
  166. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -9
  167. package/lib/WAUSync/Protocols/USyncContactProtocol.js +21 -37
  168. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -19
  169. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +26 -18
  170. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -11
  171. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +20 -12
  172. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -11
  173. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +24 -16
  174. package/lib/WAUSync/Protocols/index.d.ts +6 -6
  175. package/lib/WAUSync/Protocols/index.js +26 -6
  176. package/lib/WAUSync/USyncQuery.d.ts +28 -27
  177. package/lib/WAUSync/USyncQuery.js +62 -68
  178. package/lib/WAUSync/USyncUser.d.ts +10 -15
  179. package/lib/WAUSync/USyncUser.js +19 -20
  180. package/lib/WAUSync/index.d.ts +3 -4
  181. package/lib/WAUSync/index.js +23 -4
  182. package/lib/index.d.ts +12 -11
  183. package/lib/index.js +39 -12
  184. package/package.json +43 -87
  185. package/engine-requirements.js +0 -10
@@ -1,1408 +1,1514 @@
1
- import NodeCache from '@cacheable/node-cache';
2
- import { Boom } from '@hapi/boom';
3
- import { randomBytes } from 'crypto';
4
- import { proto } from '../../WAProto/index.js';
5
- import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
6
- import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageContent, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
7
- import { AssociationType } from '../Types/index.js';
8
- import { getUrlInfo } from '../Utils/link-preview.js';
9
- import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
10
- import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
11
- import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, resolveTcTokenJid, shouldSendNewTcToken, storeTcTokensFromIqResult } from '../Utils/tc-token-utils.js';
12
- import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isJidNewsletter, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET } from '../WABinary/index.js';
13
- import { USyncQuery, USyncUser } from '../WAUSync/index.js';
14
- import { makeNewsletterSocket } from './newsletter.js';
15
- import kikyy from './dugong.js';
16
- import { generateTableContent, generateTableContentV2, generateListContent, generateCodeBlockContent, generateCodeBlockContentV2, generateLinkContent, generateLinkContentV2, generateRichMessageContent, generateUnifiedResponseContent, captureUnifiedResponse, generateLatexContent, generateLatexImageContent, generateLatexInlineImageContent } from '../Utils/rich-message-utils.js';
17
- export const makeMessagesSocket = (config) => {
18
- const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
19
- const sock = makeNewsletterSocket(config);
20
- const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
21
- const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
22
- /**
23
- * Set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
24
- * Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
25
- * Entries are always removed in `.finally()`, so the set is bounded by concurrency.
26
- */
27
- const inFlightTcTokenIssuance = new Set();
28
- const userDevicesCache = config.userDevicesCache ||
29
- new NodeCache({
30
- stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
31
- useClones: false
32
- });
33
- /** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
34
- const devicesMutex = makeMutex();
35
- // Initialize message retry manager if enabled
36
- const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
37
- // Prevent race conditions in Signal session encryption by user
38
- const encryptionMutex = makeKeyedMutex();
39
- let mediaConn;
40
- /** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
41
- let mediaHost = DEF_MEDIA_HOST;
1
+ "use strict"
2
+
3
+ var __importDefault = (this && this.__importDefault) || function(mod) {
4
+ return (mod && mod.__esModule) ? mod : {
5
+ "default": mod
6
+ }
7
+ }
8
+
9
+ Object.defineProperty(exports, "__esModule", {
10
+ value: true
11
+ })
12
+
13
+ const node_cache_1 = __importDefault(require("@cacheable/node-cache"))
14
+ const boom_1 = require("@hapi/boom")
15
+ const crypto_1 = require("crypto")
16
+ const WAProto_1 = require("../../WAProto")
17
+ const Defaults_1 = require("../Defaults")
18
+ const Utils_1 = require("../Utils")
19
+ const Types_1 = require("../Types")
20
+ const WABinary_1 = require("../WABinary")
21
+ const WAUSync_1 = require("../WAUSync")
22
+ const newsletter_1 = require("./newsletter")
23
+ const link_preview_1 = require("../Utils/link-preview")
24
+ const make_keyed_mutex_1 = require("../Utils/make-mutex")
25
+
26
+ const makeMessagesSocket = (config) => {
27
+ const {
28
+ logger,
29
+ maxMsgRetryCount,
30
+ linkPreviewImageThumbnailWidth,
31
+ generateHighQualityLinkPreview,
32
+ options: axiosOptions,
33
+ patchMessageBeforeSending,
34
+ cachedGroupMetadata,
35
+ enableRecentMessageCache
36
+ } = config
37
+ const sock = newsletter_1.makeNewsletterSocket(config)
38
+ const {
39
+ ev,
40
+ authState,
41
+ processingMutex,
42
+ signalRepository,
43
+ upsertMessage,
44
+ createCallLink,
45
+ query,
46
+ fetchPrivacySettings,
47
+ sendNode,
48
+ groupQuery,
49
+ groupMetadata,
50
+ groupToggleEphemeral,
51
+ newsletterWMexQuery,
52
+ executeUSyncQuery
53
+ } = sock
54
+
55
+ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
56
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES,
57
+ useClones: false
58
+ })
59
+
60
+ const messageRetryManager = enableRecentMessageCache ? new Utils_1.MessageRetryManager(logger, maxMsgRetryCount) : null
61
+ const encryptionMutex = make_keyed_mutex_1.makeKeyedMutex()
62
+ const devicesMutex = make_keyed_mutex_1.makeMutex()
63
+ let mediaConn
64
+
42
65
  const refreshMediaConn = async (forceGet = false) => {
43
- const media = await mediaConn;
44
- if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
66
+ const media = await mediaConn
67
+ if (!media || forceGet || (new Date().getTime() - media.fetchDate.getTime()) > media.ttl * 1000) {
45
68
  mediaConn = (async () => {
46
69
  const result = await query({
47
70
  tag: 'iq',
48
71
  attrs: {
49
72
  type: 'set',
50
73
  xmlns: 'w:m',
51
- to: S_WHATSAPP_NET
74
+ to: WABinary_1.S_WHATSAPP_NET,
52
75
  },
53
- content: [{ tag: 'media_conn', attrs: {} }]
54
- });
55
- const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
56
- // TODO: explore full length of data that whatsapp provides
76
+ content: [{
77
+ tag: 'media_conn',
78
+ attrs: {}
79
+ }]
80
+ })
81
+ const mediaConnNode = WABinary_1.getBinaryNodeChild(result, 'media_conn')
57
82
  const node = {
58
- hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
83
+ hosts: WABinary_1.getBinaryNodeChildren(mediaConnNode, 'host').map(({
84
+ attrs
85
+ }) => ({
59
86
  hostname: attrs.hostname,
60
- maxContentLengthBytes: +attrs.maxContentLengthBytes
87
+ maxContentLengthBytes: +attrs.maxContentLengthBytes,
61
88
  })),
62
89
  auth: mediaConnNode.attrs.auth,
63
90
  ttl: +mediaConnNode.attrs.ttl,
64
91
  fetchDate: new Date()
65
- };
66
- logger.debug('fetched media conn');
67
- if (node.hosts[0]) {
68
- mediaHost = node.hosts[0].hostname;
69
92
  }
70
- return node;
71
- })();
93
+ logger.debug('fetched media conn')
94
+ return node
95
+ })()
72
96
  }
73
- return mediaConn;
74
- };
75
- /**
76
- * generic send receipt function
77
- * used for receipts of phone call, read, delivery etc.
78
- * */
97
+ return mediaConn
98
+ }
99
+
79
100
  const sendReceipt = async (jid, participant, messageIds, type) => {
80
- if (!messageIds || messageIds.length === 0) {
81
- throw new Boom('missing ids in receipt');
82
- }
83
101
  const node = {
84
102
  tag: 'receipt',
85
103
  attrs: {
86
- id: messageIds[0]
87
- }
88
- };
89
- const isReadReceipt = type === 'read' || type === 'read-self';
90
- if (isReadReceipt) {
91
- node.attrs.t = unixTimestampSeconds().toString();
104
+ id: messageIds[0],
105
+ },
92
106
  }
93
- if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
94
- node.attrs.recipient = jid;
95
- node.attrs.to = participant;
107
+ const isReadReceipt = type === 'read' || type === 'read-self'
108
+ if (isReadReceipt) {
109
+ node.attrs.t = Utils_1.unixTimestampSeconds().toString()
96
110
  }
97
- else {
98
- node.attrs.to = jid;
111
+ if (type === 'sender' && WABinary_1.isJidUser(jid)) {
112
+ node.attrs.recipient = jid
113
+ node.attrs.to = participant
114
+ } else {
115
+ node.attrs.to = jid
99
116
  if (participant) {
100
- node.attrs.participant = participant;
117
+ node.attrs.participant = participant
101
118
  }
102
119
  }
103
120
  if (type) {
104
- node.attrs.type = type;
121
+ node.attrs.type = WABinary_1.isJidNewsletter(jid) ? 'read-self' : type
105
122
  }
106
- const remainingMessageIds = messageIds.slice(1);
123
+ const remainingMessageIds = messageIds.slice(1)
107
124
  if (remainingMessageIds.length) {
108
- node.content = [
109
- {
110
- tag: 'list',
111
- attrs: {},
112
- content: remainingMessageIds.map(id => ({
113
- tag: 'item',
114
- attrs: { id }
115
- }))
116
- }
117
- ];
125
+ node.content = [{
126
+ tag: 'list',
127
+ attrs: {},
128
+ content: remainingMessageIds.map(id => ({
129
+ tag: 'item',
130
+ attrs: {
131
+ id
132
+ }
133
+ }))
134
+ }]
118
135
  }
119
- logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages');
120
- await sendNode(node);
121
- };
122
- /** Correctly bulk send receipts to multiple chats, participants */
136
+ logger.debug({
137
+ attrs: node.attrs,
138
+ messageIds
139
+ }, 'sending receipt for messages')
140
+ await sendNode(node)
141
+ }
142
+
123
143
  const sendReceipts = async (keys, type) => {
124
- const recps = aggregateMessageKeysNotFromMe(keys);
144
+ const recps = Utils_1.aggregateMessageKeysNotFromMe(keys)
125
145
  for (const { jid, participant, messageIds } of recps) {
126
- await sendReceipt(jid, participant, messageIds, type);
146
+ await sendReceipt(jid, participant, messageIds, type)
127
147
  }
128
- };
129
- /** Bulk read messages. Keys can be from different chats & participants */
148
+ }
149
+
130
150
  const readMessages = async (keys) => {
131
- const privacySettings = await fetchPrivacySettings();
132
- // based on privacy settings, we have to change the read type
133
- const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
134
- await sendReceipts(keys, readType);
135
- };
136
- /** Fetch all the devices we've to send a message to */
151
+ const privacySettings = await fetchPrivacySettings()
152
+ const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self'
153
+ await sendReceipts(keys, readType)
154
+ }
155
+
156
+ const deduplicateLidPnJids = (jids) => {
157
+ const lidUsers = new Set()
158
+ const filteredJids = []
159
+
160
+ for (const jid of jids) {
161
+ if (WABinary_1.isLidUser(jid)) {
162
+ const user = WABinary_1.jidDecode(jid)?.user
163
+ if (user) lidUsers.add(user)
164
+ }
165
+ }
166
+
167
+ for (const jid of jids) {
168
+ if (WABinary_1.isJidUser(jid)) {
169
+ const user = WABinary_1.jidDecode(jid)?.user
170
+ if (user && lidUsers.has(user)) {
171
+ logger.debug({
172
+ jid
173
+ }, 'Skipping PN - LID version exists')
174
+ continue
175
+ }
176
+ }
177
+ filteredJids.push(jid)
178
+ }
179
+ return filteredJids
180
+ }
181
+
182
+ const profilePictureUrl = async (jid) => {
183
+ if (WABinary_1.isJidNewsletter(jid)) {
184
+ let node = await newsletterWMexQuery(undefined, Types_1.QueryIds.METADATA, {
185
+ input: {
186
+ key: jid,
187
+ type: 'JID',
188
+ view_role: 'GUEST'
189
+ },
190
+ fetch_viewer_metadata: true,
191
+ fetch_full_image: true,
192
+ fetch_creation_time: true
193
+ })
194
+ let result = WABinary_1.getBinaryNodeChild(node, 'result')?.content?.toString()
195
+ let metadata = JSON.parse(result).data[Types_1.XWAPaths.NEWSLETTER]
196
+ return Utils_1.getUrlFromDirectPath(metadata.thread_metadata.picture?.direct_path || '')
197
+ } else {
198
+ const result = await query({
199
+ tag: 'iq',
200
+ attrs: {
201
+ target: WABinary_1.jidNormalizedUser(jid),
202
+ to: WABinary_1.S_WHATSAPP_NET,
203
+ type: 'get',
204
+ xmlns: 'w:profile:picture'
205
+ },
206
+ content: [{
207
+ tag: 'picture',
208
+ attrs: {
209
+ type: 'image',
210
+ query: 'url'
211
+ }
212
+ }]
213
+ })
214
+ const child = WABinary_1.getBinaryNodeChild(result, 'picture')
215
+ return child?.attrs?.url || null
216
+ }
217
+ }
218
+
137
219
  const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
138
- const deviceResults = [];
220
+ const deviceResults = []
221
+
139
222
  if (!useCache) {
140
- logger.debug('not using cache for devices');
223
+ logger.debug('not using cache for devices')
141
224
  }
142
- const toFetch = [];
225
+
226
+ const toFetch = []
227
+
228
+ jids = deduplicateLidPnJids(Array.from(new Set(jids)))
143
229
  const jidsWithUser = jids
144
230
  .map(jid => {
145
- const decoded = jidDecode(jid);
146
- const user = decoded?.user;
147
- const device = decoded?.device;
148
- const isExplicitDevice = typeof device === 'number' && device >= 0;
149
- if (isExplicitDevice && user) {
150
- deviceResults.push({
151
- user,
152
- device,
153
- jid
154
- });
155
- return null;
156
- }
157
- jid = jidNormalizedUser(jid);
158
- return { jid, user };
159
- })
160
- .filter(jid => jid !== null);
161
- let mgetDevices;
231
+ const decoded = WABinary_1.jidDecode(jid)
232
+ const user = decoded?.user
233
+ const device = decoded?.device
234
+ const isExplicitDevice = typeof device === 'number' && device >= 0
235
+
236
+ if (isExplicitDevice && user) {
237
+ deviceResults.push({
238
+ user,
239
+ device,
240
+ wireJid: jid
241
+ });
242
+ return null
243
+ }
244
+
245
+ jid = WABinary_1.jidNormalizedUser(jid)
246
+ return {
247
+ jid,
248
+ user
249
+ }
250
+ })
251
+ .filter(jid => jid !== null)
252
+
253
+ let mgetDevices
162
254
  if (useCache && userDevicesCache.mget) {
163
- const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
164
- mgetDevices = await userDevicesCache.mget(usersToFetch);
255
+ const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean)
256
+ mgetDevices = await userDevicesCache.mget(usersToFetch)
165
257
  }
258
+
166
259
  for (const { jid, user } of jidsWithUser) {
167
260
  if (useCache) {
168
- const devices = mgetDevices?.[user] ||
169
- (userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)));
261
+ const devices = mgetDevices?.[user] || (userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)))
170
262
  if (devices) {
171
- const devicesWithJid = devices.map(d => ({
263
+ const isLidJid = WABinary_1.isLidUser(jid)
264
+ const devicesWithWire = devices.map(d => ({
172
265
  ...d,
173
- jid: jidEncode(d.user, d.server, d.device)
174
- }));
175
- deviceResults.push(...devicesWithJid);
176
- logger.trace({ user }, 'using cache for devices');
177
- }
178
- else {
179
- toFetch.push(jid);
266
+ wireJid: isLidJid ? WABinary_1.jidEncode(d.user, 'lid', d.device) : WABinary_1.jidEncode(d.user, 's.whatsapp.net', d.device)
267
+ }))
268
+ deviceResults.push(...devicesWithWire)
269
+ logger.trace({
270
+ user
271
+ }, 'using cache for devices')
272
+ } else {
273
+ toFetch.push(jid)
180
274
  }
181
- }
182
- else {
183
- toFetch.push(jid);
275
+ } else {
276
+ toFetch.push(jid)
184
277
  }
185
278
  }
279
+
186
280
  if (!toFetch.length) {
187
- return deviceResults;
281
+ return deviceResults
188
282
  }
189
- const requestedLidUsers = new Set();
283
+
284
+ const requestedLidUsers = new Set()
190
285
  for (const jid of toFetch) {
191
- if (isLidUser(jid) || isHostedLidUser(jid)) {
192
- const user = jidDecode(jid)?.user;
193
- if (user)
194
- requestedLidUsers.add(user);
286
+ if (WABinary_1.isLidUser(jid)) {
287
+ const user = WABinary_1.jidDecode(jid)?.user
288
+ if (user) requestedLidUsers.add(user)
195
289
  }
196
290
  }
197
- const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
291
+
292
+ const query = new WAUSync_1.USyncQuery().withContext('message').withDeviceProtocol()
198
293
  for (const jid of toFetch) {
199
- query.withUser(new USyncUser().withId(jid)); // todo: investigate - the idea here is that <user> should have an inline lid field with the lid being the pn equivalent
294
+ query.withUser(new WAUSync_1.USyncUser().withId(jid))
200
295
  }
201
- const result = await sock.executeUSyncQuery(query);
296
+
297
+ const result = await executeUSyncQuery(query)
202
298
  if (result) {
203
- // TODO: LID MAP this stuff (lid protocol will now return lid with devices)
204
- const lidResults = result.list.filter(a => !!a.lid);
299
+ // FIX: pontalabs sebelumnya gak pernah nyimpen mapping LID<->PN
300
+ // dari hasil USync device query ini (nuiisweety nyimpen + langsung
301
+ // force-refresh session buat LID yang baru ketemu). Tanpa ini,
302
+ // pengiriman ke kontak yang butuh LID addressing bisa tetep pakai
303
+ // address PN yang salah/basi -> penerima gak bisa decrypt balasan
304
+ // bot ("Menunggu pesan ini...").
305
+ const lidResults = (result.list || []).filter(item => !!item.lid)
205
306
  if (lidResults.length > 0) {
206
- logger.trace('Storing LID maps from device call');
207
- await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
208
- // Force-refresh sessions for newly mapped LIDs to align identity addressing
209
307
  try {
210
- const lids = lidResults.map(a => a.lid);
211
- if (lids.length) {
212
- await assertSessions(lids, true);
213
- }
214
- }
215
- catch (e) {
216
- logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
308
+ await signalRepository.lidMapping.storeLIDPNMappings(
309
+ lidResults.map(item => ({ lid: item.lid, pn: item.id }))
310
+ )
311
+ const lids = lidResults.map(item => item.lid)
312
+ await assertSessions(lids, true)
313
+ } catch (error) {
314
+ logger.warn({ error, count: lidResults.length }, 'failed to store/refresh LID mappings from device sync')
217
315
  }
218
316
  }
219
- const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
220
- const deviceMap = {};
317
+ const extracted = Utils_1.extractDeviceJids(result?.list, authState.creds.me.id, ignoreZeroDevices)
318
+ const deviceMap = {}
221
319
  for (const item of extracted) {
222
- deviceMap[item.user] = deviceMap[item.user] || [];
223
- deviceMap[item.user]?.push(item);
320
+ deviceMap[item.user] = deviceMap[item.user] || []
321
+ deviceMap[item.user]?.push(item)
224
322
  }
225
- // Process each user's devices as a group for bulk LID migration
226
323
  for (const [user, userDevices] of Object.entries(deviceMap)) {
227
- const isLidUser = requestedLidUsers.has(user);
228
- // Process all devices for this user
324
+ const isLidUser = requestedLidUsers.has(user)
229
325
  for (const item of userDevices) {
230
- const finalJid = isLidUser
231
- ? jidEncode(user, item.server, item.device)
232
- : jidEncode(item.user, item.server, item.device);
326
+ const finalWireJid = isLidUser ? WABinary_1.jidEncode(user, 'lid', item.device) : WABinary_1.jidEncode(item.user, 's.whatsapp.net', item.device)
233
327
  deviceResults.push({
234
328
  ...item,
235
- jid: finalJid
329
+ wireJid: finalWireJid
236
330
  });
237
331
  logger.debug({
238
332
  user: item.user,
239
333
  device: item.device,
240
- finalJid,
334
+ finalWireJid,
241
335
  usedLid: isLidUser
242
- }, 'Processed device with LID priority');
336
+ }, 'Processed device with LID priority')
243
337
  }
244
338
  }
339
+
245
340
  await devicesMutex.mutex(async () => {
246
341
  if (userDevicesCache.mset) {
247
- // if the cache supports mset, we can set all devices in one go
248
- await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
249
- }
250
- else {
342
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({
343
+ key,
344
+ value
345
+ })))
346
+ } else {
251
347
  for (const key in deviceMap) {
252
- if (deviceMap[key])
253
- await userDevicesCache.set(key, deviceMap[key]);
348
+ if (deviceMap[key]) await userDevicesCache.set(key, deviceMap[key])
254
349
  }
255
350
  }
256
- });
257
- const userDeviceUpdates = {};
258
- for (const [userId, devices] of Object.entries(deviceMap)) {
259
- if (devices && devices.length > 0) {
260
- userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
261
- }
262
- }
263
- if (Object.keys(userDeviceUpdates).length > 0) {
264
- try {
265
- await authState.keys.set({ 'device-list': userDeviceUpdates });
266
- logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
267
- }
268
- catch (error) {
269
- logger.warn({ error }, 'failed to store user device lists');
270
- }
271
- }
351
+ })
272
352
  }
273
- return deviceResults;
274
- };
275
- /**
276
- * Update Member Label
277
- */
278
- const updateMemberLabel = (jid, memberLabel) => {
279
- return relayMessage(jid, {
280
- protocolMessage: {
281
- type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
282
- memberLabel: {
283
- label: memberLabel?.slice(0, 30),
284
- labelTimestamp: unixTimestampSeconds()
285
- }
286
- }
287
- }, {
288
- additionalNodes: [
289
- {
290
- tag: 'meta',
291
- attrs: {
292
- tag_reason: 'user_update',
293
- appdata: 'member_tag'
294
- },
295
- content: undefined
296
- }
297
- ]
298
- });
299
- };
353
+ return deviceResults
354
+ }
355
+
300
356
  const assertSessions = async (jids, force) => {
301
357
  let didFetchNewSession = false;
302
- const uniqueJids = [...new Set(jids)];
303
- const jidsRequiringFetch = [];
304
- logger.debug({ jids }, 'assertSessions call with jids');
305
- for (const jid of uniqueJids) {
306
- if (!force) {
358
+ let jidsRequiringFetch = [];
359
+
360
+ if (force) {
361
+ jidsRequiringFetch = jids;
362
+ } else {
363
+ // FIX: sebelumnya cuma ngecek "ada record tersimpan atau enggak"
364
+ // (truthy check di authState.keys.get('session', ...)). Record
365
+ // yang kosong/stale/rusak (misal device grup yang session-nya
366
+ // kebentuk tapi gak pernah kepakai/keisi ratchet) tetep truthy,
367
+ // jadi dianggap "udah punya sesi" padahal libsignal-nya beneran
368
+ // kosong -> pas encrypt muncul "SessionError: No sessions".
369
+ // validateSession ngecek session-nya beneran ada & "open" enggak,
370
+ // bukan cuma record-nya ada.
371
+ for (const jid of jids) {
307
372
  const sessionValidation = await signalRepository.validateSession(jid);
308
- if (sessionValidation.exists) {
309
- continue;
373
+ if (!sessionValidation.exists) {
374
+ jidsRequiringFetch.push(jid);
310
375
  }
311
376
  }
312
- jidsRequiringFetch.push(jid);
313
377
  }
378
+
314
379
  if (jidsRequiringFetch.length) {
315
- // LID if mapped, otherwise original
316
- const wireJids = [
317
- ...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
318
- ...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
319
- ];
320
- logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
380
+ logger.debug({ jidsRequiringFetch }, 'fetching sessions');
381
+
321
382
  const result = await query({
322
383
  tag: 'iq',
323
384
  attrs: {
324
385
  xmlns: 'encrypt',
325
386
  type: 'get',
326
- to: S_WHATSAPP_NET
387
+ to: WABinary_1.S_WHATSAPP_NET,
327
388
  },
328
389
  content: [
329
390
  {
330
391
  tag: 'key',
331
392
  attrs: {},
332
- content: wireJids.map(jid => {
333
- const attrs = { jid };
334
- if (force)
335
- attrs.reason = 'identity';
336
- return { tag: 'user', attrs };
337
- })
393
+ content: jidsRequiringFetch.map(jid => ({
394
+ tag: 'user',
395
+ attrs: { jid },
396
+ }))
338
397
  }
339
398
  ]
340
399
  });
341
- await parseAndInjectE2ESessions(result, signalRepository);
400
+ await (0, Utils_1.parseAndInjectE2ESessions)(result, signalRepository);
342
401
  didFetchNewSession = true;
343
402
  }
344
403
  return didFetchNewSession;
345
404
  };
405
+
346
406
  const sendPeerDataOperationMessage = async (pdoMessage) => {
347
- //TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
348
407
  if (!authState.creds.me?.id) {
349
- throw new Boom('Not authenticated');
408
+ throw new boom_1.Boom('Not authenticated')
350
409
  }
410
+
351
411
  const protocolMessage = {
352
412
  protocolMessage: {
353
413
  peerDataOperationRequestMessage: pdoMessage,
354
- type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
414
+ type: WAProto_1.proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
355
415
  }
356
- };
357
- const meJid = jidNormalizedUser(authState.creds.me.id);
416
+ }
417
+
418
+ const meJid = WABinary_1.jidNormalizedUser(authState.creds.me.id)
358
419
  const msgId = await relayMessage(meJid, protocolMessage, {
359
420
  additionalAttributes: {
360
421
  category: 'peer',
361
- push_priority: 'high_force'
422
+ push_priority: 'high_force',
362
423
  },
363
- additionalNodes: [
364
- {
365
- tag: 'meta',
366
- attrs: { appdata: 'default' }
367
- }
368
- ]
369
- });
370
- return msgId;
371
- };
372
- const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
373
- if (!recipientJids.length) {
374
- return { nodes: [], shouldIncludeDeviceIdentity: false };
424
+ })
425
+ return msgId
426
+ }
427
+
428
+ const createParticipantNodes = async (jids, message, extraAttrs) => {
429
+ let patched = await patchMessageBeforeSending(message, jids);
430
+
431
+ if (!Array.isArray(patched)) {
432
+ patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched];
375
433
  }
376
- const patched = await patchMessageBeforeSending(message, recipientJids);
377
- const patchedMessages = Array.isArray(patched)
378
- ? patched
379
- : recipientJids.map(jid => ({ recipientJid: jid, message: patched }));
434
+
380
435
  let shouldIncludeDeviceIdentity = false;
381
- const meId = authState.creds.me.id;
382
- const meLid = authState.creds.me?.lid;
383
- const meLidUser = meLid ? jidDecode(meLid)?.user : null;
384
- const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
385
- try {
386
- if (!jid)
387
- return null;
388
- let msgToEncrypt = patchedMessage;
389
- if (dsmMessage) {
390
- const { user: targetUser } = jidDecode(jid);
391
- const { user: ownPnUser } = jidDecode(meId);
392
- const ownLidUser = meLidUser;
393
- const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
394
- const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
395
- if (isOwnUser && !isExactSenderDevice) {
396
- msgToEncrypt = dsmMessage;
397
- logger.debug({ jid, targetUser }, 'Using DSM for own device');
398
- }
399
- }
400
- const bytes = encodeWAMessage(msgToEncrypt);
401
- const mutexKey = jid;
402
- const node = await encryptionMutex.mutex(mutexKey, async () => {
403
- const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
404
- if (type === 'pkmsg') {
405
- shouldIncludeDeviceIdentity = true;
406
- }
407
- return {
408
- tag: 'to',
409
- attrs: { jid },
410
- content: [
411
- {
412
- tag: 'enc',
413
- attrs: { v: '2', type, ...(extraAttrs || {}) },
414
- content: ciphertext
415
- }
416
- ]
417
- };
418
- });
419
- return node;
436
+ const nodes = await Promise.all(patched.map(async (patchedMessageWithJid) => {
437
+ const { recipientJid: jid, ...patchedMessage } = patchedMessageWithJid;
438
+ if (!jid) {
439
+ return {};
420
440
  }
421
- catch (err) {
422
- logger.error({ jid, err }, 'Failed to encrypt for recipient');
423
- return null;
424
- }
425
- });
426
- const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
427
- if (recipientJids.length > 0 && nodes.length === 0) {
428
- throw new Boom('All encryptions failed', { statusCode: 500 });
429
- }
441
+
442
+ const bytes = (0, Utils_1.encodeWAMessage)(patchedMessage);
443
+ // FIX: encryptMessage sebelumnya dipanggil tanpa mutex, padahal
444
+ // encryptionMutex sudah ada tapi gak dipakai. Kalau ada 2 pesan
445
+ // dikirim hampir bersamaan ke jid yang sama (misal "processing..."
446
+ // lalu hasil), encryptMessage bisa race saat baca-ubah-tulis session
447
+ // Signal, merusak ratchet -> salah satu pesan jadi gak bisa
448
+ // didekripsi penerima ("Menunggu pesan ini..." nyangkut selamanya).
449
+ const node = await encryptionMutex.mutex(jid, async () => {
450
+ const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
451
+ if (type === 'pkmsg') {
452
+ shouldIncludeDeviceIdentity = true;
453
+ }
454
+ return {
455
+ tag: 'to',
456
+ attrs: { jid },
457
+ content: [{
458
+ tag: 'enc',
459
+ attrs: {
460
+ v: '2',
461
+ type,
462
+ ...extraAttrs || {}
463
+ },
464
+ content: ciphertext
465
+ }]
466
+ };
467
+ });
468
+ return node;
469
+ }));
430
470
  return { nodes, shouldIncludeDeviceIdentity };
431
471
  };
432
- const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
433
- const meId = assertMeId(authState.creds);
434
- const meLid = authState.creds.me?.lid;
435
- const isRetryResend = Boolean(participant?.jid);
436
- let shouldIncludeDeviceIdentity = isRetryResend;
437
- const statusJid = 'status@broadcast';
438
- const { user, server } = jidDecode(jid);
439
- const isGroup = server === 'g.us';
440
- const isStatus = jid === statusJid;
441
- const isLid = server === 'lid';
442
- const isNewsletter = server === 'newsletter';
443
- const isGroupOrStatus = isGroup || isStatus;
444
- const finalJid = jid;
445
- msgId = msgId || generateMessageIDV2(meId);
446
- useUserDevicesCache = useUserDevicesCache !== false;
447
- useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
448
- const participants = [];
449
- const destinationJid = !isStatus ? finalJid : statusJid;
450
- const binaryNodeContent = [];
451
- const devices = [];
452
- let reportingMessage;
472
+
473
+ const relayMessage = async (jid, message, {
474
+ messageId: msgId,
475
+ participant,
476
+ additionalAttributes,
477
+ useUserDevicesCache,
478
+ useCachedGroupMetadata,
479
+ statusJidList,
480
+ additionalNodes,
481
+ ai = false
482
+ }) => {
483
+ const meId = authState.creds.me.id
484
+ const meLid = authState.creds.me?.lid
485
+
486
+ let additionalAlready = false
487
+ let shouldIncludeDeviceIdentity = false
488
+
489
+ const {
490
+ user,
491
+ server
492
+ } = WABinary_1.jidDecode(jid)
493
+
494
+ const statusJid = 'status@broadcast'
495
+ const isGroup = server === 'g.us'
496
+ const isPrivate = server === 's.whatsapp.net'
497
+ const isNewsletter = server == 'newsletter'
498
+ const isStatus = jid === statusJid
499
+ const isLid = server === 'lid'
500
+
501
+ const finalJid = jid
502
+
503
+ let ownId = meId
504
+
505
+ if (isLid && meLid) {
506
+ ownId = meLid
507
+ logger.debug({
508
+ to: jid,
509
+ ownId
510
+ }, 'Using LID identity for @lid conversation')
511
+ } else {
512
+ logger.debug({
513
+ to: jid,
514
+ ownId
515
+ }, 'Using PN identity for @s.whatsapp.net conversation')
516
+ }
517
+
518
+ msgId = msgId || Utils_1.generateMessageID(authState.creds.me.id)
519
+ useUserDevicesCache = useUserDevicesCache !== false
520
+ useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus
521
+
522
+ const participants = []
523
+ const destinationJid = !isStatus ? finalJid : statusJid
524
+ const binaryNodeContent = []
525
+ const devices = []
526
+
453
527
  const meMsg = {
454
528
  deviceSentMessage: {
455
529
  destinationJid,
456
530
  message
457
531
  },
458
- messageContextInfo: message.messageContextInfo
459
- };
460
- const extraAttrs = {};
532
+ messageContextInfo: message.messageContextInfo || {}
533
+ }
534
+
535
+ const extraAttrs = {}
536
+ const regexGroupOld = /^(\d{1,15})-(\d+)@g\.us$/
537
+ const messages = Utils_1.normalizeMessageContent(message)
538
+ const buttonType = getButtonType(messages)
539
+ const pollMessage = messages.pollCreationMessage || messages.pollCreationMessageV2 || messages.pollCreationMessageV3
540
+
461
541
  if (participant) {
462
542
  if (!isGroup && !isStatus) {
463
- additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
543
+ additionalAttributes = {
544
+ ...additionalAttributes,
545
+ 'device_fanout': 'false'
546
+ }
464
547
  }
465
- const { user, device } = jidDecode(participant.jid);
548
+
549
+ const {
550
+ user,
551
+ device
552
+ } = WABinary_1.jidDecode(participant.jid)
553
+
466
554
  devices.push({
467
555
  user,
468
556
  device,
469
- jid: participant.jid
470
- });
557
+ wireJid: participant.jid
558
+ })
471
559
  }
560
+
472
561
  await authState.keys.transaction(async () => {
473
- const normalizedMsg = normalizeMessageContent(message);
474
- const isGroupStatusAudio = !!(message?.groupStatusMessageV2?.message?.audioMessage);
475
- const mediaType = isGroupStatusAudio ? undefined : getMediaType(normalizedMsg || message);
562
+ const mediaType = getMediaType(messages)
476
563
  if (mediaType) {
477
- extraAttrs['mediatype'] = mediaType;
564
+ extraAttrs['mediatype'] = mediaType
478
565
  }
479
- if (isNewsletter) {
480
- const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
481
- // Evaluate mediaType from patched message (after patchMessageBeforeSending),
482
- // and place it in plaintext attrs — NOT in stanza attrs (WA server requires it there)
483
- const newsletterMediaType = getMediaType(normalizeMessageContent(patched) || patched);
484
- if (newsletterMediaType) {
485
- extraAttrs['mediatype'] = newsletterMediaType;
486
- }
487
- if (patched.interactiveMessage && !extraAttrs['mediatype']) {
488
- extraAttrs['mediatype'] = 'interactive';
489
- }
490
- const bytes = encodeNewsletterMessage(patched);
491
- binaryNodeContent.push({
492
- tag: 'plaintext',
493
- attrs: extraAttrs,
494
- content: bytes
495
- });
496
- const stanza = {
497
- tag: 'message',
498
- attrs: {
499
- to: jid,
500
- id: msgId,
501
- type: getMessageType(patched),
502
- ...(additionalAttributes || {})
503
- },
504
- content: binaryNodeContent
505
- };
506
- logger.debug({ msgId, mediaType: newsletterMediaType, extraAttrs }, `sending newsletter message to ${jid}`);
507
- await sendNode(stanza);
508
- return;
566
+
567
+ if (messages.pinInChatMessage || messages.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage) {
568
+ extraAttrs['decrypt-fail'] = 'hide'
509
569
  }
510
- if (normalizedMsg?.pinInChatMessage || normalizedMsg?.reactionMessage) {
511
- extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
570
+
571
+ if (messages.interactiveResponseMessage?.nativeFlowResponseMessage) {
572
+ extraAttrs['native_flow_name'] = messages.interactiveResponseMessage.nativeFlowResponseMessage?.name || 'menu_options'
512
573
  }
513
- if (isGroupOrStatus && !isRetryResend) {
574
+
575
+ if (isGroup || isStatus) {
514
576
  const [groupData, senderKeyMap] = await Promise.all([
515
577
  (async () => {
516
- let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined; // todo: should we rely on the cache specially if the cache is outdated and the metadata has new fields?
578
+ let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
517
579
  if (groupData && Array.isArray(groupData?.participants)) {
518
- logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata');
580
+ logger.trace({
581
+ jid,
582
+ participants: groupData.participants.length
583
+ }, 'using cached group metadata')
584
+ } else if (!isStatus) {
585
+ groupData = await global.groupMetadataCache(jid)
519
586
  }
520
- else if (!isStatus) {
521
- groupData = await groupMetadata(jid); // TODO: start storing group participant list + addr mode in Signal & stop relying on this
522
- }
523
- return groupData;
587
+ return groupData
524
588
  })(),
525
589
  (async () => {
526
590
  if (!participant && !isStatus) {
527
- // what if sender memory is less accurate than the cached metadata
528
- // on participant change in group, we should do sender memory manipulation
529
- const result = await authState.keys.get('sender-key-memory', [jid]); // TODO: check out what if the sender key memory doesn't include the LID stuff now?
530
- return result[jid] || {};
591
+ const result = await authState.keys.get('sender-key-memory', [jid])
592
+ return result[jid] || {}
531
593
  }
532
- return {};
594
+ return {}
533
595
  })()
534
- ]);
535
- const participantsList = groupData ? groupData.participants.map(p => p.id) : [];
536
- if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
537
- additionalAttributes = {
538
- ...additionalAttributes,
539
- expiration: groupData.ephemeralDuration.toString()
540
- };
541
- }
542
- if (isStatus && statusJidList) {
543
- participantsList.push(...statusJidList);
544
- }
545
- const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
546
- devices.push(...additionalDevices);
547
- if (isGroup) {
548
- additionalAttributes = {
549
- ...additionalAttributes,
550
- addressing_mode: groupData?.addressingMode || 'lid'
551
- };
552
- }
553
- const patched = await patchMessageBeforeSending(message);
554
- if (Array.isArray(patched)) {
555
- throw new Boom('Per-jid patching is not supported in groups');
596
+ ])
597
+
598
+ if (!participant) {
599
+ const participantsList = (groupData && !isStatus) ? groupData.participants.map(p => p.id) : []
600
+ if (isStatus && statusJidList) {
601
+ participantsList.push(...statusJidList)
602
+ }
603
+ if (!isStatus) {
604
+ const groupAddressingMode = groupData?.addressingMode || (isLid ? Types_1.WAMessageAddressingMode.LID : Types_1.WAMessageAddressingMode.PN)
605
+ additionalAttributes = {
606
+ ...additionalAttributes,
607
+ addressing_mode: groupAddressingMode
608
+ }
609
+ }
610
+ const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
611
+ devices.push(...additionalDevices)
556
612
  }
557
- const bytes = encodeWAMessage(patched);
558
- reportingMessage = patched;
559
- const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid';
560
- const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
561
- const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
613
+
614
+ const patched = await patchMessageBeforeSending(message, devices.map(d => WABinary_1.jidEncode(d.user, isLid ? 'lid' : 's.whatsapp.net', d.device)))
615
+ const bytes = Utils_1.encodeWAMessage(patched)
616
+
617
+ const groupAddressingMode = groupData?.addressingMode || (isLid ? 'lid' : 'pn')
618
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId
619
+
620
+ const {
621
+ ciphertext,
622
+ senderKeyDistributionMessage
623
+ } = await signalRepository.encryptGroupMessage({
562
624
  group: destinationJid,
563
625
  data: bytes,
564
626
  meId: groupSenderIdentity
565
- });
566
- const senderKeyRecipients = [];
627
+ })
628
+
629
+ const senderKeyJids = []
630
+
567
631
  for (const device of devices) {
568
- const deviceJid = device.jid;
569
- const hasKey = !!senderKeyMap[deviceJid];
570
- if ((!hasKey || !!participant) &&
571
- !isHostedLidUser(deviceJid) &&
572
- !isHostedPnUser(deviceJid) &&
573
- device.device !== 99) {
574
- //todo: revamp all this logic
575
- // the goal is to follow with what I said above for each group, and instead of a true false map of ids, we can set an array full of those the app has already sent pkmsgs
576
- senderKeyRecipients.push(deviceJid);
577
- senderKeyMap[deviceJid] = true;
632
+ const deviceJid = device.wireJid
633
+ const hasKey = !!senderKeyMap[deviceJid]
634
+ if (!hasKey || !!participant) {
635
+ senderKeyJids.push(deviceJid)
636
+ senderKeyMap[deviceJid] = true
578
637
  }
579
638
  }
580
- if (senderKeyRecipients.length) {
581
- logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key');
639
+
640
+ if (senderKeyJids.length) {
641
+ logger.debug({
642
+ senderKeyJids
643
+ }, 'sending new sender key')
582
644
  const senderKeyMsg = {
583
645
  senderKeyDistributionMessage: {
584
646
  axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
585
647
  groupId: destinationJid
586
648
  }
587
- };
588
- const senderKeySessionTargets = senderKeyRecipients;
589
- await assertSessions(senderKeySessionTargets);
590
- const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs);
591
- shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
592
- participants.push(...result.nodes);
649
+ }
650
+
651
+ await assertSessions(senderKeyJids, false)
652
+ const result = await createParticipantNodes(senderKeyJids, senderKeyMsg, extraAttrs)
653
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
654
+ participants.push(...result.nodes)
593
655
  }
656
+
594
657
  binaryNodeContent.push({
595
658
  tag: 'enc',
596
- attrs: { v: '2', type: 'skmsg', ...extraAttrs },
659
+ attrs: {
660
+ v: '2',
661
+ type: 'skmsg',
662
+ ...extraAttrs
663
+ },
597
664
  content: ciphertext
598
- });
599
- await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
600
- }
601
- else {
602
- // ADDRESSING CONSISTENCY: Match own identity to conversation context
603
- // TODO: investigate if this is true
604
- let ownId = meId;
605
- if (isLid && meLid) {
606
- ownId = meLid;
607
- logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation');
665
+ })
666
+
667
+ await authState.keys.set({
668
+ 'sender-key-memory': {
669
+ [jid]: senderKeyMap
670
+ }
671
+ })
672
+ } else if (isNewsletter) {
673
+ if (message.protocolMessage?.editedMessage) {
674
+ msgId = message.protocolMessage.key?.id
675
+ message = message.protocolMessage.editedMessage
608
676
  }
609
- else {
610
- logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
677
+
678
+ if (message.protocolMessage?.type === WAProto_1.proto.Message.ProtocolMessage.Type.REVOKE) {
679
+ msgId = message.protocolMessage.key?.id
680
+ message = {}
611
681
  }
612
- const { user: ownUser } = jidDecode(ownId);
682
+
683
+ const patched = await patchMessageBeforeSending(message, [])
684
+ const bytes = Utils_1.encodeNewsletterMessage(patched)
685
+
686
+ binaryNodeContent.push({
687
+ tag: 'plaintext',
688
+ attrs: extraAttrs,
689
+ content: bytes
690
+ })
691
+ } else {
692
+ const {
693
+ user: ownUser
694
+ } = WABinary_1.jidDecode(ownId)
695
+
613
696
  if (!participant) {
614
- const patchedForReporting = await patchMessageBeforeSending(message, [jid]);
615
- reportingMessage = Array.isArray(patchedForReporting)
616
- ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
617
- : patchedForReporting;
618
- }
619
- if (!isRetryResend) {
620
- const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
697
+ const targetUserServer = isLid ? 'lid' : 's.whatsapp.net'
621
698
  devices.push({
622
699
  user,
623
700
  device: 0,
624
- jid: jidEncode(user, targetUserServer, 0) // rajeh, todo: this entire logic is convoluted and weird.
625
- });
701
+ wireJid: WABinary_1.jidEncode(user, targetUserServer, 0)
702
+ })
703
+
626
704
  if (user !== ownUser) {
627
705
  const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
628
- const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
706
+ const ownUserForAddressing = isLid && meLid ? WABinary_1.jidDecode(meLid).user : WABinary_1.jidDecode(meId).user
629
707
  devices.push({
630
708
  user: ownUserForAddressing,
631
709
  device: 0,
632
- jid: jidEncode(ownUserForAddressing, ownUserServer, 0)
633
- });
710
+ wireJid: WABinary_1.jidEncode(ownUserForAddressing, ownUserServer, 0)
711
+ })
634
712
  }
713
+
635
714
  if (additionalAttributes?.['category'] !== 'peer') {
636
- // Clear placeholders and enumerate actual devices
637
- devices.length = 0;
638
- // Use conversation-appropriate sender identity
639
- const senderIdentity = isLid && meLid
640
- ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
641
- : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
642
- // Enumerate devices for sender and target with consistent addressing
643
- const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
644
- devices.push(...sessionDevices);
715
+ devices.length = 0
716
+ const senderIdentity = isLid && meLid ? WABinary_1.jidEncode(WABinary_1.jidDecode(meLid)?.user, 'lid', undefined) : WABinary_1.jidEncode(WABinary_1.jidDecode(meId)?.user, 's.whatsapp.net', undefined)
717
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], false, false)
718
+ devices.push(...sessionDevices)
645
719
  logger.debug({
646
720
  deviceCount: devices.length,
647
- devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`)
648
- }, 'Device enumeration complete with unified addressing');
721
+ devices: devices.map(d => `${d.user}:${d.device}@${WABinary_1.jidDecode(d.wireJid)?.server}`)
722
+ }, 'Device enumeration complete with unified addressing')
649
723
  }
650
724
  }
651
- const allRecipients = [];
652
- const meRecipients = [];
653
- const otherRecipients = [];
654
- const { user: mePnUser } = jidDecode(meId);
655
- const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
656
- for (const { user, jid } of devices) {
657
- const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
725
+
726
+ const allJids = []
727
+ const meJids = []
728
+ const otherJids = []
729
+
730
+ const {
731
+ user: mePnUser
732
+ } = WABinary_1.jidDecode(meId)
733
+ const {
734
+ user: meLidUser
735
+ } = meLid ? WABinary_1.jidDecode(meLid) : {
736
+ user: null
737
+ }
738
+
739
+ for (const { user, wireJid } of devices) {
740
+ const isExactSenderDevice = wireJid === meId || (meLid && wireJid === meLid)
658
741
  if (isExactSenderDevice) {
659
- logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
660
- continue;
742
+ logger.debug({
743
+ wireJid,
744
+ meId,
745
+ meLid
746
+ }, 'Skipping exact sender device (whatsmeow pattern)')
747
+ continue
661
748
  }
662
- // Check if this is our device (could match either PN or LID user)
663
- const isMe = user === mePnUser || user === meLidUser;
749
+ const isMe = user === mePnUser || (meLidUser && user === meLidUser)
750
+ const jid = wireJid
664
751
  if (isMe) {
665
- meRecipients.push(jid);
752
+ meJids.push(jid)
753
+ } else {
754
+ otherJids.push(jid)
666
755
  }
667
- else {
668
- otherRecipients.push(jid);
669
- }
670
- allRecipients.push(jid);
756
+ allJids.push(jid)
671
757
  }
672
- await assertSessions(allRecipients);
673
- const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
674
- // For own devices: use DSM if available (1:1 chats only)
675
- createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
676
- createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
677
- ]);
678
- participants.push(...meNodes);
679
- participants.push(...otherNodes);
680
- if (meRecipients.length > 0 || otherRecipients.length > 0) {
681
- extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]);
758
+
759
+ await assertSessions([...otherJids, ...meJids], false)
760
+ const [{
761
+ nodes: meNodes,
762
+ shouldIncludeDeviceIdentity: s1
763
+ }, {
764
+ nodes: otherNodes,
765
+ shouldIncludeDeviceIdentity: s2
766
+ }] = await Promise.all([
767
+ createParticipantNodes(meJids, meMsg || message, extraAttrs),
768
+ createParticipantNodes(otherJids, message, extraAttrs, meMsg)
769
+ ])
770
+
771
+ participants.push(...meNodes)
772
+ participants.push(...otherNodes)
773
+ if (meJids.length > 0 || otherJids.length > 0) {
774
+ extraAttrs['phash'] = Utils_1.generateParticipantHashV2([...meJids, ...otherJids])
682
775
  }
683
- shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
684
- }
685
- if (isRetryResend) {
686
- const isParticipantLid = isLidUser(participant.jid);
687
- const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId);
688
- let messageToSend = message;
689
- if (isGroupOrStatus) {
690
- let groupSenderIdentity;
691
- if (meLid && (await signalRepository.hasSenderKey({ group: destinationJid, meId: meLid }))) {
692
- groupSenderIdentity = meLid;
693
- }
694
- else if (await signalRepository.hasSenderKey({ group: destinationJid, meId })) {
695
- groupSenderIdentity = meId;
696
- }
697
- if (groupSenderIdentity) {
698
- try {
699
- const skdm = await signalRepository.getSenderKeyDistributionMessage({
700
- group: destinationJid,
701
- meId: groupSenderIdentity
702
- });
703
- messageToSend = {
704
- ...message,
705
- senderKeyDistributionMessage: {
706
- groupId: destinationJid,
707
- axolotlSenderKeyDistributionMessage: skdm
708
- }
709
- };
710
- }
711
- catch (err) {
712
- logger.warn({ err, jid: destinationJid }, 'failed to build SKDM for retry, sending without it');
713
- }
714
- }
715
- }
716
- const encodedMessageToSend = isMe
717
- ? encodeWAMessage({
718
- deviceSentMessage: {
719
- destinationJid,
720
- message: messageToSend
721
- }
722
- })
723
- : encodeWAMessage(messageToSend);
724
- const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
725
- data: encodedMessageToSend,
726
- jid: participant.jid
727
- });
728
- binaryNodeContent.push({
729
- tag: 'enc',
730
- attrs: {
731
- v: '2',
732
- type,
733
- count: participant.count.toString()
734
- },
735
- content: encryptedContent
736
- });
776
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2
737
777
  }
778
+
738
779
  if (participants.length) {
739
780
  if (additionalAttributes?.['category'] === 'peer') {
740
- const peerNode = participants[0]?.content?.[0];
781
+ const peerNode = participants[0]?.content?.[0]
741
782
  if (peerNode) {
742
- binaryNodeContent.push(peerNode); // push only enc
783
+ binaryNodeContent.push(peerNode)
743
784
  }
744
- }
745
- else {
785
+ } else {
746
786
  binaryNodeContent.push({
747
787
  tag: 'participants',
748
788
  attrs: {},
749
789
  content: participants
750
- });
790
+ })
751
791
  }
752
792
  }
793
+
753
794
  const stanza = {
754
795
  tag: 'message',
755
796
  attrs: {
756
- id: msgId,
757
797
  to: destinationJid,
758
- type: getMessageType(message),
798
+ id: msgId,
799
+ type: getTypeMessage(message),
759
800
  ...(additionalAttributes || {})
760
801
  },
761
802
  content: binaryNodeContent
762
- };
763
- // if the participant to send to is explicitly specified (generally retry recp)
764
- // ensure the message is only sent to that person
765
- // if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg
803
+ }
804
+
766
805
  if (participant) {
767
- if (isJidGroup(destinationJid)) {
768
- stanza.attrs.to = destinationJid;
769
- stanza.attrs.participant = participant.jid;
770
- }
771
- else if (areJidsSameUser(participant.jid, meId)) {
772
- stanza.attrs.to = participant.jid;
773
- stanza.attrs.recipient = destinationJid;
806
+ if (WABinary_1.isJidGroup(destinationJid)) {
807
+ stanza.attrs.to = destinationJid
808
+ stanza.attrs.participant = participant.jid
809
+ } else if (WABinary_1.areJidsSameUser(participant.jid, meId)) {
810
+ stanza.attrs.to = participant.jid
811
+ stanza.attrs.recipient = destinationJid
812
+ } else {
813
+ stanza.attrs.to = participant.jid
774
814
  }
775
- else {
776
- stanza.attrs.to = participant.jid;
777
- }
778
- }
779
- else {
780
- stanza.attrs.to = destinationJid;
815
+ } else {
816
+ stanza.attrs.to = destinationJid
781
817
  }
818
+
782
819
  if (shouldIncludeDeviceIdentity) {
783
- ;
784
820
  stanza.content.push({
785
821
  tag: 'device-identity',
786
822
  attrs: {},
787
- content: encodeSignedDeviceIdentity(authState.creds.account, true)
788
- });
789
- logger.debug({ jid }, 'adding device identity');
823
+ content: Utils_1.encodeSignedDeviceIdentity(authState.creds.account, true)
824
+ })
825
+ logger.debug({
826
+ jid
827
+ }, 'adding device identity')
790
828
  }
791
- if (!isNewsletter &&
792
- !isRetryResend &&
793
- reportingMessage?.messageContextInfo?.messageSecret &&
794
- shouldIncludeReportingToken(reportingMessage)) {
795
- try {
796
- const encoded = encodeWAMessage(reportingMessage);
797
- const reportingKey = {
798
- id: msgId,
799
- fromMe: true,
800
- remoteJid: destinationJid,
801
- participant: participant?.jid
802
- };
803
- const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
804
- if (reportingNode) {
805
- ;
806
- stanza.content.push(reportingNode);
807
- logger.trace({ jid }, 'added reporting token to message');
808
- }
809
- }
810
- catch (error) {
811
- logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
812
- }
829
+
830
+ if (isGroup && regexGroupOld.test(jid) && !message.reactionMessage) {
831
+ stanza.content.push({
832
+ tag: 'multicast',
833
+ attrs: {}
834
+ })
813
835
  }
814
- // WA Web never attaches tctoken to peer (AppStateSync) messages — server rejects with 479
815
- const isPeerMessage = additionalAttributes?.['category'] === 'peer';
816
- const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
817
- // Resolve destination to LID for tctoken storage — matches Signal session key pattern
818
- const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
819
- const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
820
- const existingTokenEntry = contactTcTokenData[tcTokenJid];
821
- let tcTokenBuffer = existingTokenEntry?.token;
822
- // Treat expired tokens the same as missing clear from cache
823
- if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
824
- logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing');
825
- tcTokenBuffer = undefined;
826
- // Preserve senderTimestamp so the fire-and-forget issuance dedupe survives cleanup.
827
- const cleared = existingTokenEntry?.senderTimestamp !== undefined
828
- ? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
829
- : null;
830
- try {
831
- await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } });
832
- }
833
- catch (err) {
834
- logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup');
836
+
837
+ if (pollMessage || messages.eventMessage) {
838
+ let attrs = {};
839
+ if (messages.eventMessage) {
840
+ attrs.event_type = 'creation';
841
+ } else {
842
+ attrs.polltype = 'creation';
843
+ if (isNewsletter) {
844
+ attrs.contenttype = (pollMessage && pollMessage.pollContentType === 2) ? 'image' : 'text';
845
+ }
835
846
  }
836
- }
837
- if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
838
- ;
839
847
  stanza.content.push({
840
- tag: 'tctoken',
841
- attrs: {},
842
- content: tcTokenBuffer
848
+ tag: 'meta',
849
+ attrs: attrs
843
850
  });
844
851
  }
845
- if (additionalNodes && additionalNodes.length > 0) {
846
- ;
847
- stanza.content.push(...additionalNodes);
852
+
853
+ if (!isNewsletter && buttonType) {
854
+ const buttonsNode = getButtonArgs(messages)
855
+ const filteredButtons = WABinary_1.getBinaryFilteredButtons(additionalNodes ? additionalNodes : [])
856
+ if (filteredButtons) {
857
+ stanza.content.push(...additionalNodes)
858
+ additionalAlready = true
859
+ } else {
860
+ stanza.content.push(buttonsNode)
861
+ }
848
862
  }
849
- // Inject <biz> node required for buttons/interactive/list messages to render on WA
850
- const _bizContent = normalizeMessageContent(message);
851
- const hasExistingBizNode = additionalNodes && additionalNodes.some(node => node.tag === 'biz');
852
- if (!hasExistingBizNode && (isGroup || !isGroup && !isStatus && !isNewsletter)) {
853
- const bizNode = getBizBinaryNode(_bizContent || message);
854
- if (bizNode) {
855
- stanza.content.push(bizNode);
863
+
864
+ if (ai && isPrivate) {
865
+ const botNode = {
866
+ tag: 'bot',
867
+ attrs: {
868
+ biz_bot: '1'
869
+ }
870
+ }
871
+ const filteredBizBot = WABinary_1.getBinaryFilteredBizBot(additionalNodes ? additionalNodes : [])
872
+ if (filteredBizBot) {
873
+ stanza.content.push(...additionalNodes)
874
+ additionalAlready = true
875
+ } else {
876
+ stanza.content.push(botNode)
856
877
  }
857
878
  }
858
- logger.debug({ msgId }, `sending message to ${participants.length} devices`);
859
- await sendNode(stanza);
860
- // Fire-and-forget: issue our token to the contact AFTER message send.
861
- // WA Web skips protocol messages and PSA/bot contacts (TcTokenChatAction: isRegularUser)
862
- const isProtocolMsg = !!normalizeMessageContent(message)?.protocolMessage;
863
- const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
864
- if (is1on1Send &&
865
- !isProtocolMsg &&
866
- !isBotOrPSA &&
867
- shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&
868
- !inFlightTcTokenIssuance.has(tcTokenJid)) {
869
- inFlightTcTokenIssuance.add(tcTokenJid);
870
- const issueTimestamp = unixTimestampSeconds();
871
- const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
872
- resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
873
- .then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
874
- .then(async (result) => {
875
- await storeTcTokensFromIqResult({
876
- result,
877
- fallbackJid: tcTokenJid,
878
- keys: authState.keys,
879
- getLIDForPN
880
- });
881
- const currentData = await authState.keys.get('tctoken', [tcTokenJid]);
882
- const currentEntry = currentData[tcTokenJid];
883
- const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
884
- await authState.keys.set({
885
- tctoken: {
886
- [tcTokenJid]: {
887
- token: Buffer.alloc(0),
888
- ...currentEntry,
889
- senderTimestamp: issueTimestamp
890
- },
891
- ...indexWrite
892
- }
893
- });
894
- })
895
- .catch(err => {
896
- logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed');
897
- })
898
- .finally(() => {
899
- inFlightTcTokenIssuance.delete(tcTokenJid);
900
- });
879
+
880
+ if (!additionalAlready && additionalNodes && additionalNodes.length > 0) {
881
+ stanza.content.push(...additionalNodes)
901
882
  }
902
- // Add message to retry cache if enabled
883
+
884
+ logger.debug({
885
+ msgId
886
+ }, `sending message to ${participants.length} devices`)
887
+
888
+ await sendNode(stanza)
903
889
  if (messageRetryManager && !participant) {
904
- messageRetryManager.addRecentMessage(destinationJid, msgId, message);
890
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message)
905
891
  }
906
- }, meId);
907
- return msgId;
908
- };
909
- const getMessageType = (message) => {
910
- const normalizedMessage = normalizeMessageContent(message);
911
- if (!normalizedMessage)
912
- return 'text';
913
- if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
914
- return 'reaction';
915
- }
916
- if (normalizedMessage.pollCreationMessage ||
917
- normalizedMessage.pollCreationMessageV2 ||
918
- normalizedMessage.pollCreationMessageV3 ||
919
- normalizedMessage.pollUpdateMessage) {
920
- return 'poll';
921
- }
922
- if (normalizedMessage.eventMessage) {
923
- return 'event';
892
+ }, meId)
893
+ return msgId
894
+ }
895
+
896
+ const getTypeMessage = (msg) => {
897
+ const message = Utils_1.normalizeMessageContent(msg)
898
+ if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) {
899
+ return 'poll'
900
+ } else if (message.reactionMessage) {
901
+ return 'reaction'
902
+ } else if (message.eventMessage) {
903
+ return 'event'
904
+ } else if (getMediaType(message)) {
905
+ return 'media'
906
+ } else {
907
+ return 'text'
924
908
  }
925
- // Audio wrapped in groupStatusMessageV2 → type "text" (Baileys behavior)
926
- const isGroupStatusAudio = !!(message?.groupStatusMessageV2?.message?.audioMessage);
927
- if (!isGroupStatusAudio && getMediaType(normalizedMessage) !== '') {
928
- return 'media';
929
- }
930
- return 'text';
931
- };
909
+ }
910
+
932
911
  const getMediaType = (message) => {
933
912
  if (message.imageMessage) {
934
- return 'image';
935
- }
936
- else if (message.videoMessage) {
937
- return message.videoMessage.gifPlayback ? 'gif' : 'video';
938
- }
939
- else if (message.audioMessage) {
940
- return message.audioMessage.ptt ? 'ptt' : 'audio';
941
- }
942
- else if (message.contactMessage) {
943
- return 'vcard';
944
- }
945
- else if (message.documentMessage) {
946
- return 'document';
913
+ return 'image'
914
+ } else if (message.stickerMessage) {
915
+ return message.stickerMessage.isLottie ? '1p_sticker' : message.stickerMessage.isAvatar ? 'avatar_sticker' : 'sticker'
916
+ } else if (message.videoMessage) {
917
+ return message.videoMessage.gifPlayback ? 'gif' : 'video'
918
+ } else if (message.audioMessage) {
919
+ return message.audioMessage.ptt ? 'ptt' : 'audio'
920
+ } else if (message.ptvMessage) {
921
+ return 'ptv'
922
+ } else if (message.albumMessage) {
923
+ return 'collection'
924
+ } else if (message.contactMessage) {
925
+ return 'vcard'
926
+ } else if (message.documentMessage) {
927
+ return 'document'
928
+ } else if (message.stickerPackMessage) {
929
+ return 'sticker_pack'
930
+ } else if (message.contactsArrayMessage) {
931
+ return 'contact_array'
932
+ } else if (message.locationMessage) {
933
+ return 'location'
934
+ } else if (message.liveLocationMessage) {
935
+ return 'livelocation'
936
+ } else if (message.listMessage) {
937
+ return 'list'
938
+ } else if (message.listResponseMessage) {
939
+ return 'list_response'
940
+ } else if (message.buttonsResponseMessage) {
941
+ return 'buttons_response'
942
+ } else if (message.orderMessage) {
943
+ return 'order'
944
+ } else if (message.productMessage) {
945
+ return 'product'
946
+ } else if (message.interactiveResponseMessage) {
947
+ return 'native_flow_response'
948
+ } else if (/https:\/\/wa\.me\/c\/\d+/.test(message.extendedTextMessage?.text)) {
949
+ return 'cataloglink'
950
+ } else if (/https:\/\/wa\.me\/p\/\d+\/\d+/.test(message.extendedTextMessage?.text)) {
951
+ return 'productlink'
952
+ } else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
953
+ return 'url'
947
954
  }
948
- else if (message.contactsArrayMessage) {
949
- return 'contact_array';
950
- }
951
- else if (message.liveLocationMessage) {
952
- return 'livelocation';
953
- }
954
- else if (message.stickerMessage) {
955
- return 'sticker';
956
- }
957
- else if (message.listMessage) {
955
+ }
956
+
957
+ const getButtonType = (message) => {
958
+ const message_content = message.viewOnceMessage?.message || message;
959
+ if (message_content.listMessage) {
958
960
  return 'list';
961
+ } else if (message_content.buttonsMessage) {
962
+ return 'buttons';
963
+ } else if (message_content.interactiveMessage?.nativeFlowMessage) {
964
+ return 'native_flow';
959
965
  }
960
- else if (message.listResponseMessage) {
961
- return 'list_response';
962
- }
963
- else if (message.buttonsResponseMessage) {
964
- return 'buttons_response';
965
- }
966
- else if (message.orderMessage) {
967
- return 'order';
966
+ };
967
+
968
+ const getButtonArgs = (message) => {
969
+ const message_content = message.viewOnceMessage?.message || message;
970
+ const message_flow = message_content.interactiveMessage?.nativeFlowMessage;
971
+ const flow_buttons_first = message_flow?.buttons?.[0]?.name;
972
+ const flow_buttons_special = [ 'mpm', 'cta_catalog', 'send_location', 'call_permission_request', 'wa_payment_transaction_details', 'automated_greeting_message_view_catalog' ];
973
+ const baseArgs = {
974
+ tag: 'biz',
975
+ attrs: {
976
+ actual_actors: '2',
977
+ host_storage: '2',
978
+ privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
979
+ }
980
+ };
981
+ if (message_flow && (flow_buttons_first === 'review_and_pay' || flow_buttons_first === 'payment_info')) {
982
+ return {
983
+ tag: 'biz',
984
+ attrs: {
985
+ native_flow_name: flow_buttons_first === 'review_and_pay' ? 'order_details' : flow_buttons_first
986
+ }
987
+ };
968
988
  }
969
- else if (message.productMessage) {
970
- return 'product';
989
+ if (message_flow && flow_buttons_special.includes(flow_buttons_first)) {
990
+ return {
991
+ ...baseArgs,
992
+ content: [
993
+ {
994
+ tag: 'interactive',
995
+ attrs: { type: 'native_flow', v: '1' },
996
+ content: [{
997
+ tag: 'native_flow',
998
+ attrs: { v: '2', name: flow_buttons_first }
999
+ }]
1000
+ },
1001
+ {
1002
+ tag: 'quality_control',
1003
+ attrs: { source_type: 'third_party' }
1004
+ }
1005
+ ]
1006
+ };
971
1007
  }
972
- else if (message.interactiveResponseMessage) {
973
- return 'native_flow_response';
1008
+ if (message_flow || message_content.buttonsMessage) {
1009
+ return {
1010
+ ...baseArgs,
1011
+ content: [
1012
+ {
1013
+ tag: 'interactive',
1014
+ attrs: { type: 'native_flow', v: '1' },
1015
+ content: [{
1016
+ tag: 'native_flow',
1017
+ attrs: { v: '9', name: 'mixed' }
1018
+ }]
1019
+ },
1020
+ {
1021
+ tag: 'quality_control',
1022
+ attrs: { source_type: 'third_party' }
1023
+ }
1024
+ ]
1025
+ };
974
1026
  }
975
- else if (message.groupInviteMessage) {
976
- return 'url';
1027
+ if (message_content.listMessage) {
1028
+ return {
1029
+ ...baseArgs,
1030
+ content: [
1031
+ {
1032
+ tag: 'list',
1033
+ attrs: { v: '2', type: 'product_list' }
1034
+ },
1035
+ {
1036
+ tag: 'quality_control',
1037
+ attrs: { source_type: 'third_party' }
1038
+ }
1039
+ ]
1040
+ };
977
1041
  }
978
- return '';
1042
+ return baseArgs;
979
1043
  };
980
- const issuePrivacyTokens = async (jids, timestamp) => {
981
- const t = (timestamp ?? unixTimestampSeconds()).toString();
1044
+
1045
+ const getPrivacyTokens = async (jids) => {
1046
+ const t = Utils_1.unixTimestampSeconds().toString()
1047
+
982
1048
  const result = await query({
983
1049
  tag: 'iq',
984
1050
  attrs: {
985
- to: S_WHATSAPP_NET,
1051
+ to: WABinary_1.S_WHATSAPP_NET,
986
1052
  type: 'set',
987
1053
  xmlns: 'privacy'
988
1054
  },
989
- content: [
990
- {
991
- tag: 'tokens',
992
- attrs: {},
993
- content: jids.map(jid => ({
994
- tag: 'token',
995
- attrs: {
996
- jid: jidNormalizedUser(jid),
997
- t,
998
- type: 'trusted_contact'
999
- }
1000
- }))
1055
+ content: [{
1056
+ tag: 'tokens',
1057
+ attrs: {},
1058
+ content: jids.map(jid => ({
1059
+ tag: 'token',
1060
+ attrs: {
1061
+ jid: WABinary_1.jidNormalizedUser(jid),
1062
+ t,
1063
+ type: 'trusted_contact'
1064
+ }
1065
+ }))
1066
+ }]
1067
+ })
1068
+
1069
+ return result
1070
+ }
1071
+
1072
+ const getEphemeralGroup = (jid) => {
1073
+ if (!WABinary_1.isJidGroup(jid)) throw new TypeError("Jid should originate from a group!")
1074
+
1075
+ return groupQuery(jid, 'get', [{
1076
+ tag: 'query',
1077
+ attrs: {
1078
+ request: 'interactive'
1001
1079
  }
1002
- ]
1003
- });
1004
- return result;
1005
- };
1006
- const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
1007
- const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
1008
- // Initialize kikyy (group story / extended message handler)
1009
- const rahmi = new kikyy({ generateWAMessageContent, generateWAMessageFromContent }, waUploadToServer, relayMessage, authState.creds.me?.id);
1010
- registerSocketEndHandler(() => {
1011
- if (!config.userDevicesCache && userDevicesCache.close) {
1012
- userDevicesCache.close();
1013
- }
1014
- mediaConn = undefined;
1015
- if (messageRetryManager) {
1016
- messageRetryManager.clear();
1017
- }
1018
- });
1080
+ }])
1081
+ .then((groups) => WABinary_1.getBinaryNodeChild(groups, 'group'))
1082
+ .then((metadata) => WABinary_1.getBinaryNodeChild(metadata, 'ephemeral')?.attrs?.expiration || 0)
1083
+ }
1084
+
1085
+ const waUploadToServer = Utils_1.getWAUploadToServer(config, refreshMediaConn)
1086
+
1087
+ const waitForMsgMediaUpdate = Utils_1.bindWaitForEvent(ev, 'messages.media-update')
1088
+
1019
1089
  return {
1020
1090
  ...sock,
1021
- userDevicesCache,
1022
- devicesMutex,
1023
- issuePrivacyTokens,
1091
+ getPrivacyTokens,
1024
1092
  assertSessions,
1025
1093
  relayMessage,
1026
1094
  sendReceipt,
1027
1095
  sendReceipts,
1028
1096
  readMessages,
1097
+ profilePictureUrl,
1098
+ getUSyncDevices,
1029
1099
  refreshMediaConn,
1030
- // Function (not getter) so the spread in chats.ts preserves the live closure binding.
1031
- getMediaHost: () => mediaHost,
1032
1100
  waUploadToServer,
1101
+ getEphemeralGroup,
1033
1102
  fetchPrivacySettings,
1034
- sendPeerDataOperationMessage,
1035
- createParticipantNodes,
1036
- getUSyncDevices,
1037
1103
  messageRetryManager,
1038
- updateMemberLabel,
1104
+ createParticipantNodes,
1105
+ sendPeerDataOperationMessage,
1039
1106
  updateMediaMessage: async (message) => {
1040
- const content = assertMediaContent(message.message);
1041
- const mediaKey = content.mediaKey;
1042
- const meId = authState.creds.me.id;
1043
- const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
1044
- let error = undefined;
1107
+ const content = Utils_1.assertMediaContent(message.message)
1108
+ const mediaKey = content.mediaKey
1109
+ const meId = authState.creds.me.id
1110
+ const node = await Utils_1.encryptMediaRetryRequest(message.key, mediaKey, meId)
1111
+ let error = undefined
1112
+
1045
1113
  await Promise.all([
1046
1114
  sendNode(node),
1047
1115
  waitForMsgMediaUpdate(async (update) => {
1048
- const result = update.find(c => c.key.id === message.key.id);
1116
+ const result = update.find(c => c.key.id === message.key.id)
1049
1117
  if (result) {
1050
1118
  if (result.error) {
1051
- error = result.error;
1052
- }
1053
- else {
1119
+ error = result.error
1120
+ } else {
1054
1121
  try {
1055
- const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
1056
- if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
1057
- const resultStr = proto.MediaRetryNotification.ResultType[media.result];
1058
- throw new Boom(`Media re-upload failed by device (${resultStr})`, {
1122
+ const media = await Utils_1.decryptMediaRetryData(result.media, mediaKey, result.key.id)
1123
+
1124
+ if (media.result !== WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS) {
1125
+ const resultStr = WAProto_1.proto.MediaRetryNotification.ResultType[media.result]
1126
+
1127
+ throw new boom_1.Boom(`Media re-upload failed by device (${resultStr})`, {
1059
1128
  data: media,
1060
- statusCode: getStatusCodeForMediaRetry(media.result) || 404
1061
- });
1129
+ statusCode: Utils_1.getStatusCodeForMediaRetry(media.result) || 404
1130
+ })
1062
1131
  }
1063
- content.directPath = media.directPath;
1064
- content.url = getUrlFromDirectPath(content.directPath, mediaHost);
1065
- logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
1066
- }
1067
- catch (err) {
1068
- error = err;
1132
+
1133
+ content.directPath = media.directPath
1134
+
1135
+ content.url = Utils_1.getUrlFromDirectPath(content.directPath)
1136
+
1137
+ logger.debug({
1138
+ directPath: media.directPath,
1139
+ key: result.key
1140
+ }, 'media update successful')
1141
+ } catch (err) {
1142
+ error = err
1069
1143
  }
1070
1144
  }
1071
- return true;
1145
+
1146
+ return true
1072
1147
  }
1073
1148
  })
1074
- ]);
1149
+ ])
1150
+
1075
1151
  if (error) {
1076
- throw error;
1152
+ throw error
1077
1153
  }
1078
- ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
1079
- return message;
1154
+
1155
+ ev.emit('messages.update', [{
1156
+ key: message.key,
1157
+ update: {
1158
+ message: message.message
1159
+ }
1160
+ }])
1161
+
1162
+ return message
1080
1163
  },
1081
- sendMessage: async (jid, content, options = {}) => {
1082
- const userJid = authState.creds.me.id;
1083
- // Update rahmi's userJid in case it changed after login
1084
- if (rahmi && userJid) rahmi.userJid = userJid;
1085
- if (Array.isArray(jid)) {
1086
- const { delayMs = 1500 } = options;
1087
- const allUsers = new Set();
1088
- const fullMsg = await generateWAMessage('status@broadcast', content, {
1164
+ sendStatusMentions: async (content, jids = []) => {
1165
+ const MAX_STATUS_MENTIONS = 5
1166
+ if (jids.length > MAX_STATUS_MENTIONS) {
1167
+ logger.warn(
1168
+ { requested: jids.length, max: MAX_STATUS_MENTIONS },
1169
+ `sendStatusMentions: max ${MAX_STATUS_MENTIONS} mentions per status, truncating`
1170
+ )
1171
+ jids = jids.slice(0, MAX_STATUS_MENTIONS)
1172
+ }
1173
+ const userJid = WABinary_1.jidNormalizedUser(authState.creds.me.id)
1174
+ let allUsers = new Set()
1175
+ allUsers.add(userJid)
1176
+
1177
+ for (const id of jids) {
1178
+ const isGroup = WABinary_1.isJidGroup(id)
1179
+ const isPrivate = WABinary_1.isJidUser(id)
1180
+
1181
+ if (isGroup) {
1182
+ try {
1183
+ const metadata = await cachedGroupMetadata(id) || await global.groupMetadataCache(id)
1184
+ const participants = metadata.participants.map(p => WABinary_1.jidNormalizedUser(p.id))
1185
+ participants.forEach(jid => allUsers.add(jid))
1186
+ } catch (error) {
1187
+ logger.error(`Error getting metadata for group ${id}: ${error}`)
1188
+ }
1189
+ } else if (isPrivate) {
1190
+ allUsers.add(WABinary_1.jidNormalizedUser(id))
1191
+ }
1192
+ }
1193
+
1194
+ const uniqueUsers = Array.from(allUsers)
1195
+ const getRandomHexColor = () => "#" + Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0")
1196
+
1197
+ const isMedia = content.image || content.video || content.audio
1198
+ const isAudio = !!content.audio
1199
+
1200
+ const messageContent = {
1201
+ ...content
1202
+ }
1203
+
1204
+ if (isMedia && !isAudio) {
1205
+ if (messageContent.text) {
1206
+ messageContent.caption = messageContent.text
1207
+
1208
+ delete messageContent.text
1209
+ }
1210
+
1211
+ delete messageContent.ptt
1212
+ delete messageContent.font
1213
+ delete messageContent.backgroundColor
1214
+ delete messageContent.textColor
1215
+ }
1216
+
1217
+ if (isAudio) {
1218
+ delete messageContent.text
1219
+ delete messageContent.caption
1220
+ delete messageContent.font
1221
+ delete messageContent.textColor
1222
+ }
1223
+
1224
+ const font = !isMedia ? (content.font || Math.floor(Math.random() * 9)) : undefined
1225
+ const textColor = !isMedia ? (content.textColor || getRandomHexColor()) : undefined
1226
+ const backgroundColor = (!isMedia || isAudio) ? (content.backgroundColor || getRandomHexColor()) : undefined
1227
+ const ptt = isAudio ? (typeof content.ptt === 'boolean' ? content.ptt : true) : undefined
1228
+
1229
+ let msg
1230
+ let mediaHandle
1231
+ try {
1232
+ msg = await Utils_1.generateWAMessage(WABinary_1.STORIES_JID, messageContent, {
1089
1233
  logger,
1090
1234
  userJid,
1091
- upload: waUploadToServer,
1235
+ getUrlInfo: text => link_preview_1.getUrlInfo(text, {
1236
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1237
+ fetchOpts: {
1238
+ timeout: 3000,
1239
+ ...axiosOptions || {}
1240
+ },
1241
+ logger,
1242
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1243
+ }),
1244
+ upload: async (encFilePath, opts) => {
1245
+ const up = await waUploadToServer(encFilePath, {
1246
+ ...opts
1247
+ })
1248
+ mediaHandle = up.handle
1249
+ return up
1250
+ },
1092
1251
  mediaCache: config.mediaCache,
1093
1252
  options: config.options,
1094
- ...options,
1095
- messageId: generateMessageIDV2(userJid)
1096
- });
1097
- for (const id of jid) {
1098
- if (isJidGroup(id)) {
1099
- try {
1100
- const groupData = (cachedGroupMetadata ? await cachedGroupMetadata(id) : null) || await groupMetadata(id);
1101
- for (const participant of groupData.participants) {
1102
- if (allUsers.has(participant.id))
1103
- continue;
1104
- allUsers.add(participant.id);
1253
+ font,
1254
+ textColor,
1255
+ backgroundColor,
1256
+ ptt
1257
+ })
1258
+ } catch (error) {
1259
+ logger.error(`Error generating message: ${error}`)
1260
+ throw error
1261
+ }
1262
+
1263
+ await relayMessage(WABinary_1.STORIES_JID, msg.message, {
1264
+ messageId: msg.key.id,
1265
+ statusJidList: uniqueUsers,
1266
+ additionalNodes: [{
1267
+ tag: 'meta',
1268
+ attrs: {},
1269
+ content: [{
1270
+ tag: 'mentioned_users',
1271
+ attrs: {},
1272
+ content: jids.map(jid => ({
1273
+ tag: 'to',
1274
+ attrs: {
1275
+ jid: WABinary_1.jidNormalizedUser(jid)
1105
1276
  }
1106
- }
1107
- catch (error) {
1108
- logger.error(`Error getting metadata group from ${id}: ${error}`);
1109
- }
1110
- }
1111
- else if (!allUsers.has(id)) {
1112
- allUsers.add(id);
1113
- }
1114
- }
1115
- await relayMessage('status@broadcast', fullMsg.message, {
1116
- messageId: fullMsg.key.id,
1117
- statusJidList: Array.from(allUsers),
1118
- additionalNodes: [
1119
- {
1120
- tag: 'meta',
1121
- attrs: {},
1122
- content: [
1123
- {
1124
- tag: 'mentioned_users',
1125
- attrs: {},
1126
- content: jid.map(id => ({
1127
- tag: 'to',
1128
- attrs: { jid: id },
1129
- content: undefined
1130
- }))
1131
- }
1132
- ]
1133
- }
1134
- ]
1135
- });
1136
- if (config.emitOwnEvents) {
1137
- process.nextTick(async () => {
1138
- await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1139
- });
1140
- }
1141
- for (const id of jid) {
1142
- const isGroup = isJidGroup(id);
1143
- const sendType = isGroup ? 'groupStatusMentionMessage' : 'statusMentionMessage';
1144
- const mentionMsg = generateWAMessageFromContent(id, {
1145
- messageContextInfo: {
1146
- messageSecret: randomBytes(32)
1147
- },
1148
- [sendType]: {
1277
+ }))
1278
+ }]
1279
+ }]
1280
+ })
1281
+
1282
+ for (const id of jids) {
1283
+ try {
1284
+ const normalizedId = WABinary_1.jidNormalizedUser(id)
1285
+ const isPrivate = WABinary_1.isJidUser(normalizedId)
1286
+ const type = isPrivate ? 'statusMentionMessage' : 'groupStatusMentionMessage'
1287
+
1288
+ const protocolMessage = {
1289
+ [type]: {
1149
1290
  message: {
1150
1291
  protocolMessage: {
1151
- key: fullMsg.key,
1292
+ key: msg.key,
1152
1293
  type: 25
1153
1294
  }
1154
1295
  }
1296
+ },
1297
+ messageContextInfo: {
1298
+ messageSecret: crypto_1.randomBytes(32)
1155
1299
  }
1156
- }, {
1157
- userJid
1158
- });
1159
- await relayMessage(id, mentionMsg.message, {
1160
- additionalNodes: [
1161
- {
1162
- tag: 'meta',
1163
- attrs: isGroup ?
1164
- { is_group_status_mention: 'true' } :
1165
- { is_status_mention: 'true' },
1166
- content: undefined
1167
- }
1168
- ]
1169
- });
1170
- if (config.emitOwnEvents) {
1171
- process.nextTick(async () => {
1172
- await messageMutex.mutex(() => upsertMessage(mentionMsg, 'append'));
1173
- });
1174
1300
  }
1175
- await delay(delayMs);
1301
+
1302
+ const statusMsg = await Utils_1.generateWAMessageFromContent(normalizedId,
1303
+ protocolMessage, {}
1304
+ )
1305
+
1306
+ await relayMessage(
1307
+ normalizedId,
1308
+ statusMsg.message, {
1309
+ additionalNodes: [{
1310
+ tag: 'meta',
1311
+ attrs: isPrivate ? {
1312
+ is_status_mention: 'true'
1313
+ } : {
1314
+ is_group_status_mention: 'true'
1315
+ }
1316
+ }]
1317
+ }
1318
+ )
1319
+
1320
+ await Utils_1.delay(2000)
1321
+ } catch (error) {
1322
+ logger.error(`Error sending to ${id}: ${error}`)
1176
1323
  }
1177
- return fullMsg;
1178
1324
  }
1179
- else if (typeof content === 'object' &&
1180
- 'disappearingMessagesInChat' in content &&
1181
- typeof content['disappearingMessagesInChat'] !== 'undefined' &&
1182
- isJidGroup(jid)) {
1183
- const { disappearingMessagesInChat } = content;
1184
- const value = typeof disappearingMessagesInChat === 'boolean'
1185
- ? disappearingMessagesInChat
1186
- ? WA_DEFAULT_EPHEMERAL
1187
- : 0
1188
- : disappearingMessagesInChat;
1189
- await groupToggleEphemeral(jid, value);
1325
+
1326
+ return msg
1327
+ },
1328
+ sendAlbumMessage: async (jid, medias, options = {}) => {
1329
+ const userJid = authState.creds.me.id
1330
+ for (const media of medias) {
1331
+ if (!media.image && !media.video) throw new TypeError(`medias[i] must have image or video property`)
1190
1332
  }
1191
- else {
1192
- // Handle GROUP_STORY (group status/story) via kikyy
1193
- const messageType = rahmi.detectType(content);
1194
- if (messageType === 'GROUP_STORY') {
1195
- return await rahmi.handleGroupStory(content, jid, options.quoted);
1333
+ if (medias.length < 2) throw new RangeError("Minimum 2 media")
1334
+ const time = options.delay || 500
1335
+ delete options.delay
1336
+ const album = await Utils_1.generateWAMessageFromContent(jid, {
1337
+ albumMessage: {
1338
+ expectedImageCount: medias.filter(media => media.image).length,
1339
+ expectedVideoCount: medias.filter(media => media.video).length,
1340
+ ...options
1196
1341
  }
1197
- else if (messageType === 'ALBUM') {
1198
- return await rahmi.handleAlbum(content, jid, options.quoted);
1342
+ }, {
1343
+ userJid,
1344
+ ...options
1345
+ })
1346
+ await relayMessage(jid, album.message, {
1347
+ messageId: album.key.id
1348
+ })
1349
+ let mediaHandle
1350
+ let msg
1351
+ for (const i in medias) {
1352
+ const media = medias[i]
1353
+ if (media.image) {
1354
+ msg = await Utils_1.generateWAMessage(jid, {
1355
+ image: media.image,
1356
+ ...media,
1357
+ ...options
1358
+ }, {
1359
+ userJid,
1360
+ upload: async (readStream, opts) => {
1361
+ const up = await waUploadToServer(readStream, {
1362
+ ...opts,
1363
+ newsletter: WABinary_1.isJidNewsletter(jid)
1364
+ })
1365
+ mediaHandle = up.handle
1366
+ return up
1367
+ },
1368
+ ...options
1369
+ })
1370
+ } else if (media.video) {
1371
+ msg = await Utils_1.generateWAMessage(jid, {
1372
+ video: media.video,
1373
+ ...media,
1374
+ ...options
1375
+ }, {
1376
+ userJid,
1377
+ upload: async (readStream, opts) => {
1378
+ const up = await waUploadToServer(readStream, {
1379
+ ...opts,
1380
+ newsletter: WABinary_1.isJidNewsletter(jid)
1381
+ })
1382
+ mediaHandle = up.handle
1383
+ return up
1384
+ },
1385
+ ...options,
1386
+ })
1199
1387
  }
1200
- else if (messageType === 'EVENT') {
1201
- return await rahmi.handleEvent(content, jid, options.quoted);
1388
+ if (msg) {
1389
+ msg.message.messageContextInfo = {
1390
+ messageSecret: crypto_1.randomBytes(32),
1391
+ messageAssociation: {
1392
+ associationType: 1,
1393
+ parentMessageKey: album.key
1394
+ }
1395
+ }
1202
1396
  }
1203
- else if (messageType === 'POLL_RESULT') {
1204
- return await rahmi.handlePollResult(content, jid, options.quoted);
1397
+ await relayMessage(jid, msg.message, {
1398
+ messageId: msg.key.id
1399
+ })
1400
+ await Utils_1.delay(time)
1401
+ }
1402
+ return album
1403
+ },
1404
+ sendMessage: async (jid, content, options = {}) => {
1405
+ const userJid = authState.creds.me.id
1406
+ const additionalAttributes = {}
1407
+
1408
+ if (typeof content === 'object' && 'disappearingMessagesInChat' in content && typeof content['disappearingMessagesInChat'] !== 'undefined' && WABinary_1.isJidGroup(jid)) {
1409
+ const { disappearingMessagesInChat } = content
1410
+ const value = typeof disappearingMessagesInChat === 'boolean' ? (disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) : disappearingMessagesInChat
1411
+ await groupToggleEphemeral(jid, value)
1412
+ } else if (typeof content === 'object' && 'album' in content && content.album) {
1413
+ const albumMsg = await Utils_1.prepareAlbumMessageContent(jid, content.album, {
1414
+ sock: {
1415
+ relayMessage,
1416
+ waUploadToServer
1417
+ },
1418
+ userJid: userJid,
1419
+ ...options
1420
+ })
1421
+
1422
+ for (const media of albumMsg) {
1423
+ await Utils_1.delay(options.delay || 500)
1424
+ await relayMessage(jid, media.message, {
1425
+ messageId: media.key.id,
1426
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1427
+ additionalAttributes,
1428
+ statusJidList: options.statusJidList,
1429
+ additionalNodes: options.additionalNodes,
1430
+ ai: options.ai
1431
+ })
1205
1432
  }
1206
- const fullMsg = await generateWAMessage(jid, content, {
1433
+
1434
+ return albumMsg
1435
+ } else {
1436
+ let mediaHandle
1437
+
1438
+ const fullMsg = await Utils_1.generateWAMessage(jid, content, {
1207
1439
  logger,
1208
1440
  userJid,
1209
- getUrlInfo: text => getUrlInfo(text, {
1441
+ getUrlInfo: text => link_preview_1.getUrlInfo(text, {
1210
1442
  thumbnailWidth: linkPreviewImageThumbnailWidth,
1211
1443
  fetchOpts: {
1212
1444
  timeout: 3000,
1213
- ...(httpRequestOptions || {})
1445
+ ...axiosOptions || {}
1214
1446
  },
1215
1447
  logger,
1216
1448
  uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1217
1449
  }),
1218
- //TODO: CACHE
1219
- getProfilePicUrl: sock.profilePictureUrl,
1220
- getCallLink: sock.createCallLink,
1221
- upload: waUploadToServer,
1450
+ getProfilePicUrl: profilePictureUrl,
1451
+ getCallLink: createCallLink,
1452
+ upload: async (encFilePath, opts) => {
1453
+ const up = await waUploadToServer(encFilePath, {
1454
+ ...opts,
1455
+ newsletter: WABinary_1.isJidNewsletter(jid)
1456
+ })
1457
+ mediaHandle = up.handle
1458
+ return up
1459
+ },
1222
1460
  mediaCache: config.mediaCache,
1223
1461
  options: config.options,
1224
- messageId: generateMessageIDV2(sock.user?.id),
1225
- ...options
1226
- });
1227
- const isNewsletter = isJidNewsletter(jid);
1228
- const isEventMsg = 'event' in content && !!content.event;
1229
- const isDeleteMsg = 'delete' in content && !!content.delete;
1230
- const isEditMsg = 'edit' in content && !!content.edit;
1231
- const isPinMsg = 'pin' in content && !!content.pin;
1232
- const isKeepMsg = 'keep' in content && !!content.keep;
1233
- const isPollMsg = 'poll' in content && !!content.poll;
1234
- const isQuizMsg = 'poll' in content && !!content.poll.pollType;
1235
- const isAiMsg = 'ai' in content && !!content.ai;
1236
- const isNeedBizAttrs = 'secureMetaServiceLabel' in content && !!content.secureMetaServiceLabel;
1237
- const additionalAttributes = options.additionalAttributes || {};
1238
- const additionalNodes = options.additionalNodes || [];
1239
- // required for delete
1240
- if (isDeleteMsg || isKeepMsg) {
1241
- // if the chat is a group, and I am not the author, then delete the message as an admin
1242
- if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1243
- additionalAttributes.edit = '8';
1244
- }
1245
- else {
1246
- additionalAttributes.edit = '7';
1462
+ messageId: Utils_1.generateMessageID(userJid),
1463
+ ...options,
1464
+ })
1465
+
1466
+ const isPin = 'pin' in content && !!content.pin
1467
+ const isEdit = 'edit' in content && !!content.edit
1468
+ const isDelete = 'delete' in content && !!content.delete
1469
+ const isKeep = 'keep' in content && !!content.keep && content.keep?.type === 2
1470
+
1471
+ if (isDelete || isKeep) {
1472
+ if (WABinary_1.isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe || WABinary_1.isJidNewsletter(jid)) {
1473
+ additionalAttributes.edit = '8'
1474
+ } else {
1475
+ additionalAttributes.edit = '7'
1247
1476
  }
1477
+ } else if (isEdit) {
1478
+ additionalAttributes.edit = WABinary_1.isJidNewsletter(jid) ? '3' : '1'
1479
+ } else if (isPin) {
1480
+ additionalAttributes.edit = '2'
1248
1481
  }
1249
- else if (isEditMsg) {
1250
- additionalAttributes.edit = isNewsletter ? '3' : '1';
1251
- }
1252
- else if (isPinMsg) {
1253
- additionalAttributes.edit = '2';
1482
+
1483
+ if (mediaHandle) {
1484
+ additionalAttributes['media_id'] = mediaHandle
1254
1485
  }
1255
- else if (isPollMsg) {
1256
- if (!isNewsletter && isQuizMsg) {
1257
- throw new Boom('Quiz are only allowed for newsletter', { statusCode: 400 });
1258
- }
1259
- additionalNodes.push({
1260
- tag: 'meta',
1261
- attrs: {
1262
- polltype: isQuizMsg ? 'quiz_creation' : 'creation',
1263
- contenttype: isPollMsg && isNewsletter ? 'text' : undefined
1264
- },
1265
- content: undefined
1266
- });
1267
- }
1268
- else if (isEventMsg) {
1269
- additionalNodes.push({
1270
- tag: 'meta',
1271
- attrs: {
1272
- event_type: 'creation'
1273
- },
1274
- content: undefined
1275
- });
1276
- }
1277
- else if (isAiMsg) {
1278
- if (!(isPnUser(jid) || isLidUser(jid))) {
1279
- throw new Boom('AI icon on message are only allowed in private chat', { statusCode: 400 });
1280
- }
1281
- if ('messageContextInfo' in fullMsg.message && !!fullMsg.message.messageContextInfo) {
1282
- fullMsg.message.messageContextInfo.supportPayload = BIZ_BOT_SUPPORT_PAYLOAD;
1283
- }
1284
- additionalNodes.push({
1285
- tag: 'bot',
1286
- attrs: {
1287
- biz_bot: '1'
1288
- },
1289
- content: undefined
1290
- });
1291
- delete content.ai;
1486
+
1487
+ if ('cachedGroupMetadata' in options) {
1488
+ console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.')
1292
1489
  }
1490
+
1293
1491
  await relayMessage(jid, fullMsg.message, {
1294
1492
  messageId: fullMsg.key.id,
1295
1493
  useCachedGroupMetadata: options.useCachedGroupMetadata,
1296
- addBizAttributes: isNeedBizAttrs,
1297
- statusJidList: options.statusJidList,
1298
1494
  additionalAttributes,
1299
- additionalNodes
1300
- });
1495
+ statusJidList: options.statusJidList,
1496
+ additionalNodes: options.additionalNodes,
1497
+ ai: options.ai
1498
+ })
1499
+
1301
1500
  if (config.emitOwnEvents) {
1302
- process.nextTick(async () => {
1303
- await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1304
- });
1305
- }
1306
- if ('album' in content) {
1307
- const { delayMs = 1500 } = options;
1308
- for (const albumMedia of content.album) {
1309
- const albumMsg = await generateWAMessage(jid, albumMedia, {
1310
- logger,
1311
- userJid,
1312
- upload: waUploadToServer,
1313
- mediaCache: config.mediaCache,
1314
- options: config.options,
1315
- ...options,
1316
- messageId: generateMessageIDV2(userJid)
1317
- });
1318
- if (!hasValidAlbumMedia(normalizeMessageContent(albumMsg.message))) {
1319
- throw new Boom('Invalid message type for album', { statusCode: 400 });
1320
- }
1321
- albumMsg.message.messageContextInfo ||= {};
1322
- albumMsg.message.messageContextInfo.messageAssociation = {
1323
- parentMessageKey: fullMsg.key,
1324
- associationType: AssociationType.MEDIA_ALBUM
1325
- };
1326
- await relayMessage(jid, albumMsg.message, {
1327
- messageId: albumMsg.key.id,
1328
- useCachedGroupMetadata: options.useCachedGroupMetadata,
1329
- addBizAttributes: isNeedBizAttrs,
1330
- statusJidList: options.statusJidList,
1331
- additionalAttributes,
1332
- additionalNodes
1333
- });
1334
- if (config.emitOwnEvents) {
1335
- process.nextTick(async () => {
1336
- await messageMutex.mutex(() => upsertMessage(albumMsg, 'append'));
1337
- });
1338
- }
1339
- await delay(delayMs);
1340
- }
1501
+ process.nextTick(() => {
1502
+ processingMutex.mutex(() => (upsertMessage(fullMsg, 'append')))
1503
+ })
1341
1504
  }
1342
- return fullMsg;
1505
+
1506
+ return fullMsg
1343
1507
  }
1344
- },
1345
- sendTable: async (jid, title, headers, rows, quoted, options = {}) => {
1346
- const { message, messageId } = generateTableContent(title, headers, rows, quoted, options);
1347
- await relayMessage(jid, message, { messageId });
1348
- return { message, messageId };
1349
- },
1350
- sendTableV2: async (jid, table, quoted, options = {}) => {
1351
- const { message, messageId } = generateTableContentV2(table, quoted, options);
1352
- await relayMessage(jid, message, { messageId });
1353
- return { message, messageId };
1354
- },
1355
- sendList: async (jid, title, items, quoted, options = {}) => {
1356
- const { message, messageId } = generateListContent(title, items, quoted, options);
1357
- await relayMessage(jid, message, { messageId });
1358
- return { message, messageId };
1359
- },
1360
- sendCodeBlock: async (jid, code, quoted, options = {}) => {
1361
- const { message, messageId } = generateCodeBlockContent(code, quoted, options);
1362
- await relayMessage(jid, message, { messageId });
1363
- return { message, messageId };
1364
- },
1365
- sendCodeBlockV2: async (jid, code, quoted, options = {}) => {
1366
- const { message, messageId } = generateCodeBlockContentV2(code, quoted, options);
1367
- await relayMessage(jid, message, { messageId });
1368
- return { message, messageId };
1369
- },
1370
- sendLink: async (jid, text, links, quoted, options = {}) => {
1371
- const { message, messageId } = generateLinkContent(text, links, quoted, options);
1372
- await relayMessage(jid, message, { messageId });
1373
- return { message, messageId };
1374
- },
1375
- sendLinkV2: async (jid, text, links, quoted, options = {}) => {
1376
- const { message, messageId } = generateLinkContentV2(text, links, quoted, options);
1377
- await relayMessage(jid, message, { messageId });
1378
- return { message, messageId };
1379
- },
1380
- sendLatex: async (jid, quoted, options) => {
1381
- const { message, messageId } = generateLatexContent(quoted, options);
1382
- await relayMessage(jid, message, { messageId });
1383
- return { message, messageId };
1384
- },
1385
- sendLatexImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
1386
- const { message, messageId } = await generateLatexImageContent(quoted, options, uploadFn, renderLatexToPng);
1387
- await relayMessage(jid, message, { messageId });
1388
- return { message, messageId };
1389
- },
1390
- sendLatexInlineImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
1391
- const { message, messageId } = await generateLatexInlineImageContent(quoted, options, uploadFn, renderLatexToPng);
1392
- await relayMessage(jid, message, { messageId });
1393
- return { message, messageId };
1394
- },
1395
- sendRichMessage: async (jid, submessages, quoted, options = {}) => {
1396
- const { message, messageId } = generateRichMessageContent(submessages, quoted);
1397
- await relayMessage(jid, message, { messageId });
1398
- return { message, messageId };
1399
- },
1400
- sendUnifiedResponse: async (jid, quoted, captured) => {
1401
- const { message, messageId } = generateUnifiedResponseContent(quoted, captured);
1402
- await relayMessage(jid, message, { messageId });
1403
- return { message, messageId };
1404
- },
1405
- captureUnifiedResponse,
1406
- };
1407
- };
1408
- //# sourceMappingURL=messages-send.js.map
1508
+ }
1509
+ }
1510
+ }
1511
+
1512
+ module.exports = {
1513
+ makeMessagesSocket
1514
+ }