meta-messenger.js 0.0.2 → 0.0.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/dist/index.js ADDED
@@ -0,0 +1,1098 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/client.ts
5
+ import { EventEmitter } from "events";
6
+
7
+ // src/native.ts
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import koffi from "koffi";
12
+ function resolveDirname() {
13
+ return path.dirname(fileURLToPath(import.meta.url));
14
+ }
15
+ __name(resolveDirname, "resolveDirname");
16
+ function libPath() {
17
+ const base = path.join(resolveDirname(), "..", "build");
18
+ if (process.platform === "win32") return path.join(base, "messagix.dll");
19
+ if (process.platform === "darwin") return path.join(base, "messagix.dylib");
20
+ return path.join(base, "messagix.so");
21
+ }
22
+ __name(libPath, "libPath");
23
+ var LIB_FILE = libPath();
24
+ if (!fs.existsSync(LIB_FILE)) {
25
+ throw new Error(`Native library not found at ${LIB_FILE}. Run: npm run build:go`);
26
+ }
27
+ var lib = koffi.load(LIB_FILE);
28
+ var mk = /* @__PURE__ */ __name((ret, name, args) => lib.func(name, ret, args), "mk");
29
+ var fns = {
30
+ MxFreeCString: mk("void", "MxFreeCString", ["char*"]),
31
+ MxNewClient: mk("str", "MxNewClient", ["str"]),
32
+ MxConnect: mk("str", "MxConnect", ["str"]),
33
+ MxConnectE2EE: mk("str", "MxConnectE2EE", ["str"]),
34
+ MxDisconnect: mk("str", "MxDisconnect", ["str"]),
35
+ MxIsConnected: mk("str", "MxIsConnected", ["str"]),
36
+ MxSendMessage: mk("str", "MxSendMessage", ["str"]),
37
+ MxSendReaction: mk("str", "MxSendReaction", ["str"]),
38
+ MxEditMessage: mk("str", "MxEditMessage", ["str"]),
39
+ MxUnsendMessage: mk("str", "MxUnsendMessage", ["str"]),
40
+ MxSendTyping: mk("str", "MxSendTyping", ["str"]),
41
+ MxMarkRead: mk("str", "MxMarkRead", ["str"]),
42
+ MxUploadMedia: mk("str", "MxUploadMedia", ["str"]),
43
+ MxSendImage: mk("str", "MxSendImage", ["str"]),
44
+ MxSendVideo: mk("str", "MxSendVideo", ["str"]),
45
+ MxSendVoice: mk("str", "MxSendVoice", ["str"]),
46
+ MxSendFile: mk("str", "MxSendFile", ["str"]),
47
+ MxSendSticker: mk("str", "MxSendSticker", ["str"]),
48
+ MxCreateThread: mk("str", "MxCreateThread", ["str"]),
49
+ MxGetUserInfo: mk("str", "MxGetUserInfo", ["str"]),
50
+ MxSetGroupPhoto: mk("str", "MxSetGroupPhoto", ["str"]),
51
+ MxRenameThread: mk("str", "MxRenameThread", ["str"]),
52
+ MxMuteThread: mk("str", "MxMuteThread", ["str"]),
53
+ MxDeleteThread: mk("str", "MxDeleteThread", ["str"]),
54
+ MxSearchUsers: mk("str", "MxSearchUsers", ["str"]),
55
+ MxPollEvents: mk("str", "MxPollEvents", ["str"]),
56
+ MxSendE2EEMessage: mk("str", "MxSendE2EEMessage", ["str"]),
57
+ MxSendE2EEReaction: mk("str", "MxSendE2EEReaction", ["str"]),
58
+ MxSendE2EETyping: mk("str", "MxSendE2EETyping", ["str"]),
59
+ MxEditE2EEMessage: mk("str", "MxEditE2EEMessage", ["str"]),
60
+ MxUnsendE2EEMessage: mk("str", "MxUnsendE2EEMessage", ["str"]),
61
+ MxGetDeviceData: mk("str", "MxGetDeviceData", ["str"]),
62
+ // E2EE Media functions
63
+ MxSendE2EEImage: mk("str", "MxSendE2EEImage", ["str"]),
64
+ MxSendE2EEVideo: mk("str", "MxSendE2EEVideo", ["str"]),
65
+ MxSendE2EEAudio: mk("str", "MxSendE2EEAudio", ["str"]),
66
+ MxSendE2EEDocument: mk("str", "MxSendE2EEDocument", ["str"]),
67
+ MxSendE2EESticker: mk("str", "MxSendE2EESticker", ["str"])
68
+ };
69
+ function call(fn, payload) {
70
+ const input = JSON.stringify(payload);
71
+ const bound = fns[fn];
72
+ const out = bound(input);
73
+ const data = JSON.parse(out);
74
+ if (!data.ok) throw new Error(data.error || "Unknown error");
75
+ return data.data;
76
+ }
77
+ __name(call, "call");
78
+ function callAsync(fn, payload) {
79
+ return new Promise((resolve, reject) => {
80
+ setTimeout(() => {
81
+ try {
82
+ const result = call(fn, payload);
83
+ resolve(result);
84
+ } catch (err) {
85
+ reject(err);
86
+ }
87
+ }, 0);
88
+ });
89
+ }
90
+ __name(callAsync, "callAsync");
91
+ var native = {
92
+ newClient: /* @__PURE__ */ __name((cfg) => call("MxNewClient", cfg), "newClient"),
93
+ connect: /* @__PURE__ */ __name((handle) => call("MxConnect", { handle }), "connect"),
94
+ connectE2EE: /* @__PURE__ */ __name((handle) => callAsync("MxConnectE2EE", { handle }), "connectE2EE"),
95
+ disconnect: /* @__PURE__ */ __name((handle) => call("MxDisconnect", { handle }), "disconnect"),
96
+ isConnected: /* @__PURE__ */ __name((handle) => call("MxIsConnected", { handle }), "isConnected"),
97
+ sendMessage: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendMessage", { handle, options }), "sendMessage"),
98
+ sendReaction: /* @__PURE__ */ __name((handle, threadId, messageId, emoji) => callAsync("MxSendReaction", { handle, threadId, messageId, emoji }), "sendReaction"),
99
+ editMessage: /* @__PURE__ */ __name((handle, messageId, newText) => callAsync("MxEditMessage", { handle, messageId, newText }), "editMessage"),
100
+ unsendMessage: /* @__PURE__ */ __name((handle, messageId) => callAsync("MxUnsendMessage", { handle, messageId }), "unsendMessage"),
101
+ sendTyping: /* @__PURE__ */ __name((handle, threadId, isTyping, isGroup, threadType) => callAsync("MxSendTyping", { handle, threadId, isTyping, isGroup, threadType }), "sendTyping"),
102
+ markRead: /* @__PURE__ */ __name((handle, threadId, watermarkTs) => callAsync("MxMarkRead", { handle, threadId, watermarkTs: watermarkTs || 0 }), "markRead"),
103
+ uploadMedia: /* @__PURE__ */ __name((handle, options) => callAsync("MxUploadMedia", { handle, options }), "uploadMedia"),
104
+ sendImage: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendImage", { handle, options }), "sendImage"),
105
+ sendVideo: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendVideo", { handle, options }), "sendVideo"),
106
+ sendVoice: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendVoice", { handle, options }), "sendVoice"),
107
+ sendFile: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendFile", { handle, options }), "sendFile"),
108
+ sendSticker: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendSticker", { handle, options }), "sendSticker"),
109
+ createThread: /* @__PURE__ */ __name((handle, options) => callAsync("MxCreateThread", { handle, options }), "createThread"),
110
+ getUserInfo: /* @__PURE__ */ __name((handle, options) => callAsync("MxGetUserInfo", { handle, options }), "getUserInfo"),
111
+ setGroupPhoto: /* @__PURE__ */ __name((handle, threadId, data, mimeType) => callAsync("MxSetGroupPhoto", { handle, threadId, data, mimeType }), "setGroupPhoto"),
112
+ renameThread: /* @__PURE__ */ __name((handle, options) => callAsync("MxRenameThread", { handle, options }), "renameThread"),
113
+ muteThread: /* @__PURE__ */ __name((handle, options) => callAsync("MxMuteThread", { handle, options }), "muteThread"),
114
+ deleteThread: /* @__PURE__ */ __name((handle, options) => callAsync("MxDeleteThread", { handle, options }), "deleteThread"),
115
+ searchUsers: /* @__PURE__ */ __name((handle, options) => callAsync("MxSearchUsers", {
116
+ handle,
117
+ options
118
+ }), "searchUsers"),
119
+ pollEvents: /* @__PURE__ */ __name((handle, timeoutMs) => callAsync("MxPollEvents", { handle, timeoutMs }), "pollEvents"),
120
+ // E2EE functions
121
+ sendE2EEMessage: /* @__PURE__ */ __name((handle, chatJid, text, replyToId, replyToSenderJid) => callAsync("MxSendE2EEMessage", {
122
+ handle,
123
+ chatJid,
124
+ text,
125
+ replyToId,
126
+ replyToSenderJid
127
+ }), "sendE2EEMessage"),
128
+ sendE2EEReaction: /* @__PURE__ */ __name((handle, chatJid, messageId, senderJid, emoji) => callAsync("MxSendE2EEReaction", { handle, chatJid, messageId, senderJid, emoji }), "sendE2EEReaction"),
129
+ sendE2EETyping: /* @__PURE__ */ __name((handle, chatJid, isTyping) => callAsync("MxSendE2EETyping", { handle, chatJid, isTyping }), "sendE2EETyping"),
130
+ editE2EEMessage: /* @__PURE__ */ __name((handle, chatJid, messageId, newText) => callAsync("MxEditE2EEMessage", { handle, chatJid, messageId, newText }), "editE2EEMessage"),
131
+ unsendE2EEMessage: /* @__PURE__ */ __name((handle, chatJid, messageId) => callAsync("MxUnsendE2EEMessage", { handle, chatJid, messageId }), "unsendE2EEMessage"),
132
+ getDeviceData: /* @__PURE__ */ __name((handle) => call("MxGetDeviceData", { handle }), "getDeviceData"),
133
+ // E2EE Media functions
134
+ sendE2EEImage: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EEImage", { handle, options }), "sendE2EEImage"),
135
+ sendE2EEVideo: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EEVideo", { handle, options }), "sendE2EEVideo"),
136
+ sendE2EEAudio: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EEAudio", { handle, options }), "sendE2EEAudio"),
137
+ sendE2EEDocument: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EEDocument", { handle, options }), "sendE2EEDocument"),
138
+ sendE2EESticker: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EESticker", { handle, options }), "sendE2EESticker"),
139
+ unload: /* @__PURE__ */ __name(() => lib.unload(), "unload")
140
+ };
141
+
142
+ // src/client.ts
143
+ var Client = class extends EventEmitter {
144
+ static {
145
+ __name(this, "Client");
146
+ }
147
+ handle = null;
148
+ options;
149
+ cookies;
150
+ _user = null;
151
+ _initialData = null;
152
+ eventLoopRunning = false;
153
+ eventLoopAbort = null;
154
+ _socketReady = false;
155
+ _e2eeConnected = false;
156
+ _fullyReadyEmitted = false;
157
+ pendingEvents = [];
158
+ /**
159
+ * Create a new Messenger client
160
+ *
161
+ * @param cookies - Authentication cookies
162
+ * @param options - Client options
163
+ */
164
+ constructor(cookies, options = {}) {
165
+ super();
166
+ this.cookies = cookies;
167
+ this.options = {
168
+ // ! todo: detect platform automatically
169
+ platform: "facebook",
170
+ logLevel: "none",
171
+ enableE2EE: true,
172
+ autoReconnect: true,
173
+ ...options
174
+ };
175
+ }
176
+ /**
177
+ * Get the current user info
178
+ */
179
+ get user() {
180
+ return this._user;
181
+ }
182
+ /**
183
+ * Get the current user's Facebook ID
184
+ */
185
+ get currentUserId() {
186
+ return this._user?.id ?? null;
187
+ }
188
+ /**
189
+ * Get initial sync data (threads and messages)
190
+ */
191
+ get initialData() {
192
+ return this._initialData;
193
+ }
194
+ /**
195
+ * Check if client is fully ready (socket ready + E2EE connected if enabled)
196
+ */
197
+ get isFullyReady() {
198
+ if (!this._socketReady) return false;
199
+ if (this.options.enableE2EE && !this._e2eeConnected) return false;
200
+ return true;
201
+ }
202
+ /**
203
+ * Check if client is connected
204
+ */
205
+ get isConnected() {
206
+ if (!this.handle) return false;
207
+ try {
208
+ const status = native.isConnected(this.handle);
209
+ return status.connected;
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
214
+ /**
215
+ * Check if E2EE is connected
216
+ */
217
+ get isE2EEConnected() {
218
+ if (!this.handle) return false;
219
+ try {
220
+ const status = native.isConnected(this.handle);
221
+ return status.e2eeConnected;
222
+ } catch {
223
+ return false;
224
+ }
225
+ }
226
+ /**
227
+ * Connect to Messenger
228
+ *
229
+ * @returns User info and initial data
230
+ */
231
+ async connect() {
232
+ const { handle } = native.newClient({
233
+ cookies: this.cookies,
234
+ platform: this.options.platform,
235
+ devicePath: this.options.devicePath,
236
+ deviceData: this.options.deviceData,
237
+ logLevel: this.options.logLevel
238
+ });
239
+ this.handle = handle;
240
+ const result = native.connect(handle);
241
+ this._user = result.user;
242
+ this._initialData = result.initialData;
243
+ this.startEventLoop();
244
+ if (this.options.enableE2EE) {
245
+ this.connectE2EE().catch((err) => {
246
+ this.emit("error", err);
247
+ });
248
+ }
249
+ return {
250
+ user: this._user,
251
+ initialData: this._initialData
252
+ };
253
+ }
254
+ /**
255
+ * Connect E2EE (end-to-end encryption)
256
+ * @warn This Promise is not resolved after the connection setup is completed; instead, it is resolved after the function finishes executing.\
257
+ * You should not rely on this Promise to wait for the E2EE connection to be fully established.
258
+ */
259
+ async connectE2EE() {
260
+ if (!this.handle) throw new Error("Not connected");
261
+ await native.connectE2EE(this.handle);
262
+ }
263
+ /**
264
+ * Disconnect from Messenger
265
+ */
266
+ async disconnect() {
267
+ this.stopEventLoop();
268
+ if (this.handle) {
269
+ native.disconnect(this.handle);
270
+ this.handle = null;
271
+ }
272
+ }
273
+ /**
274
+ * Send a text message
275
+ *
276
+ * @param threadId - Thread ID to send to
277
+ * @param options - Message options (text, reply, mentions)
278
+ * @returns Send result with message ID
279
+ */
280
+ async sendMessage(threadId, options) {
281
+ if (!this.handle) throw new Error("Not connected");
282
+ const opts = typeof options === "string" ? { text: options } : options;
283
+ return native.sendMessage(this.handle, {
284
+ threadId,
285
+ text: opts.text,
286
+ replyToId: opts.replyToId,
287
+ mentionIds: opts.mentions?.map((m) => m.userId),
288
+ mentionOffsets: opts.mentions?.map((m) => m.offset),
289
+ mentionLengths: opts.mentions?.map((m) => m.length)
290
+ });
291
+ }
292
+ /**
293
+ * Send / Remove a reaction to a message
294
+ *
295
+ * @param threadId - Thread ID
296
+ * @param messageId - Message ID to react to
297
+ * @param emoji - Reaction emoji (to remove, simply omit this parameter)
298
+ */
299
+ async sendReaction(threadId, messageId, emoji) {
300
+ if (!this.handle) throw new Error("Not connected");
301
+ await native.sendReaction(this.handle, threadId, messageId, emoji || "");
302
+ }
303
+ /**
304
+ * Edit a message
305
+ *
306
+ * @param messageId - Message ID to edit
307
+ * @param newText - New text content
308
+ */
309
+ async editMessage(messageId, newText) {
310
+ if (!this.handle) throw new Error("Not connected");
311
+ await native.editMessage(this.handle, messageId, newText);
312
+ }
313
+ /**
314
+ * Unsend/delete a message
315
+ *
316
+ * @param messageId - Message ID to unsend
317
+ */
318
+ async unsendMessage(messageId) {
319
+ if (!this.handle) throw new Error("Not connected");
320
+ await native.unsendMessage(this.handle, messageId);
321
+ }
322
+ /**
323
+ * Send typing indicator
324
+ *
325
+ * @param threadId - Thread ID
326
+ * @param isTyping - Whether typing or not
327
+ * @param isGroup - Whether it's a group chat
328
+ */
329
+ async sendTypingIndicator(threadId, isTyping = true, isGroup = false) {
330
+ if (!this.handle) throw new Error("Not connected");
331
+ await native.sendTyping(this.handle, threadId, isTyping, isGroup, isGroup ? 2 : 1);
332
+ }
333
+ /**
334
+ * Mark messages as read
335
+ *
336
+ * @param threadId - Thread ID
337
+ * @param watermarkTs - Timestamp to mark read up to (optional)
338
+ */
339
+ async markAsRead(threadId, watermarkTs) {
340
+ if (!this.handle) throw new Error("Not connected");
341
+ await native.markRead(this.handle, threadId, watermarkTs);
342
+ }
343
+ /**
344
+ * Upload media to Messenger
345
+ *
346
+ * @param threadId - Thread ID
347
+ * @param data - File data as Buffer
348
+ * @param filename - Filename
349
+ * @param mimeType - MIME type
350
+ * @param isVoice - Whether it's a voice message
351
+ * @returns Upload result with Facebook ID
352
+ */
353
+ async uploadMedia(threadId, data, filename, mimeType, isVoice = false) {
354
+ if (!this.handle) throw new Error("Not connected");
355
+ return native.uploadMedia(this.handle, {
356
+ threadId,
357
+ filename,
358
+ mimeType,
359
+ data: Array.from(data),
360
+ isVoice
361
+ });
362
+ }
363
+ /**
364
+ * Send an image
365
+ *
366
+ * @param threadId - Thread ID
367
+ * @param data - Image data as Buffer
368
+ * @param filename - Filename
369
+ * @param caption - Optional caption
370
+ */
371
+ async sendImage(threadId, data, filename, caption) {
372
+ if (!this.handle) throw new Error("Not connected");
373
+ return native.sendImage(this.handle, {
374
+ threadId,
375
+ data: Array.from(data),
376
+ filename,
377
+ caption
378
+ });
379
+ }
380
+ /**
381
+ * Send a video
382
+ *
383
+ * @param threadId - Thread ID
384
+ * @param data - Video data as Buffer
385
+ * @param filename - Filename
386
+ * @param caption - Optional caption
387
+ */
388
+ async sendVideo(threadId, data, filename, caption) {
389
+ if (!this.handle) throw new Error("Not connected");
390
+ return native.sendVideo(this.handle, {
391
+ threadId,
392
+ data: Array.from(data),
393
+ filename,
394
+ caption
395
+ });
396
+ }
397
+ /**
398
+ * Send a voice message
399
+ *
400
+ * @param threadId - Thread ID
401
+ * @param data - Audio data as Buffer
402
+ * @param filename - Filename
403
+ */
404
+ async sendVoice(threadId, data, filename) {
405
+ if (!this.handle) throw new Error("Not connected");
406
+ return native.sendVoice(this.handle, {
407
+ threadId,
408
+ data: Array.from(data),
409
+ filename
410
+ });
411
+ }
412
+ /**
413
+ * Send a file
414
+ *
415
+ * @param threadId - Thread ID
416
+ * @param data - File data as Buffer
417
+ * @param filename - Filename
418
+ * @param mimeType - MIME type
419
+ * @param caption - Optional caption
420
+ */
421
+ async sendFile(threadId, data, filename, mimeType, caption) {
422
+ if (!this.handle) throw new Error("Not connected");
423
+ return native.sendFile(this.handle, {
424
+ threadId,
425
+ data: Array.from(data),
426
+ filename,
427
+ mimeType,
428
+ caption
429
+ });
430
+ }
431
+ /**
432
+ * Send a sticker
433
+ *
434
+ * @param threadId - Thread ID
435
+ * @param stickerId - Sticker ID
436
+ */
437
+ async sendSticker(threadId, stickerId) {
438
+ if (!this.handle) throw new Error("Not connected");
439
+ return native.sendSticker(this.handle, { threadId, stickerId });
440
+ }
441
+ /**
442
+ * Create a 1:1 thread with a user
443
+ *
444
+ * @param userId - User ID to create thread with
445
+ * @returns Created thread info
446
+ */
447
+ async createThread(userId) {
448
+ if (!this.handle) throw new Error("Not connected");
449
+ return native.createThread(this.handle, { userId });
450
+ }
451
+ /**
452
+ * Get detailed information about a user
453
+ *
454
+ * @param userId - User ID
455
+ * @returns User info
456
+ */
457
+ async getUserInfo(userId) {
458
+ if (!this.handle) throw new Error("Not connected");
459
+ return native.getUserInfo(this.handle, { userId });
460
+ }
461
+ /**
462
+ * Set group photo/avatar
463
+ *
464
+ * @param threadId - Thread ID
465
+ * @param data - Image data as Buffer or base64 string
466
+ * @param mimeType - MIME type (e.g., 'image/jpeg', 'image/png')
467
+ *
468
+ * @warn Cannot remove group photo. Messenger web doesn't have a remove option?
469
+ */
470
+ async setGroupPhoto(threadId, data, mimeType = "image/jpeg") {
471
+ if (!this.handle) throw new Error("Not connected");
472
+ const base64 = Buffer.isBuffer(data) ? data.toString("base64") : data;
473
+ await native.setGroupPhoto(this.handle, threadId, base64, mimeType);
474
+ }
475
+ /**
476
+ * Rename a group thread
477
+ *
478
+ * @param threadId - Thread ID
479
+ * @param newName - New name
480
+ */
481
+ async renameThread(threadId, newName) {
482
+ if (!this.handle) throw new Error("Not connected");
483
+ native.renameThread(this.handle, { threadId, newName });
484
+ }
485
+ /**
486
+ * Mute a thread
487
+ *
488
+ * @param threadId - Thread ID
489
+ * @param muteSeconds - Duration in seconds (-1 for forever, 0 to unmute)
490
+ */
491
+ async muteThread(threadId, muteSeconds = -1) {
492
+ if (!this.handle) throw new Error("Not connected");
493
+ native.muteThread(this.handle, { threadId, muteSeconds });
494
+ }
495
+ /**
496
+ * Unmute a thread
497
+ *
498
+ * @param threadId - Thread ID
499
+ */
500
+ async unmuteThread(threadId) {
501
+ return this.muteThread(threadId, 0);
502
+ }
503
+ /**
504
+ * Delete a thread
505
+ *
506
+ * @param threadId - Thread ID
507
+ */
508
+ async deleteThread(threadId) {
509
+ if (!this.handle) throw new Error("Not connected");
510
+ native.deleteThread(this.handle, { threadId });
511
+ }
512
+ /**
513
+ * Search for users
514
+ *
515
+ * @param query - Search query
516
+ * @returns List of matching users
517
+ */
518
+ async searchUsers(query) {
519
+ if (!this.handle) throw new Error("Not connected");
520
+ const result = await native.searchUsers(this.handle, { query });
521
+ return result.users;
522
+ }
523
+ // ========== E2EE Methods ==========
524
+ /**
525
+ * Send an E2EE message
526
+ *
527
+ * @param chatJid - Chat JID
528
+ * @param text - Message text
529
+ * @param options - Optional: replyToId and replyToSenderJid for replies
530
+ */
531
+ async sendE2EEMessage(chatJid, text, options) {
532
+ if (!this.handle) throw new Error("Not connected");
533
+ return native.sendE2EEMessage(this.handle, chatJid, text, options?.replyToId, options?.replyToSenderJid);
534
+ }
535
+ /**
536
+ * Send / Remove an E2EE reaction
537
+ *
538
+ * @param chatJid - Chat JID
539
+ * @param messageId - Message ID
540
+ * @param senderJid - Sender JID
541
+ * @param emoji - Reaction emoji (To remove it, simply omit this parameter)
542
+ */
543
+ async sendE2EEReaction(chatJid, messageId, senderJid, emoji) {
544
+ if (!this.handle) throw new Error("Not connected");
545
+ await native.sendE2EEReaction(this.handle, chatJid, messageId, senderJid, emoji || "");
546
+ }
547
+ /**
548
+ * Send E2EE typing indicator
549
+ *
550
+ * @param chatJid - Chat JID
551
+ * @param isTyping - Whether typing
552
+ */
553
+ async sendE2EETyping(chatJid, isTyping = true) {
554
+ if (!this.handle) throw new Error("Not connected");
555
+ await native.sendE2EETyping(this.handle, chatJid, isTyping);
556
+ }
557
+ /**
558
+ * Edit an E2EE message
559
+ *
560
+ * @param chatJid - Chat JID
561
+ * @param messageId - Message ID to edit
562
+ * @param newText - New message text
563
+ */
564
+ async editE2EEMessage(chatJid, messageId, newText) {
565
+ if (!this.handle) throw new Error("Not connected");
566
+ await native.editE2EEMessage(this.handle, chatJid, messageId, newText);
567
+ }
568
+ /**
569
+ * Unsend/delete an E2EE message
570
+ *
571
+ * @param chatJid - Chat JID
572
+ * @param messageId - Message ID to unsend
573
+ */
574
+ async unsendE2EEMessage(chatJid, messageId) {
575
+ if (!this.handle) throw new Error("Not connected");
576
+ await native.unsendE2EEMessage(this.handle, chatJid, messageId);
577
+ }
578
+ // ========== E2EE Media Methods ==========
579
+ /**
580
+ * Send an E2EE image
581
+ *
582
+ * @param chatJid - Chat JID
583
+ * @param data - Image data as Buffer
584
+ * @param mimeType - MIME type (e.g., image/jpeg, image/png)
585
+ * @param options - Optional caption, dimensions, and reply options
586
+ */
587
+ async sendE2EEImage(chatJid, data, mimeType = "image/jpeg", options) {
588
+ if (!this.handle) throw new Error("Not connected");
589
+ return native.sendE2EEImage(this.handle, {
590
+ chatJid,
591
+ data: Array.from(data),
592
+ mimeType,
593
+ caption: options?.caption,
594
+ width: options?.width,
595
+ height: options?.height,
596
+ replyToId: options?.replyToId,
597
+ replyToSenderJid: options?.replyToSenderJid
598
+ });
599
+ }
600
+ /**
601
+ * Send an E2EE video
602
+ *
603
+ * @param chatJid - Chat JID
604
+ * @param data - Video data as Buffer
605
+ * @param mimeType - MIME type (default: video/mp4)
606
+ * @param options - Optional caption, dimensions, duration, and reply options
607
+ */
608
+ async sendE2EEVideo(chatJid, data, mimeType = "video/mp4", options) {
609
+ if (!this.handle) throw new Error("Not connected");
610
+ return native.sendE2EEVideo(this.handle, {
611
+ chatJid,
612
+ data: Array.from(data),
613
+ mimeType,
614
+ caption: options?.caption,
615
+ width: options?.width,
616
+ height: options?.height,
617
+ duration: options?.duration,
618
+ replyToId: options?.replyToId,
619
+ replyToSenderJid: options?.replyToSenderJid
620
+ });
621
+ }
622
+ /**
623
+ * Send an E2EE audio/voice message
624
+ *
625
+ * @param chatJid - Chat JID
626
+ * @param data - Audio data as Buffer
627
+ * @param mimeType - MIME type (default: audio/ogg)
628
+ * @param options - Optional PTT (push-to-talk/voice message), duration, and reply options
629
+ */
630
+ async sendE2EEAudio(chatJid, data, mimeType = "audio/ogg", options) {
631
+ if (!this.handle) throw new Error("Not connected");
632
+ return native.sendE2EEAudio(this.handle, {
633
+ chatJid,
634
+ data: Array.from(data),
635
+ mimeType,
636
+ ptt: options?.ptt ?? false,
637
+ duration: options?.duration,
638
+ replyToId: options?.replyToId,
639
+ replyToSenderJid: options?.replyToSenderJid
640
+ });
641
+ }
642
+ /**
643
+ * Send an E2EE document/file
644
+ *
645
+ * @param chatJid - Chat JID
646
+ * @param data - File data as Buffer
647
+ * @param filename - Filename
648
+ * @param mimeType - MIME type
649
+ * @param options - Optional reply options
650
+ */
651
+ async sendE2EEDocument(chatJid, data, filename, mimeType, options) {
652
+ if (!this.handle) throw new Error("Not connected");
653
+ return native.sendE2EEDocument(this.handle, {
654
+ chatJid,
655
+ data: Array.from(data),
656
+ filename,
657
+ mimeType,
658
+ replyToId: options?.replyToId,
659
+ replyToSenderJid: options?.replyToSenderJid
660
+ });
661
+ }
662
+ /**
663
+ * Send an E2EE sticker
664
+ *
665
+ * @param chatJid - Chat JID
666
+ * @param data - Sticker data as Buffer (WebP format)
667
+ * @param mimeType - MIME type (default: image/webp)
668
+ * @param options - Optional reply options
669
+ */
670
+ async sendE2EESticker(chatJid, data, mimeType = "image/webp", options) {
671
+ if (!this.handle) throw new Error("Not connected");
672
+ return native.sendE2EESticker(this.handle, {
673
+ chatJid,
674
+ data: Array.from(data),
675
+ mimeType,
676
+ replyToId: options?.replyToId,
677
+ replyToSenderJid: options?.replyToSenderJid
678
+ });
679
+ }
680
+ /**
681
+ * Get E2EE device data as JSON string
682
+ *
683
+ * Use this to persist device data externally (e.g., in a database)
684
+ *
685
+ * @returns Device data as JSON string
686
+ */
687
+ getDeviceData() {
688
+ if (!this.handle) throw new Error("Not connected");
689
+ const result = native.getDeviceData(this.handle);
690
+ return result.deviceData;
691
+ }
692
+ startEventLoop() {
693
+ if (this.eventLoopRunning) return;
694
+ this.eventLoopRunning = true;
695
+ this.eventLoopAbort = new AbortController();
696
+ const loop = /* @__PURE__ */ __name(async () => {
697
+ while (this.eventLoopRunning && this.handle) {
698
+ try {
699
+ await new Promise((resolve) => setImmediate(resolve));
700
+ const event = await native.pollEvents(this.handle, 1e3);
701
+ if (!event || event.type === "timeout") continue;
702
+ if (event.type === "closed") {
703
+ this.eventLoopRunning = false;
704
+ break;
705
+ }
706
+ this.handleEvent(event);
707
+ } catch (err) {
708
+ if (this.eventLoopRunning) {
709
+ this.emit("error", err);
710
+ }
711
+ }
712
+ }
713
+ }, "loop");
714
+ setImmediate(loop).unref();
715
+ }
716
+ stopEventLoop() {
717
+ this.eventLoopRunning = false;
718
+ this.eventLoopAbort?.abort();
719
+ this.eventLoopAbort = null;
720
+ }
721
+ checkFullyReady() {
722
+ if (this.isFullyReady && !this._fullyReadyEmitted) {
723
+ this._fullyReadyEmitted = true;
724
+ this.emit("fullyReady");
725
+ const pending = this.pendingEvents;
726
+ this.pendingEvents = [];
727
+ for (const event of pending) {
728
+ this.emitEvent(event);
729
+ }
730
+ }
731
+ }
732
+ handleEvent(event) {
733
+ switch (event.type) {
734
+ // System events
735
+ case "ready":
736
+ this._socketReady = true;
737
+ this.emit("ready", event.data);
738
+ this.checkFullyReady();
739
+ break;
740
+ case "reconnected":
741
+ this.emit("reconnected");
742
+ break;
743
+ case "disconnected":
744
+ this._socketReady = false;
745
+ this._e2eeConnected = false;
746
+ this._fullyReadyEmitted = false;
747
+ this.pendingEvents = [];
748
+ this.emit("disconnected", event.data || {});
749
+ break;
750
+ case "error":
751
+ this.emit("error", new Error(event.data.message));
752
+ break;
753
+ case "e2eeConnected":
754
+ this._e2eeConnected = true;
755
+ this.emit("e2eeConnected");
756
+ this.checkFullyReady();
757
+ break;
758
+ case "deviceDataChanged":
759
+ this.emit("deviceDataChanged", event.data);
760
+ break;
761
+ // queue until fullyReady
762
+ case "message":
763
+ case "messageEdit":
764
+ case "messageUnsend":
765
+ case "reaction":
766
+ case "typing":
767
+ case "readReceipt":
768
+ case "e2eeMessage":
769
+ case "e2eeReaction":
770
+ case "e2eeReceipt":
771
+ if (this._fullyReadyEmitted) {
772
+ this.emitEvent(event);
773
+ } else {
774
+ this.pendingEvents.push(event);
775
+ }
776
+ break;
777
+ }
778
+ }
779
+ emitEvent(event) {
780
+ switch (event.type) {
781
+ case "message":
782
+ this.emit("message", event.data);
783
+ break;
784
+ case "messageEdit":
785
+ this.emit("messageEdit", event.data);
786
+ break;
787
+ case "messageUnsend":
788
+ this.emit("messageUnsend", event.data);
789
+ break;
790
+ case "reaction":
791
+ this.emit("reaction", event.data);
792
+ break;
793
+ case "typing":
794
+ this.emit("typing", event.data);
795
+ break;
796
+ case "readReceipt":
797
+ this.emit("readReceipt", event.data);
798
+ break;
799
+ case "e2eeMessage":
800
+ this.emit("e2eeMessage", event.data);
801
+ break;
802
+ case "e2eeReaction":
803
+ this.emit("e2eeReaction", event.data);
804
+ break;
805
+ case "e2eeReceipt":
806
+ this.emit("e2eeReceipt", event.data);
807
+ break;
808
+ }
809
+ }
810
+ /**
811
+ * Unload the native library (for cleanup)
812
+ * @warn Any attempt to find or call a function from this library after unloading it will crash.
813
+ * @returns void
814
+ */
815
+ unloadLibrary() {
816
+ if (this.handle) {
817
+ native.unload();
818
+ }
819
+ }
820
+ };
821
+
822
+ // src/types.ts
823
+ var ThreadType = /* @__PURE__ */ ((ThreadType2) => {
824
+ ThreadType2[ThreadType2["ONE_TO_ONE"] = 1] = "ONE_TO_ONE";
825
+ ThreadType2[ThreadType2["GROUP"] = 2] = "GROUP";
826
+ ThreadType2[ThreadType2["PAGE"] = 3] = "PAGE";
827
+ ThreadType2[ThreadType2["MARKETPLACE"] = 4] = "MARKETPLACE";
828
+ ThreadType2[ThreadType2["ENCRYPTED_ONE_TO_ONE"] = 7] = "ENCRYPTED_ONE_TO_ONE";
829
+ ThreadType2[ThreadType2["ENCRYPTED_GROUP"] = 8] = "ENCRYPTED_GROUP";
830
+ return ThreadType2;
831
+ })(ThreadType || {});
832
+
833
+ // src/utils.ts
834
+ var Utils = class _Utils extends null {
835
+ static {
836
+ __name(this, "Utils");
837
+ }
838
+ /**
839
+ * Parse cookies from various formats
840
+ * Automatically detects the format and parses accordingly
841
+ *
842
+ * @param input - Cookie data in any supported format
843
+ * @returns Parsed cookies object
844
+ */
845
+ static parseCookies(input) {
846
+ if (typeof input === "object" && !Array.isArray(input)) {
847
+ return input;
848
+ }
849
+ if (Array.isArray(input)) {
850
+ return _Utils.fromCookieArray(input);
851
+ }
852
+ if (typeof input === "string") {
853
+ const trimmed = input.trim();
854
+ if (_Utils.isBase64(trimmed)) {
855
+ try {
856
+ const decoded = Buffer.from(trimmed, "base64").toString("utf-8");
857
+ return _Utils.parseCookies(decoded);
858
+ } catch {
859
+ }
860
+ }
861
+ if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
862
+ try {
863
+ const parsed = JSON.parse(trimmed);
864
+ return _Utils.parseCookies(parsed);
865
+ } catch {
866
+ }
867
+ }
868
+ if (trimmed.includes(" ") && (trimmed.startsWith("#") || trimmed.includes(".facebook.com") || trimmed.includes(".messenger.com"))) {
869
+ return _Utils.fromNetscape(trimmed);
870
+ }
871
+ if (trimmed.includes("=")) {
872
+ return _Utils.fromCookieString(trimmed);
873
+ }
874
+ }
875
+ throw new Error("Unable to parse cookies: unknown format");
876
+ }
877
+ /**
878
+ * Parse cookies from C3C UFC Utility / EditThisCookie array format
879
+ *
880
+ * @param cookies - Array of cookie objects
881
+ * @returns Parsed cookies object
882
+ *
883
+ * @example
884
+ * ```typescript
885
+ * const cookies = Utils.fromCookieArray([
886
+ * { name: 'c_user', value: '123456' },
887
+ * { name: 'xs', value: 'abc...' }
888
+ * ])
889
+ * ```
890
+ */
891
+ static fromCookieArray(cookies) {
892
+ const result = {};
893
+ for (const cookie of cookies) {
894
+ if (cookie.name && cookie.value !== void 0) {
895
+ result[cookie.name] = String(cookie.value);
896
+ }
897
+ }
898
+ return result;
899
+ }
900
+ /**
901
+ * Parse cookies from cookie header string format
902
+ *
903
+ * @param cookieString - Cookie string (e.g., "name1=value1; name2=value2")
904
+ * @returns Parsed cookies object
905
+ *
906
+ * @example
907
+ * ```typescript
908
+ * const cookies = Utils.fromCookieString('c_user=123456; xs=abc...; datr=xyz...')
909
+ * ```
910
+ */
911
+ static fromCookieString(cookieString) {
912
+ const result = {};
913
+ const pairs = cookieString.split(/;\s*/);
914
+ for (const pair of pairs) {
915
+ const [name, ...valueParts] = pair.split("=");
916
+ if (name && valueParts.length > 0) {
917
+ const trimmedName = name.trim();
918
+ const value = valueParts.join("=").trim();
919
+ if (trimmedName && value) {
920
+ result[trimmedName] = value;
921
+ }
922
+ }
923
+ }
924
+ return result;
925
+ }
926
+ /**
927
+ * Parse cookies from Netscape/HTTP cookie file format
928
+ *
929
+ * @param content - Netscape cookie file content
930
+ * @returns Parsed cookies object
931
+ *
932
+ * @example
933
+ * ```typescript
934
+ * const cookies = Utils.fromNetscape(`
935
+ * # Netscape HTTP Cookie File
936
+ * .facebook.com TRUE / TRUE 1234567890 c_user 123456
937
+ * .facebook.com TRUE / TRUE 1234567890 xs abc...
938
+ * `)
939
+ * ```
940
+ */
941
+ static fromNetscape(content) {
942
+ const result = {};
943
+ const lines = content.split("\n");
944
+ for (const line of lines) {
945
+ const trimmed = line.trim();
946
+ if (!trimmed || trimmed.startsWith("#")) {
947
+ continue;
948
+ }
949
+ const parts = trimmed.split(" ");
950
+ if (parts.length >= 7) {
951
+ const name = parts[5];
952
+ const value = parts[6];
953
+ if (name && value) {
954
+ result[name] = value;
955
+ }
956
+ }
957
+ }
958
+ return result;
959
+ }
960
+ /**
961
+ * Parse cookies from Base64 encoded string
962
+ *
963
+ * @param base64 - Base64 encoded cookie data
964
+ * @returns Parsed cookies object
965
+ */
966
+ static fromBase64(base64) {
967
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
968
+ return _Utils.parseCookies(decoded);
969
+ }
970
+ /**
971
+ * Convert cookies object to cookie header string
972
+ *
973
+ * @param cookies - Cookies object
974
+ * @returns Cookie header string
975
+ *
976
+ * @example
977
+ * ```typescript
978
+ * const header = Utils.toCookieString({ c_user: '123456', xs: 'abc...' })
979
+ * // Returns: "c_user=123456; xs=abc..."
980
+ * ```
981
+ */
982
+ static toCookieString(cookies) {
983
+ return Object.entries(cookies).map(([name, value]) => `${name}=${value}`).join("; ");
984
+ }
985
+ /**
986
+ * Convert cookies object to array format (C3C UFC Utility style)
987
+ *
988
+ * @param cookies - Cookies object
989
+ * @param domain - Cookie domain (default: .facebook.com)
990
+ * @returns Array of cookie objects
991
+ *
992
+ * @example
993
+ * ```typescript
994
+ * const arr = Utils.toCookieArray({ c_user: '123456', xs: 'abc...' })
995
+ * ```
996
+ */
997
+ static toCookieArray(cookies, domain = ".facebook.com") {
998
+ return Object.entries(cookies).filter(([, value]) => value !== void 0).map(([name, value]) => ({
999
+ name,
1000
+ value,
1001
+ domain,
1002
+ path: "/",
1003
+ secure: true,
1004
+ httpOnly: true
1005
+ }));
1006
+ }
1007
+ /**
1008
+ * Convert cookies object to Netscape format
1009
+ *
1010
+ * @param cookies - Cookies object
1011
+ * @param domain - Cookie domain (default: .facebook.com)
1012
+ * @returns Netscape cookie file content
1013
+ */
1014
+ static toNetscape(cookies, domain = ".facebook.com") {
1015
+ const lines = ["# Netscape HTTP Cookie File", "# Generated by meta-messenger.js", ""];
1016
+ for (const [name, value] of Object.entries(cookies)) {
1017
+ const expiration = Math.floor(Date.now() / 1e3) + 365 * 24 * 60 * 60;
1018
+ lines.push(`${domain} TRUE / TRUE ${expiration} ${name} ${value}`);
1019
+ }
1020
+ return lines.join("\n");
1021
+ }
1022
+ /**
1023
+ * Convert cookies to Base64 encoded JSON
1024
+ *
1025
+ * @param cookies - Cookies object
1026
+ * @returns Base64 encoded string
1027
+ */
1028
+ static toBase64(cookies) {
1029
+ return Buffer.from(JSON.stringify(cookies)).toString("base64");
1030
+ }
1031
+ /**
1032
+ * Filter cookies to only essential ones for Facebook/Messenger
1033
+ *
1034
+ * @param cookies - Cookies object
1035
+ * @returns Filtered cookies with only essential keys
1036
+ */
1037
+ static filterEssential(cookies) {
1038
+ const essential = ["c_user", "xs", "datr", "fr", "sb", "wd", "presence"];
1039
+ const result = {};
1040
+ for (const key of essential) {
1041
+ if (cookies[key]) {
1042
+ result[key] = cookies[key];
1043
+ }
1044
+ }
1045
+ return result;
1046
+ }
1047
+ /**
1048
+ * Validate that cookies contain required fields
1049
+ *
1050
+ * @param cookies - Cookies object
1051
+ * @returns True if cookies are valid
1052
+ */
1053
+ static validate(cookies) {
1054
+ const required = ["c_user", "xs"];
1055
+ return required.every((key) => cookies[key] && cookies[key].length > 0);
1056
+ }
1057
+ /**
1058
+ * Get missing required cookies
1059
+ *
1060
+ * @param cookies - Cookies object
1061
+ * @returns Array of missing cookie names
1062
+ */
1063
+ static getMissing(cookies) {
1064
+ const required = ["c_user", "xs"];
1065
+ return required.filter((key) => !cookies[key] || cookies[key].length === 0);
1066
+ }
1067
+ /**
1068
+ * Check if a string is valid Base64
1069
+ */
1070
+ static isBase64(str) {
1071
+ if (str.length < 4) return false;
1072
+ const base64Regex = /^[A-Za-z0-9+/]+=*$/;
1073
+ if (!base64Regex.test(str)) return false;
1074
+ return str.length > 20 && str.length % 4 === 0;
1075
+ }
1076
+ };
1077
+
1078
+ // src/index.ts
1079
+ async function login(cookies, options) {
1080
+ const client = new Client(cookies, options);
1081
+ await client.connect();
1082
+ return client;
1083
+ }
1084
+ __name(login, "login");
1085
+ function createClient(cookies, options) {
1086
+ return new Client(cookies, options);
1087
+ }
1088
+ __name(createClient, "createClient");
1089
+ var index_default = { Client, login, createClient };
1090
+ export {
1091
+ Client,
1092
+ ThreadType,
1093
+ Utils,
1094
+ createClient,
1095
+ index_default as default,
1096
+ login
1097
+ };
1098
+ //# sourceMappingURL=index.js.map