@pontalabs/baileys 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +839 -267
  3. package/WAProto/GenerateStatics.sh +4 -3
  4. package/WAProto/WAProto.proto +511 -1215
  5. package/WAProto/index.d.ts +55 -14017
  6. package/WAProto/index.js +29 -97689
  7. package/lib/Defaults/index.d.ts +74 -72
  8. package/lib/Defaults/index.js +108 -94
  9. package/lib/Signal/Group/ciphertext-message.d.ts +7 -8
  10. package/lib/Signal/Group/ciphertext-message.js +16 -9
  11. package/lib/Signal/Group/group-session-builder.d.ts +13 -11
  12. package/lib/Signal/Group/group-session-builder.js +61 -19
  13. package/lib/Signal/Group/group_cipher.d.ts +16 -14
  14. package/lib/Signal/Group/group_cipher.js +70 -41
  15. package/lib/Signal/Group/index.d.ts +11 -12
  16. package/lib/Signal/Group/index.js +61 -12
  17. package/lib/Signal/Group/keyhelper.d.ts +14 -9
  18. package/lib/Signal/Group/keyhelper.js +58 -10
  19. package/lib/Signal/Group/sender-chain-key.d.ts +13 -13
  20. package/lib/Signal/Group/sender-chain-key.js +34 -13
  21. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -16
  22. package/lib/Signal/Group/sender-key-distribution-message.js +36 -28
  23. package/lib/Signal/Group/sender-key-message.d.ts +18 -18
  24. package/lib/Signal/Group/sender-key-message.js +45 -38
  25. package/lib/Signal/Group/sender-key-name.d.ts +15 -14
  26. package/lib/Signal/Group/sender-key-name.js +31 -20
  27. package/lib/Signal/Group/sender-key-record.d.ts +25 -24
  28. package/lib/Signal/Group/sender-key-record.js +35 -18
  29. package/lib/Signal/Group/sender-key-state.d.ts +34 -29
  30. package/lib/Signal/Group/sender-key-state.js +100 -37
  31. package/lib/Signal/Group/sender-message-key.d.ts +10 -11
  32. package/lib/Signal/Group/sender-message-key.js +22 -15
  33. package/lib/Signal/libsignal.d.ts +8 -5
  34. package/lib/Signal/libsignal.js +142 -288
  35. package/lib/Signal/lid-mapping.d.ts +28 -23
  36. package/lib/Signal/lid-mapping.js +184 -277
  37. package/lib/Socket/Client/index.d.ts +2 -3
  38. package/lib/Socket/Client/index.js +22 -3
  39. package/lib/Socket/Client/types.d.ts +15 -15
  40. package/lib/Socket/Client/types.js +15 -8
  41. package/lib/Socket/Client/websocket.d.ts +12 -12
  42. package/lib/Socket/Client/websocket.js +37 -29
  43. package/lib/Socket/business.d.ts +178 -208
  44. package/lib/Socket/business.js +127 -91
  45. package/lib/Socket/chats.d.ts +93 -120
  46. package/lib/Socket/chats.js +652 -727
  47. package/lib/Socket/community.js +10 -2
  48. package/lib/Socket/groups.d.ts +111 -143
  49. package/lib/Socket/groups.js +186 -165
  50. package/lib/Socket/index.d.ts +183 -252
  51. package/lib/Socket/index.js +18 -38
  52. package/lib/Socket/messages-recv.d.ts +167 -196
  53. package/lib/Socket/messages-recv.js +1567 -1443
  54. package/lib/Socket/messages-send.d.ts +158 -192
  55. package/lib/Socket/messages-send.js +1208 -1102
  56. package/lib/Socket/newsletter.d.ts +138 -163
  57. package/lib/Socket/newsletter.js +249 -153
  58. package/lib/Socket/socket.d.ts +36 -50
  59. package/lib/Socket/socket.js +820 -749
  60. package/lib/Store/index.d.ts +4 -4
  61. package/lib/Store/index.js +24 -4
  62. package/lib/Store/make-in-memory-store.d.ts +104 -44
  63. package/lib/Store/make-in-memory-store.js +213 -199
  64. package/lib/Store/make-ordered-dictionary.d.ts +12 -13
  65. package/lib/Store/make-ordered-dictionary.js +45 -38
  66. package/lib/Store/object-repository.d.ts +10 -11
  67. package/lib/Store/object-repository.js +16 -9
  68. package/lib/Types/Auth.d.ts +97 -93
  69. package/lib/Types/Auth.js +3 -2
  70. package/lib/Types/Bussines.js +3 -2
  71. package/lib/Types/Call.d.ts +13 -14
  72. package/lib/Types/Call.js +3 -2
  73. package/lib/Types/Chat.d.ts +97 -78
  74. package/lib/Types/Chat.js +9 -8
  75. package/lib/Types/Contact.d.ts +9 -12
  76. package/lib/Types/Contact.js +3 -2
  77. package/lib/Types/Events.d.ts +176 -206
  78. package/lib/Types/Events.js +3 -2
  79. package/lib/Types/GroupMetadata.d.ts +48 -53
  80. package/lib/Types/GroupMetadata.js +3 -2
  81. package/lib/Types/Label.d.ts +14 -13
  82. package/lib/Types/Label.js +30 -24
  83. package/lib/Types/LabelAssociation.d.ts +20 -15
  84. package/lib/Types/LabelAssociation.js +12 -6
  85. package/lib/Types/Message.d.ts +412 -248
  86. package/lib/Types/Message.js +19 -17
  87. package/lib/Types/Product.d.ts +71 -58
  88. package/lib/Types/Product.js +3 -2
  89. package/lib/Types/Signal.d.ts +82 -71
  90. package/lib/Types/Signal.js +3 -2
  91. package/lib/Types/Socket.d.ts +81 -76
  92. package/lib/Types/Socket.js +3 -3
  93. package/lib/Types/State.d.ts +19 -75
  94. package/lib/Types/State.js +14 -56
  95. package/lib/Types/USync.d.ts +8 -8
  96. package/lib/Types/USync.js +3 -2
  97. package/lib/Types/index.d.ts +62 -47
  98. package/lib/Types/index.js +50 -26
  99. package/lib/Utils/auth-utils.d.ts +10 -13
  100. package/lib/Utils/auth-utils.js +472 -213
  101. package/lib/Utils/business.d.ts +20 -14
  102. package/lib/Utils/business.js +134 -110
  103. package/lib/Utils/chat-utils.d.ts +70 -88
  104. package/lib/Utils/chat-utils.js +403 -466
  105. package/lib/Utils/crypto.d.ts +46 -27
  106. package/lib/Utils/crypto.js +188 -117
  107. package/lib/Utils/decode-wa-message.d.ts +45 -58
  108. package/lib/Utils/decode-wa-message.js +311 -212
  109. package/lib/Utils/event-buffer.d.ts +17 -14
  110. package/lib/Utils/event-buffer.js +296 -321
  111. package/lib/Utils/generics.d.ts +92 -66
  112. package/lib/Utils/generics.js +436 -270
  113. package/lib/Utils/history.d.ts +23 -24
  114. package/lib/Utils/history.js +76 -106
  115. package/lib/Utils/index.d.ts +21 -24
  116. package/lib/Utils/index.js +41 -24
  117. package/lib/Utils/link-preview.d.ts +14 -12
  118. package/lib/Utils/link-preview.js +75 -40
  119. package/lib/Utils/logger.d.ts +11 -10
  120. package/lib/Utils/logger.js +7 -3
  121. package/lib/Utils/lt-hash.d.ts +14 -8
  122. package/lib/Utils/lt-hash.js +53 -3
  123. package/lib/Utils/make-mutex.d.ts +7 -7
  124. package/lib/Utils/make-mutex.js +44 -28
  125. package/lib/Utils/message-retry-manager.d.ts +88 -115
  126. package/lib/Utils/message-retry-manager.js +160 -265
  127. package/lib/Utils/messages-media.d.ts +107 -105
  128. package/lib/Utils/messages-media.js +619 -548
  129. package/lib/Utils/messages.d.ts +66 -52
  130. package/lib/Utils/messages.js +1558 -1730
  131. package/lib/Utils/noise-handler.d.ts +18 -18
  132. package/lib/Utils/noise-handler.js +130 -176
  133. package/lib/Utils/process-message.d.ts +32 -43
  134. package/lib/Utils/process-message.js +282 -484
  135. package/lib/Utils/rich-message-utils.js +724 -1220
  136. package/lib/Utils/signal.d.ts +32 -37
  137. package/lib/Utils/signal.js +105 -140
  138. package/lib/Utils/use-multi-file-auth-state.d.ts +5 -5
  139. package/lib/Utils/use-multi-file-auth-state.js +120 -70
  140. package/lib/Utils/use-single-file-auth-state.d.ts +13 -11
  141. package/lib/Utils/use-single-file-auth-state.js +66 -92
  142. package/lib/Utils/validate-connection.d.ts +13 -11
  143. package/lib/Utils/validate-connection.js +113 -128
  144. package/lib/WABinary/constants.d.ts +27 -25
  145. package/lib/WABinary/constants.js +1292 -1451
  146. package/lib/WABinary/decode.d.ts +9 -7
  147. package/lib/WABinary/decode.js +189 -163
  148. package/lib/WABinary/encode.d.ts +3 -3
  149. package/lib/WABinary/encode.js +155 -110
  150. package/lib/WABinary/generic-utils.d.ts +27 -14
  151. package/lib/WABinary/generic-utils.js +105 -177
  152. package/lib/WABinary/index.d.ts +5 -6
  153. package/lib/WABinary/index.js +25 -6
  154. package/lib/WABinary/jid-utils.d.ts +54 -41
  155. package/lib/WABinary/jid-utils.js +152 -83
  156. package/lib/WABinary/types.d.ts +13 -10
  157. package/lib/WABinary/types.js +3 -2
  158. package/lib/WAM/BinaryInfo.d.ts +15 -8
  159. package/lib/WAM/BinaryInfo.js +14 -7
  160. package/lib/WAM/constants.d.ts +37 -30
  161. package/lib/WAM/constants.js +11983 -19465
  162. package/lib/WAM/encode.d.ts +3 -3
  163. package/lib/WAM/encode.js +109 -95
  164. package/lib/WAM/index.d.ts +3 -4
  165. package/lib/WAM/index.js +23 -4
  166. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -9
  167. package/lib/WAUSync/Protocols/USyncContactProtocol.js +21 -37
  168. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -19
  169. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +26 -18
  170. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -11
  171. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +20 -12
  172. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -11
  173. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +24 -16
  174. package/lib/WAUSync/Protocols/index.d.ts +6 -6
  175. package/lib/WAUSync/Protocols/index.js +26 -6
  176. package/lib/WAUSync/USyncQuery.d.ts +28 -27
  177. package/lib/WAUSync/USyncQuery.js +62 -68
  178. package/lib/WAUSync/USyncUser.d.ts +10 -15
  179. package/lib/WAUSync/USyncUser.js +19 -20
  180. package/lib/WAUSync/index.d.ts +3 -4
  181. package/lib/WAUSync/index.js +23 -4
  182. package/lib/index.d.ts +12 -11
  183. package/lib/index.js +39 -12
  184. package/package.json +42 -86
  185. package/engine-requirements.js +0 -10
@@ -1,18 +1,21 @@
1
- import { Boom } from '@hapi/boom';
2
- import { randomBytes } from 'crypto';
3
- import { zip } from 'fflate';
4
- import { promises as fs } from 'fs';
5
- import {} from 'stream';
6
- import { proto } from '../../WAProto/index.js';
7
- import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, DONATE_URL, LIBRARY_NAME, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
8
- import { AssociationType, ButtonHeaderType, ButtonType, CarouselCardType, ListType, ProtocolType, WAMessageStatus, WAProto } from '../Types/index.js';
9
- import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidNormalizedUser } from '../WABinary/index.js';
10
- import { sha256 } from './crypto.js';
11
- import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
12
- import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getImageProcessingLibrary, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
13
- import { prepareRichResponseMessage } from './rich-message-utils.js';
14
- import { shouldIncludeReportingToken } from './reporting-utils.js';
15
- const CONCURRENCY_LIMIT = 10;
1
+ "use strict"
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ })
6
+
7
+ const boom_1 = require("@hapi/boom")
8
+ const crypto_1 = require("crypto")
9
+ const fs_1 = require("fs")
10
+ const WAProto_1 = require("../../WAProto")
11
+ const Defaults_1 = require("../Defaults")
12
+ const Types_1 = require("../Types")
13
+ const WABinary_1 = require("../WABinary")
14
+ const crypto_2 = require("./crypto")
15
+ const generics_1 = require("./generics")
16
+ const messages_media_1 = require("./messages-media")
17
+ const rich_message_utils_1 = require("./rich-message-utils")
18
+
16
19
  const MIMETYPE_MAP = {
17
20
  image: 'image/jpeg',
18
21
  video: 'video/mp4',
@@ -20,111 +23,117 @@ const MIMETYPE_MAP = {
20
23
  audio: 'audio/ogg; codecs=opus',
21
24
  sticker: 'image/webp',
22
25
  'product-catalog-image': 'image/jpeg'
23
- };
26
+ }
27
+
24
28
  const MessageTypeProto = {
25
- image: WAProto.Message.ImageMessage,
26
- video: WAProto.Message.VideoMessage,
27
- audio: WAProto.Message.AudioMessage,
28
- sticker: WAProto.Message.StickerMessage,
29
- document: WAProto.Message.DocumentMessage
30
- };
31
- /**
32
- * Uses a regex to test whether the string contains a URL, and returns the URL if it does.
33
- * @param text eg. hello https://google.com
34
- * @returns the URL, eg. https://google.com
35
- */
36
- export const extractUrlFromText = (text) => text.match(URL_REGEX)?.[0];
37
- export const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
38
- const url = extractUrlFromText(text);
29
+ 'image': Types_1.WAProto.Message.ImageMessage,
30
+ 'video': Types_1.WAProto.Message.VideoMessage,
31
+ 'audio': Types_1.WAProto.Message.AudioMessage,
32
+ 'sticker': Types_1.WAProto.Message.StickerMessage,
33
+ 'document': Types_1.WAProto.Message.DocumentMessage,
34
+ }
35
+
36
+ const extractUrlFromText = (text) => text.match(Defaults_1.URL_REGEX)?.[0]
37
+
38
+ const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
39
+ const url = extractUrlFromText(text)
39
40
  if (!!getUrlInfo && url) {
40
41
  try {
41
- const urlInfo = await getUrlInfo(url);
42
- return urlInfo;
43
- }
44
- catch (error) {
45
- // ignore if fails
46
- logger?.warn({ trace: error.stack }, 'url generation failed');
42
+ const urlInfo = await getUrlInfo(url)
43
+ return urlInfo
44
+ } catch (error) {
45
+ logger?.warn({
46
+ trace: error.stack
47
+ }, 'url generation failed')
47
48
  }
48
49
  }
49
- };
50
+ }
51
+
50
52
  const assertColor = async (color) => {
51
- let assertedColor;
53
+ let assertedColor
52
54
  if (typeof color === 'number') {
53
- assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1;
54
- }
55
- else {
56
- let hex = color.trim().replace('#', '');
55
+ assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1
56
+ } else {
57
+ let hex = color.trim().replace('#', '')
57
58
  if (hex.length <= 6) {
58
- hex = 'FF' + hex.padStart(6, '0');
59
+ hex = 'FF' + hex.padStart(6, '0')
59
60
  }
60
- assertedColor = parseInt(hex, 16);
61
- return assertedColor;
61
+ assertedColor = parseInt(hex, 16)
62
+ return assertedColor
62
63
  }
63
- };
64
- export const prepareWAMessageMedia = async (message, options) => {
65
- const logger = options.logger;
66
- let mediaType;
67
- for (const key of MEDIA_KEYS) {
64
+ }
65
+
66
+ const prepareWAMessageMedia = async (message, options) => {
67
+ const logger = options.logger
68
+ let mediaType
69
+
70
+ for (const key of Defaults_1.MEDIA_KEYS) {
68
71
  if (key in message) {
69
- mediaType = key;
72
+ mediaType = key
70
73
  }
71
74
  }
75
+
72
76
  if (!mediaType) {
73
- throw new Boom('Invalid media type', { statusCode: 400 });
77
+ throw new boom_1.Boom('Invalid media type', {
78
+ statusCode: 400
79
+ });
74
80
  }
81
+
75
82
  const uploadData = {
76
83
  ...message,
77
84
  media: message[mediaType]
78
- };
79
- delete uploadData[mediaType];
80
- // check if cacheable + generate cache key
81
- const cacheableKey = typeof uploadData.media === 'object' &&
82
- 'url' in uploadData.media &&
83
- !!uploadData.media.url &&
84
- !!options.mediaCache &&
85
- mediaType + ':' + uploadData.media.url.toString();
85
+ }
86
+ delete uploadData[mediaType]
87
+ const cacheableKey = typeof uploadData.media === 'object' && 'url' in uploadData.media && !!uploadData.media.url && !!options.mediaCache && mediaType + ':' + uploadData.media.url.toString()
88
+
86
89
  if (mediaType === 'document' && !uploadData.fileName) {
87
- uploadData.fileName = 'file';
90
+ uploadData.fileName = 'file'
88
91
  }
92
+
89
93
  if (!uploadData.mimetype) {
90
- uploadData.mimetype = MIMETYPE_MAP[mediaType];
94
+ uploadData.mimetype = MIMETYPE_MAP[mediaType]
91
95
  }
96
+
92
97
  if (cacheableKey) {
93
- const mediaBuff = await options.mediaCache.get(cacheableKey);
98
+ const mediaBuff = await options.mediaCache.get(cacheableKey)
94
99
  if (mediaBuff) {
95
- logger?.debug({ cacheableKey }, 'got media cache hit');
96
- const obj = proto.Message.decode(mediaBuff);
97
- const key = `${mediaType}Message`;
98
- Object.assign(obj[key], { ...uploadData, media: undefined });
99
- return obj;
100
+ logger?.debug({
101
+ cacheableKey
102
+ }, 'got media cache hit')
103
+ const obj = Types_1.WAProto.Message.decode(mediaBuff)
104
+ const key = `${mediaType}Message`
105
+ Object.assign(obj[key], {
106
+ ...uploadData,
107
+ media: undefined
108
+ })
109
+ return obj
100
110
  }
101
111
  }
102
- const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
112
+
113
+ const isNewsletter = !!options.jid && WABinary_1.isJidNewsletter(options.jid)
114
+
103
115
  if (isNewsletter) {
104
- logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
105
- const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
106
- const fileSha256B64 = fileSha256.toString('base64');
107
- // Generate thumbnail for image/video before uploading (filePath still exists)
108
- if ((mediaType === 'image' || mediaType === 'video') && !uploadData.jpegThumbnail) {
109
- try {
110
- const { thumbnail, originalImageDimensions } = await generateThumbnail(filePath, mediaType, options);
111
- uploadData.jpegThumbnail = thumbnail;
112
- if (!uploadData.width && originalImageDimensions) {
113
- uploadData.width = originalImageDimensions.width;
114
- uploadData.height = originalImageDimensions.height;
115
- }
116
- } catch (e) {
117
- logger?.warn({ err: e }, 'failed to generate thumbnail for newsletter media');
118
- }
119
- }
120
- const { mediaUrl, directPath } = await options.upload(filePath, {
116
+ logger?.info({
117
+ key: cacheableKey
118
+ }, 'Preparing raw media for newsletter')
119
+ const {
120
+ filePath,
121
+ fileSha256,
122
+ fileLength
123
+ } = await messages_media_1.getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger)
124
+ const fileSha256B64 = fileSha256.toString('base64')
125
+ const {
126
+ mediaUrl,
127
+ directPath
128
+ } = await options.upload(filePath, {
121
129
  fileEncSha256B64: fileSha256B64,
122
130
  mediaType: mediaType,
123
131
  timeoutMs: options.mediaUploadTimeoutMs
124
- });
125
- await fs.unlink(filePath);
126
- const obj = WAProto.Message.fromObject({
127
- // todo: add more support here
132
+ })
133
+
134
+ await fs_1.promises.unlink(filePath)
135
+
136
+ const obj = Types_1.WAProto.Message.fromObject({
128
137
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
129
138
  url: mediaUrl,
130
139
  directPath,
@@ -133,83 +142,114 @@ export const prepareWAMessageMedia = async (message, options) => {
133
142
  ...uploadData,
134
143
  media: undefined
135
144
  })
136
- });
145
+ })
146
+
137
147
  if (uploadData.ptv) {
138
- obj.ptvMessage = obj.videoMessage;
139
- delete obj.videoMessage;
140
- }
141
- if (obj.stickerMessage) {
142
- obj.stickerMessage.stickerSentTs = Date.now();
148
+ obj.ptvMessage = obj.videoMessage
149
+ delete obj.videoMessage
143
150
  }
151
+
144
152
  if (cacheableKey) {
145
- logger?.debug({ cacheableKey }, 'set cache');
146
- await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
147
- }
148
- return obj;
149
- }
150
- const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
151
- const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
152
- const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
153
- const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
154
- const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
155
- const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
153
+ logger?.debug({
154
+ cacheableKey
155
+ }, 'set cache');
156
+ await options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj).finish())
157
+ }
158
+
159
+ return obj
160
+ }
161
+
162
+ const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
163
+ const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined'
164
+ const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined'
165
+ const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
166
+ const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
167
+
168
+ const {
169
+ mediaKey,
170
+ encFilePath,
171
+ originalFilePath,
172
+ fileEncSha256,
173
+ fileSha256,
174
+ fileLength
175
+ } = await messages_media_1.encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
156
176
  logger,
157
177
  saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
158
178
  opts: options.options
159
- });
160
- const fileEncSha256B64 = fileEncSha256.toString('base64');
161
- const [{ mediaUrl, directPath }] = await Promise.all([
179
+ })
180
+
181
+ const fileEncSha256B64 = fileEncSha256.toString('base64')
182
+
183
+ const [{
184
+ mediaUrl,
185
+ directPath
186
+ }] = await Promise.all([
162
187
  (async () => {
163
188
  const result = await options.upload(encFilePath, {
164
189
  fileEncSha256B64,
165
190
  mediaType,
166
191
  timeoutMs: options.mediaUploadTimeoutMs
167
- });
168
- logger?.debug({ mediaType, cacheableKey }, 'uploaded media');
169
- return result;
192
+ })
193
+ logger?.debug({
194
+ mediaType,
195
+ cacheableKey
196
+ }, 'uploaded media')
197
+ return result
170
198
  })(),
199
+
171
200
  (async () => {
172
201
  try {
173
202
  if (requiresThumbnailComputation) {
174
- const { thumbnail, originalImageDimensions } = await generateThumbnail(originalFilePath, mediaType, options);
175
- uploadData.jpegThumbnail = thumbnail;
203
+ const {
204
+ thumbnail,
205
+ originalImageDimensions
206
+ } = await messages_media_1.generateThumbnail(originalFilePath, mediaType, options)
207
+ uploadData.jpegThumbnail = thumbnail
176
208
  if (!uploadData.width && originalImageDimensions) {
177
- uploadData.width = originalImageDimensions.width;
178
- uploadData.height = originalImageDimensions.height;
179
- logger?.debug('set dimensions');
209
+ uploadData.width = originalImageDimensions.width
210
+ uploadData.height = originalImageDimensions.height
211
+
212
+ logger?.debug('set dimensions')
180
213
  }
214
+
181
215
  logger?.debug('generated thumbnail');
182
216
  }
217
+
183
218
  if (requiresDurationComputation) {
184
- uploadData.seconds = await getAudioDuration(originalFilePath);
185
- logger?.debug('computed audio duration');
219
+ uploadData.seconds = await messages_media_1.getAudioDuration(originalFilePath)
220
+ logger?.debug('computed audio duration')
186
221
  }
222
+
187
223
  if (requiresWaveformProcessing) {
188
- uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
189
- logger?.debug('processed waveform');
224
+ uploadData.waveform = await messages_media_1.getAudioWaveform(originalFilePath, logger)
225
+ logger?.debug('processed waveform')
190
226
  }
227
+
191
228
  if (requiresAudioBackground) {
192
- uploadData.backgroundArgb = await assertColor(options.backgroundColor);
193
- logger?.debug('computed backgroundColor audio status');
229
+ uploadData.backgroundArgb = await assertColor(options.backgroundColor)
230
+ logger?.debug('computed backgroundColor audio status')
194
231
  }
195
- }
196
- catch (error) {
197
- logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
232
+ } catch (error) {
233
+ logger?.warn({
234
+ trace: error.stack
235
+ }, 'failed to obtain extra info')
198
236
  }
199
237
  })()
200
238
  ]).finally(async () => {
201
239
  try {
202
- await fs.unlink(encFilePath);
240
+ await fs_1.promises.unlink(encFilePath)
241
+
203
242
  if (originalFilePath) {
204
- await fs.unlink(originalFilePath);
243
+ await fs_1.promises.unlink(originalFilePath)
205
244
  }
206
- logger?.debug('removed tmp files');
207
- }
208
- catch (error) {
209
- logger?.warn('failed to remove tmp file');
210
- }
211
- });
212
- const obj = WAProto.Message.fromObject({
245
+
246
+ logger?.debug('removed tmp files')
247
+ } catch (error) {
248
+ logger?.warn('failed to remove tmp file')
249
+ }
250
+ })
251
+
252
+ const obj = Types_1.WAProto.Message.fromObject({
213
253
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
214
254
  url: mediaUrl,
215
255
  directPath,
@@ -217,827 +257,850 @@ export const prepareWAMessageMedia = async (message, options) => {
217
257
  fileEncSha256,
218
258
  fileSha256,
219
259
  fileLength,
220
- mediaKeyTimestamp: unixTimestampSeconds(),
260
+ mediaKeyTimestamp: generics_1.unixTimestampSeconds(),
221
261
  ...uploadData,
222
262
  media: undefined
223
263
  })
224
- });
264
+ })
265
+
225
266
  if (uploadData.ptv) {
226
- obj.ptvMessage = obj.videoMessage;
227
- delete obj.videoMessage;
267
+ obj.ptvMessage = obj.videoMessage
268
+ delete obj.videoMessage
228
269
  }
270
+
229
271
  if (cacheableKey) {
230
- logger?.debug({ cacheableKey }, 'set cache');
231
- await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
272
+ logger?.debug({
273
+ cacheableKey
274
+ }, 'set cache');
275
+ await options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj).finish())
232
276
  }
233
- return obj;
234
- };
235
- export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) => {
236
- ephemeralExpiration = ephemeralExpiration || 0;
277
+
278
+ return obj
279
+ }
280
+
281
+ const prepareAlbumMessageContent = async (jid, albums, options) => {
282
+ let mediaHandle
283
+ let mediaMsg
284
+ let message = []
285
+
286
+ const albumMsg = generateWAMessageFromContent(jid, {
287
+ albumMessage: {
288
+ expectedImageCount: albums.filter(item => 'image' in item).length,
289
+ expectedVideoCount: albums.filter(item => 'video' in item).length
290
+ }
291
+ }, options)
292
+
293
+ await options.sock.relayMessage(jid, albumMsg.message, {
294
+ messageId: albumMsg.key.id
295
+ })
296
+
297
+ for (const i in albums) {
298
+ const media = albums[i]
299
+
300
+ if ('image' in media) {
301
+ mediaMsg = await generateWAMessage(jid, {
302
+ image: media.image,
303
+ ...media,
304
+ ...options
305
+ }, {
306
+ userJid: options.userJid,
307
+ upload: async (encFilePath, opts) => {
308
+ const up = await options.sock.waUploadToServer(encFilePath, {
309
+ ...opts,
310
+ newsletter: WABinary_1.isJidNewsletter(jid)
311
+ })
312
+ mediaHandle = up.handle
313
+ return up
314
+ },
315
+ ...options
316
+ })
317
+ } else if ('video' in media) {
318
+ mediaMsg = await generateWAMessage(jid, {
319
+ video: media.video,
320
+ ...media,
321
+ ...options
322
+ }, {
323
+ userJid: options.userJid,
324
+ upload: async (encFilePath, opts) => {
325
+ const up = await options.sock.waUploadToServer(encFilePath, {
326
+ ...opts,
327
+ newsletter: WABinary_1.isJidNewsletter(jid)
328
+ })
329
+ mediaHandle = up.handle
330
+ return up
331
+ },
332
+ ...options
333
+ })
334
+ }
335
+
336
+ if (mediaMsg) {
337
+ mediaMsg.message.messageContextInfo = {
338
+ messageSecret: crypto_1.randomBytes(32),
339
+ messageAssociation: {
340
+ associationType: 1,
341
+ parentMessageKey: albumMsg.key
342
+ }
343
+ }
344
+ }
345
+
346
+ message.push(mediaMsg)
347
+ }
348
+
349
+ return message
350
+ }
351
+
352
+ const prepareDisappearingMessageSettingContent = (expiration) => {
237
353
  const content = {
238
354
  ephemeralMessage: {
239
355
  message: {
240
356
  protocolMessage: {
241
- type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
242
- ephemeralExpiration
357
+ type: Types_1.WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
358
+ ephemeralExpiration: expiration ? expiration : 0
243
359
  }
244
360
  }
245
361
  }
246
- };
247
- return WAProto.Message.fromObject(content);
248
- };
249
- /**
250
- * Generate forwarded message content like WA does
251
- * @param message the message to forward
252
- * @param options.forceForward will show the message as forwarded even if it is from you
253
- */
254
- export const generateForwardMessageContent = (message, forceForward) => {
255
- let content = message.message;
362
+ }
363
+
364
+ return Types_1.WAProto.Message.fromObject(content)
365
+ }
366
+
367
+ const generateForwardMessageContent = (message, forceForward) => {
368
+ let content = message.message
256
369
  if (!content) {
257
- throw new Boom('no content in message', { statusCode: 400 });
370
+ throw new boom_1.Boom('no content in message', {
371
+ statusCode: 400
372
+ })
258
373
  }
259
- // hacky copy
260
- content = normalizeMessageContent(content);
261
- content = proto.Message.decode(proto.Message.encode(content).finish());
262
- let key = Object.keys(content)[0];
263
- let score = content?.[key]?.contextInfo?.forwardingScore || 0;
264
- score += message.key.fromMe && !forceForward ? 0 : 1;
374
+
375
+ content = normalizeMessageContent(content)
376
+ content = WAProto_1.proto.Message.decode(WAProto_1.proto.Message.encode(content).finish())
377
+
378
+ let key = Object.keys(content)[0]
379
+ let score = content?.[key]?.contextInfo?.forwardingScore || 0
380
+
381
+ score += message.key.fromMe && !forceForward ? 0 : 1
382
+
265
383
  if (key === 'conversation') {
266
- content.extendedTextMessage = { text: content[key] };
267
- delete content.conversation;
268
- key = 'extendedTextMessage';
269
- }
270
- const key_ = content?.[key];
271
- if (score > 0) {
272
- key_.contextInfo = { forwardingScore: score, isForwarded: true };
273
- }
274
- else {
275
- key_.contextInfo = {};
276
- }
277
- return content;
278
- };
279
- export const hasNonNullishProperty = (message, key) => {
280
- return message != null &&
281
- typeof message === 'object' &&
282
- key in message &&
283
- message[key] != null;
284
- };
285
- export const hasOptionalProperty = (obj, key) => {
286
- return obj != null &&
287
- typeof obj === 'object' &&
288
- key in obj &&
289
- obj[key] != null;
290
- };
291
- // Validate album message media to avoid bug
292
- export const hasValidAlbumMedia = (message) => {
293
- return message.imageMessage ||
294
- message.videoMessage;
295
- };
296
- export const hasValidInteractiveHeader = (message) => {
297
- return message.imageMessage ||
298
- message.videoMessage ||
299
- message.documentMessage ||
300
- message.productMessage ||
301
- message.locationMessage;
302
- };
303
- // Validate carousel cards header to avoid bug
304
- export const hasValidCarouselHeader = (message) => {
305
- return message.imageMessage ||
306
- message.videoMessage ||
307
- message.productMessage;
308
- };
309
- // Extract product message into a standalone function so it can also be reused as the header for interactive messages
310
- const prepareProductMessage = async (message, options) => {
311
- if (!message.businessOwnerJid) {
312
- throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
313
- }
314
- const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
315
- const content = {
316
- ...message,
317
- product: {
318
- currencyCode: 'IDR',
319
- priceAmount1000: 1000,
320
- title: LIBRARY_NAME,
321
- ...message.product,
322
- productImage: imageMessage
323
- }
324
- };
325
- delete content.image;
326
- return content;
327
- };
328
- const prepareStickerPackMessage = async (message, options) => {
329
- const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'GitHub: pontalabs', description = '🏷️ pontalabs/baileys' } = message;
330
- if (stickers.length > 60) {
331
- throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
332
- }
333
- if (stickers.length === 0) {
334
- throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
335
- }
336
- if (!cover) {
337
- throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
338
- }
339
- const logger = options.logger;
340
- let cacheableKey = false;
341
- if (Array.isArray(stickers) && stickers.length && options.mediaCache) {
342
- const urls = [];
343
- for (let i = 0; i < stickers.length; i++) {
344
- const data = stickers[i].data;
345
- if (typeof data === 'object' && data?.url) {
346
- urls.push(data.url);
347
- }
348
- }
349
- if (urls.length > 0) {
350
- cacheableKey = 'sticker:' + urls.join('@');
384
+ content.extendedTextMessage = {
385
+ text: content[key]
351
386
  }
387
+ delete content.conversation
388
+ key = 'extendedTextMessage'
352
389
  }
353
- if (cacheableKey) {
354
- const mediaBuff = await options.mediaCache.get(cacheableKey);
355
- if (mediaBuff) {
356
- logger?.debug({ cacheableKey }, 'got media cache hit');
357
- return proto.Message.StickerPackMessage.decode(mediaBuff);
390
+
391
+ if (score > 0) {
392
+ content[key].contextInfo = {
393
+ forwardingScore: score,
394
+ isForwarded: true
358
395
  }
396
+ } else {
397
+ content[key].contextInfo = {}
359
398
  }
360
- const lib = await getImageProcessingLibrary();
361
- const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
362
- const hasImage = 'image' in lib && !!lib.image?.Transformer;
363
- const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
364
- if (!hasSharp && !hasImage) {
365
- throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP.');
366
- }
367
- const stickerPackIdValue = generateMessageIDV2();
368
- const stickerData = {};
369
- const stickerMetadata = new Array(stickers.length);
370
- for (let i = 0; i < stickers.length; i += CONCURRENCY_LIMIT) {
371
- const promises = [];
372
- const chunkEnd = Math.min(i + CONCURRENCY_LIMIT, stickers.length);
373
- for (let j = i; j < chunkEnd; j++) {
374
- promises.push((async (index) => {
375
- const sticker = stickers[index];
376
- const { stream } = await getStream(sticker.data);
377
- const buffer = await toBuffer(stream);
378
- let webpBuffer;
379
- let isAnimated = false;
380
- if (isWebPBuffer(buffer)) {
381
- webpBuffer = buffer;
382
- isAnimated = isAnimatedWebP(buffer);
383
- }
384
- else if (hasSharp) {
385
- webpBuffer = await lib.sharp.default(buffer)
386
- .resize(512, 512, { fit: 'inside' })
387
- .webp({ quality: 80 })
388
- .toBuffer();
389
- }
390
- else {
391
- webpBuffer = await new lib.image.Transformer(buffer)
392
- .resize(512, 512)
393
- .webp(80);
394
- }
395
- if (webpBuffer.length > 1024 * 1024) {
396
- throw new Boom(`Sticker at index ${index} exceeds the 1MB size limit`, { statusCode: 400 });
397
- }
398
- const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
399
- const fileName = `${hash}.webp`;
400
- stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
401
- stickerMetadata[index] = {
402
- fileName,
403
- mimetype: 'image/webp',
404
- isAnimated,
405
- emojis: sticker.emojis || ['✨'],
406
- accessibilityLabel: sticker.accessibilityLabel || '‎'
407
- };
408
- })(j));
409
- }
410
- await Promise.all(promises);
411
- }
412
- const trayIconFileName = `${stickerPackIdValue}.webp`;
413
- const { stream: coverStream } = await getStream(cover);
414
- const coverBuffer = await toBuffer(coverStream);
415
- let coverWebpBuffer;
416
- if (isWebPBuffer(coverBuffer)) {
417
- coverWebpBuffer = coverBuffer;
418
- }
419
- else if (hasSharp) {
420
- coverWebpBuffer = await lib.sharp.default(coverBuffer)
421
- .resize(512, 512, { fit: 'inside' })
422
- .webp({ quality: 80 })
423
- .toBuffer();
424
- }
425
- else {
426
- coverWebpBuffer = await new lib.image.Transformer(coverBuffer)
427
- .resize(512, 512)
428
- .webp(80);
429
- }
430
- stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
431
- const zipBuffer = await new Promise((resolve, reject) => {
432
- zip(stickerData, (error, data) => error ? reject(error) : resolve(Buffer.from(data)));
433
- });
434
- const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
435
- logger,
436
- opts: options.options
437
- });
438
- let stickerPackUploadResult;
439
- try {
440
- stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
441
- fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
442
- mediaType: 'sticker-pack',
443
- timeoutMs: options.mediaUploadTimeoutMs
444
- });
445
- }
446
- finally {
447
- fs.unlink(stickerPackUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
448
- }
449
- const obj = {
450
- name,
451
- publisher,
452
- stickerPackId: stickerPackIdValue,
453
- packDescription: description,
454
- stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
455
- stickerPackSize: zipBuffer.length,
456
- stickers: stickerMetadata,
457
- fileSha256: stickerPackUpload.fileSha256,
458
- fileEncSha256: stickerPackUpload.fileEncSha256,
459
- mediaKey: stickerPackUpload.mediaKey,
460
- directPath: stickerPackUploadResult.directPath,
461
- fileLength: stickerPackUpload.fileLength,
462
- mediaKeyTimestamp: unixTimestampSeconds(),
463
- trayIconFileName
464
- };
465
- try {
466
- let thumbnailBuffer;
467
- if (hasSharp) {
468
- thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
469
- }
470
- else if (hasImage) {
471
- thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
472
- }
473
- else if (hasJimp) {
474
- const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
475
- thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
476
- }
477
- else {
478
- throw new Error('No image processing library available for thumbnail generation');
479
- }
480
- if (!thumbnailBuffer || thumbnailBuffer.length === 0) {
481
- throw new Error('Failed to generate thumbnail buffer');
482
- }
483
- const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
484
- logger,
485
- opts: options.options,
486
- mediaKey: stickerPackUpload.mediaKey
487
- });
488
- let thumbUploadResult;
489
- try {
490
- thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
491
- fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
492
- mediaType: 'thumbnail-sticker-pack',
493
- timeoutMs: options.mediaUploadTimeoutMs
494
- });
495
- }
496
- finally {
497
- fs.unlink(thumbUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
498
- }
499
- Object.assign(obj, {
500
- thumbnailDirectPath: thumbUploadResult.directPath,
501
- thumbnailSha256: thumbUpload.fileSha256,
502
- thumbnailEncSha256: thumbUpload.fileEncSha256,
503
- thumbnailHeight: 252,
504
- thumbnailWidth: 252,
505
- imageDataHash: sha256(thumbnailBuffer).toString('base64')
506
- });
507
- }
508
- catch (error) {
509
- logger?.warn(`Thumbnail generation failed: ${error}`);
510
- }
511
- if (cacheableKey) {
512
- logger?.debug({ cacheableKey }, 'set cache (background)');
513
- options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(obj).finish());
514
- }
515
- return WAProto.Message.StickerPackMessage.fromObject(obj);
516
- };
517
- const prepareNativeFlowButtons = (message) => {
518
- const buttons = message.nativeFlow;
519
- const isButtonsFieldArray = Array.isArray(buttons);
520
- const correctedField = isButtonsFieldArray ? buttons : buttons.buttons;
521
- const messageParamsJson = {};
522
- if (hasOptionalProperty(message, 'offerText') && !!message.offerText) {
523
- Object.assign(messageParamsJson, {
524
- limited_time_offer: {
525
- text: message.offerText || LIBRARY_NAME,
526
- url: (message.offerUrl && /^https?:\/\//i.test(message.offerUrl)) ? message.offerUrl : DONATE_URL,
527
- copy_code: message.offerCode,
528
- expiration_time: message.offerExpiration
529
- }
530
- });
531
- }
532
- if (hasOptionalProperty(message, 'optionText') && !!message.optionText) {
533
- Object.assign(messageParamsJson, {
534
- bottom_sheet: {
535
- in_thread_buttons_limit: 1,
536
- divider_indices: Array.from({ length: correctedField.length }, (_, index) => index),
537
- list_title: message.optionTitle || '📄 Select Options',
538
- button_title: message.optionText
539
- }
540
- });
541
- }
542
- return {
543
- buttons: correctedField.map(button => {
544
- const buttonText = button.text || button.buttonText;
545
- const buttonIcon = button.icon?.toUpperCase();
546
- if (hasOptionalProperty(button, 'id') && !!button.id) {
547
- return { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: buttonText || '👉🏻 Click', id: button.id, icon: buttonIcon }) };
548
- }
549
- else if (hasOptionalProperty(button, 'copy') && !!button.copy) {
550
- return { name: 'cta_copy', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📋 Copy', copy_code: button.copy, icon: buttonIcon }) };
551
- }
552
- else if (hasOptionalProperty(button, 'url') && !!button.url) {
553
- return { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: buttonText || '🌐 Visit', url: button.url, merchant_url: button.url, webview_interaction: button.useWebview, icon: buttonIcon }) };
554
- }
555
- else if (hasOptionalProperty(button, 'call') && !!button.call) {
556
- return { name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📞 Call', phone_number: button.call, icon: buttonIcon }) };
557
- }
558
- else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
559
- return { name: 'single_select', buttonParamsJson: JSON.stringify({ title: buttonText || '📋 Select', sections: button.sections, icon: buttonIcon }) };
560
- }
561
- return button;
562
- }),
563
- messageParamsJson: JSON.stringify(messageParamsJson),
564
- messageVersion: 3
565
- };
566
- };
567
- export const generateWAMessageContent = async (message, options) => {
568
- var _a, _b;
569
- let m = {};
570
- if (hasNonNullishProperty(message, 'raw')) {
571
- delete message.raw;
572
- return message;
573
- }
574
- else if (hasNonNullishProperty(message, 'code') ||
575
- hasNonNullishProperty(message, 'links') ||
576
- hasNonNullishProperty(message, 'table') ||
577
- hasNonNullishProperty(message, 'aiBlocks') ||
578
- hasNonNullishProperty(message, 'latex') ||
579
- hasNonNullishProperty(message, 'gridImage') ||
580
- hasNonNullishProperty(message, 'inlineImage') ||
581
- hasNonNullishProperty(message, 'dynamic') ||
582
- hasNonNullishProperty(message, 'map') ||
583
- hasNonNullishProperty(message, 'contentItems') ||
584
- // tipe baru (nama prefixed 'rich' agar tidak konflik dengan media biasa)
585
- hasNonNullishProperty(message, 'richImage') ||
586
- hasNonNullishProperty(message, 'richVideo') ||
587
- hasNonNullishProperty(message, 'reels') ||
588
- hasNonNullishProperty(message, 'source') ||
589
- hasNonNullishProperty(message, 'richProduct') ||
590
- hasNonNullishProperty(message, 'richPost') ||
591
- hasNonNullishProperty(message, 'tip') ||
592
- hasNonNullishProperty(message, 'suggest')) {
593
- m = prepareRichResponseMessage(message);
594
- }
595
- else if (hasNonNullishProperty(message, 'text')) {
596
- const extContent = { text: message.text };
597
- let urlInfo = message.linkPreview;
399
+
400
+ return content
401
+ }
402
+
403
+ const generateWAMessageContent = async (message, options) => {
404
+ let m = {}
405
+ if ('code' in message || 'codes' in message || 'table' in message || 'items' in message || 'richText' in message || 'richResponse' in message || 'richImages' in message || 'richVideo' in message || 'richSuggestions' in message || 'richQuestion' in message || 'richLatex' in message || 'richProduct' in message || 'richPost' in message || 'richReels' in message || 'richSources' in message || 'richTip' in message || 'richFooter' in message) {
406
+ return rich_message_utils_1.prepareRichResponseMessage(message)
407
+ } else if ('text' in message) {
408
+ const extContent = {
409
+ text: message.text
410
+ }
411
+ let urlInfo = message.linkPreview
412
+
598
413
  if (typeof urlInfo === 'undefined') {
599
- urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger);
414
+ urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger)
600
415
  }
416
+
601
417
  if (urlInfo) {
602
- extContent.matchedText = urlInfo['matched-text'];
603
- extContent.jpegThumbnail = urlInfo.jpegThumbnail;
604
- extContent.description = urlInfo.description;
605
- extContent.title = urlInfo.title;
606
- extContent.previewType = 0;
607
- const img = urlInfo.highQualityThumbnail;
418
+ extContent.canonicalUrl = urlInfo['canonical-url']
419
+ extContent.matchedText = urlInfo['matched-text']
420
+ extContent.jpegThumbnail = urlInfo.jpegThumbnail
421
+ extContent.description = urlInfo.description
422
+ extContent.title = urlInfo.title
423
+ extContent.previewType = 0
424
+ const img = urlInfo.highQualityThumbnail
425
+
608
426
  if (img) {
609
- extContent.thumbnailDirectPath = img.directPath;
610
- extContent.mediaKey = img.mediaKey;
611
- extContent.mediaKeyTimestamp = img.mediaKeyTimestamp;
612
- extContent.thumbnailWidth = img.width;
613
- extContent.thumbnailHeight = img.height;
614
- extContent.thumbnailSha256 = img.fileSha256;
615
- extContent.thumbnailEncSha256 = img.fileEncSha256;
427
+ extContent.thumbnailDirectPath = img.directPath
428
+ extContent.mediaKey = img.mediaKey
429
+ extContent.mediaKeyTimestamp = img.mediaKeyTimestamp
430
+ extContent.thumbnailWidth = img.width
431
+ extContent.thumbnailHeight = img.height
432
+ extContent.thumbnailSha256 = img.fileSha256
433
+ extContent.thumbnailEncSha256 = img.fileEncSha256
616
434
  }
617
435
  }
436
+
618
437
  if (options.backgroundColor) {
619
- extContent.backgroundArgb = await assertColor(options.backgroundColor);
438
+ extContent.backgroundArgb = await assertColor(options.backgroundColor)
620
439
  }
621
- if (options.font) {
622
- extContent.font = options.font;
440
+
441
+ if (options.textColor) {
442
+ extContent.textArgb = await assertColor(options.textColor)
623
443
  }
624
- m.extendedTextMessage = extContent;
625
- }
626
- else if (hasNonNullishProperty(message, 'contacts')) {
627
- const contactLen = message.contacts.contacts.length;
444
+
445
+ if (options.font) {
446
+ extContent.font = options.font
447
+ }
448
+
449
+ extContent.contextInfo = {
450
+ ...(message.contextInfo || {}),
451
+ ...(message.mentions ? {
452
+ mentionedJid: message.mentions
453
+ } : {})
454
+ }
455
+
456
+ m.extendedTextMessage = extContent
457
+ } else if ('contacts' in message) {
458
+ const contactLen = message.contacts.contacts.length
459
+
460
+ let contactMessage
461
+
628
462
  if (!contactLen) {
629
- throw new Boom('require atleast 1 contact', { statusCode: 400 });
463
+ throw new boom_1.Boom('require atleast 1 contact', {
464
+ statusCode: 400
465
+ })
630
466
  }
467
+
631
468
  if (contactLen === 1) {
632
- m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
469
+ contactMessage = {
470
+ contactMessage: Types_1.WAProto.Message.ContactMessage.fromObject(message.contacts.contacts[0])
471
+ }
472
+ } else {
473
+ contactMessage = {
474
+ contactsArrayMessage: Types_1.WAProto.Message.ContactsArrayMessage.fromObject(message.contacts)
475
+ }
633
476
  }
634
- else {
635
- m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
477
+
478
+ const [type] = Object.keys(contactMessage)
479
+
480
+ contactMessage[type].contextInfo = {
481
+ ...(message.contextInfo || {}),
482
+ ...(message.mentions ? {
483
+ mentionedJid: message.mentions
484
+ } : {})
485
+ }
486
+
487
+ m = contactMessage
488
+ } else if ('location' in message) {
489
+ let locationMessage
490
+
491
+ if (message.live) {
492
+ locationMessage = {
493
+ liveLocationMessage: Types_1.WAProto.Message.LiveLocationMessage.fromObject(message.location)
494
+ }
495
+ } else {
496
+ locationMessage = {
497
+ locationMessage: Types_1.WAProto.Message.LocationMessage.fromObject(message.location)
498
+ }
636
499
  }
637
- }
638
- else if (hasNonNullishProperty(message, 'location')) {
639
- m.locationMessage = WAProto.Message.LocationMessage.create(message.location);
640
- }
641
- else if (hasNonNullishProperty(message, 'react')) {
500
+
501
+ const [type] = Object.keys(locationMessage)
502
+
503
+ locationMessage[type].contextInfo = {
504
+ ...(message.contextInfo || {}),
505
+ ...(message.mentions ? {
506
+ mentionedJid: message.mentions
507
+ } : {})
508
+ }
509
+
510
+ m = locationMessage
511
+ } else if ('react' in message) {
642
512
  if (!message.react.senderTimestampMs) {
643
- message.react.senderTimestampMs = Date.now();
513
+ message.react.senderTimestampMs = Date.now()
644
514
  }
645
- m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
646
- }
647
- else if (hasNonNullishProperty(message, 'delete')) {
515
+
516
+ m.reactionMessage = Types_1.WAProto.Message.ReactionMessage.fromObject(message.react)
517
+ } else if ('delete' in message) {
648
518
  m.protocolMessage = {
649
519
  key: message.delete,
650
- type: WAProto.Message.ProtocolMessage.Type.REVOKE
651
- };
652
- }
653
- else if (hasNonNullishProperty(message, 'forward')) {
654
- m = generateForwardMessageContent(message.forward, message.force);
655
- }
656
- else if (hasNonNullishProperty(message, 'disappearingMessagesInChat')) {
657
- const exp = typeof message.disappearingMessagesInChat === 'boolean'
658
- ? message.disappearingMessagesInChat
659
- ? WA_DEFAULT_EPHEMERAL
660
- : 0
661
- : message.disappearingMessagesInChat;
662
- m = prepareDisappearingMessageSettingContent(exp);
663
- }
664
- else if (hasNonNullishProperty(message, 'groupInvite')) {
665
- m.groupInviteMessage = {};
666
- m.groupInviteMessage.inviteCode = message.groupInvite.inviteCode;
667
- m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration;
668
- m.groupInviteMessage.caption = message.groupInvite.text;
669
- m.groupInviteMessage.groupJid = message.groupInvite.jid;
670
- m.groupInviteMessage.groupName = message.groupInvite.subject;
671
- //TODO: use built-in interface and get disappearing mode info etc.
672
- //TODO: cache / use store!?
520
+ type: Types_1.WAProto.Message.ProtocolMessage.Type.REVOKE
521
+ }
522
+ } else if ('forward' in message) {
523
+ const mess = generateForwardMessageContent(message.forward, message.force)
524
+ const [type] = Object.keys(mess)
525
+
526
+ mess[type].contextInfo = {
527
+ ...(message.contextInfo || {}),
528
+ ...(message.mentions ? {
529
+ mentionedJid: message.mentions
530
+ } : {})
531
+ }
532
+
533
+ m = mess
534
+ } else if ('disappearingMessagesInChat' in message) {
535
+ const exp = typeof message.disappearingMessagesInChat === 'boolean' ? (message.disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) : message.disappearingMessagesInChat
536
+ m = prepareDisappearingMessageSettingContent(exp)
537
+ } else if ('groupInvite' in message) {
538
+ m.groupInviteMessage = {}
539
+
540
+ m.groupInviteMessage.inviteCode = message.groupInvite.code
541
+ m.groupInviteMessage.inviteExpiration = message.groupInvite.expiration
542
+ m.groupInviteMessage.caption = message.groupInvite.caption
543
+ m.groupInviteMessage.groupJid = message.groupInvite.jid
544
+ m.groupInviteMessage.groupName = message.groupInvite.name
545
+ m.groupInviteMessage.contextInfo = message.contextInfo
546
+
547
+ if (options.getProfilePicUrl) {
548
+ const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid)
549
+ const { thumbnail } = await messages_media_1.generateThumbnail(pfpUrl, 'image')
550
+ m.groupInviteMessage.jpegThumbnail = thumbnail
551
+ }
552
+
553
+ m.groupInviteMessage.contextInfo = {
554
+ ...(message.contextInfo || {}),
555
+ ...(message.mentions ? {
556
+ mentionedJid: message.mentions
557
+ } : {})
558
+ }
559
+ } else if ('adminInvite' in message) {
560
+ m.newsletterAdminInviteMessage = {}
561
+
562
+ m.newsletterAdminInviteMessage.newsletterJid = message.adminInvite.jid
563
+ m.newsletterAdminInviteMessage.newsletterName = message.adminInvite.name
564
+ m.newsletterAdminInviteMessage.caption = message.adminInvite.caption
565
+ m.newsletterAdminInviteMessage.inviteExpiration = message.adminInvite.expiration
566
+ m.newsletterAdminInviteMessage.contextInfo = message.contextInfo
567
+
673
568
  if (options.getProfilePicUrl) {
674
- const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview');
675
- if (pfpUrl) {
676
- const resp = await fetch(pfpUrl, { method: 'GET', dispatcher: options?.options?.dispatcher });
677
- if (resp.ok) {
678
- const buf = Buffer.from(await resp.arrayBuffer());
679
- m.groupInviteMessage.jpegThumbnail = buf;
569
+ const pfpUrl = await options.getProfilePicUrl(message.adminInvite.jid)
570
+ const {
571
+ thumbnail
572
+ } = await messages_media_1.generateThumbnail(pfpUrl, 'image')
573
+ m.newsletterAdminInviteMessage.jpegThumbnail = thumbnail
574
+ }
575
+
576
+ m.newsletterAdminInviteMessage.contextInfo = {
577
+ ...(message.contextInfo || {}),
578
+ ...(message.mentions ? {
579
+ mentionedJid: message.mentions
580
+ } : {})
581
+ }
582
+ } else if ('pin' in message) {
583
+ m.pinInChatMessage = {}
584
+ m.messageContextInfo = {}
585
+
586
+ m.pinInChatMessage.key = message.pin.key
587
+ m.pinInChatMessage.type = message.pin?.type || 1
588
+ m.pinInChatMessage.senderTimestampMs = message.pin?.time || Date.now()
589
+ m.messageContextInfo.messageAddOnDurationInSecs = message.pin.type === 1 ? message.pin.time || 86400 : 0
590
+ m.messageContextInfo.messageAddOnExpiryType = WAProto_1.proto.MessageContextInfo.MessageAddonExpiryType.STATIC
591
+ } else if ('keep' in message) {
592
+ m.keepInChatMessage = {}
593
+
594
+ m.keepInChatMessage.key = message.keep.key
595
+ m.keepInChatMessage.keepType = message.keep?.type || 1
596
+ m.keepInChatMessage.timestampMs = message.keep?.time || Date.now()
597
+ } else if ('call' in message) {
598
+ m.scheduledCallCreationMessage = {}
599
+
600
+ m.scheduledCallCreationMessage.scheduledTimestampMs = message.call?.time || Date.now()
601
+ m.scheduledCallCreationMessage.callType = message.call?.type || 1
602
+ m.scheduledCallCreationMessage.title = message.call?.name || 'Call Creation'
603
+
604
+ m.scheduledCallCreationMessage.contextInfo = {
605
+ ...(message.contextInfo || {}),
606
+ ...(message.mentions ? {
607
+ mentionedJid: message.mentions
608
+ } : {})
609
+ }
610
+ } else if ('paymentInvite' in message) {
611
+ m.messageContextInfo = {}
612
+ m.paymentInviteMessage = {}
613
+
614
+ m.paymentInviteMessage.expiryTimestamp = message.paymentInvite?.expiry || 0
615
+ m.paymentInviteMessage.serviceType = message.paymentInvite?.type || 2
616
+
617
+ m.paymentInviteMessage.contextInfo = {
618
+ ...(message.contextInfo || {}),
619
+ ...(message.mentions ? {
620
+ mentionedJid: message.mentions
621
+ } : {})
622
+ }
623
+ } else if ('buttonLocation' in message) {
624
+ const bl = message.buttonLocation
625
+ const thumbInput = bl.jpegThumbnail || bl.thumbnail
626
+ if (!thumbInput) throw new Error('buttonLocation requires jpegThumbnail or thumbnail')
627
+ let thumb
628
+ if (Buffer.isBuffer(thumbInput)) {
629
+ thumb = thumbInput
630
+ } else if (typeof thumbInput === 'string' && (thumbInput.startsWith('http://') || thumbInput.startsWith('https://'))) {
631
+ const { stream } = await messages_media_1.getStream(thumbInput)
632
+ thumb = await messages_media_1.toBuffer(stream)
633
+ } else if (typeof thumbInput === 'string') {
634
+ thumb = await fs_1.promises.readFile(thumbInput)
635
+ } else {
636
+ throw new Error('buttonLocation thumbnail must be Buffer, URL, or file path')
637
+ }
638
+ let listButton = []
639
+ if (bl.listMenu && bl.listMenu.length) {
640
+ const nativeParams = JSON.stringify({
641
+ title: bl.listButtonText || 'Menu',
642
+ sections: [{
643
+ title: bl.listSectionTitle || 'Pilihan',
644
+ highlight_label: '',
645
+ rows: bl.listMenu.map(row => ({
646
+ header: row.header || '',
647
+ title: row.title || '',
648
+ description: row.description || '',
649
+ id: row.id || ''
650
+ }))
651
+ }]
652
+ })
653
+ listButton.push({
654
+ buttonId: 'list_menu',
655
+ buttonText: { displayText: bl.listButtonText || 'Pilih Menu' },
656
+ type: 1,
657
+ nativeFlowInfo: {
658
+ name: 'single_select',
659
+ paramsJson: nativeParams,
660
+ buttonParamsJson: nativeParams
680
661
  }
662
+ })
663
+ }
664
+ const extraButtons = (bl.extraButtons || []).map(btn => ({
665
+ buttonId: btn.id || btn.buttonId || 'btn',
666
+ buttonText: { displayText: btn.displayText || btn.name || 'Button' },
667
+ type: 1
668
+ }))
669
+ m = {
670
+ buttonsMessage: {
671
+ locationMessage: {
672
+ degreesLatitude: bl.latitude || 0,
673
+ degreesLongitude: bl.longitude || 0,
674
+ name: bl.name || 'Location',
675
+ address: bl.address || '',
676
+ jpegThumbnail: thumb
677
+ },
678
+ contentText: bl.text || '',
679
+ footerText: bl.footer || '',
680
+ buttons: [...listButton, ...extraButtons],
681
+ headerType: 6,
682
+ viewOnce: true
681
683
  }
682
684
  }
683
- }
684
- else if (hasNonNullishProperty(message, 'stickers')) {
685
- m.stickerPackMessage = await prepareStickerPackMessage(message, options);
686
- }
687
- else if (hasNonNullishProperty(message, 'pin')) {
688
- m.pinInChatMessage = {};
689
- m.messageContextInfo = {};
690
- m.pinInChatMessage.key = message.pin;
691
- m.pinInChatMessage.type = message.type;
692
- m.pinInChatMessage.senderTimestampMs = Date.now();
693
- m.messageContextInfo.messageAddOnDurationInSecs = message.type === 1 ? message.time || 86400 : 0;
694
- }
695
- else if (hasNonNullishProperty(message, 'keep')) {
696
- m.keepInChatMessage = {};
697
- m.keepInChatMessage.key = message.keep;
698
- m.keepInChatMessage.keepType = message.type;
699
- m.keepInChatMessage.timestampMs = Date.now();
700
- }
701
- else if (hasNonNullishProperty(message, 'flowReply')) {
702
- m.interactiveResponseMessage = {
703
- body: {
704
- format: message.flowReply.format || proto.Message.InteractiveResponseMessage.Body.Format.DEFAULT,
705
- text: message.flowReply.text
706
- },
707
- nativeFlowResponseMessage: {
708
- name: message.flowReply.name,
709
- paramsJson: message.flowReply.paramsJson || '{}',
710
- version: message.flowReply.version || 1
685
+ } else if ('groupStatus' in message) {
686
+ const gs = message.groupStatus
687
+ if (!gs.message) throw new Error('groupStatus.message is required')
688
+ const audienceType = gs.audienceType || 0
689
+ const backgroundColor = gs.backgroundArgb
690
+ const textColor = gs.textArgb
691
+ const font = gs.font
692
+ let messageContent = gs.message
693
+ if (typeof messageContent === 'string') {
694
+ messageContent = {
695
+ extendedTextMessage: {
696
+ text: messageContent,
697
+ contextInfo: { statusAudienceMetadata: { audienceType } },
698
+ ...(backgroundColor && { backgroundArgb: backgroundColor }),
699
+ ...(textColor && { textArgb: textColor }),
700
+ ...(font && { font })
701
+ }
711
702
  }
712
- };
713
- }
714
- else if (hasNonNullishProperty(message, 'buttonReply')) {
703
+ } else if (messageContent.extendedTextMessage) {
704
+ if (backgroundColor) messageContent.extendedTextMessage.backgroundArgb = backgroundColor
705
+ if (textColor) messageContent.extendedTextMessage.textArgb = textColor
706
+ if (font) messageContent.extendedTextMessage.font = font
707
+ messageContent.extendedTextMessage.contextInfo = {
708
+ ...messageContent.extendedTextMessage.contextInfo,
709
+ statusAudienceMetadata: { audienceType }
710
+ }
711
+ } else if (
712
+ messageContent.imageMessage || messageContent.videoMessage ||
713
+ messageContent.audioMessage || messageContent.documentMessage
714
+ ) {
715
+ const key = Object.keys(messageContent)[0]
716
+ if (!messageContent[key].contextInfo) messageContent[key].contextInfo = {}
717
+ messageContent[key].contextInfo.statusAudienceMetadata = { audienceType }
718
+ }
719
+ m = { groupStatusMessageV2: { message: messageContent } }
720
+ } else if ('orderStatus' in message) {
721
+ const os = message.orderStatus
722
+ if (!os.image) throw new Error('orderStatus requires image')
723
+ let imageInput = os.image
724
+ if (typeof imageInput === 'string' && !imageInput.startsWith('http://') && !imageInput.startsWith('https://')) {
725
+ imageInput = await fs_1.promises.readFile(imageInput)
726
+ }
727
+ const media = await prepareWAMessageMedia({ image: imageInput }, { upload: options.upload, ...options })
728
+ m = {
729
+ viewOnceMessage: {
730
+ message: {
731
+ messageContextInfo: {
732
+ deviceListMetadata: {},
733
+ deviceListMetadataVersion: 2
734
+ },
735
+ interactiveMessage: WAProto_1.proto.Message.InteractiveMessage.create({
736
+ header: {
737
+ title: os.title || 'Order Status',
738
+ hasMediaAttachment: true,
739
+ ...media
740
+ },
741
+ body: { text: os.text || 'Silakan cek status pesanan Anda.' },
742
+ footer: { text: os.footer || 'Powered by PontaCT' },
743
+ nativeFlowMessage: {
744
+ buttons: [{
745
+ name: 'order_status',
746
+ buttonParamsJson: JSON.stringify({
747
+ reference_id: os.referenceId || 'PT-001',
748
+ order: {
749
+ status: os.status || 'PROCESSING',
750
+ subtotal: {
751
+ value: os.subtotalValue || 0,
752
+ offset: os.subtotalOffset || 100
753
+ },
754
+ tax: {
755
+ value: os.taxValue || 0,
756
+ offset: os.taxOffset || 100
757
+ },
758
+ currency: os.currency || 'IDR'
759
+ }
760
+ })
761
+ }]
762
+ }
763
+ })
764
+ }
765
+ }
766
+ }
767
+ } else if ('buttonReply' in message) {
715
768
  switch (message.type) {
769
+ case 'list':
770
+ m.listResponseMessage = {
771
+ title: message.buttonReply.title,
772
+ description: message.buttonReply.description,
773
+ singleSelectReply: {
774
+ selectedRowId: message.buttonReply.rowId
775
+ },
776
+ lisType: WAProto_1.proto.Message.ListResponseMessage.ListType.SINGLE_SELECT
777
+ }
778
+ break
716
779
  case 'template':
717
780
  m.templateButtonReplyMessage = {
718
781
  selectedDisplayText: message.buttonReply.displayText,
719
782
  selectedId: message.buttonReply.id,
720
783
  selectedIndex: message.buttonReply.index
721
- };
722
- break;
784
+ }
785
+ break
723
786
  case 'plain':
724
787
  m.buttonsResponseMessage = {
725
788
  selectedButtonId: message.buttonReply.id,
726
789
  selectedDisplayText: message.buttonReply.displayText,
727
- type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
728
- };
729
- break;
730
- }
731
- }
732
- else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
733
- const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options);
734
- m.ptvMessage = videoMessage;
735
- }
736
- else if (hasNonNullishProperty(message, 'product')) {
737
- if (!message.businessOwnerJid) {
738
- throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
739
- }
740
- const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
741
- const content = {
790
+ type: WAProto_1.proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
791
+ }
792
+ break
793
+ case 'interactive':
794
+ m.interactiveResponseMessage = {
795
+ body: {
796
+ text: message.buttonReply.displayText,
797
+ format: WAProto_1.proto.Message.InteractiveResponseMessage.Body.Format.EXTENSIONS_1
798
+ },
799
+ nativeFlowResponseMessage: {
800
+ name: message.buttonReply.nativeFlows.name,
801
+ paramsJson: message.buttonReply.nativeFlows.paramsJson,
802
+ version: message.buttonReply.nativeFlows.version
803
+ }
804
+ }
805
+ break
806
+ }
807
+ } else if ('ptv' in message && message.ptv) {
808
+ const {
809
+ videoMessage
810
+ } = await prepareWAMessageMedia({
811
+ video: message.video
812
+ }, options)
813
+
814
+ m.ptvMessage = videoMessage
815
+ } else if ('album' in message) {
816
+ const imageMessages = message.album.filter(item => 'image' in item)
817
+ const videoMessages = message.album.filter(item => 'video' in item)
818
+
819
+ m.albumMessage = Types_1.WAProto.Message.AlbumMessage.fromObject({
820
+ expectedImageCount: imageMessages.length,
821
+ expectedVideoCount: videoMessages.length
822
+ })
823
+ } else if ('order' in message) {
824
+ m.orderMessage = Types_1.WAProto.Message.OrderMessage.fromObject(message.order)
825
+
826
+ m.orderMessage.contextInfo = {
827
+ ...(message.contextInfo || {}),
828
+ ...(message.mentions ? {
829
+ mentionedJid: message.mentions
830
+ } : {})
831
+ }
832
+ } else if ('event' in message) {
833
+ m.eventMessage = Types_1.WAProto.Message.EventMessage.fromObject(message.event)
834
+
835
+ if (!message.event.startTime) {
836
+ m.eventMessage.startTime = generics_1.unixTimestampSeconds() + 86400
837
+ }
838
+
839
+ if (options.getCallLink && message.event.call) {
840
+ const link = await options.getCallLink(message.event.call, m.eventMessage.startTime)
841
+ m.eventMessage.joinLink = link
842
+ }
843
+
844
+ m.eventMessage.contextInfo = {
845
+ ...(message.contextInfo || {}),
846
+ ...(message.mentions ? {
847
+ mentionedJid: message.mentions
848
+ } : {})
849
+ }
850
+ } else if ('product' in message) {
851
+ const { imageMessage } = await prepareWAMessageMedia({
852
+ image: message.product.productImage
853
+ }, options)
854
+
855
+ m.productMessage = Types_1.WAProto.Message.ProductMessage.fromObject({
742
856
  ...message,
743
857
  product: {
744
- currencyCode: 'IDR',
745
- priceAmount1000: 1000,
746
- title: LIBRARY_NAME,
747
858
  ...message.product,
748
- productImage: imageMessage
859
+ productImage: imageMessage,
749
860
  }
750
- };
751
- delete content.image;
752
- m.productMessage = WAProto.Message.ProductMessage.create(content);
753
- }
754
- else if (hasNonNullishProperty(message, 'listReply')) {
755
- m.listResponseMessage = {
756
- description: message.listReply.description,
757
- listType: proto.Message.ListResponseMessage.ListType.SINGLE_SELECT,
758
- singleSelectReply: {
759
- selectedRowId: message.listReply.id
760
- },
761
- title: message.listReply.title
762
- };
763
- }
764
- else if (hasNonNullishProperty(message, 'event')) {
765
- m.eventMessage = {};
766
- const startTime = Math.floor(message.event.startDate.getTime() / 1000);
767
- if (message.event.call && options.getCallLink) {
768
- const token = await options.getCallLink(message.event.call, { startTime });
769
- m.eventMessage.joinLink = (message.event.call === 'audio' ? CALL_AUDIO_PREFIX : CALL_VIDEO_PREFIX) + token;
770
- }
771
- m.messageContextInfo = {
772
- // encKey
773
- messageSecret: message.event.messageSecret || randomBytes(32)
774
- };
775
- m.eventMessage.name = message.event.name;
776
- m.eventMessage.description = message.event.description;
777
- m.eventMessage.startTime = startTime;
778
- m.eventMessage.endTime = message.event.endDate ? message.event.endDate.getTime() / 1000 : undefined;
779
- m.eventMessage.isCanceled = message.event.isCancelled ?? false;
780
- m.eventMessage.extraGuestsAllowed = message.event.extraGuestsAllowed;
781
- m.eventMessage.isScheduleCall = message.event.isScheduleCall ?? false;
782
- m.eventMessage.location = message.event.location;
783
- }
784
- else if (hasNonNullishProperty(message, 'poll')) {
785
- (_a = message.poll).selectableCount || (_a.selectableCount = 0);
786
- (_b = message.poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
787
- if (!Array.isArray(message.poll.values)) {
788
- throw new Boom('Invalid poll values', { statusCode: 400 });
789
- }
790
- if (message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) {
791
- throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
861
+ })
862
+
863
+ m.productMessage.contextInfo = {
864
+ ...(message.contextInfo || {}),
865
+ ...(message.mentions ? {
866
+ mentionedJid: message.mentions
867
+ } : {})
868
+ }
869
+ } else if ('pollResult' in message) {
870
+ if (!Array.isArray(message.pollResult.values)) {
871
+ throw new boom_1.Boom('Invalid pollResult values', {
792
872
  statusCode: 400
793
- });
794
- }
795
- const pollCreationMessage = {
796
- name: message.poll.name,
797
- selectableOptionsCount: message.poll.selectableCount,
798
- options: message.poll.values.map(optionName => ({ optionName })),
799
- endTime: message.poll.endDate ? message.poll.endDate.getTime() : undefined,
800
- hideParticipantName: message.poll.hideVoter ?? false,
801
- allowAddOption: message.poll.canAddOption ?? false
802
- };
803
- if (message.poll.toAnnouncementGroup) {
804
- // poll v2 is for community announcement groups (single select and multiple)
805
- m.pollCreationMessageV2 = pollCreationMessage;
806
- }
807
- else {
808
- // Add quiz message support
809
- if (message.poll.pollType === 1) {
810
- if (!message.poll.correctAnswer) {
811
- throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
812
- }
813
- m.pollCreationMessageV5 = {
814
- // quiz for newsletter only
815
- ...pollCreationMessage,
816
- correctAnswer: {
817
- optionName: message.poll.correctAnswer.toString()
818
- },
819
- pollType: 1,
820
- selectableOptionsCount: 1
821
- };
822
- }
823
- else if (message.poll.selectableCount === 1) {
824
- //poll v3 is for single select polls
825
- m.pollCreationMessageV3 = pollCreationMessage;
826
- }
827
- else {
828
- // poll for multiple choice polls
829
- m.pollCreationMessage = pollCreationMessage;
830
- }
873
+ })
831
874
  }
832
- m.messageContextInfo = {
833
- // encKey
834
- messageSecret: message.poll.messageSecret || randomBytes(32)
835
- };
836
- }
837
- else if (hasNonNullishProperty(message, 'pollResult')) {
875
+
838
876
  const pollResultSnapshotMessage = {
839
877
  name: message.pollResult.name,
840
- pollVotes: message.pollResult.votes.map(vote => ({
841
- optionName: vote.name,
842
- optionVoteCount: parseInt(vote.voteCount)
878
+ pollVotes: message.pollResult.values.map(([optionName, optionVoteCount]) => ({
879
+ optionName,
880
+ optionVoteCount
843
881
  }))
844
- };
845
- if (message.pollResult.pollType === 1) {
846
- pollResultSnapshotMessage.pollType = proto.Message.PollType.QUIZ;
847
- m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
848
882
  }
849
- else {
850
- pollResultSnapshotMessage.pollType = proto.Message.PollType.POLL;
851
- m.pollResultSnapshotMessage = pollResultSnapshotMessage;
883
+
884
+ pollResultSnapshotMessage.contextInfo = {
885
+ ...(message.contextInfo || {}),
886
+ ...(message.mentions ? {
887
+ mentionedJid: message.mentions
888
+ } : {})
852
889
  }
853
- }
854
- else if (hasNonNullishProperty(message, 'pollUpdate')) {
855
- if (!message.pollUpdate.key) {
856
- throw new Boom('Message key is required', { statusCode: 400 });
857
- }
858
- if (!message.pollUpdate.vote) {
859
- throw new Boom('Encrypted vote payload is required', { statusCode: 400 });
860
- }
861
- m.pollUpdateMessage = {
862
- metadata: message.pollUpdate.metadata,
863
- pollCreationMessageKey: message.pollUpdate.key,
864
- senderTimestampMs: Date.now(),
865
- vote: message.pollUpdate.vote
866
- };
867
- }
868
- else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
869
- m.paymentInviteMessage = {
870
- expiryTimestamp: Date.now(),
871
- serviceType: message.paymentInviteServiceType
872
- };
873
- }
874
- else if (hasNonNullishProperty(message, 'orderText')) {
875
- if (!Buffer.isBuffer(message.thumbnail)) {
876
- throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
877
- }
878
- m.orderMessage = {
879
- itemCount: 1,
880
- messageVersion: 1,
881
- orderTitle: LIBRARY_NAME,
882
- status: proto.Message.OrderMessage.OrderStatus.INQUIRY,
883
- surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
884
- token: generateMessageIDV2(),
885
- totalAmount1000: 1000,
886
- totalCurrencyCode: 'IDR',
887
- ...message,
888
- message: message.orderText
889
- };
890
- delete m.orderMessage.orderText;
891
- }
892
- else if (hasNonNullishProperty(message, 'album')) {
893
- if (!Array.isArray(message.album)) {
894
- throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
895
- }
896
- let videoCount = 0;
897
- for (let i = 0; i < message.album.length; i++) {
898
- if (message.album[i].video)
899
- videoCount++;
900
- }
901
- ;
902
- let imageCount = 0;
903
- for (let i = 0; i < message.album.length; i++) {
904
- if (message.album[i].image)
905
- imageCount++;
906
- }
907
- ;
908
- if ((videoCount + imageCount) < 2) {
909
- throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
910
- }
911
- m.albumMessage = {
912
- expectedImageCount: imageCount,
913
- expectedVideoCount: videoCount
914
- };
915
- }
916
- else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
917
- m.protocolMessage = {
918
- type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
919
- };
920
- }
921
- else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
922
- m.requestPhoneNumberMessage = {};
923
- }
924
- else if (hasNonNullishProperty(message, 'limitSharing')) {
925
- m.protocolMessage = {
926
- type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
927
- limitSharing: {
928
- sharingLimited: message.limitSharing === true,
929
- trigger: 1,
930
- limitSharingSettingTimestamp: Date.now(),
931
- initiatedByMe: true
932
- }
933
- };
934
- }
935
- else if (hasNonNullishProperty(message, 'buttonsMessage')) {
936
- // Direct buttonsMessage passthrough — supports headerType 6 (locationMessage), etc.
937
- const btnMsg = { ...message.buttonsMessage };
938
- if (btnMsg.locationMessage?.jpegThumbnail && !Buffer.isBuffer(btnMsg.locationMessage.jpegThumbnail)) {
939
- const lib = await getImageProcessingLibrary();
940
- const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
941
- if (hasSharp) {
942
- const rawBuf = typeof btnMsg.locationMessage.jpegThumbnail === 'string'
943
- ? (await fs.readFile(btnMsg.locationMessage.jpegThumbnail))
944
- : btnMsg.locationMessage.jpegThumbnail;
945
- btnMsg.locationMessage.jpegThumbnail = await lib.sharp.default(rawBuf)
946
- .resize(300, 300, { fit: 'inside', withoutEnlargement: true })
947
- .jpeg({ quality: 80 })
948
- .toBuffer();
949
- }
890
+
891
+ m.pollResultSnapshotMessage = pollResultSnapshotMessage
892
+ } else if ('poll' in message) {
893
+ if (!Array.isArray(message.poll.values)) {
894
+ throw new boom_1.Boom('Invalid poll values', {
895
+ statusCode: 400
896
+ })
950
897
  }
951
- m = { buttonsMessage: btnMsg };
952
- }
953
- else {
954
- m = await prepareWAMessageMedia(message, options);
955
- }
956
- if (hasNonNullishProperty(message, 'buttons')) {
957
- const buttonsMessage = {
958
- buttons: message.buttons.map(button => {
959
- const buttonText = button.text || button.buttonText;
960
- if (hasOptionalProperty(button, 'sections')) {
961
- return {
962
- nativeFlowInfo: {
963
- name: 'single_select',
964
- paramsJson: JSON.stringify({
965
- title: buttonText,
966
- sections: button.sections
967
- })
968
- },
969
- type: ButtonType.NATIVE_FLOW
970
- };
971
- }
972
- else if (hasOptionalProperty(button, 'name')) {
973
- return {
974
- nativeFlowInfo: {
975
- name: button.name,
976
- paramsJson: button.paramsJson
977
- },
978
- type: ButtonType.NATIVE_FLOW
979
- };
980
- }
981
- return {
982
- buttonId: button.id || button.buttonId,
983
- buttonText: typeof buttonText === 'string' ? { displayText: buttonText } : buttonText,
984
- type: button.type || ButtonType.RESPONSE
985
- };
898
+
899
+ if (message.poll.selectableCount < 0 ||
900
+ message.poll.selectableCount > message.poll.values.length) {
901
+ throw new boom_1.Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
902
+ statusCode: 400
986
903
  })
987
- };
988
- if (hasOptionalProperty(message, 'text')) {
989
- buttonsMessage.contentText = message.text;
990
- buttonsMessage.headerType = ButtonHeaderType.EMPTY;
991
- }
992
- else {
993
- if (hasOptionalProperty(message, 'caption')) {
994
- buttonsMessage.contentText = message.caption;
904
+ }
905
+
906
+ const pollCreationMessage = {
907
+ name: message.poll.name,
908
+ selectableOptionsCount: message.poll?.selectableCount || 0,
909
+ options: message.poll.values.map(optionName => ({
910
+ optionName
911
+ })),
912
+ }
913
+
914
+ pollCreationMessage.contextInfo = {
915
+ ...(message.contextInfo || {}),
916
+ ...(message.mentions ? {
917
+ mentionedJid: message.mentions
918
+ } : {})
919
+ }
920
+
921
+ if (message.poll?.toAnnouncementGroup) {
922
+ m.pollCreationMessageV2 = pollCreationMessage
923
+ } else {
924
+ if (message.poll.selectableCount > 0) {
925
+ m.pollCreationMessageV3 = pollCreationMessage
926
+ } else {
927
+ m.pollCreationMessage = pollCreationMessage
995
928
  }
996
- const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
997
- buttonsMessage.headerType = ButtonHeaderType[type];
998
- Object.assign(buttonsMessage, m);
999
929
  }
1000
- if (hasOptionalProperty(message, 'footer')) {
1001
- buttonsMessage.footerText = message.footer;
930
+ } else if ('payment' in message) {
931
+ const requestPaymentMessage = {
932
+ amount: {
933
+ currencyCode: message.payment?.currency || 'IDR',
934
+ offset: message.payment?.offset || 0,
935
+ value: message.payment?.amount || 999999999
936
+ },
937
+ expiryTimestamp: message.payment?.expiry || 0,
938
+ amount1000: message.payment?.amount || 999999999 * 1000,
939
+ currencyCodeIso4217: message.payment?.currency || 'IDR',
940
+ requestFrom: message.payment?.from || '0@s.whatsapp.net',
941
+ noteMessage: {
942
+ extendedTextMessage: {
943
+ text: message.payment?.note || 'Notes'
944
+ }
945
+ },
946
+ background: {
947
+ placeholderArgb: message.payment?.image?.placeholderArgb || 4278190080,
948
+ textArgb: message.payment?.image?.textArgb || 4294967295,
949
+ subtextArgb: message.payment?.image?.subtextArgb || 4294967295,
950
+ type: 1
951
+ }
1002
952
  }
1003
- m = { buttonsMessage };
1004
- }
1005
- else if (hasNonNullishProperty(message, 'sections')) {
953
+
954
+ requestPaymentMessage.noteMessage.extendedTextMessage.contextInfo = {
955
+ ...(message.contextInfo || {}),
956
+ ...(message.mentions ? {
957
+ mentionedJid: message.mentions
958
+ } : {})
959
+ }
960
+
961
+ m.requestPaymentMessage = requestPaymentMessage
962
+ } else if ('stickerPack' in message) {
963
+ const {
964
+ stickers,
965
+ cover,
966
+ name,
967
+ publisher,
968
+ packId,
969
+ description
970
+ } = message.stickerPack
971
+
972
+ const { zip } = require('fflate')
973
+ const stickerData = {}
974
+ const stickerPromises = stickers.map(async (s, i) => {
975
+ const { stream } = await messages_media_1.getStream(s.sticker)
976
+ const buffer = await messages_media_1.toBuffer(stream)
977
+ const hash = crypto_2.sha256(buffer).toString('base64url')
978
+ const fileName = `${i.toString().padStart(2, '0')}_${hash}.webp`
979
+ stickerData[fileName] = [new Uint8Array(buffer), {
980
+ level: 0
981
+ }]
982
+
983
+ return {
984
+ fileName,
985
+ mimetype: 'image/webp',
986
+ isAnimated: s.isAnimated || false,
987
+ isLottie: s.isLottie || false,
988
+ emojis: s.emojis || [],
989
+ accessibilityLabel: s.accessibilityLabel || ''
990
+ }
991
+ })
992
+
993
+ const stickerMetadata = await Promise.all(stickerPromises)
994
+
995
+ const zipBuffer = await new Promise((resolve, reject) => {
996
+ zip(stickerData, (err, data) => {
997
+ if (err) {
998
+ reject(err)
999
+ } else {
1000
+ resolve(Buffer.from(data))
1001
+ }
1002
+ })
1003
+ })
1004
+
1005
+ const coverBuffer = await messages_media_1.toBuffer((await messages_media_1.getStream(cover)).stream)
1006
+
1007
+ const [stickerPackUpload, coverUpload] = await Promise.all([
1008
+ messages_media_1.encryptedStream(zipBuffer, 'sticker-pack', {
1009
+ logger: options.logger,
1010
+ opts: options.options
1011
+ }),
1012
+ prepareWAMessageMedia({
1013
+ image: coverBuffer
1014
+ }, {
1015
+ ...options,
1016
+ mediaTypeOverride: 'image'
1017
+ })
1018
+ ])
1019
+
1020
+ const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
1021
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
1022
+ mediaType: 'sticker-pack',
1023
+ timeoutMs: options.mediaUploadTimeoutMs
1024
+ })
1025
+
1026
+
1027
+ const coverImage = coverUpload.imageMessage
1028
+ const imageDataHash = crypto_2.sha256(coverBuffer).toString('base64')
1029
+ const stickerPackId = packId || generics_1.generateMessageID()
1030
+
1031
+ m.stickerPackMessage = {
1032
+ name,
1033
+ publisher,
1034
+ stickerPackId,
1035
+ packDescription: description,
1036
+ stickerPackOrigin: WAProto_1.proto.Message.StickerPackMessage.StickerPackOrigin.THIRD_PARTY,
1037
+ stickerPackSize: stickerPackUpload.fileLength,
1038
+ stickers: stickerMetadata,
1039
+ fileSha256: stickerPackUpload.fileSha256,
1040
+ fileEncSha256: stickerPackUpload.fileEncSha256,
1041
+ mediaKey: stickerPackUpload.mediaKey,
1042
+ directPath: stickerPackUploadResult.directPath,
1043
+ fileLength: stickerPackUpload.fileLength,
1044
+ mediaKeyTimestamp: generics_1.unixTimestampSeconds(),
1045
+ trayIconFileName: `${stickerPackId}.webp`,
1046
+ imageDataHash,
1047
+ thumbnailDirectPath: coverImage.directPath,
1048
+ thumbnailFileSha256: coverImage.fileSha256,
1049
+ thumbnailFileEncSha256: coverImage.fileEncSha256,
1050
+ thumbnailHeight: coverImage.height,
1051
+ thumbnailWidth: coverImage.width
1052
+ }
1053
+
1054
+ m.stickerPackMessage.contextInfo = {
1055
+ ...(message.contextInfo || {}),
1056
+ ...(message.mentions ? {
1057
+ mentionedJid: message.mentions
1058
+ } : {})
1059
+ }
1060
+ } else if ('sharePhoneNumber' in message) {
1061
+ m.protocolMessage = {
1062
+ type: Types_1.WAProto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
1063
+ }
1064
+ } else if ('requestPhoneNumber' in message) {
1065
+ m.requestPhoneNumberMessage = {}
1066
+ } else {
1067
+ const mess = await prepareWAMessageMedia(message, options)
1068
+ const [type] = Object.keys(mess)
1069
+
1070
+ mess[type].contextInfo = {
1071
+ ...(message.contextInfo || {}),
1072
+ ...(message.mentions ? {
1073
+ mentionedJid: message.mentions
1074
+ } : {})
1075
+ }
1076
+
1077
+ m = mess
1078
+ }
1079
+
1080
+ if ('sections' in message && !!message.sections) {
1006
1081
  const listMessage = {
1007
- sections: message.sections,
1008
- buttonText: message.buttonText,
1009
1082
  title: message.title,
1083
+ buttonText: message.buttonText,
1010
1084
  footerText: message.footer,
1011
1085
  description: message.text,
1012
- listType: ListType.SINGLE_SELECT
1013
- };
1014
- m = { listMessage };
1015
- }
1016
- else if (hasNonNullishProperty(message, 'productList')) {
1017
- // --- validasi 100% ---
1018
- if (!message.businessOwnerJid) {
1019
- throw new Boom('"businessOwnerJid" wajib diisi untuk mengirim productList', { statusCode: 400 });
1086
+ sections: message.sections,
1087
+ listType: WAProto_1.proto.Message.ListMessage.ListType.SINGLE_SELECT
1020
1088
  }
1021
- if (!Array.isArray(message.productList) || message.productList.length === 0) {
1022
- throw new Boom('"productList" harus berupa array section yang tidak kosong', { statusCode: 400 });
1089
+
1090
+ listMessage.contextInfo = {
1091
+ ...(message.contextInfo || {}),
1092
+ ...(message.mentions ? {
1093
+ mentionedJid: message.mentions
1094
+ } : {})
1023
1095
  }
1024
- message.productList.forEach((section, i) => {
1025
- if (!section || typeof section !== 'object') {
1026
- throw new Boom(`productList[${i}] tidak valid, harus berupa object section`, { statusCode: 400 });
1027
- }
1028
- if (!Array.isArray(section.products) || section.products.length === 0) {
1029
- throw new Boom(`productList[${i}].products harus berupa array produk yang tidak kosong`, { statusCode: 400 });
1030
- }
1031
- section.products.forEach((product, j) => {
1032
- if (!product || !product.productId) {
1033
- throw new Boom(`productList[${i}].products[${j}].productId wajib diisi`, { statusCode: 400 });
1034
- }
1035
- });
1036
- });
1037
- const thumbnail = message.thumbnail
1038
- ? await generateThumbnail(message.thumbnail, 'image')
1039
- : null;
1040
- m.listMessage = {
1096
+
1097
+ m = {
1098
+ listMessage
1099
+ }
1100
+ } else if ('productList' in message && !!message.productList) {
1101
+ const thumbnail = message.thumbnail ? await messages_media_1.generateThumbnail(message.thumbnail, 'image') : null
1102
+
1103
+ const listMessage = {
1041
1104
  title: message.title,
1042
1105
  buttonText: message.buttonText,
1043
1106
  footerText: message.footer,
@@ -1050,998 +1113,763 @@ export const generateWAMessageContent = async (message, options) => {
1050
1113
  },
1051
1114
  businessOwnerJid: message.businessOwnerJid
1052
1115
  },
1053
- listType: proto.Message.ListMessage.ListType.PRODUCT_LIST
1054
- };
1055
- }
1056
- else if (hasNonNullishProperty(message, 'payment')) {
1057
- // --- validasi 100% ---
1058
- const p = message.payment;
1059
- if (!p || typeof p !== 'object') {
1060
- throw new Boom('"payment" harus berupa object', { statusCode: 400 });
1061
- }
1062
- if (!p.from) {
1063
- throw new Boom('"payment.from" (JID tujuan pembayaran) wajib diisi', { statusCode: 400 });
1064
- }
1065
- if (typeof p.amount !== 'number' || !(p.amount > 0)) {
1066
- throw new Boom('"payment.amount" wajib berupa angka lebih dari 0', { statusCode: 400 });
1116
+ listType: WAProto_1.proto.Message.ListMessage.ListType.PRODUCT_LIST
1067
1117
  }
1068
- if (p.currency !== undefined && typeof p.currency !== 'string') {
1069
- throw new Boom('"payment.currency" harus berupa string (mis. "IDR")', { statusCode: 400 });
1118
+
1119
+ listMessage.contextInfo = {
1120
+ ...(message.contextInfo || {}),
1121
+ ...(message.mentions ? {
1122
+ mentionedJid: message.mentions
1123
+ } : {})
1070
1124
  }
1071
- if (p.offset !== undefined && typeof p.offset !== 'number') {
1072
- throw new Boom('"payment.offset" harus berupa angka', { statusCode: 400 });
1125
+
1126
+ m = {
1127
+ listMessage
1073
1128
  }
1074
- if (p.expiry !== undefined && typeof p.expiry !== 'number') {
1075
- throw new Boom('"payment.expiry" harus berupa angka (unix timestamp)', { statusCode: 400 });
1129
+ } else if ('buttons' in message && !!message.buttons) {
1130
+ const buttonsMessage = {
1131
+ buttons: message.buttons.map(b => ({
1132
+ ...b,
1133
+ type: WAProto_1.proto.Message.ButtonsMessage.Button.Type.RESPONSE
1134
+ }))
1076
1135
  }
1077
- const currency = p.currency || 'IDR';
1078
- m.requestPaymentMessage = {
1079
- amount: {
1080
- currencyCode: currency,
1081
- offset: p.offset ?? 0,
1082
- value: p.amount
1083
- },
1084
- expiryTimestamp: p.expiry ?? 0,
1085
- amount1000: p.amount * 1000,
1086
- currencyCodeIso4217: currency,
1087
- requestFrom: p.from,
1088
- noteMessage: {
1089
- extendedTextMessage: { text: p.note || '' }
1090
- },
1091
- background: {
1092
- placeholderArgb: p.image?.placeholderArgb ?? 4278190080,
1093
- textArgb: p.image?.textArgb ?? 4294967295,
1094
- subtextArgb: p.image?.subtextArgb ?? 4294967295,
1095
- type: 1
1136
+
1137
+ if ('text' in message) {
1138
+ buttonsMessage.contentText = message.text
1139
+ buttonsMessage.headerType = WAProto_1.proto.Message.ButtonsMessage.HeaderType.EMPTY
1140
+ } else {
1141
+ if ('caption' in message) {
1142
+ buttonsMessage.contentText = message.caption
1096
1143
  }
1097
- };
1098
- }
1099
- else if (hasNonNullishProperty(message, 'buttonLocation')) {
1100
- // --- validasi 100% ---
1101
- const bl = message.buttonLocation;
1102
- if (!bl || typeof bl !== 'object') {
1103
- throw new Boom('"buttonLocation" harus berupa object', { statusCode: 400 });
1104
- }
1105
- if (typeof bl.latitude !== 'number' || typeof bl.longitude !== 'number') {
1106
- throw new Boom('"buttonLocation.latitude" dan "buttonLocation.longitude" wajib berupa angka', { statusCode: 400 });
1107
- }
1108
- if (bl.sections !== undefined && !Array.isArray(bl.sections)) {
1109
- throw new Boom('"buttonLocation.sections" harus berupa array', { statusCode: 400 });
1110
- }
1111
- if (bl.extraButtons !== undefined && !Array.isArray(bl.extraButtons)) {
1112
- throw new Boom('"buttonLocation.extraButtons" harus berupa array', { statusCode: 400 });
1113
- }
1114
- let thumb;
1115
- if (bl.thumbnail) {
1116
- const t = await generateThumbnail(bl.thumbnail, 'image');
1117
- thumb = t?.thumbnail;
1118
- }
1119
- const listButton = [];
1120
- if (Array.isArray(bl.sections) && bl.sections.length > 0) {
1121
- bl.sections.forEach((section, i) => {
1122
- if (!Array.isArray(section.rows) || section.rows.length === 0) {
1123
- throw new Boom(`buttonLocation.sections[${i}].rows harus berupa array yang tidak kosong`, { statusCode: 400 });
1124
- }
1125
- section.rows.forEach((row, j) => {
1126
- if (!row || !row.id || !row.title) {
1127
- throw new Boom(`buttonLocation.sections[${i}].rows[${j}] wajib punya "id" dan "title"`, { statusCode: 400 });
1128
- }
1129
- });
1130
- });
1131
- const nativeParams = JSON.stringify({
1132
- sections: bl.sections.map(section => ({
1133
- title: section.title || '',
1134
- rows: section.rows.map(row => ({
1135
- title: row.title || '',
1136
- description: row.description || '',
1137
- id: row.id || ''
1138
- }))
1139
- }))
1140
- });
1141
- listButton.push({
1142
- buttonId: 'list_menu',
1143
- buttonText: { displayText: bl.listButtonText || '📄 Pilih Menu' },
1144
- type: 1,
1145
- nativeFlowInfo: {
1146
- name: 'single_select',
1147
- paramsJson: nativeParams,
1148
- buttonParamsJson: nativeParams
1149
- }
1150
- });
1144
+
1145
+ const type = Object.keys(m)[0].replace('Message', '').toUpperCase()
1146
+
1147
+ buttonsMessage.headerType = WAProto_1.proto.Message.ButtonsMessage.HeaderType[type]
1148
+
1149
+ Object.assign(buttonsMessage, m)
1150
+ }
1151
+
1152
+ if ('footer' in message && !!message.footer) {
1153
+ buttonsMessage.footerText = message.footer
1154
+ }
1155
+
1156
+ if ('title' in message && !!message.title) {
1157
+ buttonsMessage.text = message.title
1158
+ buttonsMessage.headerType = WAProto_1.proto.Message.ButtonsMessage.HeaderType.TEXT
1159
+ }
1160
+
1161
+ buttonsMessage.contextInfo = {
1162
+ ...(message.contextInfo || {}),
1163
+ ...(message.mentions ? {
1164
+ mentionedJid: message.mentions
1165
+ } : {})
1166
+ }
1167
+
1168
+ m = {
1169
+ buttonsMessage
1151
1170
  }
1152
- const extraButtons = (bl.extraButtons || []).map((btn, i) => {
1153
- if (!btn || (!btn.id && !btn.buttonId)) {
1154
- throw new Boom(`buttonLocation.extraButtons[${i}].id wajib diisi`, { statusCode: 400 });
1171
+ } else if ('template' in message && !!message.templateButtons) {
1172
+ const hydratedTemplate = {
1173
+ hydratedButtons: message.templateButtons
1174
+ }
1175
+
1176
+ if ('text' in message) {
1177
+ hydratedTemplate.hydratedContentText = message.text
1178
+ } else {
1179
+ if ('caption' in message) {
1180
+ hydratedTemplate.hydratedContentText = message.caption
1155
1181
  }
1156
- return {
1157
- buttonId: btn.id || btn.buttonId,
1158
- buttonText: { displayText: btn.displayText || btn.name || 'Button' },
1159
- type: 1
1160
- };
1161
- });
1162
- if (listButton.length === 0 && extraButtons.length === 0) {
1163
- throw new Boom('"buttonLocation" membutuhkan minimal 1 "sections" atau 1 "extraButtons"', { statusCode: 400 });
1182
+
1183
+ Object.assign(msg, m)
1164
1184
  }
1165
- m = {
1166
- buttonsMessage: {
1167
- locationMessage: {
1168
- degreesLatitude: bl.latitude,
1169
- degreesLongitude: bl.longitude,
1170
- name: bl.name || 'Location',
1171
- address: bl.address || '',
1172
- jpegThumbnail: thumb
1173
- },
1174
- contentText: bl.text || '',
1175
- footerText: bl.footer || '',
1176
- buttons: [...listButton, ...extraButtons],
1177
- headerType: ButtonHeaderType.LOCATION,
1178
- viewOnce: true
1179
- }
1180
- };
1181
- }
1182
- else if (hasNonNullishProperty(message, 'orderStatus')) {
1183
- // --- validasi 100% ---
1184
- const os = message.orderStatus;
1185
- if (!os || typeof os !== 'object') {
1186
- throw new Boom('"orderStatus" harus berupa object', { statusCode: 400 });
1185
+
1186
+ if ('footer' in message && !!message.footer) {
1187
+ hydratedTemplate.hydratedFooterText = message.footer
1187
1188
  }
1188
- if (!os.image) {
1189
- throw new Boom('"orderStatus.image" wajib diisi (path file, URL, atau Buffer)', { statusCode: 400 });
1189
+
1190
+ hydratedTemplate.contextInfo = {
1191
+ ...(message.contextInfo || {}),
1192
+ ...(message.mentions ? {
1193
+ mentionedJid: message.mentions
1194
+ } : {})
1190
1195
  }
1191
- if (!os.referenceId) {
1192
- throw new Boom('"orderStatus.referenceId" wajib diisi', { statusCode: 400 });
1196
+
1197
+ m = {
1198
+ templateMessage: {
1199
+ hydratedTemplate
1200
+ }
1193
1201
  }
1194
- if (os.status !== undefined && typeof os.status !== 'string') {
1195
- throw new Boom('"orderStatus.status" harus berupa string', { statusCode: 400 });
1202
+ } else if ('interactive' in message && !!message.interactive) {
1203
+ const interactiveMessage = {
1204
+ nativeFlowMessage: {
1205
+ buttons: message.interactive
1206
+ }
1196
1207
  }
1197
- if (os.subtotalValue !== undefined && typeof os.subtotalValue !== 'number') {
1198
- throw new Boom('"orderStatus.subtotalValue" harus berupa angka', { statusCode: 400 });
1208
+
1209
+ if ('text' in message) {
1210
+ interactiveMessage.body = {
1211
+ text: message.text
1212
+ },
1213
+ interactiveMessage.header = {
1214
+ title: message.title,
1215
+ subtitle: message.subtitle,
1216
+ hasMediaAttachment: false
1217
+ }
1218
+ } else {
1219
+ if ('caption' in message) {
1220
+ interactiveMessage.body = {
1221
+ text: message.caption
1222
+ }
1223
+
1224
+ interactiveMessage.header = {
1225
+ title: message.title,
1226
+ subtitle: message.subtitle,
1227
+ hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1228
+ ...Object.assign(interactiveMessage, m)
1229
+ }
1230
+ }
1199
1231
  }
1200
- if (os.taxValue !== undefined && typeof os.taxValue !== 'number') {
1201
- throw new Boom('"orderStatus.taxValue" harus berupa angka', { statusCode: 400 });
1232
+
1233
+ if ('footer' in message && !!message.footer) {
1234
+ interactiveMessage.footer = {
1235
+ text: message.footer
1236
+ }
1202
1237
  }
1203
- let imageInput = os.image;
1204
- if (typeof imageInput === 'string' && !/^https?:\/\//i.test(imageInput)) {
1205
- imageInput = await fs.readFile(imageInput);
1238
+
1239
+ interactiveMessage.contextInfo = {
1240
+ ...(message.contextInfo || {}),
1241
+ ...(message.mentions ? {
1242
+ mentionedJid: message.mentions
1243
+ } : {})
1206
1244
  }
1207
- const media = await prepareWAMessageMedia({ image: imageInput }, options);
1245
+
1208
1246
  m = {
1209
- viewOnceMessage: {
1210
- message: {
1211
- messageContextInfo: {
1212
- deviceListMetadata: {},
1213
- deviceListMetadataVersion: 2
1214
- },
1215
- interactiveMessage: proto.Message.InteractiveMessage.create({
1216
- header: {
1217
- title: os.title || 'Order Status',
1218
- hasMediaAttachment: true,
1219
- ...media
1220
- },
1221
- body: {
1222
- text: os.text || 'Silakan cek status pesanan Anda.'
1223
- },
1224
- footer: {
1225
- text: os.footer || 'Powered by @pontalabs/baileys'
1226
- },
1227
- nativeFlowMessage: {
1228
- buttons: [
1229
- {
1230
- name: 'order_status',
1231
- buttonParamsJson: JSON.stringify({
1232
- reference_id: os.referenceId,
1233
- order: {
1234
- status: os.status || 'PROCESSING',
1235
- subtotal: {
1236
- value: os.subtotalValue || 0,
1237
- offset: os.subtotalOffset || 100
1238
- },
1239
- tax: {
1240
- value: os.taxValue || 0,
1241
- offset: os.taxOffset || 100
1242
- },
1243
- currency: os.currency || 'IDR'
1244
- }
1245
- })
1246
- }
1247
- ]
1248
- }
1249
- })
1250
- }
1247
+ interactiveMessage
1248
+ }
1249
+ } else if ('shop' in message && !!message.shop) {
1250
+ const interactiveMessage = {
1251
+ shopStorefrontMessage: {
1252
+ surface: message.shop.surface,
1253
+ id: message.shop.id
1251
1254
  }
1252
- };
1253
- }
1254
- else if (hasNonNullishProperty(message, 'templateButtons')) {
1255
- const hydratedTemplate = {
1256
- hydratedButtons: message.templateButtons.map((button, i) => {
1257
- const buttonText = button.text || button.buttonText;
1258
- if (hasOptionalProperty(button, 'id')) {
1259
- return {
1260
- index: i,
1261
- quickReplyButton: {
1262
- displayText: buttonText || '👉🏻 Click',
1263
- id: button.id
1264
- }
1265
- };
1255
+ }
1256
+
1257
+ if ('text' in message) {
1258
+ interactiveMessage.body = {
1259
+ text: message.text
1260
+ },
1261
+ interactiveMessage.header = {
1262
+ title: message.title,
1263
+ subtitle: message.subtitle,
1264
+ hasMediaAttachment: false
1266
1265
  }
1267
- else if (hasOptionalProperty(button, 'url')) {
1268
- return {
1269
- index: i,
1270
- urlButton: {
1271
- displayText: buttonText || '🌐 Visit',
1272
- url: button.url
1273
- }
1274
- };
1266
+ } else {
1267
+ if ('caption' in message) {
1268
+ interactiveMessage.body = {
1269
+ text: message.caption
1275
1270
  }
1276
- else if (hasOptionalProperty(button, 'call')) {
1277
- return {
1278
- index: i,
1279
- callButton: {
1280
- displayText: buttonText || '📞 Call',
1281
- phoneNumber: button.call
1282
- }
1283
- };
1271
+
1272
+ interactiveMessage.header = {
1273
+ title: message.title,
1274
+ subtitle: message.subtitle,
1275
+ hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1276
+ ...Object.assign(interactiveMessage, m)
1284
1277
  }
1285
- button.index = button.index || i;
1286
- return button;
1287
- })
1288
- };
1289
- if (hasOptionalProperty(message, 'text')) {
1290
- hydratedTemplate.hydratedContentText = message.text;
1291
- }
1292
- else {
1293
- if (hasOptionalProperty(message, 'caption')) {
1294
- hydratedTemplate.hydratedTitleText = message.title;
1295
- hydratedTemplate.hydratedContentText = message.caption;
1296
1278
  }
1297
- ;
1298
- Object.assign(hydratedTemplate, m);
1299
1279
  }
1300
- if (hasOptionalProperty(message, 'footer')) {
1301
- hydratedTemplate.hydratedFooterText = message.footer;
1280
+
1281
+ if ('footer' in message && !!message.footer) {
1282
+ interactiveMessage.footer = {
1283
+ text: message.footer
1284
+ }
1285
+ }
1286
+
1287
+ interactiveMessage.contextInfo = {
1288
+ ...(message.contextInfo || {}),
1289
+ ...(message.mentions ? {
1290
+ mentionedJid: message.mentions
1291
+ } : {})
1302
1292
  }
1303
- hydratedTemplate.templateId = message.id || 'template-' + Date.now();
1293
+
1304
1294
  m = {
1305
- templateMessage: {
1306
- hydratedFourRowTemplate: hydratedTemplate,
1307
- hydratedTemplate: hydratedTemplate
1308
- }
1309
- };
1310
- }
1311
- else if (hasNonNullishProperty(message, 'nativeFlow')) {
1295
+ interactiveMessage
1296
+ }
1297
+ } else if ('collection' in message && !!message.collection) {
1312
1298
  const interactiveMessage = {
1313
- nativeFlowMessage: prepareNativeFlowButtons(message)
1314
- };
1315
- if (hasOptionalProperty(message, 'bizJid')) {
1316
- interactiveMessage.collectionMessage = {
1317
- bizJid: message.bizJid,
1318
- id: message.id,
1319
- messageVersion: 1
1320
- };
1321
- }
1322
- else if (hasOptionalProperty(message, 'shopSurface')) {
1323
- interactiveMessage.shopStorefrontMessage = {
1324
- surface: message.shopSurface,
1325
- id: message.id,
1326
- messageVersion: 1
1327
- };
1328
- }
1329
- if (hasOptionalProperty(message, 'text')) {
1330
- interactiveMessage.body = { text: message.text };
1331
- }
1332
- else {
1333
- if (hasOptionalProperty(message, 'caption')) {
1334
- const isValidHeader = hasValidInteractiveHeader(m);
1335
- if (!isValidHeader) {
1336
- throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
1299
+ collectionMessage: {
1300
+ bizJid: message.collection.bizJid,
1301
+ id: message.collection.id,
1302
+ messageVersion: message?.collection?.version
1303
+ }
1304
+ }
1305
+
1306
+ if ('text' in message) {
1307
+ interactiveMessage.body = {
1308
+ text: message.text
1309
+ },
1310
+ interactiveMessage.header = {
1311
+ title: message.title,
1312
+ subtitle: message.subtitle,
1313
+ hasMediaAttachment: false
1314
+ }
1315
+ } else {
1316
+ if ('caption' in message) {
1317
+ interactiveMessage.body = {
1318
+ text: message.caption
1337
1319
  }
1338
1320
  interactiveMessage.header = {
1339
- title: message.title || '',
1340
- subtitle: message.subtitle || '',
1341
- hasMediaAttachment: isValidHeader
1342
- };
1343
- interactiveMessage.body = { text: message.caption };
1344
- }
1345
- if (hasOptionalProperty(message, 'thumbnail') && !!message.thumbnail) {
1346
- interactiveMessage.jpegThumbnail = message.thumbnail;
1321
+ title: message.title,
1322
+ subtitle: message.subtitle,
1323
+ hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1324
+ ...Object.assign(interactiveMessage, m)
1325
+ }
1347
1326
  }
1348
- Object.assign(interactiveMessage.header, m);
1349
1327
  }
1350
- if (hasOptionalProperty(message, 'audioFooter')) {
1351
- const { audioMessage } = await prepareWAMessageMedia({
1352
- audio: message.audioFooter
1353
- }, options);
1328
+
1329
+ if ('footer' in message && !!message.footer) {
1354
1330
  interactiveMessage.footer = {
1355
- audioMessage,
1356
- hasMediaAttachment: true
1357
- };
1331
+ text: message.footer
1332
+ }
1358
1333
  }
1359
- else if (hasOptionalProperty(message, 'footer')) {
1360
- interactiveMessage.footer = { text: message.footer };
1334
+
1335
+ interactiveMessage.contextInfo = {
1336
+ ...(message.contextInfo || {}),
1337
+ ...(message.mentions ? {
1338
+ mentionedJid: message.mentions
1339
+ } : {})
1361
1340
  }
1362
- m = { interactiveMessage };
1363
- }
1364
- else if (hasNonNullishProperty(message, 'cards')) {
1341
+
1342
+ m = {
1343
+ interactiveMessage
1344
+ }
1345
+ } else if ('cards' in message && !!message.cards) {
1346
+ const slides = await Promise.all(message.cards.map(async (slide) => {
1347
+ const {
1348
+ image,
1349
+ video,
1350
+ product,
1351
+ title,
1352
+ body,
1353
+ footer,
1354
+ buttons
1355
+ } = slide
1356
+ let header
1357
+
1358
+ if (product) {
1359
+ const { imageMessage } = await prepareWAMessageMedia({
1360
+ image: product.productImage,
1361
+ ...options
1362
+ }, options)
1363
+ header = {
1364
+ productMessage: {
1365
+ product: {
1366
+ ...product,
1367
+ productImage: imageMessage,
1368
+ },
1369
+ ...slide
1370
+ }
1371
+ }
1372
+ } else if (image) {
1373
+ header = await prepareWAMessageMedia({
1374
+ image: image,
1375
+ ...options
1376
+ }, options)
1377
+ } else if (video) {
1378
+ header = await prepareWAMessageMedia({
1379
+ video: video,
1380
+ ...options
1381
+ }, options)
1382
+ }
1383
+
1384
+ const msg = {
1385
+ header: {
1386
+ title,
1387
+ hasMediaAttachment: true,
1388
+ ...header
1389
+ },
1390
+ body: {
1391
+ text: body
1392
+ },
1393
+ footer: {
1394
+ text: footer
1395
+ },
1396
+ nativeFlowMessage: {
1397
+ buttons,
1398
+ }
1399
+ }
1400
+
1401
+ return msg
1402
+ }))
1403
+
1365
1404
  const interactiveMessage = {
1366
1405
  carouselMessage: {
1367
- cards: await Promise.all(message.cards.map(async (card) => {
1368
- let carouselHeader = {};
1369
- if (hasNonNullishProperty(card, 'product')) {
1370
- carouselHeader.productMessage = await prepareProductMessage(card, options);
1371
- }
1372
- else {
1373
- carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({}));
1374
- }
1375
- const isValidHeader = hasValidCarouselHeader(carouselHeader);
1376
- if (!isValidHeader) {
1377
- throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
1378
- }
1379
- const carouselCard = {
1380
- nativeFlowMessage: prepareNativeFlowButtons(card.nativeFlow ? card : [])
1381
- };
1382
- if (hasOptionalProperty(card, 'text')) {
1383
- carouselCard.body = { text: card.text };
1384
- }
1385
- else {
1386
- if (hasOptionalProperty(card, 'caption')) {
1387
- carouselCard.header = {
1388
- title: card.title || '',
1389
- subtitle: card.subtitle || '',
1390
- hasMediaAttachment: isValidHeader
1391
- };
1392
- carouselCard.body = { text: card.caption };
1393
- }
1394
- if (hasOptionalProperty(card, 'thumbnail') && !!card.thumbnail) {
1395
- carouselCard.jpegThumbnail = card.thumbnail;
1396
- }
1397
- Object.assign(carouselCard.header, carouselHeader);
1398
- }
1399
- if (hasOptionalProperty(card, 'audioFooter')) {
1400
- const { audioMessage } = await prepareWAMessageMedia({
1401
- audio: card.audioFooter
1402
- }, options);
1403
- carouselCard.footer = {
1404
- audioMessage,
1405
- hasMediaAttachment: true
1406
- };
1407
- }
1408
- else if (hasOptionalProperty(card, 'footer')) {
1409
- carouselCard.footer = { text: card.footer };
1410
- }
1411
- return carouselCard;
1412
- })),
1413
- carouselCardType: CarouselCardType.UNKNOWN,
1414
- messageVersion: 1
1406
+ cards: slides
1415
1407
  }
1416
- };
1417
- if (hasOptionalProperty(message, 'text')) {
1418
- interactiveMessage.body = { text: message.text };
1419
1408
  }
1420
- if (hasOptionalProperty(message, 'footer')) {
1421
- interactiveMessage.footer = { text: message.footer };
1422
- }
1423
- m = { interactiveMessage };
1424
- }
1425
- else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
1426
- const requestPaymentMessage = {
1427
- amount: {
1428
- currencyCode: 'IDR',
1429
- offset: 1000,
1430
- value: 1000
1431
- },
1432
- amount1000: 1000,
1433
- currencyCodeIso4217: 'IDR',
1434
- expiryTimestamp: Date.now(),
1435
- noteMessage: m,
1436
- requestFrom: message.requestPaymentFrom,
1437
- ...message
1438
- };
1439
- delete requestPaymentMessage.requestPaymentFrom;
1440
- if (hasNonNullishProperty(m, 'extendedTextMessage') || hasNonNullishProperty(m, 'stickerMessage')) {
1441
- Object.assign(requestPaymentMessage.noteMessage, m);
1442
- }
1443
- else {
1444
- throw new Boom('Invalid message type for request payment note message', { statusCode: 400 });
1445
- }
1446
- m = { requestPaymentMessage };
1447
- }
1448
- else if (hasNonNullishProperty(message, 'invoiceNote')) {
1449
- const attachment = m.imageMessage || m.documentMessage;
1450
- const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
1451
- const invoiceMessage = {
1452
- attachmentType: proto.Message.InvoiceMessage.AttachmentType[type === 'DOCUMENT' ? 'PDF' : 'IMAGE'],
1453
- note: message.invoiceNote
1454
- };
1455
- if (attachment) {
1456
- const { directPath, fileEncSha256, fileSha256, jpegThumbnail = undefined, mediaKey, mediaKeyTimestamp, mimetype } = attachment;
1457
- Object.assign(invoiceMessage, {
1458
- attachmentDirectPath: directPath,
1459
- attachmentFileEncSha256: fileEncSha256,
1460
- attachmentFileSha256: fileSha256,
1461
- attachmentJpegThumbnail: jpegThumbnail,
1462
- attachmentMediaKey: mediaKey,
1463
- attachmentMediaKeyTimestamp: mediaKeyTimestamp,
1464
- attachmentMimetype: mimetype,
1465
- token: generateMessageIDV2()
1466
- });
1467
- }
1468
- else {
1469
- throw new Boom('Invalid media type for invoice message', { statusCode: 400 });
1470
- }
1471
- m = { invoiceMessage };
1472
- }
1473
- if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
1474
- const messageType = Object.keys(m)[0];
1475
- const key = m[messageType];
1476
- const content = message.externalAdReply;
1477
- if ('thumbnail' in content && !Buffer.isBuffer(content.thumbnail)) {
1478
- throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
1479
- }
1480
- if (!content.url || typeof content.url !== 'string' || !/^https?:\/\//i.test(content.url)) {
1481
- options.logger?.warn({ givenUrl: content.url }, 'externalAdReply: field "url" kosong/tidak valid (harus diawali http:// atau https://) — fallback ke DONATE_URL, link produk asli TIDAK terkirim. Cek kode yang mengisi `url` di externalAdReply.');
1482
- content.url = DONATE_URL;
1483
- }
1484
- const externalAdReply = {
1485
- ...content,
1486
- body: content.body,
1487
- mediaType: content.mediaType || 1,
1488
- mediaUrl: content.url,
1489
- renderLargerThumbnail: content.largeThumbnail,
1490
- sourceUrl: content.url,
1491
- thumbnail: content.thumbnail,
1492
- thumbnailUrl: content.url + '?update=' + Date.now(),
1493
- title: content.title || LIBRARY_NAME
1494
- };
1495
- delete externalAdReply.subTitle;
1496
- delete externalAdReply.largeThumbnail;
1497
- delete externalAdReply.url;
1498
- if ('contextInfo' in key && !!key.contextInfo) {
1499
- key.contextInfo.externalAdReply = { ...key.contextInfo.externalAdReply, ...externalAdReply };
1500
- }
1501
- else if (key) {
1502
- key.contextInfo = { externalAdReply };
1409
+
1410
+ if ('text' in message) {
1411
+ interactiveMessage.body = {
1412
+ text: message.text
1413
+ },
1414
+ interactiveMessage.header = {
1415
+ title: message.title,
1416
+ subtitle: message.subtitle,
1417
+ hasMediaAttachment: false
1418
+ }
1503
1419
  }
1504
- }
1505
- if ((hasOptionalProperty(message, 'mentions') && message.mentions?.length) ||
1506
- (hasOptionalProperty(message, 'mentionAll') && message.mentionAll)) {
1507
- const messageType = Object.keys(m)[0];
1508
- const key = m[messageType];
1509
- if (key && 'contextInfo' in key) {
1510
- key.contextInfo = key.contextInfo || {};
1511
- if (message.mentions?.length) {
1512
- key.contextInfo.mentionedJid = message.mentions;
1513
- }
1514
- if (message.mentionAll) {
1515
- key.contextInfo.nonJidMentions = 1;
1420
+
1421
+ if ('footer' in message && !!message.footer) {
1422
+ interactiveMessage.footer = {
1423
+ text: message.footer
1516
1424
  }
1517
1425
  }
1518
- else if (key) {
1519
- key.contextInfo = {
1520
- mentionedJid: message.mentions,
1521
- nonJidMentions: message.mentionAll ? 1 : 0
1522
- };
1426
+
1427
+ interactiveMessage.contextInfo = {
1428
+ ...(message.contextInfo || {}),
1429
+ ...(message.mentions ? {
1430
+ mentionedJid: message.mentions
1431
+ } : {})
1523
1432
  }
1524
- }
1525
- if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
1526
- const messageType = Object.keys(m)[0];
1527
- const key = m[messageType];
1528
- if ('contextInfo' in key && !!key.contextInfo) {
1529
- key.contextInfo = { ...key.contextInfo, ...message.contextInfo };
1530
- }
1531
- else if (key) {
1532
- key.contextInfo = message.contextInfo;
1433
+
1434
+ m = {
1435
+ interactiveMessage
1533
1436
  }
1534
1437
  }
1535
- if (hasOptionalProperty(message, 'groupStatus') && !!message.groupStatus) {
1536
- const messageType = Object.keys(m)[0];
1537
- const key = m[messageType];
1538
- if ('contextInfo' in key && !!key.contextInfo) {
1539
- key.contextInfo.isGroupStatus = message.groupStatus;
1540
- }
1541
- else if (key) {
1542
- key.contextInfo = {
1543
- isGroupStatus: message.groupStatus
1544
- };
1545
- }
1546
- m = { groupStatusMessageV2: { message: m } };
1547
- delete message.groupStatus;
1548
- }
1549
- if (hasOptionalProperty(message, 'spoiler') && !!message.spoiler) {
1550
- const messageType = Object.keys(m)[0];
1551
- const key = m[messageType];
1552
- if ('contextInfo' in key && !!key.contextInfo) {
1553
- key.contextInfo.isSpoiler = message.spoiler;
1554
- }
1555
- else if (key) {
1556
- key.contextInfo = {
1557
- isSpoiler: message.spoiler
1558
- };
1559
- }
1560
- m = { spoilerMessage: { message: m } };
1561
- delete message.spoiler;
1562
- }
1563
- else if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
1564
- if (!m.interactiveMessage) {
1565
- throw new Boom('Invalid message type for template', { statusCode: 400 });
1566
- }
1438
+
1439
+ if ('ephemeral' in message && !!message.ephemeral) {
1567
1440
  m = {
1568
- templateMessage: {
1569
- interactiveMessageTemplate: m.interactiveMessage,
1570
- templateId: message.id || 'template-' + Date.now()
1441
+ ephemeralMessage: {
1442
+ message: m
1571
1443
  }
1572
- };
1573
- delete message.interactiveAsTemplate;
1574
- }
1575
- if (hasOptionalProperty(message, 'ephemeral') && !!message.ephemeral) {
1576
- m = { ephemeralMessage: { message: m } };
1577
- delete message.ephemeral;
1578
- }
1579
- if (hasOptionalProperty(message, 'isLottie') && !!message.isLottie) {
1580
- m = { lottieStickerMessage: { message: m } };
1581
- }
1582
- else if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
1583
- m = { viewOnceMessage: { message: m } };
1444
+ }
1584
1445
  }
1585
- else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
1586
- m = { viewOnceMessageV2: { message: m } };
1587
- delete message.viewOnceV2;
1446
+
1447
+ if ('viewOnce' in message && !!message.viewOnce) {
1448
+ m = {
1449
+ viewOnceMessageV2: {
1450
+ message: m
1451
+ }
1452
+ }
1588
1453
  }
1589
- else if (hasOptionalProperty(message, 'viewOnceV2Extension') && !!message.viewOnceV2Extension) {
1590
- m = { viewOnceMessageV2Extension: { message: m } };
1591
- delete message.viewOnceV2Extension;
1454
+
1455
+ if ('viewOnceExt' in message && !!message.viewOnceExt) {
1456
+ m = {
1457
+ viewOnceMessageV2Extension: {
1458
+ message: m
1459
+ }
1460
+ }
1592
1461
  }
1593
- if (hasOptionalProperty(message, 'edit')) {
1462
+
1463
+ if ('edit' in message) {
1594
1464
  m = {
1595
1465
  protocolMessage: {
1596
1466
  key: message.edit,
1597
1467
  editedMessage: m,
1598
1468
  timestampMs: Date.now(),
1599
- type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1469
+ type: Types_1.WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1600
1470
  }
1601
- };
1602
- }
1603
- if (shouldIncludeReportingToken(m)) {
1604
- m.messageContextInfo = m.messageContextInfo || {};
1605
- if (!m.messageContextInfo.messageSecret) {
1606
- m.messageContextInfo.messageSecret = randomBytes(32);
1607
1471
  }
1608
1472
  }
1609
- return WAProto.Message.create(m);
1610
- };
1611
- export const generateWAMessageFromContent = (jid, message, options) => {
1612
- // set timestamp to now
1613
- // if not specified
1473
+
1474
+ return Types_1.WAProto.Message.fromObject(m)
1475
+ }
1476
+
1477
+ const generateWAMessageFromContent = (jid, message, options) => {
1614
1478
  if (!options.timestamp) {
1615
- options.timestamp = new Date();
1616
- }
1617
- const innerMessage = normalizeMessageContent(message);
1618
- const messageContextInfo = message.messageContextInfo;
1619
- const key = getContentType(innerMessage);
1620
- const timestamp = unixTimestampSeconds(options.timestamp);
1621
- const isNewsletter = isJidNewsletter(jid);
1622
- const { quoted, userJid } = options;
1623
- if (quoted) {
1624
- const participant = quoted.key.fromMe
1625
- ? userJid // TODO: Add support for LIDs
1626
- : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
1627
- let quotedMsg = normalizeMessageContent(quoted.message);
1628
- const msgType = getContentType(quotedMsg);
1629
- // strip any redundant properties
1630
- quotedMsg = proto.Message.create({ [msgType]: quotedMsg[msgType] });
1631
- const quotedContent = quotedMsg[msgType];
1479
+ options.timestamp = new Date()
1480
+ }
1481
+
1482
+ const innerMessage = normalizeMessageContent(message)
1483
+ const key = getContentType(innerMessage)
1484
+ const timestamp = generics_1.unixTimestampSeconds(options.timestamp)
1485
+ const {
1486
+ quoted,
1487
+ userJid
1488
+ } = options
1489
+
1490
+ if (quoted && !WABinary_1.isJidNewsletter(jid)) {
1491
+ const participant = quoted.key.fromMe ? userJid : quoted.participant || quoted.key.participant || quoted.key.remoteJid
1492
+ let quotedMsg = normalizeMessageContent(quoted.message)
1493
+ const msgType = getContentType(quotedMsg)
1494
+
1495
+ quotedMsg = WAProto_1.proto.Message.fromObject({
1496
+ [msgType]: quotedMsg[msgType]
1497
+ })
1498
+
1499
+ const quotedContent = quotedMsg[msgType]
1632
1500
  if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
1633
- delete quotedContent.contextInfo;
1501
+ delete quotedContent.contextInfo
1502
+ }
1503
+
1504
+ let requestPayment
1505
+ if (key === 'requestPaymentMessage') {
1506
+ if (innerMessage?.requestPaymentMessage && innerMessage?.requestPaymentMessage?.noteMessage?.extendedTextMessage) {
1507
+ requestPayment = innerMessage?.requestPaymentMessage?.noteMessage?.extendedTextMessage
1508
+ } else if (innerMessage?.requestPaymentMessage && innerMessage?.requestPaymentMessage?.noteMessage?.stickerMessage) {
1509
+ requestPayment = innerMessage.requestPaymentMessage?.noteMessage?.stickerMessage
1510
+ }
1634
1511
  }
1635
- const contextInfo = ('contextInfo' in innerMessage[key] && innerMessage[key]?.contextInfo) || {};
1636
- contextInfo.participant = jidNormalizedUser(participant);
1637
- contextInfo.stanzaId = quoted.key.id;
1638
- contextInfo.quotedMessage = quotedMsg;
1639
- // if a participant is quoted, then it must be a group
1640
- // hence, remoteJid of group must also be entered
1641
- if (!isNewsletter && jid !== quoted.key.remoteJid) {
1642
- contextInfo.remoteJid = quoted.key.remoteJid;
1512
+
1513
+ const contextInfo = (key === 'requestPaymentMessage' ? requestPayment?.contextInfo : innerMessage[key].contextInfo) || {}
1514
+ contextInfo.participant = WABinary_1.jidNormalizedUser(participant)
1515
+ contextInfo.stanzaId = quoted.key.id
1516
+ contextInfo.quotedMessage = quotedMsg
1517
+
1518
+ if (jid !== quoted.key.remoteJid) {
1519
+ contextInfo.remoteJid = quoted.key.remoteJid
1643
1520
  }
1644
- if (contextInfo && innerMessage[key]) {
1645
- /* @ts-ignore */
1646
- innerMessage[key].contextInfo = contextInfo;
1521
+
1522
+ if (contextInfo.quotedMessage) {
1523
+ contextInfo.quotedType = WAProto_1.proto.ContextInfo.QuotedType.EXPLICIT
1524
+ }
1525
+
1526
+ if (key === 'requestPaymentMessage' && requestPayment) {
1527
+ requestPayment.contextInfo = contextInfo
1528
+ } else {
1529
+ innerMessage[key].contextInfo = contextInfo
1530
+ }
1531
+ }
1532
+
1533
+ if (key !== 'protocolMessage' && key !== 'ephemeralMessage' && !WABinary_1.isJidNewsletter(jid)) {
1534
+ message.messageContextInfo = {
1535
+ messageSecret: crypto_1.randomBytes(32),
1536
+ ...message.messageContextInfo
1647
1537
  }
1648
1538
  }
1649
- if (
1650
- // if we want to send a disappearing message
1651
- !!options?.ephemeralExpiration &&
1652
- // and it's not a protocol message -- delete, toggle disappear message
1653
- key !== 'protocolMessage' &&
1654
- // already not converted to disappearing message
1655
- key !== 'ephemeralMessage' &&
1656
- // newsletters don't support ephemeral messages
1657
- !isNewsletter) {
1658
- /* @ts-ignore */
1539
+
1540
+ if (!!options?.ephemeralExpiration && key !== 'protocolMessage' && key !== 'ephemeralMessage' && !WABinary_1.isJidNewsletter(jid)) {
1659
1541
  innerMessage[key].contextInfo = {
1660
1542
  ...(innerMessage[key].contextInfo || {}),
1661
- expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL
1662
- //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
1663
- };
1664
- }
1665
- if (messageContextInfo?.messageSecret && (isPnUser(jid) || isLidUser(jid))) {
1666
- messageContextInfo.deviceListMetadata = {
1667
- recipientKeyHash: randomBytes(10),
1668
- recipientTimestamp: unixTimestampSeconds()
1669
- };
1670
- messageContextInfo.deviceListMetadataVersion = 2;
1543
+ expiration: options.ephemeralExpiration
1544
+ }
1671
1545
  }
1672
- message = WAProto.Message.create(message);
1546
+
1547
+ message = Types_1.WAProto.Message.fromObject(message)
1548
+
1673
1549
  const messageJSON = {
1674
1550
  key: {
1675
1551
  remoteJid: jid,
1676
1552
  fromMe: true,
1677
- id: options?.messageId || generateMessageIDV2()
1553
+ id: options?.messageId || generics_1.generateMessageID()
1678
1554
  },
1679
1555
  message: message,
1680
1556
  messageTimestamp: timestamp,
1681
1557
  messageStubParameters: [],
1682
- participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined, // TODO: Add support for LIDs
1683
- status: WAMessageStatus.PENDING
1684
- };
1685
- return WAProto.WebMessageInfo.fromObject(messageJSON);
1686
- };
1687
- export const generateWAMessage = async (jid, content, options) => {
1688
- // ensure msg ID is with every log
1689
- options.logger = options?.logger?.child({ msgId: options.messageId });
1690
- // Pass jid in the options to generateWAMessageContent
1558
+ participant: WABinary_1.isJidGroup(jid) || WABinary_1.isJidStatusBroadcast(jid) ? userJid : undefined,
1559
+ status: Types_1.WAMessageStatus.PENDING
1560
+ }
1561
+
1562
+ return Types_1.WAProto.WebMessageInfo.fromObject(messageJSON)
1563
+ }
1564
+
1565
+ const generateWAMessage = async (jid, content, options) => {
1566
+ options.logger = options?.logger?.child({
1567
+ msgId: options.messageId
1568
+ })
1569
+
1691
1570
  if (jid) {
1692
- options.jid = jid;
1571
+ options.jid = jid
1693
1572
  }
1694
- return generateWAMessageFromContent(jid, await generateWAMessageContent(content, options), options);
1695
- };
1696
- /** Get the key to access the true type of content */
1697
- export const getContentType = (content) => {
1573
+
1574
+ return generateWAMessageFromContent(jid, await generateWAMessageContent(content, {
1575
+ newsletter: WABinary_1.isJidNewsletter(jid),
1576
+ ...options
1577
+ }), options)
1578
+ }
1579
+
1580
+ const getContentType = (content) => {
1698
1581
  if (content) {
1699
- const keys = Object.keys(content);
1700
- const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage');
1701
- return key;
1582
+ const keys = Object.keys(content)
1583
+ const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage' && k !== 'messageContextInfo')
1584
+
1585
+ return key
1702
1586
  }
1703
- };
1704
- export const normalizeMessageContent = (content) => {
1587
+ }
1588
+
1589
+ const normalizeMessageContent = (content) => {
1705
1590
  if (!content) {
1706
- return undefined;
1591
+ return undefined
1707
1592
  }
1708
- // set max iterations to prevent an infinite loop
1593
+
1709
1594
  for (let i = 0; i < 5; i++) {
1710
- const inner = getFutureProofMessage(content);
1595
+ const inner = getFutureProofMessage(content)
1711
1596
  if (!inner) {
1712
- break;
1597
+ break
1713
1598
  }
1714
- content = inner.message;
1599
+
1600
+ content = inner.message
1715
1601
  }
1716
- return content;
1602
+
1603
+ return content
1604
+
1717
1605
  function getFutureProofMessage(message) {
1718
- return (message?.associatedChildMessage ||
1719
- message?.botForwardedMessage ||
1720
- message?.botInvokeMessage ||
1721
- message?.botTaskMessage ||
1722
- message?.documentWithCaptionMessage ||
1723
- message?.editedMessage ||
1724
- message?.ephemeralMessage ||
1725
- message?.eventCoverImage ||
1726
- message?.groupMentionedMessage ||
1727
- message?.groupStatusMentionMessage ||
1728
- message?.groupStatusMessage ||
1729
- message?.groupStatusMessageV2 ||
1730
- message?.limitSharingMessage ||
1731
- message?.lottieStickerMessage ||
1732
- message?.newsletterAdminProfileMessage ||
1733
- message?.newsletterAdminProfileMessageV2 ||
1734
- message?.newsletterAdminProfileStatusMessage ||
1735
- message?.pollCreationMessageV4 ||
1736
- message?.pollCreationOptionImageMessage ||
1737
- message?.questionMessage ||
1738
- message?.questionReplyMessage ||
1739
- message?.spoilerMessage ||
1740
- message?.statusAddYours ||
1741
- message?.statusMentionMessage ||
1742
- message?.viewOnceMessage ||
1743
- message?.viewOnceMessageV2 ||
1744
- message?.viewOnceMessageV2Extension);
1606
+ return ((message === null || message === void 0 ? void 0 : message.editedMessage) || (message === null || message === void 0 ? void 0 : message.statusAddYours) || (message === null || message === void 0 ? void 0 : message.botTaskMessage) || (message === null || message === void 0 ? void 0 : message.eventCoverImage) || (message === null || message === void 0 ? void 0 : message.questionMessage) || (message === null || message === void 0 ? void 0 : message.questionReplyMessage) || (message === null || message === void 0 ? void 0 : message.viewOnceMessage) || (message === null || message === void 0 ? void 0 : message.botInvokeMessage) || (message === null || message === void 0 ? void 0 : message.ephemeralMessage) || (message === null || message === void 0 ? void 0 : message.lottieStickerMessage) || (message === null || message === void 0 ? void 0 : message.groupStatusMessage) || (message === null || message === void 0 ? void 0 : message.limitSharingMessage) || (message === null || message === void 0 ? void 0 : message.viewOnceMessageV2) || (message === null || message === void 0 ? void 0 : message.botForwardedMessage) || (message === null || message === void 0 ? void 0 : message.statusMentionMessage) || (message === null || message === void 0 ? void 0 : message.groupStatusMessageV2) || (message === null || message === void 0 ? void 0 : message.pollCreationMessageV4) || (message === null || message === void 0 ? void 0 : message.pollCreationMessageV5) || (message === null || message === void 0 ? void 0 : message.associatedChildMessage) || (message === null || message === void 0 ? void 0 : message.groupMentionedMessage) || (message === null || message === void 0 ? void 0 : message.groupStatusMentionMessage) || (message === null || message === void 0 ? void 0 : message.viewOnceMessageV2Extension) || (message === null || message === void 0 ? void 0 : message.documentWithCaptionMessage) || (message === null || message === void 0 ? void 0 : message.pollCreationOptionImageMessage) || (message === null || message === void 0 ? void 0 : message.spoilerMessage) || (message === null || message === void 0 ? void 0 : message.newsletterAdminProfileMessage) || (message === null || message === void 0 ? void 0 : message.newsletterAdminProfileMessageV2) || (message === null || message === void 0 ? void 0 : message.newsletterAdminProfileStatusMessage))
1745
1607
  }
1746
- };
1747
- /**
1748
- * Extract the true message content from a message
1749
- * Eg. extracts the inner message from a disappearing message/view once message
1750
- */
1751
- export const extractMessageContent = (content) => {
1752
- const extractFromTemplateMessage = (msg) => {
1753
- if (msg.imageMessage) {
1754
- return { imageMessage: msg.imageMessage };
1755
- }
1756
- else if (msg.documentMessage) {
1757
- return { documentMessage: msg.documentMessage };
1758
- }
1759
- else if (msg.videoMessage) {
1760
- return { videoMessage: msg.videoMessage };
1761
- }
1762
- else if (msg.locationMessage) {
1763
- return { locationMessage: msg.locationMessage };
1764
- }
1765
- else {
1608
+ }
1609
+
1610
+ const extractMessageContent = (content) => {
1611
+ const extractFromButtonsMessage = (msg) => {
1612
+ const header = typeof msg.header === 'object' && msg.header !== null
1613
+
1614
+ if (header ? msg.header?.imageMessage : msg.imageMessage) {
1615
+ return {
1616
+ imageMessage: header ? msg.header.imageMessage : msg.imageMessage
1617
+ }
1618
+ } else if (header ? msg.header?.documentMessage : msg.documentMessage) {
1619
+ return {
1620
+ documentMessage: header ? msg.header.documentMessage : msg.documentMessage
1621
+ }
1622
+ } else if (header ? msg.header?.videoMessage : msg.videoMessage) {
1766
1623
  return {
1767
- conversation: 'contentText' in msg ? msg.contentText : 'hydratedContentText' in msg ? msg.hydratedContentText : ''
1768
- };
1624
+ videoMessage: header ? msg.header.videoMessage : msg.videoMessage
1625
+ }
1626
+ } else if (header ? msg.header?.locationMessage : msg.locationMessage) {
1627
+ return {
1628
+ locationMessage: header ? msg.header.locationMessage : msg.locationMessage
1629
+ }
1630
+ } else if (header ? msg.header?.productMessage : msg.productMessage) {
1631
+ return {
1632
+ productMessage: header ? msg.header.productMessage : msg.productMessage
1633
+ }
1634
+ } else {
1635
+ return {
1636
+ conversation: 'contentText' in msg ? msg.contentText : ('hydratedContentText' in msg ? msg.hydratedContentText : 'body' in msg ? msg.body.text : '')
1637
+ }
1769
1638
  }
1770
- };
1771
- content = normalizeMessageContent(content);
1639
+ }
1640
+
1641
+ content = normalizeMessageContent(content)
1772
1642
  if (content?.buttonsMessage) {
1773
- return extractFromTemplateMessage(content.buttonsMessage);
1643
+ return extractFromButtonsMessage(content.buttonsMessage)
1644
+ }
1645
+ if (content?.interactiveMessage) {
1646
+ return extractFromButtonsMessage(content.interactiveMessage)
1647
+ }
1648
+ if (content?.templateMessage?.interactiveMessageTemplate) {
1649
+ return extractFromButtonsMessage(content?.templateMessage?.interactiveMessageTemplate)
1774
1650
  }
1775
1651
  if (content?.templateMessage?.hydratedFourRowTemplate) {
1776
- return extractFromTemplateMessage(content?.templateMessage?.hydratedFourRowTemplate);
1652
+ return extractFromButtonsMessage(content?.templateMessage?.hydratedFourRowTemplate)
1777
1653
  }
1778
1654
  if (content?.templateMessage?.hydratedTemplate) {
1779
- return extractFromTemplateMessage(content?.templateMessage?.hydratedTemplate);
1655
+ return extractFromButtonsMessage(content?.templateMessage?.hydratedTemplate)
1780
1656
  }
1781
1657
  if (content?.templateMessage?.fourRowTemplate) {
1782
- return extractFromTemplateMessage(content?.templateMessage?.fourRowTemplate);
1658
+ return extractFromButtonsMessage(content?.templateMessage?.fourRowTemplate)
1783
1659
  }
1784
- return content;
1785
- };
1786
- /**
1787
- * Returns the device predicted by message ID
1788
- */
1789
- export const getDevice = (id) => /^3A.{18}$/.test(id)
1790
- ? 'ios'
1791
- : /^3E.{20}$/.test(id)
1792
- ? 'web'
1793
- : /^(.{21}|.{32})$/.test(id)
1794
- ? 'android'
1795
- : /^(3F|.{18}$)/.test(id)
1796
- ? 'desktop'
1797
- : 'unknown';
1798
- /** Upserts a receipt in the message */
1799
- export const updateMessageWithReceipt = (msg, receipt) => {
1800
- msg.userReceipt = msg.userReceipt || [];
1801
- const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid);
1660
+ return content
1661
+ }
1662
+
1663
+ const getDevice = (id) => /^3A.{18}$/.test(id) ? 'ios' : /^3E.{20}$/.test(id) ? 'web' : /^(.{21}|.{32})$/.test(id) ? 'android' : /^(3F|.{18}$)/.test(id) ? 'desktop' : 'bots'
1664
+
1665
+ const updateMessageWithReceipt = (msg, receipt) => {
1666
+ msg.userReceipt = msg.userReceipt || []
1667
+ const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid)
1802
1668
  if (recp) {
1803
- Object.assign(recp, receipt);
1669
+ Object.assign(recp, receipt)
1670
+ } else {
1671
+ msg.userReceipt.push(receipt)
1804
1672
  }
1805
- else {
1806
- msg.userReceipt.push(receipt);
1807
- }
1808
- };
1809
- /** Update the message with a new reaction */
1810
- export const updateMessageWithReaction = (msg, reaction) => {
1811
- const authorID = getKeyAuthor(reaction.key);
1812
- const reactions = (msg.reactions || []).filter(r => getKeyAuthor(r.key) !== authorID);
1813
- reaction.text = reaction.text || '';
1814
- reactions.push(reaction);
1815
- msg.reactions = reactions;
1816
- };
1817
- /** Update the message with a new poll update */
1818
- export const updateMessageWithPollUpdate = (msg, update) => {
1819
- const authorID = getKeyAuthor(update.pollUpdateMessageKey);
1820
- const reactions = (msg.pollUpdates || []).filter(r => getKeyAuthor(r.pollUpdateMessageKey) !== authorID);
1673
+ }
1674
+
1675
+ const updateMessageWithReaction = (msg, reaction) => {
1676
+ const authorID = generics_1.getKeyAuthor(reaction.key)
1677
+ const reactions = (msg.reactions || []).filter(r => generics_1.getKeyAuthor(r.key) !== authorID)
1678
+ reaction.text = reaction.text || ''
1679
+ reactions.push(reaction)
1680
+ msg.reactions = reactions
1681
+ }
1682
+
1683
+ const updateMessageWithPollUpdate = (msg, update) => {
1684
+ const authorID = generics_1.getKeyAuthor(update.pollUpdateMessageKey)
1685
+ const votes = (msg.pollUpdates || []).filter(r => generics_1.getKeyAuthor(r.pollUpdateMessageKey) !== authorID)
1686
+
1821
1687
  if (update.vote?.selectedOptions?.length) {
1822
- reactions.push(update);
1688
+ votes.push(update)
1823
1689
  }
1824
- msg.pollUpdates = reactions;
1825
- };
1826
- /** Update the message with a new event response */
1827
- export const updateMessageWithEventResponse = (msg, update) => {
1828
- const authorID = getKeyAuthor(update.eventResponseMessageKey);
1829
- const responses = (msg.eventResponses || []).filter(r => getKeyAuthor(r.eventResponseMessageKey) !== authorID);
1830
- responses.push(update);
1831
- msg.eventResponses = responses;
1832
- };
1833
- /**
1834
- * Aggregates all poll updates in a poll.
1835
- * @param msg the poll creation message
1836
- * @param meId your jid
1837
- * @returns A list of options & their voters
1838
- */
1839
- export function getAggregateVotesInPollMessage({ message, pollUpdates }, meId) {
1840
- const opts = message?.pollCreationMessage?.options ||
1841
- message?.pollCreationMessageV2?.options ||
1842
- message?.pollCreationMessageV3?.options ||
1843
- [];
1690
+
1691
+ msg.pollUpdates = votes
1692
+ }
1693
+
1694
+ function getAggregateVotesInPollMessage({
1695
+ message,
1696
+ pollUpdates
1697
+ }, meId) {
1698
+ message = normalizeMessageContent(message)
1699
+
1700
+ const opts = message?.pollCreationMessage?.options || message?.pollCreationMessageV2?.options || message?.pollCreationMessageV3?.options || []
1701
+
1844
1702
  const voteHashMap = opts.reduce((acc, opt) => {
1845
- const hash = sha256(Buffer.from(opt.optionName || '')).toString();
1703
+ const hash = crypto_2.sha256(Buffer.from(opt.optionName || '')).toString()
1846
1704
  acc[hash] = {
1847
1705
  name: opt.optionName || '',
1848
1706
  voters: []
1849
- };
1850
- return acc;
1851
- }, {});
1707
+ }
1708
+
1709
+ return acc
1710
+ }, {})
1711
+
1852
1712
  for (const update of pollUpdates || []) {
1853
- const { vote } = update;
1713
+ const { vote } = update
1854
1714
  if (!vote) {
1855
- continue;
1715
+ continue
1856
1716
  }
1717
+
1857
1718
  for (const option of vote.selectedOptions || []) {
1858
- const hash = option.toString();
1859
- let data = voteHashMap[hash];
1719
+ const hash = option.toString()
1720
+ let data = voteHashMap[hash]
1721
+
1860
1722
  if (!data) {
1861
1723
  voteHashMap[hash] = {
1862
1724
  name: 'Unknown',
1863
1725
  voters: []
1864
- };
1865
- data = voteHashMap[hash];
1726
+ }
1727
+
1728
+ data = voteHashMap[hash]
1866
1729
  }
1867
- voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId));
1730
+
1731
+ voteHashMap[hash].voters.push(generics_1.getKeyAuthor(update.pollUpdateMessageKey, meId))
1868
1732
  }
1869
1733
  }
1870
- return Object.values(voteHashMap);
1734
+
1735
+ return Object.values(voteHashMap)
1871
1736
  }
1872
- /**
1873
- * Aggregates all event responses in an event message.
1874
- * @param msg the event creation message
1875
- * @param meId your jid
1876
- * @returns A list of response types & their responders
1877
- */
1878
- export function getAggregateResponsesInEventMessage({ eventResponses }, meId) {
1879
- const responseTypes = ['GOING', 'NOT_GOING', 'MAYBE'];
1880
- const responseMap = {};
1881
- for (const type of responseTypes) {
1882
- responseMap[type] = {
1883
- response: type,
1884
- responders: []
1885
- };
1886
- }
1887
- for (const update of eventResponses || []) {
1888
- const responseType = update.eventResponse || 'UNKNOWN';
1889
- if (responseType !== 'UNKNOWN' && responseMap[responseType]) {
1890
- responseMap[responseType].responders.push(getKeyAuthor(update.eventResponseMessageKey, meId));
1891
- }
1892
- }
1893
- return Object.values(responseMap);
1894
- }
1895
- /** Given a list of message keys, aggregates them by chat & sender. Useful for sending read receipts in bulk */
1896
- export const aggregateMessageKeysNotFromMe = (keys) => {
1897
- const keyMap = {};
1737
+
1738
+ const aggregateMessageKeysNotFromMe = (keys) => {
1739
+ const keyMap = {}
1740
+
1898
1741
  for (const { remoteJid, id, participant, fromMe } of keys) {
1899
1742
  if (!fromMe) {
1900
- const uqKey = `${remoteJid}:${participant || ''}`;
1743
+ const uqKey = `${remoteJid}:${participant || ''}`
1744
+
1901
1745
  if (!keyMap[uqKey]) {
1902
1746
  keyMap[uqKey] = {
1903
1747
  jid: remoteJid,
1904
1748
  participant: participant,
1905
1749
  messageIds: []
1906
- };
1750
+ }
1907
1751
  }
1908
- keyMap[uqKey].messageIds.push(id);
1752
+
1753
+ keyMap[uqKey].messageIds.push(id)
1909
1754
  }
1910
1755
  }
1911
- return Object.values(keyMap);
1912
- };
1913
- const REUPLOAD_REQUIRED_STATUS = [410, 404];
1914
- /**
1915
- * Downloads the given message. Throws an error if it's not a media message
1916
- */
1917
- export const downloadMediaMessage = async (message, type, options, ctx) => {
1756
+
1757
+ return Object.values(keyMap)
1758
+ }
1759
+
1760
+ const REUPLOAD_REQUIRED_STATUS = [410, 404]
1761
+
1762
+ const downloadMediaMessage = async (message, type, options, ctx) => {
1918
1763
  const result = await downloadMsg().catch(async (error) => {
1919
- if (ctx &&
1920
- typeof error?.status === 'number' && // treat errors with status as HTTP failures requiring reupload
1921
- REUPLOAD_REQUIRED_STATUS.includes(error.status)) {
1922
- ctx.logger.info({ key: message.key }, 'sending reupload media request...');
1923
- // request reupload
1924
- message = await ctx.reuploadRequest(message);
1925
- const result = await downloadMsg();
1926
- return result;
1927
- }
1928
- throw error;
1929
- });
1930
- return result;
1764
+ if (ctx && typeof error?.status === 'number' && REUPLOAD_REQUIRED_STATUS.includes(error.status)) {
1765
+ ctx.logger.info({
1766
+ key: message.key
1767
+ }, 'sending reupload media request...')
1768
+
1769
+ message = await ctx.reuploadRequest(message)
1770
+ const result = await downloadMsg()
1771
+ return result
1772
+ }
1773
+ throw error
1774
+ })
1775
+
1776
+ return result
1777
+
1931
1778
  async function downloadMsg() {
1932
- const mContent = extractMessageContent(message.message);
1779
+ const mContent = extractMessageContent(message.message)
1933
1780
  if (!mContent) {
1934
- throw new Boom('No message present', { statusCode: 400, data: message });
1781
+ throw new boom_1.Boom('No message present', {
1782
+ statusCode: 400,
1783
+ data: message
1784
+ })
1935
1785
  }
1936
- const contentType = getContentType(mContent);
1937
- let mediaType = contentType?.replace('Message', '');
1938
- const media = mContent[contentType];
1786
+
1787
+ const contentType = getContentType(mContent)
1788
+ let mediaType = contentType?.replace('Message', '')
1789
+ const media = contentType === 'productMessage' ? mContent[contentType]?.product?.productImage : mContent[contentType]
1790
+
1939
1791
  if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
1940
- throw new Boom(`"${contentType}" message is not a media message`);
1792
+ throw new boom_1.Boom(`"${contentType}" message is not a media message`)
1941
1793
  }
1942
- let download;
1794
+
1795
+ let download
1796
+
1943
1797
  if ('thumbnailDirectPath' in media && !('url' in media)) {
1944
1798
  download = {
1945
1799
  directPath: media.thumbnailDirectPath,
1946
1800
  mediaKey: media.mediaKey
1947
- };
1948
- mediaType = 'thumbnail-link';
1949
- }
1950
- else {
1951
- download = media;
1801
+ }
1802
+ mediaType = 'thumbnail-link'
1803
+ } else {
1804
+ download = media
1952
1805
  }
1953
- const stream = await downloadContentFromMessage(download, mediaType, options);
1806
+
1807
+ const stream = await messages_media_1.downloadContentFromMessage(download, mediaType, options)
1808
+
1954
1809
  if (type === 'buffer') {
1955
- const bufferArray = [];
1810
+ const bufferArray = []
1811
+
1956
1812
  for await (const chunk of stream) {
1957
- bufferArray.push(chunk);
1813
+ bufferArray.push(chunk)
1958
1814
  }
1959
- return Buffer.concat(bufferArray);
1815
+
1816
+ return Buffer.concat(bufferArray)
1960
1817
  }
1961
- return stream;
1818
+
1819
+ return stream
1962
1820
  }
1963
- };
1964
- /** Checks whether the given message is a media message; if it is returns the inner content */
1965
- export const assertMediaContent = (content) => {
1966
- content = extractMessageContent(content);
1967
- const mediaContent = content?.documentMessage ||
1968
- content?.imageMessage ||
1969
- content?.videoMessage ||
1970
- content?.audioMessage ||
1971
- content?.stickerMessage;
1821
+ }
1822
+
1823
+ const assertMediaContent = (content) => {
1824
+ content = extractMessageContent(content)
1825
+
1826
+ const mediaContent = content?.documentMessage || content?.imageMessage || content?.videoMessage || content?.audioMessage || content?.stickerMessage
1972
1827
  if (!mediaContent) {
1973
- throw new Boom('given message is not a media message', { statusCode: 400, data: content });
1974
- }
1975
- return mediaContent;
1976
- };
1977
- /**
1978
- * Checks if a WebP buffer is animated by looking for VP8X chunk with animation flag
1979
- * or ANIM/ANMF chunks
1980
- */
1981
- const isAnimatedWebP = (buffer) => {
1982
- // WebP must start with RIFF....WEBP
1983
- if (buffer.length < 12 ||
1984
- buffer[0] !== 0x52 ||
1985
- buffer[1] !== 0x49 ||
1986
- buffer[2] !== 0x46 ||
1987
- buffer[3] !== 0x46 ||
1988
- buffer[8] !== 0x57 ||
1989
- buffer[9] !== 0x45 ||
1990
- buffer[10] !== 0x42 ||
1991
- buffer[11] !== 0x50) {
1992
- return false;
1828
+ throw new boom_1.Boom('given message is not a media message', {
1829
+ statusCode: 400,
1830
+ data: content
1831
+ })
1993
1832
  }
1994
- ;
1995
- // Parse chunks starting after RIFF header (12 bytes)
1996
- let offset = 12;
1997
- while (offset < buffer.length - 8) {
1998
- const chunkFourCC = buffer.toString('ascii', offset, offset + 4);
1999
- const chunkSize = buffer.readUInt32LE(offset + 4);
2000
- if (chunkFourCC === 'VP8X') {
2001
- // VP8X extended header, check animation flag (bit 1 at offset+8)
2002
- const flagsOffset = offset + 8;
2003
- if (flagsOffset < buffer.length) {
2004
- const flags = buffer[flagsOffset];
2005
- if (flags & 0x02) {
2006
- return true;
1833
+ return mediaContent
1834
+ }
1835
+
1836
+ const patchMessageForMdIfRequired = (message) => {
1837
+ if (message?.buttonsMessage || message?.templateMessage || message?.listMessage || message?.interactiveMessage?.nativeFlowMesaage) {
1838
+ message = {
1839
+ viewOnceMessageV2Extension: {
1840
+ message: {
1841
+ messageContextInfo: {
1842
+ deviceListMetadataVersion: 2,
1843
+ deviceListMetadata: {}
1844
+ },
1845
+ ...message
2007
1846
  }
2008
- ;
2009
1847
  }
2010
- ;
2011
1848
  }
2012
- else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
2013
- // ANIM or ANMF chunks indicate animation
2014
- return true;
2015
- }
2016
- ;
2017
- // Move to next chunk (chunk size + 8 bytes header, padded to even)
2018
- offset += 8 + chunkSize + (chunkSize % 2);
2019
1849
  }
2020
- ;
2021
- return false;
2022
- };
2023
- /**
2024
- * Checks if a buffer is a WebP file
2025
- */
2026
- const isWebPBuffer = (buffer) => {
2027
- return (buffer.length >= 12 &&
2028
- buffer[0] === 0x52 &&
2029
- buffer[1] === 0x49 &&
2030
- buffer[2] === 0x46 &&
2031
- buffer[3] === 0x46 &&
2032
- buffer[8] === 0x57 &&
2033
- buffer[9] === 0x45 &&
2034
- buffer[10] === 0x42 &&
2035
- buffer[11] === 0x50);
2036
- };
2037
- export const shouldIncludeBizBinaryNode = (message) => {
2038
- const hasValidInteractive = message.interactiveMessage &&
2039
- !message.interactiveMessage.carouselMessage &&
2040
- !message.interactiveMessage.collectionMessage &&
2041
- !message.interactiveMessage.shopStorefrontMessage;
2042
- return (message.buttonsMessage ||
2043
- message.interactiveMessage ||
2044
- message.listMessage ||
2045
- hasValidInteractive);
2046
- };
2047
- //# sourceMappingURL=messages.js.map
1850
+ return message
1851
+ }
1852
+
1853
+ module.exports = {
1854
+ extractUrlFromText,
1855
+ generateLinkPreviewIfRequired,
1856
+ prepareWAMessageMedia,
1857
+ prepareAlbumMessageContent,
1858
+ prepareDisappearingMessageSettingContent,
1859
+ generateForwardMessageContent,
1860
+ generateWAMessageContent,
1861
+ generateWAMessageFromContent,
1862
+ generateWAMessage,
1863
+ getContentType,
1864
+ normalizeMessageContent,
1865
+ extractMessageContent,
1866
+ getDevice,
1867
+ updateMessageWithReceipt,
1868
+ updateMessageWithReaction,
1869
+ updateMessageWithPollUpdate,
1870
+ getAggregateVotesInPollMessage,
1871
+ aggregateMessageKeysNotFromMe,
1872
+ downloadMediaMessage,
1873
+ assertMediaContent,
1874
+ patchMessageForMdIfRequired
1875
+ }