zapo-js 1.5.1 → 1.6.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/dist/client/WaClient.d.ts +1 -1
- package/dist/client/WaClient.js +1 -1
- package/dist/client/WaClientFactory.js +1 -0
- package/dist/client/coordinators/WaMessageCoordinator.d.ts +69 -1
- package/dist/client/coordinators/WaMessageCoordinator.js +76 -1
- package/dist/client/media.d.ts +1 -0
- package/dist/client/media.js +1 -0
- package/dist/client/messaging/messages.d.ts +45 -1
- package/dist/client/messaging/messages.js +57 -45
- package/dist/esm/client/WaClient.js +1 -1
- package/dist/esm/client/WaClientFactory.js +1 -0
- package/dist/esm/client/coordinators/WaMessageCoordinator.js +78 -3
- package/dist/esm/client/media.js +1 -1
- package/dist/esm/client/messaging/messages.js +56 -45
- package/dist/esm/index.js +2 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +6 -2
- package/package.json +1 -1
|
@@ -131,7 +131,7 @@ declare class WaClientImpl extends EventEmitter {
|
|
|
131
131
|
ignoreKey(input: WaIgnoreKey | WaIgnoreKeyPredicate): () => void;
|
|
132
132
|
/** Auth client: pairing, credentials, registration state. */
|
|
133
133
|
get auth(): WaAuthClient;
|
|
134
|
-
/** Message coordinator: send/receive, receipts, addons, media download. */
|
|
134
|
+
/** Message coordinator: send/receive, receipts, addons, media upload/download. */
|
|
135
135
|
get message(): WaMessageCoordinator;
|
|
136
136
|
/** Presence coordinator: own/peer presence subscriptions. */
|
|
137
137
|
get presence(): WaPresenceCoordinator;
|
package/dist/client/WaClient.js
CHANGED
|
@@ -414,7 +414,7 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
414
414
|
get auth() {
|
|
415
415
|
return this.deps.authClient;
|
|
416
416
|
}
|
|
417
|
-
/** Message coordinator: send/receive, receipts, addons, media download. */
|
|
417
|
+
/** Message coordinator: send/receive, receipts, addons, media upload/download. */
|
|
418
418
|
get message() {
|
|
419
419
|
return this.deps.messageCoordinator;
|
|
420
420
|
}
|
|
@@ -504,6 +504,7 @@ function buildWaClientDependencies(input) {
|
|
|
504
504
|
const messageCoordinator = new WaMessageCoordinator_1.WaMessageCoordinator({
|
|
505
505
|
messageDispatch,
|
|
506
506
|
mediaTransfer,
|
|
507
|
+
mediaUploadOptions: mediaMessageBuildOptions,
|
|
507
508
|
logger,
|
|
508
509
|
messageStore: sessionStore.messages,
|
|
509
510
|
messageSecretStore: sessionStore.messageSecret,
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { Readable } from 'node:stream';
|
|
2
2
|
import type { WaMessageDispatchCoordinator } from '../coordinators/WaMessageDispatchCoordinator';
|
|
3
3
|
import type { WaTrustedContactTokenCoordinator } from '../coordinators/WaTrustedContactTokenCoordinator';
|
|
4
|
+
import { type WaUploadMediaSource } from '../media';
|
|
5
|
+
import { type UploadResult, type WaMediaMessageOptions } from '../messaging/messages';
|
|
4
6
|
import type { WaDownloadMediaOptions, WaIncomingAddonEvent, WaIncomingMessageEvent, WaSendMessageOptions } from '../types';
|
|
5
7
|
import type { Logger } from '../../infra/log/types';
|
|
6
8
|
import type { WaMediaTransferClient } from '../../media/transfer/WaMediaTransferClient';
|
|
9
|
+
import type { MediaKind } from '../../media/types';
|
|
7
10
|
import type { PeerDataOperationRequester } from '../../message/primitives/peer-data-operation';
|
|
8
11
|
import type { WaMessagePublishResult, WaSendMessageContent, WaSendReceiptEventOptions, WaSendReceiptOptions } from '../../message/types';
|
|
9
12
|
import { type Proto } from '../../proto';
|
|
@@ -13,6 +16,8 @@ import { type WaMexQuerySocket } from '../../transport/node/mex/client';
|
|
|
13
16
|
export interface WaMessageCoordinatorDeps {
|
|
14
17
|
readonly messageDispatch: WaMessageDispatchCoordinator;
|
|
15
18
|
readonly mediaTransfer: WaMediaTransferClient;
|
|
19
|
+
/** Media upload wiring shared with the send path; backs {@link WaMessageCoordinator.upload}. */
|
|
20
|
+
readonly mediaUploadOptions: WaMediaMessageOptions;
|
|
16
21
|
readonly logger: Logger;
|
|
17
22
|
readonly messageStore: WaMessageStore;
|
|
18
23
|
readonly messageSecretStore: WaMessageSecretStore;
|
|
@@ -58,14 +63,47 @@ export interface WaMessageCappingInfo {
|
|
|
58
63
|
readonly mvStatus: string | null;
|
|
59
64
|
readonly cappingStatus: string | null;
|
|
60
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Media kinds accepted by {@link WaMessageCoordinator.upload}. `gif` is a
|
|
68
|
+
* GIF-playback video, `ptt` a voice note, `ptv` a round video note.
|
|
69
|
+
*/
|
|
70
|
+
export type WaUploadMediaType = MediaKind | 'gif' | 'ptt';
|
|
71
|
+
/** Options for {@link WaMessageCoordinator.upload}. */
|
|
72
|
+
export interface WaUploadMediaOptions {
|
|
73
|
+
/** Media type: sets the encryption context and CDN upload path. */
|
|
74
|
+
readonly type: WaUploadMediaType;
|
|
75
|
+
/** `Content-Type` for the upload and the mimetype for the message proto. */
|
|
76
|
+
readonly mimetype?: string;
|
|
77
|
+
/** Reuse a 32-byte media key instead of generating one. */
|
|
78
|
+
readonly mediaKey?: Uint8Array;
|
|
79
|
+
/** Override the streaming sidecar (default on for video/ptv/audio/gif/ptt). */
|
|
80
|
+
readonly sidecar?: boolean;
|
|
81
|
+
/** Animated-sticker first-frame length for the first-frame sidecar. */
|
|
82
|
+
readonly firstFrameLength?: number;
|
|
83
|
+
/** Per-upload transfer timeout override (ms). */
|
|
84
|
+
readonly timeoutMs?: number;
|
|
85
|
+
/** Cancellation signal forwarded to the CDN request. */
|
|
86
|
+
readonly signal?: AbortSignal;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Reusable descriptor returned by {@link WaMessageCoordinator.upload}: the
|
|
90
|
+
* {@link UploadResult} fields plus the media-key timestamp and echoed mimetype.
|
|
91
|
+
*/
|
|
92
|
+
export interface WaMediaUploadResult extends UploadResult {
|
|
93
|
+
/** Unix seconds the media key was minted; belongs on the message proto. */
|
|
94
|
+
readonly mediaKeyTimestamp: number;
|
|
95
|
+
/** Echo of the `mimetype` option when provided. */
|
|
96
|
+
readonly mimetype?: string;
|
|
97
|
+
}
|
|
61
98
|
/**
|
|
62
99
|
* Coordinates outbound message sending, receipts, addon decryption, media
|
|
63
|
-
* download, and the related MEX account queries. Accessed via
|
|
100
|
+
* upload/download, and the related MEX account queries. Accessed via
|
|
64
101
|
* {@link WaClient.message}.
|
|
65
102
|
*/
|
|
66
103
|
export declare class WaMessageCoordinator {
|
|
67
104
|
private readonly messageDispatch;
|
|
68
105
|
private readonly mediaTransfer;
|
|
106
|
+
private readonly mediaUploadOptions;
|
|
69
107
|
private readonly logger;
|
|
70
108
|
private readonly messageStore;
|
|
71
109
|
private readonly messageSecretStore;
|
|
@@ -183,6 +221,36 @@ export declare class WaMessageCoordinator {
|
|
|
183
221
|
*/
|
|
184
222
|
sendReceipt(target: WaIncomingMessageEvent | readonly WaIncomingMessageEvent[], options?: WaSendReceiptEventOptions): Promise<void>;
|
|
185
223
|
sendReceipt(jid: string, ids: string | readonly string[], options?: WaSendReceiptOptions): Promise<void>;
|
|
224
|
+
/**
|
|
225
|
+
* Encrypts and uploads standalone media to the WhatsApp CDN and returns the
|
|
226
|
+
* reusable descriptor, without sending a message - pre-upload once and
|
|
227
|
+
* reference it across sends, or build custom protos. Needs a connected
|
|
228
|
+
* session (the host token comes from a `media_conn` IQ). `source` is bytes,
|
|
229
|
+
* a file path, or a `Readable`. To send the result, spread its fields onto
|
|
230
|
+
* the matching proto message and pass that to {@link send}.
|
|
231
|
+
*
|
|
232
|
+
* @throws when `source` is unsupported, the file is unreadable, or the upload fails.
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const media = await client.message.upload(await readFile('photo.jpg'), {
|
|
236
|
+
* type: 'image',
|
|
237
|
+
* mimetype: 'image/jpeg'
|
|
238
|
+
* })
|
|
239
|
+
* await client.message.send(jid, {
|
|
240
|
+
* imageMessage: {
|
|
241
|
+
* url: media.url,
|
|
242
|
+
* directPath: media.directPath,
|
|
243
|
+
* mediaKey: media.mediaKey,
|
|
244
|
+
* fileSha256: media.fileSha256,
|
|
245
|
+
* fileEncSha256: media.fileEncSha256,
|
|
246
|
+
* fileLength: media.fileLength,
|
|
247
|
+
* mediaKeyTimestamp: media.mediaKeyTimestamp,
|
|
248
|
+
* mimetype: media.mimetype
|
|
249
|
+
* }
|
|
250
|
+
* })
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
upload(source: WaUploadMediaSource, options: WaUploadMediaOptions): Promise<WaMediaUploadResult>;
|
|
186
254
|
/**
|
|
187
255
|
* Resolves the media payload inside `source` and returns a `Readable`
|
|
188
256
|
* stream of the decrypted bytes. Throws when the message has no
|
|
@@ -5,6 +5,8 @@ const node_fs_1 = require("node:fs");
|
|
|
5
5
|
const promises_1 = require("node:stream/promises");
|
|
6
6
|
const receipt_1 = require("../events/receipt");
|
|
7
7
|
const media_1 = require("../media");
|
|
8
|
+
const messages_1 = require("../messaging/messages");
|
|
9
|
+
const constants_1 = require("../../media/constants");
|
|
8
10
|
const addon_crypto_1 = require("../../message/crypto/addon-crypto");
|
|
9
11
|
const content_1 = require("../../message/encode/content");
|
|
10
12
|
const _proto_1 = require("../../proto");
|
|
@@ -34,15 +36,36 @@ function parseMessageCappingMexResponse(data) {
|
|
|
34
36
|
cappingStatus: (0, coercion_1.tryAsString)(root?.capping_status)
|
|
35
37
|
};
|
|
36
38
|
}
|
|
39
|
+
const SIDECAR_UPLOAD_TYPES = new Set([
|
|
40
|
+
'video',
|
|
41
|
+
'ptv',
|
|
42
|
+
'audio',
|
|
43
|
+
'gif',
|
|
44
|
+
'ptt'
|
|
45
|
+
]);
|
|
46
|
+
async function normalizeUploadSource(source) {
|
|
47
|
+
if (source instanceof Uint8Array) {
|
|
48
|
+
return source;
|
|
49
|
+
}
|
|
50
|
+
if (typeof source === 'string') {
|
|
51
|
+
await (0, media_1.assertReadableFile)(source);
|
|
52
|
+
return (0, node_fs_1.createReadStream)(source);
|
|
53
|
+
}
|
|
54
|
+
if ((0, media_1.isReadableStream)(source)) {
|
|
55
|
+
return source;
|
|
56
|
+
}
|
|
57
|
+
throw new Error('media upload received unsupported source type');
|
|
58
|
+
}
|
|
37
59
|
/**
|
|
38
60
|
* Coordinates outbound message sending, receipts, addon decryption, media
|
|
39
|
-
* download, and the related MEX account queries. Accessed via
|
|
61
|
+
* upload/download, and the related MEX account queries. Accessed via
|
|
40
62
|
* {@link WaClient.message}.
|
|
41
63
|
*/
|
|
42
64
|
class WaMessageCoordinator {
|
|
43
65
|
constructor(deps) {
|
|
44
66
|
this.messageDispatch = deps.messageDispatch;
|
|
45
67
|
this.mediaTransfer = deps.mediaTransfer;
|
|
68
|
+
this.mediaUploadOptions = deps.mediaUploadOptions;
|
|
46
69
|
this.logger = deps.logger;
|
|
47
70
|
this.messageStore = deps.messageStore;
|
|
48
71
|
this.messageSecretStore = deps.messageSecretStore;
|
|
@@ -222,6 +245,58 @@ class WaMessageCoordinator {
|
|
|
222
245
|
});
|
|
223
246
|
}
|
|
224
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Encrypts and uploads standalone media to the WhatsApp CDN and returns the
|
|
250
|
+
* reusable descriptor, without sending a message - pre-upload once and
|
|
251
|
+
* reference it across sends, or build custom protos. Needs a connected
|
|
252
|
+
* session (the host token comes from a `media_conn` IQ). `source` is bytes,
|
|
253
|
+
* a file path, or a `Readable`. To send the result, spread its fields onto
|
|
254
|
+
* the matching proto message and pass that to {@link send}.
|
|
255
|
+
*
|
|
256
|
+
* @throws when `source` is unsupported, the file is unreadable, or the upload fails.
|
|
257
|
+
* @example
|
|
258
|
+
* ```ts
|
|
259
|
+
* const media = await client.message.upload(await readFile('photo.jpg'), {
|
|
260
|
+
* type: 'image',
|
|
261
|
+
* mimetype: 'image/jpeg'
|
|
262
|
+
* })
|
|
263
|
+
* await client.message.send(jid, {
|
|
264
|
+
* imageMessage: {
|
|
265
|
+
* url: media.url,
|
|
266
|
+
* directPath: media.directPath,
|
|
267
|
+
* mediaKey: media.mediaKey,
|
|
268
|
+
* fileSha256: media.fileSha256,
|
|
269
|
+
* fileEncSha256: media.fileEncSha256,
|
|
270
|
+
* fileLength: media.fileLength,
|
|
271
|
+
* mediaKeyTimestamp: media.mediaKeyTimestamp,
|
|
272
|
+
* mimetype: media.mimetype
|
|
273
|
+
* }
|
|
274
|
+
* })
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
async upload(source, options) {
|
|
278
|
+
const uploadPath = constants_1.MEDIA_UPLOAD_PATHS[options.type];
|
|
279
|
+
if (!uploadPath) {
|
|
280
|
+
throw new Error(`unknown media upload type: ${String(options.type)}`);
|
|
281
|
+
}
|
|
282
|
+
const result = await (0, messages_1.uploadMedia)(this.mediaUploadOptions, {
|
|
283
|
+
source: await normalizeUploadSource(source),
|
|
284
|
+
cryptoType: options.type,
|
|
285
|
+
uploadPath,
|
|
286
|
+
contentType: options.mimetype,
|
|
287
|
+
mediaKey: options.mediaKey,
|
|
288
|
+
sidecar: options.sidecar ?? SIDECAR_UPLOAD_TYPES.has(options.type),
|
|
289
|
+
firstFrameLength: options.firstFrameLength,
|
|
290
|
+
timeoutMs: options.timeoutMs,
|
|
291
|
+
signal: options.signal,
|
|
292
|
+
logLabel: 'user media upload'
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
...result,
|
|
296
|
+
mediaKeyTimestamp: this.mediaUploadOptions.serverClock.nowSeconds(),
|
|
297
|
+
mimetype: options.mimetype
|
|
298
|
+
};
|
|
299
|
+
}
|
|
225
300
|
/**
|
|
226
301
|
* Resolves the media payload inside `source` and returns a `Readable`
|
|
227
302
|
* stream of the decrypted bytes. Throws when the message has no
|
package/dist/client/media.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface ResolvedMediaInputs {
|
|
|
57
57
|
readonly uploadMedia: Uint8Array | Readable;
|
|
58
58
|
readonly tempFilePath?: string;
|
|
59
59
|
}
|
|
60
|
+
export declare function assertReadableFile(filePath: string): Promise<void>;
|
|
60
61
|
export declare function resolveMediaInputs(shouldProcess: boolean, raw: Uint8Array | ArrayBuffer | Readable | string): Promise<ResolvedMediaInputs>;
|
|
61
62
|
export declare function shouldNormalizeVoiceNote(media: WaMediaOptions | undefined, content: WaSendMediaMessage): content is WaSendMediaMessage & {
|
|
62
63
|
type: 'audio';
|
package/dist/client/media.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.buildMediaUploadUrl = buildMediaUploadUrl;
|
|
|
9
9
|
exports.assertMediaUploadStatus = assertMediaUploadStatus;
|
|
10
10
|
exports.parseMediaUploadJsonBody = parseMediaUploadJsonBody;
|
|
11
11
|
exports.performPlaintextMediaUpload = performPlaintextMediaUpload;
|
|
12
|
+
exports.assertReadableFile = assertReadableFile;
|
|
12
13
|
exports.resolveMediaInputs = resolveMediaInputs;
|
|
13
14
|
exports.shouldNormalizeVoiceNote = shouldNormalizeVoiceNote;
|
|
14
15
|
exports.hasMediaProcessingTasks = hasMediaProcessingTasks;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
1
2
|
import type { ResolvedLinkPreviewResult } from '../messaging/link-preview';
|
|
2
3
|
import type { WaMediaOptions } from '../types';
|
|
3
4
|
import type { Logger } from '../../infra/log/types';
|
|
4
5
|
import type { WaMediaTransferClient } from '../../media/transfer/WaMediaTransferClient';
|
|
5
|
-
import type { WaMediaConn } from '../../media/types';
|
|
6
|
+
import type { MediaCryptoType, WaMediaConn } from '../../media/types';
|
|
6
7
|
import type { WaMessageBuildResult, WaSendMessageContent, WaSendTextMessage } from '../../message/types';
|
|
7
8
|
import type { BinaryNode } from '../../transport/types';
|
|
8
9
|
import type { ServerClock } from '../../util/clock';
|
|
@@ -26,3 +27,46 @@ export interface WaBuildMessageContext {
|
|
|
26
27
|
}
|
|
27
28
|
export declare function buildMediaMessageContent(options: WaMediaMessageOptions, content: WaSendMessageContent, ctx?: WaBuildMessageContext): Promise<WaMessageBuildResult>;
|
|
28
29
|
export declare function getMediaConn(options: WaMediaMessageOptions, forceRefresh?: boolean): Promise<WaMediaConn>;
|
|
30
|
+
/** Descriptor produced by {@link uploadMedia}: CDN location, media key, hashes, and optional sidecars. */
|
|
31
|
+
export interface UploadResult {
|
|
32
|
+
/** Absolute CDN URL of the uploaded ciphertext. */
|
|
33
|
+
readonly url: string;
|
|
34
|
+
/** Host-relative path; pairs with a media host or feeds `downloadMediaMessage`. */
|
|
35
|
+
readonly directPath: string;
|
|
36
|
+
/** 32-byte media key used to encrypt (and later decrypt) the payload. */
|
|
37
|
+
readonly mediaKey: Uint8Array;
|
|
38
|
+
/** SHA-256 of the plaintext. */
|
|
39
|
+
readonly fileSha256: Uint8Array;
|
|
40
|
+
/** SHA-256 of the encrypted `ciphertext||mac`. */
|
|
41
|
+
readonly fileEncSha256: Uint8Array;
|
|
42
|
+
/** Plaintext byte length. */
|
|
43
|
+
readonly fileLength: number;
|
|
44
|
+
/** Server metadata URL, when the CDN returns one (video). */
|
|
45
|
+
readonly metadataUrl?: string;
|
|
46
|
+
/** Streaming sidecar for seekable playback, when computed. */
|
|
47
|
+
readonly streamingSidecar?: Uint8Array;
|
|
48
|
+
/** First-frame sidecar for animated stickers, when computed. */
|
|
49
|
+
readonly firstFrameSidecar?: Uint8Array;
|
|
50
|
+
/** First-frame length echoed back for animated stickers. */
|
|
51
|
+
readonly firstFrameLength?: number;
|
|
52
|
+
}
|
|
53
|
+
/** Fully-resolved input for {@link uploadMedia} (crypto type, path, sidecar already decided). */
|
|
54
|
+
export interface WaMediaUploadInput {
|
|
55
|
+
readonly source: Uint8Array | Readable;
|
|
56
|
+
readonly cryptoType: MediaCryptoType;
|
|
57
|
+
readonly uploadPath: string;
|
|
58
|
+
readonly contentType?: string;
|
|
59
|
+
readonly mediaKey?: Uint8Array;
|
|
60
|
+
readonly sidecar: boolean;
|
|
61
|
+
readonly firstFrameLength?: number;
|
|
62
|
+
readonly logLabel?: string;
|
|
63
|
+
readonly timeoutMs?: number;
|
|
64
|
+
readonly signal?: AbortSignal;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Shared media upload primitive: derives (or reuses) a media key, encrypts,
|
|
68
|
+
* fetches a media connection, and POSTs the ciphertext. Bytes take a
|
|
69
|
+
* zero-temp-file fast path; streams stage to a temp file first. Backs both the
|
|
70
|
+
* send path and `WaMessageCoordinator.upload`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function uploadMedia(options: WaMediaMessageOptions, input: WaMediaUploadInput): Promise<UploadResult>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildMediaMessageContent = buildMediaMessageContent;
|
|
4
4
|
exports.getMediaConn = getMediaConn;
|
|
5
|
+
exports.uploadMedia = uploadMedia;
|
|
5
6
|
const node_fs_1 = require("node:fs");
|
|
6
7
|
const node_stream_1 = require("node:stream");
|
|
7
8
|
const media_1 = require("../media");
|
|
@@ -345,9 +346,15 @@ async function buildMediaMessage(options, content) {
|
|
|
345
346
|
const firstFrameLength = content.type === 'sticker'
|
|
346
347
|
? (content.firstFrameLength ?? detectedFirstFrameLength)
|
|
347
348
|
: undefined;
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
:
|
|
349
|
+
const uploadType = resolveUploadType(content);
|
|
350
|
+
const uploadPromise = uploadMedia(options, {
|
|
351
|
+
source: resolved.uploadMedia,
|
|
352
|
+
cryptoType: uploadType,
|
|
353
|
+
uploadPath: resolveUploadPath(uploadType),
|
|
354
|
+
contentType: mimetype,
|
|
355
|
+
sidecar: needsSidecar(content),
|
|
356
|
+
firstFrameLength
|
|
357
|
+
});
|
|
351
358
|
const processPromise = (0, media_1.runMediaProcessor)(options.media, resolved.processorInput, content, options.logger);
|
|
352
359
|
const [uploadResult, processResult] = await Promise.allSettled([
|
|
353
360
|
uploadPromise,
|
|
@@ -508,29 +515,42 @@ function parseUploadResponse(body, status) {
|
|
|
508
515
|
...(parsed.metadata_url ? { metadataUrl: parsed.metadata_url } : {})
|
|
509
516
|
};
|
|
510
517
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
518
|
+
/**
|
|
519
|
+
* Shared media upload primitive: derives (or reuses) a media key, encrypts,
|
|
520
|
+
* fetches a media connection, and POSTs the ciphertext. Bytes take a
|
|
521
|
+
* zero-temp-file fast path; streams stage to a temp file first. Backs both the
|
|
522
|
+
* send path and `WaMessageCoordinator.upload`.
|
|
523
|
+
*/
|
|
524
|
+
async function uploadMedia(options, input) {
|
|
525
|
+
const mediaKey = input.mediaKey ?? (await WaMediaCrypto_1.WaMediaCrypto.generateMediaKey());
|
|
526
|
+
if (input.source instanceof Uint8Array) {
|
|
527
|
+
return uploadMediaBytes(options, input, mediaKey, input.source);
|
|
528
|
+
}
|
|
529
|
+
return uploadMediaStream(options, input, mediaKey, input.source);
|
|
530
|
+
}
|
|
531
|
+
async function uploadMediaBytes(options, input, mediaKey, mediaBytes) {
|
|
514
532
|
const [encrypted, mediaConn] = await Promise.all([
|
|
515
|
-
WaMediaCrypto_1.WaMediaCrypto.encryptBytes(
|
|
516
|
-
sidecar:
|
|
517
|
-
firstFrameLength
|
|
533
|
+
WaMediaCrypto_1.WaMediaCrypto.encryptBytes(input.cryptoType, mediaKey, mediaBytes, {
|
|
534
|
+
sidecar: input.sidecar,
|
|
535
|
+
firstFrameLength: input.firstFrameLength
|
|
518
536
|
}),
|
|
519
537
|
getMediaConn(options)
|
|
520
538
|
]);
|
|
521
539
|
const selectedHost = (0, media_1.selectMediaUploadHost)(mediaConn);
|
|
522
|
-
const uploadUrl = (0, media_1.buildMediaUploadUrl)(selectedHost,
|
|
523
|
-
options.logger.debug('sending media upload request', {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
540
|
+
const uploadUrl = (0, media_1.buildMediaUploadUrl)(selectedHost, input.uploadPath, mediaConn.auth, encrypted.fileEncSha256);
|
|
541
|
+
options.logger.debug(input.logLabel ?? 'sending media upload request', {
|
|
542
|
+
cryptoType: input.cryptoType,
|
|
543
|
+
host: selectedHost,
|
|
544
|
+
size: mediaBytes.byteLength
|
|
527
545
|
});
|
|
528
546
|
const uploadResponse = await options.mediaTransfer.uploadStream({
|
|
529
547
|
url: uploadUrl,
|
|
530
548
|
method: 'POST',
|
|
531
549
|
body: encrypted.ciphertextHmac,
|
|
532
550
|
contentLength: encrypted.ciphertextHmac.byteLength,
|
|
533
|
-
contentType:
|
|
551
|
+
contentType: input.contentType,
|
|
552
|
+
timeoutMs: input.timeoutMs,
|
|
553
|
+
signal: input.signal
|
|
534
554
|
});
|
|
535
555
|
const responseBody = await options.mediaTransfer.readResponseBytes(uploadResponse);
|
|
536
556
|
const parsed = parseUploadResponse(responseBody, uploadResponse.status);
|
|
@@ -542,19 +562,22 @@ async function uploadMediaBytes(options, content, mediaBytes, firstFrameLength,
|
|
|
542
562
|
fileLength: mediaBytes.byteLength,
|
|
543
563
|
streamingSidecar: encrypted.streamingSidecar,
|
|
544
564
|
firstFrameSidecar: encrypted.firstFrameSidecar,
|
|
545
|
-
firstFrameLength
|
|
565
|
+
firstFrameLength: input.firstFrameLength
|
|
546
566
|
};
|
|
547
567
|
}
|
|
548
|
-
async function
|
|
549
|
-
const encResult = await WaMediaCrypto_1.WaMediaCrypto.encryptToFile(input.cryptoType,
|
|
568
|
+
async function uploadMediaStream(options, input, mediaKey, plaintext) {
|
|
569
|
+
const encResult = await WaMediaCrypto_1.WaMediaCrypto.encryptToFile(input.cryptoType, mediaKey, plaintext, {
|
|
570
|
+
sidecar: input.sidecar,
|
|
571
|
+
firstFrameLength: input.firstFrameLength
|
|
572
|
+
});
|
|
550
573
|
let readStream;
|
|
551
574
|
try {
|
|
552
575
|
const mediaConn = await getMediaConn(options);
|
|
553
576
|
const selectedHost = (0, media_1.selectMediaUploadHost)(mediaConn);
|
|
554
577
|
const uploadUrl = (0, media_1.buildMediaUploadUrl)(selectedHost, input.uploadPath, mediaConn.auth, encResult.fileEncSha256);
|
|
555
|
-
options.logger.debug(input.logLabel, {
|
|
578
|
+
options.logger.debug(input.logLabel ?? 'sending media stream upload request', {
|
|
579
|
+
cryptoType: input.cryptoType,
|
|
556
580
|
host: selectedHost,
|
|
557
|
-
uploadPath: input.uploadPath,
|
|
558
581
|
plaintextLength: encResult.plaintextLength,
|
|
559
582
|
encryptedSize: encResult.fileSize
|
|
560
583
|
});
|
|
@@ -564,13 +587,15 @@ async function uploadEncryptedStream(options, input) {
|
|
|
564
587
|
method: 'POST',
|
|
565
588
|
body: readStream,
|
|
566
589
|
contentLength: encResult.fileSize,
|
|
567
|
-
contentType: input.contentType
|
|
590
|
+
contentType: input.contentType,
|
|
591
|
+
timeoutMs: input.timeoutMs,
|
|
592
|
+
signal: input.signal
|
|
568
593
|
});
|
|
569
594
|
const responseBody = await options.mediaTransfer.readResponseBytes(uploadResponse);
|
|
570
595
|
const parsed = parseUploadResponse(responseBody, uploadResponse.status);
|
|
571
596
|
return {
|
|
572
597
|
...parsed,
|
|
573
|
-
mediaKey
|
|
598
|
+
mediaKey,
|
|
574
599
|
fileSha256: encResult.fileSha256,
|
|
575
600
|
fileEncSha256: encResult.fileEncSha256,
|
|
576
601
|
fileLength: encResult.plaintextLength,
|
|
@@ -589,19 +614,6 @@ async function uploadEncryptedStream(options, input) {
|
|
|
589
614
|
await WaMediaCrypto_1.WaMediaCrypto.cleanupEncryptedFile(encResult.filePath);
|
|
590
615
|
}
|
|
591
616
|
}
|
|
592
|
-
async function uploadMediaStream(options, content, stream, firstFrameLength, mimetype) {
|
|
593
|
-
const cryptoType = resolveUploadType(content);
|
|
594
|
-
return uploadEncryptedStream(options, {
|
|
595
|
-
plaintext: stream,
|
|
596
|
-
mediaKey: await WaMediaCrypto_1.WaMediaCrypto.generateMediaKey(),
|
|
597
|
-
cryptoType,
|
|
598
|
-
uploadPath: resolveUploadPath(cryptoType),
|
|
599
|
-
contentType: mimetype,
|
|
600
|
-
logLabel: 'sending media stream upload request',
|
|
601
|
-
sidecar: needsSidecar(content),
|
|
602
|
-
firstFrameLength
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
617
|
function openStickerPackInputStream(media) {
|
|
606
618
|
return typeof media === 'string' ? (0, node_fs_1.createReadStream)(media) : node_stream_1.Readable.from([media]);
|
|
607
619
|
}
|
|
@@ -613,23 +625,23 @@ async function buildStickerPackMediaMessage(options, content) {
|
|
|
613
625
|
const coverThumbnail = content.coverThumbnail;
|
|
614
626
|
const mediaKey = await WaMediaCrypto_1.WaMediaCrypto.generateMediaKey();
|
|
615
627
|
const [bundle, cover] = await Promise.all([
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
mediaKey,
|
|
628
|
+
uploadMedia(options, {
|
|
629
|
+
source: (0, sticker_pack_1.createStickerPackZipStream)((0, sticker_pack_2.toStickerPackZipEntries)(content)),
|
|
619
630
|
cryptoType: 'sticker-pack',
|
|
620
631
|
uploadPath: constants_1.MEDIA_UPLOAD_PATHS['sticker-pack'],
|
|
621
632
|
contentType: 'application/zip',
|
|
622
|
-
logLabel: 'sending sticker pack bundle upload',
|
|
623
|
-
sidecar: false
|
|
624
|
-
}),
|
|
625
|
-
uploadEncryptedStream(options, {
|
|
626
|
-
plaintext: openStickerPackInputStream(coverThumbnail),
|
|
627
633
|
mediaKey,
|
|
634
|
+
sidecar: false,
|
|
635
|
+
logLabel: 'sending sticker pack bundle upload'
|
|
636
|
+
}),
|
|
637
|
+
uploadMedia(options, {
|
|
638
|
+
source: openStickerPackInputStream(coverThumbnail),
|
|
628
639
|
cryptoType: 'thumbnail-sticker-pack',
|
|
629
640
|
uploadPath: constants_1.MEDIA_UPLOAD_PATHS['thumbnail-sticker-pack'],
|
|
630
641
|
contentType: 'image/jpeg',
|
|
631
|
-
|
|
632
|
-
sidecar: false
|
|
642
|
+
mediaKey,
|
|
643
|
+
sidecar: false,
|
|
644
|
+
logLabel: 'sending sticker pack thumbnail upload'
|
|
633
645
|
})
|
|
634
646
|
]);
|
|
635
647
|
if (cover.fileLength === 0) {
|
|
@@ -411,7 +411,7 @@ class WaClientImpl extends EventEmitter {
|
|
|
411
411
|
get auth() {
|
|
412
412
|
return this.deps.authClient;
|
|
413
413
|
}
|
|
414
|
-
/** Message coordinator: send/receive, receipts, addons, media download. */
|
|
414
|
+
/** Message coordinator: send/receive, receipts, addons, media upload/download. */
|
|
415
415
|
get message() {
|
|
416
416
|
return this.deps.messageCoordinator;
|
|
417
417
|
}
|
|
@@ -500,6 +500,7 @@ export function buildWaClientDependencies(input) {
|
|
|
500
500
|
const messageCoordinator = new WaMessageCoordinator({
|
|
501
501
|
messageDispatch,
|
|
502
502
|
mediaTransfer,
|
|
503
|
+
mediaUploadOptions: mediaMessageBuildOptions,
|
|
503
504
|
logger,
|
|
504
505
|
messageStore: sessionStore.messages,
|
|
505
506
|
messageSecretStore: sessionStore.messageSecret,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { createWriteStream } from 'node:fs';
|
|
1
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
2
2
|
import { pipeline } from 'node:stream/promises';
|
|
3
3
|
import { aggregateReceiptTargets } from '../events/receipt.js';
|
|
4
|
-
import { downloadMediaMessage } from '../media.js';
|
|
4
|
+
import { assertReadableFile, downloadMediaMessage, isReadableStream } from '../media.js';
|
|
5
|
+
import { uploadMedia } from '../messaging/messages.js';
|
|
6
|
+
import { MEDIA_UPLOAD_PATHS } from '../../media/constants.js';
|
|
5
7
|
import { buildAddonAdditionalData, decodeAddonPlaintext, decryptAddonPayload, identifyEncryptedAddon, resolveParentMessageSecret, resolvePollOptionNames, shouldUseAddonAdditionalData } from '../../message/crypto/addon-crypto.js';
|
|
6
8
|
import { unwrapMessage } from '../../message/encode/content.js';
|
|
7
9
|
import { proto } from '../../proto.js';
|
|
@@ -31,15 +33,36 @@ function parseMessageCappingMexResponse(data) {
|
|
|
31
33
|
cappingStatus: tryAsString(root?.capping_status)
|
|
32
34
|
};
|
|
33
35
|
}
|
|
36
|
+
const SIDECAR_UPLOAD_TYPES = new Set([
|
|
37
|
+
'video',
|
|
38
|
+
'ptv',
|
|
39
|
+
'audio',
|
|
40
|
+
'gif',
|
|
41
|
+
'ptt'
|
|
42
|
+
]);
|
|
43
|
+
async function normalizeUploadSource(source) {
|
|
44
|
+
if (source instanceof Uint8Array) {
|
|
45
|
+
return source;
|
|
46
|
+
}
|
|
47
|
+
if (typeof source === 'string') {
|
|
48
|
+
await assertReadableFile(source);
|
|
49
|
+
return createReadStream(source);
|
|
50
|
+
}
|
|
51
|
+
if (isReadableStream(source)) {
|
|
52
|
+
return source;
|
|
53
|
+
}
|
|
54
|
+
throw new Error('media upload received unsupported source type');
|
|
55
|
+
}
|
|
34
56
|
/**
|
|
35
57
|
* Coordinates outbound message sending, receipts, addon decryption, media
|
|
36
|
-
* download, and the related MEX account queries. Accessed via
|
|
58
|
+
* upload/download, and the related MEX account queries. Accessed via
|
|
37
59
|
* {@link WaClient.message}.
|
|
38
60
|
*/
|
|
39
61
|
export class WaMessageCoordinator {
|
|
40
62
|
constructor(deps) {
|
|
41
63
|
this.messageDispatch = deps.messageDispatch;
|
|
42
64
|
this.mediaTransfer = deps.mediaTransfer;
|
|
65
|
+
this.mediaUploadOptions = deps.mediaUploadOptions;
|
|
43
66
|
this.logger = deps.logger;
|
|
44
67
|
this.messageStore = deps.messageStore;
|
|
45
68
|
this.messageSecretStore = deps.messageSecretStore;
|
|
@@ -219,6 +242,58 @@ export class WaMessageCoordinator {
|
|
|
219
242
|
});
|
|
220
243
|
}
|
|
221
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Encrypts and uploads standalone media to the WhatsApp CDN and returns the
|
|
247
|
+
* reusable descriptor, without sending a message - pre-upload once and
|
|
248
|
+
* reference it across sends, or build custom protos. Needs a connected
|
|
249
|
+
* session (the host token comes from a `media_conn` IQ). `source` is bytes,
|
|
250
|
+
* a file path, or a `Readable`. To send the result, spread its fields onto
|
|
251
|
+
* the matching proto message and pass that to {@link send}.
|
|
252
|
+
*
|
|
253
|
+
* @throws when `source` is unsupported, the file is unreadable, or the upload fails.
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* const media = await client.message.upload(await readFile('photo.jpg'), {
|
|
257
|
+
* type: 'image',
|
|
258
|
+
* mimetype: 'image/jpeg'
|
|
259
|
+
* })
|
|
260
|
+
* await client.message.send(jid, {
|
|
261
|
+
* imageMessage: {
|
|
262
|
+
* url: media.url,
|
|
263
|
+
* directPath: media.directPath,
|
|
264
|
+
* mediaKey: media.mediaKey,
|
|
265
|
+
* fileSha256: media.fileSha256,
|
|
266
|
+
* fileEncSha256: media.fileEncSha256,
|
|
267
|
+
* fileLength: media.fileLength,
|
|
268
|
+
* mediaKeyTimestamp: media.mediaKeyTimestamp,
|
|
269
|
+
* mimetype: media.mimetype
|
|
270
|
+
* }
|
|
271
|
+
* })
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
async upload(source, options) {
|
|
275
|
+
const uploadPath = MEDIA_UPLOAD_PATHS[options.type];
|
|
276
|
+
if (!uploadPath) {
|
|
277
|
+
throw new Error(`unknown media upload type: ${String(options.type)}`);
|
|
278
|
+
}
|
|
279
|
+
const result = await uploadMedia(this.mediaUploadOptions, {
|
|
280
|
+
source: await normalizeUploadSource(source),
|
|
281
|
+
cryptoType: options.type,
|
|
282
|
+
uploadPath,
|
|
283
|
+
contentType: options.mimetype,
|
|
284
|
+
mediaKey: options.mediaKey,
|
|
285
|
+
sidecar: options.sidecar ?? SIDECAR_UPLOAD_TYPES.has(options.type),
|
|
286
|
+
firstFrameLength: options.firstFrameLength,
|
|
287
|
+
timeoutMs: options.timeoutMs,
|
|
288
|
+
signal: options.signal,
|
|
289
|
+
logLabel: 'user media upload'
|
|
290
|
+
});
|
|
291
|
+
return {
|
|
292
|
+
...result,
|
|
293
|
+
mediaKeyTimestamp: this.mediaUploadOptions.serverClock.nowSeconds(),
|
|
294
|
+
mimetype: options.mimetype
|
|
295
|
+
};
|
|
296
|
+
}
|
|
222
297
|
/**
|
|
223
298
|
* Resolves the media payload inside `source` and returns a `Readable`
|
|
224
299
|
* stream of the decrypted bytes. Throws when the message has no
|
package/dist/esm/client/media.js
CHANGED
|
@@ -198,7 +198,7 @@ export async function performPlaintextMediaUpload(deps, input) {
|
|
|
198
198
|
await prepared.cleanup?.();
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
async function assertReadableFile(filePath) {
|
|
201
|
+
export async function assertReadableFile(filePath) {
|
|
202
202
|
try {
|
|
203
203
|
const stats = await stat(filePath);
|
|
204
204
|
if (!stats.isFile()) {
|
|
@@ -341,9 +341,15 @@ async function buildMediaMessage(options, content) {
|
|
|
341
341
|
const firstFrameLength = content.type === 'sticker'
|
|
342
342
|
? (content.firstFrameLength ?? detectedFirstFrameLength)
|
|
343
343
|
: undefined;
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
:
|
|
344
|
+
const uploadType = resolveUploadType(content);
|
|
345
|
+
const uploadPromise = uploadMedia(options, {
|
|
346
|
+
source: resolved.uploadMedia,
|
|
347
|
+
cryptoType: uploadType,
|
|
348
|
+
uploadPath: resolveUploadPath(uploadType),
|
|
349
|
+
contentType: mimetype,
|
|
350
|
+
sidecar: needsSidecar(content),
|
|
351
|
+
firstFrameLength
|
|
352
|
+
});
|
|
347
353
|
const processPromise = runMediaProcessor(options.media, resolved.processorInput, content, options.logger);
|
|
348
354
|
const [uploadResult, processResult] = await Promise.allSettled([
|
|
349
355
|
uploadPromise,
|
|
@@ -504,29 +510,42 @@ function parseUploadResponse(body, status) {
|
|
|
504
510
|
...(parsed.metadata_url ? { metadataUrl: parsed.metadata_url } : {})
|
|
505
511
|
};
|
|
506
512
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
513
|
+
/**
|
|
514
|
+
* Shared media upload primitive: derives (or reuses) a media key, encrypts,
|
|
515
|
+
* fetches a media connection, and POSTs the ciphertext. Bytes take a
|
|
516
|
+
* zero-temp-file fast path; streams stage to a temp file first. Backs both the
|
|
517
|
+
* send path and `WaMessageCoordinator.upload`.
|
|
518
|
+
*/
|
|
519
|
+
export async function uploadMedia(options, input) {
|
|
520
|
+
const mediaKey = input.mediaKey ?? (await WaMediaCrypto.generateMediaKey());
|
|
521
|
+
if (input.source instanceof Uint8Array) {
|
|
522
|
+
return uploadMediaBytes(options, input, mediaKey, input.source);
|
|
523
|
+
}
|
|
524
|
+
return uploadMediaStream(options, input, mediaKey, input.source);
|
|
525
|
+
}
|
|
526
|
+
async function uploadMediaBytes(options, input, mediaKey, mediaBytes) {
|
|
510
527
|
const [encrypted, mediaConn] = await Promise.all([
|
|
511
|
-
WaMediaCrypto.encryptBytes(
|
|
512
|
-
sidecar:
|
|
513
|
-
firstFrameLength
|
|
528
|
+
WaMediaCrypto.encryptBytes(input.cryptoType, mediaKey, mediaBytes, {
|
|
529
|
+
sidecar: input.sidecar,
|
|
530
|
+
firstFrameLength: input.firstFrameLength
|
|
514
531
|
}),
|
|
515
532
|
getMediaConn(options)
|
|
516
533
|
]);
|
|
517
534
|
const selectedHost = selectMediaUploadHost(mediaConn);
|
|
518
|
-
const uploadUrl = buildMediaUploadUrl(selectedHost,
|
|
519
|
-
options.logger.debug('sending media upload request', {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
535
|
+
const uploadUrl = buildMediaUploadUrl(selectedHost, input.uploadPath, mediaConn.auth, encrypted.fileEncSha256);
|
|
536
|
+
options.logger.debug(input.logLabel ?? 'sending media upload request', {
|
|
537
|
+
cryptoType: input.cryptoType,
|
|
538
|
+
host: selectedHost,
|
|
539
|
+
size: mediaBytes.byteLength
|
|
523
540
|
});
|
|
524
541
|
const uploadResponse = await options.mediaTransfer.uploadStream({
|
|
525
542
|
url: uploadUrl,
|
|
526
543
|
method: 'POST',
|
|
527
544
|
body: encrypted.ciphertextHmac,
|
|
528
545
|
contentLength: encrypted.ciphertextHmac.byteLength,
|
|
529
|
-
contentType:
|
|
546
|
+
contentType: input.contentType,
|
|
547
|
+
timeoutMs: input.timeoutMs,
|
|
548
|
+
signal: input.signal
|
|
530
549
|
});
|
|
531
550
|
const responseBody = await options.mediaTransfer.readResponseBytes(uploadResponse);
|
|
532
551
|
const parsed = parseUploadResponse(responseBody, uploadResponse.status);
|
|
@@ -538,19 +557,22 @@ async function uploadMediaBytes(options, content, mediaBytes, firstFrameLength,
|
|
|
538
557
|
fileLength: mediaBytes.byteLength,
|
|
539
558
|
streamingSidecar: encrypted.streamingSidecar,
|
|
540
559
|
firstFrameSidecar: encrypted.firstFrameSidecar,
|
|
541
|
-
firstFrameLength
|
|
560
|
+
firstFrameLength: input.firstFrameLength
|
|
542
561
|
};
|
|
543
562
|
}
|
|
544
|
-
async function
|
|
545
|
-
const encResult = await WaMediaCrypto.encryptToFile(input.cryptoType,
|
|
563
|
+
async function uploadMediaStream(options, input, mediaKey, plaintext) {
|
|
564
|
+
const encResult = await WaMediaCrypto.encryptToFile(input.cryptoType, mediaKey, plaintext, {
|
|
565
|
+
sidecar: input.sidecar,
|
|
566
|
+
firstFrameLength: input.firstFrameLength
|
|
567
|
+
});
|
|
546
568
|
let readStream;
|
|
547
569
|
try {
|
|
548
570
|
const mediaConn = await getMediaConn(options);
|
|
549
571
|
const selectedHost = selectMediaUploadHost(mediaConn);
|
|
550
572
|
const uploadUrl = buildMediaUploadUrl(selectedHost, input.uploadPath, mediaConn.auth, encResult.fileEncSha256);
|
|
551
|
-
options.logger.debug(input.logLabel, {
|
|
573
|
+
options.logger.debug(input.logLabel ?? 'sending media stream upload request', {
|
|
574
|
+
cryptoType: input.cryptoType,
|
|
552
575
|
host: selectedHost,
|
|
553
|
-
uploadPath: input.uploadPath,
|
|
554
576
|
plaintextLength: encResult.plaintextLength,
|
|
555
577
|
encryptedSize: encResult.fileSize
|
|
556
578
|
});
|
|
@@ -560,13 +582,15 @@ async function uploadEncryptedStream(options, input) {
|
|
|
560
582
|
method: 'POST',
|
|
561
583
|
body: readStream,
|
|
562
584
|
contentLength: encResult.fileSize,
|
|
563
|
-
contentType: input.contentType
|
|
585
|
+
contentType: input.contentType,
|
|
586
|
+
timeoutMs: input.timeoutMs,
|
|
587
|
+
signal: input.signal
|
|
564
588
|
});
|
|
565
589
|
const responseBody = await options.mediaTransfer.readResponseBytes(uploadResponse);
|
|
566
590
|
const parsed = parseUploadResponse(responseBody, uploadResponse.status);
|
|
567
591
|
return {
|
|
568
592
|
...parsed,
|
|
569
|
-
mediaKey
|
|
593
|
+
mediaKey,
|
|
570
594
|
fileSha256: encResult.fileSha256,
|
|
571
595
|
fileEncSha256: encResult.fileEncSha256,
|
|
572
596
|
fileLength: encResult.plaintextLength,
|
|
@@ -585,19 +609,6 @@ async function uploadEncryptedStream(options, input) {
|
|
|
585
609
|
await WaMediaCrypto.cleanupEncryptedFile(encResult.filePath);
|
|
586
610
|
}
|
|
587
611
|
}
|
|
588
|
-
async function uploadMediaStream(options, content, stream, firstFrameLength, mimetype) {
|
|
589
|
-
const cryptoType = resolveUploadType(content);
|
|
590
|
-
return uploadEncryptedStream(options, {
|
|
591
|
-
plaintext: stream,
|
|
592
|
-
mediaKey: await WaMediaCrypto.generateMediaKey(),
|
|
593
|
-
cryptoType,
|
|
594
|
-
uploadPath: resolveUploadPath(cryptoType),
|
|
595
|
-
contentType: mimetype,
|
|
596
|
-
logLabel: 'sending media stream upload request',
|
|
597
|
-
sidecar: needsSidecar(content),
|
|
598
|
-
firstFrameLength
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
612
|
function openStickerPackInputStream(media) {
|
|
602
613
|
return typeof media === 'string' ? createReadStream(media) : Readable.from([media]);
|
|
603
614
|
}
|
|
@@ -609,23 +620,23 @@ async function buildStickerPackMediaMessage(options, content) {
|
|
|
609
620
|
const coverThumbnail = content.coverThumbnail;
|
|
610
621
|
const mediaKey = await WaMediaCrypto.generateMediaKey();
|
|
611
622
|
const [bundle, cover] = await Promise.all([
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
mediaKey,
|
|
623
|
+
uploadMedia(options, {
|
|
624
|
+
source: createStickerPackZipStream(toStickerPackZipEntries(content)),
|
|
615
625
|
cryptoType: 'sticker-pack',
|
|
616
626
|
uploadPath: MEDIA_UPLOAD_PATHS['sticker-pack'],
|
|
617
627
|
contentType: 'application/zip',
|
|
618
|
-
logLabel: 'sending sticker pack bundle upload',
|
|
619
|
-
sidecar: false
|
|
620
|
-
}),
|
|
621
|
-
uploadEncryptedStream(options, {
|
|
622
|
-
plaintext: openStickerPackInputStream(coverThumbnail),
|
|
623
628
|
mediaKey,
|
|
629
|
+
sidecar: false,
|
|
630
|
+
logLabel: 'sending sticker pack bundle upload'
|
|
631
|
+
}),
|
|
632
|
+
uploadMedia(options, {
|
|
633
|
+
source: openStickerPackInputStream(coverThumbnail),
|
|
624
634
|
cryptoType: 'thumbnail-sticker-pack',
|
|
625
635
|
uploadPath: MEDIA_UPLOAD_PATHS['thumbnail-sticker-pack'],
|
|
626
636
|
contentType: 'image/jpeg',
|
|
627
|
-
|
|
628
|
-
sidecar: false
|
|
637
|
+
mediaKey,
|
|
638
|
+
sidecar: false,
|
|
639
|
+
logLabel: 'sending sticker pack thumbnail upload'
|
|
629
640
|
})
|
|
630
641
|
]);
|
|
631
642
|
if (cover.fileLength === 0) {
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { WaClient } from './client/index.js';
|
|
2
2
|
export { defineWaClientPlugin } from './client/plugins/index.js';
|
|
3
3
|
export { downloadMediaMessage } from './client/media.js';
|
|
4
|
+
export { WaMediaCrypto } from './media/crypto/WaMediaCrypto.js';
|
|
5
|
+
export { WaMediaTransferClient } from './media/transfer/WaMediaTransferClient.js';
|
|
4
6
|
export { createFileCompanionHostPersistence } from './client/persistence/companion-host.js';
|
|
5
7
|
export { parseUsyncResultEnvelope } from './transport/node/builders/usync.js';
|
|
6
8
|
export { getContentType, resolveEncMediaType, resolveMessageTarget, unwrapMessage } from './message/encode/content.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { defineWaClientPlugin } from './client/plugins';
|
|
|
3
3
|
export type { WaClientDependencies } from './client/WaClientFactory';
|
|
4
4
|
export type { WaClientEventMap, WaClientOptions, WaClientProxyOptions, WaDownloadMediaOptions, WaHistorySyncChunkEvent, WaHistorySyncOptions, WaWriteBehindOptions } from './client/types';
|
|
5
5
|
export type { WaClientPluginContext, WaClientPluginDefinition } from './client/plugins';
|
|
6
|
-
export type { WaMessageCoordinator } from './client/coordinators/WaMessageCoordinator';
|
|
6
|
+
export type { WaMediaUploadResult, WaMessageCoordinator, WaUploadMediaOptions, WaUploadMediaType } from './client/coordinators/WaMessageCoordinator';
|
|
7
7
|
export type { WaAccountTakeoverNoticeEvent, WaAppStateMutationEvent, WaAppStateMutationSource, WaBusinessEvent, WaBusinessEventAction, WaBusinessProfileResult, WaConnectionEvent, WaGroupEvent, WaGroupEventAction, WaGroupEventLinkedGroup, WaGroupEventMembershipRequest, WaGroupEventParticipant, WaGroupEventSubgroupSuggestion, WaIgnoreKey, WaIgnoreKeyContext, WaIgnoreKeyPredicate, WaIgnoreStanzaKind, WaIncomingAddonEvent, WaIncomingBaseEvent, WaIncomingBotChunkEvent, WaIncomingCallEvent, WaIncomingChatstateEvent, WaIncomingErrorStanzaEvent, WaIncomingFailureEvent, WaIncomingMessageEvent, WaIncomingMessageKey, WaIncomingNewsletterEvent, WaIncomingNewsletterMessageUpdateEvent, WaIncomingNodeHandler, WaIncomingNodeHandlerRegistration, WaIncomingNotificationEvent, WaIncomingPresenceEvent, WaIncomingProtocolMessageEvent, WaIncomingReceiptEvent, WaIncomingStanzaFilter, WaIncomingUnavailableMessageEvent, WaIncomingUnhandledStanzaEvent, WaMexLidChangeEvent, WaMexMessageCappingEvent, WaMexMessageCappingStatus, WaMexNotificationEvent, WaMexNotificationGraphQlError, WaMexNotificationOperationName, WaMexNotificationUnknownEvent, WaMexOwnUsernameSyncEvent, WaMexTextStatusUpdateEvent, WaMexTextStatusUpdateHintEvent, WaMexUsernameDeleteEvent, WaMexUsernameSetEvent, WaMexUsernameUpdateHintEvent, WaOfflineResumeEvent, WaOutgoingMessageEvent, WaPictureEvent, WaPictureEventAction, WaPrivacyTokenUpdateEvent, WaReceiptStatus, WaRegistrationCodeEvent, WaSendMessageOptions, WaUnavailableMessageKind, WaVerifiedNameResult, WaAddonKind, WaNewsletterEventAction, WaNewsletterMessageUpdate, WaNewsletterPollVoteEntry, WaNewsletterReactionEntry } from './client/types';
|
|
8
8
|
export type { WaAppStateMutationCoordinator, WaBroadcastListParticipant, WaSetBroadcastListInput, WaSetStatusPrivacyInput } from './client/coordinators/WaAppStateMutationCoordinator';
|
|
9
9
|
export type { WaBotCoordinator, WaBotInfo, WaBotPosingAsProfessional, WaBotProfileCommand, WaBotProfilePrompt, WaBotProfileResult, WaBotPromptOptions, WaGetBotProfileOptions } from './client/coordinators/WaBotCoordinator';
|
|
@@ -11,6 +11,9 @@ export type { WaBroadcastListCoordinator, WaSendBroadcastListMessageInput } from
|
|
|
11
11
|
export type { WaBusinessCoordinator, WaVerifiedNameBatchEntry } from './client/coordinators/WaBusinessCoordinator';
|
|
12
12
|
export { downloadMediaMessage } from './client/media';
|
|
13
13
|
export type { WaDownloadMediaMessageOptions, WaUploadMediaSource } from './client/media';
|
|
14
|
+
export { WaMediaCrypto } from './media/crypto/WaMediaCrypto';
|
|
15
|
+
export { WaMediaTransferClient } from './media/transfer/WaMediaTransferClient';
|
|
16
|
+
export type { MediaCryptoType, MediaKind, WaMediaConn, WaMediaDecryptReadableOptions, WaMediaDecryptionResult, WaMediaDerivedKeys, WaMediaEncryptionResult, WaMediaFileEncryptionResult, WaMediaReadableDecryptionResult, WaMediaReadableEncryptionResult, WaMediaTransferClientOptions } from './media/types';
|
|
14
17
|
export type { WaEditBusinessProfileInput } from './transport/node/builders/business';
|
|
15
18
|
export type { WaEmailCoordinator, WaEmailStatus, WaEmailVerifyCodeResult } from './client/coordinators/WaEmailCoordinator';
|
|
16
19
|
export type { WaCommunityCreateOptions, WaCommunitySubGroup, WaCommunitySubGroupResult, WaCommunitySubGroupsResult, WaGroupCoordinator, WaGroupCreateOptions, WaGroupMetadata, WaGroupParticipant, WaLinkSubGroupsResult, WaMembershipRequest, WaUnlinkSubGroupsResult } from './client/coordinators/WaGroupCoordinator';
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.proto = exports.WA_XMLNS = exports.WA_PRIVACY_VALUES = exports.WA_PRIVACY_TAGS = exports.WA_PRIVACY_SETTING_TO_CATEGORY = exports.WA_PRIVACY_DISALLOWED_LIST_CATEGORIES = exports.WA_PRIVACY_CATEGORY_TO_SETTING = exports.WA_PRIVACY_CATEGORIES = exports.WA_SUPPORTED_DIRTY_TYPES = exports.WA_STREAM_SIGNALING = exports.WA_SIGNALING = exports.WA_RETRYABLE_ACK_CODES = exports.WA_READY_STATES = exports.WA_PREVIEW_MEDIA_HKDF_INFO = exports.WA_PAIRING_KDF_INFO = exports.WA_NODE_TAGS = exports.WA_MEDIA_HKDF_INFO = exports.WA_MESSAGE_TYPES = exports.WA_MESSAGE_TAGS = exports.WA_LOGOUT_REASONS = exports.WA_IQ_TYPES = exports.WA_DISCONNECT_REASONS = exports.WA_DIRTY_TYPES = exports.WA_DIRTY_PROTOCOLS = exports.WA_DEFAULTS = exports.WA_COMPANION_PLATFORM_IDS = exports.WA_BUSINESS_HOURS_MODES = exports.WA_BUSINESS_HOURS_DAYS = exports.WA_BROWSERS = exports.WA_APP_STATE_SYNC_DATA_TYPE = exports.WA_APP_STATE_KEY_TYPES = exports.WA_APP_STATE_KDF_INFO = exports.WA_APP_STATE_ERROR_CODES = exports.WA_APP_STATE_COLLECTION_STATES = void 0;
|
|
3
|
+
exports.toUserJid = exports.splitJid = exports.signalAddressKey = exports.parseSignalAddressFromJid = exports.parsePhoneJid = exports.parseJidFull = exports.normalizeRecipientJid = exports.normalizeDeviceJid = exports.isUserJid = exports.isStatusBroadcastJid = exports.isNewsletterJid = exports.isLidJid = exports.isHostedServer = exports.isHostedDeviceJid = exports.isHostedDeviceId = exports.isGroupOrBroadcastJid = exports.isGroupJid = exports.isBroadcastJid = exports.isBotJid = exports.getWaMediaHkdfInfo = exports.getWaCompanionPlatformId = exports.getLoginIdentity = exports.canonicalizeSignalServer = exports.canonicalizeSignalJid = exports.buildDeviceJid = exports.delay = exports.WaAuthMemoryStore = exports.createStore = exports.WA_VERSION = exports.createNoopLogger = exports.createPinoLogger = exports.PinoLogger = exports.ConsoleLogger = exports.fetchLatestWaMobileVersion = exports.fetchLatestWaWebVersion = exports.writeRandomPadMax16 = exports.unpadPkcs7 = exports.resolveMediaPayload = exports.getContextInfo = exports.unwrapMessage = exports.resolveMessageTarget = exports.resolveEncMediaType = exports.getContentType = exports.parseUsyncResultEnvelope = exports.createFileCompanionHostPersistence = exports.WaMediaTransferClient = exports.WaMediaCrypto = exports.downloadMediaMessage = exports.defineWaClientPlugin = exports.WaClient = void 0;
|
|
4
|
+
exports.proto = exports.WA_XMLNS = exports.WA_PRIVACY_VALUES = exports.WA_PRIVACY_TAGS = exports.WA_PRIVACY_SETTING_TO_CATEGORY = exports.WA_PRIVACY_DISALLOWED_LIST_CATEGORIES = exports.WA_PRIVACY_CATEGORY_TO_SETTING = exports.WA_PRIVACY_CATEGORIES = exports.WA_SUPPORTED_DIRTY_TYPES = exports.WA_STREAM_SIGNALING = exports.WA_SIGNALING = exports.WA_RETRYABLE_ACK_CODES = exports.WA_READY_STATES = exports.WA_PREVIEW_MEDIA_HKDF_INFO = exports.WA_PAIRING_KDF_INFO = exports.WA_NODE_TAGS = exports.WA_MEDIA_HKDF_INFO = exports.WA_MESSAGE_TYPES = exports.WA_MESSAGE_TAGS = exports.WA_LOGOUT_REASONS = exports.WA_IQ_TYPES = exports.WA_DISCONNECT_REASONS = exports.WA_DIRTY_TYPES = exports.WA_DIRTY_PROTOCOLS = exports.WA_DEFAULTS = exports.WA_COMPANION_PLATFORM_IDS = exports.WA_BUSINESS_HOURS_MODES = exports.WA_BUSINESS_HOURS_DAYS = exports.WA_BROWSERS = exports.WA_APP_STATE_SYNC_DATA_TYPE = exports.WA_APP_STATE_KEY_TYPES = exports.WA_APP_STATE_KDF_INFO = exports.WA_APP_STATE_ERROR_CODES = exports.WA_APP_STATE_COLLECTION_STATES = exports.WA_APP_STATE_COLLECTIONS = exports.WA_ACCOUNT_SYNC_PROTOCOLS = void 0;
|
|
5
5
|
var _client_1 = require("./client");
|
|
6
6
|
Object.defineProperty(exports, "WaClient", { enumerable: true, get: function () { return _client_1.WaClient; } });
|
|
7
7
|
var plugins_1 = require("./client/plugins");
|
|
8
8
|
Object.defineProperty(exports, "defineWaClientPlugin", { enumerable: true, get: function () { return plugins_1.defineWaClientPlugin; } });
|
|
9
9
|
var media_1 = require("./client/media");
|
|
10
10
|
Object.defineProperty(exports, "downloadMediaMessage", { enumerable: true, get: function () { return media_1.downloadMediaMessage; } });
|
|
11
|
+
var WaMediaCrypto_1 = require("./media/crypto/WaMediaCrypto");
|
|
12
|
+
Object.defineProperty(exports, "WaMediaCrypto", { enumerable: true, get: function () { return WaMediaCrypto_1.WaMediaCrypto; } });
|
|
13
|
+
var WaMediaTransferClient_1 = require("./media/transfer/WaMediaTransferClient");
|
|
14
|
+
Object.defineProperty(exports, "WaMediaTransferClient", { enumerable: true, get: function () { return WaMediaTransferClient_1.WaMediaTransferClient; } });
|
|
11
15
|
var companion_host_1 = require("./client/persistence/companion-host");
|
|
12
16
|
Object.defineProperty(exports, "createFileCompanionHostPersistence", { enumerable: true, get: function () { return companion_host_1.createFileCompanionHostPersistence; } });
|
|
13
17
|
var usync_1 = require("./transport/node/builders/usync");
|