@photon-ai/advanced-imessage-kit 1.2.1 → 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
  };
@@ -538,6 +543,51 @@ var MessageModule = class {
538
543
  }
539
544
  };
540
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
+
541
591
  // modules/scheduled.ts
542
592
  var ScheduledMessageModule = class {
543
593
  constructor(http) {
@@ -579,14 +629,6 @@ var ServerModule = class {
579
629
  });
580
630
  return response.data.data;
581
631
  }
582
- async getAlerts() {
583
- const response = await this.http.get("/api/v1/server/alert");
584
- return response.data.data;
585
- }
586
- async markAlertAsRead(ids) {
587
- const response = await this.http.post("/api/v1/server/alert/read", { ids });
588
- return response.data.data;
589
- }
590
632
  async getMediaStatistics(options) {
591
633
  const params = {};
592
634
  if (options?.only) params.only = options.only.join(",");
@@ -621,6 +663,7 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
621
663
  __publicField(this, "handles");
622
664
  __publicField(this, "facetime");
623
665
  __publicField(this, "icloud");
666
+ __publicField(this, "polls");
624
667
  __publicField(this, "scheduledMessages");
625
668
  __publicField(this, "server");
626
669
  // Message deduplication feature
@@ -634,6 +677,11 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
634
677
  // Purpose: Ensure all outgoing messages (text, attachments, stickers, etc.) from
635
678
  // a single user/SDK instance are sent in strict order, preventing race conditions.
636
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);
637
685
  this.config = {
638
686
  serverUrl: "http://localhost:1234",
639
687
  logLevel: "info",
@@ -676,6 +724,7 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
676
724
  this.handles = new HandleModule(this.http);
677
725
  this.facetime = new FaceTimeModule(this.http);
678
726
  this.icloud = new ICloudModule(this.http);
727
+ this.polls = new PollModule(this.http);
679
728
  this.scheduledMessages = new ScheduledMessageModule(this.http);
680
729
  this.server = new ServerModule(this.http);
681
730
  }
@@ -744,11 +793,15 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
744
793
  }
745
794
  this.socket.on("disconnect", () => {
746
795
  this.logger.info("Disconnected from iMessage server");
796
+ this.readyEmitted = false;
747
797
  this.emit("disconnect");
748
798
  });
749
799
  this.socket.on("auth-ok", () => {
750
800
  this.logger.info("Authentication successful");
751
- this.emit("ready");
801
+ if (!this.readyEmitted) {
802
+ this.readyEmitted = true;
803
+ this.emit("ready");
804
+ }
752
805
  });
753
806
  this.socket.on("auth-error", (error) => {
754
807
  this.logger.error(`Authentication failed: ${error.message} ${error.reason ? `(${error.reason})` : ""}`);
@@ -762,7 +815,10 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
762
815
  this.logger.info("Connected to iMessage server, waiting for authentication...");
763
816
  if (!this.config.apiKey) {
764
817
  this.logger.info("No API key provided, skipping authentication (legacy server mode)");
765
- this.emit("ready");
818
+ if (!this.readyEmitted) {
819
+ this.readyEmitted = true;
820
+ this.emit("ready");
821
+ }
766
822
  }
767
823
  });
768
824
  if (!this.socket.connected) {
@@ -846,6 +902,144 @@ var IMESSAGE_ALIASES_REMOVED = "imessage-aliases-removed";
846
902
  var FT_CALL_STATUS_CHANGED = "ft-call-status-changed";
847
903
  var NEW_FINDMY_LOCATION = "new-findmy-location";
848
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
+
849
1043
  exports.AdvancedIMessageKit = AdvancedIMessageKit;
850
1044
  exports.CHAT_READ_STATUS_CHANGED = CHAT_READ_STATUS_CHANGED;
851
1045
  exports.FT_CALL_STATUS_CHANGED = FT_CALL_STATUS_CHANGED;
@@ -880,6 +1074,12 @@ exports.THEME_BACKUP_DELETED = THEME_BACKUP_DELETED;
880
1074
  exports.THEME_BACKUP_UPDATED = THEME_BACKUP_UPDATED;
881
1075
  exports.TYPING_INDICATOR = TYPING_INDICATOR;
882
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;
883
1083
  exports.setGlobalLogLevel = setGlobalLogLevel;
884
1084
  //# sourceMappingURL=index.cjs.map
885
1085
  //# sourceMappingURL=index.cjs.map