fb-messenger-e2ee 0.1.3 → 0.1.4

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/DOCS.md CHANGED
@@ -217,6 +217,47 @@ await client.sendImage({
217
217
  });
218
218
  ```
219
219
 
220
+ ### `sendFiles`
221
+
222
+ Sends multiple files/attachments in a single call.
223
+
224
+ - **Non-E2EE threads**: Bundles all files into a single Messenger message containing multiple attachments.
225
+ - **E2EE threads**: Since the Messenger E2EE transport protocol does not support multi-attachment payloads in a single encrypted frame, the attachments are sent sequentially as separate encrypted messages. Optional fields like `caption` and `replyToMessageId` are only applied to the first attachment.
226
+
227
+ Returns either a single result object (for non-E2EE) or an array of result objects (one per attachment for E2EE).
228
+
229
+ ```typescript
230
+ await client.sendFiles({
231
+ threadId: "1234567890.0@msgr",
232
+ caption: "Here are some files",
233
+ attachments: [
234
+ { data: imageBuffer, fileName: "photo.jpg" },
235
+ { data: pdfBuffer, fileName: "document.pdf" }
236
+ ]
237
+ });
238
+ ```
239
+
240
+ **SendMultipleMediaInput**
241
+
242
+ | Field | Type | Description |
243
+ |---|---|---|
244
+ | `threadId` | `string` | The target thread identifier. |
245
+ | `attachments` | `SendAttachmentItem[]` | An array of attachment objects to send. |
246
+ | `caption` | `string` | Optional caption. Applied to the bundled message for non-E2EE, or to the first attachment for E2EE. |
247
+ | `replyToMessageId` | `string` | Optional message ID to reply to. |
248
+
249
+ **SendAttachmentItem**
250
+
251
+ | Field | Type | Description |
252
+ |---|---|---|
253
+ | `data` | `Buffer` | The raw file bytes. |
254
+ | `fileName` | `string` | The filename. |
255
+ | `mimeType` | `string` | Optional MIME type (inferred from filename extension if omitted). |
256
+ | `width` | `number` | Optional width in pixels (for E2EE images/videos). |
257
+ | `height` | `number` | Optional height in pixels (for E2EE images/videos). |
258
+ | `seconds` | `number` | Optional duration in seconds (for E2EE videos/audios). |
259
+ | `ptt` | `boolean` | Optional flag indicating if audio should be sent as voice note (push-to-talk). |
260
+
220
261
  ---
221
262
 
222
263
  ## Event Handling
package/dist/index.cjs CHANGED
@@ -3472,6 +3472,30 @@ var FacebookGatewayService = class {
3472
3472
  );
3473
3473
  return response ?? {};
3474
3474
  }
3475
+ /**
3476
+ * Send multiple attachments in a single FCA message.
3477
+ * FCA-unofficial accepts an array in `attachment` field, which bundles all
3478
+ * files into one Messenger message on the wire.
3479
+ */
3480
+ async sendMultipleAttachmentsMessage(api, input) {
3481
+ if (input.attachments.length === 0) {
3482
+ throw new Error("sendMultipleAttachmentsMessage requires at least one attachment");
3483
+ }
3484
+ const streams = input.attachments.map(({ data, fileName }) => {
3485
+ const stream = import_node_stream.Readable.from(data);
3486
+ Object.assign(stream, { path: fileName });
3487
+ return stream;
3488
+ });
3489
+ const payload = {
3490
+ body: input.caption ?? "",
3491
+ // FCA-unofficial accepts a single Readable or an array of Readables
3492
+ attachment: streams.length === 1 ? streams[0] : streams
3493
+ };
3494
+ const response = await Promise.resolve(
3495
+ api.sendMessage(payload, input.threadId, void 0, input.replyToMessageId)
3496
+ );
3497
+ return response ?? {};
3498
+ }
3475
3499
  async sendReaction(api, messageId, reaction) {
3476
3500
  if (!api.setMessageReaction) {
3477
3501
  throw new Error("setMessageReaction is not available in fca-unofficial");
@@ -3648,6 +3672,13 @@ var MediaService = class {
3648
3672
  replyToMessageId: input.replyToMessageId
3649
3673
  });
3650
3674
  }
3675
+ /**
3676
+ * Send multiple attachments bundled into a single FCA message.
3677
+ * Non-E2EE only — for E2EE threads use the controller which sends them sequentially.
3678
+ */
3679
+ async sendFiles(api, input) {
3680
+ return this.gateway.sendMultipleAttachmentsMessage(api, input);
3681
+ }
3651
3682
  async sendSticker(api, input) {
3652
3683
  return this.gateway.sendStickerMessage(api, {
3653
3684
  threadId: input.threadId,
@@ -8527,7 +8558,7 @@ var ClientController = class {
8527
8558
  async markAsRead(input) {
8528
8559
  await this.messagingService.markAsRead(this.requireApi(), input);
8529
8560
  }
8530
- // --- E2EE Media Upload Config ---
8561
+ // E2EE Media Upload Config
8531
8562
  async getE2EEMediaUploadConfig() {
8532
8563
  if (this.e2eeUploadConfig && !this.isMediaUploadConfigExpired(this.e2eeUploadConfig)) {
8533
8564
  return this.e2eeUploadConfig;
@@ -8674,6 +8705,62 @@ var ClientController = class {
8674
8705
  }
8675
8706
  return this.mediaService.sendFile(this.requireApi(), input);
8676
8707
  }
8708
+ /**
8709
+ * Send multiple files/attachments.
8710
+ *
8711
+ * - **Non-E2EE threads**: All files are bundled into a single FCA message.
8712
+ * - **E2EE threads**: Files are sent as separate sequential encrypted E2EE
8713
+ * media messages (the protocol does not support multi-attachment in one
8714
+ * encrypted frame). Returns an array with one result per attachment.
8715
+ */
8716
+ async sendFiles(input) {
8717
+ if (input.attachments.length === 0) {
8718
+ throw new Error("sendFiles requires at least one attachment");
8719
+ }
8720
+ if (this.e2eeConnected && this.isE2EEThreadId(input.threadId)) {
8721
+ const results = [];
8722
+ for (let i = 0; i < input.attachments.length; i++) {
8723
+ const att = input.attachments[i];
8724
+ const mediaInput = {
8725
+ threadId: input.threadId,
8726
+ data: att.data,
8727
+ fileName: att.fileName,
8728
+ mimeType: att.mimeType,
8729
+ // Caption on first attachment only
8730
+ caption: i === 0 ? input.caption : void 0,
8731
+ replyToMessageId: i === 0 ? input.replyToMessageId : void 0,
8732
+ width: att.width,
8733
+ height: att.height,
8734
+ seconds: att.seconds,
8735
+ ptt: att.ptt
8736
+ };
8737
+ const mediaType = inferMediaType(att.fileName, att.mimeType);
8738
+ let result;
8739
+ switch (mediaType) {
8740
+ case "image":
8741
+ result = await this.sendE2EEImage(mediaInput);
8742
+ break;
8743
+ case "video":
8744
+ result = await this.sendE2EEVideo(mediaInput);
8745
+ break;
8746
+ case "audio":
8747
+ result = await this.sendE2EEAudio(mediaInput);
8748
+ break;
8749
+ default:
8750
+ result = await this.sendE2EEFile(mediaInput);
8751
+ break;
8752
+ }
8753
+ results.push(result);
8754
+ }
8755
+ return results;
8756
+ }
8757
+ return this.mediaService.sendFiles(this.requireApi(), {
8758
+ threadId: input.threadId,
8759
+ attachments: input.attachments.map((a) => ({ data: a.data, fileName: a.fileName })),
8760
+ caption: input.caption,
8761
+ replyToMessageId: input.replyToMessageId
8762
+ });
8763
+ }
8677
8764
  async sendSticker(input) {
8678
8765
  return this.mediaService.sendSticker(this.requireApi(), input);
8679
8766
  }
@@ -8713,7 +8800,6 @@ var ClientController = class {
8713
8800
  async createPoll(input) {
8714
8801
  await this.threadService.createPoll(this.requireApi(), input);
8715
8802
  }
8716
- // editMessage is now handled by the E2EE-aware method above (routes E2EE → sendE2EEEdit, else → threadService.editMessage).
8717
8803
  async addGroupMember(input) {
8718
8804
  await this.threadService.addGroupMember(this.requireApi(), input);
8719
8805
  }
@@ -8731,6 +8817,17 @@ var ClientController = class {
8731
8817
  return this.api;
8732
8818
  }
8733
8819
  };
8820
+ function inferMediaType(fileName, mimeType) {
8821
+ const mime = (mimeType ?? "").toLowerCase();
8822
+ if (mime.startsWith("image/")) return "image";
8823
+ if (mime.startsWith("video/")) return "video";
8824
+ if (mime.startsWith("audio/")) return "audio";
8825
+ const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
8826
+ if (["jpg", "jpeg", "png", "gif", "webp", "heic", "heif", "bmp"].includes(ext)) return "image";
8827
+ if (["mp4", "mov", "avi", "mkv", "webm", "3gp", "m4v"].includes(ext)) return "video";
8828
+ if (["mp3", "ogg", "aac", "flac", "wav", "m4a", "opus"].includes(ext)) return "audio";
8829
+ return "document";
8830
+ }
8734
8831
 
8735
8832
  // src/repositories/session.repository.ts
8736
8833
  init_cjs_shims();
@@ -8992,6 +9089,17 @@ var FBClient = class {
8992
9089
  async sendFile(input) {
8993
9090
  return this.controller.sendFile(input);
8994
9091
  }
9092
+ /**
9093
+ * Send multiple files/attachments in one call.
9094
+ *
9095
+ * - **Non-E2EE threads**: All attachments land in a single Messenger message.
9096
+ * - **E2EE threads**: Each attachment is sent as a separate encrypted message
9097
+ * (E2EE does not support multi-attachment in one frame). Returns an array
9098
+ * with one result per attachment.
9099
+ */
9100
+ async sendFiles(input) {
9101
+ return this.controller.sendFiles(input);
9102
+ }
8995
9103
  };
8996
9104
  // Annotate the CommonJS export names for ESM import in node:
8997
9105
  0 && (module.exports = {