@photon-ai/advanced-imessage-kit 1.2.0 → 1.3.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.cjs CHANGED
@@ -202,15 +202,23 @@ var AttachmentModule = class {
202
202
  const fileName = options.fileName || path__namespace.default.basename(options.filePath);
203
203
  const form = new FormData__default.default();
204
204
  form.append("attachment", await promises.readFile(options.filePath), fileName);
205
- const { data } = await this.http.post("/api/v1/attachment/upload", form, {
205
+ form.append("name", fileName);
206
+ form.append("chatGuid", options.chatGuid);
207
+ form.append("isSticker", "true");
208
+ form.append("method", "private-api");
209
+ if (options.selectedMessageGuid) {
210
+ form.append("selectedMessageGuid", options.selectedMessageGuid);
211
+ form.append("partIndex", "0");
212
+ }
213
+ form.append("stickerX", String(options.stickerX ?? 0.5));
214
+ form.append("stickerY", String(options.stickerY ?? 0.5));
215
+ form.append("stickerScale", String(options.stickerScale ?? 0.75));
216
+ form.append("stickerRotation", String(options.stickerRotation ?? 0));
217
+ form.append("stickerWidth", String(options.stickerWidth ?? 300));
218
+ const { data } = await this.http.post("/api/v1/message/attachment", form, {
206
219
  headers: form.getHeaders()
207
220
  });
208
- const response = await this.http.post("/api/v1/message/multipart", {
209
- chatGuid: options.chatGuid,
210
- selectedMessageGuid: options.selectedMessageGuid,
211
- parts: [{ partIndex: 0, attachment: data.data.path, name: fileName }]
212
- });
213
- return response.data.data;
221
+ return data.data;
214
222
  });
215
223
  }
216
224
  };
@@ -227,35 +235,32 @@ var ChatModule = class {
227
235
  return response.data.data;
228
236
  }
229
237
  async getChat(guid, options) {
230
- const response = await this.http.get(`/api/v1/chat/${guid}`, {
238
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(guid)}`, {
231
239
  params: options?.with ? { with: options.with.join(",") } : {}
232
240
  });
233
241
  return response.data.data;
234
242
  }
235
243
  async updateChat(guid, options) {
236
- const response = await this.http.put(`/api/v1/chat/${guid}`, options);
244
+ const response = await this.http.put(`/api/v1/chat/${encodeURIComponent(guid)}`, options);
237
245
  return response.data.data;
238
246
  }
239
247
  async deleteChat(guid) {
240
- await this.http.delete(`/api/v1/chat/${guid}`);
248
+ await this.http.delete(`/api/v1/chat/${encodeURIComponent(guid)}`);
241
249
  }
242
250
  async markChatRead(guid) {
243
- await this.http.post(`/api/v1/chat/${guid}/read`);
244
- }
245
- async markChatUnread(guid) {
246
- await this.http.post(`/api/v1/chat/${guid}/unread`);
251
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(guid)}/read`);
247
252
  }
248
253
  async leaveChat(guid) {
249
- await this.http.post(`/api/v1/chat/${guid}/leave`);
254
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(guid)}/leave`);
250
255
  }
251
256
  async addParticipant(chatGuid, address) {
252
- const response = await this.http.post(`/api/v1/chat/${chatGuid}/participant`, {
257
+ const response = await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/participant`, {
253
258
  address
254
259
  });
255
260
  return response.data.data;
256
261
  }
257
262
  async removeParticipant(chatGuid, address) {
258
- const response = await this.http.delete(`/api/v1/chat/${chatGuid}/participant`, {
263
+ const response = await this.http.delete(`/api/v1/chat/${encodeURIComponent(chatGuid)}/participant`, {
259
264
  data: { address }
260
265
  });
261
266
  return response.data.data;
@@ -268,7 +273,7 @@ var ChatModule = class {
268
273
  if (options?.before !== void 0) params.before = options.before;
269
274
  if (options?.after !== void 0) params.after = options.after;
270
275
  if (options?.with) params.with = options.with.join(",");
271
- const response = await this.http.get(`/api/v1/chat/${chatGuid}/message`, {
276
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(chatGuid)}/message`, {
272
277
  params
273
278
  });
274
279
  return response.data.data;
@@ -278,15 +283,15 @@ var ChatModule = class {
278
283
  const fileName = path__namespace.default.basename(filePath);
279
284
  const form = new FormData__default.default();
280
285
  form.append("icon", fileBuffer, fileName);
281
- await this.http.post(`/api/v1/chat/${chatGuid}/icon`, form, {
286
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/icon`, form, {
282
287
  headers: form.getHeaders()
283
288
  });
284
289
  }
285
290
  async removeGroupIcon(chatGuid) {
286
- await this.http.delete(`/api/v1/chat/${chatGuid}/icon`);
291
+ await this.http.delete(`/api/v1/chat/${encodeURIComponent(chatGuid)}/icon`);
287
292
  }
288
293
  async getGroupIcon(chatGuid) {
289
- const response = await this.http.get(`/api/v1/chat/${chatGuid}/icon`, {
294
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(chatGuid)}/icon`, {
290
295
  responseType: "arraybuffer"
291
296
  });
292
297
  return Buffer.from(response.data);
@@ -298,10 +303,10 @@ var ChatModule = class {
298
303
  return response.data.data;
299
304
  }
300
305
  async startTyping(chatGuid) {
301
- await this.http.post(`/api/v1/chat/${chatGuid}/typing`);
306
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/typing`);
302
307
  }
303
308
  async stopTyping(chatGuid) {
304
- await this.http.delete(`/api/v1/chat/${chatGuid}/typing`);
309
+ await this.http.delete(`/api/v1/chat/${encodeURIComponent(chatGuid)}/typing`);
305
310
  }
306
311
  };
307
312
 
@@ -321,10 +326,10 @@ var ContactModule = class {
321
326
  return response.data.data;
322
327
  }
323
328
  async shareContactCard(chatGuid) {
324
- await this.http.post(`/api/v1/chat/${chatGuid}/share/contact`);
329
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/share/contact`);
325
330
  }
326
331
  async shouldShareContact(chatGuid) {
327
- const response = await this.http.get(`/api/v1/chat/${chatGuid}/share/contact/status`);
332
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(chatGuid)}/share/contact/status`);
328
333
  return response.data.data;
329
334
  }
330
335
  };
@@ -390,6 +395,40 @@ var ICloudModule = class {
390
395
  await this.http.post("/api/v1/icloud/findmy/friends/refresh");
391
396
  }
392
397
  };
398
+
399
+ // lib/auto-create-chat.ts
400
+ function isChatNotExistError(error) {
401
+ const axiosError = error;
402
+ const errorMsg = axiosError?.response?.data?.error?.message || axiosError?.response?.data?.message || "";
403
+ const lowerMsg = errorMsg.toLowerCase();
404
+ return lowerMsg.includes("chat does not exist") || lowerMsg.includes("chat not found");
405
+ }
406
+ function extractAddress(chatGuid) {
407
+ const parts = chatGuid.split(";-;");
408
+ if (parts.length !== 2 || !parts[1]) {
409
+ return void 0;
410
+ }
411
+ return parts[1];
412
+ }
413
+ async function createChatWithMessage(options) {
414
+ const { http, address, message, tempGuid, subject, effectId } = options;
415
+ try {
416
+ const response = await http.post("/api/v1/chat/new", {
417
+ addresses: [address],
418
+ message,
419
+ tempGuid,
420
+ subject,
421
+ effectId
422
+ });
423
+ return response.data.data?.guid;
424
+ } catch (error) {
425
+ throw new Error(
426
+ `Failed to create chat with address "${address}": ${error instanceof Error ? error.message : String(error)}`
427
+ );
428
+ }
429
+ }
430
+
431
+ // modules/message.ts
393
432
  var MessageModule = class {
394
433
  constructor(http, enqueueSend = (task) => task()) {
395
434
  this.http = http;
@@ -397,12 +436,25 @@ var MessageModule = class {
397
436
  }
398
437
  async sendMessage(options) {
399
438
  return this.enqueueSend(async () => {
400
- const payload = {
401
- ...options,
402
- tempGuid: options.tempGuid || crypto.randomUUID()
403
- };
404
- const response = await this.http.post("/api/v1/message/text", payload);
405
- return response.data.data;
439
+ const tempGuid = options.tempGuid || crypto.randomUUID();
440
+ const payload = { ...options, tempGuid };
441
+ try {
442
+ const response = await this.http.post("/api/v1/message/text", payload);
443
+ return response.data.data;
444
+ } catch (error) {
445
+ if (!isChatNotExistError(error)) throw error;
446
+ const address = extractAddress(options.chatGuid);
447
+ if (!address) throw error;
448
+ await createChatWithMessage({
449
+ http: this.http,
450
+ address,
451
+ message: options.message,
452
+ tempGuid,
453
+ subject: options.subject,
454
+ effectId: options.effectId
455
+ });
456
+ return { guid: tempGuid, text: options.message, dateCreated: Date.now() };
457
+ }
406
458
  });
407
459
  }
408
460
  async getMessage(guid, options) {
@@ -491,6 +543,51 @@ var MessageModule = class {
491
543
  }
492
544
  };
493
545
 
546
+ // modules/poll.ts
547
+ var PollModule = class {
548
+ constructor(http) {
549
+ this.http = http;
550
+ }
551
+ async create(options) {
552
+ if (options.options.length < 2) {
553
+ throw new Error("Poll must have at least 2 options");
554
+ }
555
+ const { data } = await this.http.post("/api/v1/poll/create", {
556
+ chatGuid: options.chatGuid,
557
+ title: options.title ?? "",
558
+ options: options.options
559
+ });
560
+ return data.data;
561
+ }
562
+ async vote(options) {
563
+ const { data } = await this.http.post("/api/v1/poll/vote", {
564
+ chatGuid: options.chatGuid,
565
+ pollMessageGuid: options.pollMessageGuid,
566
+ optionIdentifier: options.optionIdentifier
567
+ });
568
+ return data.data;
569
+ }
570
+ async unvote(options) {
571
+ const { data } = await this.http.post("/api/v1/poll/unvote", {
572
+ chatGuid: options.chatGuid,
573
+ pollMessageGuid: options.pollMessageGuid,
574
+ optionIdentifier: options.optionIdentifier
575
+ });
576
+ return data.data;
577
+ }
578
+ async addOption(options) {
579
+ if (!options.optionText || options.optionText.trim().length === 0) {
580
+ throw new Error("Option text cannot be empty");
581
+ }
582
+ const { data } = await this.http.post("/api/v1/poll/option", {
583
+ chatGuid: options.chatGuid,
584
+ pollMessageGuid: options.pollMessageGuid,
585
+ optionText: options.optionText
586
+ });
587
+ return data.data;
588
+ }
589
+ };
590
+
494
591
  // modules/scheduled.ts
495
592
  var ScheduledMessageModule = class {
496
593
  constructor(http) {
@@ -532,14 +629,6 @@ var ServerModule = class {
532
629
  });
533
630
  return response.data.data;
534
631
  }
535
- async getAlerts() {
536
- const response = await this.http.get("/api/v1/server/alert");
537
- return response.data.data;
538
- }
539
- async markAlertAsRead(ids) {
540
- const response = await this.http.post("/api/v1/server/alert/read", { ids });
541
- return response.data.data;
542
- }
543
632
  async getMediaStatistics(options) {
544
633
  const params = {};
545
634
  if (options?.only) params.only = options.only.join(",");
@@ -574,6 +663,7 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
574
663
  __publicField(this, "handles");
575
664
  __publicField(this, "facetime");
576
665
  __publicField(this, "icloud");
666
+ __publicField(this, "polls");
577
667
  __publicField(this, "scheduledMessages");
578
668
  __publicField(this, "server");
579
669
  // Message deduplication feature
@@ -587,6 +677,11 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
587
677
  // Purpose: Ensure all outgoing messages (text, attachments, stickers, etc.) from
588
678
  // a single user/SDK instance are sent in strict order, preventing race conditions.
589
679
  __publicField(this, "sendQueue", Promise.resolve());
680
+ // Flag to track if 'ready' event has been emitted
681
+ //
682
+ // Purpose: Prevent duplicate 'ready' events when both legacy mode (no API key)
683
+ // and auth-ok events occur, which would cause user callbacks to fire twice.
684
+ __publicField(this, "readyEmitted", false);
590
685
  this.config = {
591
686
  serverUrl: "http://localhost:1234",
592
687
  logLevel: "info",
@@ -629,6 +724,7 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
629
724
  this.handles = new HandleModule(this.http);
630
725
  this.facetime = new FaceTimeModule(this.http);
631
726
  this.icloud = new ICloudModule(this.http);
727
+ this.polls = new PollModule(this.http);
632
728
  this.scheduledMessages = new ScheduledMessageModule(this.http);
633
729
  this.server = new ServerModule(this.http);
634
730
  }
@@ -697,11 +793,15 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
697
793
  }
698
794
  this.socket.on("disconnect", () => {
699
795
  this.logger.info("Disconnected from iMessage server");
796
+ this.readyEmitted = false;
700
797
  this.emit("disconnect");
701
798
  });
702
799
  this.socket.on("auth-ok", () => {
703
800
  this.logger.info("Authentication successful");
704
- this.emit("ready");
801
+ if (!this.readyEmitted) {
802
+ this.readyEmitted = true;
803
+ this.emit("ready");
804
+ }
705
805
  });
706
806
  this.socket.on("auth-error", (error) => {
707
807
  this.logger.error(`Authentication failed: ${error.message} ${error.reason ? `(${error.reason})` : ""}`);
@@ -713,6 +813,13 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
713
813
  }
714
814
  this.socket.once("connect", () => {
715
815
  this.logger.info("Connected to iMessage server, waiting for authentication...");
816
+ if (!this.config.apiKey) {
817
+ this.logger.info("No API key provided, skipping authentication (legacy server mode)");
818
+ if (!this.readyEmitted) {
819
+ this.readyEmitted = true;
820
+ this.emit("ready");
821
+ }
822
+ }
716
823
  });
717
824
  if (!this.socket.connected) {
718
825
  this.socket.connect();
@@ -795,6 +902,144 @@ var IMESSAGE_ALIASES_REMOVED = "imessage-aliases-removed";
795
902
  var FT_CALL_STATUS_CHANGED = "ft-call-status-changed";
796
903
  var NEW_FINDMY_LOCATION = "new-findmy-location";
797
904
 
905
+ // lib/poll-utils.ts
906
+ var POLL_BALLOON_BUNDLE_ID = "com.apple.messages.MSMessageExtensionBalloonPlugin:0000000000:com.apple.messages.Polls";
907
+ var pollCache = /* @__PURE__ */ new Map();
908
+ function cachePoll(messageGuid, poll) {
909
+ pollCache.set(messageGuid, poll);
910
+ }
911
+ function getOptionTextById(optionId) {
912
+ for (const poll of pollCache.values()) {
913
+ const option = poll.options.find((o) => o.optionIdentifier === optionId);
914
+ if (option) return option.text;
915
+ }
916
+ return null;
917
+ }
918
+ function isPollMessage(message) {
919
+ return message.balloonBundleId === POLL_BALLOON_BUNDLE_ID;
920
+ }
921
+ function isPollVote(message) {
922
+ return isPollMessage(message) && message.associatedMessageType === "4000";
923
+ }
924
+ function extractDataUrl(payloadData) {
925
+ if (!payloadData || payloadData.length === 0) return null;
926
+ const payload = payloadData[0];
927
+ if (!payload) return null;
928
+ if (payload.URL && typeof payload.URL === "string") {
929
+ return payload.URL;
930
+ }
931
+ const objects = payload.$objects;
932
+ if (Array.isArray(objects)) {
933
+ for (const obj of objects) {
934
+ if (typeof obj === "object" && obj !== null) {
935
+ if (obj["NS.relative"] && typeof obj["NS.relative"] === "object") {
936
+ const relativeObj = objects[obj["NS.relative"].UID];
937
+ if (typeof relativeObj === "string" && relativeObj.startsWith("data:,")) {
938
+ return relativeObj;
939
+ }
940
+ }
941
+ if (typeof obj === "string" && obj.startsWith("data:,")) {
942
+ return obj;
943
+ }
944
+ }
945
+ }
946
+ }
947
+ return null;
948
+ }
949
+ function parseDataUrl(dataUrl) {
950
+ try {
951
+ const prefix = "data:,";
952
+ if (!dataUrl.startsWith(prefix)) return null;
953
+ let data = dataUrl.slice(prefix.length);
954
+ const queryIndex = data.indexOf("?");
955
+ if (queryIndex !== -1) {
956
+ data = data.slice(0, queryIndex);
957
+ }
958
+ data = decodeURIComponent(data);
959
+ try {
960
+ return JSON.parse(data);
961
+ } catch {
962
+ const decoded = Buffer.from(data, "base64").toString("utf-8");
963
+ return JSON.parse(decoded);
964
+ }
965
+ } catch {
966
+ return null;
967
+ }
968
+ }
969
+ function parsePollDefinition(message) {
970
+ if (!isPollMessage(message)) return null;
971
+ if (isPollVote(message)) return null;
972
+ const dataUrl = extractDataUrl(message.payloadData);
973
+ if (!dataUrl) return null;
974
+ const data = parseDataUrl(dataUrl);
975
+ if (!data || !data.item) return null;
976
+ const parsed = {
977
+ title: data.item.title || "",
978
+ creatorHandle: data.item.creatorHandle || "",
979
+ options: data.item.orderedPollOptions || []
980
+ };
981
+ if (message.guid) {
982
+ cachePoll(message.guid, parsed);
983
+ }
984
+ return parsed;
985
+ }
986
+ function parsePollVotes(message) {
987
+ if (!isPollMessage(message)) return null;
988
+ if (!isPollVote(message)) return null;
989
+ const dataUrl = extractDataUrl(message.payloadData);
990
+ if (!dataUrl) return null;
991
+ const data = parseDataUrl(dataUrl);
992
+ if (!data || !data.item) return null;
993
+ return {
994
+ votes: data.item.votes || []
995
+ };
996
+ }
997
+ function getPollSummary(message) {
998
+ if (!isPollMessage(message)) {
999
+ return message.text || "(no text)";
1000
+ }
1001
+ if (isPollVote(message)) {
1002
+ const voteData = parsePollVotes(message);
1003
+ if (voteData && voteData.votes.length > 0) {
1004
+ const votes = voteData.votes.map((v) => {
1005
+ const optionText = getOptionTextById(v.voteOptionIdentifier);
1006
+ const optionDisplay = optionText ? `"${optionText}"` : `option ${v.voteOptionIdentifier}`;
1007
+ return `${v.participantHandle || "Someone"} voted ${optionDisplay}`;
1008
+ }).join(", ");
1009
+ return `[Poll Vote] ${votes}`;
1010
+ }
1011
+ return "[Poll Vote]";
1012
+ }
1013
+ const pollData = parsePollDefinition(message);
1014
+ if (pollData) {
1015
+ const title = pollData.title ? `"${pollData.title}"` : "(untitled poll)";
1016
+ const optionsList = pollData.options.map((opt, i) => ` ${i + 1}. ${opt.text}`).join("\n");
1017
+ return `[Poll] ${title}
1018
+ ${optionsList}`;
1019
+ }
1020
+ return "[Poll] (unable to parse)";
1021
+ }
1022
+ function getPollOneLiner(message) {
1023
+ if (!isPollMessage(message)) {
1024
+ return message.text || "(no text)";
1025
+ }
1026
+ if (isPollVote(message)) {
1027
+ const voteData = parsePollVotes(message);
1028
+ if (voteData && voteData.votes.length > 0) {
1029
+ return `[Poll Vote] ${voteData.votes.length} vote(s)`;
1030
+ }
1031
+ return "[Poll Vote]";
1032
+ }
1033
+ const pollData = parsePollDefinition(message);
1034
+ if (pollData) {
1035
+ const title = pollData.title || "Poll";
1036
+ const optionsPreview = pollData.options.slice(0, 2).map((o) => o.text).join(", ");
1037
+ const moreOptions = pollData.options.length > 2 ? `, +${pollData.options.length - 2} more` : "";
1038
+ return `[${title}] ${optionsPreview}${moreOptions}`;
1039
+ }
1040
+ return "[Poll]";
1041
+ }
1042
+
798
1043
  exports.AdvancedIMessageKit = AdvancedIMessageKit;
799
1044
  exports.CHAT_READ_STATUS_CHANGED = CHAT_READ_STATUS_CHANGED;
800
1045
  exports.FT_CALL_STATUS_CHANGED = FT_CALL_STATUS_CHANGED;
@@ -829,6 +1074,12 @@ exports.THEME_BACKUP_DELETED = THEME_BACKUP_DELETED;
829
1074
  exports.THEME_BACKUP_UPDATED = THEME_BACKUP_UPDATED;
830
1075
  exports.TYPING_INDICATOR = TYPING_INDICATOR;
831
1076
  exports.getLogger = getLogger;
1077
+ exports.getPollOneLiner = getPollOneLiner;
1078
+ exports.getPollSummary = getPollSummary;
1079
+ exports.isPollMessage = isPollMessage;
1080
+ exports.isPollVote = isPollVote;
1081
+ exports.parsePollDefinition = parsePollDefinition;
1082
+ exports.parsePollVotes = parsePollVotes;
832
1083
  exports.setGlobalLogLevel = setGlobalLogLevel;
833
1084
  //# sourceMappingURL=index.cjs.map
834
1085
  //# sourceMappingURL=index.cjs.map