@alannxd/baileys 6.0.3 → 6.0.5

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 (234) hide show
  1. package/WAProto/GenerateStatics.sh +3 -0
  2. package/WAProto/WAProto.proto +5479 -0
  3. package/WAProto/fix-imports.js +85 -0
  4. package/WAProto/index.d.ts +14017 -0
  5. package/WAProto/index.js +201 -160
  6. package/engine-requirements.js +1 -1
  7. package/lib/Defaults/index.d.ts +37 -15
  8. package/lib/Defaults/index.js +119 -136
  9. package/lib/Signal/Group/ciphertext-message.d.ts +1 -0
  10. package/lib/Signal/Group/ciphertext-message.js +2 -5
  11. package/lib/Signal/Group/group-session-builder.d.ts +4 -3
  12. package/lib/Signal/Group/group-session-builder.js +7 -41
  13. package/lib/Signal/Group/group_cipher.d.ts +4 -4
  14. package/lib/Signal/Group/group_cipher.js +37 -51
  15. package/lib/Signal/Group/index.d.ts +12 -11
  16. package/lib/Signal/Group/index.js +12 -57
  17. package/lib/Signal/Group/keyhelper.d.ts +2 -1
  18. package/lib/Signal/Group/keyhelper.js +7 -44
  19. package/lib/Signal/Group/sender-chain-key.d.ts +3 -2
  20. package/lib/Signal/Group/sender-chain-key.js +7 -15
  21. package/lib/Signal/Group/sender-key-distribution-message.d.ts +2 -1
  22. package/lib/Signal/Group/sender-key-distribution-message.js +8 -11
  23. package/lib/Signal/Group/sender-key-message.d.ts +2 -1
  24. package/lib/Signal/Group/sender-key-message.js +9 -12
  25. package/lib/Signal/Group/sender-key-name.d.ts +1 -0
  26. package/lib/Signal/Group/sender-key-name.js +2 -5
  27. package/lib/Signal/Group/sender-key-record.d.ts +3 -2
  28. package/lib/Signal/Group/sender-key-record.js +9 -21
  29. package/lib/Signal/Group/sender-key-state.d.ts +7 -6
  30. package/lib/Signal/Group/sender-key-state.js +27 -42
  31. package/lib/Signal/Group/sender-message-key.d.ts +1 -0
  32. package/lib/Signal/Group/sender-message-key.js +4 -7
  33. package/lib/Signal/libsignal.d.ts +5 -3
  34. package/lib/Signal/libsignal.js +347 -90
  35. package/lib/Signal/lid-mapping.d.ts +23 -0
  36. package/lib/Signal/lid-mapping.js +277 -0
  37. package/lib/Socket/Client/index.d.ts +3 -3
  38. package/lib/Socket/Client/index.js +3 -19
  39. package/lib/Socket/Client/{abstract-socket-client.d.ts → types.d.ts} +4 -5
  40. package/lib/Socket/Client/types.js +11 -0
  41. package/lib/Socket/Client/{web-socket-client.d.ts → websocket.d.ts} +3 -2
  42. package/lib/Socket/Client/websocket.js +54 -0
  43. package/lib/Socket/business.d.ts +154 -108
  44. package/lib/Socket/business.js +162 -43
  45. package/lib/Socket/chats.d.ts +96 -239
  46. package/lib/Socket/chats.js +627 -427
  47. package/lib/Socket/communities.d.ts +239 -146
  48. package/lib/Socket/communities.js +90 -80
  49. package/lib/Socket/groups.d.ts +104 -57
  50. package/lib/Socket/groups.js +154 -161
  51. package/lib/Socket/index.d.ts +202 -115
  52. package/lib/Socket/index.js +11 -10
  53. package/lib/Socket/luxu.d.ts +22 -266
  54. package/lib/Socket/luxu.js +422 -465
  55. package/lib/Socket/messages-recv.d.ts +136 -84
  56. package/lib/Socket/messages-recv.js +1421 -615
  57. package/lib/Socket/messages-send.d.ts +142 -126
  58. package/lib/Socket/messages-send.js +878 -671
  59. package/lib/Socket/mex.d.ts +3 -0
  60. package/lib/Socket/mex.js +42 -0
  61. package/lib/Socket/newsletter.d.ts +121 -85
  62. package/lib/Socket/newsletter.js +147 -272
  63. package/lib/Socket/socket.d.ts +34 -19
  64. package/lib/Socket/socket.js +544 -313
  65. package/lib/Store/index.d.ts +10 -3
  66. package/lib/Store/index.js +10 -10
  67. package/lib/Store/keyed-db.d.ts +22 -0
  68. package/lib/Store/keyed-db.js +108 -0
  69. package/lib/Store/make-cache-manager-store.d.ts +17 -11
  70. package/lib/Store/make-cache-manager-store.js +43 -41
  71. package/lib/Store/make-in-memory-store.d.ts +39 -118
  72. package/lib/Store/make-in-memory-store.js +112 -341
  73. package/lib/Store/make-ordered-dictionary.d.ts +11 -10
  74. package/lib/Store/make-ordered-dictionary.js +14 -20
  75. package/lib/Store/object-repository.d.ts +10 -9
  76. package/lib/Store/object-repository.js +11 -6
  77. package/lib/Types/Auth.d.ts +19 -12
  78. package/lib/Types/Auth.js +2 -2
  79. package/lib/Types/Bussines.d.ts +25 -0
  80. package/lib/Types/Bussines.js +2 -0
  81. package/lib/Types/Call.d.ts +3 -1
  82. package/lib/Types/Call.js +2 -2
  83. package/lib/Types/Chat.d.ts +35 -13
  84. package/lib/Types/Chat.js +8 -4
  85. package/lib/Types/Contact.d.ts +8 -1
  86. package/lib/Types/Contact.js +2 -2
  87. package/lib/Types/Events.d.ts +116 -17
  88. package/lib/Types/Events.js +2 -2
  89. package/lib/Types/GroupMetadata.d.ts +21 -5
  90. package/lib/Types/GroupMetadata.js +2 -2
  91. package/lib/Types/Label.d.ts +12 -0
  92. package/lib/Types/Label.js +3 -5
  93. package/lib/Types/LabelAssociation.d.ts +1 -0
  94. package/lib/Types/LabelAssociation.js +3 -5
  95. package/lib/Types/Message.d.ts +105 -58
  96. package/lib/Types/Message.js +11 -9
  97. package/lib/Types/Mex.d.ts +141 -0
  98. package/lib/Types/Mex.js +37 -0
  99. package/lib/Types/Product.d.ts +2 -1
  100. package/lib/Types/Product.js +2 -2
  101. package/lib/Types/Signal.d.ts +32 -2
  102. package/lib/Types/Signal.js +2 -2
  103. package/lib/Types/Socket.d.ts +50 -25
  104. package/lib/Types/Socket.js +3 -2
  105. package/lib/Types/State.d.ts +72 -2
  106. package/lib/Types/State.js +56 -2
  107. package/lib/Types/USync.d.ts +3 -2
  108. package/lib/Types/USync.js +2 -2
  109. package/lib/Types/index.d.ts +22 -14
  110. package/lib/Types/index.js +15 -31
  111. package/lib/Utils/auth-utils.d.ts +12 -6
  112. package/lib/Utils/auth-utils.js +239 -143
  113. package/lib/Utils/browser-utils.d.ts +4 -0
  114. package/lib/Utils/browser-utils.js +28 -0
  115. package/lib/Utils/business.d.ts +3 -2
  116. package/lib/Utils/business.js +66 -69
  117. package/lib/Utils/chat-utils.d.ts +52 -23
  118. package/lib/Utils/chat-utils.js +396 -253
  119. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  120. package/lib/Utils/companion-reg-client-utils.js +35 -0
  121. package/lib/Utils/crypto.d.ts +18 -22
  122. package/lib/Utils/crypto.js +57 -90
  123. package/lib/Utils/decode-wa-message.d.ts +55 -8
  124. package/lib/Utils/decode-wa-message.js +203 -84
  125. package/lib/Utils/event-buffer.d.ts +9 -8
  126. package/lib/Utils/event-buffer.js +185 -77
  127. package/lib/Utils/generics.d.ts +28 -29
  128. package/lib/Utils/generics.js +180 -210
  129. package/lib/Utils/history.d.ts +18 -9
  130. package/lib/Utils/history.js +93 -55
  131. package/lib/Utils/identity-change-handler.d.ts +44 -0
  132. package/lib/Utils/identity-change-handler.js +50 -0
  133. package/lib/Utils/index.d.ts +22 -17
  134. package/lib/Utils/index.js +22 -33
  135. package/lib/Utils/link-preview.d.ts +5 -5
  136. package/lib/Utils/link-preview.js +16 -24
  137. package/lib/Utils/logger.d.ts +11 -3
  138. package/lib/Utils/logger.js +3 -7
  139. package/lib/Utils/lt-hash.d.ts +8 -12
  140. package/lib/Utils/lt-hash.js +3 -46
  141. package/lib/Utils/make-mutex.d.ts +4 -2
  142. package/lib/Utils/make-mutex.js +24 -34
  143. package/lib/Utils/message-retry-manager.d.ts +115 -0
  144. package/lib/Utils/message-retry-manager.js +265 -0
  145. package/lib/Utils/messages-media.d.ts +61 -44
  146. package/lib/Utils/messages-media.js +451 -482
  147. package/lib/Utils/messages.d.ts +32 -18
  148. package/lib/Utils/messages.js +458 -369
  149. package/lib/Utils/noise-handler.d.ts +13 -14
  150. package/lib/Utils/noise-handler.js +145 -99
  151. package/lib/Utils/offline-node-processor.d.ts +17 -0
  152. package/lib/Utils/offline-node-processor.js +40 -0
  153. package/lib/Utils/pre-key-manager.d.ts +28 -0
  154. package/lib/Utils/pre-key-manager.js +106 -0
  155. package/lib/Utils/process-message.d.ts +31 -12
  156. package/lib/Utils/process-message.js +459 -150
  157. package/lib/Utils/reporting-utils.d.ts +11 -0
  158. package/lib/Utils/reporting-utils.js +258 -0
  159. package/lib/Utils/signal.d.ts +20 -5
  160. package/lib/Utils/signal.js +120 -72
  161. package/lib/Utils/stanza-ack.d.ts +11 -0
  162. package/lib/Utils/stanza-ack.js +38 -0
  163. package/lib/Utils/sync-action-utils.d.ts +19 -0
  164. package/lib/Utils/sync-action-utils.js +49 -0
  165. package/lib/Utils/tc-token-utils.d.ts +37 -0
  166. package/lib/Utils/tc-token-utils.js +163 -0
  167. package/lib/Utils/use-multi-file-auth-state.d.ts +2 -2
  168. package/lib/Utils/use-multi-file-auth-state.js +29 -27
  169. package/lib/Utils/validate-connection.d.ts +7 -7
  170. package/lib/Utils/validate-connection.js +73 -99
  171. package/lib/WABinary/constants.d.ts +25 -27
  172. package/lib/WABinary/constants.js +1281 -20
  173. package/lib/WABinary/decode.d.ts +5 -5
  174. package/lib/WABinary/decode.js +52 -42
  175. package/lib/WABinary/encode.d.ts +3 -3
  176. package/lib/WABinary/encode.js +110 -155
  177. package/lib/WABinary/generic-utils.d.ts +8 -7
  178. package/lib/WABinary/generic-utils.js +48 -49
  179. package/lib/WABinary/index.d.ts +6 -5
  180. package/lib/WABinary/index.js +6 -21
  181. package/lib/WABinary/jid-utils.d.ts +25 -8
  182. package/lib/WABinary/jid-utils.js +74 -40
  183. package/lib/WABinary/types.d.ts +2 -1
  184. package/lib/WABinary/types.js +2 -2
  185. package/lib/WAM/BinaryInfo.d.ts +3 -11
  186. package/lib/WAM/BinaryInfo.js +2 -5
  187. package/lib/WAM/constants.d.ts +5 -3
  188. package/lib/WAM/constants.js +19071 -11568
  189. package/lib/WAM/encode.d.ts +3 -3
  190. package/lib/WAM/encode.js +17 -22
  191. package/lib/WAM/index.d.ts +4 -3
  192. package/lib/WAM/index.js +4 -19
  193. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +4 -3
  194. package/lib/WAUSync/Protocols/USyncContactProtocol.js +33 -13
  195. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +3 -2
  196. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -14
  197. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +3 -2
  198. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +9 -12
  199. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +3 -2
  200. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -13
  201. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  202. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  203. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +4 -3
  204. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -22
  205. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +5 -3
  206. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +13 -8
  207. package/lib/WAUSync/Protocols/index.d.ts +6 -4
  208. package/lib/WAUSync/Protocols/index.js +6 -20
  209. package/lib/WAUSync/USyncQuery.d.ts +6 -4
  210. package/lib/WAUSync/USyncQuery.js +44 -35
  211. package/lib/WAUSync/USyncUser.d.ts +10 -5
  212. package/lib/WAUSync/USyncUser.js +10 -5
  213. package/lib/WAUSync/index.d.ts +4 -0
  214. package/lib/WAUSync/index.js +4 -19
  215. package/lib/index.d.ts +10 -9
  216. package/lib/index.js +12 -34
  217. package/package.json +84 -53
  218. package/WAProto/fix-import.js +0 -29
  219. package/lib/Defaults/baileys-version.json +0 -3
  220. package/lib/Defaults/phonenumber-mcc.json +0 -223
  221. package/lib/Signal/Group/queue-job.d.ts +0 -1
  222. package/lib/Signal/Group/queue-job.js +0 -57
  223. package/lib/Socket/Client/abstract-socket-client.js +0 -13
  224. package/lib/Socket/Client/mobile-socket-client.d.ts +0 -13
  225. package/lib/Socket/Client/mobile-socket-client.js +0 -65
  226. package/lib/Socket/Client/web-socket-client.js +0 -62
  227. package/lib/Socket/registration.d.ts +0 -267
  228. package/lib/Socket/registration.js +0 -166
  229. package/lib/Socket/usync.d.ts +0 -36
  230. package/lib/Socket/usync.js +0 -70
  231. package/lib/Types/Newsletter.d.ts +0 -103
  232. package/lib/Types/Newsletter.js +0 -38
  233. package/lib/Utils/baileys-event-stream.d.ts +0 -16
  234. package/lib/Utils/baileys-event-stream.js +0 -63
@@ -1,75 +1,70 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.makeMessagesSocket = void 0;
7
- const boom_1 = require("@hapi/boom");
8
- const node_cache_1 = __importDefault(require("node-cache"));
9
- const WAProto_1 = require("../../WAProto");
10
- const Defaults_1 = require("../Defaults");
11
- const axios_1 = require("axios")
12
- const Types_1 = require("../Types")
13
- const Utils_1 = require("../Utils");
14
- const link_preview_1 = require("../Utils/link-preview");
15
- const WABinary_1 = require("../WABinary");
16
- const communities_1 = require("./communities");
17
- const WAUSync_1 = require("../WAUSync");
18
- const crypto = require("crypto");
19
- const imup = require('./luxu');
20
- var ListType = WAProto_1.proto.Message.ListMessage.ListType;
21
- const makeMessagesSocket = (config) => {
22
- const {
23
- logger,
24
- linkPreviewImageThumbnailWidth,
25
- generateHighQualityLinkPreview,
26
- options: axiosOptions,
27
- patchMessageBeforeSending
28
- } = config;
29
- const sock = (0, communities_1.makeCommunitiesSocket)(config);
30
- const {
31
- ev,
32
- authState,
33
- processingMutex,
34
- signalRepository,
35
- upsertMessage,
36
- query,
37
- fetchPrivacySettings,
38
- generateMessageTag,
39
- sendNode,
40
- groupMetadata,
41
- groupToggleEphemeral,
42
- executeUSyncQuery
43
- } = sock;
44
- const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
45
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES,
46
- useClones: false
47
- });
1
+ import NodeCache from '@cacheable/node-cache';
2
+ import { Boom } from '@hapi/boom';
3
+ import { proto } from '../../WAProto/index.js';
4
+ import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
5
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
6
+ import { getUrlInfo } from '../Utils/link-preview.js';
7
+ import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
8
+ import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
9
+ import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, resolveTcTokenJid, shouldSendNewTcToken, storeTcTokensFromIqResult } from '../Utils/tc-token-utils.js';
10
+ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET, getAdditionalNode, getBinaryNodeFilter } from '../WABinary/index.js';
11
+ import { USyncQuery, USyncUser } from '../WAUSync/index.js';
12
+ import { makeNewsletterSocket } from './newsletter.js';
13
+ import imup from './luxu.js';
14
+ import * as Utils_1 from '../Utils/index.js';
15
+ export const makeMessagesSocket = (config) => {
16
+ const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount, aiLabel } = config;
17
+ const sock = makeNewsletterSocket(config);
18
+ const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
19
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
20
+ /**
21
+ * Set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
22
+ * Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
23
+ * Entries are always removed in `.finally()`, so the set is bounded by concurrency.
24
+ */
25
+ const inFlightTcTokenIssuance = new Set();
26
+ const userDevicesCache = config.userDevicesCache ||
27
+ new NodeCache({
28
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
29
+ useClones: false
30
+ });
31
+ /** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
32
+ const devicesMutex = makeMutex();
33
+ // Initialize message retry manager if enabled
34
+ const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
35
+ // Prevent race conditions in Signal session encryption by user
36
+ const encryptionMutex = makeKeyedMutex();
48
37
  let mediaConn;
38
+ /** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
39
+ let mediaHost = DEF_MEDIA_HOST;
49
40
  const refreshMediaConn = async (forceGet = false) => {
50
41
  const media = await mediaConn;
51
- if (!media || forceGet || (new Date().getTime() - media.fetchDate.getTime()) > media.ttl * 1000) {
42
+ if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
52
43
  mediaConn = (async () => {
53
44
  const result = await query({
54
45
  tag: 'iq',
55
46
  attrs: {
56
47
  type: 'set',
57
48
  xmlns: 'w:m',
58
- to: WABinary_1.S_WHATSAPP_NET,
49
+ to: S_WHATSAPP_NET
59
50
  },
60
51
  content: [{ tag: 'media_conn', attrs: {} }]
61
52
  });
62
- const mediaConnNode = WABinary_1.getBinaryNodeChild(result, 'media_conn');
53
+ const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
54
+ // TODO: explore full length of data that whatsapp provides
63
55
  const node = {
64
- hosts: WABinary_1.getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
56
+ hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
65
57
  hostname: attrs.hostname,
66
- maxContentLengthBytes: +attrs.maxContentLengthBytes,
58
+ maxContentLengthBytes: +attrs.maxContentLengthBytes
67
59
  })),
68
60
  auth: mediaConnNode.attrs.auth,
69
61
  ttl: +mediaConnNode.attrs.ttl,
70
62
  fetchDate: new Date()
71
63
  };
72
64
  logger.debug('fetched media conn');
65
+ if (node.hosts[0]) {
66
+ mediaHost = node.hosts[0].hostname;
67
+ }
73
68
  return node;
74
69
  })();
75
70
  }
@@ -80,17 +75,20 @@ const makeMessagesSocket = (config) => {
80
75
  * used for receipts of phone call, read, delivery etc.
81
76
  * */
82
77
  const sendReceipt = async (jid, participant, messageIds, type) => {
78
+ if (!messageIds || messageIds.length === 0) {
79
+ throw new Boom('missing ids in receipt');
80
+ }
83
81
  const node = {
84
82
  tag: 'receipt',
85
83
  attrs: {
86
- id: messageIds[0],
87
- },
84
+ id: messageIds[0]
85
+ }
88
86
  };
89
87
  const isReadReceipt = type === 'read' || type === 'read-self';
90
88
  if (isReadReceipt) {
91
- node.attrs.t = (0, Utils_1.unixTimestampSeconds)().toString();
89
+ node.attrs.t = unixTimestampSeconds().toString();
92
90
  }
93
- if (type === 'sender' && WABinary_1.isJidUser(jid)) {
91
+ if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
94
92
  node.attrs.recipient = jid;
95
93
  node.attrs.to = participant;
96
94
  }
@@ -101,7 +99,7 @@ const makeMessagesSocket = (config) => {
101
99
  }
102
100
  }
103
101
  if (type) {
104
- node.attrs.type = WABinary_1.isJidNewsLetter(jid) ? 'read-self' : type;
102
+ node.attrs.type = type;
105
103
  }
106
104
  const remainingMessageIds = messageIds.slice(1);
107
105
  if (remainingMessageIds.length) {
@@ -121,7 +119,7 @@ const makeMessagesSocket = (config) => {
121
119
  };
122
120
  /** Correctly bulk send receipts to multiple chats, participants */
123
121
  const sendReceipts = async (keys, type) => {
124
- const recps = (0, Utils_1.aggregateMessageKeysNotFromMe)(keys);
122
+ const recps = aggregateMessageKeysNotFromMe(keys);
125
123
  for (const { jid, participant, messageIds } of recps) {
126
124
  await sendReceipt(jid, participant, messageIds, type);
127
125
  }
@@ -135,290 +133,450 @@ const makeMessagesSocket = (config) => {
135
133
  };
136
134
  /** Fetch all the devices we've to send a message to */
137
135
  const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
138
- const deviceResults = []
139
-
136
+ const deviceResults = [];
140
137
  if (!useCache) {
141
- logger.debug('not using cache for devices')
138
+ logger.debug('not using cache for devices');
142
139
  }
143
-
144
- const toFetch = []
145
-
146
- jids = Array.from(new Set(jids))
147
-
148
- for (let jid of jids) {
149
- const user = WABinary_1.jidDecode(jid)?.user
150
-
151
- jid = WABinary_1.jidNormalizedUser(jid)
152
-
140
+ const toFetch = [];
141
+ const jidsWithUser = jids
142
+ .map(jid => {
143
+ const decoded = jidDecode(jid);
144
+ const user = decoded?.user;
145
+ const device = decoded?.device;
146
+ const isExplicitDevice = typeof device === 'number' && device >= 0;
147
+ if (isExplicitDevice && user) {
148
+ deviceResults.push({
149
+ user,
150
+ device,
151
+ jid
152
+ });
153
+ return null;
154
+ }
155
+ jid = jidNormalizedUser(jid);
156
+ return { jid, user };
157
+ })
158
+ .filter(jid => jid !== null);
159
+ let mgetDevices;
160
+ if (useCache && userDevicesCache.mget) {
161
+ const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
162
+ mgetDevices = await userDevicesCache.mget(usersToFetch);
163
+ }
164
+ for (const { jid, user } of jidsWithUser) {
153
165
  if (useCache) {
154
- const devices = userDevicesCache.get(user)
155
-
166
+ const devices = mgetDevices?.[user] ||
167
+ (userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)));
156
168
  if (devices) {
157
- deviceResults.push(...devices)
158
- logger.trace({ user }, 'using cache for devices')
169
+ const devicesWithJid = devices.map(d => ({
170
+ ...d,
171
+ jid: jidEncode(d.user, d.server, d.device)
172
+ }));
173
+ deviceResults.push(...devicesWithJid);
174
+ logger.trace({ user }, 'using cache for devices');
159
175
  }
160
-
161
176
  else {
162
- toFetch.push(jid)
177
+ toFetch.push(jid);
163
178
  }
164
179
  }
165
-
166
180
  else {
167
- toFetch.push(jid)
181
+ toFetch.push(jid);
168
182
  }
169
183
  }
170
-
171
184
  if (!toFetch.length) {
172
- return deviceResults
185
+ return deviceResults;
173
186
  }
174
-
175
- const query = new WAUSync_1.USyncQuery()
176
- .withContext('message')
177
- .withDeviceProtocol()
178
-
187
+ const requestedLidUsers = new Set();
188
+ for (const jid of toFetch) {
189
+ if (isLidUser(jid) || isHostedLidUser(jid)) {
190
+ const user = jidDecode(jid)?.user;
191
+ if (user)
192
+ requestedLidUsers.add(user);
193
+ }
194
+ }
195
+ const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
179
196
  for (const jid of toFetch) {
180
- query.withUser(new WAUSync_1.USyncUser().withId(jid))
197
+ 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
181
198
  }
182
-
183
- const result = await executeUSyncQuery(query)
184
-
199
+ const result = await sock.executeUSyncQuery(query);
185
200
  if (result) {
186
- const extracted = Utils_1.extractDeviceJids(result?.list, authState.creds.me.id, ignoreZeroDevices)
187
- const deviceMap = {}
188
-
201
+ // TODO: LID MAP this stuff (lid protocol will now return lid with devices)
202
+ const lidResults = result.list.filter(a => !!a.lid);
203
+ if (lidResults.length > 0) {
204
+ logger.trace('Storing LID maps from device call');
205
+ await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
206
+ // Force-refresh sessions for newly mapped LIDs to align identity addressing
207
+ try {
208
+ const lids = lidResults.map(a => a.lid);
209
+ if (lids.length) {
210
+ await assertSessions(lids, true);
211
+ }
212
+ }
213
+ catch (e) {
214
+ logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
215
+ }
216
+ }
217
+ const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
218
+ const deviceMap = {};
189
219
  for (const item of extracted) {
190
- deviceMap[item.user] = deviceMap[item.user] || []
191
- deviceMap[item.user].push(item)
192
- deviceResults.push(item)
220
+ deviceMap[item.user] = deviceMap[item.user] || [];
221
+ deviceMap[item.user]?.push(item);
193
222
  }
194
-
195
- for (const key in deviceMap) {
196
- userDevicesCache.set(key, deviceMap[key])
223
+ // Process each user's devices as a group for bulk LID migration
224
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
225
+ const isLidUser = requestedLidUsers.has(user);
226
+ // Process all devices for this user
227
+ for (const item of userDevices) {
228
+ const finalJid = isLidUser
229
+ ? jidEncode(user, item.server, item.device)
230
+ : jidEncode(item.user, item.server, item.device);
231
+ deviceResults.push({
232
+ ...item,
233
+ jid: finalJid
234
+ });
235
+ logger.debug({
236
+ user: item.user,
237
+ device: item.device,
238
+ finalJid,
239
+ usedLid: isLidUser
240
+ }, 'Processed device with LID priority');
241
+ }
242
+ }
243
+ await devicesMutex.mutex(async () => {
244
+ if (userDevicesCache.mset) {
245
+ // if the cache supports mset, we can set all devices in one go
246
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
247
+ }
248
+ else {
249
+ for (const key in deviceMap) {
250
+ if (deviceMap[key])
251
+ await userDevicesCache.set(key, deviceMap[key]);
252
+ }
253
+ }
254
+ });
255
+ const userDeviceUpdates = {};
256
+ for (const [userId, devices] of Object.entries(deviceMap)) {
257
+ if (devices && devices.length > 0) {
258
+ userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
259
+ }
260
+ }
261
+ if (Object.keys(userDeviceUpdates).length > 0) {
262
+ try {
263
+ await authState.keys.set({ 'device-list': userDeviceUpdates });
264
+ logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
265
+ }
266
+ catch (error) {
267
+ logger.warn({ error }, 'failed to store user device lists');
268
+ }
197
269
  }
198
270
  }
199
-
200
- return deviceResults
201
- }
271
+ return deviceResults;
272
+ };
273
+ /**
274
+ * Update Member Label
275
+ */
276
+ const updateMemberLabel = (jid, memberLabel) => {
277
+ return relayMessage(jid, {
278
+ protocolMessage: {
279
+ type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
280
+ memberLabel: {
281
+ label: memberLabel?.slice(0, 30),
282
+ labelTimestamp: unixTimestampSeconds()
283
+ }
284
+ }
285
+ }, {
286
+ additionalNodes: [
287
+ {
288
+ tag: 'meta',
289
+ attrs: {
290
+ tag_reason: 'user_update',
291
+ appdata: 'member_tag'
292
+ },
293
+ content: undefined
294
+ }
295
+ ]
296
+ });
297
+ };
202
298
  const assertSessions = async (jids, force) => {
203
299
  let didFetchNewSession = false;
204
- let jidsRequiringFetch = [];
205
- if (force) {
206
- jidsRequiringFetch = jids;
207
- }
208
- else {
209
- const addrs = jids.map(jid => (signalRepository
210
- .jidToSignalProtocolAddress(jid)));
211
- const sessions = await authState.keys.get('session', addrs);
212
- for (const jid of jids) {
213
- const signalId = signalRepository
214
- .jidToSignalProtocolAddress(jid);
215
- if (!sessions[signalId]) {
216
- jidsRequiringFetch.push(jid);
300
+ const uniqueJids = [...new Set(jids)];
301
+ const jidsRequiringFetch = [];
302
+ logger.debug({ jids }, 'assertSessions call with jids');
303
+ for (const jid of uniqueJids) {
304
+ if (!force) {
305
+ const sessionValidation = await signalRepository.validateSession(jid);
306
+ if (sessionValidation.exists) {
307
+ continue;
217
308
  }
218
309
  }
310
+ jidsRequiringFetch.push(jid);
219
311
  }
220
312
  if (jidsRequiringFetch.length) {
221
- logger.debug({ jidsRequiringFetch }, 'fetching sessions');
313
+ // LID if mapped, otherwise original
314
+ const wireJids = [
315
+ ...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
316
+ ...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
317
+ ];
318
+ logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
222
319
  const result = await query({
223
320
  tag: 'iq',
224
321
  attrs: {
225
322
  xmlns: 'encrypt',
226
323
  type: 'get',
227
- to: WABinary_1.S_WHATSAPP_NET,
324
+ to: S_WHATSAPP_NET
228
325
  },
229
326
  content: [
230
327
  {
231
328
  tag: 'key',
232
329
  attrs: {},
233
- content: jidsRequiringFetch.map(jid => ({
234
- tag: 'user',
235
- attrs: { jid },
236
- }))
330
+ content: wireJids.map(jid => {
331
+ const attrs = { jid };
332
+ if (force)
333
+ attrs.reason = 'identity';
334
+ return { tag: 'user', attrs };
335
+ })
237
336
  }
238
337
  ]
239
338
  });
240
- await (0, Utils_1.parseAndInjectE2ESessions)(result, signalRepository);
339
+ await parseAndInjectE2ESessions(result, signalRepository);
241
340
  didFetchNewSession = true;
242
341
  }
243
342
  return didFetchNewSession;
244
343
  };
245
-
246
-
247
344
  const sendPeerDataOperationMessage = async (pdoMessage) => {
345
+ //TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
248
346
  if (!authState.creds.me?.id) {
249
- throw new boom_1.Boom('Not authenticated')
347
+ throw new Boom('Not authenticated');
250
348
  }
251
-
252
349
  const protocolMessage = {
253
350
  protocolMessage: {
254
351
  peerDataOperationRequestMessage: pdoMessage,
255
- type: WAProto_1.proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
352
+ type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
256
353
  }
257
354
  };
258
- const meJid = WABinary_1.jidNormalizedUser(authState.creds.me.id);
355
+ const meJid = jidNormalizedUser(authState.creds.me.id);
259
356
  const msgId = await relayMessage(meJid, protocolMessage, {
260
357
  additionalAttributes: {
261
358
  category: 'peer',
262
- // eslint-disable-next-line camelcase
263
- push_priority: 'high_force',
359
+ push_priority: 'high_force'
264
360
  },
361
+ additionalNodes: [
362
+ {
363
+ tag: 'meta',
364
+ attrs: { appdata: 'default' }
365
+ }
366
+ ]
265
367
  });
266
368
  return msgId;
267
369
  };
268
- const createParticipantNodes = async (jids, message, extraAttrs) => {
269
- const patched = await patchMessageBeforeSending(message, jids);
270
- const bytes = (0, Utils_1.encodeWAMessage)(patched);
370
+ const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
371
+ if (!recipientJids.length) {
372
+ return { nodes: [], shouldIncludeDeviceIdentity: false };
373
+ }
374
+ const patched = await patchMessageBeforeSending(message, recipientJids);
375
+ const patchedMessages = Array.isArray(patched)
376
+ ? patched
377
+ : recipientJids.map(jid => ({ recipientJid: jid, message: patched }));
271
378
  let shouldIncludeDeviceIdentity = false;
272
- const nodes = await Promise.all(jids.map(async (jid) => {
273
- const { type, ciphertext } = await signalRepository
274
- .encryptMessage({ jid, data: bytes });
275
- if (type === 'pkmsg') {
276
- shouldIncludeDeviceIdentity = true;
379
+ const meId = authState.creds.me.id;
380
+ const meLid = authState.creds.me?.lid;
381
+ const meLidUser = meLid ? jidDecode(meLid)?.user : null;
382
+ const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
383
+ try {
384
+ if (!jid)
385
+ return null;
386
+ let msgToEncrypt = patchedMessage;
387
+ if (dsmMessage) {
388
+ const { user: targetUser } = jidDecode(jid);
389
+ const { user: ownPnUser } = jidDecode(meId);
390
+ const ownLidUser = meLidUser;
391
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
392
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
393
+ if (isOwnUser && !isExactSenderDevice) {
394
+ msgToEncrypt = dsmMessage;
395
+ logger.debug({ jid, targetUser }, 'Using DSM for own device');
396
+ }
397
+ }
398
+ const bytes = encodeWAMessage(msgToEncrypt);
399
+ const mutexKey = jid;
400
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
401
+ const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
402
+ if (type === 'pkmsg') {
403
+ shouldIncludeDeviceIdentity = true;
404
+ }
405
+ return {
406
+ tag: 'to',
407
+ attrs: { jid },
408
+ content: [
409
+ {
410
+ tag: 'enc',
411
+ attrs: { v: '2', type, ...(extraAttrs || {}) },
412
+ content: ciphertext
413
+ }
414
+ ]
415
+ };
416
+ });
417
+ return node;
277
418
  }
278
- const node = {
279
- tag: 'to',
280
- attrs: { jid },
281
- content: [{
282
- tag: 'enc',
283
- attrs: {
284
- v: '2',
285
- type,
286
- ...extraAttrs || {}
287
- },
288
- content: ciphertext
289
- }]
290
- };
291
- return node;
292
- }));
419
+ catch (err) {
420
+ logger.error({ jid, err }, 'Failed to encrypt for recipient');
421
+ return null;
422
+ }
423
+ });
424
+ const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
425
+ if (recipientJids.length > 0 && nodes.length === 0) {
426
+ throw new Boom('All encryptions failed', { statusCode: 500 });
427
+ }
293
428
  return { nodes, shouldIncludeDeviceIdentity };
294
429
  };
295
-
296
- const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, cachedGroupMetadata, useCachedGroupMetadata, statusJidList, AI = true }) => {
297
- const meId = authState.creds.me.id;
298
- let shouldIncludeDeviceIdentity = false;
299
- let didPushAdditional = false
300
- const { user, server } = WABinary_1.jidDecode(jid);
430
+ const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
431
+ const meId = assertMeId(authState.creds);
432
+ const meLid = authState.creds.me?.lid;
433
+ const isRetryResend = Boolean(participant?.jid);
434
+ let shouldIncludeDeviceIdentity = isRetryResend;
301
435
  const statusJid = 'status@broadcast';
436
+ const { user, server } = jidDecode(jid);
302
437
  const isGroup = server === 'g.us';
303
438
  const isStatus = jid === statusJid;
304
439
  const isLid = server === 'lid';
305
- const isPrivate = server === 's.whatsapp.net'
306
440
  const isNewsletter = server === 'newsletter';
307
- msgId = msgId || (0, Utils_1.generateMessageID)();
441
+ const isGroupOrStatus = isGroup || isStatus;
442
+ const finalJid = jid;
443
+ msgId = msgId || generateMessageIDV2(meId);
308
444
  useUserDevicesCache = useUserDevicesCache !== false;
309
- useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus
445
+ useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
310
446
  const participants = [];
311
- const destinationJid = (!isStatus) ? WABinary_1.jidEncode(user, isLid ? 'lid' : isGroup ? 'g.us' : isNewsletter ? 'newsletter' : 's.whatsapp.net') : statusJid;
447
+ const destinationJid = !isStatus ? finalJid : statusJid;
312
448
  const binaryNodeContent = [];
313
449
  const devices = [];
450
+ const buttonType = getButtonType(message);
451
+ let didPushAdditional = false;
452
+ let reportingMessage;
314
453
  const meMsg = {
315
454
  deviceSentMessage: {
316
455
  destinationJid,
317
456
  message
318
- }
457
+ },
458
+ messageContextInfo: message.messageContextInfo
319
459
  };
320
- const extraAttrs = {}
321
- const messages = Utils_1.normalizeMessageContent(message)
322
- const buttonType = getButtonType(messages);
460
+ const extraAttrs = {};
323
461
  if (participant) {
324
- // when the retry request is not for a group
325
- // only send to the specific device that asked for a retry
326
- // otherwise the message is sent out to every device that should be a recipient
327
462
  if (!isGroup && !isStatus) {
328
- additionalAttributes = { ...additionalAttributes, 'device_fanout': 'false' };
463
+ additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
329
464
  }
330
- const { user, device } = WABinary_1.jidDecode(participant.jid);
331
- devices.push({ user, device });
465
+ const { user, device } = jidDecode(participant.jid);
466
+ devices.push({
467
+ user,
468
+ device,
469
+ jid: participant.jid
470
+ });
332
471
  }
333
472
  await authState.keys.transaction(async () => {
334
- const mediaType = getMediaType(messages);
335
-
473
+ const mediaType = getMediaType(message);
336
474
  if (mediaType) {
337
- extraAttrs['mediatype'] = mediaType
475
+ extraAttrs['mediatype'] = mediaType;
476
+ }
477
+ if (isNewsletter) {
478
+ const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
479
+ const bytes = encodeNewsletterMessage(patched);
480
+ binaryNodeContent.push({
481
+ tag: 'plaintext',
482
+ attrs: {},
483
+ content: bytes
484
+ });
485
+ const stanza = {
486
+ tag: 'message',
487
+ attrs: {
488
+ to: jid,
489
+ id: msgId,
490
+ type: getMessageType(message),
491
+ ...(additionalAttributes || {})
492
+ },
493
+ content: binaryNodeContent
494
+ };
495
+ logger.debug({ msgId }, `sending newsletter message to ${jid}`);
496
+ await sendNode(stanza);
497
+ return;
338
498
  }
339
-
340
- if (messages.pinInChatMessage || messages.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage) {
341
- extraAttrs['decrypt-fail'] = 'hide'
342
- }
343
-
344
- if (messages.interactiveResponseMessage?.nativeFlowResponseMessage) {
345
- extraAttrs['native_flow_name'] = messages.interactiveResponseMessage?.nativeFlowResponseMessage.name
499
+ if (normalizeMessageContent(message)?.pinInChatMessage || normalizeMessageContent(message)?.reactionMessage) {
500
+ extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
346
501
  }
347
-
348
- if (isGroup || isStatus) {
502
+ if (isGroupOrStatus && !isRetryResend) {
349
503
  const [groupData, senderKeyMap] = await Promise.all([
350
504
  (async () => {
351
- let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
352
- if (groupData) {
505
+ 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?
506
+ if (groupData && Array.isArray(groupData?.participants)) {
353
507
  logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata');
354
508
  }
355
-
356
509
  else if (!isStatus) {
357
- groupData = await groupMetadata(jid)
510
+ groupData = await groupMetadata(jid); // TODO: start storing group participant list + addr mode in Signal & stop relying on this
358
511
  }
359
-
360
512
  return groupData;
361
513
  })(),
362
514
  (async () => {
363
515
  if (!participant && !isStatus) {
364
- const result = await authState.keys.get('sender-key-memory', [jid])
365
- return result[jid] || {}
516
+ // what if sender memory is less accurate than the cached metadata
517
+ // on participant change in group, we should do sender memory manipulation
518
+ 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?
519
+ return result[jid] || {};
366
520
  }
367
-
368
- return {}
369
-
370
- })()
521
+ return {};
522
+ })()
371
523
  ]);
372
- if (!participant) {
373
- const participantsList = (groupData && !isStatus) ? groupData.participants.map(p => p.id) : []
374
-
375
- if (isStatus && statusJidList) {
376
- participantsList.push(...statusJidList)
377
- }
378
-
379
- // if (!isStatus) {
380
- // const expiration = await getEphemeralGroup(jid)
381
- // additionalAttributes = {
382
- // ...additionalAttributes,
383
- // addressing_mode: 'pn',
384
- // ...expiration ? { expiration: expiration.toString() } : null
385
- // }
386
- // }
387
-
388
- const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
389
- devices.push(...additionalDevices)
524
+ const participantsList = groupData ? groupData.participants.map(p => p.id) : [];
525
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
526
+ additionalAttributes = {
527
+ ...additionalAttributes,
528
+ expiration: groupData.ephemeralDuration.toString()
529
+ };
390
530
  }
391
-
392
- const patched = await patchMessageBeforeSending(message, devices.map(d => WABinary_1.jidEncode(d.user, isLid ? 'lid' : 's.whatsapp.net', d.device)));
393
- const bytes = Utils_1.encodeWAMessage(patched);
394
-
531
+ if (isStatus && statusJidList) {
532
+ participantsList.push(...statusJidList);
533
+ }
534
+ const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
535
+ devices.push(...additionalDevices);
536
+ if (isGroup) {
537
+ additionalAttributes = {
538
+ ...additionalAttributes,
539
+ addressing_mode: groupData?.addressingMode || 'lid'
540
+ };
541
+ }
542
+ const patched = await patchMessageBeforeSending(message);
543
+ if (Array.isArray(patched)) {
544
+ throw new Boom('Per-jid patching is not supported in groups');
545
+ }
546
+ const bytes = encodeWAMessage(patched);
547
+ reportingMessage = patched;
548
+ const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid';
549
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
395
550
  const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
396
551
  group: destinationJid,
397
552
  data: bytes,
398
- meId,
553
+ meId: groupSenderIdentity
399
554
  });
400
- const senderKeyJids = [];
401
-
402
- for (const { user, device } of devices) {
403
- const jid = WABinary_1.jidEncode(user, (groupData === null || groupData === void 0 ? void 0 : groupData.addressingMode) === 'lid' ? 'lid' : 's.whatsapp.net', device);
404
- if (!senderKeyMap[jid] || !!participant) {
405
- senderKeyJids.push(jid);
406
- // store that this person has had the sender keys sent to them
407
- senderKeyMap[jid] = true;
555
+ const senderKeyRecipients = [];
556
+ for (const device of devices) {
557
+ const deviceJid = device.jid;
558
+ const hasKey = !!senderKeyMap[deviceJid];
559
+ if ((!hasKey || !!participant) &&
560
+ !isHostedLidUser(deviceJid) &&
561
+ !isHostedPnUser(deviceJid) &&
562
+ device.device !== 99) {
563
+ //todo: revamp all this logic
564
+ // 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
565
+ senderKeyRecipients.push(deviceJid);
566
+ senderKeyMap[deviceJid] = true;
408
567
  }
409
568
  }
410
- // if there are some participants with whom the session has not been established
411
- // if there are, we re-send the senderkey
412
- if (senderKeyJids.length) {
413
- logger.debug({ senderKeyJids }, 'sending new sender key');
569
+ if (senderKeyRecipients.length) {
570
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key');
414
571
  const senderKeyMsg = {
415
572
  senderKeyDistributionMessage: {
416
573
  axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
417
574
  groupId: destinationJid
418
575
  }
419
576
  };
420
- await assertSessions(senderKeyJids, false);
421
- const result = await createParticipantNodes(senderKeyJids, senderKeyMsg, extraAttrs)
577
+ const senderKeySessionTargets = senderKeyRecipients;
578
+ await assertSessions(senderKeySessionTargets);
579
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs);
422
580
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
423
581
  participants.push(...result.nodes);
424
582
  }
@@ -429,104 +587,177 @@ const makeMessagesSocket = (config) => {
429
587
  });
430
588
  await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
431
589
  }
432
- else if (isNewsletter) {
433
- // Message edit
434
- if (message.protocolMessage?.editedMessage) {
435
- msgId = message.protocolMessage.key?.id
436
- message = message.protocolMessage.editedMessage
590
+ else {
591
+ // ADDRESSING CONSISTENCY: Match own identity to conversation context
592
+ // TODO: investigate if this is true
593
+ let ownId = meId;
594
+ if (isLid && meLid) {
595
+ ownId = meLid;
596
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation');
437
597
  }
438
-
439
- // Message delete
440
- if (message.protocolMessage?.type === WAProto_1.proto.Message.ProtocolMessage.Type.REVOKE) {
441
- msgId = message.protocolMessage.key?.id
442
- message = {}
598
+ else {
599
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
443
600
  }
444
-
445
- const patched = await patchMessageBeforeSending(message, [])
446
- const bytes = Utils_1.encodeNewsletterMessage(patched)
447
-
448
- binaryNodeContent.push({
449
- tag: 'plaintext',
450
- attrs: extraAttrs ? extraAttrs : {},
451
- content: bytes
452
- })
453
- }
454
- else {
455
- const { user: meUser } = WABinary_1.jidDecode(meId);
601
+ const { user: ownUser } = jidDecode(ownId);
456
602
  if (!participant) {
457
- devices.push({ user })
458
- if (user !== meUser) {
459
- devices.push({ user: meUser })
603
+ const patchedForReporting = await patchMessageBeforeSending(message, [jid]);
604
+ reportingMessage = Array.isArray(patchedForReporting)
605
+ ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
606
+ : patchedForReporting;
607
+ }
608
+ if (!isRetryResend) {
609
+ const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
610
+ devices.push({
611
+ user,
612
+ device: 0,
613
+ jid: jidEncode(user, targetUserServer, 0) // rajeh, todo: this entire logic is convoluted and weird.
614
+ });
615
+ if (user !== ownUser) {
616
+ const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
617
+ const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
618
+ devices.push({
619
+ user: ownUserForAddressing,
620
+ device: 0,
621
+ jid: jidEncode(ownUserForAddressing, ownUserServer, 0)
622
+ });
460
623
  }
461
-
462
624
  if (additionalAttributes?.['category'] !== 'peer') {
463
- const additionalDevices = await getUSyncDevices([meId, jid], !!useUserDevicesCache, true)
464
-
465
- devices.push(...additionalDevices)
625
+ // Clear placeholders and enumerate actual devices
626
+ devices.length = 0;
627
+ // Use conversation-appropriate sender identity
628
+ const senderIdentity = isLid && meLid
629
+ ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
630
+ : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
631
+ // Enumerate devices for sender and target with consistent addressing
632
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
633
+ devices.push(...sessionDevices);
634
+ logger.debug({
635
+ deviceCount: devices.length,
636
+ devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`)
637
+ }, 'Device enumeration complete with unified addressing');
466
638
  }
467
639
  }
468
- const allJids = [];
469
- const meJids = [];
470
- const otherJids = [];
471
- for (const { user, device } of devices) {
472
- const isMe = user === meUser
473
- const jid = WABinary_1.jidEncode(isMe && isLid ? authState.creds?.me?.lid?.split(':')[0] || user : user, isLid ? 'lid' : 's.whatsapp.net', device)
474
-
640
+ const allRecipients = [];
641
+ const meRecipients = [];
642
+ const otherRecipients = [];
643
+ const { user: mePnUser } = jidDecode(meId);
644
+ const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
645
+ for (const { user, jid } of devices) {
646
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
647
+ if (isExactSenderDevice) {
648
+ logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
649
+ continue;
650
+ }
651
+ // Check if this is our device (could match either PN or LID user)
652
+ const isMe = user === mePnUser || user === meLidUser;
475
653
  if (isMe) {
476
- meJids.push(jid)
654
+ meRecipients.push(jid);
477
655
  }
478
-
479
656
  else {
480
- otherJids.push(jid)
657
+ otherRecipients.push(jid);
481
658
  }
482
-
483
- allJids.push(jid)
659
+ allRecipients.push(jid);
484
660
  }
485
- await assertSessions(allJids, false);
661
+ await assertSessions(allRecipients);
486
662
  const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
487
- createParticipantNodes(meJids, meMsg, extraAttrs),
488
- createParticipantNodes(otherJids, message, extraAttrs)
489
- ])
663
+ // For own devices: use DSM if available (1:1 chats only)
664
+ createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
665
+ createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
666
+ ]);
490
667
  participants.push(...meNodes);
491
668
  participants.push(...otherNodes);
669
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
670
+ extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]);
671
+ }
492
672
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
493
673
  }
674
+ if (isRetryResend) {
675
+ const isParticipantLid = isLidUser(participant.jid);
676
+ const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId);
677
+ let messageToSend = message;
678
+ if (isGroupOrStatus) {
679
+ let groupSenderIdentity;
680
+ if (meLid && (await signalRepository.hasSenderKey({ group: destinationJid, meId: meLid }))) {
681
+ groupSenderIdentity = meLid;
682
+ }
683
+ else if (await signalRepository.hasSenderKey({ group: destinationJid, meId })) {
684
+ groupSenderIdentity = meId;
685
+ }
686
+ if (groupSenderIdentity) {
687
+ try {
688
+ const skdm = await signalRepository.getSenderKeyDistributionMessage({
689
+ group: destinationJid,
690
+ meId: groupSenderIdentity
691
+ });
692
+ messageToSend = {
693
+ ...message,
694
+ senderKeyDistributionMessage: {
695
+ groupId: destinationJid,
696
+ axolotlSenderKeyDistributionMessage: skdm
697
+ }
698
+ };
699
+ }
700
+ catch (err) {
701
+ logger.warn({ err, jid: destinationJid }, 'failed to build SKDM for retry, sending without it');
702
+ }
703
+ }
704
+ }
705
+ const encodedMessageToSend = isMe
706
+ ? encodeWAMessage({
707
+ deviceSentMessage: {
708
+ destinationJid,
709
+ message: messageToSend
710
+ }
711
+ })
712
+ : encodeWAMessage(messageToSend);
713
+ const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
714
+ data: encodedMessageToSend,
715
+ jid: participant.jid
716
+ });
717
+ binaryNodeContent.push({
718
+ tag: 'enc',
719
+ attrs: {
720
+ v: '2',
721
+ type,
722
+ count: participant.count.toString()
723
+ },
724
+ content: encryptedContent
725
+ });
726
+ }
494
727
  if (participants.length) {
495
728
  if (additionalAttributes?.['category'] === 'peer') {
496
- const peerNode = participants[0]?.content?.[0]
497
-
729
+ const peerNode = participants[0]?.content?.[0];
498
730
  if (peerNode) {
499
- binaryNodeContent.push(peerNode) // push only enc
731
+ binaryNodeContent.push(peerNode); // push only enc
500
732
  }
501
733
  }
502
-
503
734
  else {
504
735
  binaryNodeContent.push({
505
736
  tag: 'participants',
506
737
  attrs: {},
507
738
  content: participants
508
- })
739
+ });
509
740
  }
510
741
  }
511
-
512
742
  const stanza = {
513
743
  tag: 'message',
514
744
  attrs: {
515
745
  id: msgId,
516
- type: getTypeMessage(messages),
746
+ to: destinationJid,
747
+ type: getMessageType(message),
517
748
  ...(additionalAttributes || {})
518
749
  },
519
750
  content: binaryNodeContent
520
- }
751
+ };
521
752
  // if the participant to send to is explicitly specified (generally retry recp)
522
753
  // ensure the message is only sent to that person
523
754
  // if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg
524
755
  if (participant) {
525
- if (WABinary_1.isJidGroup(destinationJid)) {
756
+ if (isJidGroup(destinationJid)) {
526
757
  stanza.attrs.to = destinationJid;
527
758
  stanza.attrs.participant = participant.jid;
528
759
  }
529
- else if (WABinary_1.areJidsSameUser(participant.jid, meId)) {
760
+ else if (areJidsSameUser(participant.jid, meId)) {
530
761
  stanza.attrs.to = participant.jid;
531
762
  stanza.attrs.recipient = destinationJid;
532
763
  }
@@ -538,170 +769,221 @@ const makeMessagesSocket = (config) => {
538
769
  stanza.attrs.to = destinationJid;
539
770
  }
540
771
  if (shouldIncludeDeviceIdentity) {
772
+ ;
541
773
  stanza.content.push({
542
774
  tag: 'device-identity',
543
775
  attrs: {},
544
- content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
776
+ content: encodeSignedDeviceIdentity(authState.creds.account, true)
545
777
  });
546
778
  logger.debug({ jid }, 'adding device identity');
547
779
  }
548
-
549
- if (AI && isPrivate) {
780
+ if (!isNewsletter && buttonType && !isStatus) {
781
+ const content = getAdditionalNode(buttonType)
782
+ const filteredNode = getBinaryNodeFilter(additionalNodes)
783
+
784
+ if (filteredNode) {
785
+ didPushAdditional = true
786
+ stanza.content.push(...additionalNodes)
787
+ }
788
+ else {
789
+ stanza.content.push(...content)
790
+ }
791
+ logger.debug({ jid }, 'adding business node')
792
+ }
793
+ if (!isNewsletter && !isGroupOrStatus && aiLabel) {
550
794
  const botNode = {
551
795
  tag: 'bot',
552
796
  attrs: {
553
797
  biz_bot: '1'
554
798
  }
555
799
  }
556
-
557
- const filteredBizBot = WABinary_1.getBinaryNodeFilter(additionalNodes ? additionalNodes : [])
558
-
800
+ const filteredBizBot = getBinaryNodeFilter(additionalNodes ? additionalNodes : [])
559
801
  if (filteredBizBot) {
560
802
  stanza.content.push(...additionalNodes)
561
803
  didPushAdditional = true
562
804
  }
563
-
564
805
  else {
565
806
  stanza.content.push(botNode)
566
807
  }
567
808
  }
568
-
569
- if(!isNewsletter && buttonType && !isStatus) {
570
- const content = WABinary_1.getAdditionalNode(buttonType)
571
- const filteredNode = WABinary_1.getBinaryNodeFilter(additionalNodes)
572
-
573
- if (filteredNode) {
574
- didPushAdditional = true
575
- stanza.content.push(...additionalNodes)
576
- }
577
- else {
578
- stanza.content.push(...content)
809
+ if (!isNewsletter &&
810
+ !isRetryResend &&
811
+ reportingMessage?.messageContextInfo?.messageSecret &&
812
+ shouldIncludeReportingToken(reportingMessage)) {
813
+ try {
814
+ const encoded = encodeWAMessage(reportingMessage);
815
+ const reportingKey = {
816
+ id: msgId,
817
+ fromMe: true,
818
+ remoteJid: destinationJid,
819
+ participant: participant?.jid
820
+ };
821
+ const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
822
+ if (reportingNode) {
823
+ ;
824
+ stanza.content.push(reportingNode);
825
+ logger.trace({ jid }, 'added reporting token to message');
826
+ }
579
827
  }
580
- logger.debug({ jid }, 'adding business node')
581
- }
582
-
828
+ catch (error) {
829
+ logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
830
+ }
831
+ }
832
+ // WA Web never attaches tctoken to peer (AppStateSync) messages — server rejects with 479
833
+ const isPeerMessage = additionalAttributes?.['category'] === 'peer';
834
+ const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
835
+ // Resolve destination to LID for tctoken storage — matches Signal session key pattern
836
+ const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
837
+ const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
838
+ const existingTokenEntry = contactTcTokenData[tcTokenJid];
839
+ let tcTokenBuffer = existingTokenEntry?.token;
840
+ // Treat expired tokens the same as missing — clear from cache
841
+ if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
842
+ logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing');
843
+ tcTokenBuffer = undefined;
844
+ // Preserve senderTimestamp so the fire-and-forget issuance dedupe survives cleanup.
845
+ const cleared = existingTokenEntry?.senderTimestamp !== undefined
846
+ ? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
847
+ : null;
848
+ try {
849
+ await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } });
850
+ }
851
+ catch (err) {
852
+ logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup');
853
+ }
854
+ }
855
+ if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
856
+ ;
857
+ stanza.content.push({
858
+ tag: 'tctoken',
859
+ attrs: {},
860
+ content: tcTokenBuffer
861
+ });
862
+ }
583
863
  if (!didPushAdditional && additionalNodes && additionalNodes.length > 0) {
864
+ ;
584
865
  stanza.content.push(...additionalNodes);
585
866
  }
586
-
587
867
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
588
868
  await sendNode(stanza);
589
- });
869
+ // Fire-and-forget: issue our token to the contact AFTER message send.
870
+ // WA Web skips protocol messages and PSA/bot contacts (TcTokenChatAction: isRegularUser)
871
+ const isProtocolMsg = !!normalizeMessageContent(message)?.protocolMessage;
872
+ const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
873
+ if (is1on1Send &&
874
+ !isProtocolMsg &&
875
+ !isBotOrPSA &&
876
+ shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&
877
+ !inFlightTcTokenIssuance.has(tcTokenJid)) {
878
+ inFlightTcTokenIssuance.add(tcTokenJid);
879
+ const issueTimestamp = unixTimestampSeconds();
880
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
881
+ resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
882
+ .then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
883
+ .then(async (result) => {
884
+ await storeTcTokensFromIqResult({
885
+ result,
886
+ fallbackJid: tcTokenJid,
887
+ keys: authState.keys,
888
+ getLIDForPN
889
+ });
890
+ const currentData = await authState.keys.get('tctoken', [tcTokenJid]);
891
+ const currentEntry = currentData[tcTokenJid];
892
+ const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
893
+ await authState.keys.set({
894
+ tctoken: {
895
+ [tcTokenJid]: {
896
+ token: Buffer.alloc(0),
897
+ ...currentEntry,
898
+ senderTimestamp: issueTimestamp
899
+ },
900
+ ...indexWrite
901
+ }
902
+ });
903
+ })
904
+ .catch(err => {
905
+ logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed');
906
+ })
907
+ .finally(() => {
908
+ inFlightTcTokenIssuance.delete(tcTokenJid);
909
+ });
910
+ }
911
+ // Add message to retry cache if enabled
912
+ if (messageRetryManager && !participant) {
913
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message);
914
+ }
915
+ }, meId);
590
916
  return msgId;
591
917
  };
592
-
593
- const sendMessageMembers = async (jid, message, options = {}) => {
594
- const {
595
- messageId: idm,
596
- quoted,
597
- delayMs = 1500,
598
- useUserDevicesCache = true,
599
- cachedGroupMetadata
600
- } = options;
601
-
602
- const { server } = WABinary_1.jidDecode(jid);
603
- if (server !== "g.us") throw new Error("@g.us server required");
604
-
605
- const meId = authState.creds.me.id;
606
- const messages = Utils_1.normalizeMessageContent(message);
607
- const groupData = cachedGroupMetadata? await cachedGroupMetadata(jid) : await groupMetadata(jid);
608
- const isLid = groupData.addressingMode === "lid";
609
- const participantJids = groupData.participants.map(p => p.id);
610
-
611
- logger.info(`Sending message to ${participantJids.length} members from ${jid}`);
612
-
613
- for (let i = 0; i < participantJids.length; i++) {
614
- const jid = participantJids[i];
615
- if (WABinary_1.areJidsSameUser(jid, meId)) continue;
616
-
617
- try {
618
- const msgId = `${idm || Utils_1.generateMessageID()}_${i}`;
619
- const fullMsg = await Utils_1.generateWAMessageFromContent(jid, message, {
620
- messageId: msgId,
621
- quoted
622
- })
623
- await relayMessage(jid, fullMsg.message, {
624
- messageId: fullMsg.key.id
625
- });
626
-
627
- logger.debug(`Message successfully sent to ${jid}`);
628
- if (delayMs && i < participantJids.length - 1) {
629
- await new Promise(z => setTimeout(z, delayMs));
630
- }
631
- } catch (e) {
632
- logger.error({ jid, e }, "Error sending message to");
918
+ const getMessageType = (message) => {
919
+ const normalizedMessage = normalizeMessageContent(message);
920
+ if (!normalizedMessage)
921
+ return 'text';
922
+ if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
923
+ return 'reaction';
633
924
  }
634
- }
635
- return JSON.stringify({
636
- members_total: participantJids.length,
637
- message
638
- }, null, 4);
639
- };
640
-
641
- const getTypeMessage = (msg) => {
642
- const message = Utils_1.normalizeMessageContent(msg)
643
- if (message.reactionMessage) {
644
- return 'reaction'
645
- }
646
- else if (getMediaType(message)) {
647
- return 'media'
648
- }
649
- else {
650
- return 'text'
925
+ if (normalizedMessage.pollCreationMessage ||
926
+ normalizedMessage.pollCreationMessageV2 ||
927
+ normalizedMessage.pollCreationMessageV3 ||
928
+ normalizedMessage.pollUpdateMessage) {
929
+ return 'poll';
651
930
  }
652
- }
653
-
931
+ if (normalizedMessage.eventMessage) {
932
+ return 'event';
933
+ }
934
+ if (getMediaType(normalizedMessage) !== '') {
935
+ return 'media';
936
+ }
937
+ return 'text';
938
+ };
654
939
  const getMediaType = (message) => {
655
940
  if (message.imageMessage) {
656
- return 'image'
941
+ return 'image';
657
942
  }
658
943
  else if (message.videoMessage) {
659
- return message.videoMessage.gifPlayback ? 'gif' : 'video'
944
+ return message.videoMessage.gifPlayback ? 'gif' : 'video';
660
945
  }
661
946
  else if (message.audioMessage) {
662
- return message.audioMessage.ptt ? 'ptt' : 'audio'
947
+ return message.audioMessage.ptt ? 'ptt' : 'audio';
663
948
  }
664
949
  else if (message.contactMessage) {
665
- return 'vcard'
950
+ return 'vcard';
666
951
  }
667
952
  else if (message.documentMessage) {
668
- return 'document'
953
+ return 'document';
669
954
  }
670
955
  else if (message.contactsArrayMessage) {
671
- return 'contact_array'
956
+ return 'contact_array';
672
957
  }
673
958
  else if (message.liveLocationMessage) {
674
- return 'livelocation'
959
+ return 'livelocation';
675
960
  }
676
961
  else if (message.stickerMessage) {
677
- return 'sticker'
962
+ return 'sticker';
678
963
  }
679
964
  else if (message.listMessage) {
680
- return 'list'
965
+ return 'list';
681
966
  }
682
967
  else if (message.listResponseMessage) {
683
- return 'list_response'
968
+ return 'list_response';
684
969
  }
685
970
  else if (message.buttonsResponseMessage) {
686
- return 'buttons_response'
971
+ return 'buttons_response';
687
972
  }
688
973
  else if (message.orderMessage) {
689
- return 'order'
974
+ return 'order';
690
975
  }
691
976
  else if (message.productMessage) {
692
- return 'product'
977
+ return 'product';
693
978
  }
694
979
  else if (message.interactiveResponseMessage) {
695
- return 'native_flow_response'
980
+ return 'native_flow_response';
696
981
  }
697
982
  else if (message.groupInviteMessage) {
698
- return 'url'
983
+ return 'url';
699
984
  }
700
- else if (/https:\/\/wa\.me\/p\/\d+\/\d+/.test(message.extendedTextMessage?.text)) {
701
- return 'productlink'
702
- }
703
- }
704
-
985
+ return '';
986
+ };
705
987
  const getButtonType = (message) => {
706
988
  if (message.listMessage) {
707
989
  return 'list'
@@ -731,13 +1013,13 @@ const makeMessagesSocket = (config) => {
731
1013
  else if (message.interactiveMessage?.nativeFlowMessage) {
732
1014
  return 'native_flow'
733
1015
  }
734
- }
735
- const getPrivacyTokens = async (jids) => {
736
- const t = Utils_1.unixTimestampSeconds().toString();
1016
+ };
1017
+ const issuePrivacyTokens = async (jids, timestamp) => {
1018
+ const t = (timestamp ?? unixTimestampSeconds()).toString();
737
1019
  const result = await query({
738
1020
  tag: 'iq',
739
1021
  attrs: {
740
- to: WABinary_1.S_WHATSAPP_NET,
1022
+ to: S_WHATSAPP_NET,
741
1023
  type: 'set',
742
1024
  xmlns: 'privacy'
743
1025
  },
@@ -748,7 +1030,7 @@ const makeMessagesSocket = (config) => {
748
1030
  content: jids.map(jid => ({
749
1031
  tag: 'token',
750
1032
  attrs: {
751
- jid: WABinary_1.jidNormalizedUser(jid),
1033
+ jid: jidNormalizedUser(jid),
752
1034
  t,
753
1035
  type: 'trusted_contact'
754
1036
  }
@@ -757,158 +1039,47 @@ const makeMessagesSocket = (config) => {
757
1039
  ]
758
1040
  });
759
1041
  return result;
760
- }
761
- const waUploadToServer = (0, Utils_1.getWAUploadToServer)(config, refreshMediaConn);
762
- const luki = new imup(Utils_1, waUploadToServer, relayMessage, authState);
763
- const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update');
764
- const sendMessage = async (jid, content, options = {}) => {
765
- const userJid = authState.creds.me.id;
766
- delete options.ephemeralExpiration
767
- const { ptcp = false, quoted } = options;
768
- const getParticipantAttr = () => ptcp ? { participant: { jid } } : {};
769
- const messageType = luki.detectType(content);
770
- if (typeof content === 'object' && 'disappearingMessagesInChat' in content &&
771
- typeof content['disappearingMessagesInChat'] !== 'undefined' && WABinary_1.isJidGroup(jid)) {
772
- const { disappearingMessagesInChat } = content
773
-
774
- const value = typeof disappearingMessagesInChat === 'boolean' ?
775
- (disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) :
776
- disappearingMessagesInChat
777
-
778
- await groupToggleEphemeral(jid, value)
779
- }
780
-
781
- else {
782
- let mediaHandle
783
-
784
-
785
- if (messageType) {
786
- switch(messageType) {
787
- case 'PAYMENT':
788
- const paymentContent = await luki.handlePayment(content, quoted);
789
- return await relayMessage(jid, paymentContent, {
790
- messageId: Utils_1.generateMessageID(),
791
- ...getParticipantAttr()
792
- });
793
-
794
- case 'PRODUCT':
795
- const productContent = await luki.handleProduct(content, jid, quoted);
796
- const productMsg = await Utils_1.generateWAMessageFromContent(jid, productContent, { quoted });
797
- return await relayMessage(jid, productMsg.message, {
798
- messageId: productMsg.key.id,
799
- ...getParticipantAttr()
800
- });
801
-
802
- case 'INTERACTIVE':
803
- const interactiveContent = await luki.handleInteractive(content, jid, quoted);
804
- const interactiveMsg = await Utils_1.generateWAMessageFromContent(jid, interactiveContent, { quoted });
805
- return await relayMessage(jid, interactiveMsg.message, {
806
- messageId: interactiveMsg.key.id,
807
- ...getParticipantAttr()
808
- });
809
-
810
- case 'ALBUM':
811
- return await luki.handleAlbum(content, jid, quoted)
812
- case 'EVENT':
813
- return await luki.handleEvent(content, jid, quoted)
814
- case 'POLL_RESULT':
815
- return await luki.handlePollResult(content, jid, quoted)
816
- case 'ORDER':
817
- return await luki.handleOrderMessage(content, jid, quoted)
818
- case 'GROUP_STATUS':
819
- return await luki.handleGroupStory(content, jid, quoted)
820
- case 'GROUP_LABEL':
821
- return await luki.handleGbLabel(content, jid)
822
- }
823
- }
824
- const fullMsg = await Utils_1.generateWAMessage(jid, content, {
825
- logger,
826
- userJid,
827
- quoted,
828
- getUrlInfo: text => link_preview_1.getUrlInfo(text, {
829
- thumbnailWidth: linkPreviewImageThumbnailWidth,
830
- fetchOpts: {
831
- timeout: 3000,
832
- ...axiosOptions || {}
833
- },
834
- logger,
835
- uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
836
- }),
837
- upload: async (readStream, opts) => {
838
- const up = await waUploadToServer(readStream, {
839
- ...opts,
840
- newsletter: WABinary_1.isJidNewsLetter(jid)
841
- });
842
- return up;
843
- },
844
- mediaCache: config.mediaCache,
845
- options: config.options,
846
- ...options
847
- });
848
-
849
- const isDeleteMsg = 'delete' in content && !!content.delete;
850
- const isEditMsg = 'edit' in content && !!content.edit;
851
- const isAiMsg = 'ai' in content && !!content.ai;
852
-
853
- const additionalAttributes = {};
854
- const additionalNodes = [];
855
-
856
- if (isDeleteMsg) {
857
- const fromMe = content.delete?.fromMe;
858
- const isGroup = WABinary_1.isJidGroup(content.delete?.remoteJid);
859
- additionalAttributes.edit = (isGroup && !fromMe) || WABinary_1.isJidNewsLetter(jid) ? '8' : '7';
860
- } else if (isEditMsg) {
861
- additionalAttributes.edit = WABinary_1.isJidNewsLetter(jid) ? '3' : '1';
862
- } else if (isAiMsg) {
863
- additionalNodes.push({
864
- attrs: {
865
- biz_bot: '1'
866
- }, tag: "bot"
867
- });
868
- }
869
-
870
- await relayMessage(jid, fullMsg.message, {
871
- messageId: fullMsg.key.id,
872
- cachedGroupMetadata: options.cachedGroupMetadata,
873
- additionalNodes: isAiMsg ? additionalNodes : options.additionalNodes,
874
- additionalAttributes,
875
- statusJidList: options.statusJidList
876
- });
877
-
878
- if (config.emitOwnEvents) {
879
- process.nextTick(() => {
880
- processingMutex.mutex(() => upsertMessage(fullMsg, 'append'));
881
- });
1042
+ };
1043
+ const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
1044
+ const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
1045
+ registerSocketEndHandler(() => {
1046
+ if (!config.userDevicesCache && userDevicesCache.close) {
1047
+ userDevicesCache.close();
882
1048
  }
883
- return fullMsg;
1049
+ mediaConn = undefined;
1050
+ if (messageRetryManager) {
1051
+ messageRetryManager.clear();
884
1052
  }
885
- }
1053
+ });
886
1054
  return {
887
1055
  ...sock,
888
- getPrivacyTokens,
1056
+ userDevicesCache,
1057
+ devicesMutex,
1058
+ issuePrivacyTokens,
889
1059
  assertSessions,
890
1060
  relayMessage,
891
- sendMessageMembers,
892
1061
  sendReceipt,
893
1062
  sendReceipts,
894
- luki,
895
- sendMessage,
896
1063
  readMessages,
897
1064
  refreshMediaConn,
898
- getUSyncDevices,
899
- createParticipantNodes,
1065
+ // Function (not getter) so the spread in chats.ts preserves the live closure binding.
1066
+ getMediaHost: () => mediaHost,
900
1067
  waUploadToServer,
901
- sendPeerDataOperationMessage,
902
1068
  fetchPrivacySettings,
1069
+ sendPeerDataOperationMessage,
1070
+ createParticipantNodes,
1071
+ getUSyncDevices,
1072
+ messageRetryManager,
1073
+ updateMemberLabel,
903
1074
  updateMediaMessage: async (message) => {
904
- const content = (0, Utils_1.assertMediaContent)(message.message);
1075
+ const content = assertMediaContent(message.message);
905
1076
  const mediaKey = content.mediaKey;
906
1077
  const meId = authState.creds.me.id;
907
- const node = (0, Utils_1.encryptMediaRetryRequest)(message.key, mediaKey, meId);
1078
+ const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
908
1079
  let error = undefined;
909
1080
  await Promise.all([
910
1081
  sendNode(node),
911
- waitForMsgMediaUpdate(update => {
1082
+ waitForMsgMediaUpdate(async (update) => {
912
1083
  const result = update.find(c => c.key.id === message.key.id);
913
1084
  if (result) {
914
1085
  if (result.error) {
@@ -916,13 +1087,16 @@ const makeMessagesSocket = (config) => {
916
1087
  }
917
1088
  else {
918
1089
  try {
919
- const media = (0, Utils_1.decryptMediaRetryData)(result.media, mediaKey, result.key.id);
920
- if (media.result !== WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS) {
921
- const resultStr = WAProto_1.proto.MediaRetryNotification.ResultType[media.result];
922
- throw new boom_1.Boom(`Media re-upload failed by device (${resultStr})`, { data: media, statusCode: (0, Utils_1.getStatusCodeForMediaRetry)(media.result) || 404 });
1090
+ const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
1091
+ if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
1092
+ const resultStr = proto.MediaRetryNotification.ResultType[media.result];
1093
+ throw new Boom(`Media re-upload failed by device (${resultStr})`, {
1094
+ data: media,
1095
+ statusCode: getStatusCodeForMediaRetry(media.result) || 404
1096
+ });
923
1097
  }
924
1098
  content.directPath = media.directPath;
925
- content.url = (0, Utils_1.getUrlFromDirectPath)(content.directPath);
1099
+ content.url = getUrlFromDirectPath(content.directPath, mediaHost);
926
1100
  logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
927
1101
  }
928
1102
  catch (err) {
@@ -936,149 +1110,182 @@ const makeMessagesSocket = (config) => {
936
1110
  if (error) {
937
1111
  throw error;
938
1112
  }
939
- ev.emit('messages.update', [
940
- {
941
- key: message.key,
942
- update: {
943
- message: message.message
944
- }
945
- }
946
- ]);
1113
+ ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
947
1114
  return message;
948
1115
  },
949
- sendText: async (jid, text, options, quoted = null) => {
950
- return sendMessage(jid, {
951
- text,
952
- ...options
953
- }, { quoted })
954
- },
955
- sendImage: async (jid, image, caption, options, quoted = null) => {
956
- return sendMessage(jid, {
957
- image,
958
- caption,
959
- ...options
960
- }, { quoted })
961
- },
962
- sendVideo: async (jid, video, caption, options, quoted = null) => {
963
- return sendMessage(jid, {
964
- video,
965
- caption,
966
- ...options
967
- }, { quoted })
968
- },
969
- sendDocument: async (jid, document, caption, options, quoted = null) => {
970
- return sendMessage(jid, {
971
- document,
972
- caption,
973
- ...options
974
- }, { quoted })
975
- },
976
- sendAudio: async (jid, audio, options, quoted = null) => {
977
- return sendMessage(jid, {
978
- audio,
979
- ...options
980
- }, { quoted })
981
- },
982
- sendLocation: async (jid, name, degreesLongitude, degreesLatitude, url, address, options, quoted = null) => {
983
- return sendMessage(jid, {
984
- location: {
985
- degreesLongitude,
986
- degreesLatitude,
987
- name,
988
- url,
989
- address
990
- },
991
- ...options
992
- }, { quoted })
993
- },
994
- sendPoll: async (jid, name, pollVote = [], multiSelect = false, options, quoted = null) => {
995
- const selectableCount = multiSelect ? pollVote.length : 1;
996
-
997
- return sendMessage(jid, {
998
- poll: {
999
- name,
1000
- values: pollVote,
1001
- selectableCount
1002
- },
1003
- ...options
1004
- }, { quoted });
1005
- },
1006
- sendQuiz: (
1007
- jid,
1008
- name,
1009
- pollVote = [],
1010
- answer,
1011
- options,
1012
- quoted
1013
- ) => {
1014
- const poll = {
1015
- name,
1016
- values: pollVote,
1017
- selectableCount: 1,
1018
- type: "QUIZ",
1019
- answer: { optionName: answer }
1116
+ sendMessage: async (jid, content, options = {}) => {
1117
+ const userJid = authState.creds.me.id;
1118
+ const luki = new imup(Utils_1, waUploadToServer, relayMessage)
1119
+ const { quoted } = options;
1120
+ const messageType = luki.detectType(content);
1121
+ if (typeof content === 'object' &&
1122
+ 'disappearingMessagesInChat' in content &&
1123
+ typeof content['disappearingMessagesInChat'] !== 'undefined' &&
1124
+ isJidGroup(jid)) {
1125
+ const { disappearingMessagesInChat } = content;
1126
+ const value = typeof disappearingMessagesInChat === 'boolean'
1127
+ ? disappearingMessagesInChat
1128
+ ? WA_DEFAULT_EPHEMERAL
1129
+ : 0
1130
+ : disappearingMessagesInChat;
1131
+ await groupToggleEphemeral(jid, value);
1020
1132
  }
1021
- return sendMessage(jid, {
1022
- poll,
1023
- ...options
1024
- }, { quoted })
1025
- },
1026
- sendPtv: (jid, ptv, options, quoted = null) => {
1027
- return sendMessage(jid, {
1028
- ptv,
1029
- ...options
1030
- }, { quoted })
1031
- },
1032
- statusMention: async (jid, content) => {
1033
- const msg = Utils_1.generateWAMessageFromContent("status@broadcast", content, {})
1034
-
1035
- await relayMessage("status@broadcast", msg.message, {
1036
- messageId: msg.key.id,
1037
- statusJidList: [jid, authState.creds.me.id],
1038
- additionalNodes: [
1039
- {
1040
- tag: "meta",
1041
- attrs: {},
1042
- content: [
1043
- {
1044
- tag: "mentioned_users",
1045
- attrs: {},
1046
- content: [
1047
- {
1048
- tag: "to",
1049
- attrs: { jid },
1050
- content: undefined
1051
- }
1052
- ]
1053
- }
1054
- ]
1133
+ else {
1134
+ if (messageType) {
1135
+ switch(messageType) {
1136
+ case 'PAYMENT':
1137
+ const paymentContent = await luki.handlePayment(content, quoted);
1138
+ return await relayMessage(jid, paymentContent, {
1139
+ messageId: Utils_1.generateMessageID()
1140
+ });
1141
+ case 'PRODUCT':
1142
+ const productContent = await luki.handleProduct(content, jid, quoted);
1143
+ const productMsg = await Utils_1.generateWAMessageFromContent(jid, productContent, { quoted });
1144
+ return await relayMessage(jid, productMsg.message, {
1145
+ messageId: productMsg.key.id,
1146
+ });
1147
+
1148
+ case 'INTERACTIVE':
1149
+ const interactiveContent = await luki.handleInteractive(content, jid, quoted);
1150
+ const interactiveMsg = await Utils_1.generateWAMessageFromContent(jid, interactiveContent, { quoted });
1151
+ return await relayMessage(jid, interactiveMsg.message, {
1152
+ messageId: interactiveMsg.key.id
1153
+ });
1154
+ case 'ALBUM':
1155
+ return await luki.handleAlbum(content, jid, quoted)
1156
+ case 'EVENT':
1157
+ return await luki.handleEvent(content, jid, quoted)
1158
+ case 'POLL_RESULT':
1159
+ return await luki.handlePollResult(content, jid, quoted)
1160
+ case 'ORDER':
1161
+ return await luki.handleOrderMessage(content, jid, quoted)
1162
+ case 'GROUP_STATUS':
1163
+ return await luki.handleGroupStory(content, jid, quoted)
1164
+ case 'GROUP_LABEL':
1165
+ return await luki.handleGbLabel(content, jid)
1055
1166
  }
1056
- ]
1057
- })
1058
-
1059
- const mentionMsg = {
1060
- statusMentionMessage: {
1061
- message: {
1062
- protocolMessage: {
1063
- key: msg.key,
1064
- type: "STATUS_MENTION_MESSAGE",
1065
- timestamp: Math.floor(Date.now() / 1000)
1066
- }
1167
+ }
1168
+ const fullMsg = await generateWAMessage(jid, content, {
1169
+ logger,
1170
+ userJid,
1171
+ getUrlInfo: text => getUrlInfo(text, {
1172
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1173
+ fetchOpts: {
1174
+ timeout: 3000,
1175
+ ...(httpRequestOptions || {})
1176
+ },
1177
+ logger,
1178
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1179
+ }),
1180
+ //TODO: CACHE
1181
+ getProfilePicUrl: sock.profilePictureUrl,
1182
+ getCallLink: sock.createCallLink,
1183
+ upload: waUploadToServer,
1184
+ mediaCache: config.mediaCache,
1185
+ options: config.options,
1186
+ messageId: generateMessageIDV2(sock.user?.id),
1187
+ ...options
1188
+ });
1189
+ const isEventMsg = 'event' in content && !!content.event;
1190
+ const isDeleteMsg = 'delete' in content && !!content.delete;
1191
+ const isEditMsg = 'edit' in content && !!content.edit;
1192
+ const isPinMsg = 'pin' in content && !!content.pin;
1193
+ const isPollMessage = 'poll' in content && !!content.poll;
1194
+ const additionalAttributes = {};
1195
+ const additionalNodes = [];
1196
+ // required for delete
1197
+ if (isDeleteMsg) {
1198
+ // if the chat is a group, and I am not the author, then delete the message as an admin
1199
+ if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1200
+ additionalAttributes.edit = '8';
1067
1201
  }
1202
+ else {
1203
+ additionalAttributes.edit = '7';
1204
+ }
1205
+ }
1206
+ else if (isEditMsg) {
1207
+ additionalAttributes.edit = '1';
1068
1208
  }
1209
+ else if (isPinMsg) {
1210
+ additionalAttributes.edit = '2';
1211
+ }
1212
+ else if (isPollMessage) {
1213
+ additionalNodes.push({
1214
+ tag: 'meta',
1215
+ attrs: {
1216
+ polltype: 'creation'
1217
+ }
1218
+ });
1219
+ }
1220
+ else if (isEventMsg) {
1221
+ additionalNodes.push({
1222
+ tag: 'meta',
1223
+ attrs: {
1224
+ event_type: 'creation'
1225
+ }
1226
+ });
1227
+ }
1228
+ await relayMessage(jid, fullMsg.message, {
1229
+ messageId: fullMsg.key.id,
1230
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1231
+ additionalAttributes,
1232
+ statusJidList: options.statusJidList,
1233
+ additionalNodes: aiLabel ? additionalNodes : options.additionalNodes
1234
+ });
1235
+ if (config.emitOwnEvents) {
1236
+ process.nextTick(async () => {
1237
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1238
+ });
1239
+ }
1240
+ return fullMsg;
1069
1241
  }
1070
-
1071
- const x = Utils_1.generateWAMessageFromContent(jid, mentionMsg, {})
1072
- return relayMessage(jid, x.message, {
1073
- messageId: x.key.id,
1074
- additionalNodes: [
1075
- {
1076
- tag: "meta",
1077
- attrs: { is_status_mention: "true" }
1242
+ },
1243
+ sendMessageMembers: async (jid, message, options = {}) => {
1244
+ const {
1245
+ messageId: idm,
1246
+ quoted,
1247
+ delayMs = 1500,
1248
+ useUserDevicesCache = true,
1249
+ cachedGroupMetadata,
1250
+ onlyMember = true
1251
+ } = options;
1252
+ const { server } = jidDecode(jid);
1253
+ if (server !== "g.us") throw new Error("@g.us server required");
1254
+ const meId = authState.creds.me.id;
1255
+ const messages = Utils_1.normalizeMessageContent(message);
1256
+ const groupData = cachedGroupMetadata? await cachedGroupMetadata(jid) : await groupMetadata(jid);
1257
+ const isLid = groupData.addressingMode === "lid";
1258
+ const isAdmin = groupData.participants.filter((x) => x.admin !== null).map((y) => y.id)
1259
+ let participantJids = groupData.participants.map(z => z.id);
1260
+ if (onlyMember) {
1261
+ participantJids = isAdmin ? isAdmin : participantJids;
1262
+ }
1263
+ logger.info(`Sending message to ${participantJids.length} members from ${jid}`);
1264
+ for (let i = 0; i < participantJids.length; i++) {
1265
+ const jid = participantJids[i];
1266
+ if (areJidsSameUser(jid, meId)) continue;
1267
+ try {
1268
+ const msgId = `${idm || Utils_1.generateMessageID()}_${i}`;
1269
+ const fullMsg = await Utils_1.generateWAMessageFromContent(jid, message, {
1270
+ messageId: msgId,
1271
+ quoted
1272
+ })
1273
+ await relayMessage(jid, fullMsg.message, {
1274
+ messageId: fullMsg.key.id
1275
+ });
1276
+ logger.debug(`Message successfully sent to ${jid}`);
1277
+ if (delayMs && i < participantJids.length - 1) {
1278
+ await new Promise(z => setTimeout(z, delayMs));
1078
1279
  }
1079
- ]
1080
- })
1280
+ } catch (e) {
1281
+ logger.error({ jid, e }, "Error sending message to");
1282
+ }
1283
+ }
1284
+ return JSON.stringify({
1285
+ members_total: participantJids.length,
1286
+ message
1287
+ }, null, 4);
1081
1288
  }
1082
- }
1289
+ };
1083
1290
  };
1084
- exports.makeMessagesSocket = makeMessagesSocket;
1291
+ //# sourceMappingURL=messages-send.js.map