bbgun 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,987 @@
1
+ import EventEmitter$1, { EventEmitter } from 'events';
2
+ import axios from 'axios';
3
+ import io from 'socket.io-client';
4
+ import * as fs from 'fs';
5
+ import * as os from 'os';
6
+ import * as path from 'path';
7
+ import path__default from 'path';
8
+ import { randomUUID } from 'crypto';
9
+ import { readFile } from 'fs/promises';
10
+ import FormData from 'form-data';
11
+
12
+ var __defProp = Object.defineProperty;
13
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
14
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
15
+ var Logger = class extends EventEmitter$1 {
16
+ constructor(tag, level = "info", logToFile = true) {
17
+ super();
18
+ __publicField(this, "tag");
19
+ __publicField(this, "logLevel", "info");
20
+ __publicField(this, "logFile");
21
+ this.tag = tag;
22
+ this.logLevel = level;
23
+ if (logToFile) {
24
+ try {
25
+ const logDir = path.join(os.homedir(), "Library", "Logs", "BBGun");
26
+ if (!fs.existsSync(logDir)) {
27
+ fs.mkdirSync(logDir, { recursive: true });
28
+ }
29
+ this.logFile = path.join(logDir, "sdk.log");
30
+ } catch {
31
+ }
32
+ }
33
+ }
34
+ setLogLevel(level) {
35
+ this.logLevel = level;
36
+ }
37
+ shouldLog(level) {
38
+ const levels = ["debug", "info", "warn", "error"];
39
+ const currentIndex = levels.indexOf(this.logLevel);
40
+ const messageIndex = levels.indexOf(level);
41
+ return messageIndex >= currentIndex;
42
+ }
43
+ formatMessage(level, message) {
44
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
45
+ return `[${timestamp}][${level.toUpperCase()}][${this.tag}] ${message}`;
46
+ }
47
+ writeLog(level, message) {
48
+ if (!this.shouldLog(level)) return;
49
+ const formatted = this.formatMessage(level, message);
50
+ switch (level) {
51
+ case "error":
52
+ console.error(formatted);
53
+ break;
54
+ case "warn":
55
+ console.warn(formatted);
56
+ break;
57
+ case "debug":
58
+ console.debug(formatted);
59
+ break;
60
+ default:
61
+ console.log(formatted);
62
+ }
63
+ if (this.logFile) {
64
+ try {
65
+ fs.appendFileSync(this.logFile, `${formatted}
66
+ `);
67
+ } catch {
68
+ }
69
+ }
70
+ this.emit("log", { level, message, tag: this.tag });
71
+ }
72
+ info(message) {
73
+ this.writeLog("info", message);
74
+ }
75
+ debug(message) {
76
+ this.writeLog("debug", message);
77
+ }
78
+ error(message) {
79
+ const msg = message instanceof Error ? message.message : message;
80
+ this.writeLog("error", msg);
81
+ if (message instanceof Error && message.stack) {
82
+ this.writeLog("error", message.stack);
83
+ }
84
+ }
85
+ warn(message) {
86
+ this.writeLog("warn", message);
87
+ }
88
+ log(message, level = "info") {
89
+ this.writeLog(level, message);
90
+ }
91
+ };
92
+
93
+ // lib/Loggable.ts
94
+ var loggers = {};
95
+ var globalLogLevel = "info";
96
+ var globalLogToFile = true;
97
+ var setGlobalLogLevel = (level) => {
98
+ globalLogLevel = level;
99
+ Object.values(loggers).forEach((logger) => {
100
+ logger.setLogLevel(level);
101
+ });
102
+ };
103
+ var setGlobalLogToFile = (logToFile) => {
104
+ globalLogToFile = logToFile;
105
+ };
106
+ var getLogger = (tag) => {
107
+ let logger = loggers[tag];
108
+ if (!logger) {
109
+ logger = new Logger(tag, globalLogLevel, globalLogToFile);
110
+ loggers[tag] = logger;
111
+ }
112
+ return logger;
113
+ };
114
+
115
+ // lib/auto-create-chat.ts
116
+ function isChatNotExistError(error) {
117
+ const axiosError = error;
118
+ const errorMsg = axiosError?.response?.data?.error?.message || axiosError?.response?.data?.message || "";
119
+ const lowerMsg = errorMsg.toLowerCase();
120
+ return lowerMsg.includes("chat does not exist") || lowerMsg.includes("chat not found");
121
+ }
122
+ function extractAddress(chatGuid) {
123
+ const parts = chatGuid.split(";-;");
124
+ if (parts.length !== 2 || !parts[1]) {
125
+ return void 0;
126
+ }
127
+ return parts[1];
128
+ }
129
+ function extractService(chatGuid) {
130
+ if (!chatGuid) return void 0;
131
+ const prefix = chatGuid.split(";")[0]?.toLowerCase();
132
+ if (prefix === "imessage") return "iMessage";
133
+ if (prefix === "sms") return "SMS";
134
+ return void 0;
135
+ }
136
+ async function createChatWithMessage(options) {
137
+ const { http, address, message, tempGuid, subject, effectId, service } = options;
138
+ try {
139
+ const response = await http.post("/api/v1/chat/new", {
140
+ addresses: [address],
141
+ message,
142
+ tempGuid,
143
+ subject,
144
+ effectId,
145
+ ...service && { service }
146
+ });
147
+ return response.data.data?.guid;
148
+ } catch (error) {
149
+ throw new Error(
150
+ `Failed to create chat with address "${address}": ${error instanceof Error ? error.message : String(error)}`
151
+ );
152
+ }
153
+ }
154
+
155
+ // modules/attachment.ts
156
+ var AttachmentModule = class {
157
+ constructor(http, enqueueSend = (task) => task()) {
158
+ this.http = http;
159
+ this.enqueueSend = enqueueSend;
160
+ }
161
+ async getAttachmentCount() {
162
+ const response = await this.http.get("/api/v1/attachment/count");
163
+ return response.data.data.total;
164
+ }
165
+ async getAttachment(guid) {
166
+ const response = await this.http.get(`/api/v1/attachment/${encodeURIComponent(guid)}`);
167
+ return response.data.data;
168
+ }
169
+ async downloadAttachment(guid, options) {
170
+ const params = {};
171
+ if (options?.original !== void 0) params.original = options.original;
172
+ if (options?.force !== void 0) params.force = options.force;
173
+ if (options?.height !== void 0) params.height = options.height;
174
+ if (options?.width !== void 0) params.width = options.width;
175
+ if (options?.quality !== void 0) params.quality = options.quality;
176
+ const response = await this.http.get(`/api/v1/attachment/${encodeURIComponent(guid)}/download`, {
177
+ params,
178
+ responseType: "arraybuffer"
179
+ });
180
+ return Buffer.from(response.data);
181
+ }
182
+ async downloadAttachmentLive(guid) {
183
+ const response = await this.http.get(`/api/v1/attachment/${encodeURIComponent(guid)}/live`, {
184
+ responseType: "arraybuffer"
185
+ });
186
+ return Buffer.from(response.data);
187
+ }
188
+ async getAttachmentBlurhash(guid, options) {
189
+ const params = {};
190
+ if (options?.height !== void 0) params.height = options.height;
191
+ if (options?.width !== void 0) params.width = options.width;
192
+ if (options?.quality !== void 0) params.quality = options.quality;
193
+ const response = await this.http.get(`/api/v1/attachment/${encodeURIComponent(guid)}/blurhash`, {
194
+ params
195
+ });
196
+ const data = response.data.data;
197
+ return typeof data === "string" ? data : data.blurhash;
198
+ }
199
+ async ensureChatExists(chatGuid) {
200
+ const address = extractAddress(chatGuid);
201
+ if (!address) return;
202
+ const service = extractService(chatGuid);
203
+ await this.http.post("/api/v1/chat/new", {
204
+ addresses: [address],
205
+ ...service && { service }
206
+ });
207
+ }
208
+ async sendAttachment(options) {
209
+ return this.enqueueSend(async () => {
210
+ const fileBuffer = await readFile(options.filePath);
211
+ const fileName = options.fileName || path__default.basename(options.filePath);
212
+ const tempGuid = randomUUID();
213
+ const buildForm = () => {
214
+ const form = new FormData();
215
+ form.append("chatGuid", options.chatGuid);
216
+ form.append("attachment", fileBuffer, fileName);
217
+ form.append("name", fileName);
218
+ form.append("tempGuid", tempGuid);
219
+ if (options.isAudioMessage !== void 0) {
220
+ form.append("isAudioMessage", options.isAudioMessage.toString());
221
+ if (options.isAudioMessage) {
222
+ form.append("method", "private-api");
223
+ }
224
+ }
225
+ if (options.selectedMessageGuid) {
226
+ form.append("selectedMessageGuid", options.selectedMessageGuid);
227
+ }
228
+ return form;
229
+ };
230
+ try {
231
+ const form = buildForm();
232
+ const response = await this.http.post("/api/v1/message/attachment", form, {
233
+ headers: form.getHeaders()
234
+ });
235
+ return response.data.data;
236
+ } catch (error) {
237
+ if (!isChatNotExistError(error)) throw error;
238
+ await this.ensureChatExists(options.chatGuid);
239
+ const form = buildForm();
240
+ const response = await this.http.post("/api/v1/message/attachment", form, {
241
+ headers: form.getHeaders()
242
+ });
243
+ return response.data.data;
244
+ }
245
+ });
246
+ }
247
+ async sendSticker(options) {
248
+ return this.enqueueSend(async () => {
249
+ const fileName = options.fileName || path__default.basename(options.filePath);
250
+ const fileBuffer = await readFile(options.filePath);
251
+ const buildForm = () => {
252
+ const form = new FormData();
253
+ form.append("attachment", fileBuffer, fileName);
254
+ form.append("name", fileName);
255
+ form.append("chatGuid", options.chatGuid);
256
+ form.append("isSticker", "true");
257
+ form.append("method", "private-api");
258
+ if (options.selectedMessageGuid) {
259
+ form.append("selectedMessageGuid", options.selectedMessageGuid);
260
+ form.append("partIndex", "0");
261
+ form.append("stickerX", String(options.stickerX ?? 0.5));
262
+ form.append("stickerY", String(options.stickerY ?? 0.5));
263
+ form.append("stickerScale", String(options.stickerScale ?? 0.75));
264
+ form.append("stickerRotation", String(options.stickerRotation ?? 0));
265
+ form.append("stickerWidth", String(options.stickerWidth ?? 300));
266
+ }
267
+ return form;
268
+ };
269
+ try {
270
+ const form = buildForm();
271
+ const { data } = await this.http.post("/api/v1/message/attachment", form, {
272
+ headers: form.getHeaders()
273
+ });
274
+ return data.data;
275
+ } catch (error) {
276
+ if (!isChatNotExistError(error)) throw error;
277
+ await this.ensureChatExists(options.chatGuid);
278
+ const form = buildForm();
279
+ const { data } = await this.http.post("/api/v1/message/attachment", form, {
280
+ headers: form.getHeaders()
281
+ });
282
+ return data.data;
283
+ }
284
+ });
285
+ }
286
+ };
287
+ var ChatModule = class {
288
+ constructor(http) {
289
+ this.http = http;
290
+ }
291
+ async getChats(options) {
292
+ const payload = {};
293
+ if (options?.offset !== void 0) payload.offset = options.offset;
294
+ if (options?.limit !== void 0) payload.limit = options.limit;
295
+ if (options?.sort) payload.sort = options.sort;
296
+ if (options?.withLastMessage) {
297
+ payload.with = ["lastmessage"];
298
+ }
299
+ const response = await this.http.post("/api/v1/chat/query", payload);
300
+ return response.data.data;
301
+ }
302
+ async createChat(options) {
303
+ const response = await this.http.post("/api/v1/chat/new", options);
304
+ return response.data.data;
305
+ }
306
+ async getChat(guid, options) {
307
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(guid)}`, {
308
+ params: options?.with ? { with: options.with.join(",") } : {}
309
+ });
310
+ return response.data.data;
311
+ }
312
+ async updateChat(guid, options) {
313
+ const response = await this.http.put(`/api/v1/chat/${encodeURIComponent(guid)}`, options);
314
+ return response.data.data;
315
+ }
316
+ async deleteChat(guid) {
317
+ await this.http.delete(`/api/v1/chat/${encodeURIComponent(guid)}`);
318
+ }
319
+ async markChatRead(guid) {
320
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(guid)}/read`);
321
+ }
322
+ async markChatUnread(guid) {
323
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(guid)}/unread`);
324
+ }
325
+ async leaveChat(guid) {
326
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(guid)}/leave`);
327
+ }
328
+ async addParticipant(chatGuid, address) {
329
+ const response = await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/participant`, {
330
+ address
331
+ });
332
+ return response.data.data;
333
+ }
334
+ async removeParticipant(chatGuid, address) {
335
+ const response = await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/participant/remove`, {
336
+ address
337
+ });
338
+ return response.data.data;
339
+ }
340
+ async getChatMessages(chatGuid, options) {
341
+ const params = {};
342
+ if (options?.offset !== void 0) params.offset = options.offset;
343
+ if (options?.limit !== void 0) params.limit = options.limit;
344
+ if (options?.sort) params.sort = options.sort;
345
+ if (options?.before !== void 0) params.before = options.before;
346
+ if (options?.after !== void 0) params.after = options.after;
347
+ if (options?.with) params.with = options.with.join(",");
348
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(chatGuid)}/message`, {
349
+ params
350
+ });
351
+ return response.data.data;
352
+ }
353
+ async setGroupIcon(chatGuid, filePath) {
354
+ const fileBuffer = await readFile(filePath);
355
+ const fileName = path__default.basename(filePath);
356
+ const form = new FormData();
357
+ form.append("icon", fileBuffer, fileName);
358
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/icon`, form, {
359
+ headers: form.getHeaders()
360
+ });
361
+ }
362
+ async removeGroupIcon(chatGuid) {
363
+ await this.http.delete(`/api/v1/chat/${encodeURIComponent(chatGuid)}/icon`);
364
+ }
365
+ async getGroupIcon(chatGuid) {
366
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(chatGuid)}/icon`, {
367
+ responseType: "arraybuffer"
368
+ });
369
+ return Buffer.from(response.data);
370
+ }
371
+ async getChatCount(options) {
372
+ const response = await this.http.get("/api/v1/chat/count", {
373
+ params: options?.includeArchived !== void 0 ? { includeArchived: options.includeArchived } : {}
374
+ });
375
+ return response.data.data;
376
+ }
377
+ async startTyping(chatGuid) {
378
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/typing`);
379
+ }
380
+ };
381
+
382
+ // modules/contact.ts
383
+ var ContactModule = class {
384
+ constructor(http) {
385
+ this.http = http;
386
+ }
387
+ async getContacts() {
388
+ const response = await this.http.get("/api/v1/contact");
389
+ return response.data.data;
390
+ }
391
+ async getContactCard(address) {
392
+ const response = await this.http.post("/api/v1/contact/query", {
393
+ addresses: [address]
394
+ });
395
+ const contacts = response.data.data;
396
+ return contacts?.[0] ?? null;
397
+ }
398
+ async shareContactCard(chatGuid) {
399
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/share/contact`);
400
+ }
401
+ async shouldShareContact(chatGuid) {
402
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(chatGuid)}/share/contact/status`);
403
+ return response.data.data;
404
+ }
405
+ };
406
+
407
+ // modules/facetime.ts
408
+ var FaceTimeModule = class {
409
+ constructor(http) {
410
+ this.http = http;
411
+ }
412
+ async createFaceTimeLink() {
413
+ const response = await this.http.post("/api/v1/facetime/session");
414
+ return response.data.data;
415
+ }
416
+ };
417
+
418
+ // modules/handle.ts
419
+ var HandleModule = class {
420
+ constructor(http) {
421
+ this.http = http;
422
+ }
423
+ async getHandleCount() {
424
+ const response = await this.http.get("/api/v1/handle/count");
425
+ return response.data.data.total;
426
+ }
427
+ async queryHandles(options) {
428
+ const body = { offset: 0 };
429
+ if (options?.address) body.address = options.address;
430
+ if (options?.with) body.with = options.with.join(",");
431
+ if (options?.offset !== void 0) body.offset = options.offset;
432
+ if (options?.limit !== void 0) body.limit = options.limit;
433
+ const response = await this.http.post("/api/v1/handle/query", body);
434
+ return {
435
+ data: response.data.data,
436
+ metadata: response.data.metadata
437
+ };
438
+ }
439
+ async getHandle(guid) {
440
+ const response = await this.http.get(`/api/v1/handle/${encodeURIComponent(guid)}`);
441
+ return response.data.data;
442
+ }
443
+ async getHandleAvailability(address, type) {
444
+ const response = await this.http.get(`/api/v1/handle/availability/${type}`, {
445
+ params: { address }
446
+ });
447
+ return response.data.data.available;
448
+ }
449
+ async getHandleFocusStatus(guid) {
450
+ const response = await this.http.get(`/api/v1/handle/${encodeURIComponent(guid)}/focus`);
451
+ return response.data.data.status;
452
+ }
453
+ };
454
+
455
+ // modules/icloud.ts
456
+ var ICloudModule = class {
457
+ constructor(http) {
458
+ this.http = http;
459
+ }
460
+ async getFindMyFriends() {
461
+ const response = await this.http.get("/api/v1/icloud/findmy/friends");
462
+ return response.data.data;
463
+ }
464
+ async refreshFindMyFriends() {
465
+ const response = await this.http.post("/api/v1/icloud/findmy/friends/refresh");
466
+ return response.data.data;
467
+ }
468
+ async getLocationForHandle(handle) {
469
+ const friends = await this.getFindMyFriends();
470
+ return friends.find((f) => f.handle === handle) ?? null;
471
+ }
472
+ async isHandleSharingLocation(handle) {
473
+ const location = await this.getLocationForHandle(handle);
474
+ return location !== null;
475
+ }
476
+ };
477
+ var SEND_TIMEOUT_MS = 5e3;
478
+ async function withSendTimeout(promise, fallback) {
479
+ promise.catch(() => fallback());
480
+ let timer;
481
+ const timeout = new Promise((resolve) => {
482
+ timer = setTimeout(() => resolve(fallback()), SEND_TIMEOUT_MS);
483
+ });
484
+ try {
485
+ return await Promise.race([promise, timeout]);
486
+ } finally {
487
+ clearTimeout(timer);
488
+ }
489
+ }
490
+ var MessageModule = class {
491
+ constructor(http, enqueueSend = (task) => task()) {
492
+ this.http = http;
493
+ this.enqueueSend = enqueueSend;
494
+ }
495
+ async sendMessage(options) {
496
+ return this.enqueueSend(async () => {
497
+ const tempGuid = options.tempGuid || randomUUID();
498
+ const payload = {
499
+ ...options,
500
+ tempGuid,
501
+ ...options.selectedMessageGuid && { method: "private-api" }
502
+ };
503
+ const doSend = async () => {
504
+ const response = await this.http.post("/api/v1/message/text", payload);
505
+ return response.data.data;
506
+ };
507
+ const fallback = () => ({ guid: tempGuid, tempGuid, text: options.message, dateCreated: Date.now() });
508
+ try {
509
+ return await withSendTimeout(doSend(), fallback);
510
+ } catch (error) {
511
+ if (!isChatNotExistError(error)) throw error;
512
+ const address = extractAddress(options.chatGuid);
513
+ if (!address) throw error;
514
+ const service = extractService(options.chatGuid);
515
+ await createChatWithMessage({
516
+ http: this.http,
517
+ address,
518
+ message: options.message,
519
+ tempGuid,
520
+ subject: options.subject,
521
+ effectId: options.effectId,
522
+ service
523
+ });
524
+ return fallback();
525
+ }
526
+ });
527
+ }
528
+ async getMessage(guid, options) {
529
+ const response = await this.http.get(`/api/v1/message/${encodeURIComponent(guid)}`, {
530
+ params: options?.with ? { with: options.with.join(",") } : {}
531
+ });
532
+ return response.data.data;
533
+ }
534
+ async getMessages(options) {
535
+ const response = await this.http.post("/api/v1/message/query", options ?? {});
536
+ return response.data.data;
537
+ }
538
+ async getMessageCount(options) {
539
+ const params = {};
540
+ if (options?.after !== void 0) params.after = options.after;
541
+ if (options?.before !== void 0) params.before = options.before;
542
+ if (options?.chatGuid) params.chatGuid = options.chatGuid;
543
+ if (options?.minRowId !== void 0) params.minRowId = options.minRowId;
544
+ if (options?.maxRowId !== void 0) params.maxRowId = options.maxRowId;
545
+ const response = await this.http.get("/api/v1/message/count", { params });
546
+ return response.data.data.total;
547
+ }
548
+ async getUpdatedMessageCount(options) {
549
+ const params = {};
550
+ params.after = options?.after ?? 0;
551
+ if (options?.before !== void 0) params.before = options.before;
552
+ if (options?.chatGuid) params.chatGuid = options.chatGuid;
553
+ if (options?.minRowId !== void 0) params.minRowId = options.minRowId;
554
+ if (options?.maxRowId !== void 0) params.maxRowId = options.maxRowId;
555
+ const response = await this.http.get("/api/v1/message/count/updated", {
556
+ params
557
+ });
558
+ return response.data.data.total;
559
+ }
560
+ async getSentMessageCount(options) {
561
+ const params = {};
562
+ if (options?.after !== void 0) params.after = options.after;
563
+ if (options?.before !== void 0) params.before = options.before;
564
+ if (options?.chatGuid) params.chatGuid = options.chatGuid;
565
+ if (options?.minRowId !== void 0) params.minRowId = options.minRowId;
566
+ if (options?.maxRowId !== void 0) params.maxRowId = options.maxRowId;
567
+ const response = await this.http.get("/api/v1/message/count/me", {
568
+ params
569
+ });
570
+ return response.data.data.total;
571
+ }
572
+ async editMessage(options) {
573
+ return this.enqueueSend(async () => {
574
+ const doEdit = async () => {
575
+ const response = await this.http.post(
576
+ `/api/v1/message/${encodeURIComponent(options.messageGuid)}/edit`,
577
+ {
578
+ editedMessage: options.editedMessage,
579
+ backwardsCompatibilityMessage: options.backwardsCompatibilityMessage || options.editedMessage,
580
+ partIndex: options.partIndex ?? 0
581
+ }
582
+ );
583
+ return response.data.data;
584
+ };
585
+ return withSendTimeout(
586
+ doEdit(),
587
+ () => ({
588
+ guid: options.messageGuid,
589
+ text: options.editedMessage,
590
+ dateEdited: Date.now()
591
+ })
592
+ );
593
+ });
594
+ }
595
+ async sendReaction(options) {
596
+ return this.enqueueSend(async () => {
597
+ const doReact = async () => {
598
+ const response = await this.http.post("/api/v1/message/react", {
599
+ chatGuid: options.chatGuid,
600
+ selectedMessageGuid: options.messageGuid,
601
+ reaction: options.reaction,
602
+ partIndex: options.partIndex ?? 0
603
+ });
604
+ return response.data.data;
605
+ };
606
+ return withSendTimeout(
607
+ doReact(),
608
+ () => ({
609
+ guid: randomUUID(),
610
+ associatedMessageGuid: options.messageGuid,
611
+ dateCreated: Date.now()
612
+ })
613
+ );
614
+ });
615
+ }
616
+ async unsendMessage(options) {
617
+ return this.enqueueSend(async () => {
618
+ const doUnsend = async () => {
619
+ const response = await this.http.post(
620
+ `/api/v1/message/${encodeURIComponent(options.messageGuid)}/unsend`,
621
+ { partIndex: options.partIndex ?? 0 }
622
+ );
623
+ return response.data.data;
624
+ };
625
+ return withSendTimeout(
626
+ doUnsend(),
627
+ () => ({
628
+ guid: options.messageGuid,
629
+ dateRetracted: Date.now()
630
+ })
631
+ );
632
+ });
633
+ }
634
+ async notifyMessage(guid) {
635
+ await this.http.post(`/api/v1/message/${encodeURIComponent(guid)}/notify`);
636
+ }
637
+ async getEmbeddedMedia(guid) {
638
+ const response = await this.http.get(`/api/v1/message/${encodeURIComponent(guid)}/embedded-media`);
639
+ return response.data.data;
640
+ }
641
+ async searchMessages(options) {
642
+ const { query, chatGuid, offset, limit, sort, before, after } = options;
643
+ if (!query || query.trim().length === 0) {
644
+ throw new Error("Search query cannot be empty");
645
+ }
646
+ const where = [
647
+ {
648
+ statement: "message.text LIKE :text",
649
+ args: { text: `%${query}%` }
650
+ }
651
+ ];
652
+ const payload = {
653
+ where
654
+ };
655
+ if (chatGuid) payload.chatGuid = chatGuid;
656
+ if (offset !== void 0) payload.offset = offset;
657
+ if (limit !== void 0) payload.limit = limit;
658
+ if (sort) payload.sort = sort;
659
+ if (before !== void 0) payload.before = before;
660
+ if (after !== void 0) payload.after = after;
661
+ const response = await this.http.post("/api/v1/message/query", payload);
662
+ return response.data.data;
663
+ }
664
+ };
665
+
666
+ // modules/scheduled.ts
667
+ var ScheduledMessageModule = class {
668
+ constructor(http) {
669
+ this.http = http;
670
+ }
671
+ async createScheduledMessage(options) {
672
+ const response = await this.http.post("/api/v1/message/schedule", options);
673
+ return response.data.data;
674
+ }
675
+ async getScheduledMessages() {
676
+ const response = await this.http.get("/api/v1/message/schedule");
677
+ return response.data.data;
678
+ }
679
+ async updateScheduledMessage(id, options) {
680
+ const response = await this.http.put(`/api/v1/message/schedule/${encodeURIComponent(id)}`, options);
681
+ return response.data.data;
682
+ }
683
+ async deleteScheduledMessage(id) {
684
+ await this.http.delete(`/api/v1/message/schedule/${encodeURIComponent(id)}`);
685
+ }
686
+ };
687
+
688
+ // modules/server.ts
689
+ var ServerModule = class {
690
+ constructor(http) {
691
+ this.http = http;
692
+ }
693
+ async getServerInfo() {
694
+ const response = await this.http.get("/api/v1/server/info");
695
+ return response.data.data;
696
+ }
697
+ async getMessageStats() {
698
+ const response = await this.http.get("/api/v1/server/statistics/totals");
699
+ return response.data.data;
700
+ }
701
+ async getServerLogs(count) {
702
+ const response = await this.http.get("/api/v1/server/logs", {
703
+ params: count !== void 0 ? { count } : {}
704
+ });
705
+ return response.data.data;
706
+ }
707
+ async getMediaStatistics(options) {
708
+ const params = {};
709
+ if (options?.only) params.only = options.only.join(",");
710
+ const response = await this.http.get("/api/v1/server/statistics/media", {
711
+ params
712
+ });
713
+ return response.data.data;
714
+ }
715
+ async getMediaStatisticsByChat(options) {
716
+ const params = {};
717
+ if (options?.only) params.only = options.only.join(",");
718
+ const response = await this.http.get("/api/v1/server/statistics/media/chat", {
719
+ params
720
+ });
721
+ return response.data.data;
722
+ }
723
+ };
724
+
725
+ // client.ts
726
+ var _BBGun = class _BBGun extends EventEmitter {
727
+ constructor(config = {}) {
728
+ super();
729
+ __publicField(this, "config");
730
+ __publicField(this, "logger", getLogger("BBGun"));
731
+ __publicField(this, "http");
732
+ __publicField(this, "socket");
733
+ __publicField(this, "attachments");
734
+ __publicField(this, "messages");
735
+ __publicField(this, "chats");
736
+ __publicField(this, "contacts");
737
+ __publicField(this, "handles");
738
+ __publicField(this, "facetime");
739
+ __publicField(this, "icloud");
740
+ __publicField(this, "scheduledMessages");
741
+ __publicField(this, "server");
742
+ __publicField(this, "processedMessages", /* @__PURE__ */ new Set());
743
+ __publicField(this, "lastMessageTime", 0);
744
+ __publicField(this, "sendQueue", Promise.resolve());
745
+ __publicField(this, "readyEmitted", false);
746
+ __publicField(this, "listenersAttached", false);
747
+ this.config = {
748
+ serverUrl: "http://localhost:1234",
749
+ logLevel: "info",
750
+ logToFile: true,
751
+ ...config
752
+ };
753
+ if (this.config.logToFile === false) {
754
+ setGlobalLogToFile(false);
755
+ }
756
+ if (this.config.logLevel) {
757
+ setGlobalLogLevel(this.config.logLevel);
758
+ }
759
+ this.http = axios.create({
760
+ baseURL: this.config.serverUrl,
761
+ params: this.config.apiKey ? { password: this.config.apiKey } : void 0
762
+ });
763
+ this.socket = io(this.config.serverUrl, {
764
+ query: this.config.apiKey ? { password: this.config.apiKey } : void 0,
765
+ transports: ["websocket"],
766
+ timeout: 1e4,
767
+ forceNew: true,
768
+ reconnection: true,
769
+ reconnectionAttempts: Number.POSITIVE_INFINITY,
770
+ reconnectionDelay: 100,
771
+ reconnectionDelayMax: 2e3,
772
+ randomizationFactor: 0.1
773
+ });
774
+ const enqueueSend = this.enqueueSend.bind(this);
775
+ this.attachments = new AttachmentModule(this.http, enqueueSend);
776
+ this.messages = new MessageModule(this.http, enqueueSend);
777
+ this.chats = new ChatModule(this.http);
778
+ this.contacts = new ContactModule(this.http);
779
+ this.handles = new HandleModule(this.http);
780
+ this.facetime = new FaceTimeModule(this.http);
781
+ this.icloud = new ICloudModule(this.http);
782
+ this.scheduledMessages = new ScheduledMessageModule(this.http);
783
+ this.server = new ServerModule(this.http);
784
+ }
785
+ static getInstance(config) {
786
+ const existing = _BBGun.getGlobalSdk();
787
+ if (existing) return existing;
788
+ const instance = new _BBGun(config);
789
+ _BBGun.setGlobalSdk(instance);
790
+ return instance;
791
+ }
792
+ emit(event, ...args) {
793
+ return super.emit(event, ...args);
794
+ }
795
+ on(event, listener) {
796
+ return super.on(event, listener);
797
+ }
798
+ once(event, listener) {
799
+ return super.once(event, listener);
800
+ }
801
+ off(event, listener) {
802
+ return super.off(event, listener);
803
+ }
804
+ addListener(event, listener) {
805
+ return super.addListener(event, listener);
806
+ }
807
+ removeListener(event, listener) {
808
+ return super.removeListener(event, listener);
809
+ }
810
+ async connect() {
811
+ if (!this.listenersAttached) {
812
+ this.listenersAttached = true;
813
+ this.attachSocketListeners();
814
+ }
815
+ if (this.socket.connected) {
816
+ this.logger.info("Already connected to BlueBubbles server");
817
+ return;
818
+ }
819
+ this.socket.connect();
820
+ }
821
+ attachSocketListeners() {
822
+ const serverEvents = [
823
+ "new-message",
824
+ "message-updated",
825
+ "updated-message",
826
+ "chat-read-status-changed",
827
+ "group-name-change",
828
+ "participant-added",
829
+ "participant-removed",
830
+ "participant-left",
831
+ "group-icon-changed",
832
+ "group-icon-removed",
833
+ "message-send-error",
834
+ "typing-indicator",
835
+ "new-server",
836
+ "incoming-facetime",
837
+ "ft-call-status-changed",
838
+ "hello-world"
839
+ ];
840
+ for (const eventName of serverEvents) {
841
+ this.socket.on(eventName, (...args) => {
842
+ if (eventName === "new-message" && args.length > 0) {
843
+ const message = args[0];
844
+ if (message?.guid) {
845
+ if (this.processedMessages.has(message.guid)) {
846
+ this.logger.debug(`Message already processed, skipping duplicate: ${message.guid}`);
847
+ return;
848
+ }
849
+ this.processedMessages.add(message.guid);
850
+ if (message.dateCreated && message.dateCreated > this.lastMessageTime) {
851
+ this.lastMessageTime = message.dateCreated;
852
+ }
853
+ }
854
+ }
855
+ if (args.length > 0) {
856
+ super.emit(eventName, args[0]);
857
+ } else {
858
+ super.emit(eventName);
859
+ }
860
+ });
861
+ }
862
+ this.socket.on("disconnect", (reason) => {
863
+ this.logger.info(`Disconnected from BlueBubbles server (reason: ${reason})`);
864
+ this.readyEmitted = false;
865
+ this.emit("disconnect");
866
+ if (reason === "io server disconnect") {
867
+ this.logger.info("Server disconnected, manually triggering reconnect...");
868
+ this.socket.connect();
869
+ }
870
+ });
871
+ this.socket.io.on("reconnect_attempt", (attempt) => {
872
+ this.logger.info(`Reconnection attempt #${attempt}...`);
873
+ });
874
+ this.socket.io.on("reconnect", (attempt) => {
875
+ this.logger.info(`Reconnected successfully after ${attempt} attempt(s)`);
876
+ });
877
+ this.socket.io.on("reconnect_error", (error) => {
878
+ this.logger.warn(`Reconnection error: ${error.message}`);
879
+ });
880
+ this.socket.io.on("reconnect_failed", () => {
881
+ this.logger.error("All reconnection attempts failed");
882
+ });
883
+ this.socket.on("connect", async () => {
884
+ this.logger.info("Connected to BlueBubbles server");
885
+ if (!this.readyEmitted) {
886
+ this.readyEmitted = true;
887
+ await this.recoverMissedMessages();
888
+ this.emit("ready");
889
+ }
890
+ });
891
+ this.socket.on("connect_error", (error) => {
892
+ this.logger.warn(`Connection error: ${error.message}`);
893
+ });
894
+ }
895
+ async close() {
896
+ this.socket.disconnect();
897
+ }
898
+ async recoverMissedMessages() {
899
+ if (this.lastMessageTime <= 0) return;
900
+ try {
901
+ const after = this.lastMessageTime;
902
+ const messages = await this.messages.getMessages({
903
+ after,
904
+ sort: "ASC",
905
+ limit: 100
906
+ });
907
+ if (messages.length === 0) {
908
+ this.logger.debug("No missed messages to recover");
909
+ return;
910
+ }
911
+ this.logger.info(`Recovering ${messages.length} missed message(s)`);
912
+ for (const msg of messages) {
913
+ if (msg.guid && !this.processedMessages.has(msg.guid)) {
914
+ this.processedMessages.add(msg.guid);
915
+ if (msg.dateCreated && msg.dateCreated > this.lastMessageTime) {
916
+ this.lastMessageTime = msg.dateCreated;
917
+ }
918
+ super.emit("new-message", msg);
919
+ }
920
+ }
921
+ } catch (e) {
922
+ this.logger.warn(`Failed to recover missed messages: ${e}`);
923
+ }
924
+ }
925
+ clearProcessedMessages(maxSize = 1e3) {
926
+ if (this.processedMessages.size > maxSize) {
927
+ const messages = Array.from(this.processedMessages);
928
+ this.processedMessages.clear();
929
+ messages.slice(-Math.floor(maxSize / 2)).forEach((guid) => {
930
+ this.processedMessages.add(guid);
931
+ });
932
+ this.logger.debug(`Cleared processed message records, retained ${this.processedMessages.size} messages`);
933
+ }
934
+ }
935
+ getProcessedMessageCount() {
936
+ return this.processedMessages.size;
937
+ }
938
+ enqueueSend(task) {
939
+ const result = this.sendQueue.then(() => task());
940
+ this.sendQueue = result.catch(() => {
941
+ });
942
+ return result;
943
+ }
944
+ };
945
+ __publicField(_BBGun, "getGlobalSdk", () => globalThis.__BBGun__ ?? null);
946
+ __publicField(_BBGun, "setGlobalSdk", (sdk) => {
947
+ globalThis.__BBGun__ = sdk;
948
+ });
949
+ var BBGun = _BBGun;
950
+ var SDK = BBGun.getInstance;
951
+
952
+ // events.ts
953
+ var SCHEDULED_MESSAGE_ERROR = "scheduled-message-error";
954
+ var SCHEDULED_MESSAGE_SENT = "scheduled-message-sent";
955
+ var SCHEDULED_MESSAGE_DELETED = "scheduled-message-deleted";
956
+ var SCHEDULED_MESSAGE_UPDATED = "scheduled-message-updated";
957
+ var SCHEDULED_MESSAGE_CREATED = "scheduled-message-created";
958
+ var NEW_MESSAGE = "new-message";
959
+ var MESSAGE_SEND_ERROR = "message-send-error";
960
+ var MESSAGE_UPDATED = "updated-message";
961
+ var NEW_SERVER = "new-server";
962
+ var PARTICIPANT_REMOVED = "participant-removed";
963
+ var PARTICIPANT_ADDED = "participant-added";
964
+ var PARTICIPANT_LEFT = "participant-left";
965
+ var GROUP_ICON_CHANGED = "group-icon-changed";
966
+ var GROUP_ICON_REMOVED = "group-icon-removed";
967
+ var CHAT_READ_STATUS_CHANGED = "chat-read-status-changed";
968
+ var HELLO_WORLD = "hello-world";
969
+ var TYPING_INDICATOR = "typing-indicator";
970
+ var SERVER_UPDATE = "server-update";
971
+ var SERVER_UPDATE_DOWNLOADING = "server-update-downloading";
972
+ var SERVER_UPDATE_INSTALLING = "server-update-installing";
973
+ var GROUP_NAME_CHANGE = "group-name-change";
974
+ var INCOMING_FACETIME = "incoming-facetime";
975
+ var SETTINGS_BACKUP_CREATED = "settings-backup-created";
976
+ var SETTINGS_BACKUP_DELETED = "settings-backup-deleted";
977
+ var SETTINGS_BACKUP_UPDATED = "settings-backup-updated";
978
+ var THEME_BACKUP_CREATED = "theme-backup-created";
979
+ var THEME_BACKUP_DELETED = "theme-backup-deleted";
980
+ var THEME_BACKUP_UPDATED = "theme-backup-updated";
981
+ var IMESSAGE_ALIASES_REMOVED = "imessage-aliases-removed";
982
+ var FT_CALL_STATUS_CHANGED = "ft-call-status-changed";
983
+ var NEW_FINDMY_LOCATION = "new-findmy-location";
984
+
985
+ export { BBGun, CHAT_READ_STATUS_CHANGED, FT_CALL_STATUS_CHANGED, GROUP_ICON_CHANGED, GROUP_ICON_REMOVED, GROUP_NAME_CHANGE, HELLO_WORLD, IMESSAGE_ALIASES_REMOVED, INCOMING_FACETIME, MESSAGE_SEND_ERROR, MESSAGE_UPDATED, NEW_FINDMY_LOCATION, NEW_MESSAGE, NEW_SERVER, PARTICIPANT_ADDED, PARTICIPANT_LEFT, PARTICIPANT_REMOVED, SCHEDULED_MESSAGE_CREATED, SCHEDULED_MESSAGE_DELETED, SCHEDULED_MESSAGE_ERROR, SCHEDULED_MESSAGE_SENT, SCHEDULED_MESSAGE_UPDATED, SDK, SERVER_UPDATE, SERVER_UPDATE_DOWNLOADING, SERVER_UPDATE_INSTALLING, SETTINGS_BACKUP_CREATED, SETTINGS_BACKUP_DELETED, SETTINGS_BACKUP_UPDATED, THEME_BACKUP_CREATED, THEME_BACKUP_DELETED, THEME_BACKUP_UPDATED, TYPING_INDICATOR, getLogger, setGlobalLogLevel, setGlobalLogToFile };
986
+ //# sourceMappingURL=index.js.map
987
+ //# sourceMappingURL=index.js.map