@sixcore/baileys 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +606 -0
- package/WAProto/GenerateStatics.sh +4 -0
- package/WAProto/WAProto.proto +4357 -0
- package/WAProto/index.d.ts +50383 -0
- package/WAProto/index.js +155693 -0
- package/WASignalGroup/GroupProtocol.js +1697 -0
- package/WASignalGroup/ciphertext_message.js +16 -0
- package/WASignalGroup/generate-proto.sh +1 -0
- package/WASignalGroup/group.proto +42 -0
- package/WASignalGroup/group_cipher.js +120 -0
- package/WASignalGroup/group_session_builder.js +46 -0
- package/WASignalGroup/index.js +5 -0
- package/WASignalGroup/keyhelper.js +21 -0
- package/WASignalGroup/protobufs.js +3 -0
- package/WASignalGroup/queue_job.js +69 -0
- package/WASignalGroup/sender_chain_key.js +50 -0
- package/WASignalGroup/sender_key_distribution_message.js +78 -0
- package/WASignalGroup/sender_key_message.js +92 -0
- package/WASignalGroup/sender_key_name.js +70 -0
- package/WASignalGroup/sender_key_record.js +56 -0
- package/WASignalGroup/sender_key_state.js +129 -0
- package/WASignalGroup/sender_message_key.js +39 -0
- package/lib/Defaults/baileys-version.json +3 -0
- package/lib/Defaults/index.d.ts +53 -0
- package/lib/Defaults/index.js +108 -0
- package/lib/Signal/libsignal.d.ts +3 -0
- package/lib/Signal/libsignal.js +152 -0
- package/lib/Socket/Client/abstract-socket-client.d.ts +17 -0
- package/lib/Socket/Client/abstract-socket-client.js +13 -0
- package/lib/Socket/Client/index.d.ts +3 -0
- package/lib/Socket/Client/index.js +19 -0
- package/lib/Socket/Client/mobile-socket-client.d.ts +13 -0
- package/lib/Socket/Client/mobile-socket-client.js +65 -0
- package/lib/Socket/Client/web-socket-client.d.ts +12 -0
- package/lib/Socket/Client/web-socket-client.js +62 -0
- package/lib/Socket/business.d.ts +170 -0
- package/lib/Socket/business.js +260 -0
- package/lib/Socket/chats.d.ts +81 -0
- package/lib/Socket/chats.js +950 -0
- package/lib/Socket/groups.d.ts +115 -0
- package/lib/Socket/groups.js +315 -0
- package/lib/Socket/index.d.ts +172 -0
- package/lib/Socket/index.js +10 -0
- package/lib/Socket/messages-recv.d.ts +158 -0
- package/lib/Socket/messages-recv.js +972 -0
- package/lib/Socket/messages-send.d.ts +155 -0
- package/lib/Socket/messages-send.js +1087 -0
- package/lib/Socket/newsletter.d.ts +132 -0
- package/lib/Socket/newsletter.js +236 -0
- package/lib/Socket/registration.d.ts +264 -0
- package/lib/Socket/registration.js +166 -0
- package/lib/Socket/socket.d.ts +44 -0
- package/lib/Socket/socket.js +643 -0
- package/lib/Socket/usync.d.ts +37 -0
- package/lib/Socket/usync.js +70 -0
- package/lib/Store/index.d.ts +3 -0
- package/lib/Store/index.js +10 -0
- package/lib/Store/make-cache-manager-store.d.ts +14 -0
- package/lib/Store/make-cache-manager-store.js +83 -0
- package/lib/Store/make-in-memory-store.d.ts +118 -0
- package/lib/Store/make-in-memory-store.js +431 -0
- package/lib/Store/make-ordered-dictionary.d.ts +13 -0
- package/lib/Store/make-ordered-dictionary.js +81 -0
- package/lib/Store/object-repository.d.ts +10 -0
- package/lib/Store/object-repository.js +27 -0
- package/lib/Types/Auth.d.ts +109 -0
- package/lib/Types/Auth.js +2 -0
- package/lib/Types/Call.d.ts +13 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.d.ts +107 -0
- package/lib/Types/Chat.js +4 -0
- package/lib/Types/Contact.d.ts +19 -0
- package/lib/Types/Contact.js +2 -0
- package/lib/Types/Events.d.ts +172 -0
- package/lib/Types/Events.js +2 -0
- package/lib/Types/GroupMetadata.d.ts +56 -0
- package/lib/Types/GroupMetadata.js +2 -0
- package/lib/Types/Label.d.ts +46 -0
- package/lib/Types/Label.js +27 -0
- package/lib/Types/LabelAssociation.d.ts +29 -0
- package/lib/Types/LabelAssociation.js +9 -0
- package/lib/Types/Message.d.ts +433 -0
- package/lib/Types/Message.js +9 -0
- package/lib/Types/Newsletter.d.ts +92 -0
- package/lib/Types/Newsletter.js +32 -0
- package/lib/Types/Product.d.ts +78 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/Signal.d.ts +57 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.d.ts +116 -0
- package/lib/Types/Socket.js +2 -0
- package/lib/Types/State.d.ts +27 -0
- package/lib/Types/State.js +2 -0
- package/lib/Types/USync.d.ts +25 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.d.ts +66 -0
- package/lib/Types/index.js +42 -0
- package/lib/Utils/auth-utils.d.ts +18 -0
- package/lib/Utils/auth-utils.js +227 -0
- package/lib/Utils/baileys-event-stream.d.ts +16 -0
- package/lib/Utils/baileys-event-stream.js +63 -0
- package/lib/Utils/business.d.ts +22 -0
- package/lib/Utils/business.js +234 -0
- package/lib/Utils/chat-utils.d.ts +70 -0
- package/lib/Utils/chat-utils.js +745 -0
- package/lib/Utils/crypto.d.ts +40 -0
- package/lib/Utils/crypto.js +199 -0
- package/lib/Utils/decode-wa-message.d.ts +36 -0
- package/lib/Utils/decode-wa-message.js +234 -0
- package/lib/Utils/event-buffer.d.ts +35 -0
- package/lib/Utils/event-buffer.js +517 -0
- package/lib/Utils/generics.d.ts +88 -0
- package/lib/Utils/generics.js +402 -0
- package/lib/Utils/history.d.ts +19 -0
- package/lib/Utils/history.js +94 -0
- package/lib/Utils/index.d.ts +17 -0
- package/lib/Utils/index.js +33 -0
- package/lib/Utils/link-preview.d.ts +21 -0
- package/lib/Utils/link-preview.js +93 -0
- package/lib/Utils/logger.d.ts +2 -0
- package/lib/Utils/logger.js +7 -0
- package/lib/Utils/lt-hash.d.ts +12 -0
- package/lib/Utils/lt-hash.js +51 -0
- package/lib/Utils/make-mutex.d.ts +7 -0
- package/lib/Utils/make-mutex.js +43 -0
- package/lib/Utils/messages-media.d.ts +113 -0
- package/lib/Utils/messages-media.js +643 -0
- package/lib/Utils/messages.d.ts +77 -0
- package/lib/Utils/messages.js +1221 -0
- package/lib/Utils/noise-handler.d.ts +20 -0
- package/lib/Utils/noise-handler.js +160 -0
- package/lib/Utils/process-message.d.ts +41 -0
- package/lib/Utils/process-message.js +373 -0
- package/lib/Utils/signal.d.ts +33 -0
- package/lib/Utils/signal.js +159 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +12 -0
- package/lib/Utils/use-multi-file-auth-state.js +295 -0
- package/lib/Utils/use-single-file-auth-state.d.ts +12 -0
- package/lib/Utils/use-single-file-auth-state.js +75 -0
- package/lib/Utils/validate-connection.d.ts +11 -0
- package/lib/Utils/validate-connection.js +205 -0
- package/lib/WABinary/constants.d.ts +27 -0
- package/lib/WABinary/constants.js +40 -0
- package/lib/WABinary/decode.d.ts +6 -0
- package/lib/WABinary/decode.js +264 -0
- package/lib/WABinary/encode.d.ts +2 -0
- package/lib/WABinary/encode.js +252 -0
- package/lib/WABinary/generic-utils.d.ts +14 -0
- package/lib/WABinary/generic-utils.js +110 -0
- package/lib/WABinary/index.d.ts +5 -0
- package/lib/WABinary/index.js +21 -0
- package/lib/WABinary/jid-utils.d.ts +31 -0
- package/lib/WABinary/jid-utils.js +62 -0
- package/lib/WABinary/types.d.ts +18 -0
- package/lib/WABinary/types.js +2 -0
- package/lib/WAM/BinaryInfo.d.ts +8 -0
- package/lib/WAM/BinaryInfo.js +13 -0
- package/lib/WAM/constants.d.ts +38 -0
- package/lib/WAM/constants.js +15350 -0
- package/lib/WAM/encode.d.ts +2 -0
- package/lib/WAM/encode.js +155 -0
- package/lib/WAM/index.d.ts +3 -0
- package/lib/WAM/index.js +19 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +32 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +42 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +25 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +53 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +8 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +24 -0
- package/lib/WAUSync/Protocols/index.d.ts +4 -0
- package/lib/WAUSync/Protocols/index.js +20 -0
- package/lib/WAUSync/USyncQuery.d.ts +28 -0
- package/lib/WAUSync/USyncQuery.js +89 -0
- package/lib/WAUSync/USyncUser.d.ts +10 -0
- package/lib/WAUSync/USyncUser.js +26 -0
- package/lib/WAUSync/index.d.ts +3 -0
- package/lib/WAUSync/index.js +19 -0
- package/lib/index.js +31 -0
- package/package.json +51 -0
- package/src/Defaults/baileys-version.json +3 -0
- package/src/Defaults/index.ts +133 -0
- package/src/Signal/Group/ciphertext-message.ts +9 -0
- package/src/Signal/Group/group-session-builder.ts +56 -0
- package/src/Signal/Group/group_cipher.ts +117 -0
- package/src/Signal/Group/index.ts +11 -0
- package/src/Signal/Group/keyhelper.ts +28 -0
- package/src/Signal/Group/sender-chain-key.ts +34 -0
- package/src/Signal/Group/sender-key-distribution-message.ts +95 -0
- package/src/Signal/Group/sender-key-message.ts +96 -0
- package/src/Signal/Group/sender-key-name.ts +66 -0
- package/src/Signal/Group/sender-key-record.ts +69 -0
- package/src/Signal/Group/sender-key-state.ts +134 -0
- package/src/Signal/Group/sender-message-key.ts +36 -0
- package/src/Signal/libsignal.ts +447 -0
- package/src/Signal/lid-mapping.ts +209 -0
- package/src/Socket/Client/index.ts +2 -0
- package/src/Socket/Client/types.ts +22 -0
- package/src/Socket/Client/websocket.ts +56 -0
- package/src/Socket/business.ts +421 -0
- package/src/Socket/chats.ts +1223 -0
- package/src/Socket/communities.ts +477 -0
- package/src/Socket/groups.ts +361 -0
- package/src/Socket/index.ts +22 -0
- package/src/Socket/messages-recv.ts +1563 -0
- package/src/Socket/messages-send.ts +1210 -0
- package/src/Socket/mex.ts +58 -0
- package/src/Socket/newsletter.ts +229 -0
- package/src/Socket/socket.ts +1072 -0
- package/src/Types/Auth.ts +115 -0
- package/src/Types/Bussines.ts +20 -0
- package/src/Types/Call.ts +14 -0
- package/src/Types/Chat.ts +138 -0
- package/src/Types/Contact.ts +24 -0
- package/src/Types/Events.ts +132 -0
- package/src/Types/GroupMetadata.ts +70 -0
- package/src/Types/Label.ts +48 -0
- package/src/Types/LabelAssociation.ts +35 -0
- package/src/Types/Message.ts +424 -0
- package/src/Types/Newsletter.ts +98 -0
- package/src/Types/Product.ts +85 -0
- package/src/Types/Signal.ts +76 -0
- package/src/Types/Socket.ts +150 -0
- package/src/Types/State.ts +43 -0
- package/src/Types/USync.ts +27 -0
- package/src/Types/globals.d.ts +8 -0
- package/src/Types/index.ts +67 -0
- package/src/Utils/auth-utils.ts +331 -0
- package/src/Utils/browser-utils.ts +31 -0
- package/src/Utils/business.ts +286 -0
- package/src/Utils/chat-utils.ts +933 -0
- package/src/Utils/crypto.ts +184 -0
- package/src/Utils/decode-wa-message.ts +355 -0
- package/src/Utils/event-buffer.ts +662 -0
- package/src/Utils/generics.ts +470 -0
- package/src/Utils/history.ts +114 -0
- package/src/Utils/index.ts +18 -0
- package/src/Utils/link-preview.ts +111 -0
- package/src/Utils/logger.ts +13 -0
- package/src/Utils/lt-hash.ts +65 -0
- package/src/Utils/make-mutex.ts +45 -0
- package/src/Utils/message-retry-manager.ts +229 -0
- package/src/Utils/messages-media.ts +820 -0
- package/src/Utils/messages.ts +1137 -0
- package/src/Utils/noise-handler.ts +192 -0
- package/src/Utils/pre-key-manager.ts +126 -0
- package/src/Utils/process-message.ts +622 -0
- package/src/Utils/signal.ts +214 -0
- package/src/Utils/use-multi-file-auth-state.ts +136 -0
- package/src/Utils/validate-connection.ts +253 -0
- package/src/WABinary/constants.ts +1305 -0
- package/src/WABinary/decode.ts +281 -0
- package/src/WABinary/encode.ts +253 -0
- package/src/WABinary/generic-utils.ts +127 -0
- package/src/WABinary/index.ts +5 -0
- package/src/WABinary/jid-utils.ts +128 -0
- package/src/WABinary/types.ts +17 -0
- package/src/WAM/BinaryInfo.ts +12 -0
- package/src/WAM/constants.ts +22889 -0
- package/src/WAM/encode.ts +169 -0
- package/src/WAM/index.ts +3 -0
- package/src/WAUSync/Protocols/USyncContactProtocol.ts +32 -0
- package/src/WAUSync/Protocols/USyncDeviceProtocol.ts +78 -0
- package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.ts +35 -0
- package/src/WAUSync/Protocols/USyncStatusProtocol.ts +44 -0
- package/src/WAUSync/Protocols/UsyncBotProfileProtocol.ts +76 -0
- package/src/WAUSync/Protocols/UsyncLIDProtocol.ts +33 -0
- package/src/WAUSync/Protocols/index.ts +4 -0
- package/src/WAUSync/USyncQuery.ts +133 -0
- package/src/WAUSync/USyncUser.ts +32 -0
- package/src/WAUSync/index.ts +3 -0
- package/src/index.ts +13 -0
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
// Lena-Baileys — Módulo de Mídia
|
|
5
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
|
|
43
|
+
exports.getStatusCodeForMediaRetry =
|
|
44
|
+
exports.decryptMediaRetryData =
|
|
45
|
+
exports.decodeMediaRetryNode =
|
|
46
|
+
exports.encryptMediaRetryRequest =
|
|
47
|
+
exports.getWAUploadToServer =
|
|
48
|
+
exports.downloadEncryptedContent =
|
|
49
|
+
exports.downloadContentFromMessageV1 =
|
|
50
|
+
exports.clearDownloadCache =
|
|
51
|
+
exports.detectMimeType =
|
|
52
|
+
exports.downloadContentFromMessage =
|
|
53
|
+
exports.getUrlFromDirectPath =
|
|
54
|
+
exports.encryptedStream =
|
|
55
|
+
exports.prepareStream =
|
|
56
|
+
exports.getHttpStream =
|
|
57
|
+
exports.getStream =
|
|
58
|
+
exports.toBuffer =
|
|
59
|
+
exports.toReadable =
|
|
60
|
+
exports.mediaMessageSHA256B64 =
|
|
61
|
+
exports.generateProfilePicture =
|
|
62
|
+
exports.encodeBase64EncodedStringForUpload =
|
|
63
|
+
exports.extractImageThumb =
|
|
64
|
+
exports.hkdfInfoKey = void 0;
|
|
65
|
+
|
|
66
|
+
exports.getMediaKeys = getMediaKeys;
|
|
67
|
+
exports.getAudioDuration = getAudioDuration;
|
|
68
|
+
exports.getAudioWaveform = getAudioWaveform;
|
|
69
|
+
exports.generateThumbnail = generateThumbnail;
|
|
70
|
+
exports.extensionForMediaMessage = extensionForMediaMessage;
|
|
71
|
+
|
|
72
|
+
// ─── Dependências ─────────────────────────────────────────────────────────────
|
|
73
|
+
const { Boom } = require("@hapi/boom");
|
|
74
|
+
const { exec } = require("child_process");
|
|
75
|
+
const Crypto = __importStar(require("crypto"));
|
|
76
|
+
const { once } = require("events");
|
|
77
|
+
const { createReadStream, createWriteStream, writeFileSync, promises: fsp } = require("fs");
|
|
78
|
+
const { tmpdir } = require("os");
|
|
79
|
+
const { join } = require("path");
|
|
80
|
+
const { Readable, Transform } = require("stream");
|
|
81
|
+
const Proto = require("../../WAProto");
|
|
82
|
+
const Config = require("../Defaults");
|
|
83
|
+
const JidUtils = require("../WABinary");
|
|
84
|
+
const CryptoUtils = require("./crypto");
|
|
85
|
+
const Helpers = require("./generics");
|
|
86
|
+
|
|
87
|
+
// ─── Cache de downloads ───────────────────────────────────────────────────────
|
|
88
|
+
const DOWNLOAD_CACHE = new Map()
|
|
89
|
+
const MAX_RETRIES = 3
|
|
90
|
+
const RETRY_DELAY_MS = 1000
|
|
91
|
+
|
|
92
|
+
// ─── Helpers internos ─────────────────────────────────────────────────────────
|
|
93
|
+
const getTmpDir = () => tmpdir()
|
|
94
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
|
|
95
|
+
|
|
96
|
+
// ─── Detecta mime type pelo buffer ───────────────────────────────────────────
|
|
97
|
+
const detectMimeType = (buffer) => {
|
|
98
|
+
if (!buffer || buffer.length < 4) return 'application/octet-stream'
|
|
99
|
+
const hex = buffer.slice(0, 4).toString('hex')
|
|
100
|
+
if (hex.startsWith('ffd8ff')) return 'image/jpeg'
|
|
101
|
+
if (hex.startsWith('89504e47')) return 'image/png'
|
|
102
|
+
if (hex.startsWith('47494638')) return 'image/gif'
|
|
103
|
+
if (hex.startsWith('25504446')) return 'application/pdf'
|
|
104
|
+
if (hex.startsWith('494433') || hex.startsWith('fffb')) return 'audio/mpeg'
|
|
105
|
+
if (hex.startsWith('4f676753')) return 'audio/ogg'
|
|
106
|
+
if (hex.startsWith('1a45dfa3')) return 'video/webm'
|
|
107
|
+
if (buffer.slice(4, 8).toString('ascii') === 'ftyp') return 'video/mp4'
|
|
108
|
+
if (hex.startsWith('52494646')) return 'audio/wav'
|
|
109
|
+
if (hex.startsWith('504b0304')) return 'application/zip'
|
|
110
|
+
return 'application/octet-stream'
|
|
111
|
+
}
|
|
112
|
+
exports.detectMimeType = detectMimeType
|
|
113
|
+
|
|
114
|
+
// ─── Limpar cache de downloads ────────────────────────────────────────────────
|
|
115
|
+
const clearDownloadCache = () => DOWNLOAD_CACHE.clear()
|
|
116
|
+
exports.clearDownloadCache = clearDownloadCache
|
|
117
|
+
|
|
118
|
+
// ─── Biblioteca de processamento de imagem ────────────────────────────────────
|
|
119
|
+
const getImageProcessingLibrary = async () => {
|
|
120
|
+
const [_jimp, sharp] = await Promise.all([
|
|
121
|
+
import('jimp').catch(() => {}),
|
|
122
|
+
import('sharp').catch(() => {}),
|
|
123
|
+
])
|
|
124
|
+
if (sharp) return { sharp }
|
|
125
|
+
const jimp = _jimp?.default || _jimp
|
|
126
|
+
if (jimp) return { jimp }
|
|
127
|
+
throw new Boom('Nenhuma biblioteca de imagem disponível')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── Chave HKDF por tipo de mídia ────────────────────────────────────────────
|
|
131
|
+
const hkdfInfoKey = (type) => `WhatsApp ${Config.MEDIA_HKDF_KEY_MAPPING[type]} Keys`
|
|
132
|
+
exports.hkdfInfoKey = hkdfInfoKey
|
|
133
|
+
|
|
134
|
+
// ─── Deriva chaves de criptografia ───────────────────────────────────────────
|
|
135
|
+
async function getMediaKeys(buffer, mediaType) {
|
|
136
|
+
if (!buffer) throw new Boom('Chave de mídia vazia')
|
|
137
|
+
if (typeof buffer === 'string') buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64')
|
|
138
|
+
const expanded = await CryptoUtils.hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) })
|
|
139
|
+
return {
|
|
140
|
+
iv: expanded.slice(0, 16),
|
|
141
|
+
cipherKey: expanded.slice(16, 48),
|
|
142
|
+
macKey: expanded.slice(48, 80),
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Extrai thumbnail de vídeo via ffmpeg ─────────────────────────────────────
|
|
147
|
+
const extractVideoThumb = (path, dest, time, size) => new Promise((resolve, reject) => {
|
|
148
|
+
const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${dest}`
|
|
149
|
+
exec(cmd, (err) => err ? reject(err) : resolve())
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// ─── Extrai thumbnail de imagem ───────────────────────────────────────────────
|
|
153
|
+
const extractImageThumb = async (bufferOrPath, width = 32) => {
|
|
154
|
+
var _a, _b
|
|
155
|
+
if (bufferOrPath instanceof Readable) bufferOrPath = await toBuffer(bufferOrPath)
|
|
156
|
+
const lib = await getImageProcessingLibrary()
|
|
157
|
+
if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
|
|
158
|
+
const img = lib.sharp.default(bufferOrPath)
|
|
159
|
+
const meta = await img.metadata()
|
|
160
|
+
const buf = await img.resize(width).jpeg({ quality: 50 }).toBuffer()
|
|
161
|
+
return { buffer: buf, original: { width: meta.width, height: meta.height } }
|
|
162
|
+
} else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
|
|
163
|
+
const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp
|
|
164
|
+
const jimp = await read(bufferOrPath)
|
|
165
|
+
const buf = await jimp.quality(50).resize(width, AUTO, RESIZE_BILINEAR).getBufferAsync(MIME_JPEG)
|
|
166
|
+
return { buffer: buf, original: { width: jimp.getWidth(), height: jimp.getHeight() } }
|
|
167
|
+
}
|
|
168
|
+
throw new Boom('Nenhuma biblioteca de imagem disponível')
|
|
169
|
+
}
|
|
170
|
+
exports.extractImageThumb = extractImageThumb
|
|
171
|
+
|
|
172
|
+
// ─── Encode base64 URL-safe para upload ──────────────────────────────────────
|
|
173
|
+
const encodeBase64EncodedStringForUpload = (b64) =>
|
|
174
|
+
encodeURIComponent(b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, ''))
|
|
175
|
+
exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload
|
|
176
|
+
|
|
177
|
+
// ─── Gera foto de perfil ──────────────────────────────────────────────────────
|
|
178
|
+
const generateProfilePicture = async (mediaUpload) => {
|
|
179
|
+
const input = Buffer.isBuffer(mediaUpload)
|
|
180
|
+
? mediaUpload
|
|
181
|
+
: typeof mediaUpload === 'object' && 'url' in mediaUpload
|
|
182
|
+
? mediaUpload.url.toString()
|
|
183
|
+
: await toBuffer(mediaUpload.stream)
|
|
184
|
+
const { read, MIME_JPEG, AUTO } = require('jimp')
|
|
185
|
+
const jimp = await read(input)
|
|
186
|
+
const cropped = jimp.crop(0, 0, jimp.getWidth(), jimp.getHeight())
|
|
187
|
+
return { img: await cropped.quality(100).scaleToFit(720, 720, AUTO).getBufferAsync(MIME_JPEG) }
|
|
188
|
+
}
|
|
189
|
+
exports.generateProfilePicture = generateProfilePicture
|
|
190
|
+
|
|
191
|
+
// ─── SHA256 de mensagem de mídia ──────────────────────────────────────────────
|
|
192
|
+
const mediaMessageSHA256B64 = (message) => {
|
|
193
|
+
const media = Object.values(message)[0]
|
|
194
|
+
return media?.fileSha256 && Buffer.from(media.fileSha256).toString('base64')
|
|
195
|
+
}
|
|
196
|
+
exports.mediaMessageSHA256B64 = mediaMessageSHA256B64
|
|
197
|
+
|
|
198
|
+
// ─── Duração de áudio ─────────────────────────────────────────────────────────
|
|
199
|
+
async function getAudioDuration(buffer) {
|
|
200
|
+
const mm = await import('music-metadata')
|
|
201
|
+
if (Buffer.isBuffer(buffer)) return (await mm.parseBuffer(buffer, undefined, { duration: true })).format.duration
|
|
202
|
+
if (typeof buffer === 'string') {
|
|
203
|
+
const rs = createReadStream(buffer)
|
|
204
|
+
try { return (await mm.parseStream(rs, undefined, { duration: true })).format.duration }
|
|
205
|
+
finally { rs.destroy() }
|
|
206
|
+
}
|
|
207
|
+
return (await mm.parseStream(buffer, undefined, { duration: true })).format.duration
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ─── Waveform de áudio ────────────────────────────────────────────────────────
|
|
211
|
+
async function getAudioWaveform(buffer, logger) {
|
|
212
|
+
try {
|
|
213
|
+
const audioDecode = (b) => import('audio-decode').then(({ default: d }) => d(b))
|
|
214
|
+
let data = Buffer.isBuffer(buffer) ? buffer
|
|
215
|
+
: typeof buffer === 'string' ? await toBuffer(createReadStream(buffer))
|
|
216
|
+
: await toBuffer(buffer)
|
|
217
|
+
const decoded = await audioDecode(data)
|
|
218
|
+
const raw = decoded.getChannelData(0)
|
|
219
|
+
const samples = 64
|
|
220
|
+
const blockSize = Math.floor(raw.length / samples)
|
|
221
|
+
const filtered = Array.from({ length: samples }, (_, i) => {
|
|
222
|
+
let sum = 0
|
|
223
|
+
for (let j = 0; j < blockSize; j++) sum += Math.abs(raw[i * blockSize + j])
|
|
224
|
+
return sum / blockSize
|
|
225
|
+
})
|
|
226
|
+
const mult = Math.pow(Math.max(...filtered), -1)
|
|
227
|
+
return new Uint8Array(filtered.map(n => Math.floor(100 * n * mult)))
|
|
228
|
+
} catch (e) {
|
|
229
|
+
logger?.debug('Falha ao gerar waveform: ' + e)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ─── Buffer / Stream helpers ──────────────────────────────────────────────────
|
|
234
|
+
const toReadable = (buffer) => {
|
|
235
|
+
const r = new Readable({ read: () => {} })
|
|
236
|
+
r.push(buffer)
|
|
237
|
+
r.push(null)
|
|
238
|
+
return r
|
|
239
|
+
}
|
|
240
|
+
exports.toReadable = toReadable
|
|
241
|
+
|
|
242
|
+
const toBuffer = async (stream) => {
|
|
243
|
+
const chunks = []
|
|
244
|
+
for await (const chunk of stream) chunks.push(chunk)
|
|
245
|
+
stream.destroy()
|
|
246
|
+
return Buffer.concat(chunks)
|
|
247
|
+
}
|
|
248
|
+
exports.toBuffer = toBuffer
|
|
249
|
+
|
|
250
|
+
const getStream = async (item, opts) => {
|
|
251
|
+
if (Buffer.isBuffer(item)) return { stream: toReadable(item), type: 'buffer' }
|
|
252
|
+
if ('stream' in item) return { stream: item.stream, type: 'readable' }
|
|
253
|
+
const url = item.url.toString()
|
|
254
|
+
if (url.startsWith('http://') || url.startsWith('https://'))
|
|
255
|
+
return { stream: await getHttpStream(item.url, opts), type: 'remote' }
|
|
256
|
+
return { stream: createReadStream(item.url), type: 'file' }
|
|
257
|
+
}
|
|
258
|
+
exports.getStream = getStream
|
|
259
|
+
|
|
260
|
+
// ─── Gera thumbnail ───────────────────────────────────────────────────────────
|
|
261
|
+
async function generateThumbnail(file, mediaType, options) {
|
|
262
|
+
var _a
|
|
263
|
+
let thumbnail, originalImageDimensions
|
|
264
|
+
if (mediaType === 'image') {
|
|
265
|
+
const { buffer, original } = await extractImageThumb(file)
|
|
266
|
+
thumbnail = buffer.toString('base64')
|
|
267
|
+
if (original.width && original.height) originalImageDimensions = original
|
|
268
|
+
} else if (mediaType === 'video') {
|
|
269
|
+
const imgPath = join(getTmpDir(), Helpers.generateMessageID() + '.jpg')
|
|
270
|
+
try {
|
|
271
|
+
await extractVideoThumb(file, imgPath, '00:00:00', { width: 32, height: 32 })
|
|
272
|
+
thumbnail = (await fsp.readFile(imgPath)).toString('base64')
|
|
273
|
+
await fsp.unlink(imgPath)
|
|
274
|
+
} catch (err) {
|
|
275
|
+
(_a = options.logger) === null || _a === void 0 ? void 0 : _a.debug('Falha ao gerar thumbnail de vídeo: ' + err)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { thumbnail, originalImageDimensions }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ─── HTTP Stream ──────────────────────────────────────────────────────────────
|
|
282
|
+
const getHttpStream = async (url, options = {}) => {
|
|
283
|
+
const { default: axios } = await import('axios')
|
|
284
|
+
const res = await axios.get(url.toString(), { ...options, responseType: 'stream' })
|
|
285
|
+
return res.data
|
|
286
|
+
}
|
|
287
|
+
exports.getHttpStream = getHttpStream
|
|
288
|
+
|
|
289
|
+
// ─── Prepara stream (newsletter) ──────────────────────────────────────────────
|
|
290
|
+
const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
|
|
291
|
+
const { stream, type } = await getStream(media, opts)
|
|
292
|
+
logger?.debug('stream de mídia obtido')
|
|
293
|
+
let bodyPath, didSaveToTmpPath = false
|
|
294
|
+
try {
|
|
295
|
+
const buffer = await toBuffer(stream)
|
|
296
|
+
if (type === 'file') { bodyPath = media.url }
|
|
297
|
+
else if (saveOriginalFileIfRequired) {
|
|
298
|
+
bodyPath = join(getTmpDir(), mediaType + Helpers.generateMessageID())
|
|
299
|
+
writeFileSync(bodyPath, buffer)
|
|
300
|
+
didSaveToTmpPath = true
|
|
301
|
+
}
|
|
302
|
+
const fileSha256 = Crypto.createHash('sha256').update(buffer).digest()
|
|
303
|
+
stream?.destroy()
|
|
304
|
+
return { mediaKey: undefined, encWriteStream: buffer, fileLength: buffer.length, fileSha256, fileEncSha256: undefined, bodyPath, didSaveToTmpPath }
|
|
305
|
+
} catch (error) {
|
|
306
|
+
stream.destroy()
|
|
307
|
+
if (didSaveToTmpPath) { try { await fsp.unlink(bodyPath) } catch (e) { logger?.error({ e }, 'falha ao remover tmp') } }
|
|
308
|
+
throw error
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
exports.prepareStream = prepareStream
|
|
312
|
+
|
|
313
|
+
// ─── Stream encriptado ────────────────────────────────────────────────────────
|
|
314
|
+
const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
|
|
315
|
+
const { stream, type } = await getStream(media, opts)
|
|
316
|
+
logger?.debug('stream de mídia obtido')
|
|
317
|
+
const mediaKey = Crypto.randomBytes(32)
|
|
318
|
+
const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType)
|
|
319
|
+
const encWriteStream = new Readable({ read: () => {} })
|
|
320
|
+
let bodyPath, writeStream, didSaveToTmpPath = false
|
|
321
|
+
if (type === 'file') { bodyPath = media.url }
|
|
322
|
+
else if (saveOriginalFileIfRequired) {
|
|
323
|
+
bodyPath = join(getTmpDir(), mediaType + Helpers.generateMessageID())
|
|
324
|
+
writeStream = createWriteStream(bodyPath)
|
|
325
|
+
didSaveToTmpPath = true
|
|
326
|
+
}
|
|
327
|
+
let fileLength = 0
|
|
328
|
+
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
|
|
329
|
+
let hmac = Crypto.createHmac('sha256', macKey).update(iv)
|
|
330
|
+
let sha256Plain = Crypto.createHash('sha256')
|
|
331
|
+
let sha256Enc = Crypto.createHash('sha256')
|
|
332
|
+
|
|
333
|
+
function onChunk(buff) {
|
|
334
|
+
sha256Enc = sha256Enc.update(buff)
|
|
335
|
+
hmac = hmac.update(buff)
|
|
336
|
+
encWriteStream.push(buff)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
for await (const data of stream) {
|
|
341
|
+
fileLength += data.length
|
|
342
|
+
if (type === 'remote' && opts?.maxContentLength && fileLength + data.length > opts.maxContentLength)
|
|
343
|
+
throw new Boom(`conteúdo excedeu o limite para "${type}"`, { data: { media, type } })
|
|
344
|
+
sha256Plain = sha256Plain.update(data)
|
|
345
|
+
if (writeStream) { if (!writeStream.write(data)) await once(writeStream, 'drain') }
|
|
346
|
+
onChunk(aes.update(data))
|
|
347
|
+
}
|
|
348
|
+
onChunk(aes.final())
|
|
349
|
+
const mac = hmac.digest().slice(0, 10)
|
|
350
|
+
sha256Enc = sha256Enc.update(mac)
|
|
351
|
+
const fileSha256 = sha256Plain.digest()
|
|
352
|
+
const fileEncSha256 = sha256Enc.digest()
|
|
353
|
+
encWriteStream.push(mac)
|
|
354
|
+
encWriteStream.push(null)
|
|
355
|
+
writeStream?.end()
|
|
356
|
+
stream.destroy()
|
|
357
|
+
logger?.debug('dados encriptados com sucesso')
|
|
358
|
+
return { mediaKey, encWriteStream, bodyPath, mac, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath }
|
|
359
|
+
} catch (error) {
|
|
360
|
+
encWriteStream.destroy(); writeStream?.destroy()
|
|
361
|
+
aes.destroy(); hmac.destroy(); sha256Plain.destroy(); sha256Enc.destroy(); stream.destroy()
|
|
362
|
+
if (didSaveToTmpPath) { try { await fsp.unlink(bodyPath) } catch (e) { logger?.error({ e }, 'falha ao remover tmp') } }
|
|
363
|
+
throw error
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
exports.encryptedStream = encryptedStream
|
|
367
|
+
|
|
368
|
+
// ─── URL de download ──────────────────────────────────────────────────────────
|
|
369
|
+
const DEF_HOST = 'mmg.whatsapp.net'
|
|
370
|
+
const AES_CHUNK_SIZE = 16
|
|
371
|
+
const toSmallestChunk = (num) => Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE
|
|
372
|
+
const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`
|
|
373
|
+
exports.getUrlFromDirectPath = getUrlFromDirectPath
|
|
374
|
+
|
|
375
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
376
|
+
// downloadContentFromMessage — original
|
|
377
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
378
|
+
const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
|
|
379
|
+
const downloadUrl = url || getUrlFromDirectPath(directPath)
|
|
380
|
+
const keys = await getMediaKeys(mediaKey, type)
|
|
381
|
+
return downloadEncryptedContent(downloadUrl, keys, opts)
|
|
382
|
+
}
|
|
383
|
+
exports.downloadContentFromMessage = downloadContentFromMessage
|
|
384
|
+
|
|
385
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
386
|
+
// downloadContentFromMessageV1 — versão melhorada
|
|
387
|
+
// Melhorias: retry automático, progresso, cache, timeout, mime type, buffer, arquivo
|
|
388
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
389
|
+
const downloadContentFromMessageV1 = async (
|
|
390
|
+
{ mediaKey, directPath, url },
|
|
391
|
+
type,
|
|
392
|
+
opts = {}
|
|
393
|
+
) => {
|
|
394
|
+
const {
|
|
395
|
+
onProgress = null, // ({ downloaded, total, percent }) => void
|
|
396
|
+
useCache = false, // cache em memória
|
|
397
|
+
saveToFile = null, // salvar direto em arquivo
|
|
398
|
+
timeoutMs = 60_000, // timeout em ms
|
|
399
|
+
retries = MAX_RETRIES,
|
|
400
|
+
asBuffer = false, // retornar buffer em vez de stream
|
|
401
|
+
} = opts
|
|
402
|
+
|
|
403
|
+
const downloadUrl = url || getUrlFromDirectPath(directPath)
|
|
404
|
+
|
|
405
|
+
// ── Cache ────────────────────────────────────────────────────────────────
|
|
406
|
+
if (useCache && DOWNLOAD_CACHE.has(downloadUrl)) {
|
|
407
|
+
const cached = DOWNLOAD_CACHE.get(downloadUrl)
|
|
408
|
+
return asBuffer ? cached : toReadable(cached)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const keys = await getMediaKeys(mediaKey, type)
|
|
412
|
+
|
|
413
|
+
// ── Retry automático ──────────────────────────────────────────────────────
|
|
414
|
+
let lastError
|
|
415
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
416
|
+
try {
|
|
417
|
+
const result = await _executeDownload({ downloadUrl, keys, opts, onProgress, saveToFile, timeoutMs, asBuffer })
|
|
418
|
+
if (useCache && Buffer.isBuffer(result)) DOWNLOAD_CACHE.set(downloadUrl, result)
|
|
419
|
+
return result
|
|
420
|
+
} catch (err) {
|
|
421
|
+
lastError = err
|
|
422
|
+
if (attempt < retries) {
|
|
423
|
+
const delay = RETRY_DELAY_MS * attempt
|
|
424
|
+
console.warn(`[downloadV1] Tentativa ${attempt}/${retries} falhou. Retentando em ${delay}ms...`)
|
|
425
|
+
await sleep(delay)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
throw new Error(`[downloadV1] Falhou após ${retries} tentativas: ${lastError?.message}`)
|
|
431
|
+
}
|
|
432
|
+
exports.downloadContentFromMessageV1 = downloadContentFromMessageV1
|
|
433
|
+
|
|
434
|
+
// ─── Executa download com progresso/timeout ───────────────────────────────────
|
|
435
|
+
const _executeDownload = async ({ downloadUrl, keys, opts, onProgress, saveToFile, timeoutMs, asBuffer }) => {
|
|
436
|
+
const controller = new AbortController()
|
|
437
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
438
|
+
let fetchedStream
|
|
439
|
+
try {
|
|
440
|
+
fetchedStream = await downloadEncryptedContent(downloadUrl, keys, {
|
|
441
|
+
...opts,
|
|
442
|
+
options: { ...(opts.options || {}), signal: controller.signal }
|
|
443
|
+
})
|
|
444
|
+
} finally {
|
|
445
|
+
clearTimeout(timer)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (!onProgress && !saveToFile && !asBuffer) return fetchedStream
|
|
449
|
+
|
|
450
|
+
return new Promise((resolve, reject) => {
|
|
451
|
+
const chunks = []
|
|
452
|
+
let downloaded = 0
|
|
453
|
+
const total = parseInt(fetchedStream.headers?.['content-length'] || '0', 10)
|
|
454
|
+
|
|
455
|
+
fetchedStream.on('data', (chunk) => {
|
|
456
|
+
chunks.push(chunk)
|
|
457
|
+
downloaded += chunk.length
|
|
458
|
+
if (onProgress) onProgress({
|
|
459
|
+
downloaded,
|
|
460
|
+
total,
|
|
461
|
+
percent: total > 0 ? Math.round((downloaded / total) * 100) : null,
|
|
462
|
+
})
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
fetchedStream.on('end', async () => {
|
|
466
|
+
const buffer = Buffer.concat(chunks)
|
|
467
|
+
if (saveToFile) {
|
|
468
|
+
try { await fsp.writeFile(saveToFile, buffer); resolve(saveToFile) }
|
|
469
|
+
catch (err) { reject(err) }
|
|
470
|
+
return
|
|
471
|
+
}
|
|
472
|
+
resolve(asBuffer ? buffer : toReadable(buffer))
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
fetchedStream.on('error', reject)
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
480
|
+
// downloadEncryptedContent — desencripta stream AES256-CBC
|
|
481
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
482
|
+
const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
483
|
+
let bytesFetched = 0, startChunk = 0, firstBlockIsIV = false
|
|
484
|
+
if (startByte) {
|
|
485
|
+
const chunk = toSmallestChunk(startByte || 0)
|
|
486
|
+
if (chunk) { startChunk = chunk - AES_CHUNK_SIZE; bytesFetched = chunk; firstBlockIsIV = true }
|
|
487
|
+
}
|
|
488
|
+
const endChunk = endByte ? toSmallestChunk(endByte || 0) + AES_CHUNK_SIZE : undefined
|
|
489
|
+
const headers = { ...(options?.headers || {}), Origin: Config.DEFAULT_ORIGIN }
|
|
490
|
+
if (startChunk || endChunk) {
|
|
491
|
+
headers.Range = `bytes=${startChunk}-`
|
|
492
|
+
if (endChunk) headers.Range += endChunk
|
|
493
|
+
}
|
|
494
|
+
const fetched = await getHttpStream(downloadUrl, { ...options || {}, headers, maxBodyLength: Infinity, maxContentLength: Infinity })
|
|
495
|
+
let remainingBytes = Buffer.from([])
|
|
496
|
+
let aes
|
|
497
|
+
|
|
498
|
+
const pushBytes = (bytes, push) => {
|
|
499
|
+
if (startByte || endByte) {
|
|
500
|
+
const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0)
|
|
501
|
+
const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0)
|
|
502
|
+
push(bytes.slice(start, end))
|
|
503
|
+
bytesFetched += bytes.length
|
|
504
|
+
} else {
|
|
505
|
+
push(bytes)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const output = new Transform({
|
|
510
|
+
transform(chunk, _, callback) {
|
|
511
|
+
let data = Buffer.concat([remainingBytes, chunk])
|
|
512
|
+
const decryptLen = toSmallestChunk(data.length)
|
|
513
|
+
remainingBytes = data.slice(decryptLen)
|
|
514
|
+
data = data.slice(0, decryptLen)
|
|
515
|
+
if (!aes) {
|
|
516
|
+
let ivValue = iv
|
|
517
|
+
if (firstBlockIsIV) { ivValue = data.slice(0, AES_CHUNK_SIZE); data = data.slice(AES_CHUNK_SIZE) }
|
|
518
|
+
aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue)
|
|
519
|
+
if (endByte) aes.setAutoPadding(false)
|
|
520
|
+
}
|
|
521
|
+
try { pushBytes(aes.update(data), b => this.push(b)); callback() }
|
|
522
|
+
catch (err) { callback(err) }
|
|
523
|
+
},
|
|
524
|
+
final(callback) {
|
|
525
|
+
try { pushBytes(aes.final(), b => this.push(b)); callback() }
|
|
526
|
+
catch (err) { callback(err) }
|
|
527
|
+
}
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
return fetched.pipe(output, { end: true })
|
|
531
|
+
}
|
|
532
|
+
exports.downloadEncryptedContent = downloadEncryptedContent
|
|
533
|
+
|
|
534
|
+
// ─── Extensão por tipo de mensagem ───────────────────────────────────────────
|
|
535
|
+
function extensionForMediaMessage(message) {
|
|
536
|
+
const type = Object.keys(message)[0]
|
|
537
|
+
if (['locationMessage', 'liveLocationMessage', 'productMessage'].includes(type)) return '.jpeg'
|
|
538
|
+
return '.' + message[type].mimetype.split(';')[0].split('/')[1]
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ─── Upload para servidor WA ──────────────────────────────────────────────────
|
|
542
|
+
const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
|
|
543
|
+
return async (stream, { mediaType, fileEncSha256B64, newsletter, timeoutMs }) => {
|
|
544
|
+
var _a, _b
|
|
545
|
+
const { default: axios } = await import('axios')
|
|
546
|
+
let uploadInfo = await refreshMediaConn(false)
|
|
547
|
+
let urls
|
|
548
|
+
const hosts = [...customUploadHosts, ...uploadInfo.hosts]
|
|
549
|
+
const chunks = []
|
|
550
|
+
if (!Buffer.isBuffer(stream)) { for await (const c of stream) chunks.push(c) }
|
|
551
|
+
const body = Buffer.isBuffer(stream) ? stream : Buffer.concat(chunks)
|
|
552
|
+
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
|
|
553
|
+
let media = Config.MEDIA_PATH_MAP[mediaType]
|
|
554
|
+
if (newsletter) media = media?.replace('/mms/', '/newsletter/newsletter-')
|
|
555
|
+
|
|
556
|
+
for (const { hostname, maxContentLengthBytes } of hosts) {
|
|
557
|
+
logger.debug(`enviando para "${hostname}"`)
|
|
558
|
+
const auth = encodeURIComponent(uploadInfo.auth)
|
|
559
|
+
const url = `https://${hostname}${media}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
|
|
560
|
+
let result
|
|
561
|
+
try {
|
|
562
|
+
if (maxContentLengthBytes && body.length > maxContentLengthBytes)
|
|
563
|
+
throw new Boom(`Body muito grande para "${hostname}"`, { statusCode: 413 })
|
|
564
|
+
const res = await axios.post(url, body, {
|
|
565
|
+
...options,
|
|
566
|
+
headers: { ...options.headers || {}, 'Content-Type': 'application/octet-stream', 'Origin': Config.DEFAULT_ORIGIN },
|
|
567
|
+
httpsAgent: fetchAgent, timeout: timeoutMs, responseType: 'json',
|
|
568
|
+
maxBodyLength: Infinity, maxContentLength: Infinity,
|
|
569
|
+
})
|
|
570
|
+
result = res.data
|
|
571
|
+
if (result?.url || result?.directPath) {
|
|
572
|
+
urls = { mediaUrl: result.url, directPath: result.direct_path, handle: result.handle }
|
|
573
|
+
break
|
|
574
|
+
} else {
|
|
575
|
+
uploadInfo = await refreshMediaConn(true)
|
|
576
|
+
throw new Error(`upload falhou: ${JSON.stringify(result)}`)
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
if (axios.isAxiosError(error)) result = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data
|
|
580
|
+
const isLast = hostname === ((_b = hosts[uploadInfo.hosts.length - 1]) === null || _b === void 0 ? void 0 : _b.hostname)
|
|
581
|
+
logger.warn({ trace: error.stack, uploadResult: result }, `Erro ao enviar para ${hostname}${isLast ? '' : ', tentando próximo...'}`)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (!urls) throw new Boom('Upload de mídia falhou em todos os hosts', { statusCode: 500 })
|
|
585
|
+
return urls
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
exports.getWAUploadToServer = getWAUploadToServer
|
|
589
|
+
|
|
590
|
+
// ─── Retry de mídia ───────────────────────────────────────────────────────────
|
|
591
|
+
const getMediaRetryKey = (mediaKey) => CryptoUtils.hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' })
|
|
592
|
+
|
|
593
|
+
const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
594
|
+
const recp = Proto.proto.ServerErrorReceipt.encode({ stanzaId: key.id }).finish()
|
|
595
|
+
const iv = Crypto.randomBytes(12)
|
|
596
|
+
const retryKey = await getMediaRetryKey(mediaKey)
|
|
597
|
+
const ciphertext = CryptoUtils.aesEncryptGCM(recp, retryKey, iv, Buffer.from(key.id))
|
|
598
|
+
return {
|
|
599
|
+
tag: 'receipt',
|
|
600
|
+
attrs: { id: key.id, to: JidUtils.jidNormalizedUser(meId), type: 'server-error' },
|
|
601
|
+
content: [
|
|
602
|
+
{ tag: 'encrypt', attrs: {}, content: [
|
|
603
|
+
{ tag: 'enc_p', attrs: {}, content: ciphertext },
|
|
604
|
+
{ tag: 'enc_iv', attrs: {}, content: iv }
|
|
605
|
+
]},
|
|
606
|
+
{ tag: 'rmr', attrs: { jid: key.remoteJid, 'from_me': (!!key.fromMe).toString(), participant: key.participant || undefined } }
|
|
607
|
+
]
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
exports.encryptMediaRetryRequest = encryptMediaRetryRequest
|
|
611
|
+
|
|
612
|
+
const decodeMediaRetryNode = (node) => {
|
|
613
|
+
const rmr = JidUtils.getBinaryNodeChild(node, 'rmr')
|
|
614
|
+
const event = { key: { id: node.attrs.id, remoteJid: rmr.attrs.jid, fromMe: rmr.attrs.from_me === 'true', participant: rmr.attrs.participant } }
|
|
615
|
+
const err = JidUtils.getBinaryNodeChild(node, 'error')
|
|
616
|
+
if (err) {
|
|
617
|
+
const code = +err.attrs.code
|
|
618
|
+
event.error = new Boom(`Falha ao reenviar mídia (${code})`, { data: err.attrs, statusCode: getStatusCodeForMediaRetry(code) })
|
|
619
|
+
} else {
|
|
620
|
+
const enc = JidUtils.getBinaryNodeChild(node, 'encrypt')
|
|
621
|
+
const ciphertext = JidUtils.getBinaryNodeChildBuffer(enc, 'enc_p')
|
|
622
|
+
const iv = JidUtils.getBinaryNodeChildBuffer(enc, 'enc_iv')
|
|
623
|
+
event.media = (ciphertext && iv) ? { ciphertext, iv } : undefined
|
|
624
|
+
if (!event.media) event.error = new Boom('Falha ao reenviar mídia (sem ciphertext)', { statusCode: 404 })
|
|
625
|
+
}
|
|
626
|
+
return event
|
|
627
|
+
}
|
|
628
|
+
exports.decodeMediaRetryNode = decodeMediaRetryNode
|
|
629
|
+
|
|
630
|
+
const decryptMediaRetryData = async ({ ciphertext, iv }, mediaKey, msgId) => {
|
|
631
|
+
const retryKey = await getMediaRetryKey(mediaKey)
|
|
632
|
+
return Proto.proto.MediaRetryNotification.decode(CryptoUtils.aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId)))
|
|
633
|
+
}
|
|
634
|
+
exports.decryptMediaRetryData = decryptMediaRetryData
|
|
635
|
+
|
|
636
|
+
const MEDIA_RETRY_STATUS_MAP = {
|
|
637
|
+
[Proto.proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
|
|
638
|
+
[Proto.proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
|
|
639
|
+
[Proto.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
640
|
+
[Proto.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
|
|
641
|
+
}
|
|
642
|
+
const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code]
|
|
643
|
+
exports.getStatusCodeForMediaRetry = getStatusCodeForMediaRetry
|