@photon-ai/advanced-imessage-kit 1.2.1 → 1.3.2

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
@@ -191,6 +191,9 @@ var AttachmentModule = class {
191
191
  form.append("method", "private-api");
192
192
  }
193
193
  }
194
+ if (options.selectedMessageGuid) {
195
+ form.append("selectedMessageGuid", options.selectedMessageGuid);
196
+ }
194
197
  const response = await this.http.post("/api/v1/message/attachment", form, {
195
198
  headers: form.getHeaders()
196
199
  });
@@ -202,15 +205,23 @@ var AttachmentModule = class {
202
205
  const fileName = options.fileName || path__namespace.default.basename(options.filePath);
203
206
  const form = new FormData__default.default();
204
207
  form.append("attachment", await promises.readFile(options.filePath), fileName);
205
- const { data } = await this.http.post("/api/v1/attachment/upload", form, {
208
+ form.append("name", fileName);
209
+ form.append("chatGuid", options.chatGuid);
210
+ form.append("isSticker", "true");
211
+ form.append("method", "private-api");
212
+ if (options.selectedMessageGuid) {
213
+ form.append("selectedMessageGuid", options.selectedMessageGuid);
214
+ form.append("partIndex", "0");
215
+ }
216
+ form.append("stickerX", String(options.stickerX ?? 0.5));
217
+ form.append("stickerY", String(options.stickerY ?? 0.5));
218
+ form.append("stickerScale", String(options.stickerScale ?? 0.75));
219
+ form.append("stickerRotation", String(options.stickerRotation ?? 0));
220
+ form.append("stickerWidth", String(options.stickerWidth ?? 300));
221
+ const { data } = await this.http.post("/api/v1/message/attachment", form, {
206
222
  headers: form.getHeaders()
207
223
  });
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;
224
+ return data.data;
214
225
  });
215
226
  }
216
227
  };
@@ -227,35 +238,32 @@ var ChatModule = class {
227
238
  return response.data.data;
228
239
  }
229
240
  async getChat(guid, options) {
230
- const response = await this.http.get(`/api/v1/chat/${guid}`, {
241
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(guid)}`, {
231
242
  params: options?.with ? { with: options.with.join(",") } : {}
232
243
  });
233
244
  return response.data.data;
234
245
  }
235
246
  async updateChat(guid, options) {
236
- const response = await this.http.put(`/api/v1/chat/${guid}`, options);
247
+ const response = await this.http.put(`/api/v1/chat/${encodeURIComponent(guid)}`, options);
237
248
  return response.data.data;
238
249
  }
239
250
  async deleteChat(guid) {
240
- await this.http.delete(`/api/v1/chat/${guid}`);
251
+ await this.http.delete(`/api/v1/chat/${encodeURIComponent(guid)}`);
241
252
  }
242
253
  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`);
254
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(guid)}/read`);
247
255
  }
248
256
  async leaveChat(guid) {
249
- await this.http.post(`/api/v1/chat/${guid}/leave`);
257
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(guid)}/leave`);
250
258
  }
251
259
  async addParticipant(chatGuid, address) {
252
- const response = await this.http.post(`/api/v1/chat/${chatGuid}/participant`, {
260
+ const response = await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/participant`, {
253
261
  address
254
262
  });
255
263
  return response.data.data;
256
264
  }
257
265
  async removeParticipant(chatGuid, address) {
258
- const response = await this.http.delete(`/api/v1/chat/${chatGuid}/participant`, {
266
+ const response = await this.http.delete(`/api/v1/chat/${encodeURIComponent(chatGuid)}/participant`, {
259
267
  data: { address }
260
268
  });
261
269
  return response.data.data;
@@ -268,7 +276,7 @@ var ChatModule = class {
268
276
  if (options?.before !== void 0) params.before = options.before;
269
277
  if (options?.after !== void 0) params.after = options.after;
270
278
  if (options?.with) params.with = options.with.join(",");
271
- const response = await this.http.get(`/api/v1/chat/${chatGuid}/message`, {
279
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(chatGuid)}/message`, {
272
280
  params
273
281
  });
274
282
  return response.data.data;
@@ -278,15 +286,15 @@ var ChatModule = class {
278
286
  const fileName = path__namespace.default.basename(filePath);
279
287
  const form = new FormData__default.default();
280
288
  form.append("icon", fileBuffer, fileName);
281
- await this.http.post(`/api/v1/chat/${chatGuid}/icon`, form, {
289
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/icon`, form, {
282
290
  headers: form.getHeaders()
283
291
  });
284
292
  }
285
293
  async removeGroupIcon(chatGuid) {
286
- await this.http.delete(`/api/v1/chat/${chatGuid}/icon`);
294
+ await this.http.delete(`/api/v1/chat/${encodeURIComponent(chatGuid)}/icon`);
287
295
  }
288
296
  async getGroupIcon(chatGuid) {
289
- const response = await this.http.get(`/api/v1/chat/${chatGuid}/icon`, {
297
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(chatGuid)}/icon`, {
290
298
  responseType: "arraybuffer"
291
299
  });
292
300
  return Buffer.from(response.data);
@@ -298,10 +306,10 @@ var ChatModule = class {
298
306
  return response.data.data;
299
307
  }
300
308
  async startTyping(chatGuid) {
301
- await this.http.post(`/api/v1/chat/${chatGuid}/typing`);
309
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/typing`);
302
310
  }
303
311
  async stopTyping(chatGuid) {
304
- await this.http.delete(`/api/v1/chat/${chatGuid}/typing`);
312
+ await this.http.delete(`/api/v1/chat/${encodeURIComponent(chatGuid)}/typing`);
305
313
  }
306
314
  };
307
315
 
@@ -321,10 +329,10 @@ var ContactModule = class {
321
329
  return response.data.data;
322
330
  }
323
331
  async shareContactCard(chatGuid) {
324
- await this.http.post(`/api/v1/chat/${chatGuid}/share/contact`);
332
+ await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/share/contact`);
325
333
  }
326
334
  async shouldShareContact(chatGuid) {
327
- const response = await this.http.get(`/api/v1/chat/${chatGuid}/share/contact/status`);
335
+ const response = await this.http.get(`/api/v1/chat/${encodeURIComponent(chatGuid)}/share/contact/status`);
328
336
  return response.data.data;
329
337
  }
330
338
  };
@@ -538,6 +546,51 @@ var MessageModule = class {
538
546
  }
539
547
  };
540
548
 
549
+ // modules/poll.ts
550
+ var PollModule = class {
551
+ constructor(http) {
552
+ this.http = http;
553
+ }
554
+ async create(options) {
555
+ if (options.options.length < 2) {
556
+ throw new Error("Poll must have at least 2 options");
557
+ }
558
+ const { data } = await this.http.post("/api/v1/poll/create", {
559
+ chatGuid: options.chatGuid,
560
+ title: options.title ?? "",
561
+ options: options.options
562
+ });
563
+ return data.data;
564
+ }
565
+ async vote(options) {
566
+ const { data } = await this.http.post("/api/v1/poll/vote", {
567
+ chatGuid: options.chatGuid,
568
+ pollMessageGuid: options.pollMessageGuid,
569
+ optionIdentifier: options.optionIdentifier
570
+ });
571
+ return data.data;
572
+ }
573
+ async unvote(options) {
574
+ const { data } = await this.http.post("/api/v1/poll/unvote", {
575
+ chatGuid: options.chatGuid,
576
+ pollMessageGuid: options.pollMessageGuid,
577
+ optionIdentifier: options.optionIdentifier
578
+ });
579
+ return data.data;
580
+ }
581
+ async addOption(options) {
582
+ if (!options.optionText || options.optionText.trim().length === 0) {
583
+ throw new Error("Option text cannot be empty");
584
+ }
585
+ const { data } = await this.http.post("/api/v1/poll/option", {
586
+ chatGuid: options.chatGuid,
587
+ pollMessageGuid: options.pollMessageGuid,
588
+ optionText: options.optionText
589
+ });
590
+ return data.data;
591
+ }
592
+ };
593
+
541
594
  // modules/scheduled.ts
542
595
  var ScheduledMessageModule = class {
543
596
  constructor(http) {
@@ -579,14 +632,6 @@ var ServerModule = class {
579
632
  });
580
633
  return response.data.data;
581
634
  }
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
635
  async getMediaStatistics(options) {
591
636
  const params = {};
592
637
  if (options?.only) params.only = options.only.join(",");
@@ -621,6 +666,7 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
621
666
  __publicField(this, "handles");
622
667
  __publicField(this, "facetime");
623
668
  __publicField(this, "icloud");
669
+ __publicField(this, "polls");
624
670
  __publicField(this, "scheduledMessages");
625
671
  __publicField(this, "server");
626
672
  // Message deduplication feature
@@ -634,6 +680,11 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
634
680
  // Purpose: Ensure all outgoing messages (text, attachments, stickers, etc.) from
635
681
  // a single user/SDK instance are sent in strict order, preventing race conditions.
636
682
  __publicField(this, "sendQueue", Promise.resolve());
683
+ // Flag to track if 'ready' event has been emitted
684
+ //
685
+ // Purpose: Prevent duplicate 'ready' events when both legacy mode (no API key)
686
+ // and auth-ok events occur, which would cause user callbacks to fire twice.
687
+ __publicField(this, "readyEmitted", false);
637
688
  this.config = {
638
689
  serverUrl: "http://localhost:1234",
639
690
  logLevel: "info",
@@ -676,6 +727,7 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
676
727
  this.handles = new HandleModule(this.http);
677
728
  this.facetime = new FaceTimeModule(this.http);
678
729
  this.icloud = new ICloudModule(this.http);
730
+ this.polls = new PollModule(this.http);
679
731
  this.scheduledMessages = new ScheduledMessageModule(this.http);
680
732
  this.server = new ServerModule(this.http);
681
733
  }
@@ -744,11 +796,15 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
744
796
  }
745
797
  this.socket.on("disconnect", () => {
746
798
  this.logger.info("Disconnected from iMessage server");
799
+ this.readyEmitted = false;
747
800
  this.emit("disconnect");
748
801
  });
749
802
  this.socket.on("auth-ok", () => {
750
803
  this.logger.info("Authentication successful");
751
- this.emit("ready");
804
+ if (!this.readyEmitted) {
805
+ this.readyEmitted = true;
806
+ this.emit("ready");
807
+ }
752
808
  });
753
809
  this.socket.on("auth-error", (error) => {
754
810
  this.logger.error(`Authentication failed: ${error.message} ${error.reason ? `(${error.reason})` : ""}`);
@@ -762,7 +818,10 @@ var _AdvancedIMessageKit = class _AdvancedIMessageKit extends EventEmitter.Event
762
818
  this.logger.info("Connected to iMessage server, waiting for authentication...");
763
819
  if (!this.config.apiKey) {
764
820
  this.logger.info("No API key provided, skipping authentication (legacy server mode)");
765
- this.emit("ready");
821
+ if (!this.readyEmitted) {
822
+ this.readyEmitted = true;
823
+ this.emit("ready");
824
+ }
766
825
  }
767
826
  });
768
827
  if (!this.socket.connected) {
@@ -846,6 +905,144 @@ var IMESSAGE_ALIASES_REMOVED = "imessage-aliases-removed";
846
905
  var FT_CALL_STATUS_CHANGED = "ft-call-status-changed";
847
906
  var NEW_FINDMY_LOCATION = "new-findmy-location";
848
907
 
908
+ // lib/poll-utils.ts
909
+ var POLL_BALLOON_BUNDLE_ID = "com.apple.messages.MSMessageExtensionBalloonPlugin:0000000000:com.apple.messages.Polls";
910
+ var pollCache = /* @__PURE__ */ new Map();
911
+ function cachePoll(messageGuid, poll) {
912
+ pollCache.set(messageGuid, poll);
913
+ }
914
+ function getOptionTextById(optionId) {
915
+ for (const poll of pollCache.values()) {
916
+ const option = poll.options.find((o) => o.optionIdentifier === optionId);
917
+ if (option) return option.text;
918
+ }
919
+ return null;
920
+ }
921
+ function isPollMessage(message) {
922
+ return message.balloonBundleId === POLL_BALLOON_BUNDLE_ID;
923
+ }
924
+ function isPollVote(message) {
925
+ return isPollMessage(message) && message.associatedMessageType === "4000";
926
+ }
927
+ function extractDataUrl(payloadData) {
928
+ if (!payloadData || payloadData.length === 0) return null;
929
+ const payload = payloadData[0];
930
+ if (!payload) return null;
931
+ if (payload.URL && typeof payload.URL === "string") {
932
+ return payload.URL;
933
+ }
934
+ const objects = payload.$objects;
935
+ if (Array.isArray(objects)) {
936
+ for (const obj of objects) {
937
+ if (typeof obj === "object" && obj !== null) {
938
+ if (obj["NS.relative"] && typeof obj["NS.relative"] === "object") {
939
+ const relativeObj = objects[obj["NS.relative"].UID];
940
+ if (typeof relativeObj === "string" && relativeObj.startsWith("data:,")) {
941
+ return relativeObj;
942
+ }
943
+ }
944
+ if (typeof obj === "string" && obj.startsWith("data:,")) {
945
+ return obj;
946
+ }
947
+ }
948
+ }
949
+ }
950
+ return null;
951
+ }
952
+ function parseDataUrl(dataUrl) {
953
+ try {
954
+ const prefix = "data:,";
955
+ if (!dataUrl.startsWith(prefix)) return null;
956
+ let data = dataUrl.slice(prefix.length);
957
+ const queryIndex = data.indexOf("?");
958
+ if (queryIndex !== -1) {
959
+ data = data.slice(0, queryIndex);
960
+ }
961
+ data = decodeURIComponent(data);
962
+ try {
963
+ return JSON.parse(data);
964
+ } catch {
965
+ const decoded = Buffer.from(data, "base64").toString("utf-8");
966
+ return JSON.parse(decoded);
967
+ }
968
+ } catch {
969
+ return null;
970
+ }
971
+ }
972
+ function parsePollDefinition(message) {
973
+ if (!isPollMessage(message)) return null;
974
+ if (isPollVote(message)) return null;
975
+ const dataUrl = extractDataUrl(message.payloadData);
976
+ if (!dataUrl) return null;
977
+ const data = parseDataUrl(dataUrl);
978
+ if (!data || !data.item) return null;
979
+ const parsed = {
980
+ title: data.item.title || "",
981
+ creatorHandle: data.item.creatorHandle || "",
982
+ options: data.item.orderedPollOptions || []
983
+ };
984
+ if (message.guid) {
985
+ cachePoll(message.guid, parsed);
986
+ }
987
+ return parsed;
988
+ }
989
+ function parsePollVotes(message) {
990
+ if (!isPollMessage(message)) return null;
991
+ if (!isPollVote(message)) return null;
992
+ const dataUrl = extractDataUrl(message.payloadData);
993
+ if (!dataUrl) return null;
994
+ const data = parseDataUrl(dataUrl);
995
+ if (!data || !data.item) return null;
996
+ return {
997
+ votes: data.item.votes || []
998
+ };
999
+ }
1000
+ function getPollSummary(message) {
1001
+ if (!isPollMessage(message)) {
1002
+ return message.text || "(no text)";
1003
+ }
1004
+ if (isPollVote(message)) {
1005
+ const voteData = parsePollVotes(message);
1006
+ if (voteData && voteData.votes.length > 0) {
1007
+ const votes = voteData.votes.map((v) => {
1008
+ const optionText = getOptionTextById(v.voteOptionIdentifier);
1009
+ const optionDisplay = optionText ? `"${optionText}"` : `option ${v.voteOptionIdentifier}`;
1010
+ return `${v.participantHandle || "Someone"} voted ${optionDisplay}`;
1011
+ }).join(", ");
1012
+ return `[Poll Vote] ${votes}`;
1013
+ }
1014
+ return "[Poll Vote]";
1015
+ }
1016
+ const pollData = parsePollDefinition(message);
1017
+ if (pollData) {
1018
+ const title = pollData.title ? `"${pollData.title}"` : "(untitled poll)";
1019
+ const optionsList = pollData.options.map((opt, i) => ` ${i + 1}. ${opt.text}`).join("\n");
1020
+ return `[Poll] ${title}
1021
+ ${optionsList}`;
1022
+ }
1023
+ return "[Poll] (unable to parse)";
1024
+ }
1025
+ function getPollOneLiner(message) {
1026
+ if (!isPollMessage(message)) {
1027
+ return message.text || "(no text)";
1028
+ }
1029
+ if (isPollVote(message)) {
1030
+ const voteData = parsePollVotes(message);
1031
+ if (voteData && voteData.votes.length > 0) {
1032
+ return `[Poll Vote] ${voteData.votes.length} vote(s)`;
1033
+ }
1034
+ return "[Poll Vote]";
1035
+ }
1036
+ const pollData = parsePollDefinition(message);
1037
+ if (pollData) {
1038
+ const title = pollData.title || "Poll";
1039
+ const optionsPreview = pollData.options.slice(0, 2).map((o) => o.text).join(", ");
1040
+ const moreOptions = pollData.options.length > 2 ? `, +${pollData.options.length - 2} more` : "";
1041
+ return `[${title}] ${optionsPreview}${moreOptions}`;
1042
+ }
1043
+ return "[Poll]";
1044
+ }
1045
+
849
1046
  exports.AdvancedIMessageKit = AdvancedIMessageKit;
850
1047
  exports.CHAT_READ_STATUS_CHANGED = CHAT_READ_STATUS_CHANGED;
851
1048
  exports.FT_CALL_STATUS_CHANGED = FT_CALL_STATUS_CHANGED;
@@ -880,6 +1077,12 @@ exports.THEME_BACKUP_DELETED = THEME_BACKUP_DELETED;
880
1077
  exports.THEME_BACKUP_UPDATED = THEME_BACKUP_UPDATED;
881
1078
  exports.TYPING_INDICATOR = TYPING_INDICATOR;
882
1079
  exports.getLogger = getLogger;
1080
+ exports.getPollOneLiner = getPollOneLiner;
1081
+ exports.getPollSummary = getPollSummary;
1082
+ exports.isPollMessage = isPollMessage;
1083
+ exports.isPollVote = isPollVote;
1084
+ exports.parsePollDefinition = parsePollDefinition;
1085
+ exports.parsePollVotes = parsePollVotes;
883
1086
  exports.setGlobalLogLevel = setGlobalLogLevel;
884
1087
  //# sourceMappingURL=index.cjs.map
885
1088
  //# sourceMappingURL=index.cjs.map