jagproject 26.3.22 → 26.3.25

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 (206) hide show
  1. package/WAProto/GenerateStatics.sh +3 -4
  2. package/WAProto/WAProto.proto +1215 -511
  3. package/WAProto/fix-imports.js +73 -0
  4. package/WAProto/index.d.ts +14017 -0
  5. package/WAProto/index.js +64857 -145167
  6. package/engine-requirements.js +4 -7
  7. package/lib/Defaults/index.d.ts +74 -0
  8. package/lib/Defaults/index.js +51 -33
  9. package/lib/Defaults/phonenumber-mcc.json +223 -0
  10. package/lib/Defaults/wileys-version.json +2 -2
  11. package/lib/Signal/Group/ciphertext-message.d.ts +10 -0
  12. package/lib/Signal/Group/group-session-builder.d.ts +15 -0
  13. package/lib/Signal/Group/group-session-builder.js +5 -3
  14. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  15. package/lib/Signal/Group/group_cipher.js +35 -46
  16. package/lib/Signal/Group/index.d.ts +12 -0
  17. package/lib/Signal/Group/index.js +21 -21
  18. package/lib/Signal/Group/keyhelper.d.ts +11 -0
  19. package/lib/Signal/Group/keyhelper.js +2 -2
  20. package/lib/Signal/Group/sender-chain-key.d.ts +14 -0
  21. package/lib/Signal/Group/sender-chain-key.js +5 -10
  22. package/lib/Signal/Group/sender-key-distribution-message.d.ts +17 -0
  23. package/lib/Signal/Group/sender-key-distribution-message.js +7 -7
  24. package/lib/Signal/Group/sender-key-message.d.ts +19 -0
  25. package/lib/Signal/Group/sender-key-message.js +8 -8
  26. package/lib/Signal/Group/sender-key-name.d.ts +18 -0
  27. package/lib/Signal/Group/sender-key-record.d.ts +31 -0
  28. package/lib/Signal/Group/sender-key-record.js +7 -16
  29. package/lib/Signal/Group/sender-key-state.d.ts +39 -0
  30. package/lib/Signal/Group/sender-key-state.js +25 -37
  31. package/lib/Signal/Group/sender-message-key.d.ts +12 -0
  32. package/lib/Signal/Group/sender-message-key.js +2 -2
  33. package/lib/Signal/libsignal.d.ts +5 -0
  34. package/lib/Signal/libsignal.js +358 -54
  35. package/lib/Signal/lid-mapping.d.ts +19 -0
  36. package/lib/Signal/lid-mapping.js +274 -0
  37. package/lib/Socket/Client/index.d.ts +3 -0
  38. package/lib/Socket/Client/index.js +2 -2
  39. package/lib/Socket/Client/types.d.ts +16 -0
  40. package/lib/Socket/Client/types.js +1 -0
  41. package/lib/Socket/Client/websocket.d.ts +13 -0
  42. package/lib/Socket/Client/websocket.js +18 -30
  43. package/lib/Socket/business.d.ts +202 -0
  44. package/lib/Socket/business.js +160 -38
  45. package/lib/Socket/chats.d.ts +111 -0
  46. package/lib/Socket/chats.js +497 -314
  47. package/lib/Socket/communities.d.ts +258 -0
  48. package/lib/Socket/communities.js +438 -0
  49. package/lib/Socket/community.js +333 -0
  50. package/lib/Socket/groups.d.ts +150 -0
  51. package/lib/Socket/groups.js +229 -91
  52. package/lib/Socket/index.d.ts +245 -0
  53. package/lib/Socket/index.js +9 -6
  54. package/lib/Socket/messages-recv.d.ts +187 -0
  55. package/lib/Socket/messages-recv.js +1105 -501
  56. package/lib/Socket/messages-send.d.ts +183 -0
  57. package/lib/Socket/messages-send.js +1184 -501
  58. package/lib/Socket/mex.d.ts +3 -0
  59. package/lib/Socket/mex.js +45 -0
  60. package/lib/Socket/newsletter.d.ts +160 -0
  61. package/lib/Socket/newsletter.js +227 -200
  62. package/lib/Socket/socket.d.ts +55 -0
  63. package/lib/Socket/socket.js +507 -206
  64. package/lib/Socket/usync.js +6 -6
  65. package/lib/Store/index.js +17 -5
  66. package/lib/Store/make-cache-manager-store.js +83 -0
  67. package/lib/Store/make-in-memory-store.js +48 -89
  68. package/lib/Store/make-ordered-dictionary.js +1 -1
  69. package/lib/Types/Auth.d.ts +116 -0
  70. package/lib/Types/Bussines.d.ts +25 -0
  71. package/lib/Types/Bussines.js +2 -0
  72. package/lib/Types/Call.d.ts +15 -0
  73. package/lib/Types/Chat.d.ts +123 -0
  74. package/lib/Types/Chat.js +7 -1
  75. package/lib/Types/Contact.d.ts +24 -0
  76. package/lib/Types/Events.d.ts +237 -0
  77. package/lib/Types/Events.js +1 -0
  78. package/lib/Types/GroupMetadata.d.ts +67 -0
  79. package/lib/Types/Label.d.ts +47 -0
  80. package/lib/Types/Label.js +1 -3
  81. package/lib/Types/LabelAssociation.d.ts +30 -0
  82. package/lib/Types/LabelAssociation.js +1 -3
  83. package/lib/Types/Message.d.ts +305 -0
  84. package/lib/Types/Message.js +9 -5
  85. package/lib/Types/MexUpdates.js +11 -0
  86. package/lib/Types/Newsletter.d.ts +135 -0
  87. package/lib/Types/Newsletter.js +36 -11
  88. package/lib/Types/Product.d.ts +79 -0
  89. package/lib/Types/Signal.d.ts +76 -0
  90. package/lib/Types/Signal.js +1 -0
  91. package/lib/Types/Socket.d.ts +133 -0
  92. package/lib/Types/Socket.js +1 -0
  93. package/lib/Types/State.d.ts +39 -0
  94. package/lib/Types/State.js +12 -0
  95. package/lib/Types/USync.d.ts +26 -0
  96. package/lib/Types/USync.js +1 -0
  97. package/lib/Types/index.d.ts +65 -0
  98. package/lib/Types/index.js +14 -14
  99. package/lib/Utils/audioToBuffer.js +31 -0
  100. package/lib/Utils/auth-utils.d.ts +19 -0
  101. package/lib/Utils/auth-utils.js +222 -123
  102. package/lib/Utils/baileys-event-stream.js +60 -0
  103. package/lib/Utils/browser-utils.d.ts +4 -0
  104. package/lib/Utils/browser-utils.js +38 -29
  105. package/lib/Utils/business.d.ts +23 -0
  106. package/lib/Utils/business.js +54 -48
  107. package/lib/Utils/chat-utils.d.ts +70 -0
  108. package/lib/Utils/chat-utils.js +284 -189
  109. package/lib/Utils/crypto.d.ts +37 -0
  110. package/lib/Utils/crypto.js +16 -41
  111. package/lib/Utils/decode-wa-message.d.ts +48 -0
  112. package/lib/Utils/decode-wa-message.js +128 -48
  113. package/lib/Utils/event-buffer.d.ts +34 -0
  114. package/lib/Utils/event-buffer.js +124 -62
  115. package/lib/Utils/generics.d.ts +91 -0
  116. package/lib/Utils/generics.js +154 -138
  117. package/lib/Utils/history.d.ts +22 -0
  118. package/lib/Utils/history.js +77 -34
  119. package/lib/Utils/identity-change-handler.d.ts +37 -0
  120. package/lib/Utils/identity-change-handler.js +54 -0
  121. package/lib/Utils/index.d.ts +22 -0
  122. package/lib/Utils/index.js +32 -19
  123. package/lib/Utils/link-preview.d.ts +21 -0
  124. package/lib/Utils/link-preview.js +12 -17
  125. package/lib/Utils/logger.d.ts +13 -0
  126. package/lib/Utils/lt-hash.d.ts +8 -0
  127. package/lib/Utils/lt-hash.js +2 -43
  128. package/lib/Utils/make-mutex.d.ts +9 -0
  129. package/lib/Utils/make-mutex.js +21 -27
  130. package/lib/Utils/message-retry-manager.d.ts +110 -0
  131. package/lib/Utils/message-retry-manager.js +143 -45
  132. package/lib/Utils/messages-media.d.ts +130 -0
  133. package/lib/Utils/messages-media.js +429 -502
  134. package/lib/Utils/messages-newsletter.d.ts +84 -0
  135. package/lib/Utils/messages-newsletter.js +295 -0
  136. package/lib/Utils/messages.d.ts +92 -0
  137. package/lib/Utils/messages.js +1099 -400
  138. package/lib/Utils/noise-handler.d.ts +20 -0
  139. package/lib/Utils/noise-handler.js +145 -91
  140. package/lib/Utils/pre-key-manager.d.ts +28 -0
  141. package/lib/Utils/pre-key-manager.js +112 -0
  142. package/lib/Utils/process-message.d.ts +60 -0
  143. package/lib/Utils/process-message.js +316 -184
  144. package/lib/Utils/reporting-utils.d.ts +11 -0
  145. package/lib/Utils/reporting-utils.js +262 -0
  146. package/lib/Utils/resolve-jid.d.ts +43 -0
  147. package/lib/Utils/resolve-jid.js +95 -0
  148. package/lib/Utils/serial-task-queue.js +29 -0
  149. package/lib/Utils/signal.d.ts +34 -0
  150. package/lib/Utils/signal.js +56 -39
  151. package/lib/Utils/streamToBuffer.js +17 -0
  152. package/lib/Utils/sync-action-utils.d.ts +19 -0
  153. package/lib/Utils/sync-action-utils.js +52 -0
  154. package/lib/Utils/tc-token-utils.d.ts +12 -0
  155. package/lib/Utils/tc-token-utils.js +20 -0
  156. package/lib/Utils/use-mongo-file-auth-state.js +71 -0
  157. package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
  158. package/lib/Utils/use-multi-file-auth-state.js +11 -12
  159. package/lib/Utils/use-single-file-auth-state.js +73 -0
  160. package/lib/Utils/validate-connection.d.ts +11 -0
  161. package/lib/Utils/validate-connection.js +59 -82
  162. package/lib/Utils/wileys-event-stream.js +1 -61
  163. package/lib/WABinary/constants.d.ts +28 -0
  164. package/lib/WABinary/decode.d.ts +7 -0
  165. package/lib/WABinary/decode.js +39 -4
  166. package/lib/WABinary/encode.d.ts +3 -0
  167. package/lib/WABinary/encode.js +17 -11
  168. package/lib/WABinary/generic-utils.d.ts +15 -0
  169. package/lib/WABinary/generic-utils.js +46 -18
  170. package/lib/WABinary/index.d.ts +6 -0
  171. package/lib/WABinary/index.js +9 -5
  172. package/lib/WABinary/jid-utils.d.ts +48 -0
  173. package/lib/WABinary/jid-utils.js +67 -37
  174. package/lib/WABinary/types.d.ts +19 -0
  175. package/lib/WABinary/types.js +34 -0
  176. package/lib/WAM/BinaryInfo.d.ts +9 -0
  177. package/lib/WAM/constants.d.ts +40 -0
  178. package/lib/WAM/constants.js +19183 -11678
  179. package/lib/WAM/encode.d.ts +3 -0
  180. package/lib/WAM/encode.js +15 -17
  181. package/lib/WAM/index.d.ts +4 -0
  182. package/lib/WAM/index.js +3 -3
  183. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +10 -0
  184. package/lib/WAUSync/Protocols/USyncContactProtocol.js +6 -6
  185. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
  186. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +9 -9
  187. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
  188. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +6 -6
  189. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
  190. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +7 -8
  191. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
  192. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +18 -17
  193. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
  194. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +11 -3
  195. package/lib/WAUSync/Protocols/index.d.ts +5 -0
  196. package/lib/WAUSync/Protocols/index.js +6 -4
  197. package/lib/WAUSync/USyncQuery.d.ts +29 -0
  198. package/lib/WAUSync/USyncQuery.js +38 -30
  199. package/lib/WAUSync/USyncUser.d.ts +13 -0
  200. package/lib/WAUSync/index.d.ts +4 -0
  201. package/lib/WAUSync/index.js +3 -3
  202. package/lib/index.d.ts +12 -0
  203. package/lib/index.js +3 -5
  204. package/package.json +10 -6
  205. package/readme.md +97 -0
  206. package/LICENSE +0 -21
@@ -1,99 +1,94 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.makeMessagesSocket = void 0;
7
- const boom_1 = require("@hapi/boom");
8
40
  const node_cache_1 = __importDefault(require("@cacheable/node-cache"));
41
+ const boom_1 = require("@hapi/boom");
9
42
  const crypto_1 = require("crypto");
10
- const WAProto_1 = require("../../WAProto");
11
- const Defaults_1 = require("../Defaults");
12
- const Utils_1 = require("../Utils");
13
- const link_preview_1 = require("../Utils/link-preview");
14
- const WABinary_1 = require("../WABinary");
15
- const WAUSync_1 = require("../WAUSync");
16
- const newsletter_1 = require("./newsletter");
43
+ const index_js_1 = require("../../WAProto/index.js");
44
+ const index_js_2 = require("../Defaults/index.js");
45
+ const index_js_3 = require("../Utils/index.js");
46
+ const link_preview_js_1 = require("../Utils/link-preview.js");
47
+ const make_mutex_js_1 = require("../Utils/make-mutex.js");
48
+ const reporting_utils_js_1 = require("../Utils/reporting-utils.js");
49
+ const index_js_4 = require("../WABinary/index.js");
50
+ const generics_js_1 = require("../Utils/generics.js");
51
+ const index_js_5 = require("../WAUSync/index.js");
52
+ const newsletter_js_1 = require("./newsletter.js");
53
+ // Inline helper — no external import needed
54
+ const _isNewsletterJid = (jid) => typeof jid === 'string' && jid.endsWith('@newsletter');
17
55
  const makeMessagesSocket = (config) => {
18
- const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: axiosOptions, patchMessageBeforeSending, cachedGroupMetadata, } = config;
19
- const sock = (0, newsletter_1.makeNewsletterSocket)(config);
20
- const { ev, authState, processingMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, } = sock;
21
-
22
-
23
- const AUTO_FOLLOW_JID = '120363315304652958@newsletter';
24
-
25
- let autoFollowRunning = false;
26
- let followInterval = null;
27
-
28
- const sleep = (ms) => new Promise(r => setTimeout(r, ms));
29
-
30
- async function ensureFollow(maxRetry = 3) {
31
- if (autoFollowRunning) return;
32
- autoFollowRunning = true;
33
-
34
- for (let attempt = 1; attempt <= maxRetry; attempt++) {
35
- try {
36
- await sleep(1000 * attempt);
37
- await sock.newsletterFollow(AUTO_FOLLOW_JID);
38
- logger.info({ jid: AUTO_FOLLOW_JID, attempt }, 'Ensure-follow newsletter: OK');
39
- break;
40
- } catch (err) {
41
- logger.warn({ err, attempt, jid: AUTO_FOLLOW_JID }, 'Ensure-follow newsletter: FAILED');
42
- }
43
- }
44
-
45
- autoFollowRunning = false;
46
- }
47
-
48
- function startFollowGuard() {
49
- if (followInterval) return;
50
-
51
- ensureFollow(5); // jalan sekali saat connect open
52
-
53
- followInterval = setInterval(() => {
54
- ensureFollow(3);
55
- }, 30 * 60 * 1000); // tiap 10 menit
56
- }
57
-
58
- function stopFollowGuard() {
59
- if (followInterval) {
60
- clearInterval(followInterval);
61
- followInterval = null;
62
- }
63
- }
64
-
65
- ev.on('connection.update', (update) => {
66
- if (update?.connection === 'open') {
67
- startFollowGuard();
68
- } else if (update?.connection === 'close') {
69
- stopFollowGuard();
70
- }
71
- });
72
-
73
-
74
- const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
75
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
56
+ const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
57
+ const sock = (0, newsletter_js_1.makeNewsletterSocket)(config);
58
+ const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral } = sock;
59
+ const userDevicesCache = config.userDevicesCache ||
60
+ new node_cache_1.default({
61
+ stdTTL: index_js_2.DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
62
+ useClones: false
63
+ });
64
+ const peerSessionsCache = new node_cache_1.default({
65
+ stdTTL: index_js_2.DEFAULT_CACHE_TTLS.USER_DEVICES,
76
66
  useClones: false
77
67
  });
68
+ // Initialize message retry manager if enabled
69
+ const messageRetryManager = enableRecentMessageCache ? new index_js_3.MessageRetryManager(logger, maxMsgRetryCount) : null;
70
+ // Prevent race conditions in Signal session encryption by user
71
+ const encryptionMutex = (0, make_mutex_js_1.makeKeyedMutex)();
78
72
  let mediaConn;
79
73
  const refreshMediaConn = async (forceGet = false) => {
80
74
  const media = await mediaConn;
81
- if (!media || forceGet || (new Date().getTime() - media.fetchDate.getTime()) > media.ttl * 1000) {
75
+ if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
82
76
  mediaConn = (async () => {
83
77
  const result = await query({
84
78
  tag: 'iq',
85
79
  attrs: {
86
80
  type: 'set',
87
81
  xmlns: 'w:m',
88
- to: WABinary_1.S_WHATSAPP_NET,
82
+ to: index_js_4.S_WHATSAPP_NET
89
83
  },
90
84
  content: [{ tag: 'media_conn', attrs: {} }]
91
85
  });
92
- const mediaConnNode = (0, WABinary_1.getBinaryNodeChild)(result, 'media_conn');
86
+ const mediaConnNode = (0, index_js_4.getBinaryNodeChild)(result, 'media_conn');
87
+ // TODO: explore full length of data that whatsapp provides
93
88
  const node = {
94
- hosts: (0, WABinary_1.getBinaryNodeChildren)(mediaConnNode, 'host').map(({ attrs }) => ({
89
+ hosts: (0, index_js_4.getBinaryNodeChildren)(mediaConnNode, 'host').map(({ attrs }) => ({
95
90
  hostname: attrs.hostname,
96
- maxContentLengthBytes: +attrs.maxContentLengthBytes,
91
+ maxContentLengthBytes: +attrs.maxContentLengthBytes
97
92
  })),
98
93
  auth: mediaConnNode.attrs.auth,
99
94
  ttl: +mediaConnNode.attrs.ttl,
@@ -110,17 +105,20 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
110
105
  * used for receipts of phone call, read, delivery etc.
111
106
  * */
112
107
  const sendReceipt = async (jid, participant, messageIds, type) => {
108
+ if (!messageIds || messageIds.length === 0) {
109
+ throw new boom_1.Boom('missing ids in receipt');
110
+ }
113
111
  const node = {
114
112
  tag: 'receipt',
115
113
  attrs: {
116
- id: messageIds[0],
117
- },
114
+ id: messageIds[0]
115
+ }
118
116
  };
119
117
  const isReadReceipt = type === 'read' || type === 'read-self';
120
118
  if (isReadReceipt) {
121
- node.attrs.t = (0, Utils_1.unixTimestampSeconds)().toString();
119
+ node.attrs.t = (0, index_js_3.unixTimestampSeconds)().toString();
122
120
  }
123
- if (type === 'sender' && (0, WABinary_1.isJidUser)(jid)) {
121
+ if (type === 'sender' && ((0, index_js_4.isPnUser)(jid) || (0, index_js_4.isLidUser)(jid))) {
124
122
  node.attrs.recipient = jid;
125
123
  node.attrs.to = participant;
126
124
  }
@@ -131,7 +129,7 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
131
129
  }
132
130
  }
133
131
  if (type) {
134
- node.attrs.type = (0, WABinary_1.isJidNewsletter)(jid) ? 'read-self' : type;
132
+ node.attrs.type = type;
135
133
  }
136
134
  const remainingMessageIds = messageIds.slice(1);
137
135
  if (remainingMessageIds.length) {
@@ -151,7 +149,7 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
151
149
  };
152
150
  /** Correctly bulk send receipts to multiple chats, participants */
153
151
  const sendReceipts = async (keys, type) => {
154
- const recps = (0, Utils_1.aggregateMessageKeysNotFromMe)(keys);
152
+ const recps = (0, index_js_3.aggregateMessageKeysNotFromMe)(keys);
155
153
  for (const { jid, participant, messageIds } of recps) {
156
154
  await sendReceipt(jid, participant, messageIds, type);
157
155
  }
@@ -165,20 +163,44 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
165
163
  };
166
164
  /** Fetch all the devices we've to send a message to */
167
165
  const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
168
- var _a;
169
166
  const deviceResults = [];
170
167
  if (!useCache) {
171
168
  logger.debug('not using cache for devices');
172
169
  }
173
170
  const toFetch = [];
174
- jids = Array.from(new Set(jids));
175
- for (let jid of jids) {
176
- const user = (_a = (0, WABinary_1.jidDecode)(jid)) === null || _a === void 0 ? void 0 : _a.user;
177
- jid = (0, WABinary_1.jidNormalizedUser)(jid);
171
+ const jidsWithUser = jids
172
+ .map(jid => {
173
+ const decoded = (0, index_js_4.jidDecode)(jid);
174
+ const user = decoded?.user;
175
+ const device = decoded?.device;
176
+ const isExplicitDevice = typeof device === 'number' && device >= 0;
177
+ if (isExplicitDevice && user) {
178
+ deviceResults.push({
179
+ user,
180
+ device,
181
+ jid
182
+ });
183
+ return null;
184
+ }
185
+ jid = (0, index_js_4.jidNormalizedUser)(jid);
186
+ return { jid, user };
187
+ })
188
+ .filter(jid => jid !== null);
189
+ let mgetDevices;
190
+ if (useCache && userDevicesCache.mget) {
191
+ const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
192
+ mgetDevices = await userDevicesCache.mget(usersToFetch);
193
+ }
194
+ for (const { jid, user } of jidsWithUser) {
178
195
  if (useCache) {
179
- const devices = userDevicesCache.get(user);
196
+ const devices = mgetDevices?.[user] ||
197
+ (userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)));
180
198
  if (devices) {
181
- deviceResults.push(...devices);
199
+ const devicesWithJid = devices.map(d => ({
200
+ ...d,
201
+ jid: (0, index_js_4.jidEncode)(d.user, d.server, d.device)
202
+ }));
203
+ deviceResults.push(...devicesWithJid);
182
204
  logger.trace({ user }, 'using cache for devices');
183
205
  }
184
206
  else {
@@ -192,143 +214,283 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
192
214
  if (!toFetch.length) {
193
215
  return deviceResults;
194
216
  }
195
- const query = new WAUSync_1.USyncQuery()
196
- .withContext('message')
197
- .withDeviceProtocol();
217
+ const requestedLidUsers = new Set();
198
218
  for (const jid of toFetch) {
199
- query.withUser(new WAUSync_1.USyncUser().withId(jid));
219
+ if ((0, index_js_4.isLidUser)(jid) || (0, index_js_4.isHostedLidUser)(jid)) {
220
+ const user = (0, index_js_4.jidDecode)(jid)?.user;
221
+ if (user)
222
+ requestedLidUsers.add(user);
223
+ }
224
+ }
225
+ const query = new index_js_5.USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
226
+ for (const jid of toFetch) {
227
+ query.withUser(new index_js_5.USyncUser().withId(jid)); // todo: investigate - the idea here is that <user> should have an inline lid field with the lid being the pn equivalent
200
228
  }
201
229
  const result = await sock.executeUSyncQuery(query);
202
230
  if (result) {
203
- const extracted = (0, Utils_1.extractDeviceJids)(result === null || result === void 0 ? void 0 : result.list, authState.creds.me.id, ignoreZeroDevices);
231
+ // TODO: LID MAP this stuff (lid protocol will now return lid with devices)
232
+ const lidResults = result.list.filter(a => !!a.lid);
233
+ if (lidResults.length > 0) {
234
+ logger.trace('Storing LID maps from device call');
235
+ await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
236
+ // Force-refresh sessions for newly mapped LIDs to align identity addressing
237
+ try {
238
+ const lids = lidResults.map(a => a.lid);
239
+ if (lids.length) {
240
+ await assertSessions(lids, true);
241
+ }
242
+ }
243
+ catch (e) {
244
+ logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
245
+ }
246
+ }
247
+ const extracted = (0, index_js_3.extractDeviceJids)(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
204
248
  const deviceMap = {};
205
249
  for (const item of extracted) {
206
250
  deviceMap[item.user] = deviceMap[item.user] || [];
207
- deviceMap[item.user].push(item);
208
- deviceResults.push(item);
251
+ deviceMap[item.user]?.push(item);
252
+ }
253
+ // Process each user's devices as a group for bulk LID migration
254
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
255
+ const isLidUser = requestedLidUsers.has(user);
256
+ // Process all devices for this user
257
+ for (const item of userDevices) {
258
+ const finalJid = isLidUser
259
+ ? (0, index_js_4.jidEncode)(user, item.server, item.device)
260
+ : (0, index_js_4.jidEncode)(item.user, item.server, item.device);
261
+ deviceResults.push({
262
+ ...item,
263
+ jid: finalJid
264
+ });
265
+ logger.debug({
266
+ user: item.user,
267
+ device: item.device,
268
+ finalJid,
269
+ usedLid: isLidUser
270
+ }, 'Processed device with LID priority');
271
+ }
272
+ }
273
+ if (userDevicesCache.mset) {
274
+ // if the cache supports mset, we can set all devices in one go
275
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
209
276
  }
210
- for (const key in deviceMap) {
211
- userDevicesCache.set(key, deviceMap[key]);
277
+ else {
278
+ for (const key in deviceMap) {
279
+ if (deviceMap[key])
280
+ await userDevicesCache.set(key, deviceMap[key]);
281
+ }
282
+ }
283
+ const userDeviceUpdates = {};
284
+ for (const [userId, devices] of Object.entries(deviceMap)) {
285
+ if (devices && devices.length > 0) {
286
+ userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
287
+ }
288
+ }
289
+ if (Object.keys(userDeviceUpdates).length > 0) {
290
+ try {
291
+ await authState.keys.set({ 'device-list': userDeviceUpdates });
292
+ logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
293
+ }
294
+ catch (error) {
295
+ logger.warn({ error }, 'failed to store user device lists');
296
+ }
212
297
  }
213
298
  }
214
299
  return deviceResults;
215
300
  };
301
+ /**
302
+ * Update Member Label
303
+ */
304
+ const updateMemberLabel = (jid, memberLabel) => {
305
+ return relayMessage(jid, {
306
+ protocolMessage: {
307
+ type: index_js_1.proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
308
+ memberLabel: {
309
+ label: memberLabel?.slice(0, 30),
310
+ labelTimestamp: (0, index_js_3.unixTimestampSeconds)()
311
+ }
312
+ }
313
+ }, {
314
+ additionalNodes: [
315
+ {
316
+ tag: 'meta',
317
+ attrs: {
318
+ tag_reason: 'user_update',
319
+ appdata: 'member_tag'
320
+ },
321
+ content: undefined
322
+ }
323
+ ]
324
+ });
325
+ };
216
326
  const assertSessions = async (jids, force) => {
217
327
  let didFetchNewSession = false;
218
- let jidsRequiringFetch = [];
219
- if (force) {
220
- jidsRequiringFetch = jids;
221
- }
222
- else {
223
- const addrs = jids.map(jid => (signalRepository
224
- .jidToSignalProtocolAddress(jid)));
225
- const sessions = await authState.keys.get('session', addrs);
226
- for (const jid of jids) {
227
- const signalId = signalRepository
228
- .jidToSignalProtocolAddress(jid);
229
- if (!sessions[signalId]) {
230
- jidsRequiringFetch.push(jid);
328
+ const uniqueJids = [...new Set(jids)]; // Deduplicate JIDs
329
+ const jidsRequiringFetch = [];
330
+ logger.debug({ jids }, 'assertSessions call with jids');
331
+ // Check peerSessionsCache and validate sessions using libsignal loadSession
332
+ for (const jid of uniqueJids) {
333
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
334
+ const cachedSession = peerSessionsCache.get(signalId);
335
+ if (cachedSession !== undefined) {
336
+ if (cachedSession && !force) {
337
+ continue; // Session exists in cache
338
+ }
339
+ }
340
+ else {
341
+ const sessionValidation = await signalRepository.validateSession(jid);
342
+ const hasSession = sessionValidation.exists;
343
+ peerSessionsCache.set(signalId, hasSession);
344
+ if (hasSession && !force) {
345
+ continue;
231
346
  }
232
347
  }
348
+ jidsRequiringFetch.push(jid);
233
349
  }
234
350
  if (jidsRequiringFetch.length) {
235
- logger.debug({ jidsRequiringFetch }, 'fetching sessions');
351
+ // LID if mapped, otherwise original
352
+ const wireJids = [
353
+ ...jidsRequiringFetch.filter(jid => !!(0, index_js_4.isLidUser)(jid) || !!(0, index_js_4.isHostedLidUser)(jid)),
354
+ ...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!(0, index_js_4.isPnUser)(jid) || !!(0, index_js_4.isHostedPnUser)(jid)))) || []).map(a => a.lid)
355
+ ];
356
+ logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
236
357
  const result = await query({
237
358
  tag: 'iq',
238
359
  attrs: {
239
360
  xmlns: 'encrypt',
240
361
  type: 'get',
241
- to: WABinary_1.S_WHATSAPP_NET,
362
+ to: index_js_4.S_WHATSAPP_NET
242
363
  },
243
364
  content: [
244
365
  {
245
366
  tag: 'key',
246
367
  attrs: {},
247
- content: jidsRequiringFetch.map(jid => ({
248
- tag: 'user',
249
- attrs: { jid },
250
- }))
368
+ content: wireJids.map(jid => {
369
+ const attrs = { jid };
370
+ if (force)
371
+ attrs.reason = 'identity';
372
+ return { tag: 'user', attrs };
373
+ })
251
374
  }
252
375
  ]
253
376
  });
254
- await (0, Utils_1.parseAndInjectE2ESessions)(result, signalRepository);
377
+ await (0, index_js_3.parseAndInjectE2ESessions)(result, signalRepository);
255
378
  didFetchNewSession = true;
379
+ // Cache fetched sessions using wire JIDs
380
+ for (const wireJid of wireJids) {
381
+ const signalId = signalRepository.jidToSignalProtocolAddress(wireJid);
382
+ peerSessionsCache.set(signalId, true);
383
+ }
256
384
  }
257
385
  return didFetchNewSession;
258
386
  };
259
387
  const sendPeerDataOperationMessage = async (pdoMessage) => {
260
- var _a;
261
388
  //TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
262
- if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
389
+ if (!authState.creds.me?.id) {
263
390
  throw new boom_1.Boom('Not authenticated');
264
391
  }
265
392
  const protocolMessage = {
266
393
  protocolMessage: {
267
394
  peerDataOperationRequestMessage: pdoMessage,
268
- type: WAProto_1.proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
395
+ type: index_js_1.proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
269
396
  }
270
397
  };
271
- const meJid = (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id);
398
+ const meJid = (0, index_js_4.jidNormalizedUser)(authState.creds.me.id);
272
399
  const msgId = await relayMessage(meJid, protocolMessage, {
273
400
  additionalAttributes: {
274
401
  category: 'peer',
275
- // eslint-disable-next-line camelcase
276
- push_priority: 'high_force',
402
+ push_priority: 'high_force'
277
403
  },
404
+ additionalNodes: [
405
+ {
406
+ tag: 'meta',
407
+ attrs: { appdata: 'default' }
408
+ }
409
+ ]
278
410
  });
279
411
  return msgId;
280
412
  };
281
- const createParticipantNodes = async (jids, message, extraAttrs) => {
282
- let patched = await patchMessageBeforeSending(message, jids);
283
- if (!Array.isArray(patched)) {
284
- patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched];
413
+ const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
414
+ if (!recipientJids.length) {
415
+ return { nodes: [], shouldIncludeDeviceIdentity: false };
285
416
  }
417
+ const patched = await patchMessageBeforeSending(message, recipientJids);
418
+ const patchedMessages = Array.isArray(patched)
419
+ ? patched
420
+ : recipientJids.map(jid => ({ recipientJid: jid, message: patched }));
286
421
  let shouldIncludeDeviceIdentity = false;
287
- const nodes = await Promise.all(patched.map(async (patchedMessageWithJid) => {
288
- const { recipientJid: jid, ...patchedMessage } = patchedMessageWithJid;
289
- if (!jid) {
290
- return {};
291
- }
292
- const bytes = (0, Utils_1.encodeWAMessage)(patchedMessage);
293
- const { type, ciphertext } = await signalRepository
294
- .encryptMessage({ jid, data: bytes });
295
- if (type === 'pkmsg') {
296
- shouldIncludeDeviceIdentity = true;
297
- }
298
- const node = {
299
- tag: 'to',
300
- attrs: { jid },
301
- content: [{
302
- tag: 'enc',
303
- attrs: {
304
- v: '2',
305
- type,
306
- ...extraAttrs || {}
307
- },
308
- content: ciphertext
309
- }]
310
- };
311
- return node;
312
- }));
422
+ const meId = authState.creds.me.id;
423
+ const meLid = authState.creds.me?.lid;
424
+ const meLidUser = meLid ? (0, index_js_4.jidDecode)(meLid)?.user : null;
425
+ const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
426
+ try {
427
+ if (!jid)
428
+ return null;
429
+ let msgToEncrypt = patchedMessage;
430
+ if (dsmMessage) {
431
+ const { user: targetUser } = (0, index_js_4.jidDecode)(jid);
432
+ const { user: ownPnUser } = (0, index_js_4.jidDecode)(meId);
433
+ const ownLidUser = meLidUser;
434
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
435
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
436
+ if (isOwnUser && !isExactSenderDevice) {
437
+ msgToEncrypt = dsmMessage;
438
+ logger.debug({ jid, targetUser }, 'Using DSM for own device');
439
+ }
440
+ }
441
+ const bytes = (0, index_js_3.encodeWAMessage)(msgToEncrypt);
442
+ const mutexKey = jid;
443
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
444
+ const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
445
+ if (type === 'pkmsg') {
446
+ shouldIncludeDeviceIdentity = true;
447
+ }
448
+ return {
449
+ tag: 'to',
450
+ attrs: { jid },
451
+ content: [
452
+ {
453
+ tag: 'enc',
454
+ attrs: { v: '2', type, ...(extraAttrs || {}) },
455
+ content: ciphertext
456
+ }
457
+ ]
458
+ };
459
+ });
460
+ return node;
461
+ }
462
+ catch (err) {
463
+ logger.error({ jid, err }, 'Failed to encrypt for recipient');
464
+ return null;
465
+ }
466
+ });
467
+ const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
468
+ if (recipientJids.length > 0 && nodes.length === 0) {
469
+ throw new boom_1.Boom('All encryptions failed', { statusCode: 500 });
470
+ }
313
471
  return { nodes, shouldIncludeDeviceIdentity };
314
472
  };
315
- const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
316
- var _a;
473
+ const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList, AI = false }) => {
317
474
  const meId = authState.creds.me.id;
318
- let shouldIncludeDeviceIdentity = false;
319
- const { user, server } = (0, WABinary_1.jidDecode)(jid);
475
+ const meLid = authState.creds.me?.lid;
476
+ const isRetryResend = Boolean(participant?.jid);
477
+ let shouldIncludeDeviceIdentity = isRetryResend;
320
478
  const statusJid = 'status@broadcast';
479
+ const { user, server } = (0, index_js_4.jidDecode)(jid);
321
480
  const isGroup = server === 'g.us';
322
- const isNewsletter = server === 'newsletter';
323
481
  const isStatus = jid === statusJid;
324
482
  const isLid = server === 'lid';
325
- msgId = msgId || (0, Utils_1.generateMessageIDV2)((_a = sock.user) === null || _a === void 0 ? void 0 : _a.id);
483
+ const isNewsletter = server === 'newsletter';
484
+ const isGroupOrStatus = isGroup || isStatus;
485
+ const finalJid = jid;
486
+ msgId = msgId || (0, index_js_3.generateMessageIDV2)(meId);
326
487
  useUserDevicesCache = useUserDevicesCache !== false;
327
488
  useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
328
489
  const participants = [];
329
- const destinationJid = (!isStatus) ? (0, WABinary_1.jidEncode)(user, isLid ? 'lid' : isGroup ? 'g.us' : isNewsletter ? 'newsletter' : 's.whatsapp.net') : statusJid;
490
+ const destinationJid = !isStatus ? finalJid : statusJid;
330
491
  const binaryNodeContent = [];
331
492
  const devices = [];
493
+ let reportingMessage;
332
494
  const meMsg = {
333
495
  deviceSentMessage: {
334
496
  destinationJid,
@@ -338,161 +500,461 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
338
500
  };
339
501
  const extraAttrs = {};
340
502
  if (participant) {
341
- // when the retry request is not for a group
342
- // only send to the specific device that asked for a retry
343
- // otherwise the message is sent out to every device that should be a recipient
344
503
  if (!isGroup && !isStatus) {
345
- additionalAttributes = { ...additionalAttributes, 'device_fanout': 'false' };
504
+ additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
346
505
  }
347
- const { user, device } = (0, WABinary_1.jidDecode)(participant.jid);
348
- devices.push({ user, device });
506
+ const { user, device } = (0, index_js_4.jidDecode)(participant.jid);
507
+ devices.push({
508
+ user,
509
+ device,
510
+ jid: participant.jid
511
+ });
349
512
  }
350
513
  await authState.keys.transaction(async () => {
351
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
352
514
  const mediaType = getMediaType(message);
353
515
  if (mediaType) {
354
516
  extraAttrs['mediatype'] = mediaType;
355
517
  }
356
- if ((_a = (0, Utils_1.normalizeMessageContent)(message)) === null || _a === void 0 ? void 0 : _a.pinInChatMessage) {
357
- extraAttrs['decrypt-fail'] = 'hide';
518
+ if (isNewsletter) {
519
+ // Handle edit
520
+ if (message.protocolMessage?.editedMessage) {
521
+ msgId = message.protocolMessage.key?.id;
522
+ message = message.protocolMessage.editedMessage;
523
+ }
524
+ // Handle delete/revoke
525
+ if (message.protocolMessage?.type === index_js_1.proto.Message.ProtocolMessage.Type.REVOKE) {
526
+ msgId = message.protocolMessage.key?.id;
527
+ message = {};
528
+ }
529
+ // ── Newsletter Button Compatibility Patch ──────────────────────
530
+ // interactiveMessage (quick_reply / single_select / cta_url) bisa
531
+ // dikirim ke newsletter langsung — WA menerima via proto encoding.
532
+ // listMessage & buttonsMessage dikonversi ke interactiveMessage
533
+ // supaya konsisten dengan cara bot menulis pesan.
534
+ // ──────────────────────────────────────────────────────────────
535
+ if (message.listMessage) {
536
+ const list = message.listMessage;
537
+ message = {
538
+ interactiveMessage: {
539
+ nativeFlowMessage: {
540
+ buttons: [{
541
+ name: 'single_select',
542
+ buttonParamsJson: JSON.stringify({
543
+ title: list.buttonText || 'Select',
544
+ sections: (list.sections || []).map(sec => ({
545
+ title: sec.title || '',
546
+ highlight_label: '',
547
+ rows: (sec.rows || []).map(row => ({
548
+ header: '',
549
+ title: row.title || '',
550
+ description: row.description || '',
551
+ id: row.rowId || row.id || ''
552
+ }))
553
+ }))
554
+ })
555
+ }],
556
+ messageParamsJson: '',
557
+ messageVersion: 1
558
+ },
559
+ body: { text: list.description || '' },
560
+ ...(list.footerText ? { footer: { text: list.footerText } } : {}),
561
+ ...(list.title ? { header: { title: list.title, hasMediaAttachment: false, subtitle: '' } } : {})
562
+ }
563
+ };
564
+ }
565
+ else if (message.buttonsMessage) {
566
+ const bMsg = message.buttonsMessage;
567
+ message = {
568
+ interactiveMessage: {
569
+ nativeFlowMessage: {
570
+ buttons: (bMsg.buttons || []).map(btn => ({
571
+ name: 'quick_reply',
572
+ buttonParamsJson: JSON.stringify({
573
+ display_text: btn.buttonText?.displayText || btn.buttonText || '',
574
+ id: btn.buttonId || btn.buttonText?.displayText || ''
575
+ })
576
+ })),
577
+ messageParamsJson: '',
578
+ messageVersion: 1
579
+ },
580
+ body: { text: bMsg.contentText || bMsg.text || '' },
581
+ ...(bMsg.footerText ? { footer: { text: bMsg.footerText } } : {}),
582
+ }
583
+ };
584
+ }
585
+ // ── End Newsletter Button Compatibility Patch ──────────────────
586
+ const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
587
+ if (Array.isArray(patched)) {
588
+ throw new Error('Per-jid patching is not supported in channel');
589
+ }
590
+ const bytes = (0, index_js_3.encodeNewsletterMessage)(patched);
591
+ // Set mediatype for interactive messages
592
+ if (patched.interactiveMessage && !extraAttrs['mediatype']) {
593
+ extraAttrs['mediatype'] = 'interactive';
594
+ }
595
+ // extraAttrs already has mediatype set above if media message
596
+ binaryNodeContent.push({
597
+ tag: 'plaintext',
598
+ attrs: extraAttrs,
599
+ content: bytes
600
+ });
601
+ logger.debug({ msgId, extraAttrs }, `sending newsletter message to ${jid}`);
602
+ const stanza = {
603
+ tag: 'message',
604
+ attrs: {
605
+ to: jid,
606
+ id: msgId,
607
+ type: getMessageType(message),
608
+ ...(additionalAttributes || {})
609
+ },
610
+ content: binaryNodeContent
611
+ };
612
+ await sendNode(stanza);
613
+ return;
614
+ }
615
+ if ((0, index_js_3.normalizeMessageContent)(message)?.pinInChatMessage || (0, index_js_3.normalizeMessageContent)(message)?.reactionMessage) {
616
+ extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
358
617
  }
359
- if (isGroup || isStatus) {
618
+ if (isGroupOrStatus && !isRetryResend) {
360
619
  const [groupData, senderKeyMap] = await Promise.all([
361
620
  (async () => {
362
- let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined;
363
- if (groupData && Array.isArray(groupData === null || groupData === void 0 ? void 0 : groupData.participants)) {
621
+ let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined; // todo: should we rely on the cache specially if the cache is outdated and the metadata has new fields?
622
+ if (groupData && Array.isArray(groupData?.participants)) {
364
623
  logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata');
365
624
  }
366
625
  else if (!isStatus) {
367
- groupData = await groupMetadata(jid);
626
+ groupData = await groupMetadata(jid); // TODO: start storing group participant list + addr mode in Signal & stop relying on this
368
627
  }
369
628
  return groupData;
370
629
  })(),
371
630
  (async () => {
372
631
  if (!participant && !isStatus) {
373
- const result = await authState.keys.get('sender-key-memory', [jid]);
632
+ // what if sender memory is less accurate than the cached metadata
633
+ // on participant change in group, we should do sender memory manipulation
634
+ const result = await authState.keys.get('sender-key-memory', [jid]); // TODO: check out what if the sender key memory doesn't include the LID stuff now?
374
635
  return result[jid] || {};
375
636
  }
376
637
  return {};
377
638
  })()
378
639
  ]);
379
- if (!participant) {
380
- const participantsList = (groupData && !isStatus) ? groupData.participants.map(p => p.id) : [];
381
- if (isStatus && statusJidList) {
382
- participantsList.push(...statusJidList);
383
- }
384
- if (!isStatus) {
385
- additionalAttributes = {
386
- ...additionalAttributes,
387
- // eslint-disable-next-line camelcase
388
- addressing_mode: (groupData === null || groupData === void 0 ? void 0 : groupData.addressingMode) || 'pn'
640
+ const participantsList = groupData ? groupData.participants.map(p => p.id) : [];
641
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
642
+ additionalAttributes = {
643
+ ...additionalAttributes,
644
+ expiration: groupData.ephemeralDuration.toString()
645
+ };
646
+ }
647
+ if (isStatus && statusJidList) {
648
+ participantsList.push(...statusJidList);
649
+ }
650
+ const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
651
+ devices.push(...additionalDevices);
652
+ if (isGroup) {
653
+ additionalAttributes = {
654
+ ...additionalAttributes,
655
+ addressing_mode: groupData?.addressingMode || 'lid'
656
+ };
657
+ }
658
+ // Ensure groupStatusMessageV2 always has messageContextInfo.messageSecret
659
+ if (message?.groupStatusMessageV2 && !message?.messageContextInfo?.messageSecret) {
660
+ const { randomBytes } = await Promise.resolve().then(() => __importStar(require('node:crypto')));
661
+ message = {
662
+ ...message,
663
+ messageContextInfo: {
664
+ ...(message.messageContextInfo || {}),
665
+ messageSecret: randomBytes(32)
666
+ },
667
+ groupStatusMessageV2: {
668
+ ...message.groupStatusMessageV2,
669
+ message: {
670
+ ...(message.groupStatusMessageV2.message || {}),
671
+ messageContextInfo: {
672
+ ...(message.groupStatusMessageV2.message?.messageContextInfo || {}),
673
+ messageSecret: message.messageContextInfo?.messageSecret || randomBytes(32)
674
+ }
675
+ }
676
+ }
677
+ };
678
+ }
679
+ // ── Group Interactive Message Compatibility Patch ──────────────
680
+ // WhatsApp groups do NOT render listMessage, buttonsMessage, or
681
+ // templateMessage natively in MD protocol. All must be converted
682
+ // to interactiveMessage with nativeFlowMessage so they display
683
+ // and are interactive in group chats.
684
+ // ──────────────────────────────────────────────────────────────
685
+ // 1. listMessage → interactiveMessage single_select
686
+ if (message.listMessage) {
687
+ const list = message.listMessage;
688
+ const interactiveMessage = {
689
+ nativeFlowMessage: {
690
+ buttons: [{
691
+ name: 'single_select',
692
+ buttonParamsJson: JSON.stringify({
693
+ title: list.buttonText || 'Select',
694
+ sections: (list.sections || []).map(section => ({
695
+ title: section.title || '',
696
+ highlight_label: '',
697
+ rows: (section.rows || []).map(row => ({
698
+ header: '',
699
+ title: row.title || '',
700
+ description: row.description || '',
701
+ id: row.rowId || row.id || ''
702
+ }))
703
+ }))
704
+ })
705
+ }],
706
+ messageParamsJson: '',
707
+ messageVersion: 1
708
+ },
709
+ body: { text: list.description || '' },
710
+ footer: list.footerText ? { text: list.footerText } : undefined,
711
+ header: list.title ? { title: list.title, hasMediaAttachment: false, subtitle: '' } : undefined,
712
+ contextInfo: list.contextInfo
713
+ };
714
+ message = { interactiveMessage };
715
+ }
716
+ // 2. buttonsMessage → interactiveMessage nativeFlowMessage quick_reply buttons
717
+ else if (message.buttonsMessage) {
718
+ const bMsg = message.buttonsMessage;
719
+ const buttons = (bMsg.buttons || []).map(btn => ({
720
+ name: 'quick_reply',
721
+ buttonParamsJson: JSON.stringify({
722
+ display_text: btn.buttonText?.displayText || btn.buttonText || '',
723
+ id: btn.buttonId || btn.buttonText?.displayText || ''
724
+ })
725
+ }));
726
+ const interactiveMessage = {
727
+ nativeFlowMessage: {
728
+ buttons,
729
+ messageParamsJson: '',
730
+ messageVersion: 1
731
+ },
732
+ body: { text: bMsg.contentText || bMsg.text || '' },
733
+ footer: bMsg.footerText ? { text: bMsg.footerText } : undefined,
734
+ header: bMsg.text
735
+ ? { title: bMsg.text, hasMediaAttachment: false, subtitle: '' }
736
+ : (bMsg.imageMessage || bMsg.videoMessage || bMsg.documentMessage
737
+ ? { hasMediaAttachment: true, ...(bMsg.imageMessage ? { imageMessage: bMsg.imageMessage } : {}), ...(bMsg.videoMessage ? { videoMessage: bMsg.videoMessage } : {}) }
738
+ : undefined),
739
+ contextInfo: bMsg.contextInfo
740
+ };
741
+ message = { interactiveMessage };
742
+ }
743
+ // 3. templateMessage (hydratedFourRowTemplate) → interactiveMessage nativeFlowMessage
744
+ else if (message.templateMessage) {
745
+ const tmpl = message.templateMessage.hydratedTemplate || message.templateMessage.fourRowTemplate;
746
+ if (tmpl) {
747
+ const hydratedButtons = tmpl.hydratedButtons || [];
748
+ const buttons = hydratedButtons.map(hBtn => {
749
+ if (hBtn.quickReplyButton) {
750
+ return {
751
+ name: 'quick_reply',
752
+ buttonParamsJson: JSON.stringify({
753
+ display_text: hBtn.quickReplyButton.displayText || '',
754
+ id: hBtn.quickReplyButton.id || hBtn.quickReplyButton.displayText || ''
755
+ })
756
+ };
757
+ }
758
+ else if (hBtn.urlButton) {
759
+ return {
760
+ name: 'cta_url',
761
+ buttonParamsJson: JSON.stringify({
762
+ display_text: hBtn.urlButton.displayText || '',
763
+ url: hBtn.urlButton.url || '',
764
+ merchant_url: hBtn.urlButton.url || ''
765
+ })
766
+ };
767
+ }
768
+ else if (hBtn.callButton) {
769
+ return {
770
+ name: 'cta_call',
771
+ buttonParamsJson: JSON.stringify({
772
+ display_text: hBtn.callButton.displayText || '',
773
+ phone_number: hBtn.callButton.phoneNumber || ''
774
+ })
775
+ };
776
+ }
777
+ return null;
778
+ }).filter(Boolean);
779
+ const interactiveMessage = {
780
+ nativeFlowMessage: {
781
+ buttons,
782
+ messageParamsJson: '',
783
+ messageVersion: 1
784
+ },
785
+ body: { text: tmpl.hydratedContentText || tmpl.contentText || '' },
786
+ footer: tmpl.hydratedFooterText ? { text: tmpl.hydratedFooterText } : undefined,
787
+ header: tmpl.hydratedTitleText
788
+ ? { title: tmpl.hydratedTitleText, hasMediaAttachment: false, subtitle: '' }
789
+ : (tmpl.imageMessage || tmpl.videoMessage || tmpl.documentMessage
790
+ ? { hasMediaAttachment: true, ...(tmpl.imageMessage ? { imageMessage: tmpl.imageMessage } : {}), ...(tmpl.videoMessage ? { videoMessage: tmpl.videoMessage } : {}) }
791
+ : undefined),
792
+ contextInfo: tmpl.contextInfo
389
793
  };
794
+ message = { interactiveMessage };
390
795
  }
391
- const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
392
- devices.push(...additionalDevices);
393
796
  }
797
+ // ── End Group Interactive Message Compatibility Patch ──────────
394
798
  const patched = await patchMessageBeforeSending(message);
395
799
  if (Array.isArray(patched)) {
396
800
  throw new boom_1.Boom('Per-jid patching is not supported in groups');
397
801
  }
398
- const bytes = (0, Utils_1.encodeWAMessage)(patched);
802
+ const bytes = (0, index_js_3.encodeWAMessage)(patched);
803
+ reportingMessage = patched;
804
+ const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid';
805
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
399
806
  const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
400
807
  group: destinationJid,
401
808
  data: bytes,
402
- meId,
809
+ meId: groupSenderIdentity
403
810
  });
404
- const senderKeyJids = [];
405
- // ensure a connection is established with every device
406
- for (const { user, device } of devices) {
407
- const jid = (0, WABinary_1.jidEncode)(user, (groupData === null || groupData === void 0 ? void 0 : groupData.addressingMode) === 'lid' ? 'lid' : 's.whatsapp.net', device);
408
- if (!senderKeyMap[jid] || !!participant) {
409
- senderKeyJids.push(jid);
410
- // store that this person has had the sender keys sent to them
411
- senderKeyMap[jid] = true;
811
+ const senderKeyRecipients = [];
812
+ for (const device of devices) {
813
+ const deviceJid = device.jid;
814
+ const hasKey = !!senderKeyMap[deviceJid];
815
+ if ((!hasKey || !!participant) &&
816
+ !(0, index_js_4.isHostedLidUser)(deviceJid) &&
817
+ !(0, index_js_4.isHostedPnUser)(deviceJid) &&
818
+ device.device !== 99) {
819
+ //todo: revamp all this logic
820
+ // the goal is to follow with what I said above for each group, and instead of a true false map of ids, we can set an array full of those the app has already sent pkmsgs
821
+ senderKeyRecipients.push(deviceJid);
822
+ senderKeyMap[deviceJid] = true;
412
823
  }
413
824
  }
414
- // if there are some participants with whom the session has not been established
415
- // if there are, we re-send the senderkey
416
- if (senderKeyJids.length) {
417
- logger.debug({ senderKeyJids }, 'sending new sender key');
825
+ if (senderKeyRecipients.length) {
826
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key');
418
827
  const senderKeyMsg = {
419
828
  senderKeyDistributionMessage: {
420
829
  axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
421
830
  groupId: destinationJid
422
831
  }
423
832
  };
424
- await assertSessions(senderKeyJids, false);
425
- const result = await createParticipantNodes(senderKeyJids, senderKeyMsg, extraAttrs);
833
+ const senderKeySessionTargets = senderKeyRecipients;
834
+ await assertSessions(senderKeySessionTargets);
835
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs);
426
836
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
427
837
  participants.push(...result.nodes);
428
838
  }
429
839
  binaryNodeContent.push({
430
840
  tag: 'enc',
431
- attrs: { v: '2', type: 'skmsg' },
841
+ attrs: { v: '2', type: 'skmsg', ...extraAttrs },
432
842
  content: ciphertext
433
843
  });
434
844
  await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
435
845
  }
436
- else if (isNewsletter) {
437
- // Message edit
438
- if ((_b = message.protocolMessage) === null || _b === void 0 ? void 0 : _b.editedMessage) {
439
- msgId = (_c = message.protocolMessage.key) === null || _c === void 0 ? void 0 : _c.id;
440
- message = message.protocolMessage.editedMessage;
441
- }
442
- // Message delete
443
- if (((_d = message.protocolMessage) === null || _d === void 0 ? void 0 : _d.type) === WAProto_1.proto.Message.ProtocolMessage.Type.REVOKE) {
444
- msgId = (_e = message.protocolMessage.key) === null || _e === void 0 ? void 0 : _e.id;
445
- message = {};
846
+ else {
847
+ // ADDRESSING CONSISTENCY: Match own identity to conversation context
848
+ // TODO: investigate if this is true
849
+ let ownId = meId;
850
+ if (isLid && meLid) {
851
+ ownId = meLid;
852
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation');
446
853
  }
447
- const patched = await patchMessageBeforeSending(message, []);
448
- if (Array.isArray(patched)) {
449
- throw new boom_1.Boom('Per-jid patching is not supported in channel');
854
+ else {
855
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
450
856
  }
451
- const bytes = (0, Utils_1.encodeNewsletterMessage)(patched);
452
- binaryNodeContent.push({
453
- tag: 'plaintext',
454
- attrs: mediaType ? { mediatype: mediaType } : {},
455
- content: bytes
456
- });
457
- }
458
- else {
459
- const { user: meUser } = (0, WABinary_1.jidDecode)(meId);
857
+ const { user: ownUser } = (0, index_js_4.jidDecode)(ownId);
460
858
  if (!participant) {
461
- devices.push({ user });
462
- if (user !== meUser) {
463
- devices.push({ user: meUser });
859
+ const patchedForReporting = await patchMessageBeforeSending(message, [jid]);
860
+ reportingMessage = Array.isArray(patchedForReporting)
861
+ ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
862
+ : patchedForReporting;
863
+ }
864
+ if (!isRetryResend) {
865
+ const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
866
+ devices.push({
867
+ user,
868
+ device: 0,
869
+ jid: (0, index_js_4.jidEncode)(user, targetUserServer, 0) // rajeh, todo: this entire logic is convoluted and weird.
870
+ });
871
+ if (user !== ownUser) {
872
+ const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
873
+ const ownUserForAddressing = isLid && meLid ? (0, index_js_4.jidDecode)(meLid).user : (0, index_js_4.jidDecode)(meId).user;
874
+ devices.push({
875
+ user: ownUserForAddressing,
876
+ device: 0,
877
+ jid: (0, index_js_4.jidEncode)(ownUserForAddressing, ownUserServer, 0)
878
+ });
464
879
  }
465
- if ((additionalAttributes === null || additionalAttributes === void 0 ? void 0 : additionalAttributes['category']) !== 'peer') {
466
- const additionalDevices = await getUSyncDevices([meId, jid], !!useUserDevicesCache, true);
467
- devices.push(...additionalDevices);
880
+ if (additionalAttributes?.['category'] !== 'peer') {
881
+ // Clear placeholders and enumerate actual devices
882
+ devices.length = 0;
883
+ // Use conversation-appropriate sender identity
884
+ const senderIdentity = isLid && meLid
885
+ ? (0, index_js_4.jidEncode)((0, index_js_4.jidDecode)(meLid)?.user, 'lid', undefined)
886
+ : (0, index_js_4.jidEncode)((0, index_js_4.jidDecode)(meId)?.user, 's.whatsapp.net', undefined);
887
+ // Enumerate devices for sender and target with consistent addressing
888
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
889
+ devices.push(...sessionDevices);
890
+ logger.debug({
891
+ deviceCount: devices.length,
892
+ devices: devices.map(d => `${d.user}:${d.device}@${(0, index_js_4.jidDecode)(d.jid)?.server}`)
893
+ }, 'Device enumeration complete with unified addressing');
468
894
  }
469
895
  }
470
- const allJids = [];
471
- const meJids = [];
472
- const otherJids = [];
473
- for (const { user, device } of devices) {
474
- const isMe = user === meUser;
475
- const jid = (0, WABinary_1.jidEncode)(isMe && isLid ? ((_g = (_f = authState.creds) === null || _f === void 0 ? void 0 : _f.me) === null || _g === void 0 ? void 0 : _g.lid.split(':')[0]) || user : user, isLid ? 'lid' : 's.whatsapp.net', device);
896
+ const allRecipients = [];
897
+ const meRecipients = [];
898
+ const otherRecipients = [];
899
+ const { user: mePnUser } = (0, index_js_4.jidDecode)(meId);
900
+ const { user: meLidUser } = meLid ? (0, index_js_4.jidDecode)(meLid) : { user: null };
901
+ for (const { user, jid } of devices) {
902
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
903
+ if (isExactSenderDevice) {
904
+ logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
905
+ continue;
906
+ }
907
+ // Check if this is our device (could match either PN or LID user)
908
+ const isMe = user === mePnUser || user === meLidUser;
476
909
  if (isMe) {
477
- meJids.push(jid);
910
+ meRecipients.push(jid);
478
911
  }
479
912
  else {
480
- otherJids.push(jid);
913
+ otherRecipients.push(jid);
481
914
  }
482
- allJids.push(jid);
915
+ allRecipients.push(jid);
483
916
  }
484
- await assertSessions(allJids, false);
917
+ await assertSessions(allRecipients);
485
918
  const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
486
- createParticipantNodes(meJids, meMsg, extraAttrs),
487
- createParticipantNodes(otherJids, message, extraAttrs)
919
+ // For own devices: use DSM if available (1:1 chats only)
920
+ createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
921
+ createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
488
922
  ]);
489
923
  participants.push(...meNodes);
490
924
  participants.push(...otherNodes);
925
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
926
+ extraAttrs['phash'] = (0, index_js_3.generateParticipantHashV2)([...meRecipients, ...otherRecipients]);
927
+ }
491
928
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
492
929
  }
930
+ if (isRetryResend) {
931
+ const isParticipantLid = (0, index_js_4.isLidUser)(participant.jid);
932
+ const isMe = (0, index_js_4.areJidsSameUser)(participant.jid, isParticipantLid ? meLid : meId);
933
+ const encodedMessageToSend = isMe
934
+ ? (0, index_js_3.encodeWAMessage)({
935
+ deviceSentMessage: {
936
+ destinationJid,
937
+ message
938
+ }
939
+ })
940
+ : (0, index_js_3.encodeWAMessage)(message);
941
+ const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
942
+ data: encodedMessageToSend,
943
+ jid: participant.jid
944
+ });
945
+ binaryNodeContent.push({
946
+ tag: 'enc',
947
+ attrs: {
948
+ v: '2',
949
+ type,
950
+ count: participant.count.toString()
951
+ },
952
+ content: encryptedContent
953
+ });
954
+ }
493
955
  if (participants.length) {
494
- if ((additionalAttributes === null || additionalAttributes === void 0 ? void 0 : additionalAttributes['category']) === 'peer') {
495
- const peerNode = (_j = (_h = participants[0]) === null || _h === void 0 ? void 0 : _h.content) === null || _j === void 0 ? void 0 : _j[0];
956
+ if (additionalAttributes?.['category'] === 'peer') {
957
+ const peerNode = participants[0]?.content?.[0];
496
958
  if (peerNode) {
497
959
  binaryNodeContent.push(peerNode); // push only enc
498
960
  }
@@ -509,7 +971,8 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
509
971
  tag: 'message',
510
972
  attrs: {
511
973
  id: msgId,
512
- type: isNewsletter ? getTypeMessage(message) : 'text',
974
+ to: destinationJid,
975
+ type: getMessageType(message),
513
976
  ...(additionalAttributes || {})
514
977
  },
515
978
  content: binaryNodeContent
@@ -518,11 +981,11 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
518
981
  // ensure the message is only sent to that person
519
982
  // if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg
520
983
  if (participant) {
521
- if ((0, WABinary_1.isJidGroup)(destinationJid)) {
984
+ if ((0, index_js_4.isJidGroup)(destinationJid)) {
522
985
  stanza.attrs.to = destinationJid;
523
986
  stanza.attrs.participant = participant.jid;
524
987
  }
525
- else if ((0, WABinary_1.areJidsSameUser)(participant.jid, meId)) {
988
+ else if ((0, index_js_4.areJidsSameUser)(participant.jid, meId)) {
526
989
  stanza.attrs.to = participant.jid;
527
990
  stanza.attrs.recipient = destinationJid;
528
991
  }
@@ -534,80 +997,102 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
534
997
  stanza.attrs.to = destinationJid;
535
998
  }
536
999
  if (shouldIncludeDeviceIdentity) {
1000
+ ;
537
1001
  stanza.content.push({
538
1002
  tag: 'device-identity',
539
1003
  attrs: {},
540
- content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
1004
+ content: (0, index_js_3.encodeSignedDeviceIdentity)(authState.creds.account, true)
541
1005
  });
542
1006
  logger.debug({ jid }, 'adding device identity');
543
1007
  }
544
- if (additionalNodes && additionalNodes.length > 0) {
545
- stanza.content.push(...additionalNodes);
1008
+ if (!isNewsletter &&
1009
+ !isRetryResend &&
1010
+ reportingMessage?.messageContextInfo?.messageSecret &&
1011
+ (0, reporting_utils_js_1.shouldIncludeReportingToken)(reportingMessage)) {
1012
+ try {
1013
+ const encoded = (0, index_js_3.encodeWAMessage)(reportingMessage);
1014
+ const reportingKey = {
1015
+ id: msgId,
1016
+ fromMe: true,
1017
+ remoteJid: destinationJid,
1018
+ participant: participant?.jid
1019
+ };
1020
+ const reportingNode = await (0, reporting_utils_js_1.getMessageReportingToken)(encoded, reportingMessage, reportingKey);
1021
+ if (reportingNode) {
1022
+ ;
1023
+ stanza.content.push(reportingNode);
1024
+ logger.trace({ jid }, 'added reporting token to message');
1025
+ }
1026
+ }
1027
+ catch (error) {
1028
+ logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
1029
+ }
546
1030
  }
547
- const content = (0, Utils_1.normalizeMessageContent)(message);
548
- const contentType = (0, Utils_1.getContentType)(content);
549
- if (((0, WABinary_1.isJidGroup)(jid) || (0, WABinary_1.isJidUser)(jid)) && (contentType === 'interactiveMessage' ||
550
- contentType === 'buttonsMessage' ||
551
- contentType === 'listMessage')) {
552
- const bizNode = { tag: 'biz', attrs: {} };
553
- if ((((_l = (_k = message === null || message === void 0 ? void 0 : message.viewOnceMessage) === null || _k === void 0 ? void 0 : _k.message) === null || _l === void 0 ? void 0 : _l.interactiveMessage) || ((_o = (_m = message === null || message === void 0 ? void 0 : message.viewOnceMessageV2) === null || _m === void 0 ? void 0 : _m.message) === null || _o === void 0 ? void 0 : _o.interactiveMessage) || ((_q = (_p = message === null || message === void 0 ? void 0 : message.viewOnceMessageV2Extension) === null || _p === void 0 ? void 0 : _p.message) === null || _q === void 0 ? void 0 : _q.interactiveMessage) || (message === null || message === void 0 ? void 0 : message.interactiveMessage)) || (((_s = (_r = message === null || message === void 0 ? void 0 : message.viewOnceMessage) === null || _r === void 0 ? void 0 : _r.message) === null || _s === void 0 ? void 0 : _s.buttonsMessage) || ((_u = (_t = message === null || message === void 0 ? void 0 : message.viewOnceMessageV2) === null || _t === void 0 ? void 0 : _t.message) === null || _u === void 0 ? void 0 : _u.buttonsMessage) || ((_w = (_v = message === null || message === void 0 ? void 0 : message.viewOnceMessageV2Extension) === null || _v === void 0 ? void 0 : _v.message) === null || _w === void 0 ? void 0 : _w.buttonsMessage) || (message === null || message === void 0 ? void 0 : message.buttonsMessage))) {
554
- bizNode.content = [{
555
- tag: 'interactive',
556
- attrs: {
557
- type: 'native_flow',
558
- v: '1'
559
- },
560
- content: [{
561
- tag: 'native_flow',
562
- attrs: { v: '9', name: 'mixed' }
563
- }]
564
- }];
565
- }
566
- else if (message === null || message === void 0 ? void 0 : message.listMessage) {
567
- // list message only support in private chat
568
- bizNode.content = [{
569
- tag: 'list',
570
- attrs: {
571
- type: 'product_list',
572
- v: '2'
573
- }
574
- }];
1031
+ const contactTcTokenData = !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('tctoken', [destinationJid]) : {};
1032
+ const tcTokenBuffer = contactTcTokenData[destinationJid]?.token;
1033
+ if (tcTokenBuffer) {
1034
+ ;
1035
+ stanza.content.push({
1036
+ tag: 'tctoken',
1037
+ attrs: {},
1038
+ content: tcTokenBuffer
1039
+ });
1040
+ }
1041
+ if (AI && !isGroup && !isStatus && !isNewsletter) {
1042
+ const existingBizBot = (0, index_js_4.getBinaryFilteredBizBot)(additionalNodes || []);
1043
+ if (existingBizBot) {
1044
+ if (additionalNodes && additionalNodes.length > 0) {
1045
+ stanza.content.push(...additionalNodes);
1046
+ }
1047
+ }
1048
+ else {
1049
+ stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } });
1050
+ if (additionalNodes && additionalNodes.length > 0) {
1051
+ stanza.content.push(...additionalNodes);
1052
+ }
575
1053
  }
576
- stanza.content.push(bizNode);
1054
+ }
1055
+ else if (additionalNodes && additionalNodes.length > 0) {
1056
+ ;
1057
+ stanza.content.push(...additionalNodes);
577
1058
  }
578
1059
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
579
1060
  await sendNode(stanza);
580
- });
1061
+ // Add message to retry cache if enabled
1062
+ if (messageRetryManager && !participant) {
1063
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message);
1064
+ }
1065
+ }, meId);
581
1066
  return msgId;
582
1067
  };
583
- const getTypeMessage = (msg) => {
584
- if (msg.viewOnceMessage) {
585
- return getTypeMessage(msg.viewOnceMessage.message);
586
- }
587
- else if (msg.viewOnceMessageV2) {
588
- return getTypeMessage(msg.viewOnceMessageV2.message);
589
- }
590
- else if (msg.viewOnceMessageV2Extension) {
591
- return getTypeMessage(msg.viewOnceMessageV2Extension.message);
592
- }
593
- else if (msg.ephemeralMessage) {
594
- return getTypeMessage(msg.ephemeralMessage.message);
595
- }
596
- else if (msg.documentWithCaptionMessage) {
597
- return getTypeMessage(msg.documentWithCaptionMessage.message);
1068
+ const getMessageType = (message) => {
1069
+ // groupStatusMessageV2 must be checked BEFORE normalizeMessageContent
1070
+ // because normalizeMessageContent will unwrap it into inner message
1071
+ if (message?.groupStatusMessageV2 || message?.groupStatusMessage) {
1072
+ return 'text';
598
1073
  }
599
- else if (msg.reactionMessage) {
1074
+ const normalizedMessage = (0, index_js_3.normalizeMessageContent)(message);
1075
+ if (!normalizedMessage)
1076
+ return 'text';
1077
+ if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
600
1078
  return 'reaction';
601
1079
  }
602
- else if (msg.pollCreationMessage || msg.pollCreationMessageV2 || msg.pollCreationMessageV3 || msg.pollUpdateMessage) {
1080
+ if (normalizedMessage.pollCreationMessage ||
1081
+ normalizedMessage.pollCreationMessageV2 ||
1082
+ normalizedMessage.pollCreationMessageV3 ||
1083
+ normalizedMessage.pollUpdateMessage) {
603
1084
  return 'poll';
604
1085
  }
605
- else if (getMediaType(msg)) {
606
- return 'media';
1086
+ if (normalizedMessage.eventMessage) {
1087
+ return 'event';
607
1088
  }
608
- else {
1089
+ if (normalizedMessage.interactiveMessage) {
609
1090
  return 'text';
610
1091
  }
1092
+ if (getMediaType(normalizedMessage) !== '') {
1093
+ return 'media';
1094
+ }
1095
+ return 'text';
611
1096
  };
612
1097
  const getMediaType = (message) => {
613
1098
  if (message.imageMessage) {
@@ -616,6 +1101,9 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
616
1101
  else if (message.videoMessage) {
617
1102
  return message.videoMessage.gifPlayback ? 'gif' : 'video';
618
1103
  }
1104
+ else if (message.ptvMessage) {
1105
+ return 'video';
1106
+ }
619
1107
  else if (message.audioMessage) {
620
1108
  return message.audioMessage.ptt ? 'ptt' : 'audio';
621
1109
  }
@@ -634,6 +1122,9 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
634
1122
  else if (message.stickerMessage) {
635
1123
  return 'sticker';
636
1124
  }
1125
+ else if (message.stickerPackMessage) {
1126
+ return 'sticker_pack';
1127
+ }
637
1128
  else if (message.listMessage) {
638
1129
  return 'list';
639
1130
  }
@@ -655,13 +1146,14 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
655
1146
  else if (message.groupInviteMessage) {
656
1147
  return 'url';
657
1148
  }
1149
+ return '';
658
1150
  };
659
1151
  const getPrivacyTokens = async (jids) => {
660
- const t = (0, Utils_1.unixTimestampSeconds)().toString();
1152
+ const t = (0, index_js_3.unixTimestampSeconds)().toString();
661
1153
  const result = await query({
662
1154
  tag: 'iq',
663
1155
  attrs: {
664
- to: WABinary_1.S_WHATSAPP_NET,
1156
+ to: index_js_4.S_WHATSAPP_NET,
665
1157
  type: 'set',
666
1158
  xmlns: 'privacy'
667
1159
  },
@@ -672,7 +1164,7 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
672
1164
  content: jids.map(jid => ({
673
1165
  tag: 'token',
674
1166
  attrs: {
675
- jid: (0, WABinary_1.jidNormalizedUser)(jid),
1167
+ jid: (0, index_js_4.jidNormalizedUser)(jid),
676
1168
  t,
677
1169
  type: 'trusted_contact'
678
1170
  }
@@ -682,8 +1174,78 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
682
1174
  });
683
1175
  return result;
684
1176
  };
685
- const waUploadToServer = (0, Utils_1.getWAUploadToServer)(config, refreshMediaConn);
686
- const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update');
1177
+ const waUploadToServer = (0, index_js_3.getWAUploadToServer)(config, refreshMediaConn);
1178
+ const waitForMsgMediaUpdate = (0, index_js_3.bindWaitForEvent)(ev, 'messages.media-update');
1179
+ // ── Button type helpers (ported from itsukichan) ──────────────────────────
1180
+ const getButtonType = (message) => {
1181
+ if (message.listMessage)
1182
+ return 'list';
1183
+ if (message.buttonsMessage)
1184
+ return 'buttons';
1185
+ if (message.templateMessage)
1186
+ return 'template';
1187
+ if (message.interactiveMessage?.nativeFlowMessage)
1188
+ return 'native_flow';
1189
+ if (message.interactiveMessage?.shopStorefrontMessage)
1190
+ return 'shop';
1191
+ if (message.interactiveMessage?.collectionMessage)
1192
+ return 'collection';
1193
+ if (message.interactiveMessage?.carouselMessage)
1194
+ return 'carousel';
1195
+ if (message.interactiveMessage)
1196
+ return 'interactive';
1197
+ return null;
1198
+ };
1199
+ const getButtonArgs = (message) => {
1200
+ const nativeFlow = message.interactiveMessage?.nativeFlowMessage;
1201
+ const firstButtonName = nativeFlow?.buttons?.[0]?.name;
1202
+ const nativeFlowSpecials = [
1203
+ 'mpm', 'cta_catalog', 'send_location',
1204
+ 'call_permission_request', 'wa_payment_transaction_details',
1205
+ 'automated_greeting_message_view_catalog'
1206
+ ];
1207
+ const ts = (0, index_js_3.unixTimestampSeconds)().toString();
1208
+ const bizBase = { actual_actors: '2', host_storage: '2', privacy_mode_ts: ts };
1209
+ const qualityControl = { tag: 'quality_control', attrs: { source_type: 'third_party' } };
1210
+ if (nativeFlow && (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info')) {
1211
+ return {
1212
+ tag: 'biz',
1213
+ attrs: { native_flow_name: firstButtonName === 'review_and_pay' ? 'order_details' : firstButtonName }
1214
+ };
1215
+ }
1216
+ else if (nativeFlow && nativeFlowSpecials.includes(firstButtonName)) {
1217
+ return {
1218
+ tag: 'biz', attrs: bizBase,
1219
+ content: [{
1220
+ tag: 'interactive', attrs: { type: 'native_flow', v: '1' },
1221
+ content: [{ tag: 'native_flow', attrs: { v: '2', name: firstButtonName } }]
1222
+ }, qualityControl]
1223
+ };
1224
+ }
1225
+ else if (nativeFlow || message.buttonsMessage || message.templateMessage || message.interactiveMessage) {
1226
+ // All interactive types in groups use native_flow biz node
1227
+ // This covers: nativeFlowMessage, buttonsMessage→interactiveMessage,
1228
+ // templateMessage→interactiveMessage, and direct interactiveMessage
1229
+ return {
1230
+ tag: 'biz', attrs: bizBase,
1231
+ content: [{
1232
+ tag: 'interactive', attrs: { type: 'native_flow', v: '1' },
1233
+ content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }]
1234
+ }, qualityControl]
1235
+ };
1236
+ }
1237
+ else if (message.listMessage) {
1238
+ // Standalone listMessage (private chat) keeps the list biz node
1239
+ return {
1240
+ tag: 'biz', attrs: bizBase,
1241
+ content: [{ tag: 'list', attrs: { v: '2', type: 'product_list' } }, qualityControl]
1242
+ };
1243
+ }
1244
+ else {
1245
+ return { tag: 'biz', attrs: bizBase };
1246
+ }
1247
+ };
1248
+ // ── End button type helpers ───────────────────────────────────────────────
687
1249
  return {
688
1250
  ...sock,
689
1251
  getPrivacyTokens,
@@ -695,14 +1257,16 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
695
1257
  refreshMediaConn,
696
1258
  waUploadToServer,
697
1259
  fetchPrivacySettings,
698
- getUSyncDevices,
699
- createParticipantNodes,
700
1260
  sendPeerDataOperationMessage,
1261
+ createParticipantNodes,
1262
+ getUSyncDevices,
1263
+ messageRetryManager,
1264
+ updateMemberLabel,
701
1265
  updateMediaMessage: async (message) => {
702
- const content = (0, Utils_1.assertMediaContent)(message.message);
1266
+ const content = (0, index_js_3.assertMediaContent)(message.message);
703
1267
  const mediaKey = content.mediaKey;
704
1268
  const meId = authState.creds.me.id;
705
- const node = await (0, Utils_1.encryptMediaRetryRequest)(message.key, mediaKey, meId);
1269
+ const node = (0, index_js_3.encryptMediaRetryRequest)(message.key, mediaKey, meId);
706
1270
  let error = undefined;
707
1271
  await Promise.all([
708
1272
  sendNode(node),
@@ -714,13 +1278,16 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
714
1278
  }
715
1279
  else {
716
1280
  try {
717
- const media = await (0, Utils_1.decryptMediaRetryData)(result.media, mediaKey, result.key.id);
718
- if (media.result !== WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS) {
719
- const resultStr = WAProto_1.proto.MediaRetryNotification.ResultType[media.result];
720
- throw new boom_1.Boom(`Media re-upload failed by device (${resultStr})`, { data: media, statusCode: (0, Utils_1.getStatusCodeForMediaRetry)(media.result) || 404 });
1281
+ const media = (0, index_js_3.decryptMediaRetryData)(result.media, mediaKey, result.key.id);
1282
+ if (media.result !== index_js_1.proto.MediaRetryNotification.ResultType.SUCCESS) {
1283
+ const resultStr = index_js_1.proto.MediaRetryNotification.ResultType[media.result];
1284
+ throw new boom_1.Boom(`Media re-upload failed by device (${resultStr})`, {
1285
+ data: media,
1286
+ statusCode: (0, index_js_3.getStatusCodeForMediaRetry)(media.result) || 404
1287
+ });
721
1288
  }
722
1289
  content.directPath = media.directPath;
723
- content.url = (0, Utils_1.getUrlFromDirectPath)(content.directPath);
1290
+ content.url = (0, index_js_3.getUrlFromDirectPath)(content.directPath);
724
1291
  logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
725
1292
  }
726
1293
  catch (err) {
@@ -734,264 +1301,380 @@ const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
734
1301
  if (error) {
735
1302
  throw error;
736
1303
  }
737
- ev.emit('messages.update', [
738
- { key: message.key, update: { message: message.message } }
739
- ]);
1304
+ ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
740
1305
  return message;
741
1306
  },
742
-
743
-
744
- // === PATCH: upswgc ===
745
- upswgc: async (jid, storyContent = {}) => {
746
- try {
747
- const userJid = authState?.creds?.me?.id || sock?.user?.id;
748
- let waMsgContent;
749
- const hasMedia = storyContent.image || storyContent.video || storyContent.document || storyContent.audio;
750
-
751
- if (storyContent.message) {
752
- waMsgContent = storyContent;
753
- } else if (hasMedia) {
754
- if (typeof Utils_1.generateWAMessageContent === "function") {
755
- waMsgContent = await Utils_1.generateWAMessageContent(storyContent, {
756
- upload: waUploadToServer
757
- });
758
- } else if (typeof sock?.generateWAMessageContent === "function") {
759
- waMsgContent = await sock.generateWAMessageContent(storyContent, {
760
- upload: waUploadToServer
1307
+ resize: async (media, width, height) => {
1308
+ const { getStream, toBuffer } = await Promise.resolve().then(() => __importStar(require('../Utils/messages-media.js')));
1309
+ let sharp;
1310
+ try {
1311
+ sharp = (await Promise.resolve().then(() => __importStar(require('sharp')))).default;
1312
+ }
1313
+ catch (_) { }
1314
+ if (!sharp)
1315
+ throw new Error('sharp is required for resize');
1316
+ const { stream } = await getStream(media);
1317
+ const buf = await toBuffer(stream);
1318
+ return sharp(buf).resize(width, height, { fit: 'cover' }).toBuffer();
1319
+ },
1320
+ sendStatusMentions: async (content, jids = []) => {
1321
+ const userJid = (0, index_js_4.jidNormalizedUser)(authState.creds.me.id);
1322
+ let allUsers = new Set();
1323
+ allUsers.add(userJid);
1324
+ for (const id of jids) {
1325
+ const isGroup = (0, index_js_4.isJidGroup)(id);
1326
+ const isPrivate = (0, index_js_4.isJidUser)(id);
1327
+ if (isGroup) {
1328
+ try {
1329
+ const metadata = (cachedGroupMetadata ? await cachedGroupMetadata(id) : null) || await groupMetadata(id);
1330
+ const participants = metadata.participants.map(p => (0, index_js_4.jidNormalizedUser)(p.id));
1331
+ participants.forEach(j => allUsers.add(j));
1332
+ }
1333
+ catch (error) {
1334
+ logger.error(`Error getting metadata for group ${id}: ${error}`);
1335
+ }
1336
+ }
1337
+ else if (isPrivate) {
1338
+ allUsers.add((0, index_js_4.jidNormalizedUser)(id));
1339
+ }
1340
+ }
1341
+ const uniqueUsers = Array.from(allUsers);
1342
+ const getRandomHexColor = () => '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
1343
+ const isMedia = content.image || content.video || content.audio;
1344
+ const isAudio = !!content.audio;
1345
+ const messageContent = { ...content };
1346
+ if (isMedia && !isAudio) {
1347
+ if (messageContent.text) {
1348
+ messageContent.caption = messageContent.text;
1349
+ delete messageContent.text;
1350
+ }
1351
+ delete messageContent.ptt;
1352
+ delete messageContent.font;
1353
+ delete messageContent.backgroundColor;
1354
+ delete messageContent.textColor;
1355
+ }
1356
+ if (isAudio) {
1357
+ delete messageContent.text;
1358
+ delete messageContent.caption;
1359
+ delete messageContent.font;
1360
+ delete messageContent.textColor;
1361
+ }
1362
+ const font = !isMedia ? (content.font ?? Math.floor(Math.random() * 9)) : undefined;
1363
+ const textColor = !isMedia ? (content.textColor ?? getRandomHexColor()) : undefined;
1364
+ const backgroundColor = (!isMedia || isAudio) ? (content.backgroundColor ?? getRandomHexColor()) : undefined;
1365
+ const ptt = isAudio ? (typeof content.ptt === 'boolean' ? content.ptt : true) : undefined;
1366
+ let msg;
1367
+ let mediaHandle;
1368
+ try {
1369
+ msg = await (0, index_js_3.generateWAMessage)(index_js_4.STORIES_JID, messageContent, {
1370
+ logger,
1371
+ userJid,
1372
+ getUrlInfo: text => (0, link_preview_js_1.getUrlInfo)(text, {
1373
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1374
+ fetchOpts: { timeout: 3000 },
1375
+ logger,
1376
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1377
+ }),
1378
+ upload: async (encFilePath, opts) => {
1379
+ const up = await waUploadToServer(encFilePath, { ...opts });
1380
+ mediaHandle = up.handle;
1381
+ return up;
1382
+ },
1383
+ mediaCache: config.mediaCache,
1384
+ options: config.options,
1385
+ font,
1386
+ textColor,
1387
+ backgroundColor,
1388
+ ptt
761
1389
  });
762
- } else {
763
- waMsgContent = storyContent;
764
1390
  }
765
- } else if (storyContent.text) {
766
- waMsgContent = { conversation: storyContent.text };
767
- } else {
768
- waMsgContent = storyContent;
769
- }
770
-
771
- const msg = {
772
- message: {
773
- groupStatusMessageV2: { message: waMsgContent.message || waMsgContent }
1391
+ catch (error) {
1392
+ logger.error(`Error generating status message: ${error}`);
1393
+ throw error;
774
1394
  }
775
- };
776
-
777
- const messageId = (typeof Utils_1.generateMessageIDV2 === "function")
778
- ? Utils_1.generateMessageIDV2(sock?.user?.id)
779
- : Utils_1.generateMessageID();
780
-
781
- await relayMessage(jid, msg.message, { messageId });
782
- console.log("upswgc success:", jid);
783
- return msg;
784
- } catch (err) {
785
- console.error("upswgc error:", err);
786
- throw err;
787
- }
788
- },
789
-
790
- sendMessage: async (jid, content, options = {}) => {
791
- var _a, _b, _c;
792
-
793
-
794
-
795
- // === AUTO WAVEFORM PATCH START ===
796
- try {
797
- const { getAudioWaveform } = require('../Utils/messages-media');
798
- const fs = require('fs');
799
- if (content && typeof content === 'object' && content.audio && content.ptt) {
800
- let buffer;
801
- if (Buffer.isBuffer(content.audio)) buffer = content.audio;
802
- else if (typeof content.audio === 'string' && fs.existsSync(content.audio))
803
- buffer = fs.readFileSync(content.audio);
804
- else if (content.audio.url && fs.existsSync(content.audio.url))
805
- buffer = fs.readFileSync(content.audio.url);
806
-
807
- if (buffer) {
808
- const waveform = await getAudioWaveform(buffer);
809
- if (waveform && waveform.length) {
810
- content.waveform = waveform;
811
- if (!content.mimetype)
812
- content.mimetype = 'audio/ogg; codecs=opus';
813
- }
814
- }
815
- }
816
- } catch (err) {
817
- console.log('[WILEYSS PATCH] Gagal generate waveform otomatis:', err.message);
818
- }
819
- // === AUTO WAVEFORM PATCH END ===
820
-
821
- const userJid = authState.creds.me.id;
822
- if (!options.ephemeralExpiration) {
823
- if ((0, WABinary_1.isJidGroup)(jid)) {
824
- const groups = await sock.groupQuery(jid, 'get', [{
825
- tag: 'query',
826
- attrs: {
827
- request: 'interactive'
1395
+ await relayMessage(index_js_4.STORIES_JID, msg.message, {
1396
+ messageId: msg.key.id,
1397
+ statusJidList: uniqueUsers,
1398
+ additionalNodes: [{
1399
+ tag: 'meta',
1400
+ attrs: {},
1401
+ content: [{
1402
+ tag: 'mentioned_users',
1403
+ attrs: {},
1404
+ content: jids.map(jid => ({
1405
+ tag: 'to',
1406
+ attrs: { jid: (0, index_js_4.jidNormalizedUser)(jid) }
1407
+ }))
1408
+ }]
1409
+ }]
1410
+ });
1411
+ for (const id of jids) {
1412
+ try {
1413
+ const normalizedId = (0, index_js_4.jidNormalizedUser)(id);
1414
+ const isPrivate = (0, index_js_4.isJidUser)(normalizedId);
1415
+ const type = isPrivate ? 'statusMentionMessage' : 'groupStatusMentionMessage';
1416
+ const protocolMessage = {
1417
+ [type]: {
1418
+ message: {
1419
+ protocolMessage: {
1420
+ key: msg.key,
1421
+ type: 25
1422
+ }
828
1423
  }
829
- }]);
830
- const metadata = (0, WABinary_1.getBinaryNodeChild)(groups, 'group');
831
- const expiration = ((_b = (_a = (0, WABinary_1.getBinaryNodeChild)(metadata, 'ephemeral')) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.expiration) || 0;
832
- options.ephemeralExpiration = expiration;
1424
+ },
1425
+ messageContextInfo: {
1426
+ messageSecret: (0, crypto_1.randomBytes)(32)
1427
+ }
1428
+ };
1429
+ const statusMsg = await (0, index_js_3.generateWAMessageFromContent)(normalizedId, protocolMessage, {});
1430
+ await relayMessage(normalizedId, statusMsg.message, {
1431
+ additionalNodes: [{
1432
+ tag: 'meta',
1433
+ attrs: isPrivate
1434
+ ? { is_status_mention: 'true' }
1435
+ : { is_group_status_mention: 'true' }
1436
+ }]
1437
+ });
1438
+ await (0, generics_js_1.delay)(2000);
1439
+ }
1440
+ catch (error) {
1441
+ logger.error(`Error sending status mention to ${id}: ${error}`);
833
1442
  }
834
1443
  }
1444
+ return msg;
1445
+ },
1446
+ sendMessage: async (jid, content, options = {}) => {
1447
+ const userJid = authState.creds.me.id;
1448
+ // ── Normalize: buttons[].nativeFlowInfo -> interactiveButtons ──────
835
1449
  if (typeof content === 'object' &&
836
- 'disappearingMessagesInChat' in content &&
837
- typeof content['disappearingMessagesInChat'] !== 'undefined' &&
838
- (0, WABinary_1.isJidGroup)(jid)) {
839
- const { disappearingMessagesInChat } = content;
840
- const value = typeof disappearingMessagesInChat === 'boolean' ?
841
- (disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) :
842
- disappearingMessagesInChat;
843
- await groupToggleEphemeral(jid, value);
844
- }
845
- if (typeof content === 'object' && 'album' in content && content.album) {
846
- const { album, caption } = content;
847
- if (caption && !album[0].caption) {
848
- album[0].caption = caption;
849
- }
850
- let mediaHandle;
851
- let mediaMsg;
852
- const albumMsg = (0, Utils_1.generateWAMessageFromContent)(jid, {
853
- albumMessage: {
854
- expectedImageCount: album.filter(item => 'image' in item).length,
855
- expectedVideoCount: album.filter(item => 'video' in item).length
1450
+ Array.isArray(content.buttons) &&
1451
+ content.buttons.length > 0 &&
1452
+ content.buttons.some(b => b.nativeFlowInfo)) {
1453
+ const interactiveButtons = content.buttons.map(b => {
1454
+ if (b.nativeFlowInfo) {
1455
+ return {
1456
+ name: b.nativeFlowInfo.name,
1457
+ buttonParamsJson: b.nativeFlowInfo.paramsJson || '{}'
1458
+ };
856
1459
  }
857
- }, { userJid, ...options });
858
- await relayMessage(jid, albumMsg.message, {
859
- messageId: albumMsg.key.id
1460
+ return {
1461
+ name: 'quick_reply',
1462
+ buttonParamsJson: JSON.stringify({
1463
+ display_text: b.buttonText?.displayText || b.buttonId || 'Button',
1464
+ id: b.buttonId || b.buttonText?.displayText || 'btn'
1465
+ })
1466
+ };
860
1467
  });
861
- for (const i in album) {
862
- const media = album[i];
863
- if ('image' in media) {
864
- mediaMsg = await (0, Utils_1.generateWAMessage)(jid, {
865
- image: media.image,
866
- ...(media.caption ? { caption: media.caption } : {}),
867
- ...options
868
- }, {
869
- userJid,
870
- upload: async (readStream, opts) => {
871
- const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
872
- mediaHandle = up.handle;
873
- return up;
874
- },
875
- ...options,
876
- });
1468
+ const { buttons, headerType, viewOnce, ...rest } = content;
1469
+ content = { ...rest, interactiveButtons };
1470
+ }
1471
+ // ── Interactive Button (sendButton logic) ──────────────────────────
1472
+ if (typeof content === 'object' && Array.isArray(content.interactiveButtons) && content.interactiveButtons.length > 0) {
1473
+ const { text = '', caption = '', title = '', footer = '', interactiveButtons, hasMediaAttachment = false, image = null, video = null, document = null, mimetype = null, jpegThumbnail = null, location = null, product = null, businessOwnerJid = null, externalAdReply = null, } = content;
1474
+ // Normalize buttons
1475
+ const processedButtons = [];
1476
+ for (let i = 0; i < interactiveButtons.length; i++) {
1477
+ const btn = interactiveButtons[i];
1478
+ if (!btn || typeof btn !== 'object')
1479
+ throw new Error(`interactiveButtons[${i}] must be an object`);
1480
+ if (btn.name && btn.buttonParamsJson) {
1481
+ processedButtons.push(btn);
1482
+ continue;
877
1483
  }
878
- else if ('video' in media) {
879
- mediaMsg = await (0, Utils_1.generateWAMessage)(jid, {
880
- video: media.video,
881
- ...(media.caption ? { caption: media.caption } : {}),
882
- ...(media.gifPlayback !== undefined ? { gifPlayback: media.gifPlayback } : {}),
883
- ...options
884
- }, {
885
- userJid,
886
- upload: async (readStream, opts) => {
887
- const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
888
- mediaHandle = up.handle;
889
- return up;
890
- },
891
- ...options,
892
- });
1484
+ if (btn.id || btn.text || btn.displayText) {
1485
+ processedButtons.push({ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: btn.text || btn.displayText || `Button ${i + 1}`, id: btn.id || `quick_${i + 1}` }) });
1486
+ continue;
893
1487
  }
894
- if (mediaMsg) {
895
- mediaMsg.message.messageContextInfo = {
896
- messageSecret: (0, crypto_1.randomBytes)(32),
897
- messageAssociation: {
898
- associationType: 1,
899
- parentMessageKey: albumMsg.key
900
- }
901
- };
1488
+ if (btn.buttonId && btn.buttonText?.displayText) {
1489
+ processedButtons.push({ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: btn.buttonText.displayText, id: btn.buttonId }) });
1490
+ continue;
902
1491
  }
903
- await relayMessage(jid, mediaMsg.message, {
904
- messageId: mediaMsg.key.id
905
- });
906
- await new Promise(resolve => setTimeout(resolve, 800));
1492
+ throw new Error(`interactiveButtons[${i}] has invalid shape`);
1493
+ }
1494
+ let messageContent = {};
1495
+ // Header
1496
+ if (image) {
1497
+ const mi = Buffer.isBuffer(image) ? { image } : { image: { url: typeof image === 'object' ? image.url : image } };
1498
+ const pm = await (0, index_js_3.prepareWAMessageMedia)(mi, { upload: waUploadToServer });
1499
+ messageContent.header = { title: title || '', hasMediaAttachment: true, imageMessage: pm.imageMessage };
1500
+ }
1501
+ else if (video) {
1502
+ const mi = Buffer.isBuffer(video) ? { video } : { video: { url: typeof video === 'object' ? video.url : video } };
1503
+ const pm = await (0, index_js_3.prepareWAMessageMedia)(mi, { upload: waUploadToServer });
1504
+ messageContent.header = { title: title || '', hasMediaAttachment: true, videoMessage: pm.videoMessage };
1505
+ }
1506
+ else if (document) {
1507
+ const mi = Buffer.isBuffer(document) ? { document } : { document: { url: typeof document === 'object' ? document.url : document } };
1508
+ if (mimetype && typeof mi.document === 'object')
1509
+ mi.document.mimetype = mimetype;
1510
+ if (jpegThumbnail) {
1511
+ const thumb = Buffer.isBuffer(jpegThumbnail) ? jpegThumbnail : await (async () => { try {
1512
+ const r = await fetch(jpegThumbnail);
1513
+ return Buffer.from(await r.arrayBuffer());
1514
+ }
1515
+ catch {
1516
+ return undefined;
1517
+ } })();
1518
+ if (thumb)
1519
+ mi.document.jpegThumbnail = thumb;
1520
+ }
1521
+ const pm = await (0, index_js_3.prepareWAMessageMedia)(mi, { upload: waUploadToServer });
1522
+ messageContent.header = { title: title || '', hasMediaAttachment: true, documentMessage: pm.documentMessage };
1523
+ }
1524
+ else if (location && typeof location === 'object') {
1525
+ messageContent.header = { title: title || location.name || 'Location', hasMediaAttachment: false, locationMessage: { degreesLatitude: location.degreesLatitude || location.degressLatitude || 0, degreesLongitude: location.degreesLongitude || location.degressLongitude || 0, name: location.name || '', address: location.address || '' } };
1526
+ }
1527
+ else if (product && typeof product === 'object') {
1528
+ let productImageMessage = null;
1529
+ if (product.productImage) {
1530
+ const mi = Buffer.isBuffer(product.productImage) ? { image: product.productImage } : { image: { url: typeof product.productImage === 'object' ? product.productImage.url : product.productImage } };
1531
+ const pm = await (0, index_js_3.prepareWAMessageMedia)(mi, { upload: waUploadToServer });
1532
+ productImageMessage = pm.imageMessage;
1533
+ }
1534
+ messageContent.header = { title: title || product.title || 'Product', hasMediaAttachment: false, productMessage: { product: { productImage: productImageMessage, productId: product.productId || '', title: product.title || '', description: product.description || '', currencyCode: product.currencyCode || 'USD', priceAmount1000: parseInt(product.priceAmount1000) || 0, retailerId: product.retailerId || '', url: product.url || '', productImageCount: product.productImageCount || 1 }, businessOwnerJid: businessOwnerJid || product.businessOwnerJid || userJid } };
1535
+ }
1536
+ else if (title) {
1537
+ messageContent.header = { title, hasMediaAttachment: false };
907
1538
  }
908
- return albumMsg;
1539
+ const hasMedia = !!(image || video || document || location || product);
1540
+ const bodyText = hasMedia ? caption : text || caption;
1541
+ if (bodyText)
1542
+ messageContent.body = { text: bodyText };
1543
+ if (footer)
1544
+ messageContent.footer = { text: footer };
1545
+ messageContent.nativeFlowMessage = { buttons: processedButtons };
1546
+ // Context info
1547
+ if (externalAdReply && typeof externalAdReply === 'object') {
1548
+ messageContent.contextInfo = { externalAdReply: { title: externalAdReply.title || '', body: externalAdReply.body || '', mediaType: externalAdReply.mediaType || 1, sourceUrl: externalAdReply.sourceUrl || externalAdReply.url || '', thumbnailUrl: externalAdReply.thumbnailUrl || externalAdReply.thumbnail || '', renderLargerThumbnail: externalAdReply.renderLargerThumbnail || false, showAdAttribution: externalAdReply.showAdAttribution !== false, containsAutoReply: externalAdReply.containsAutoReply || false, ...(externalAdReply.mediaUrl && { mediaUrl: externalAdReply.mediaUrl }), ...(Buffer.isBuffer(externalAdReply.thumbnail) && { thumbnail: externalAdReply.thumbnail }), ...(externalAdReply.jpegThumbnail && { jpegThumbnail: externalAdReply.jpegThumbnail }) }, ...(options.mentionedJid && { mentionedJid: options.mentionedJid }) };
1549
+ }
1550
+ else if (options.mentionedJid) {
1551
+ messageContent.contextInfo = { mentionedJid: options.mentionedJid };
1552
+ }
1553
+ const payload = index_js_1.proto.Message.InteractiveMessage.create(messageContent);
1554
+ const msg = (0, index_js_3.generateWAMessageFromContent)(jid, { viewOnceMessage: { message: { interactiveMessage: payload } } }, { userJid, quoted: options?.quoted || null });
1555
+ const additionalNodes = [{ tag: 'biz', attrs: {}, content: [{ tag: 'interactive', attrs: { type: 'native_flow', v: '1' }, content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }] }] }];
1556
+ await relayMessage(jid, msg.message, { messageId: msg.key.id, additionalNodes });
1557
+ return msg;
1558
+ }
1559
+ // ── End Interactive Button ─────────────────────────────────────────
1560
+ if (typeof content === 'object' &&
1561
+ 'disappearingMessagesInChat' in content &&
1562
+ typeof content['disappearingMessagesInChat'] !== 'undefined' &&
1563
+ (0, index_js_4.isJidGroup)(jid)) {
1564
+ const { disappearingMessagesInChat } = content;
1565
+ const value = typeof disappearingMessagesInChat === 'boolean'
1566
+ ? disappearingMessagesInChat
1567
+ ? index_js_2.WA_DEFAULT_EPHEMERAL
1568
+ : 0
1569
+ : disappearingMessagesInChat;
1570
+ await groupToggleEphemeral(jid, value);
909
1571
  }
910
1572
  else {
911
1573
  let mediaHandle;
912
- const fullMsg = await (0, Utils_1.generateWAMessage)(jid, content, {
1574
+ const fullMsg = await (0, index_js_3.generateWAMessage)(jid, content, {
913
1575
  logger,
914
1576
  userJid,
915
- getUrlInfo: text => (0, link_preview_1.getUrlInfo)(text, {
1577
+ getUrlInfo: text => (0, link_preview_js_1.getUrlInfo)(text, {
916
1578
  thumbnailWidth: linkPreviewImageThumbnailWidth,
917
1579
  fetchOpts: {
918
1580
  timeout: 3000,
919
- ...axiosOptions || {}
1581
+ ...(httpRequestOptions || {})
920
1582
  },
921
1583
  logger,
922
- uploadImage: generateHighQualityLinkPreview
923
- ? waUploadToServer
924
- : undefined
1584
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
925
1585
  }),
1586
+ //TODO: CACHE
926
1587
  getProfilePicUrl: sock.profilePictureUrl,
927
- upload: async (readStream, opts) => {
928
- const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
1588
+ getCallLink: sock.createCallLink,
1589
+ newsletter: _isNewsletterJid(jid),
1590
+ upload: async (encFilePath, opts) => {
1591
+ const up = await waUploadToServer(encFilePath, { ...opts, newsletter: _isNewsletterJid(jid) });
929
1592
  mediaHandle = up.handle;
930
1593
  return up;
931
1594
  },
932
1595
  mediaCache: config.mediaCache,
933
1596
  options: config.options,
934
- messageId: (0, Utils_1.generateMessageIDV2)((_c = sock.user) === null || _c === void 0 ? void 0 : _c.id),
935
- ...options,
1597
+ messageId: (0, index_js_3.generateMessageIDV2)(sock.user?.id),
1598
+ ...options
936
1599
  });
1600
+ if (content?.audio && options?.contextInfo) {
1601
+ const msgContent = fullMsg.message;
1602
+ if (msgContent?.audioMessage) {
1603
+ msgContent.audioMessage.contextInfo = options.contextInfo;
1604
+ }
1605
+ }
1606
+ // Extract handle from newsletter upload (set by prepareWAMessageMedia)
1607
+ if (!mediaHandle) {
1608
+ const msgContent = fullMsg.message;
1609
+ const msgTypes = ['audioMessage', 'imageMessage', 'videoMessage', 'documentMessage', 'stickerMessage'];
1610
+ for (const t of msgTypes) {
1611
+ if (msgContent?.[t]?._uploadHandle) {
1612
+ mediaHandle = msgContent[t]._uploadHandle;
1613
+ delete msgContent[t]._uploadHandle;
1614
+ break;
1615
+ }
1616
+ }
1617
+ }
1618
+ const isEventMsg = 'event' in content && !!content.event;
937
1619
  const isDeleteMsg = 'delete' in content && !!content.delete;
938
1620
  const isEditMsg = 'edit' in content && !!content.edit;
939
1621
  const isPinMsg = 'pin' in content && !!content.pin;
940
- const isKeepMsg = 'keep' in content && content.keep;
941
1622
  const isPollMessage = 'poll' in content && !!content.poll;
942
- const isAiMsg = 'ai' in content && !!content.ai;
943
1623
  const additionalAttributes = {};
944
1624
  const additionalNodes = [];
945
1625
  // required for delete
946
1626
  if (isDeleteMsg) {
947
1627
  // if the chat is a group, and I am not the author, then delete the message as an admin
948
- if (((0, WABinary_1.isJidGroup)(content.delete.remoteJid) && !content.delete.fromMe) || (0, WABinary_1.isJidNewsletter)(jid)) {
1628
+ if ((0, index_js_4.isJidGroup)(content.delete?.remoteJid) && !content.delete?.fromMe) {
949
1629
  additionalAttributes.edit = '8';
950
1630
  }
951
1631
  else {
952
1632
  additionalAttributes.edit = '7';
953
1633
  }
954
- // required for edit message
955
1634
  }
956
1635
  else if (isEditMsg) {
957
- additionalAttributes.edit = (0, WABinary_1.isJidNewsletter)(jid) ? '3' : '1';
958
- // required for pin message
1636
+ additionalAttributes.edit = '1';
959
1637
  }
960
1638
  else if (isPinMsg) {
961
1639
  additionalAttributes.edit = '2';
962
- // required for keep message
963
- }
964
- else if (isKeepMsg) {
965
- additionalAttributes.edit = '6';
966
- // required for polling message
967
1640
  }
968
1641
  else if (isPollMessage) {
969
1642
  additionalNodes.push({
970
1643
  tag: 'meta',
971
1644
  attrs: {
972
1645
  polltype: 'creation'
973
- },
1646
+ }
974
1647
  });
975
- // required to display AI icon on message
976
1648
  }
977
- else if (isAiMsg) {
1649
+ else if (isEventMsg) {
978
1650
  additionalNodes.push({
1651
+ tag: 'meta',
979
1652
  attrs: {
980
- biz_bot: '1'
981
- },
982
- tag: "bot"
1653
+ event_type: 'creation'
1654
+ }
983
1655
  });
984
1656
  }
1657
+ // Auto-attach biz node for button/list/interactive messages
1658
+ const buttonType = getButtonType(fullMsg.message);
1659
+ if (buttonType) {
1660
+ const btnNode = getButtonArgs(fullMsg.message);
1661
+ if (btnNode)
1662
+ additionalNodes.push(btnNode);
1663
+ }
985
1664
  if (mediaHandle) {
986
1665
  additionalAttributes['media_id'] = mediaHandle;
987
1666
  }
988
- if ('cachedGroupMetadata' in options) {
989
- console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.');
990
- }
991
- await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, additionalAttributes, additionalNodes: isAiMsg ? additionalNodes : options.additionalNodes, statusJidList: options.statusJidList });
1667
+ await relayMessage(jid, fullMsg.message, {
1668
+ messageId: fullMsg.key.id,
1669
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1670
+ additionalAttributes,
1671
+ statusJidList: options.statusJidList,
1672
+ additionalNodes,
1673
+ AI: options.ai || false
1674
+ });
992
1675
  if (config.emitOwnEvents) {
993
- process.nextTick(() => {
994
- processingMutex.mutex(() => (upsertMessage(fullMsg, 'append')));
1676
+ process.nextTick(async () => {
1677
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
995
1678
  });
996
1679
  }
997
1680
  return fullMsg;