@alannxd/baileys 6.0.6 → 6.0.9

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