fb-messenger-e2ee 0.1.3 → 0.1.5
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 +41 -0
- package/dist/index.cjs +155 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -14
- package/dist/index.d.ts +85 -14
- package/dist/index.js +155 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|
|
@@ -3945,8 +3976,8 @@ init_cjs_shims();
|
|
|
3945
3976
|
var MessageBuilder = class {
|
|
3946
3977
|
content;
|
|
3947
3978
|
replyTo;
|
|
3948
|
-
setReply(
|
|
3949
|
-
this.replyTo =
|
|
3979
|
+
setReply(replyTo) {
|
|
3980
|
+
this.replyTo = replyTo;
|
|
3950
3981
|
return this;
|
|
3951
3982
|
}
|
|
3952
3983
|
getReply() {
|
|
@@ -4170,7 +4201,7 @@ function encodeMessageApplication(consumerAppBytes, replyTo) {
|
|
|
4170
4201
|
const appPayload = new ProtoWriter().bytes(4, payloadSubProto).build();
|
|
4171
4202
|
let metadataWriter = new ProtoWriter().bytes(8, frankingKey).varint(9, 0);
|
|
4172
4203
|
if (replyTo) {
|
|
4173
|
-
const quoted = new ProtoWriter().string(1, replyTo.
|
|
4204
|
+
const quoted = new ProtoWriter().string(1, replyTo.messageId).string(2, replyTo.chatJid).string(3, replyTo.senderJid).build();
|
|
4174
4205
|
metadataWriter = metadataWriter.bytes(10, quoted);
|
|
4175
4206
|
}
|
|
4176
4207
|
const metadata = metadataWriter.build();
|
|
@@ -5542,7 +5573,16 @@ var E2EEClient = class {
|
|
|
5542
5573
|
async buildDMTextFanoutPayloads(opts) {
|
|
5543
5574
|
const builder = new MessageBuilder().setText(opts.text);
|
|
5544
5575
|
if (opts.replyToId && opts.replyToSenderJid) {
|
|
5545
|
-
|
|
5576
|
+
const replyTo = {
|
|
5577
|
+
messageId: opts.replyToId,
|
|
5578
|
+
// chatJid = remoteJID: the thread/chat that holds the quoted message
|
|
5579
|
+
// For DM, this is the peer's bare JID (toJid).
|
|
5580
|
+
chatJid: opts.toJid,
|
|
5581
|
+
// senderJid = participant: who sent the original message.
|
|
5582
|
+
// Caller must supply the correct sender JID via replyToSenderJid.
|
|
5583
|
+
senderJid: opts.replyToSenderJid
|
|
5584
|
+
};
|
|
5585
|
+
builder.setReply(replyTo);
|
|
5546
5586
|
}
|
|
5547
5587
|
const consumerApp = builder.build();
|
|
5548
5588
|
const { messageApp, frankingTag } = encodeMessageApplication(consumerApp, builder.getReply());
|
|
@@ -5577,7 +5617,14 @@ var E2EEClient = class {
|
|
|
5577
5617
|
async encryptGroupText(groupJid, selfJid, text, messageId, replyToId, replyToSenderJid) {
|
|
5578
5618
|
const builder = new MessageBuilder().setText(text);
|
|
5579
5619
|
if (replyToId && replyToSenderJid) {
|
|
5580
|
-
|
|
5620
|
+
const replyTo = {
|
|
5621
|
+
messageId: replyToId,
|
|
5622
|
+
// chatJid = remoteJID: for a group message, this is the group JID
|
|
5623
|
+
chatJid: groupJid,
|
|
5624
|
+
// participant: the specific member who sent the original message
|
|
5625
|
+
senderJid: replyToSenderJid
|
|
5626
|
+
};
|
|
5627
|
+
builder.setReply(replyTo);
|
|
5581
5628
|
}
|
|
5582
5629
|
const consumerApp = builder.build();
|
|
5583
5630
|
const { messageApp, frankingTag } = encodeMessageApplication(consumerApp, builder.getReply());
|
|
@@ -8177,15 +8224,25 @@ var ClientController = class {
|
|
|
8177
8224
|
if (this.e2eeConnected && isE2EE) {
|
|
8178
8225
|
let messageId;
|
|
8179
8226
|
if (isGroup) {
|
|
8180
|
-
messageId = await this.sendE2EEGroupText(
|
|
8227
|
+
messageId = await this.sendE2EEGroupText(
|
|
8228
|
+
input.threadId,
|
|
8229
|
+
input.text,
|
|
8230
|
+
input.replyToMessageId,
|
|
8231
|
+
input.replyToSenderJid
|
|
8232
|
+
);
|
|
8181
8233
|
} else {
|
|
8182
|
-
messageId = await this.sendE2EEText(
|
|
8234
|
+
messageId = await this.sendE2EEText(
|
|
8235
|
+
input.threadId,
|
|
8236
|
+
input.text,
|
|
8237
|
+
input.replyToMessageId,
|
|
8238
|
+
input.replyToSenderJid
|
|
8239
|
+
);
|
|
8183
8240
|
}
|
|
8184
8241
|
return { messageId, timestampMs: now() };
|
|
8185
8242
|
}
|
|
8186
8243
|
return this.messagingService.sendText(this.requireApi(), input);
|
|
8187
8244
|
}
|
|
8188
|
-
async sendE2EEText(threadId, text, replyToMessageId) {
|
|
8245
|
+
async sendE2EEText(threadId, text, replyToMessageId, replyToSenderJid) {
|
|
8189
8246
|
if (!this.e2eeSocket) throw new Error("E2EE not connected");
|
|
8190
8247
|
const e2eeClient = this.e2eeService.getClient();
|
|
8191
8248
|
const selfJid = this.getSelfE2EEJid();
|
|
@@ -8197,7 +8254,9 @@ var ClientController = class {
|
|
|
8197
8254
|
text,
|
|
8198
8255
|
isGroup: false,
|
|
8199
8256
|
replyToId: replyToMessageId,
|
|
8200
|
-
|
|
8257
|
+
// For DM: participant = sender of original msg. If caller knows who sent it, use that;
|
|
8258
|
+
// otherwise fall back to the peer's JID (correct for messages they sent to us).
|
|
8259
|
+
replyToSenderJid: replyToMessageId ? replyToSenderJid ?? toJid : void 0
|
|
8201
8260
|
});
|
|
8202
8261
|
const participantNodes = [];
|
|
8203
8262
|
const deviceJids = uniqueJids(await this.e2eeHandler.getDeviceList([toJid, toBareMessengerJid(selfJid)]));
|
|
@@ -8243,7 +8302,7 @@ var ClientController = class {
|
|
|
8243
8302
|
logger.info("ClientController", `E2EE DM message sent to ${toJid} with ${participantNodes.length} devices`);
|
|
8244
8303
|
return messageId;
|
|
8245
8304
|
}
|
|
8246
|
-
async sendE2EEGroupText(groupJid, text, replyToMessageId) {
|
|
8305
|
+
async sendE2EEGroupText(groupJid, text, replyToMessageId, replyToSenderJid) {
|
|
8247
8306
|
if (!this.e2eeSocket) throw new Error("E2EE not connected");
|
|
8248
8307
|
const e2eeClient = this.e2eeService.getClient();
|
|
8249
8308
|
const selfJid = this.getSelfE2EEJid();
|
|
@@ -8259,7 +8318,8 @@ var ClientController = class {
|
|
|
8259
8318
|
text,
|
|
8260
8319
|
messageId,
|
|
8261
8320
|
replyToMessageId,
|
|
8262
|
-
|
|
8321
|
+
replyToSenderJid
|
|
8322
|
+
// pass through — undefined when not replying
|
|
8263
8323
|
);
|
|
8264
8324
|
const participantNodes = [];
|
|
8265
8325
|
for (const deviceJid of deviceJids) {
|
|
@@ -8527,7 +8587,7 @@ var ClientController = class {
|
|
|
8527
8587
|
async markAsRead(input) {
|
|
8528
8588
|
await this.messagingService.markAsRead(this.requireApi(), input);
|
|
8529
8589
|
}
|
|
8530
|
-
//
|
|
8590
|
+
// E2EE Media Upload Config
|
|
8531
8591
|
async getE2EEMediaUploadConfig() {
|
|
8532
8592
|
if (this.e2eeUploadConfig && !this.isMediaUploadConfigExpired(this.e2eeUploadConfig)) {
|
|
8533
8593
|
return this.e2eeUploadConfig;
|
|
@@ -8599,7 +8659,11 @@ var ClientController = class {
|
|
|
8599
8659
|
const consumerApp = buildMessage(media.mediaFields);
|
|
8600
8660
|
const { messageApp, frankingTag } = e2eeClient.buildMessageApplication(
|
|
8601
8661
|
consumerApp,
|
|
8602
|
-
input.replyToMessageId ? {
|
|
8662
|
+
input.replyToMessageId ? {
|
|
8663
|
+
messageId: input.replyToMessageId,
|
|
8664
|
+
chatJid: toJid,
|
|
8665
|
+
senderJid: input.replyToSenderJid ?? toJid
|
|
8666
|
+
} : void 0
|
|
8603
8667
|
);
|
|
8604
8668
|
const devicePayload = e2eeClient.buildMessageTransport({ messageApp });
|
|
8605
8669
|
const selfDevicePayload = e2eeClient.buildMessageTransport({
|
|
@@ -8674,6 +8738,62 @@ var ClientController = class {
|
|
|
8674
8738
|
}
|
|
8675
8739
|
return this.mediaService.sendFile(this.requireApi(), input);
|
|
8676
8740
|
}
|
|
8741
|
+
/**
|
|
8742
|
+
* Send multiple files/attachments.
|
|
8743
|
+
*
|
|
8744
|
+
* - **Non-E2EE threads**: All files are bundled into a single FCA message.
|
|
8745
|
+
* - **E2EE threads**: Files are sent as separate sequential encrypted E2EE
|
|
8746
|
+
* media messages (the protocol does not support multi-attachment in one
|
|
8747
|
+
* encrypted frame). Returns an array with one result per attachment.
|
|
8748
|
+
*/
|
|
8749
|
+
async sendFiles(input) {
|
|
8750
|
+
if (input.attachments.length === 0) {
|
|
8751
|
+
throw new Error("sendFiles requires at least one attachment");
|
|
8752
|
+
}
|
|
8753
|
+
if (this.e2eeConnected && this.isE2EEThreadId(input.threadId)) {
|
|
8754
|
+
const results = [];
|
|
8755
|
+
for (let i = 0; i < input.attachments.length; i++) {
|
|
8756
|
+
const att = input.attachments[i];
|
|
8757
|
+
const mediaInput = {
|
|
8758
|
+
threadId: input.threadId,
|
|
8759
|
+
data: att.data,
|
|
8760
|
+
fileName: att.fileName,
|
|
8761
|
+
mimeType: att.mimeType,
|
|
8762
|
+
// Caption on first attachment only
|
|
8763
|
+
caption: i === 0 ? input.caption : void 0,
|
|
8764
|
+
replyToMessageId: i === 0 ? input.replyToMessageId : void 0,
|
|
8765
|
+
width: att.width,
|
|
8766
|
+
height: att.height,
|
|
8767
|
+
seconds: att.seconds,
|
|
8768
|
+
ptt: att.ptt
|
|
8769
|
+
};
|
|
8770
|
+
const mediaType = inferMediaType(att.fileName, att.mimeType);
|
|
8771
|
+
let result;
|
|
8772
|
+
switch (mediaType) {
|
|
8773
|
+
case "image":
|
|
8774
|
+
result = await this.sendE2EEImage(mediaInput);
|
|
8775
|
+
break;
|
|
8776
|
+
case "video":
|
|
8777
|
+
result = await this.sendE2EEVideo(mediaInput);
|
|
8778
|
+
break;
|
|
8779
|
+
case "audio":
|
|
8780
|
+
result = await this.sendE2EEAudio(mediaInput);
|
|
8781
|
+
break;
|
|
8782
|
+
default:
|
|
8783
|
+
result = await this.sendE2EEFile(mediaInput);
|
|
8784
|
+
break;
|
|
8785
|
+
}
|
|
8786
|
+
results.push(result);
|
|
8787
|
+
}
|
|
8788
|
+
return results;
|
|
8789
|
+
}
|
|
8790
|
+
return this.mediaService.sendFiles(this.requireApi(), {
|
|
8791
|
+
threadId: input.threadId,
|
|
8792
|
+
attachments: input.attachments.map((a) => ({ data: a.data, fileName: a.fileName })),
|
|
8793
|
+
caption: input.caption,
|
|
8794
|
+
replyToMessageId: input.replyToMessageId
|
|
8795
|
+
});
|
|
8796
|
+
}
|
|
8677
8797
|
async sendSticker(input) {
|
|
8678
8798
|
return this.mediaService.sendSticker(this.requireApi(), input);
|
|
8679
8799
|
}
|
|
@@ -8713,7 +8833,6 @@ var ClientController = class {
|
|
|
8713
8833
|
async createPoll(input) {
|
|
8714
8834
|
await this.threadService.createPoll(this.requireApi(), input);
|
|
8715
8835
|
}
|
|
8716
|
-
// editMessage is now handled by the E2EE-aware method above (routes E2EE → sendE2EEEdit, else → threadService.editMessage).
|
|
8717
8836
|
async addGroupMember(input) {
|
|
8718
8837
|
await this.threadService.addGroupMember(this.requireApi(), input);
|
|
8719
8838
|
}
|
|
@@ -8731,6 +8850,17 @@ var ClientController = class {
|
|
|
8731
8850
|
return this.api;
|
|
8732
8851
|
}
|
|
8733
8852
|
};
|
|
8853
|
+
function inferMediaType(fileName, mimeType) {
|
|
8854
|
+
const mime = (mimeType ?? "").toLowerCase();
|
|
8855
|
+
if (mime.startsWith("image/")) return "image";
|
|
8856
|
+
if (mime.startsWith("video/")) return "video";
|
|
8857
|
+
if (mime.startsWith("audio/")) return "audio";
|
|
8858
|
+
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
8859
|
+
if (["jpg", "jpeg", "png", "gif", "webp", "heic", "heif", "bmp"].includes(ext)) return "image";
|
|
8860
|
+
if (["mp4", "mov", "avi", "mkv", "webm", "3gp", "m4v"].includes(ext)) return "video";
|
|
8861
|
+
if (["mp3", "ogg", "aac", "flac", "wav", "m4a", "opus"].includes(ext)) return "audio";
|
|
8862
|
+
return "document";
|
|
8863
|
+
}
|
|
8734
8864
|
|
|
8735
8865
|
// src/repositories/session.repository.ts
|
|
8736
8866
|
init_cjs_shims();
|
|
@@ -8992,6 +9122,17 @@ var FBClient = class {
|
|
|
8992
9122
|
async sendFile(input) {
|
|
8993
9123
|
return this.controller.sendFile(input);
|
|
8994
9124
|
}
|
|
9125
|
+
/**
|
|
9126
|
+
* Send multiple files/attachments in one call.
|
|
9127
|
+
*
|
|
9128
|
+
* - **Non-E2EE threads**: All attachments land in a single Messenger message.
|
|
9129
|
+
* - **E2EE threads**: Each attachment is sent as a separate encrypted message
|
|
9130
|
+
* (E2EE does not support multi-attachment in one frame). Returns an array
|
|
9131
|
+
* with one result per attachment.
|
|
9132
|
+
*/
|
|
9133
|
+
async sendFiles(input) {
|
|
9134
|
+
return this.controller.sendFiles(input);
|
|
9135
|
+
}
|
|
8995
9136
|
};
|
|
8996
9137
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8997
9138
|
0 && (module.exports = {
|