zapo-js 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/client/WaClient.d.ts +1 -1
  2. package/dist/client/WaClient.js +1 -1
  3. package/dist/client/WaClientFactory.js +3 -1
  4. package/dist/client/coordinators/WaMessageCoordinator.d.ts +69 -1
  5. package/dist/client/coordinators/WaMessageCoordinator.js +76 -1
  6. package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +2 -2
  7. package/dist/client/coordinators/WaMessageDispatchCoordinator.js +6 -36
  8. package/dist/client/coordinators/WaPrivacyCoordinator.d.ts +18 -5
  9. package/dist/client/coordinators/WaPrivacyCoordinator.js +29 -4
  10. package/dist/client/media.d.ts +1 -0
  11. package/dist/client/media.js +1 -0
  12. package/dist/client/messaging/messages.d.ts +45 -1
  13. package/dist/client/messaging/messages.js +57 -45
  14. package/dist/esm/client/WaClient.js +1 -1
  15. package/dist/esm/client/WaClientFactory.js +3 -1
  16. package/dist/esm/client/coordinators/WaMessageCoordinator.js +78 -3
  17. package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +6 -36
  18. package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +30 -5
  19. package/dist/esm/client/media.js +1 -1
  20. package/dist/esm/client/messaging/messages.js +56 -45
  21. package/dist/esm/index.js +2 -0
  22. package/dist/esm/signal/api/SignalDeviceSyncApi.js +62 -1
  23. package/dist/esm/transport/WaComms.js +7 -0
  24. package/dist/esm/transport/keepalive/WaKeepAlive.js +24 -7
  25. package/dist/esm/transport/node/builders/privacy.js +32 -2
  26. package/dist/index.d.ts +4 -1
  27. package/dist/index.js +6 -2
  28. package/dist/signal/api/SignalDeviceSyncApi.d.ts +27 -0
  29. package/dist/signal/api/SignalDeviceSyncApi.js +61 -0
  30. package/dist/transport/WaComms.d.ts +3 -0
  31. package/dist/transport/WaComms.js +7 -0
  32. package/dist/transport/keepalive/WaKeepAlive.d.ts +8 -2
  33. package/dist/transport/keepalive/WaKeepAlive.js +24 -7
  34. package/dist/transport/node/builders/privacy.d.ts +26 -1
  35. package/dist/transport/node/builders/privacy.js +34 -3
  36. 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;
@@ -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
  }
@@ -375,7 +375,8 @@ function buildWaClientDependencies(input) {
375
375
  logger
376
376
  });
377
377
  const privacyCoordinator = (0, WaPrivacyCoordinator_1.createPrivacyCoordinator)({
378
- queryWithContext: runtime.queryWithContext
378
+ queryWithContext: runtime.queryWithContext,
379
+ resolveUserJidPair: (userJid) => signalDeviceSync.resolveUserJidPair(userJid)
379
380
  });
380
381
  const businessCoordinator = (0, WaBusinessCoordinator_1.createBusinessCoordinator)({
381
382
  queryWithContext: runtime.queryWithContext,
@@ -504,6 +505,7 @@ function buildWaClientDependencies(input) {
504
505
  const messageCoordinator = new WaMessageCoordinator_1.WaMessageCoordinator({
505
506
  messageDispatch,
506
507
  mediaTransfer,
508
+ mediaUploadOptions: mediaMessageBuildOptions,
507
509
  logger,
508
510
  messageStore: sessionStore.messages,
509
511
  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
@@ -80,8 +80,8 @@ export declare class WaMessageDispatchCoordinator {
80
80
  sendMessage(to: string, content: WaSendMessageContent, options?: WaSendMessageOptions): Promise<WaMessagePublishResult>;
81
81
  /**
82
82
  * For a 1:1 recipient passed in PN form, returns the LID-addressed user JID
83
- * (cache-first; falls back to a one-shot `queryLidsByPhoneJids`). Switching
84
- * to LID before fanout ensures the envelope, eligible-requester list, and
83
+ * (via {@link SignalDeviceSyncApi.resolveUserJidPair}). Switching to LID
84
+ * before fanout ensures the envelope, eligible-requester list, and
85
85
  * retry-receipt addressing all agree, which keeps the retry tracker from
86
86
  * rejecting receipts that arrive in LID form. Returns the original PN if
87
87
  * no LID is known/resolvable. Inputs already in LID form pass through.
@@ -300,8 +300,8 @@ class WaMessageDispatchCoordinator {
300
300
  }
301
301
  /**
302
302
  * For a 1:1 recipient passed in PN form, returns the LID-addressed user JID
303
- * (cache-first; falls back to a one-shot `queryLidsByPhoneJids`). Switching
304
- * to LID before fanout ensures the envelope, eligible-requester list, and
303
+ * (via {@link SignalDeviceSyncApi.resolveUserJidPair}). Switching to LID
304
+ * before fanout ensures the envelope, eligible-requester list, and
305
305
  * retry-receipt addressing all agree, which keeps the retry tracker from
306
306
  * rejecting receipts that arrive in LID form. Returns the original PN if
307
307
  * no LID is known/resolvable. Inputs already in LID form pass through.
@@ -309,26 +309,8 @@ class WaMessageDispatchCoordinator {
309
309
  async resolveDirectRecipientLid(pnUserJid) {
310
310
  if ((0, jid_1.isLidJid)(pnUserJid))
311
311
  return pnUserJid;
312
- const cached = await this.deps.deviceListStore.findByAnyUserJid(pnUserJid);
313
- if (cached) {
314
- if ((0, jid_1.isLidJid)(cached.userJid))
315
- return cached.userJid;
316
- if (cached.altUserJid && (0, jid_1.isLidJid)(cached.altUserJid))
317
- return cached.altUserJid;
318
- }
319
- try {
320
- const results = await this.deps.signalDeviceSync.queryLidsByPhoneJids([pnUserJid]);
321
- const match = results.find((entry) => entry.queriedJid === pnUserJid);
322
- if (match?.lidJid)
323
- return match.lidJid;
324
- }
325
- catch (error) {
326
- this.deps.logger.debug('lid resolution failed for direct recipient', {
327
- pnUserJid,
328
- message: (0, primitives_2.toError)(error).message
329
- });
330
- }
331
- return pnUserJid;
312
+ const pair = await this.deps.signalDeviceSync.resolveUserJidPair(pnUserJid);
313
+ return pair.lidJid ?? pnUserJid;
332
314
  }
333
315
  /**
334
316
  * Resolves the `peer_recipient_pn` cross-reference for a 1:1 send, or
@@ -344,20 +326,8 @@ class WaMessageDispatchCoordinator {
344
326
  return undefined;
345
327
  if ((0, jid_1.isUserJid)(recipientUserJid))
346
328
  return recipientUserJid;
347
- try {
348
- const snapshot = await this.deps.deviceListStore.findByAnyUserJid(directRecipientJid);
349
- if (snapshot?.userJid && (0, jid_1.isUserJid)(snapshot.userJid))
350
- return snapshot.userJid;
351
- if (snapshot?.altUserJid && (0, jid_1.isUserJid)(snapshot.altUserJid))
352
- return snapshot.altUserJid;
353
- }
354
- catch (error) {
355
- this.deps.logger.trace('peer_recipient_pn store lookup failed', {
356
- lid: directRecipientJid,
357
- message: (0, primitives_2.toError)(error).message
358
- });
359
- }
360
- return undefined;
329
+ const pair = await this.deps.signalDeviceSync.resolveUserJidPair(directRecipientJid);
330
+ return pair.pnJid ?? undefined;
361
331
  }
362
332
  async syncSignalSession(jid, reasonIdentity = false) {
363
333
  const address = (0, jid_1.parseSignalAddressFromJid)(jid);
@@ -1,4 +1,5 @@
1
1
  import { type WaPrivacyDisallowedListSettingName, type WaPrivacySettingName, type WaPrivacySettingValueMap } from '../../protocol/privacy';
2
+ import type { SignalUserJidPair } from '../../signal/api/SignalDeviceSyncApi';
2
3
  import type { BinaryNode } from '../../transport/types';
3
4
  export type WaPrivacySettings = {
4
5
  readonly [K in WaPrivacySettingName]?: WaPrivacySettingValueMap[K];
@@ -36,17 +37,29 @@ export interface WaPrivacyCoordinator {
36
37
  /** Returns the current account-wide blocklist. */
37
38
  readonly getBlocklist: () => Promise<WaBlocklistResult>;
38
39
  /**
39
- * Blocks `jid` (account-wide blocklist). After this, the peer can no
40
- * longer message/call you and cannot see your last seen/online/photo/
41
- * status. The block is symmetric only from the peer's read perspective -
42
- * they don't get an explicit "you were blocked" notification.
40
+ * Blocks a user (account-wide blocklist). Accepts a phone-number jid, a
41
+ * LID jid, or a bare phone number (digits only). After this, the peer can
42
+ * no longer
43
+ * message/call you and cannot see your last seen/online/photo/status. The
44
+ * block is symmetric only from the peer's read perspective - they don't
45
+ * get an explicit "you were blocked" notification.
46
+ *
47
+ * The server keys blocklist entries by LID for migrated accounts, so a
48
+ * phone-number input is resolved to its LID first (device-list cache,
49
+ * then a usync query). Non-migrated accounts fall back to the plain
50
+ * phone-jid form.
43
51
  */
44
52
  readonly blockUser: (jid: string) => Promise<void>;
45
- /** Removes `jid` from the blocklist. */
53
+ /**
54
+ * Removes a user from the blocklist. Accepts the same inputs as
55
+ * {@link blockUser} and performs the same LID resolution - unblocking a
56
+ * migrated entry by phone jid is rejected by the server.
57
+ */
46
58
  readonly unblockUser: (jid: string) => Promise<void>;
47
59
  }
48
60
  interface WaPrivacyCoordinatorOptions {
49
61
  readonly queryWithContext: (context: string, node: BinaryNode, timeoutMs?: number, contextData?: Readonly<Record<string, unknown>>) => Promise<BinaryNode>;
62
+ readonly resolveUserJidPair: (userJid: string) => Promise<SignalUserJidPair>;
50
63
  }
51
64
  /** Builds a {@link WaPrivacyCoordinator} backed by the given IQ query function. */
52
65
  export declare function createPrivacyCoordinator(options: WaPrivacyCoordinatorOptions): WaPrivacyCoordinator;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createPrivacyCoordinator = createPrivacyCoordinator;
4
+ const jid_1 = require("../../protocol/jid");
4
5
  const nodes_1 = require("../../protocol/nodes");
5
6
  const privacy_1 = require("../../protocol/privacy");
6
7
  const privacy_2 = require("../../transport/node/builders/privacy");
@@ -88,6 +89,23 @@ function parseBlocklist(result) {
88
89
  jids.length = jidsCount;
89
90
  return { jids, dhash };
90
91
  }
92
+ /**
93
+ * Resolves a blocklist input into both addressing forms via
94
+ * `resolveUserJidPair` (device-list cache first, usync fallback for phone
95
+ * jids). Resolution failures degrade to the single known form instead of
96
+ * throwing - the server then decides whether that form is acceptable.
97
+ */
98
+ async function resolveBlocklistTarget(options, jid) {
99
+ const normalized = (0, jid_1.normalizeRecipientJid)(jid);
100
+ if (!(0, jid_1.isLidJid)(normalized) && !(0, jid_1.isUserJid)(normalized)) {
101
+ throw new Error(`blocklist target must be a user jid: ${jid}`);
102
+ }
103
+ const pair = await options.resolveUserJidPair(normalized);
104
+ if (pair.lidJid !== null) {
105
+ return { lidJid: pair.lidJid, pnJid: pair.pnJid };
106
+ }
107
+ return { lidJid: null, pnJid: pair.pnJid ?? normalized };
108
+ }
91
109
  /** Builds a {@link WaPrivacyCoordinator} backed by the given IQ query function. */
92
110
  function createPrivacyCoordinator(options) {
93
111
  const { queryWithContext } = options;
@@ -123,13 +141,20 @@ function createPrivacyCoordinator(options) {
123
141
  return parseBlocklist(result);
124
142
  },
125
143
  blockUser: async (jid) => {
126
- const node = (0, privacy_2.buildBlocklistChangeIq)(jid, 'block');
127
- const result = await queryWithContext('privacy.blockUser', node, undefined, { jid });
144
+ const target = await resolveBlocklistTarget(options, jid);
145
+ const node = (0, privacy_2.buildBlocklistBlockIq)(target);
146
+ const result = await queryWithContext('privacy.blockUser', node, undefined, {
147
+ jid: target.lidJid ?? target.pnJid
148
+ });
128
149
  (0, query_1.assertIqResult)(result, 'privacy.blockUser');
129
150
  },
130
151
  unblockUser: async (jid) => {
131
- const node = (0, privacy_2.buildBlocklistChangeIq)(jid, 'unblock');
132
- const result = await queryWithContext('privacy.unblockUser', node, undefined, { jid });
152
+ const target = await resolveBlocklistTarget(options, jid);
153
+ const unblockJid = target.lidJid ?? target.pnJid;
154
+ const node = (0, privacy_2.buildBlocklistUnblockIq)(unblockJid);
155
+ const result = await queryWithContext('privacy.unblockUser', node, undefined, {
156
+ jid: unblockJid
157
+ });
133
158
  (0, query_1.assertIqResult)(result, 'privacy.unblockUser');
134
159
  }
135
160
  };
@@ -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';
@@ -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>;