@mrlegendbot/baileys 1.2.5 → 1.2.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 (212) hide show
  1. package/WAProto/GenerateStatics.sh +3 -0
  2. package/WAProto/fix-imports.js +85 -0
  3. package/WAProto/index.d.ts +14017 -0
  4. package/WAProto/index.js +97691 -2
  5. package/lib/Defaults/index.d.ts +75 -0
  6. package/lib/Defaults/index.js +92 -89
  7. package/lib/Signal/Group/ciphertext-message.d.ts +10 -0
  8. package/lib/Signal/Group/ciphertext-message.js +10 -9
  9. package/lib/Signal/Group/group-session-builder.d.ts +15 -0
  10. package/lib/Signal/Group/group-session-builder.js +22 -21
  11. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  12. package/lib/Signal/Group/group_cipher.js +69 -75
  13. package/lib/Signal/Group/index.d.ts +12 -0
  14. package/lib/Signal/Group/index.js +1 -0
  15. package/lib/Signal/Group/keyhelper.d.ts +11 -0
  16. package/lib/Signal/Group/keyhelper.js +10 -9
  17. package/lib/Signal/Group/sender-chain-key.d.ts +14 -0
  18. package/lib/Signal/Group/sender-chain-key.js +22 -21
  19. package/lib/Signal/Group/sender-key-distribution-message.d.ts +17 -0
  20. package/lib/Signal/Group/sender-key-distribution-message.js +60 -51
  21. package/lib/Signal/Group/sender-key-message.d.ts +19 -0
  22. package/lib/Signal/Group/sender-key-message.js +62 -56
  23. package/lib/Signal/Group/sender-key-name.d.ts +18 -0
  24. package/lib/Signal/Group/sender-key-name.js +40 -38
  25. package/lib/Signal/Group/sender-key-record.d.ts +31 -0
  26. package/lib/Signal/Group/sender-key-record.js +34 -33
  27. package/lib/Signal/Group/sender-key-state.d.ts +39 -0
  28. package/lib/Signal/Group/sender-key-state.js +76 -72
  29. package/lib/Signal/Group/sender-message-key.d.ts +12 -0
  30. package/lib/Signal/Group/sender-message-key.js +23 -22
  31. package/lib/Signal/libsignal.d.ts +5 -0
  32. package/lib/Signal/libsignal.js +405 -506
  33. package/lib/Signal/lid-mapping.d.ts +23 -0
  34. package/lib/Signal/lid-mapping.js +258 -276
  35. package/lib/Socket/Client/index.d.ts +3 -0
  36. package/lib/Socket/Client/index.js +1 -0
  37. package/lib/Socket/Client/types.d.ts +16 -0
  38. package/lib/Socket/Client/types.js +7 -6
  39. package/lib/Socket/Client/websocket.d.ts +13 -0
  40. package/lib/Socket/Client/websocket.js +49 -48
  41. package/lib/Socket/business.d.ts +217 -0
  42. package/lib/Socket/business.js +369 -349
  43. package/lib/Socket/chats.d.ts +125 -0
  44. package/lib/Socket/chats.js +1148 -1172
  45. package/lib/Socket/communities.d.ts +273 -0
  46. package/lib/Socket/communities.js +413 -473
  47. package/lib/Socket/groups.d.ts +161 -0
  48. package/lib/Socket/groups.js +331 -387
  49. package/lib/Socket/index.d.ts +260 -0
  50. package/lib/Socket/index.js +8 -6
  51. package/lib/Socket/messages-recv.d.ts +203 -0
  52. package/lib/Socket/messages-recv.js +1719 -1782
  53. package/lib/Socket/messages-send.d.ts +200 -0
  54. package/lib/Socket/messages-send.js +2508 -1384
  55. package/lib/Socket/mex.d.ts +3 -0
  56. package/lib/Socket/mex.js +35 -40
  57. package/lib/Socket/newsletter.d.ts +170 -0
  58. package/lib/Socket/newsletter.js +191 -239
  59. package/lib/Socket/socket.d.ts +59 -0
  60. package/lib/Socket/socket.js +923 -992
  61. package/lib/Types/Auth.d.ts +117 -0
  62. package/lib/Types/Auth.js +1 -0
  63. package/lib/Types/Bussines.d.ts +25 -0
  64. package/lib/Types/Bussines.js +1 -0
  65. package/lib/Types/Call.d.ts +15 -0
  66. package/lib/Types/Call.js +1 -0
  67. package/lib/Types/Chat.d.ts +124 -0
  68. package/lib/Types/Chat.js +8 -1
  69. package/lib/Types/Contact.d.ts +26 -0
  70. package/lib/Types/Contact.js +1 -0
  71. package/lib/Types/Events.d.ts +256 -0
  72. package/lib/Types/Events.js +2 -1
  73. package/lib/Types/GroupMetadata.d.ts +71 -0
  74. package/lib/Types/GroupMetadata.js +1 -0
  75. package/lib/Types/Label.d.ts +47 -0
  76. package/lib/Types/Label.js +22 -20
  77. package/lib/Types/LabelAssociation.d.ts +30 -0
  78. package/lib/Types/LabelAssociation.js +4 -2
  79. package/lib/Types/Message.d.ts +320 -0
  80. package/lib/Types/Message.js +5 -3
  81. package/lib/Types/Mex.d.ts +141 -0
  82. package/lib/Types/Mex.js +31 -26
  83. package/lib/Types/Product.d.ts +79 -0
  84. package/lib/Types/Product.js +1 -0
  85. package/lib/Types/Signal.d.ts +87 -0
  86. package/lib/Types/Signal.js +2 -1
  87. package/lib/Types/Socket.d.ts +136 -0
  88. package/lib/Types/Socket.js +3 -2
  89. package/lib/Types/State.d.ts +97 -0
  90. package/lib/Types/State.js +52 -4
  91. package/lib/Types/USync.d.ts +26 -0
  92. package/lib/Types/USync.js +1 -0
  93. package/lib/Types/index.d.ts +66 -0
  94. package/lib/Types/index.js +11 -10
  95. package/lib/Utils/auth-utils.d.ts +24 -0
  96. package/lib/Utils/auth-utils.js +277 -240
  97. package/lib/Utils/browser-utils.d.ts +4 -0
  98. package/lib/Utils/browser-utils.js +23 -20
  99. package/lib/Utils/business.d.ts +23 -0
  100. package/lib/Utils/business.js +204 -200
  101. package/lib/Utils/chat-utils.d.ts +100 -0
  102. package/lib/Utils/chat-utils.js +810 -836
  103. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  104. package/lib/Utils/companion-reg-client-utils.js +25 -23
  105. package/lib/Utils/crypto.d.ts +37 -0
  106. package/lib/Utils/crypto.js +86 -70
  107. package/lib/Utils/decode-wa-message.d.ts +66 -0
  108. package/lib/Utils/decode-wa-message.js +284 -277
  109. package/lib/Utils/event-buffer.d.ts +36 -0
  110. package/lib/Utils/event-buffer.js +595 -592
  111. package/lib/Utils/generics.d.ts +92 -0
  112. package/lib/Utils/generics.js +335 -312
  113. package/lib/Utils/history.d.ts +24 -0
  114. package/lib/Utils/history.js +115 -123
  115. package/lib/Utils/identity-change-handler.d.ts +44 -0
  116. package/lib/Utils/identity-change-handler.js +50 -0
  117. package/lib/Utils/index.d.ts +22 -0
  118. package/lib/Utils/index.js +2 -1
  119. package/lib/Utils/link-preview.d.ts +21 -0
  120. package/lib/Utils/link-preview.js +73 -74
  121. package/lib/Utils/logger.d.ts +12 -0
  122. package/lib/Utils/logger.js +2 -3
  123. package/lib/Utils/lt-hash.d.ts +8 -0
  124. package/lib/Utils/lt-hash.js +6 -0
  125. package/lib/Utils/make-mutex.d.ts +9 -0
  126. package/lib/Utils/make-mutex.js +27 -27
  127. package/lib/Utils/message-retry-manager.d.ts +115 -0
  128. package/lib/Utils/message-retry-manager.js +254 -213
  129. package/lib/Utils/messages-media.d.ts +133 -0
  130. package/lib/Utils/messages-media.js +715 -741
  131. package/lib/Utils/messages.d.ts +91 -0
  132. package/lib/Utils/messages.js +827 -791
  133. package/lib/Utils/noise-handler.d.ts +20 -0
  134. package/lib/Utils/noise-handler.js +187 -215
  135. package/lib/Utils/offline-node-processor.d.ts +17 -0
  136. package/lib/Utils/offline-node-processor.js +40 -0
  137. package/lib/Utils/pre-key-manager.d.ts +28 -0
  138. package/lib/Utils/pre-key-manager.js +98 -69
  139. package/lib/Utils/process-message.d.ts +60 -0
  140. package/lib/Utils/process-message.js +586 -617
  141. package/lib/Utils/reporting-utils.d.ts +11 -0
  142. package/lib/Utils/reporting-utils.js +234 -553
  143. package/lib/Utils/signal.d.ts +47 -0
  144. package/lib/Utils/signal.js +170 -217
  145. package/lib/Utils/stanza-ack.d.ts +11 -0
  146. package/lib/Utils/stanza-ack.js +35 -27
  147. package/lib/Utils/sync-action-utils.d.ts +19 -0
  148. package/lib/Utils/sync-action-utils.js +41 -37
  149. package/lib/Utils/tc-token-utils.d.ts +37 -0
  150. package/lib/Utils/tc-token-utils.js +141 -120
  151. package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
  152. package/lib/Utils/use-multi-file-auth-state.js +105 -87
  153. package/lib/Utils/validate-connection.d.ts +11 -0
  154. package/lib/Utils/validate-connection.js +188 -190
  155. package/lib/WABinary/constants.d.ts +28 -0
  156. package/lib/WABinary/constants.js +1294 -28
  157. package/lib/WABinary/decode.d.ts +7 -0
  158. package/lib/WABinary/decode.js +249 -245
  159. package/lib/WABinary/encode.d.ts +3 -0
  160. package/lib/WABinary/encode.js +206 -201
  161. package/lib/WABinary/generic-utils.d.ts +15 -0
  162. package/lib/WABinary/generic-utils.js +89 -86
  163. package/lib/WABinary/index.d.ts +6 -0
  164. package/lib/WABinary/index.js +1 -0
  165. package/lib/WABinary/jid-utils.d.ts +48 -0
  166. package/lib/WABinary/jid-utils.js +77 -68
  167. package/lib/WABinary/types.d.ts +19 -0
  168. package/lib/WABinary/types.js +1 -0
  169. package/lib/WAM/BinaryInfo.d.ts +9 -0
  170. package/lib/WAM/BinaryInfo.js +8 -7
  171. package/lib/WAM/constants.d.ts +40 -0
  172. package/lib/WAM/constants.js +22853 -19892
  173. package/lib/WAM/encode.d.ts +3 -0
  174. package/lib/WAM/encode.js +132 -121
  175. package/lib/WAM/index.d.ts +4 -0
  176. package/lib/WAM/index.js +1 -0
  177. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +10 -0
  178. package/lib/WAUSync/Protocols/USyncContactProtocol.js +43 -46
  179. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
  180. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +48 -47
  181. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
  182. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +23 -22
  183. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
  184. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +33 -30
  185. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  186. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +20 -19
  187. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
  188. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +44 -53
  189. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
  190. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +24 -24
  191. package/lib/WAUSync/Protocols/index.d.ts +6 -0
  192. package/lib/WAUSync/Protocols/index.js +1 -0
  193. package/lib/WAUSync/USyncQuery.d.ts +30 -0
  194. package/lib/WAUSync/USyncQuery.js +88 -79
  195. package/lib/WAUSync/USyncUser.d.ts +17 -0
  196. package/lib/WAUSync/USyncUser.js +29 -28
  197. package/lib/WAUSync/index.d.ts +4 -0
  198. package/lib/WAUSync/index.js +1 -0
  199. package/lib/index.d.ts +12 -0
  200. package/lib/index.js +2 -2
  201. package/package.json +86 -22
  202. package/README.upstream.md +0 -1312
  203. package/WAProto/WAProto.json +0 -1
  204. package/WAProto/WAProto.proto +0 -6275
  205. package/WAProto/compiler.js +0 -550
  206. package/lib/Store/index.js +0 -8
  207. package/lib/Store/keyed-db.js +0 -108
  208. package/lib/Store/make-cache-manager-store.js +0 -70
  209. package/lib/Store/make-in-memory-store.js +0 -417
  210. package/lib/Store/make-ordered-dictionary.js +0 -69
  211. package/lib/Store/object-repository.js +0 -27
  212. package/lib/Utils/use-sqlite-auth-state.js +0 -162
@@ -1,8 +1,8 @@
1
1
  import { Boom } from '@hapi/boom';
2
2
  import { randomBytes } from 'crypto';
3
3
  import { promises as fs } from 'fs';
4
- import 'stream';
5
- import { proto } from '../../WAProto/compiler.js';
4
+ import {} from 'stream';
5
+ import { proto } from '../../WAProto/index.js';
6
6
  import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
7
7
  import { WAMessageStatus, WAProto } from '../Types/index.js';
8
8
  import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
@@ -11,847 +11,883 @@ import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './gener
11
11
  import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData } from './messages-media.js';
12
12
  import { shouldIncludeReportingToken } from './reporting-utils.js';
13
13
  const MIMETYPE_MAP = {
14
- image: 'image/jpeg',
15
- video: 'video/mp4',
16
- document: 'application/pdf',
17
- audio: 'audio/ogg; codecs=opus',
18
- sticker: 'image/webp',
19
- 'product-catalog-image': 'image/jpeg'
14
+ image: 'image/jpeg',
15
+ video: 'video/mp4',
16
+ document: 'application/pdf',
17
+ audio: 'audio/ogg; codecs=opus',
18
+ sticker: 'image/webp',
19
+ 'product-catalog-image': 'image/jpeg'
20
20
  };
21
21
  const MessageTypeProto = {
22
- image: WAProto.Message.ImageMessage,
23
- video: WAProto.Message.VideoMessage,
24
- audio: WAProto.Message.AudioMessage,
25
- sticker: WAProto.Message.StickerMessage,
26
- document: WAProto.Message.DocumentMessage
22
+ image: WAProto.Message.ImageMessage,
23
+ video: WAProto.Message.VideoMessage,
24
+ audio: WAProto.Message.AudioMessage,
25
+ sticker: WAProto.Message.StickerMessage,
26
+ document: WAProto.Message.DocumentMessage
27
27
  };
28
- export const extractUrlFromText = text => text.match(URL_REGEX)?.[0];
28
+ /**
29
+ * Uses a regex to test whether the string contains a URL, and returns the URL if it does.
30
+ * @param text eg. hello https://google.com
31
+ * @returns the URL, eg. https://google.com
32
+ */
33
+ export const extractUrlFromText = (text) => text.match(URL_REGEX)?.[0];
29
34
  export const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
30
- const url = extractUrlFromText(text);
31
- if (!!getUrlInfo && url) {
32
- try {
33
- const urlInfo = await getUrlInfo(url);
34
- return urlInfo;
35
- } catch (error) {
36
- logger?.warn({
37
- trace: error.stack
38
- }, 'url generation failed');
39
- }
40
- }
35
+ const url = extractUrlFromText(text);
36
+ if (!!getUrlInfo && url) {
37
+ try {
38
+ const urlInfo = await getUrlInfo(url);
39
+ return urlInfo;
40
+ }
41
+ catch (error) {
42
+ // ignore if fails
43
+ logger?.warn({ trace: error.stack }, 'url generation failed');
44
+ }
45
+ }
41
46
  };
42
- const assertColor = async color => {
43
- let assertedColor;
44
- if (typeof color === 'number') {
45
- assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1;
46
- } else {
47
- let hex = color.trim().replace('#', '');
48
- if (hex.length <= 6) {
49
- hex = 'FF' + hex.padStart(6, '0');
50
- }
51
- assertedColor = parseInt(hex, 16);
52
- return assertedColor;
53
- }
47
+ const assertColor = async (color) => {
48
+ let assertedColor;
49
+ if (typeof color === 'number') {
50
+ assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1;
51
+ }
52
+ else {
53
+ let hex = color.trim().replace('#', '');
54
+ if (hex.length <= 6) {
55
+ hex = 'FF' + hex.padStart(6, '0');
56
+ }
57
+ assertedColor = parseInt(hex, 16);
58
+ return assertedColor;
59
+ }
54
60
  };
55
61
  export const prepareWAMessageMedia = async (message, options) => {
56
- const logger = options.logger;
57
- let mediaType;
58
- for (const key of MEDIA_KEYS) {
59
- if (key in message) {
60
- mediaType = key;
61
- }
62
- }
63
- if (!mediaType) {
64
- throw new Boom('Invalid media type', {
65
- statusCode: 400
62
+ const logger = options.logger;
63
+ let mediaType;
64
+ for (const key of MEDIA_KEYS) {
65
+ if (key in message) {
66
+ mediaType = key;
67
+ }
68
+ }
69
+ if (!mediaType) {
70
+ throw new Boom('Invalid media type', { statusCode: 400 });
71
+ }
72
+ const uploadData = {
73
+ ...message,
74
+ media: message[mediaType]
75
+ };
76
+ delete uploadData[mediaType];
77
+ // check if cacheable + generate cache key
78
+ const cacheableKey = typeof uploadData.media === 'object' &&
79
+ 'url' in uploadData.media &&
80
+ !!uploadData.media.url &&
81
+ !!options.mediaCache &&
82
+ mediaType + ':' + uploadData.media.url.toString();
83
+ if (mediaType === 'document' && !uploadData.fileName) {
84
+ uploadData.fileName = 'file';
85
+ }
86
+ if (!uploadData.mimetype) {
87
+ uploadData.mimetype = MIMETYPE_MAP[mediaType];
88
+ }
89
+ if (cacheableKey) {
90
+ const mediaBuff = await options.mediaCache.get(cacheableKey);
91
+ if (mediaBuff) {
92
+ logger?.debug({ cacheableKey }, 'got media cache hit');
93
+ const obj = proto.Message.decode(mediaBuff);
94
+ const key = `${mediaType}Message`;
95
+ Object.assign(obj[key], { ...uploadData, media: undefined });
96
+ return obj;
97
+ }
98
+ }
99
+ const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
100
+ if (isNewsletter) {
101
+ logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
102
+ const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
103
+ const fileSha256B64 = fileSha256.toString('base64');
104
+ const { mediaUrl, directPath } = await options.upload(filePath, {
105
+ fileEncSha256B64: fileSha256B64,
106
+ mediaType: mediaType,
107
+ timeoutMs: options.mediaUploadTimeoutMs
108
+ });
109
+ await fs.unlink(filePath);
110
+ const obj = WAProto.Message.fromObject({
111
+ // todo: add more support here
112
+ [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
113
+ url: mediaUrl,
114
+ directPath,
115
+ fileSha256,
116
+ fileLength,
117
+ ...uploadData,
118
+ media: undefined
119
+ })
120
+ });
121
+ if (uploadData.ptv) {
122
+ obj.ptvMessage = obj.videoMessage;
123
+ delete obj.videoMessage;
124
+ }
125
+ if (obj.stickerMessage) {
126
+ obj.stickerMessage.stickerSentTs = Date.now();
127
+ }
128
+ if (cacheableKey) {
129
+ logger?.debug({ cacheableKey }, 'set cache');
130
+ await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
131
+ }
132
+ return obj;
133
+ }
134
+ const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
135
+ const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
136
+ const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
137
+ const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
138
+ const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
139
+ const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
140
+ logger,
141
+ saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
142
+ opts: options.options
66
143
  });
67
- }
68
- const uploadData = {
69
- ...message,
70
- media: message[mediaType]
71
- };
72
- delete uploadData[mediaType];
73
- const cacheableKey = typeof uploadData.media === 'object' && 'url' in uploadData.media && !!uploadData.media.url && !!options.mediaCache && mediaType + ':' + uploadData.media.url.toString();
74
- if (mediaType === 'document' && !uploadData.fileName) {
75
- uploadData.fileName = 'file';
76
- }
77
- if (!uploadData.mimetype) {
78
- uploadData.mimetype = MIMETYPE_MAP[mediaType];
79
- }
80
- if (cacheableKey) {
81
- const mediaBuff = await options.mediaCache.get(cacheableKey);
82
- if (mediaBuff) {
83
- logger?.debug({
84
- cacheableKey
85
- }, 'got media cache hit');
86
- const obj = proto.Message.decode(mediaBuff);
87
- const key = `${mediaType}Message`;
88
- Object.assign(obj[key], {
89
- ...uploadData,
90
- media: undefined
91
- });
92
- return obj;
93
- }
94
- }
95
- const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
96
- if (isNewsletter) {
97
- logger?.info({
98
- key: cacheableKey
99
- }, 'Preparing raw media for newsletter');
100
- const {
101
- filePath,
102
- fileSha256,
103
- fileLength
104
- } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
105
- const fileSha256B64 = fileSha256.toString('base64');
106
- const {
107
- mediaUrl,
108
- directPath
109
- } = await options.upload(filePath, {
110
- fileEncSha256B64: fileSha256B64,
111
- mediaType: mediaType,
112
- timeoutMs: options.mediaUploadTimeoutMs
144
+ const fileEncSha256B64 = fileEncSha256.toString('base64');
145
+ const [{ mediaUrl, directPath }] = await Promise.all([
146
+ (async () => {
147
+ const result = await options.upload(encFilePath, {
148
+ fileEncSha256B64,
149
+ mediaType,
150
+ timeoutMs: options.mediaUploadTimeoutMs
151
+ });
152
+ logger?.debug({ mediaType, cacheableKey }, 'uploaded media');
153
+ return result;
154
+ })(),
155
+ (async () => {
156
+ try {
157
+ if (requiresThumbnailComputation) {
158
+ const { thumbnail, originalImageDimensions } = await generateThumbnail(originalFilePath, mediaType, options);
159
+ uploadData.jpegThumbnail = thumbnail;
160
+ if (!uploadData.width && originalImageDimensions) {
161
+ uploadData.width = originalImageDimensions.width;
162
+ uploadData.height = originalImageDimensions.height;
163
+ logger?.debug('set dimensions');
164
+ }
165
+ logger?.debug('generated thumbnail');
166
+ }
167
+ if (requiresDurationComputation) {
168
+ uploadData.seconds = await getAudioDuration(originalFilePath);
169
+ logger?.debug('computed audio duration');
170
+ }
171
+ if (requiresWaveformProcessing) {
172
+ uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
173
+ logger?.debug('processed waveform');
174
+ }
175
+ if (requiresAudioBackground) {
176
+ uploadData.backgroundArgb = await assertColor(options.backgroundColor);
177
+ logger?.debug('computed backgroundColor audio status');
178
+ }
179
+ }
180
+ catch (error) {
181
+ logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
182
+ }
183
+ })()
184
+ ]).finally(async () => {
185
+ try {
186
+ await fs.unlink(encFilePath);
187
+ if (originalFilePath) {
188
+ await fs.unlink(originalFilePath);
189
+ }
190
+ logger?.debug('removed tmp files');
191
+ }
192
+ catch (error) {
193
+ logger?.warn('failed to remove tmp file');
194
+ }
113
195
  });
114
- await fs.unlink(filePath);
115
196
  const obj = WAProto.Message.fromObject({
116
- [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
117
- url: mediaUrl,
118
- directPath,
119
- fileSha256,
120
- fileLength,
121
- ...uploadData,
122
- media: undefined
123
- })
197
+ [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
198
+ url: mediaUrl,
199
+ directPath,
200
+ mediaKey,
201
+ fileEncSha256,
202
+ fileSha256,
203
+ fileLength,
204
+ mediaKeyTimestamp: unixTimestampSeconds(),
205
+ ...uploadData,
206
+ media: undefined
207
+ })
124
208
  });
125
209
  if (uploadData.ptv) {
126
- obj.ptvMessage = obj.videoMessage;
127
- delete obj.videoMessage;
128
- }
129
- if (obj.stickerMessage) {
130
- obj.stickerMessage.stickerSentTs = Date.now();
210
+ obj.ptvMessage = obj.videoMessage;
211
+ delete obj.videoMessage;
131
212
  }
132
213
  if (cacheableKey) {
133
- logger?.debug({
134
- cacheableKey
135
- }, 'set cache');
136
- await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
214
+ logger?.debug({ cacheableKey }, 'set cache');
215
+ await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
137
216
  }
138
217
  return obj;
139
- }
140
- const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
141
- const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
142
- const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
143
- const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
144
- const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
145
- const {
146
- mediaKey,
147
- encFilePath,
148
- originalFilePath,
149
- fileEncSha256,
150
- fileSha256,
151
- fileLength
152
- } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
153
- logger,
154
- saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
155
- opts: options.options
156
- });
157
- const fileEncSha256B64 = fileEncSha256.toString('base64');
158
- const [{
159
- mediaUrl,
160
- directPath
161
- }] = await Promise.all([(async () => {
162
- const result = await options.upload(encFilePath, {
163
- fileEncSha256B64,
164
- mediaType,
165
- timeoutMs: options.mediaUploadTimeoutMs
166
- });
167
- logger?.debug({
168
- mediaType,
169
- cacheableKey
170
- }, 'uploaded media');
171
- return result;
172
- })(), (async () => {
173
- try {
174
- if (requiresThumbnailComputation) {
175
- const {
176
- thumbnail,
177
- originalImageDimensions
178
- } = await generateThumbnail(originalFilePath, mediaType, options);
179
- uploadData.jpegThumbnail = thumbnail;
180
- if (!uploadData.width && originalImageDimensions) {
181
- uploadData.width = originalImageDimensions.width;
182
- uploadData.height = originalImageDimensions.height;
183
- logger?.debug('set dimensions');
184
- }
185
- logger?.debug('generated thumbnail');
186
- }
187
- if (requiresDurationComputation) {
188
- uploadData.seconds = await getAudioDuration(originalFilePath);
189
- logger?.debug('computed audio duration');
190
- }
191
- if (requiresWaveformProcessing) {
192
- uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
193
- logger?.debug('processed waveform');
194
- }
195
- if (requiresAudioBackground) {
196
- uploadData.backgroundArgb = await assertColor(options.backgroundColor);
197
- logger?.debug('computed backgroundColor audio status');
198
- }
199
- } catch (error) {
200
- logger?.warn({
201
- trace: error.stack
202
- }, 'failed to obtain extra info');
203
- }
204
- })()]).finally(async () => {
205
- try {
206
- await fs.unlink(encFilePath);
207
- if (originalFilePath) {
208
- await fs.unlink(originalFilePath);
209
- }
210
- logger?.debug('removed tmp files');
211
- } catch (error) {
212
- logger?.warn('failed to remove tmp file');
213
- }
214
- });
215
- const obj = WAProto.Message.fromObject({
216
- [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
217
- url: mediaUrl,
218
- directPath,
219
- mediaKey,
220
- fileEncSha256,
221
- fileSha256,
222
- fileLength,
223
- mediaKeyTimestamp: unixTimestampSeconds(),
224
- ...uploadData,
225
- media: undefined
226
- })
227
- });
228
- if (uploadData.ptv) {
229
- obj.ptvMessage = obj.videoMessage;
230
- delete obj.videoMessage;
231
- }
232
- if (cacheableKey) {
233
- logger?.debug({
234
- cacheableKey
235
- }, 'set cache');
236
- await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
237
- }
238
- return obj;
239
218
  };
240
- export const prepareDisappearingMessageSettingContent = ephemeralExpiration => {
241
- ephemeralExpiration = ephemeralExpiration || 0;
242
- const content = {
243
- ephemeralMessage: {
244
- message: {
245
- protocolMessage: {
246
- type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
247
- ephemeralExpiration
248
- }
249
- }
250
- }
251
- };
252
- return WAProto.Message.fromObject(content);
219
+ export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) => {
220
+ ephemeralExpiration = ephemeralExpiration || 0;
221
+ const content = {
222
+ ephemeralMessage: {
223
+ message: {
224
+ protocolMessage: {
225
+ type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
226
+ ephemeralExpiration
227
+ }
228
+ }
229
+ }
230
+ };
231
+ return WAProto.Message.fromObject(content);
253
232
  };
233
+ /**
234
+ * Generate forwarded message content like WA does
235
+ * @param message the message to forward
236
+ * @param options.forceForward will show the message as forwarded even if it is from you
237
+ */
254
238
  export const generateForwardMessageContent = (message, forceForward) => {
255
- let content = message.message;
256
- if (!content) {
257
- throw new Boom('no content in message', {
258
- statusCode: 400
259
- });
260
- }
261
- content = normalizeMessageContent(content);
262
- content = proto.Message.decode(proto.Message.encode(content).finish());
263
- let key = Object.keys(content)[0];
264
- let score = content?.[key]?.contextInfo?.forwardingScore || 0;
265
- score += message.key.fromMe && !forceForward ? 0 : 1;
266
- if (key === 'conversation') {
267
- content.extendedTextMessage = {
268
- text: content[key]
269
- };
270
- delete content.conversation;
271
- key = 'extendedTextMessage';
272
- }
273
- const key_ = content?.[key];
274
- if (score > 0) {
275
- key_.contextInfo = {
276
- forwardingScore: score,
277
- isForwarded: true
278
- };
279
- } else {
280
- key_.contextInfo = {};
281
- }
282
- return content;
239
+ let content = message.message;
240
+ if (!content) {
241
+ throw new Boom('no content in message', { statusCode: 400 });
242
+ }
243
+ // hacky copy
244
+ content = normalizeMessageContent(content);
245
+ content = proto.Message.decode(proto.Message.encode(content).finish());
246
+ let key = Object.keys(content)[0];
247
+ let score = content?.[key]?.contextInfo?.forwardingScore || 0;
248
+ score += message.key.fromMe && !forceForward ? 0 : 1;
249
+ if (key === 'conversation') {
250
+ content.extendedTextMessage = { text: content[key] };
251
+ delete content.conversation;
252
+ key = 'extendedTextMessage';
253
+ }
254
+ const key_ = content?.[key];
255
+ if (score > 0) {
256
+ key_.contextInfo = { forwardingScore: score, isForwarded: true };
257
+ }
258
+ else {
259
+ key_.contextInfo = {};
260
+ }
261
+ return content;
283
262
  };
284
263
  export const hasNonNullishProperty = (message, key) => {
285
- return typeof message === 'object' && message !== null && key in message && message[key] !== null && message[key] !== undefined;
264
+ return (typeof message === 'object' &&
265
+ message !== null &&
266
+ key in message &&
267
+ message[key] !== null &&
268
+ message[key] !== undefined);
286
269
  };
287
270
  function hasOptionalProperty(obj, key) {
288
- return typeof obj === 'object' && obj !== null && key in obj && obj[key] !== null;
271
+ return typeof obj === 'object' && obj !== null && key in obj && obj[key] !== null;
289
272
  }
290
273
  export const generateWAMessageContent = async (message, options) => {
291
- var _a, _b;
292
- let m = {};
293
- if (hasNonNullishProperty(message, 'text')) {
294
- const extContent = {
295
- text: message.text
296
- };
297
- let urlInfo = message.linkPreview;
298
- if (typeof urlInfo === 'undefined') {
299
- urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger);
300
- }
301
- if (urlInfo) {
302
- extContent.matchedText = urlInfo['matched-text'];
303
- extContent.jpegThumbnail = urlInfo.jpegThumbnail;
304
- extContent.description = urlInfo.description;
305
- extContent.title = urlInfo.title;
306
- extContent.previewType = 0;
307
- const img = urlInfo.highQualityThumbnail;
308
- if (img) {
309
- extContent.thumbnailDirectPath = img.directPath;
310
- extContent.mediaKey = img.mediaKey;
311
- extContent.mediaKeyTimestamp = img.mediaKeyTimestamp;
312
- extContent.thumbnailWidth = img.width;
313
- extContent.thumbnailHeight = img.height;
314
- extContent.thumbnailSha256 = img.fileSha256;
315
- extContent.thumbnailEncSha256 = img.fileEncSha256;
316
- }
317
- }
318
- if (options.backgroundColor) {
319
- extContent.backgroundArgb = await assertColor(options.backgroundColor);
320
- }
321
- if (options.font) {
322
- extContent.font = options.font;
323
- }
324
- m.extendedTextMessage = extContent;
325
- } else if (hasNonNullishProperty(message, 'contacts')) {
326
- const contactLen = message.contacts.contacts.length;
327
- if (!contactLen) {
328
- throw new Boom('require atleast 1 contact', {
329
- statusCode: 400
330
- });
331
- }
332
- if (contactLen === 1) {
333
- m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
334
- } else {
335
- m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
336
- }
337
- } else if (hasNonNullishProperty(message, 'location')) {
338
- m.locationMessage = WAProto.Message.LocationMessage.create(message.location);
339
- } else if (hasNonNullishProperty(message, 'react')) {
340
- if (!message.react.senderTimestampMs) {
341
- message.react.senderTimestampMs = Date.now();
342
- }
343
- m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
344
- } else if (hasNonNullishProperty(message, 'delete')) {
345
- m.protocolMessage = {
346
- key: message.delete,
347
- type: WAProto.Message.ProtocolMessage.Type.REVOKE
348
- };
349
- } else if (hasNonNullishProperty(message, 'forward')) {
350
- m = generateForwardMessageContent(message.forward, message.force);
351
- } else if (hasNonNullishProperty(message, 'disappearingMessagesInChat')) {
352
- const exp = typeof message.disappearingMessagesInChat === 'boolean' ? message.disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0 : message.disappearingMessagesInChat;
353
- m = prepareDisappearingMessageSettingContent(exp);
354
- } else if (hasNonNullishProperty(message, 'botInvoke')) {
355
- m.botInvokeMessage = {
356
- message: message.botInvoke
357
- };
358
- } else if (hasNonNullishProperty(message, 'groupInvite')) {
359
- m.groupInviteMessage = {};
360
- m.groupInviteMessage.inviteCode = message.groupInvite.inviteCode;
361
- m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration;
362
- m.groupInviteMessage.caption = message.groupInvite.text;
363
- m.groupInviteMessage.groupJid = message.groupInvite.jid;
364
- m.groupInviteMessage.groupName = message.groupInvite.subject;
365
- if (options.getProfilePicUrl) {
366
- const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview');
367
- if (pfpUrl) {
368
- const resp = await fetch(pfpUrl, {
369
- method: 'GET',
370
- dispatcher: options?.options?.dispatcher
274
+ var _a, _b;
275
+ let m = {};
276
+ if (hasNonNullishProperty(message, 'text')) {
277
+ const extContent = { text: message.text };
278
+ let urlInfo = message.linkPreview;
279
+ if (typeof urlInfo === 'undefined') {
280
+ urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger);
281
+ }
282
+ if (urlInfo) {
283
+ extContent.matchedText = urlInfo['matched-text'];
284
+ extContent.jpegThumbnail = urlInfo.jpegThumbnail;
285
+ extContent.description = urlInfo.description;
286
+ extContent.title = urlInfo.title;
287
+ extContent.previewType = 0;
288
+ const img = urlInfo.highQualityThumbnail;
289
+ if (img) {
290
+ extContent.thumbnailDirectPath = img.directPath;
291
+ extContent.mediaKey = img.mediaKey;
292
+ extContent.mediaKeyTimestamp = img.mediaKeyTimestamp;
293
+ extContent.thumbnailWidth = img.width;
294
+ extContent.thumbnailHeight = img.height;
295
+ extContent.thumbnailSha256 = img.fileSha256;
296
+ extContent.thumbnailEncSha256 = img.fileEncSha256;
297
+ }
298
+ }
299
+ if (options.backgroundColor) {
300
+ extContent.backgroundArgb = await assertColor(options.backgroundColor);
301
+ }
302
+ if (options.font) {
303
+ extContent.font = options.font;
304
+ }
305
+ m.extendedTextMessage = extContent;
306
+ }
307
+ else if (hasNonNullishProperty(message, 'contacts')) {
308
+ const contactLen = message.contacts.contacts.length;
309
+ if (!contactLen) {
310
+ throw new Boom('require atleast 1 contact', { statusCode: 400 });
311
+ }
312
+ if (contactLen === 1) {
313
+ m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
314
+ }
315
+ else {
316
+ m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
317
+ }
318
+ }
319
+ else if (hasNonNullishProperty(message, 'location')) {
320
+ m.locationMessage = WAProto.Message.LocationMessage.create(message.location);
321
+ }
322
+ else if (hasNonNullishProperty(message, 'react')) {
323
+ if (!message.react.senderTimestampMs) {
324
+ message.react.senderTimestampMs = Date.now();
325
+ }
326
+ m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
327
+ }
328
+ else if (hasNonNullishProperty(message, 'delete')) {
329
+ m.protocolMessage = {
330
+ key: message.delete,
331
+ type: WAProto.Message.ProtocolMessage.Type.REVOKE
332
+ };
333
+ }
334
+ else if (hasNonNullishProperty(message, 'forward')) {
335
+ m = generateForwardMessageContent(message.forward, message.force);
336
+ }
337
+ else if (hasNonNullishProperty(message, 'disappearingMessagesInChat')) {
338
+ const exp = typeof message.disappearingMessagesInChat === 'boolean'
339
+ ? message.disappearingMessagesInChat
340
+ ? WA_DEFAULT_EPHEMERAL
341
+ : 0
342
+ : message.disappearingMessagesInChat;
343
+ m = prepareDisappearingMessageSettingContent(exp);
344
+ }
345
+ else if (hasNonNullishProperty(message, 'groupInvite')) {
346
+ m.groupInviteMessage = {};
347
+ m.groupInviteMessage.inviteCode = message.groupInvite.inviteCode;
348
+ m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration;
349
+ m.groupInviteMessage.caption = message.groupInvite.text;
350
+ m.groupInviteMessage.groupJid = message.groupInvite.jid;
351
+ m.groupInviteMessage.groupName = message.groupInvite.subject;
352
+ //TODO: use built-in interface and get disappearing mode info etc.
353
+ //TODO: cache / use store!?
354
+ if (options.getProfilePicUrl) {
355
+ const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview');
356
+ if (pfpUrl) {
357
+ const resp = await fetch(pfpUrl, { method: 'GET', dispatcher: options?.options?.dispatcher });
358
+ if (resp.ok) {
359
+ const buf = Buffer.from(await resp.arrayBuffer());
360
+ m.groupInviteMessage.jpegThumbnail = buf;
361
+ }
362
+ }
363
+ }
364
+ }
365
+ else if (hasNonNullishProperty(message, 'pin')) {
366
+ m.pinInChatMessage = {};
367
+ m.messageContextInfo = {};
368
+ m.pinInChatMessage.key = message.pin;
369
+ m.pinInChatMessage.type = message.type;
370
+ m.pinInChatMessage.senderTimestampMs = Date.now();
371
+ m.messageContextInfo.messageAddOnDurationInSecs = message.type === 1 ? message.time || 86400 : 0;
372
+ }
373
+ else if (hasNonNullishProperty(message, 'buttonReply')) {
374
+ switch (message.type) {
375
+ case 'template':
376
+ m.templateButtonReplyMessage = {
377
+ selectedDisplayText: message.buttonReply.displayText,
378
+ selectedId: message.buttonReply.id,
379
+ selectedIndex: message.buttonReply.index
380
+ };
381
+ break;
382
+ case 'plain':
383
+ m.buttonsResponseMessage = {
384
+ selectedButtonId: message.buttonReply.id,
385
+ selectedDisplayText: message.buttonReply.displayText,
386
+ type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
387
+ };
388
+ break;
389
+ }
390
+ }
391
+ else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
392
+ const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options);
393
+ m.ptvMessage = videoMessage;
394
+ }
395
+ else if (hasNonNullishProperty(message, 'product')) {
396
+ const { imageMessage } = await prepareWAMessageMedia({ image: message.product.productImage }, options);
397
+ m.productMessage = WAProto.Message.ProductMessage.create({
398
+ ...message,
399
+ product: {
400
+ ...message.product,
401
+ productImage: imageMessage
402
+ }
371
403
  });
372
- if (resp.ok) {
373
- const buf = Buffer.from(await resp.arrayBuffer());
374
- m.groupInviteMessage.jpegThumbnail = buf;
375
- }
376
- }
377
- }
378
- } else if (hasNonNullishProperty(message, 'pin')) {
379
- m.pinInChatMessage = {};
380
- m.messageContextInfo = {};
381
- m.pinInChatMessage.key = message.pin;
382
- m.pinInChatMessage.type = message.type;
383
- m.pinInChatMessage.senderTimestampMs = Date.now();
384
- m.messageContextInfo.messageAddOnDurationInSecs = message.type === 1 ? message.time || 86400 : 0;
385
- } else if (hasNonNullishProperty(message, 'buttonReply')) {
386
- switch (message.type) {
387
- case 'template':
388
- m.templateButtonReplyMessage = {
389
- selectedDisplayText: message.buttonReply.displayText,
390
- selectedId: message.buttonReply.id,
391
- selectedIndex: message.buttonReply.index
404
+ }
405
+ else if (hasNonNullishProperty(message, 'listReply')) {
406
+ m.listResponseMessage = { ...message.listReply };
407
+ }
408
+ else if (hasNonNullishProperty(message, 'event')) {
409
+ m.eventMessage = {};
410
+ const startTime = Math.floor(message.event.startDate.getTime() / 1000);
411
+ if (message.event.call && options.getCallLink) {
412
+ const token = await options.getCallLink(message.event.call, { startTime });
413
+ m.eventMessage.joinLink = (message.event.call === 'audio' ? CALL_AUDIO_PREFIX : CALL_VIDEO_PREFIX) + token;
414
+ }
415
+ m.messageContextInfo = {
416
+ // encKey
417
+ messageSecret: message.event.messageSecret || randomBytes(32)
392
418
  };
393
- break;
394
- case 'plain':
395
- m.buttonsResponseMessage = {
396
- selectedButtonId: message.buttonReply.id,
397
- selectedDisplayText: message.buttonReply.displayText,
398
- type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
419
+ m.eventMessage.name = message.event.name;
420
+ m.eventMessage.description = message.event.description;
421
+ m.eventMessage.startTime = startTime;
422
+ m.eventMessage.endTime = message.event.endDate ? message.event.endDate.getTime() / 1000 : undefined;
423
+ m.eventMessage.isCanceled = message.event.isCancelled ?? false;
424
+ m.eventMessage.extraGuestsAllowed = message.event.extraGuestsAllowed;
425
+ m.eventMessage.isScheduleCall = message.event.isScheduleCall ?? false;
426
+ m.eventMessage.location = message.event.location;
427
+ }
428
+ else if (hasNonNullishProperty(message, 'poll')) {
429
+ (_a = message.poll).selectableCount || (_a.selectableCount = 0);
430
+ (_b = message.poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
431
+ if (!Array.isArray(message.poll.values)) {
432
+ throw new Boom('Invalid poll values', { statusCode: 400 });
433
+ }
434
+ if (message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) {
435
+ throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
436
+ statusCode: 400
437
+ });
438
+ }
439
+ m.messageContextInfo = {
440
+ // encKey
441
+ messageSecret: message.poll.messageSecret || randomBytes(32)
399
442
  };
400
- break;
401
- }
402
- } else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
403
- const {
404
- videoMessage
405
- } = await prepareWAMessageMedia({
406
- video: message.video
407
- }, options);
408
- m.ptvMessage = videoMessage;
409
- } else if (hasNonNullishProperty(message, 'product')) {
410
- const {
411
- imageMessage
412
- } = await prepareWAMessageMedia({
413
- image: message.product.productImage
414
- }, options);
415
- m.productMessage = WAProto.Message.ProductMessage.create({
416
- ...message,
417
- product: {
418
- ...message.product,
419
- productImage: imageMessage
420
- }
421
- });
422
- } else if (hasNonNullishProperty(message, 'listReply')) {
423
- m.listResponseMessage = {
424
- ...message.listReply
425
- };
426
- } else if (hasNonNullishProperty(message, 'event')) {
427
- m.eventMessage = {};
428
- const startTime = Math.floor(message.event.startDate.getTime() / 1000);
429
- if (message.event.call && options.getCallLink) {
430
- const token = await options.getCallLink(message.event.call, {
431
- startTime
432
- });
433
- m.eventMessage.joinLink = (message.event.call === 'audio' ? CALL_AUDIO_PREFIX : CALL_VIDEO_PREFIX) + token;
434
- }
435
- m.messageContextInfo = {
436
- messageSecret: message.event.messageSecret || randomBytes(32)
437
- };
438
- m.eventMessage.name = message.event.name;
439
- m.eventMessage.description = message.event.description;
440
- m.eventMessage.startTime = startTime;
441
- m.eventMessage.endTime = message.event.endDate ? message.event.endDate.getTime() / 1000 : undefined;
442
- m.eventMessage.isCanceled = message.event.isCancelled ?? false;
443
- m.eventMessage.extraGuestsAllowed = message.event.extraGuestsAllowed;
444
- m.eventMessage.isScheduleCall = message.event.isScheduleCall ?? false;
445
- m.eventMessage.location = message.event.location;
446
- } else if (hasNonNullishProperty(message, 'poll')) {
447
- (_a = message.poll).selectableCount || (_a.selectableCount = 0);
448
- (_b = message.poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
449
- if (!Array.isArray(message.poll.values)) {
450
- throw new Boom('Invalid poll values', {
451
- statusCode: 400
452
- });
453
- }
454
- if (message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) {
455
- throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
456
- statusCode: 400
457
- });
458
- }
459
- m.messageContextInfo = {
460
- messageSecret: message.poll.messageSecret || randomBytes(32)
461
- };
462
- const pollCreationMessage = {
463
- name: message.poll.name,
464
- selectableOptionsCount: message.poll.selectableCount,
465
- options: message.poll.values.map(optionName => ({
466
- optionName
467
- }))
468
- };
469
- if (message.poll.toAnnouncementGroup) {
470
- m.pollCreationMessageV2 = pollCreationMessage;
471
- } else {
472
- if (message.poll.selectableCount === 1) {
473
- m.pollCreationMessageV3 = pollCreationMessage;
474
- } else {
475
- m.pollCreationMessage = pollCreationMessage;
476
- }
477
- }
478
- } else if (hasNonNullishProperty(message, 'album')) {
479
- m.albumMessage = {
480
- expectedImageCount: message.album.expectedImageCount,
481
- expectedVideoCount: message.album.expectedVideoCount
482
- };
483
- } else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
484
- m.protocolMessage = {
485
- type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
486
- };
487
- } else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
488
- m.requestPhoneNumberMessage = {};
489
- } else if (hasNonNullishProperty(message, 'limitSharing')) {
490
- m.protocolMessage = {
491
- type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
492
- limitSharing: {
493
- sharingLimited: message.limitSharing === true,
494
- trigger: 1,
495
- limitSharingSettingTimestamp: Date.now(),
496
- initiatedByMe: true
497
- }
498
- };
499
- } else {
500
- m = await prepareWAMessageMedia(message, options);
501
- }
502
- if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
503
- m = {
504
- viewOnceMessage: {
505
- message: m
506
- }
507
- };
508
- }
509
- if (hasOptionalProperty(message, 'mentions') && message.mentions?.length || hasOptionalProperty(message, 'mentionAll') && message.mentionAll) {
510
- const messageType = Object.keys(m)[0];
511
- const key = m[messageType];
512
- if (key && 'contextInfo' in key) {
513
- key.contextInfo = key.contextInfo || {};
514
- if (message.mentions?.length) {
515
- key.contextInfo.mentionedJid = message.mentions;
516
- }
517
- if (message.mentionAll) {
518
- key.contextInfo.nonJidMentions = 1;
519
- }
520
- } else if (key) {
521
- key.contextInfo = {
522
- mentionedJid: message.mentions,
523
- nonJidMentions: message.mentionAll ? 1 : 0
524
- };
525
- }
526
- }
527
- if (hasOptionalProperty(message, 'edit')) {
528
- m = {
529
- protocolMessage: {
530
- key: message.edit,
531
- editedMessage: m,
532
- timestampMs: Date.now(),
533
- type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
534
- }
535
- };
536
- }
537
- if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
538
- const messageType = Object.keys(m)[0];
539
- const key = m[messageType];
540
- if ('contextInfo' in key && !!key.contextInfo) {
541
- key.contextInfo = {
542
- ...key.contextInfo,
543
- ...message.contextInfo
544
- };
545
- } else if (key) {
546
- key.contextInfo = message.contextInfo;
547
- }
548
- }
549
- if (hasOptionalProperty(message, 'albumParentKey') && !!message.albumParentKey) {
550
- m.messageContextInfo = {
551
- ...m.messageContextInfo,
552
- messageAssociation: {
553
- associationType: WAProto.MessageAssociation.AssociationType.MEDIA_ALBUM,
554
- parentMessageKey: message.albumParentKey
555
- }
556
- };
557
- }
558
- if (shouldIncludeReportingToken(m)) {
559
- m.messageContextInfo = m.messageContextInfo || {};
560
- if (!m.messageContextInfo.messageSecret) {
561
- m.messageContextInfo.messageSecret = randomBytes(32);
562
- }
563
- }
564
- return WAProto.Message.create(m);
443
+ const pollCreationMessage = {
444
+ name: message.poll.name,
445
+ selectableOptionsCount: message.poll.selectableCount,
446
+ options: message.poll.values.map(optionName => ({ optionName }))
447
+ };
448
+ if (message.poll.toAnnouncementGroup) {
449
+ // poll v2 is for community announcement groups (single select and multiple)
450
+ m.pollCreationMessageV2 = pollCreationMessage;
451
+ }
452
+ else {
453
+ if (message.poll.selectableCount === 1) {
454
+ //poll v3 is for single select polls
455
+ m.pollCreationMessageV3 = pollCreationMessage;
456
+ }
457
+ else {
458
+ // poll for multiple choice polls
459
+ m.pollCreationMessage = pollCreationMessage;
460
+ }
461
+ }
462
+ }
463
+ else if (hasNonNullishProperty(message, 'album')) {
464
+ m.albumMessage = {
465
+ expectedImageCount: message.album.expectedImageCount,
466
+ expectedVideoCount: message.album.expectedVideoCount
467
+ };
468
+ }
469
+ else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
470
+ m.protocolMessage = {
471
+ type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
472
+ };
473
+ }
474
+ else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
475
+ m.requestPhoneNumberMessage = {};
476
+ }
477
+ else if (hasNonNullishProperty(message, 'limitSharing')) {
478
+ m.protocolMessage = {
479
+ type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
480
+ limitSharing: {
481
+ sharingLimited: message.limitSharing === true,
482
+ trigger: 1,
483
+ limitSharingSettingTimestamp: Date.now(),
484
+ initiatedByMe: true
485
+ }
486
+ };
487
+ }
488
+ else {
489
+ m = await prepareWAMessageMedia(message, options);
490
+ }
491
+ if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
492
+ m = { viewOnceMessage: { message: m } };
493
+ }
494
+ if ((hasOptionalProperty(message, 'mentions') && message.mentions?.length) ||
495
+ (hasOptionalProperty(message, 'mentionAll') && message.mentionAll)) {
496
+ const messageType = Object.keys(m)[0];
497
+ const key = m[messageType];
498
+ if (key && 'contextInfo' in key) {
499
+ key.contextInfo = key.contextInfo || {};
500
+ if (message.mentions?.length) {
501
+ key.contextInfo.mentionedJid = message.mentions;
502
+ }
503
+ if (message.mentionAll) {
504
+ key.contextInfo.nonJidMentions = 1;
505
+ }
506
+ }
507
+ else if (key) {
508
+ key.contextInfo = {
509
+ mentionedJid: message.mentions,
510
+ nonJidMentions: message.mentionAll ? 1 : 0
511
+ };
512
+ }
513
+ }
514
+ if (hasOptionalProperty(message, 'edit')) {
515
+ m = {
516
+ protocolMessage: {
517
+ key: message.edit,
518
+ editedMessage: m,
519
+ timestampMs: Date.now(),
520
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
521
+ }
522
+ };
523
+ }
524
+ if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
525
+ const messageType = Object.keys(m)[0];
526
+ const key = m[messageType];
527
+ if ('contextInfo' in key && !!key.contextInfo) {
528
+ key.contextInfo = { ...key.contextInfo, ...message.contextInfo };
529
+ }
530
+ else if (key) {
531
+ key.contextInfo = message.contextInfo;
532
+ }
533
+ }
534
+ if (hasOptionalProperty(message, 'albumParentKey') && !!message.albumParentKey) {
535
+ m.messageContextInfo = {
536
+ ...m.messageContextInfo,
537
+ messageAssociation: {
538
+ associationType: WAProto.MessageAssociation.AssociationType.MEDIA_ALBUM,
539
+ parentMessageKey: message.albumParentKey
540
+ }
541
+ };
542
+ }
543
+ if (shouldIncludeReportingToken(m)) {
544
+ m.messageContextInfo = m.messageContextInfo || {};
545
+ if (!m.messageContextInfo.messageSecret) {
546
+ m.messageContextInfo.messageSecret = randomBytes(32);
547
+ }
548
+ }
549
+ return WAProto.Message.create(m);
565
550
  };
566
551
  export const generateWAMessageFromContent = (jid, message, options) => {
567
- if (!options.timestamp) {
568
- options.timestamp = new Date();
569
- }
570
- const innerMessage = normalizeMessageContent(message);
571
- const key = getContentType(innerMessage);
572
- const timestamp = unixTimestampSeconds(options.timestamp);
573
- const {
574
- quoted,
575
- userJid
576
- } = options;
577
- if (quoted && !isJidNewsletter(jid)) {
578
- const participant = quoted.key.fromMe ? userJid : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
579
- let quotedMsg = normalizeMessageContent(quoted.message);
580
- const msgType = getContentType(quotedMsg);
581
- quotedMsg = proto.Message.create({
582
- [msgType]: quotedMsg[msgType]
583
- });
584
- const quotedContent = quotedMsg[msgType];
585
- if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
586
- delete quotedContent.contextInfo;
587
- }
588
- const contextInfo = 'contextInfo' in innerMessage[key] && innerMessage[key]?.contextInfo || {};
589
- contextInfo.participant = jidNormalizedUser(participant);
590
- contextInfo.stanzaId = quoted.key.id;
591
- contextInfo.quotedMessage = quotedMsg;
592
- if (jid !== quoted.key.remoteJid) {
593
- contextInfo.remoteJid = quoted.key.remoteJid;
594
- }
595
- if (contextInfo && innerMessage[key]) {
596
- innerMessage[key].contextInfo = contextInfo;
597
- }
598
- }
599
- if (!!options?.ephemeralExpiration && key !== 'protocolMessage' && key !== 'ephemeralMessage' && !isJidNewsletter(jid)) {
600
- innerMessage[key].contextInfo = {
601
- ...(innerMessage[key].contextInfo || {}),
602
- expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL
552
+ // set timestamp to now
553
+ // if not specified
554
+ if (!options.timestamp) {
555
+ options.timestamp = new Date();
556
+ }
557
+ const innerMessage = normalizeMessageContent(message);
558
+ const key = getContentType(innerMessage);
559
+ const timestamp = unixTimestampSeconds(options.timestamp);
560
+ const { quoted, userJid } = options;
561
+ if (quoted && !isJidNewsletter(jid)) {
562
+ const participant = quoted.key.fromMe
563
+ ? userJid // TODO: Add support for LIDs
564
+ : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
565
+ let quotedMsg = normalizeMessageContent(quoted.message);
566
+ const msgType = getContentType(quotedMsg);
567
+ // strip any redundant properties
568
+ quotedMsg = proto.Message.create({ [msgType]: quotedMsg[msgType] });
569
+ const quotedContent = quotedMsg[msgType];
570
+ if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
571
+ delete quotedContent.contextInfo;
572
+ }
573
+ const contextInfo = ('contextInfo' in innerMessage[key] && innerMessage[key]?.contextInfo) || {};
574
+ contextInfo.participant = jidNormalizedUser(participant);
575
+ contextInfo.stanzaId = quoted.key.id;
576
+ contextInfo.quotedMessage = quotedMsg;
577
+ // if a participant is quoted, then it must be a group
578
+ // hence, remoteJid of group must also be entered
579
+ if (jid !== quoted.key.remoteJid) {
580
+ contextInfo.remoteJid = quoted.key.remoteJid;
581
+ }
582
+ if (contextInfo && innerMessage[key]) {
583
+ /* @ts-ignore */
584
+ innerMessage[key].contextInfo = contextInfo;
585
+ }
586
+ }
587
+ if (
588
+ // if we want to send a disappearing message
589
+ !!options?.ephemeralExpiration &&
590
+ // and it's not a protocol message -- delete, toggle disappear message
591
+ key !== 'protocolMessage' &&
592
+ // already not converted to disappearing message
593
+ key !== 'ephemeralMessage' &&
594
+ // newsletters don't support ephemeral messages
595
+ !isJidNewsletter(jid)) {
596
+ /* @ts-ignore */
597
+ innerMessage[key].contextInfo = {
598
+ ...(innerMessage[key].contextInfo || {}),
599
+ expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL
600
+ //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
601
+ };
602
+ }
603
+ message = WAProto.Message.create(message);
604
+ const messageJSON = {
605
+ key: {
606
+ remoteJid: jid,
607
+ fromMe: true,
608
+ id: options?.messageId || generateMessageIDV2()
609
+ },
610
+ message: message,
611
+ messageTimestamp: timestamp,
612
+ messageStubParameters: [],
613
+ participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined, // TODO: Add support for LIDs
614
+ status: WAMessageStatus.PENDING
603
615
  };
604
- }
605
- message = WAProto.Message.create(message);
606
- const messageJSON = {
607
- key: {
608
- remoteJid: jid,
609
- fromMe: true,
610
- id: options?.messageId || generateMessageIDV2()
611
- },
612
- message: message,
613
- messageTimestamp: timestamp,
614
- messageStubParameters: [],
615
- participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined,
616
- status: WAMessageStatus.PENDING
617
- };
618
- return WAProto.WebMessageInfo.fromObject(messageJSON);
616
+ return WAProto.WebMessageInfo.fromObject(messageJSON);
619
617
  };
620
618
  export const generateWAMessage = async (jid, content, options) => {
621
- options.logger = options?.logger?.child({
622
- msgId: options.messageId
623
- });
624
- return generateWAMessageFromContent(jid, await generateWAMessageContent(content, {
625
- ...options,
626
- jid
627
- }), options);
619
+ // ensure msg ID is with every log
620
+ options.logger = options?.logger?.child({ msgId: options.messageId });
621
+ // Pass jid in the options to generateWAMessageContent
622
+ return generateWAMessageFromContent(jid, await generateWAMessageContent(content, { ...options, jid }), options);
628
623
  };
629
- export const getContentType = content => {
630
- if (content) {
631
- const keys = Object.keys(content);
632
- const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage');
633
- return key;
634
- }
624
+ /** Get the key to access the true type of content */
625
+ export const getContentType = (content) => {
626
+ if (content) {
627
+ const keys = Object.keys(content);
628
+ const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage');
629
+ return key;
630
+ }
635
631
  };
636
- export const normalizeMessageContent = content => {
637
- if (!content) {
638
- return undefined;
639
- }
640
- for (let i = 0; i < 5; i++) {
641
- const inner = getFutureProofMessage(content);
642
- if (!inner) {
643
- break;
644
- }
645
- content = inner.message;
646
- }
647
- return content;
648
- function getFutureProofMessage(message) {
649
- return message?.ephemeralMessage || message?.viewOnceMessage || message?.documentWithCaptionMessage || message?.viewOnceMessageV2 || message?.viewOnceMessageV2Extension || message?.editedMessage || message?.associatedChildMessage || message?.groupStatusMessage || message?.groupStatusMessageV2;
650
- }
632
+ /**
633
+ * Normalizes ephemeral, view once messages to regular message content
634
+ * Eg. image messages in ephemeral messages, in view once messages etc.
635
+ * @param content
636
+ * @returns
637
+ */
638
+ export const normalizeMessageContent = (content) => {
639
+ if (!content) {
640
+ return undefined;
641
+ }
642
+ // set max iterations to prevent an infinite loop
643
+ for (let i = 0; i < 5; i++) {
644
+ const inner = getFutureProofMessage(content);
645
+ if (!inner) {
646
+ break;
647
+ }
648
+ content = inner.message;
649
+ }
650
+ return content;
651
+ function getFutureProofMessage(message) {
652
+ return (message?.ephemeralMessage ||
653
+ message?.viewOnceMessage ||
654
+ message?.documentWithCaptionMessage ||
655
+ message?.viewOnceMessageV2 ||
656
+ message?.viewOnceMessageV2Extension ||
657
+ message?.editedMessage ||
658
+ message?.associatedChildMessage ||
659
+ message?.groupStatusMessage ||
660
+ message?.groupStatusMessageV2);
661
+ }
651
662
  };
652
- export const extractMessageContent = content => {
653
- const extractFromTemplateMessage = msg => {
654
- if (msg.imageMessage) {
655
- return {
656
- imageMessage: msg.imageMessage
657
- };
658
- } else if (msg.documentMessage) {
659
- return {
660
- documentMessage: msg.documentMessage
661
- };
662
- } else if (msg.videoMessage) {
663
- return {
664
- videoMessage: msg.videoMessage
665
- };
666
- } else if (msg.locationMessage) {
667
- return {
668
- locationMessage: msg.locationMessage
669
- };
670
- } else {
671
- return {
672
- conversation: 'contentText' in msg ? msg.contentText : 'hydratedContentText' in msg ? msg.hydratedContentText : ''
673
- };
674
- }
675
- };
676
- content = normalizeMessageContent(content);
677
- if (content?.buttonsMessage) {
678
- return extractFromTemplateMessage(content.buttonsMessage);
679
- }
680
- if (content?.templateMessage?.hydratedFourRowTemplate) {
681
- return extractFromTemplateMessage(content?.templateMessage?.hydratedFourRowTemplate);
682
- }
683
- if (content?.templateMessage?.hydratedTemplate) {
684
- return extractFromTemplateMessage(content?.templateMessage?.hydratedTemplate);
685
- }
686
- if (content?.templateMessage?.fourRowTemplate) {
687
- return extractFromTemplateMessage(content?.templateMessage?.fourRowTemplate);
688
- }
689
- return content;
663
+ /**
664
+ * Extract the true message content from a message
665
+ * Eg. extracts the inner message from a disappearing message/view once message
666
+ */
667
+ export const extractMessageContent = (content) => {
668
+ const extractFromTemplateMessage = (msg) => {
669
+ if (msg.imageMessage) {
670
+ return { imageMessage: msg.imageMessage };
671
+ }
672
+ else if (msg.documentMessage) {
673
+ return { documentMessage: msg.documentMessage };
674
+ }
675
+ else if (msg.videoMessage) {
676
+ return { videoMessage: msg.videoMessage };
677
+ }
678
+ else if (msg.locationMessage) {
679
+ return { locationMessage: msg.locationMessage };
680
+ }
681
+ else {
682
+ return {
683
+ conversation: 'contentText' in msg ? msg.contentText : 'hydratedContentText' in msg ? msg.hydratedContentText : ''
684
+ };
685
+ }
686
+ };
687
+ content = normalizeMessageContent(content);
688
+ if (content?.buttonsMessage) {
689
+ return extractFromTemplateMessage(content.buttonsMessage);
690
+ }
691
+ if (content?.templateMessage?.hydratedFourRowTemplate) {
692
+ return extractFromTemplateMessage(content?.templateMessage?.hydratedFourRowTemplate);
693
+ }
694
+ if (content?.templateMessage?.hydratedTemplate) {
695
+ return extractFromTemplateMessage(content?.templateMessage?.hydratedTemplate);
696
+ }
697
+ if (content?.templateMessage?.fourRowTemplate) {
698
+ return extractFromTemplateMessage(content?.templateMessage?.fourRowTemplate);
699
+ }
700
+ return content;
690
701
  };
691
- export const getDevice = id => /^3A.{18}$/.test(id) ? 'ios' : /^3E.{20}$/.test(id) ? 'web' : /^(.{21}|.{32})$/.test(id) ? 'android' : /^(3F|.{18}$)/.test(id) ? 'desktop' : 'unknown';
702
+ /**
703
+ * Returns the device predicted by message ID
704
+ */
705
+ export const getDevice = (id) => /^3A.{18}$/.test(id)
706
+ ? 'ios'
707
+ : /^3E.{20}$/.test(id)
708
+ ? 'web'
709
+ : /^(.{21}|.{32})$/.test(id)
710
+ ? 'android'
711
+ : /^(3F|.{18}$)/.test(id)
712
+ ? 'desktop'
713
+ : 'unknown';
714
+ /** Upserts a receipt in the message */
692
715
  export const updateMessageWithReceipt = (msg, receipt) => {
693
- msg.userReceipt = msg.userReceipt || [];
694
- const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid);
695
- if (recp) {
696
- Object.assign(recp, receipt);
697
- } else {
698
- msg.userReceipt.push(receipt);
699
- }
716
+ msg.userReceipt = msg.userReceipt || [];
717
+ const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid);
718
+ if (recp) {
719
+ Object.assign(recp, receipt);
720
+ }
721
+ else {
722
+ msg.userReceipt.push(receipt);
723
+ }
700
724
  };
725
+ /** Update the message with a new reaction */
701
726
  export const updateMessageWithReaction = (msg, reaction) => {
702
- const authorID = getKeyAuthor(reaction.key);
703
- const reactions = (msg.reactions || []).filter(r => getKeyAuthor(r.key) !== authorID);
704
- reaction.text = reaction.text || '';
705
- reactions.push(reaction);
706
- msg.reactions = reactions;
727
+ const authorID = getKeyAuthor(reaction.key);
728
+ const reactions = (msg.reactions || []).filter(r => getKeyAuthor(r.key) !== authorID);
729
+ reaction.text = reaction.text || '';
730
+ reactions.push(reaction);
731
+ msg.reactions = reactions;
707
732
  };
733
+ /** Update the message with a new poll update */
708
734
  export const updateMessageWithPollUpdate = (msg, update) => {
709
- const authorID = getKeyAuthor(update.pollUpdateMessageKey);
710
- const reactions = (msg.pollUpdates || []).filter(r => getKeyAuthor(r.pollUpdateMessageKey) !== authorID);
711
- if (update.vote?.selectedOptions?.length) {
712
- reactions.push(update);
713
- }
714
- msg.pollUpdates = reactions;
735
+ const authorID = getKeyAuthor(update.pollUpdateMessageKey);
736
+ const reactions = (msg.pollUpdates || []).filter(r => getKeyAuthor(r.pollUpdateMessageKey) !== authorID);
737
+ if (update.vote?.selectedOptions?.length) {
738
+ reactions.push(update);
739
+ }
740
+ msg.pollUpdates = reactions;
715
741
  };
742
+ /** Update the message with a new event response */
716
743
  export const updateMessageWithEventResponse = (msg, update) => {
717
- const authorID = getKeyAuthor(update.eventResponseMessageKey);
718
- const responses = (msg.eventResponses || []).filter(r => getKeyAuthor(r.eventResponseMessageKey) !== authorID);
719
- responses.push(update);
720
- msg.eventResponses = responses;
744
+ const authorID = getKeyAuthor(update.eventResponseMessageKey);
745
+ const responses = (msg.eventResponses || []).filter(r => getKeyAuthor(r.eventResponseMessageKey) !== authorID);
746
+ responses.push(update);
747
+ msg.eventResponses = responses;
721
748
  };
722
- export function getAggregateVotesInPollMessage({
723
- message,
724
- pollUpdates
725
- }, meId) {
726
- const opts = message?.pollCreationMessage?.options || message?.pollCreationMessageV2?.options || message?.pollCreationMessageV3?.options || [];
727
- const voteHashMap = opts.reduce((acc, opt) => {
728
- const hash = sha256(Buffer.from(opt.optionName || '')).toString();
729
- acc[hash] = {
730
- name: opt.optionName || '',
731
- voters: []
732
- };
733
- return acc;
734
- }, {});
735
- for (const update of pollUpdates || []) {
736
- const {
737
- vote
738
- } = update;
739
- if (!vote) {
740
- continue;
741
- }
742
- for (const option of vote.selectedOptions || []) {
743
- const hash = option.toString();
744
- let data = voteHashMap[hash];
745
- if (!data) {
746
- voteHashMap[hash] = {
747
- name: 'Unknown',
748
- voters: []
749
+ /**
750
+ * Aggregates all poll updates in a poll.
751
+ * @param msg the poll creation message
752
+ * @param meId your jid
753
+ * @returns A list of options & their voters
754
+ */
755
+ export function getAggregateVotesInPollMessage({ message, pollUpdates }, meId) {
756
+ const opts = message?.pollCreationMessage?.options ||
757
+ message?.pollCreationMessageV2?.options ||
758
+ message?.pollCreationMessageV3?.options ||
759
+ [];
760
+ const voteHashMap = opts.reduce((acc, opt) => {
761
+ const hash = sha256(Buffer.from(opt.optionName || '')).toString();
762
+ acc[hash] = {
763
+ name: opt.optionName || '',
764
+ voters: []
749
765
  };
750
- data = voteHashMap[hash];
751
- }
752
- voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId));
766
+ return acc;
767
+ }, {});
768
+ for (const update of pollUpdates || []) {
769
+ const { vote } = update;
770
+ if (!vote) {
771
+ continue;
772
+ }
773
+ for (const option of vote.selectedOptions || []) {
774
+ const hash = option.toString();
775
+ let data = voteHashMap[hash];
776
+ if (!data) {
777
+ voteHashMap[hash] = {
778
+ name: 'Unknown',
779
+ voters: []
780
+ };
781
+ data = voteHashMap[hash];
782
+ }
783
+ voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId));
784
+ }
753
785
  }
754
- }
755
- return Object.values(voteHashMap);
756
- }
757
- export function getAggregateResponsesInEventMessage({
758
- eventResponses
759
- }, meId) {
760
- const responseTypes = ['GOING', 'NOT_GOING', 'MAYBE'];
761
- const responseMap = {};
762
- for (const type of responseTypes) {
763
- responseMap[type] = {
764
- response: type,
765
- responders: []
766
- };
767
- }
768
- for (const update of eventResponses || []) {
769
- const responseType = update.eventResponse || 'UNKNOWN';
770
- if (responseType !== 'UNKNOWN' && responseMap[responseType]) {
771
- responseMap[responseType].responders.push(getKeyAuthor(update.eventResponseMessageKey, meId));
772
- }
773
- }
774
- return Object.values(responseMap);
786
+ return Object.values(voteHashMap);
775
787
  }
776
- export const aggregateMessageKeysNotFromMe = keys => {
777
- const keyMap = {};
778
- for (const {
779
- remoteJid,
780
- id,
781
- participant,
782
- fromMe
783
- } of keys) {
784
- if (!fromMe) {
785
- const uqKey = `${remoteJid}:${participant || ''}`;
786
- if (!keyMap[uqKey]) {
787
- keyMap[uqKey] = {
788
- jid: remoteJid,
789
- participant: participant,
790
- messageIds: []
788
+ /**
789
+ * Aggregates all event responses in an event message.
790
+ * @param msg the event creation message
791
+ * @param meId your jid
792
+ * @returns A list of response types & their responders
793
+ */
794
+ export function getAggregateResponsesInEventMessage({ eventResponses }, meId) {
795
+ const responseTypes = ['GOING', 'NOT_GOING', 'MAYBE'];
796
+ const responseMap = {};
797
+ for (const type of responseTypes) {
798
+ responseMap[type] = {
799
+ response: type,
800
+ responders: []
791
801
  };
792
- }
793
- keyMap[uqKey].messageIds.push(id);
794
802
  }
795
- }
796
- return Object.values(keyMap);
803
+ for (const update of eventResponses || []) {
804
+ const responseType = update.eventResponse || 'UNKNOWN';
805
+ if (responseType !== 'UNKNOWN' && responseMap[responseType]) {
806
+ responseMap[responseType].responders.push(getKeyAuthor(update.eventResponseMessageKey, meId));
807
+ }
808
+ }
809
+ return Object.values(responseMap);
810
+ }
811
+ /** Given a list of message keys, aggregates them by chat & sender. Useful for sending read receipts in bulk */
812
+ export const aggregateMessageKeysNotFromMe = (keys) => {
813
+ const keyMap = {};
814
+ for (const { remoteJid, id, participant, fromMe } of keys) {
815
+ if (!fromMe) {
816
+ const uqKey = `${remoteJid}:${participant || ''}`;
817
+ if (!keyMap[uqKey]) {
818
+ keyMap[uqKey] = {
819
+ jid: remoteJid,
820
+ participant: participant,
821
+ messageIds: []
822
+ };
823
+ }
824
+ keyMap[uqKey].messageIds.push(id);
825
+ }
826
+ }
827
+ return Object.values(keyMap);
797
828
  };
798
829
  const REUPLOAD_REQUIRED_STATUS = [410, 404];
830
+ /**
831
+ * Downloads the given message. Throws an error if it's not a media message
832
+ */
799
833
  export const downloadMediaMessage = async (message, type, options, ctx) => {
800
- const result = await downloadMsg().catch(async error => {
801
- if (ctx && typeof error?.status === 'number' && REUPLOAD_REQUIRED_STATUS.includes(error.status)) {
802
- ctx.logger.info({
803
- key: message.key
804
- }, 'sending reupload media request...');
805
- message = await ctx.reuploadRequest(message);
806
- const result = await downloadMsg();
807
- return result;
808
- }
809
- throw error;
810
- });
811
- return result;
812
- async function downloadMsg() {
813
- const mContent = extractMessageContent(message.message);
814
- if (!mContent) {
815
- throw new Boom('No message present', {
816
- statusCode: 400,
817
- data: message
818
- });
819
- }
820
- const contentType = getContentType(mContent);
821
- let mediaType = contentType?.replace('Message', '');
822
- const media = mContent[contentType];
823
- if (!media || typeof media !== 'object' || !('url' in media) && !('thumbnailDirectPath' in media)) {
824
- throw new Boom(`"${contentType}" message is not a media message`);
825
- }
826
- let download;
827
- if ('thumbnailDirectPath' in media && !('url' in media)) {
828
- download = {
829
- directPath: media.thumbnailDirectPath,
830
- mediaKey: media.mediaKey
831
- };
832
- mediaType = 'thumbnail-link';
833
- } else {
834
- download = media;
835
- }
836
- const stream = await downloadContentFromMessage(download, mediaType, options);
837
- if (type === 'buffer') {
838
- const bufferArray = [];
839
- for await (const chunk of stream) {
840
- bufferArray.push(chunk);
841
- }
842
- return Buffer.concat(bufferArray);
843
- }
844
- return stream;
845
- }
846
- };
847
- export const assertMediaContent = content => {
848
- content = extractMessageContent(content);
849
- const mediaContent = content?.documentMessage || content?.imageMessage || content?.videoMessage || content?.audioMessage || content?.stickerMessage;
850
- if (!mediaContent) {
851
- throw new Boom('given message is not a media message', {
852
- statusCode: 400,
853
- data: content
834
+ const result = await downloadMsg().catch(async (error) => {
835
+ if (ctx &&
836
+ typeof error?.status === 'number' && // treat errors with status as HTTP failures requiring reupload
837
+ REUPLOAD_REQUIRED_STATUS.includes(error.status)) {
838
+ ctx.logger.info({ key: message.key }, 'sending reupload media request...');
839
+ // request reupload
840
+ message = await ctx.reuploadRequest(message);
841
+ const result = await downloadMsg();
842
+ return result;
843
+ }
844
+ throw error;
854
845
  });
855
- }
856
- return mediaContent;
846
+ return result;
847
+ async function downloadMsg() {
848
+ const mContent = extractMessageContent(message.message);
849
+ if (!mContent) {
850
+ throw new Boom('No message present', { statusCode: 400, data: message });
851
+ }
852
+ const contentType = getContentType(mContent);
853
+ let mediaType = contentType?.replace('Message', '');
854
+ const media = mContent[contentType];
855
+ if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
856
+ throw new Boom(`"${contentType}" message is not a media message`);
857
+ }
858
+ let download;
859
+ if ('thumbnailDirectPath' in media && !('url' in media)) {
860
+ download = {
861
+ directPath: media.thumbnailDirectPath,
862
+ mediaKey: media.mediaKey
863
+ };
864
+ mediaType = 'thumbnail-link';
865
+ }
866
+ else {
867
+ download = media;
868
+ }
869
+ const stream = await downloadContentFromMessage(download, mediaType, options);
870
+ if (type === 'buffer') {
871
+ const bufferArray = [];
872
+ for await (const chunk of stream) {
873
+ bufferArray.push(chunk);
874
+ }
875
+ return Buffer.concat(bufferArray);
876
+ }
877
+ return stream;
878
+ }
879
+ };
880
+ /** Checks whether the given message is a media message; if it is returns the inner content */
881
+ export const assertMediaContent = (content) => {
882
+ content = extractMessageContent(content);
883
+ const mediaContent = content?.documentMessage ||
884
+ content?.imageMessage ||
885
+ content?.videoMessage ||
886
+ content?.audioMessage ||
887
+ content?.stickerMessage;
888
+ if (!mediaContent) {
889
+ throw new Boom('given message is not a media message', { statusCode: 400, data: content });
890
+ }
891
+ return mediaContent;
857
892
  };
893
+ //# sourceMappingURL=messages.js.map