@spectrum-ts/imessage 8.0.0 → 8.1.1

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.
Files changed (2) hide show
  1. package/dist/index.js +104 -56
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -204,8 +204,9 @@ const spaceSchema = z.object({
204
204
  const spaceParamsSchema = z.object({ phone: z.string().optional() });
205
205
  /**
206
206
  * iMessage-specific per-message metadata surfaced on `IMessageMessage`.
207
- * - `partIndex`: attachment index within a multi-part message (0 for bare
208
- * or single-attachment messages; 0..N-1 for a group's sub-items).
207
+ * - `partIndex`: ordered part index within a multi-part message. Text and
208
+ * attachment parts both consume an index (0 for bare or single-part
209
+ * messages; 0..N-1 for a group's sub-items).
209
210
  * - `parentId`: guid of the parent message for a group sub-item. Undefined
210
211
  * when the message itself is the parent.
211
212
  */
@@ -445,6 +446,43 @@ const getPollCache = (owner) => {
445
446
  }
446
447
  return cache;
447
448
  };
449
+ const addTextPart = (parts, text) => {
450
+ const trimmed = text?.trim();
451
+ if (trimmed) parts.push({
452
+ type: "text",
453
+ text: trimmed
454
+ });
455
+ };
456
+ const hasUsableTextPart = (text) => text?.split("").some((segment) => segment.trim()) ?? false;
457
+ const addAttachmentParts = (parts, attachments) => {
458
+ for (const attachment of attachments) if (attachment) parts.push({
459
+ type: "attachment",
460
+ attachment
461
+ });
462
+ };
463
+ const toOrderedParts = (text, attachments) => {
464
+ const parts = [];
465
+ if (!text) {
466
+ addAttachmentParts(parts, attachments);
467
+ return parts;
468
+ }
469
+ if (!text.includes("")) {
470
+ addAttachmentParts(parts, attachments);
471
+ addTextPart(parts, text);
472
+ return parts;
473
+ }
474
+ const textSegments = text.split("");
475
+ for (let i = 0; i < attachments.length; i++) {
476
+ addTextPart(parts, textSegments[i]);
477
+ const attachment = attachments[i];
478
+ if (attachment) parts.push({
479
+ type: "attachment",
480
+ attachment
481
+ });
482
+ }
483
+ addTextPart(parts, textSegments.slice(attachments.length).join(""));
484
+ return parts;
485
+ };
448
486
  //#endregion
449
487
  //#region src/shared/vcard.ts
450
488
  const VCARD_MIME_TYPES = new Set([
@@ -487,11 +525,10 @@ const toVCardContent$1 = async (att) => {
487
525
  const localAttachmentContent = async (att) => isVCardAttachment(att.mimeType, att.fileName) ? await toVCardContent$1(att) : toAttachmentContent$1(att);
488
526
  //#endregion
489
527
  //#region src/local/inbound.ts
490
- const ATTACHMENT_PLACEHOLDER = "";
491
528
  const ATTACHMENT_JOIN_RETRY_DELAY_MS = 250;
492
529
  const ATTACHMENT_JOIN_RETRY_LIMIT = 8;
493
530
  const ATTACHMENT_JOIN_FETCH_LIMIT = 10;
494
- const hasAttachmentPlaceholder = (message) => message.text?.includes(ATTACHMENT_PLACEHOLDER) ?? false;
531
+ const hasAttachmentPlaceholder = (message) => message.text?.includes("") ?? false;
495
532
  const isPendingAttachmentJoin = (message) => message.attachments.length === 0 && (message.hasAttachments || hasAttachmentPlaceholder(message));
496
533
  const refetchUntilAttachmentsSettle = async (client, message) => {
497
534
  if (!message.chatId) return message;
@@ -529,11 +566,31 @@ const toMessages = async (message) => {
529
566
  },
530
567
  timestamp: message.createdAt
531
568
  };
532
- if (message.attachments.length > 0) return Promise.all(message.attachments.map(async (att) => ({
533
- ...base,
534
- id: `${message.id}:${att.id}`,
535
- content: await localAttachmentContent(att)
536
- })));
569
+ if (message.attachments.length > 0) {
570
+ if (!hasUsableTextPart(message.text)) return Promise.all(message.attachments.map(async (att) => ({
571
+ ...base,
572
+ id: `${message.id}:${att.id}`,
573
+ content: await localAttachmentContent(att)
574
+ })));
575
+ const parts = toOrderedParts(message.text, message.attachments);
576
+ const messages = [];
577
+ for (let i = 0; i < parts.length; i++) {
578
+ const part = parts[i];
579
+ if (!part) continue;
580
+ messages.push(part.type === "text" ? {
581
+ ...base,
582
+ id: `${message.id}:text:${i}`,
583
+ content: asText(part.text),
584
+ partIndex: i
585
+ } : {
586
+ ...base,
587
+ id: `${message.id}:${part.attachment.id}`,
588
+ content: await localAttachmentContent(part.attachment),
589
+ partIndex: i
590
+ });
591
+ }
592
+ return messages;
593
+ }
537
594
  return [{
538
595
  ...base,
539
596
  id: message.id,
@@ -868,35 +925,54 @@ const buildAttachmentMessage = async (client, base, info, id, partIndex, parentI
868
925
  if (parentId !== void 0) msg.parentId = parentId;
869
926
  return msg;
870
927
  };
871
- const rebuildFromAppleMessage = async (client, message, phone, chatGuidHint) => {
872
- const messageGuidStr = message.guid;
873
- const base = buildMessageBase(message, chatGuidHint, message.dateCreated ?? /* @__PURE__ */ new Date(), phone);
928
+ const buildTextMessage = (base, text, id, partIndex, parentId) => {
929
+ const msg = {
930
+ ...base,
931
+ id,
932
+ content: asText(text),
933
+ partIndex
934
+ };
935
+ if (parentId !== void 0) msg.parentId = parentId;
936
+ return msg;
937
+ };
938
+ const buildOrderedPartMessage = async (client, base, part, id, partIndex, parentId) => part.type === "text" ? buildTextMessage(base, part.text, id, partIndex, parentId) : await buildAttachmentMessage(client, base, part.attachment, id, partIndex, parentId);
939
+ const buildContentMessage = async (client, base, message, messageGuidStr) => {
874
940
  const attachments = messageAttachments(message);
875
- if (attachments.length === 1) {
876
- const info = attachments[0];
877
- if (!info) throw new Error("Unreachable: attachments.length === 1 but no element");
878
- return buildAttachmentMessage(client, base, info, messageGuidStr, 0);
879
- }
880
- if (attachments.length > 1) {
881
- const items = [];
882
- for (let i = 0; i < attachments.length; i++) {
883
- const info = attachments[i];
884
- if (!info) continue;
885
- items.push(await buildAttachmentMessage(client, base, info, formatChildId(i, messageGuidStr), i, messageGuidStr));
886
- }
941
+ if (attachments.length === 0) {
942
+ const text = message.content.text;
887
943
  return {
888
944
  ...base,
889
945
  id: messageGuidStr,
890
- content: asProviderGroup(items)
946
+ content: text ? asText(text) : asCustom(message)
891
947
  };
892
948
  }
893
- const text = message.content.text;
949
+ const parts = toOrderedParts(message.content.text, attachments);
950
+ if (parts.length === 0) return {
951
+ ...base,
952
+ id: messageGuidStr,
953
+ content: asCustom(message)
954
+ };
955
+ if (parts.length === 1) {
956
+ const part = parts[0];
957
+ if (!part) throw new Error("Unreachable: parts.length === 1 but no element");
958
+ return buildOrderedPartMessage(client, base, part, messageGuidStr, 0);
959
+ }
960
+ const items = [];
961
+ for (let i = 0; i < parts.length; i++) {
962
+ const part = parts[i];
963
+ if (!part) continue;
964
+ items.push(await buildOrderedPartMessage(client, base, part, formatChildId(i, messageGuidStr), i, messageGuidStr));
965
+ }
894
966
  return {
895
967
  ...base,
896
968
  id: messageGuidStr,
897
- content: text ? asText(text) : asCustom(message)
969
+ content: asProviderGroup(items)
898
970
  };
899
971
  };
972
+ const rebuildFromAppleMessage = async (client, message, phone, chatGuidHint) => {
973
+ const messageGuidStr = message.guid;
974
+ return buildContentMessage(client, buildMessageBase(message, chatGuidHint, message.dateCreated ?? /* @__PURE__ */ new Date(), phone), message, messageGuidStr);
975
+ };
900
976
  const cacheMessage = (cache, message) => {
901
977
  cache.set(message.id, message);
902
978
  if (message.content.type === "group") {
@@ -906,35 +982,7 @@ const cacheMessage = (cache, message) => {
906
982
  const toInboundMessages = async (client, cache, event, phone) => {
907
983
  const base = buildMessageBase(event.message, event.chatGuid, event.occurredAt, phone);
908
984
  const messageGuidStr = event.message.guid;
909
- const attachments = messageAttachments(event.message);
910
- if (attachments.length === 1) {
911
- const info = attachments[0];
912
- if (!info) throw new Error("Unreachable: attachments.length === 1 but no element");
913
- const msg = await buildAttachmentMessage(client, base, info, messageGuidStr, 0);
914
- cacheMessage(cache, msg);
915
- return [msg];
916
- }
917
- if (attachments.length > 1) {
918
- const items = [];
919
- for (let i = 0; i < attachments.length; i++) {
920
- const info = attachments[i];
921
- if (!info) continue;
922
- items.push(await buildAttachmentMessage(client, base, info, formatChildId(i, messageGuidStr), i, messageGuidStr));
923
- }
924
- const parent = {
925
- ...base,
926
- id: messageGuidStr,
927
- content: asProviderGroup(items)
928
- };
929
- cacheMessage(cache, parent);
930
- return [parent];
931
- }
932
- const text = event.message.content.text;
933
- const msg = {
934
- ...base,
935
- id: messageGuidStr,
936
- content: text ? asText(text) : asCustom(event.message)
937
- };
985
+ const msg = await buildContentMessage(client, base, event.message, messageGuidStr);
938
986
  cacheMessage(cache, msg);
939
987
  return [msg];
940
988
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectrum-ts/imessage",
3
- "version": "8.0.0",
3
+ "version": "8.1.1",
4
4
  "description": "iMessage provider for spectrum-ts — local and remote (advanced) modes.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@photon-ai/advanced-imessage": "^0.12.0",
35
35
  "@photon-ai/imessage-kit": "^3.0.0",
36
- "@photon-ai/otel": "^1.0.0",
36
+ "@photon-ai/otel": "^3.1.0",
37
37
  "lru-cache": "^11.0.0",
38
38
  "marked": "^18.0.5",
39
39
  "zod": "^4.2.1"