@spectrum-ts/imessage 9.3.1 → 11.0.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/README.md +10 -2
- package/dist/index.d.ts +27 -28
- package/dist/index.js +92 -337
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @spectrum-ts/imessage
|
|
2
2
|
|
|
3
|
-
iMessage provider for [spectrum-ts](https://github.com/photon-hq/spectrum-ts),
|
|
3
|
+
iMessage provider for [spectrum-ts](https://github.com/photon-hq/spectrum-ts), including tapbacks, special effects, polls, and mini-apps.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ import { Spectrum } from "spectrum-ts";
|
|
|
15
15
|
import { imessage } from "@spectrum-ts/imessage";
|
|
16
16
|
|
|
17
17
|
const spectrum = Spectrum({
|
|
18
|
-
|
|
18
|
+
platforms: [imessage.config()],
|
|
19
19
|
});
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -32,3 +32,11 @@ await space.shareContactCard();
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
See the [spectrum-ts documentation](https://photon.codes/spectrum) for the full guide.
|
|
35
|
+
|
|
36
|
+
For direct access to the local macOS Messages database, install and import
|
|
37
|
+
`@spectrum-ts/imessage-local` separately. The local provider is intentionally
|
|
38
|
+
not included in the batteries-included `spectrum-ts` package.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
bun add @spectrum-ts/imessage-local
|
|
42
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { AdvancedIMessage, ChatServiceType
|
|
2
|
-
import { IMessageSDK } from "@photon-ai/imessage-kit";
|
|
1
|
+
import { AdvancedIMessage, ChatServiceType } from "@photon-ai/advanced-imessage";
|
|
3
2
|
import { Attachment, ContentBuilder, ContentInput, SchemaMessage, Space, read } from "@spectrum-ts/core";
|
|
4
3
|
import { PhotoInput } from "@spectrum-ts/core/authoring";
|
|
5
4
|
import z from "zod";
|
|
@@ -168,7 +167,22 @@ type CustomizedMiniAppInput = Omit<CustomizedMiniApp, "type" | "__platform">;
|
|
|
168
167
|
declare function customizedMiniApp(input: CustomizedMiniAppInput): ContentBuilder;
|
|
169
168
|
//#endregion
|
|
170
169
|
//#region src/content/effect.d.ts
|
|
171
|
-
|
|
170
|
+
declare const messageEffects: {
|
|
171
|
+
readonly balloons: "com.apple.messages.effect.CKBalloonEffect";
|
|
172
|
+
readonly celebration: "com.apple.messages.effect.CKHappyBirthdayEffect";
|
|
173
|
+
readonly confetti: "com.apple.messages.effect.CKConfettiEffect";
|
|
174
|
+
readonly echo: "com.apple.messages.effect.CKEchoEffect";
|
|
175
|
+
readonly fireworks: "com.apple.messages.effect.CKFireworksEffect";
|
|
176
|
+
readonly gentle: "com.apple.MobileSMS.expressivesend.gentle";
|
|
177
|
+
readonly heart: "com.apple.messages.effect.CKHeartEffect";
|
|
178
|
+
readonly invisible: "com.apple.MobileSMS.expressivesend.invisibleink";
|
|
179
|
+
readonly lasers: "com.apple.messages.effect.CKLasersEffect";
|
|
180
|
+
readonly loud: "com.apple.MobileSMS.expressivesend.loud";
|
|
181
|
+
readonly slam: "com.apple.MobileSMS.expressivesend.impact";
|
|
182
|
+
readonly sparkles: "com.apple.messages.effect.CKSparklesEffect";
|
|
183
|
+
readonly spotlight: "com.apple.messages.effect.CKSpotlightEffect";
|
|
184
|
+
};
|
|
185
|
+
type IMessageMessageEffect = (typeof messageEffects)[keyof typeof messageEffects];
|
|
172
186
|
declare function effect(input: ContentInput, messageEffect: IMessageMessageEffect): ContentBuilder;
|
|
173
187
|
//#endregion
|
|
174
188
|
//#region src/types.d.ts
|
|
@@ -176,7 +190,7 @@ interface RemoteClient {
|
|
|
176
190
|
client: AdvancedIMessage;
|
|
177
191
|
phone: string;
|
|
178
192
|
}
|
|
179
|
-
type IMessageClient =
|
|
193
|
+
type IMessageClient = RemoteClient[];
|
|
180
194
|
declare const userSchema: z.ZodObject<{
|
|
181
195
|
address: z.ZodOptional<z.ZodString>;
|
|
182
196
|
country: z.ZodOptional<z.ZodString>;
|
|
@@ -219,10 +233,7 @@ type IMessageMessage = SchemaMessage<typeof userSchema, typeof spaceSchema> & {
|
|
|
219
233
|
};
|
|
220
234
|
//#endregion
|
|
221
235
|
//#region src/index.d.ts
|
|
222
|
-
declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-ts/core").PlatformDef<"iMessage", import("zod").
|
|
223
|
-
local: import("zod").ZodLiteral<true>;
|
|
224
|
-
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
225
|
-
local: import("zod").ZodDefault<import("zod").ZodOptional<import("zod").ZodLiteral<false>>>;
|
|
236
|
+
declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-ts/core").PlatformDef<"iMessage", import("zod").ZodObject<{
|
|
226
237
|
clients: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodObject<{
|
|
227
238
|
address: import("zod").ZodString;
|
|
228
239
|
token: import("zod").ZodString;
|
|
@@ -232,7 +243,7 @@ declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-t
|
|
|
232
243
|
token: import("zod").ZodString;
|
|
233
244
|
phone: import("zod").ZodString;
|
|
234
245
|
}, import("zod/v4/core").$strip>>]>>;
|
|
235
|
-
}, import("zod/v4/core").$
|
|
246
|
+
}, import("zod/v4/core").$strict>, import("zod").ZodObject<{
|
|
236
247
|
address: import("zod").ZodOptional<import("zod").ZodString>;
|
|
237
248
|
country: import("zod").ZodOptional<import("zod").ZodString>;
|
|
238
249
|
service: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
@@ -276,9 +287,6 @@ declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-t
|
|
|
276
287
|
}: {
|
|
277
288
|
client: IMessageClient;
|
|
278
289
|
config: {
|
|
279
|
-
local: true;
|
|
280
|
-
} | {
|
|
281
|
-
local: false;
|
|
282
290
|
clients?: {
|
|
283
291
|
address: string;
|
|
284
292
|
token: string;
|
|
@@ -303,9 +311,6 @@ declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-t
|
|
|
303
311
|
}: {
|
|
304
312
|
client: IMessageClient;
|
|
305
313
|
config: {
|
|
306
|
-
local: true;
|
|
307
|
-
} | {
|
|
308
|
-
local: false;
|
|
309
314
|
clients?: {
|
|
310
315
|
address: string;
|
|
311
316
|
token: string;
|
|
@@ -330,9 +335,6 @@ declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-t
|
|
|
330
335
|
}: {
|
|
331
336
|
client: IMessageClient;
|
|
332
337
|
config: {
|
|
333
|
-
local: true;
|
|
334
|
-
} | {
|
|
335
|
-
local: false;
|
|
336
338
|
clients?: {
|
|
337
339
|
address: string;
|
|
338
340
|
token: string;
|
|
@@ -357,9 +359,6 @@ declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-t
|
|
|
357
359
|
}: {
|
|
358
360
|
client: IMessageClient;
|
|
359
361
|
config: {
|
|
360
|
-
local: true;
|
|
361
|
-
} | {
|
|
362
|
-
local: false;
|
|
363
362
|
clients?: {
|
|
364
363
|
address: string;
|
|
365
364
|
token: string;
|
|
@@ -387,19 +386,19 @@ declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-t
|
|
|
387
386
|
}>> & Readonly<{
|
|
388
387
|
effect: {
|
|
389
388
|
message: {
|
|
390
|
-
readonly
|
|
391
|
-
readonly
|
|
392
|
-
readonly gentle: "com.apple.MobileSMS.expressivesend.gentle";
|
|
393
|
-
readonly invisible: "com.apple.MobileSMS.expressivesend.invisibleink";
|
|
389
|
+
readonly balloons: "com.apple.messages.effect.CKBalloonEffect";
|
|
390
|
+
readonly celebration: "com.apple.messages.effect.CKHappyBirthdayEffect";
|
|
394
391
|
readonly confetti: "com.apple.messages.effect.CKConfettiEffect";
|
|
392
|
+
readonly echo: "com.apple.messages.effect.CKEchoEffect";
|
|
395
393
|
readonly fireworks: "com.apple.messages.effect.CKFireworksEffect";
|
|
396
|
-
readonly
|
|
394
|
+
readonly gentle: "com.apple.MobileSMS.expressivesend.gentle";
|
|
397
395
|
readonly heart: "com.apple.messages.effect.CKHeartEffect";
|
|
396
|
+
readonly invisible: "com.apple.MobileSMS.expressivesend.invisibleink";
|
|
398
397
|
readonly lasers: "com.apple.messages.effect.CKLasersEffect";
|
|
399
|
-
readonly
|
|
398
|
+
readonly loud: "com.apple.MobileSMS.expressivesend.loud";
|
|
399
|
+
readonly slam: "com.apple.MobileSMS.expressivesend.impact";
|
|
400
400
|
readonly sparkles: "com.apple.messages.effect.CKSparklesEffect";
|
|
401
401
|
readonly spotlight: "com.apple.messages.effect.CKSpotlightEffect";
|
|
402
|
-
readonly echo: "com.apple.messages.effect.CKEchoEffect";
|
|
403
402
|
};
|
|
404
403
|
};
|
|
405
404
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { IMessageSDK } from "@photon-ai/imessage-kit";
|
|
1
|
+
import { NotFoundError, ValidationError, createClient } from "@photon-ai/advanced-imessage";
|
|
3
2
|
import { sanitizePhone, withSpan } from "@photon-ai/otel";
|
|
4
|
-
import { UnsupportedError, appLayoutSchema, cloud, definePlatform, fromVCard, mergeStreams, read,
|
|
3
|
+
import { UnsupportedError, appLayoutSchema, cloud, definePlatform, fromVCard, mergeStreams, read, text, toVCard } from "@spectrum-ts/core";
|
|
5
4
|
import { addMemberSchema, asAttachment, asContact, asCustom, asGroup, asPoll, asPollOption, asReply, asText, avatarSchema, buildPhotoAction, createLogger, ensureM4a, errorAttrs, groupSchema, leaveSpaceSchema, messageEffectSchema, photoActionSchema, reactionSchema, removeMemberSchema, renameSchema, resumableOrderedStream, sanitizeErrorMessage } from "@spectrum-ts/core/authoring";
|
|
6
5
|
import z from "zod";
|
|
7
|
-
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
8
|
-
import { createReadStream } from "node:fs";
|
|
9
|
-
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
10
|
-
import { Readable } from "node:stream";
|
|
11
|
-
import { tmpdir } from "node:os";
|
|
12
|
-
import { basename, join } from "node:path";
|
|
13
6
|
import { Marked } from "marked";
|
|
14
7
|
import { LRUCache } from "lru-cache";
|
|
15
8
|
//#region src/content/background.ts
|
|
@@ -156,7 +149,22 @@ function customizedMiniApp(input) {
|
|
|
156
149
|
}
|
|
157
150
|
//#endregion
|
|
158
151
|
//#region src/content/effect.ts
|
|
159
|
-
const
|
|
152
|
+
const messageEffects = {
|
|
153
|
+
balloons: "com.apple.messages.effect.CKBalloonEffect",
|
|
154
|
+
celebration: "com.apple.messages.effect.CKHappyBirthdayEffect",
|
|
155
|
+
confetti: "com.apple.messages.effect.CKConfettiEffect",
|
|
156
|
+
echo: "com.apple.messages.effect.CKEchoEffect",
|
|
157
|
+
fireworks: "com.apple.messages.effect.CKFireworksEffect",
|
|
158
|
+
gentle: "com.apple.MobileSMS.expressivesend.gentle",
|
|
159
|
+
heart: "com.apple.messages.effect.CKHeartEffect",
|
|
160
|
+
invisible: "com.apple.MobileSMS.expressivesend.invisibleink",
|
|
161
|
+
lasers: "com.apple.messages.effect.CKLasersEffect",
|
|
162
|
+
loud: "com.apple.MobileSMS.expressivesend.loud",
|
|
163
|
+
slam: "com.apple.MobileSMS.expressivesend.impact",
|
|
164
|
+
sparkles: "com.apple.messages.effect.CKSparklesEffect",
|
|
165
|
+
spotlight: "com.apple.messages.effect.CKSpotlightEffect"
|
|
166
|
+
};
|
|
167
|
+
const SUPPORTED_EFFECTS = new Set(Object.values(messageEffects));
|
|
160
168
|
const resolveContent = (input) => typeof input === "string" ? text(input).build() : input.build();
|
|
161
169
|
function effect(input, messageEffect) {
|
|
162
170
|
return { build: async () => {
|
|
@@ -178,16 +186,12 @@ function effect(input, messageEffect) {
|
|
|
178
186
|
* no `from` parameter on send), so all routing through it tags this sentinel.
|
|
179
187
|
*/
|
|
180
188
|
const SHARED_PHONE = "shared";
|
|
181
|
-
const isLocal = (client) => client instanceof IMessageSDK;
|
|
182
189
|
const clientEntry = z.object({
|
|
183
190
|
address: z.string(),
|
|
184
191
|
token: z.string(),
|
|
185
192
|
phone: z.string()
|
|
186
193
|
});
|
|
187
|
-
const configSchema = z.
|
|
188
|
-
local: z.literal(false).optional().default(false),
|
|
189
|
-
clients: clientEntry.or(z.array(clientEntry)).optional()
|
|
190
|
-
})]);
|
|
194
|
+
const configSchema = z.strictObject({ clients: clientEntry.or(z.array(clientEntry)).optional() });
|
|
191
195
|
const userSchema = z.object({
|
|
192
196
|
address: z.string().optional(),
|
|
193
197
|
country: z.string().optional(),
|
|
@@ -436,10 +440,9 @@ var PollCache = class {
|
|
|
436
440
|
const messageCaches = /* @__PURE__ */ new WeakMap();
|
|
437
441
|
const pollCaches = /* @__PURE__ */ new WeakMap();
|
|
438
442
|
/**
|
|
439
|
-
* Returns a per-client message cache. Keyed by
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
* state accidentally.
|
|
443
|
+
* Returns a per-client message cache. Keyed by the client array so each
|
|
444
|
+
* iMessage provider instance has its own cache and multiple providers don't
|
|
445
|
+
* share state accidentally.
|
|
443
446
|
*/
|
|
444
447
|
const getMessageCache = (owner) => {
|
|
445
448
|
let cache = messageCaches.get(owner);
|
|
@@ -457,257 +460,6 @@ const getPollCache = (owner) => {
|
|
|
457
460
|
}
|
|
458
461
|
return cache;
|
|
459
462
|
};
|
|
460
|
-
const addTextPart = (parts, text) => {
|
|
461
|
-
const trimmed = text?.trim();
|
|
462
|
-
if (trimmed) parts.push({
|
|
463
|
-
type: "text",
|
|
464
|
-
text: trimmed
|
|
465
|
-
});
|
|
466
|
-
};
|
|
467
|
-
const hasUsableTextPart = (text) => text?.split("").some((segment) => segment.trim()) ?? false;
|
|
468
|
-
const addAttachmentParts = (parts, attachments) => {
|
|
469
|
-
for (const attachment of attachments) if (attachment) parts.push({
|
|
470
|
-
type: "attachment",
|
|
471
|
-
attachment
|
|
472
|
-
});
|
|
473
|
-
};
|
|
474
|
-
const toOrderedParts = (text, attachments) => {
|
|
475
|
-
const parts = [];
|
|
476
|
-
if (!text) {
|
|
477
|
-
addAttachmentParts(parts, attachments);
|
|
478
|
-
return parts;
|
|
479
|
-
}
|
|
480
|
-
if (!text.includes("")) {
|
|
481
|
-
addAttachmentParts(parts, attachments);
|
|
482
|
-
addTextPart(parts, text);
|
|
483
|
-
return parts;
|
|
484
|
-
}
|
|
485
|
-
const textSegments = text.split("");
|
|
486
|
-
for (let i = 0; i < attachments.length; i++) {
|
|
487
|
-
addTextPart(parts, textSegments[i]);
|
|
488
|
-
const attachment = attachments[i];
|
|
489
|
-
if (attachment) parts.push({
|
|
490
|
-
type: "attachment",
|
|
491
|
-
attachment
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
addTextPart(parts, textSegments.slice(attachments.length).join(""));
|
|
495
|
-
return parts;
|
|
496
|
-
};
|
|
497
|
-
//#endregion
|
|
498
|
-
//#region src/shared/vcard.ts
|
|
499
|
-
const VCARD_MIME_TYPES = new Set([
|
|
500
|
-
"text/vcard",
|
|
501
|
-
"text/x-vcard",
|
|
502
|
-
"text/directory",
|
|
503
|
-
"application/vcard",
|
|
504
|
-
"application/x-vcard"
|
|
505
|
-
]);
|
|
506
|
-
const normalizeMimeType = (mimeType) => (mimeType.split(";")[0] ?? "").trim().toLowerCase();
|
|
507
|
-
const isVCardAttachment = (mimeType, fileName) => {
|
|
508
|
-
if (mimeType && VCARD_MIME_TYPES.has(normalizeMimeType(mimeType))) return true;
|
|
509
|
-
return Boolean(fileName?.toLowerCase().endsWith(".vcf"));
|
|
510
|
-
};
|
|
511
|
-
const vcardFileName = (contact) => {
|
|
512
|
-
return `${(contact.name?.formatted ?? contact.user?.id ?? "contact").replace(/[^a-zA-Z0-9_\-.]/g, "_")}.vcf`;
|
|
513
|
-
};
|
|
514
|
-
const readLocalAttachment = async (att) => {
|
|
515
|
-
if (!att.localPath) throw new Error(`iMessage attachment ${att.id} has no local file available on disk`);
|
|
516
|
-
return readFile(att.localPath);
|
|
517
|
-
};
|
|
518
|
-
const toAttachmentContent$1 = (att) => {
|
|
519
|
-
const { localPath } = att;
|
|
520
|
-
return asAttachment({
|
|
521
|
-
id: att.id,
|
|
522
|
-
name: att.fileName ?? "attachment",
|
|
523
|
-
mimeType: att.mimeType,
|
|
524
|
-
size: att.sizeBytes,
|
|
525
|
-
read: () => readLocalAttachment(att),
|
|
526
|
-
stream: localPath ? async () => Readable.toWeb(createReadStream(localPath)) : void 0
|
|
527
|
-
});
|
|
528
|
-
};
|
|
529
|
-
const toVCardContent$1 = async (att) => {
|
|
530
|
-
try {
|
|
531
|
-
return asContact(fromVCard((await readLocalAttachment(att)).toString("utf8")));
|
|
532
|
-
} catch {
|
|
533
|
-
return toAttachmentContent$1(att);
|
|
534
|
-
}
|
|
535
|
-
};
|
|
536
|
-
const localAttachmentContent = async (att) => isVCardAttachment(att.mimeType, att.fileName) ? await toVCardContent$1(att) : toAttachmentContent$1(att);
|
|
537
|
-
//#endregion
|
|
538
|
-
//#region src/local/inbound.ts
|
|
539
|
-
const ATTACHMENT_JOIN_RETRY_DELAY_MS = 250;
|
|
540
|
-
const ATTACHMENT_JOIN_RETRY_LIMIT = 8;
|
|
541
|
-
const ATTACHMENT_JOIN_FETCH_LIMIT = 10;
|
|
542
|
-
const hasAttachmentPlaceholder = (message) => message.text?.includes("") ?? false;
|
|
543
|
-
const isPendingAttachmentJoin = (message) => message.attachments.length === 0 && (message.hasAttachments || hasAttachmentPlaceholder(message));
|
|
544
|
-
const replyTargetId = (message) => message.threadRootMessageId ?? void 0;
|
|
545
|
-
const stubReplyTarget$1 = (space, targetId) => ({
|
|
546
|
-
id: targetId,
|
|
547
|
-
content: asCustom({
|
|
548
|
-
imessage_type: "reply-target",
|
|
549
|
-
stub: true
|
|
550
|
-
}),
|
|
551
|
-
space
|
|
552
|
-
});
|
|
553
|
-
const asProviderReply$1 = (content, target) => asReply({
|
|
554
|
-
content,
|
|
555
|
-
target
|
|
556
|
-
});
|
|
557
|
-
const wrapReply = (messages, targetId) => {
|
|
558
|
-
if (!targetId) return messages;
|
|
559
|
-
return messages.map((message) => ({
|
|
560
|
-
...message,
|
|
561
|
-
content: asProviderReply$1(message.content, stubReplyTarget$1(message.space, targetId))
|
|
562
|
-
}));
|
|
563
|
-
};
|
|
564
|
-
const refetchUntilAttachmentsSettle = async (client, message) => {
|
|
565
|
-
if (!message.chatId) return message;
|
|
566
|
-
for (let attempt = 0; attempt < ATTACHMENT_JOIN_RETRY_LIMIT; attempt += 1) {
|
|
567
|
-
await setTimeout$1(ATTACHMENT_JOIN_RETRY_DELAY_MS);
|
|
568
|
-
let rows;
|
|
569
|
-
try {
|
|
570
|
-
rows = await client.getMessages({
|
|
571
|
-
chatId: message.chatId,
|
|
572
|
-
limit: ATTACHMENT_JOIN_FETCH_LIMIT,
|
|
573
|
-
since: message.createdAt
|
|
574
|
-
});
|
|
575
|
-
} catch {
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
const refreshed = rows.find((row) => row.id === message.id);
|
|
579
|
-
if (refreshed && !isPendingAttachmentJoin(refreshed)) return refreshed;
|
|
580
|
-
}
|
|
581
|
-
return message;
|
|
582
|
-
};
|
|
583
|
-
const toMessages = async (message) => {
|
|
584
|
-
const { chatId, chatKind } = message;
|
|
585
|
-
if (!chatId || chatKind === "unknown") return [];
|
|
586
|
-
if (message.reaction !== null || message.kind !== "text" || message.retractedAt !== null) return [];
|
|
587
|
-
if (isPendingAttachmentJoin(message)) return [];
|
|
588
|
-
const base = {
|
|
589
|
-
sender: {
|
|
590
|
-
id: message.participant ?? "",
|
|
591
|
-
...message.participant ? { address: message.participant } : {}
|
|
592
|
-
},
|
|
593
|
-
space: {
|
|
594
|
-
id: chatId,
|
|
595
|
-
type: chatKind === "group" ? "group" : "dm",
|
|
596
|
-
phone: ""
|
|
597
|
-
},
|
|
598
|
-
timestamp: message.createdAt
|
|
599
|
-
};
|
|
600
|
-
const targetId = replyTargetId(message);
|
|
601
|
-
if (message.attachments.length > 0) {
|
|
602
|
-
if (!hasUsableTextPart(message.text)) return wrapReply(await Promise.all(message.attachments.map(async (att) => ({
|
|
603
|
-
...base,
|
|
604
|
-
id: `${message.id}:${att.id}`,
|
|
605
|
-
content: await localAttachmentContent(att)
|
|
606
|
-
}))), targetId);
|
|
607
|
-
const parts = toOrderedParts(message.text, message.attachments);
|
|
608
|
-
const messages = [];
|
|
609
|
-
for (let i = 0; i < parts.length; i++) {
|
|
610
|
-
const part = parts[i];
|
|
611
|
-
if (!part) continue;
|
|
612
|
-
messages.push(part.type === "text" ? {
|
|
613
|
-
...base,
|
|
614
|
-
id: `${message.id}:text:${i}`,
|
|
615
|
-
content: asText(part.text),
|
|
616
|
-
partIndex: i
|
|
617
|
-
} : {
|
|
618
|
-
...base,
|
|
619
|
-
id: `${message.id}:${part.attachment.id}`,
|
|
620
|
-
content: await localAttachmentContent(part.attachment),
|
|
621
|
-
partIndex: i
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
return wrapReply(messages, targetId);
|
|
625
|
-
}
|
|
626
|
-
return wrapReply([{
|
|
627
|
-
...base,
|
|
628
|
-
id: message.id,
|
|
629
|
-
content: {
|
|
630
|
-
type: "text",
|
|
631
|
-
text: message.text ?? ""
|
|
632
|
-
}
|
|
633
|
-
}], targetId);
|
|
634
|
-
};
|
|
635
|
-
const messages$3 = (client) => stream((emit, end) => {
|
|
636
|
-
let lastPromise = Promise.resolve();
|
|
637
|
-
const handleIncoming = async (message) => {
|
|
638
|
-
const ms = await toMessages(isPendingAttachmentJoin(message) ? await refetchUntilAttachmentsSettle(client, message) : message);
|
|
639
|
-
for (const m of ms) await emit(m);
|
|
640
|
-
};
|
|
641
|
-
const startPromise = client.startWatching({
|
|
642
|
-
onIncomingMessage: (message) => {
|
|
643
|
-
lastPromise = lastPromise.then(() => handleIncoming(message)).catch(end);
|
|
644
|
-
},
|
|
645
|
-
onError: end
|
|
646
|
-
}).catch(end);
|
|
647
|
-
return async () => {
|
|
648
|
-
await startPromise.catch(() => {});
|
|
649
|
-
await client.stopWatching();
|
|
650
|
-
await lastPromise.catch(() => {});
|
|
651
|
-
};
|
|
652
|
-
});
|
|
653
|
-
//#endregion
|
|
654
|
-
//#region src/shared/errors.ts
|
|
655
|
-
const IMESSAGE_PLATFORM = "iMessage";
|
|
656
|
-
const LOCAL_IMESSAGE_PLATFORM = "iMessage (local mode)";
|
|
657
|
-
const unsupportedRemoteContent = (type, detail) => UnsupportedError.content(type, IMESSAGE_PLATFORM, detail);
|
|
658
|
-
const unsupportedLocalContent = (type, detail) => UnsupportedError.content(type, LOCAL_IMESSAGE_PLATFORM, detail);
|
|
659
|
-
//#endregion
|
|
660
|
-
//#region src/local/send.ts
|
|
661
|
-
const synthRecord = (spaceId, content) => ({
|
|
662
|
-
id: crypto.randomUUID(),
|
|
663
|
-
content,
|
|
664
|
-
space: { id: spaceId },
|
|
665
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
666
|
-
});
|
|
667
|
-
const sendTempFile = async (client, spaceId, name, data) => {
|
|
668
|
-
const safeName = basename(name) || "attachment";
|
|
669
|
-
const dir = await mkdtemp(join(tmpdir(), "spectrum-"));
|
|
670
|
-
const tmp = join(dir, safeName);
|
|
671
|
-
await writeFile(tmp, data);
|
|
672
|
-
try {
|
|
673
|
-
await client.send({
|
|
674
|
-
to: spaceId,
|
|
675
|
-
attachments: [tmp]
|
|
676
|
-
});
|
|
677
|
-
} finally {
|
|
678
|
-
await rm(dir, {
|
|
679
|
-
recursive: true,
|
|
680
|
-
force: true
|
|
681
|
-
}).catch(() => {});
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
const send$3 = async (client, spaceId, content) => {
|
|
685
|
-
switch (content.type) {
|
|
686
|
-
case "text":
|
|
687
|
-
await client.send({
|
|
688
|
-
to: spaceId,
|
|
689
|
-
text: content.text
|
|
690
|
-
});
|
|
691
|
-
return synthRecord(spaceId, content);
|
|
692
|
-
case "attachment":
|
|
693
|
-
await sendTempFile(client, spaceId, content.name, await content.read());
|
|
694
|
-
return synthRecord(spaceId, content);
|
|
695
|
-
case "contact": {
|
|
696
|
-
const vcf = await toVCard(content);
|
|
697
|
-
await sendTempFile(client, spaceId, vcardFileName(content), Buffer.from(vcf, "utf8"));
|
|
698
|
-
return synthRecord(spaceId, content);
|
|
699
|
-
}
|
|
700
|
-
case "effect": throw unsupportedLocalContent("effect", "message effects require remote iMessage");
|
|
701
|
-
case "poll": throw unsupportedLocalContent("poll");
|
|
702
|
-
default: throw unsupportedLocalContent(content.type);
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
const getMessage$3 = async (_client, _id) => void 0;
|
|
706
|
-
//#endregion
|
|
707
|
-
//#region src/local/api.ts
|
|
708
|
-
const messages$2 = (client) => messages$3(client);
|
|
709
|
-
const send$2 = async (client, spaceId, content) => send$3(client, spaceId, content);
|
|
710
|
-
const getMessage$2 = async (client, id) => getMessage$3(client, id);
|
|
711
463
|
//#endregion
|
|
712
464
|
//#region src/remote/ids.ts
|
|
713
465
|
const PART_PREFIX = /^p:(\d+)\//;
|
|
@@ -824,6 +576,59 @@ const sendCustomizedMiniApp$1 = async (remote, spaceId, content) => {
|
|
|
824
576
|
const updateCustomizedMiniApp$1 = async (remote, spaceId, session, content) => {
|
|
825
577
|
return toProviderRecord(await remote.messages.updateCustomizedMiniApp(session, content), content, spaceId);
|
|
826
578
|
};
|
|
579
|
+
const addTextPart = (parts, text) => {
|
|
580
|
+
const trimmed = text?.trim();
|
|
581
|
+
if (trimmed) parts.push({
|
|
582
|
+
type: "text",
|
|
583
|
+
text: trimmed
|
|
584
|
+
});
|
|
585
|
+
};
|
|
586
|
+
const addAttachmentParts = (parts, attachments) => {
|
|
587
|
+
for (const attachment of attachments) if (attachment) parts.push({
|
|
588
|
+
type: "attachment",
|
|
589
|
+
attachment
|
|
590
|
+
});
|
|
591
|
+
};
|
|
592
|
+
const toOrderedParts = (text, attachments) => {
|
|
593
|
+
const parts = [];
|
|
594
|
+
if (!text) {
|
|
595
|
+
addAttachmentParts(parts, attachments);
|
|
596
|
+
return parts;
|
|
597
|
+
}
|
|
598
|
+
if (!text.includes("")) {
|
|
599
|
+
addAttachmentParts(parts, attachments);
|
|
600
|
+
addTextPart(parts, text);
|
|
601
|
+
return parts;
|
|
602
|
+
}
|
|
603
|
+
const textSegments = text.split("");
|
|
604
|
+
for (let i = 0; i < attachments.length; i++) {
|
|
605
|
+
addTextPart(parts, textSegments[i]);
|
|
606
|
+
const attachment = attachments[i];
|
|
607
|
+
if (attachment) parts.push({
|
|
608
|
+
type: "attachment",
|
|
609
|
+
attachment
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
addTextPart(parts, textSegments.slice(attachments.length).join(""));
|
|
613
|
+
return parts;
|
|
614
|
+
};
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/shared/vcard.ts
|
|
617
|
+
const VCARD_MIME_TYPES = new Set([
|
|
618
|
+
"text/vcard",
|
|
619
|
+
"text/x-vcard",
|
|
620
|
+
"text/directory",
|
|
621
|
+
"application/vcard",
|
|
622
|
+
"application/x-vcard"
|
|
623
|
+
]);
|
|
624
|
+
const normalizeMimeType = (mimeType) => (mimeType.split(";")[0] ?? "").trim().toLowerCase();
|
|
625
|
+
const isVCardAttachment = (mimeType, fileName) => {
|
|
626
|
+
if (mimeType && VCARD_MIME_TYPES.has(normalizeMimeType(mimeType))) return true;
|
|
627
|
+
return Boolean(fileName?.toLowerCase().endsWith(".vcf"));
|
|
628
|
+
};
|
|
629
|
+
const vcardFileName = (contact) => {
|
|
630
|
+
return `${(contact.name?.formatted ?? contact.user?.id ?? "contact").replace(/[^a-zA-Z0-9_\-.]/g, "_")}.vcf`;
|
|
631
|
+
};
|
|
827
632
|
//#endregion
|
|
828
633
|
//#region src/remote/attachments.ts
|
|
829
634
|
/**
|
|
@@ -1273,6 +1078,10 @@ const getDisplayName$1 = async (remote, spaceId) => {
|
|
|
1273
1078
|
return displayName || void 0;
|
|
1274
1079
|
};
|
|
1275
1080
|
//#endregion
|
|
1081
|
+
//#region src/shared/errors.ts
|
|
1082
|
+
const IMESSAGE_PLATFORM = "iMessage";
|
|
1083
|
+
const unsupportedRemoteContent = (type, detail) => UnsupportedError.content(type, IMESSAGE_PLATFORM, detail);
|
|
1084
|
+
//#endregion
|
|
1276
1085
|
//#region src/remote/markdown.ts
|
|
1277
1086
|
const markdownLexer = new Marked();
|
|
1278
1087
|
const BULLET = "• ";
|
|
@@ -2276,7 +2085,6 @@ const cacheRemoteOutbound = (remote, space, record) => {
|
|
|
2276
2085
|
return record;
|
|
2277
2086
|
};
|
|
2278
2087
|
const handleEdit = async (client, space, content) => {
|
|
2279
|
-
if (isLocal(client)) throw UnsupportedError.action("edit", "iMessage (local mode)");
|
|
2280
2088
|
const miniAppCardSession = content.target.miniAppCardSession;
|
|
2281
2089
|
const updateMiniAppCardSession = (record) => {
|
|
2282
2090
|
const nextSession = record?.miniAppCardSession;
|
|
@@ -2300,7 +2108,6 @@ const handleEdit = async (client, space, content) => {
|
|
|
2300
2108
|
await editMessage(clientForPhone(client, space.phone), space.id, content.target.id, content.content);
|
|
2301
2109
|
};
|
|
2302
2110
|
const handleUnsend = async (client, space, content) => {
|
|
2303
|
-
if (isLocal(client)) throw UnsupportedError.action("unsend", "iMessage (local mode)");
|
|
2304
2111
|
if (isPollContent(content.target.content)) throw UnsupportedError.action("unsend", "iMessage", "iMessage polls cannot be unsent");
|
|
2305
2112
|
const remote = clientForPhone(client, space.phone);
|
|
2306
2113
|
const targetContent = content.target.content;
|
|
@@ -2311,53 +2118,43 @@ const handleUnsend = async (client, space, content) => {
|
|
|
2311
2118
|
await unsendMessage(remote, space.id, content.target.id);
|
|
2312
2119
|
};
|
|
2313
2120
|
const handleStreamText = async (client, space, content) => {
|
|
2314
|
-
if (isLocal(client)) throw UnsupportedError.action("streamText", "iMessage (local mode)", "streaming text responses require remote iMessage");
|
|
2315
2121
|
const remote = clientForPhone(client, space.phone);
|
|
2316
2122
|
return cacheRemoteOutbound(remote, space, await sendStreamText(remote, space.id, content));
|
|
2317
2123
|
};
|
|
2318
2124
|
const handleBackground = async (client, space, content) => {
|
|
2319
|
-
if (isLocal(client)) throw UnsupportedError.action("background", "iMessage (local mode)", "chat backgrounds require remote iMessage");
|
|
2320
2125
|
await setBackground(clientForPhone(client, space.phone), space.id, content);
|
|
2321
2126
|
};
|
|
2322
2127
|
const handleCustomizedMiniApp = async (client, space, content) => {
|
|
2323
|
-
if (isLocal(client)) throw UnsupportedError.action("customized-mini-app", "iMessage (local mode)", "mini app cards require remote iMessage");
|
|
2324
2128
|
const remote = clientForPhone(client, space.phone);
|
|
2325
2129
|
return cacheRemoteOutbound(remote, space, await sendCustomizedMiniApp(remote, space.id, content));
|
|
2326
2130
|
};
|
|
2327
2131
|
/**
|
|
2328
2132
|
* Render the universal `app` content. On remote it becomes a native Spectrum
|
|
2329
2133
|
* mini-app card (fixed `SPECTRUM_MINI_APP` identity + the URL + the layout
|
|
2330
|
-
* already parsed from the URL's link metadata).
|
|
2331
|
-
* so it degrades to the bare URL as a text message.
|
|
2134
|
+
* already parsed from the URL's link metadata).
|
|
2332
2135
|
*/
|
|
2333
2136
|
const handleApp = async (client, space, content) => {
|
|
2334
2137
|
const url = await content.url();
|
|
2335
|
-
if (isLocal(client)) return await send$2(client, space.id, await text(url).build());
|
|
2336
2138
|
const layout = await content.layout();
|
|
2337
2139
|
const remote = clientForPhone(client, space.phone);
|
|
2338
2140
|
return cacheRemoteOutbound(remote, space, await sendCustomizedMiniApp(remote, space.id, toSpectrumMiniApp(url, layout, content.live)));
|
|
2339
2141
|
};
|
|
2340
2142
|
const handleRead = async (client, space) => {
|
|
2341
|
-
if (isLocal(client)) throw UnsupportedError.action("read", "iMessage (local mode)", "marking chats as read requires remote iMessage");
|
|
2342
2143
|
await markRead(clientForPhone(client, space.phone), space.id);
|
|
2343
2144
|
};
|
|
2344
2145
|
const handleShareContactCard = async (client, space) => {
|
|
2345
|
-
if (isLocal(client)) throw UnsupportedError.action("shareContactCard", "iMessage (local mode)", "sharing the contact card requires remote iMessage");
|
|
2346
2146
|
await shareContactCard(clientForPhone(client, space.phone), space.id);
|
|
2347
2147
|
};
|
|
2348
2148
|
const handleTyping = async (client, space, state) => {
|
|
2349
|
-
if (isLocal(client)) return;
|
|
2350
2149
|
const remote = clientForPhone(client, space.phone);
|
|
2351
2150
|
if (state === "start") await startTyping(remote, space.id);
|
|
2352
2151
|
else await stopTyping(remote, space.id);
|
|
2353
2152
|
};
|
|
2354
2153
|
const handleRename = async (client, space, content) => {
|
|
2355
|
-
if (isLocal(client)) throw UnsupportedError.action("rename", "iMessage (local mode)", "renaming chats requires remote iMessage");
|
|
2356
2154
|
if (space.type !== "group") throw UnsupportedError.action("rename", "iMessage", "only group chats can be renamed (this space is a DM)");
|
|
2357
2155
|
await setDisplayName(clientForPhone(client, space.phone), space.id, content);
|
|
2358
2156
|
};
|
|
2359
2157
|
const handleAvatar = async (client, space, content) => {
|
|
2360
|
-
if (isLocal(client)) throw UnsupportedError.action("avatar", "iMessage (local mode)", "setting group avatars requires remote iMessage");
|
|
2361
2158
|
if (space.type !== "group") throw UnsupportedError.action("avatar", "iMessage", "only group chats have avatars (this space is a DM)");
|
|
2362
2159
|
await setIcon(clientForPhone(client, space.phone), space.id, content);
|
|
2363
2160
|
};
|
|
@@ -2367,27 +2164,17 @@ const handleAvatar = async (client, space, content) => {
|
|
|
2367
2164
|
* guard sequence.
|
|
2368
2165
|
*/
|
|
2369
2166
|
const remoteGroupClient = (client, space, action, detail) => {
|
|
2370
|
-
if (
|
|
2371
|
-
if (space.type !== "group") throw UnsupportedError.action(action, "iMessage", detail.dm);
|
|
2167
|
+
if (space.type !== "group") throw UnsupportedError.action(action, "iMessage", detail);
|
|
2372
2168
|
return clientForPhone(client, space.phone);
|
|
2373
2169
|
};
|
|
2374
2170
|
const handleAddMember = async (client, space, content) => {
|
|
2375
|
-
await addParticipants(remoteGroupClient(client, space, "addMember",
|
|
2376
|
-
dm: "only group chats can add members (this space is a DM — iMessage cannot convert a DM into a group; create a group via space.create instead)",
|
|
2377
|
-
local: "adding members requires remote iMessage"
|
|
2378
|
-
}), space.id, content);
|
|
2171
|
+
await addParticipants(remoteGroupClient(client, space, "addMember", "only group chats can add members (this space is a DM — iMessage cannot convert a DM into a group; create a group via space.create instead)"), space.id, content);
|
|
2379
2172
|
};
|
|
2380
2173
|
const handleRemoveMember = async (client, space, content) => {
|
|
2381
|
-
await removeParticipants(remoteGroupClient(client, space, "removeMember",
|
|
2382
|
-
dm: "only group chats can remove members (this space is a DM — iMessage cannot convert a DM into a group; create a group via space.create instead)",
|
|
2383
|
-
local: "removing members requires remote iMessage"
|
|
2384
|
-
}), space.id, content);
|
|
2174
|
+
await removeParticipants(remoteGroupClient(client, space, "removeMember", "only group chats can remove members (this space is a DM — iMessage cannot convert a DM into a group; create a group via space.create instead)"), space.id, content);
|
|
2385
2175
|
};
|
|
2386
2176
|
const handleLeaveSpace = async (client, space) => {
|
|
2387
|
-
await leaveGroup(remoteGroupClient(client, space, "leaveSpace",
|
|
2388
|
-
dm: "only group chats can be left (this space is a DM)",
|
|
2389
|
-
local: "leaving chats requires remote iMessage"
|
|
2390
|
-
}), space.id);
|
|
2177
|
+
await leaveGroup(remoteGroupClient(client, space, "leaveSpace", "only group chats can be left (this space is a DM)"), space.id);
|
|
2391
2178
|
};
|
|
2392
2179
|
/**
|
|
2393
2180
|
* Dispatch the iMessage-only fire-and-forget control signals that live outside
|
|
@@ -2409,21 +2196,18 @@ const handleProviderControlSignal = async (client, space, content) => {
|
|
|
2409
2196
|
};
|
|
2410
2197
|
/**
|
|
2411
2198
|
* Resolve the remote client for a `reply` / `reaction` whose target is another
|
|
2412
|
-
* message.
|
|
2413
|
-
*
|
|
2414
|
-
* `pollNoun` is the plural used in the poll-unsupported message.
|
|
2199
|
+
* message. `action` labels the error and `pollNoun` is the plural used in the
|
|
2200
|
+
* poll-unsupported message.
|
|
2415
2201
|
*/
|
|
2416
2202
|
const remoteForMessageTarget = (client, space, target, action, pollNoun) => {
|
|
2417
|
-
if (isLocal(client)) throw UnsupportedError.action(action, "iMessage (local mode)");
|
|
2418
2203
|
if (isPollContent(target.content)) throw UnsupportedError.action(action, "iMessage", `iMessage polls do not support ${pollNoun}`);
|
|
2419
2204
|
return clientForPhone(client, space.phone);
|
|
2420
2205
|
};
|
|
2421
2206
|
const imessage = definePlatform("iMessage", {
|
|
2422
2207
|
config: configSchema,
|
|
2423
|
-
static: { effect: { message:
|
|
2208
|
+
static: { effect: { message: messageEffects } },
|
|
2424
2209
|
lifecycle: {
|
|
2425
2210
|
createClient: async ({ config, projectId, projectSecret }) => {
|
|
2426
|
-
if (config.local) return new IMessageSDK();
|
|
2427
2211
|
if (config.clients) return (Array.isArray(config.clients) ? config.clients : [config.clients]).map((e) => ({
|
|
2428
2212
|
phone: e.phone,
|
|
2429
2213
|
client: createClient({
|
|
@@ -2434,14 +2218,10 @@ const imessage = definePlatform("iMessage", {
|
|
|
2434
2218
|
token: e.token
|
|
2435
2219
|
})
|
|
2436
2220
|
}));
|
|
2437
|
-
if (!(projectId && projectSecret)) throw new Error("iMessage requires projectId and projectSecret.
|
|
2221
|
+
if (!(projectId && projectSecret)) throw new Error("Cloud iMessage requires projectId and projectSecret. Pass credentials to Spectrum() or provide explicit clients with imessage.config({ clients: [...] }). For local Messages access, install @spectrum-ts/imessage-local and use its imessage.config().");
|
|
2438
2222
|
return await createCloudClients(projectId, projectSecret);
|
|
2439
2223
|
},
|
|
2440
2224
|
destroyClient: async ({ client }) => {
|
|
2441
|
-
if (isLocal(client)) {
|
|
2442
|
-
await client.close();
|
|
2443
|
-
return;
|
|
2444
|
-
}
|
|
2445
2225
|
await disposeCloudAuth(client);
|
|
2446
2226
|
await Promise.all(client.map((entry) => entry.client.close()));
|
|
2447
2227
|
}
|
|
@@ -2455,14 +2235,6 @@ const imessage = definePlatform("iMessage", {
|
|
|
2455
2235
|
params: spaceParamsSchema,
|
|
2456
2236
|
create: async ({ input, client }) => {
|
|
2457
2237
|
if (input.users.length === 0) throw new Error("iMessage space creation requires at least one user");
|
|
2458
|
-
if (isLocal(client)) {
|
|
2459
|
-
if (input.users.length > 1) throw UnsupportedError.action("space.create", "iMessage (local mode)", "local mode cannot create group chats — use space.get(chatGuid) for an existing group");
|
|
2460
|
-
return {
|
|
2461
|
-
id: dmChatGuid(input.users[0]?.id ?? ""),
|
|
2462
|
-
type: "dm",
|
|
2463
|
-
phone: ""
|
|
2464
|
-
};
|
|
2465
|
-
}
|
|
2466
2238
|
if (client.length === 0) throw new Error("No iMessage clients configured");
|
|
2467
2239
|
const addresses = input.users.map((u) => u.id);
|
|
2468
2240
|
if (isSharedMode(client)) {
|
|
@@ -2482,11 +2254,6 @@ const imessage = definePlatform("iMessage", {
|
|
|
2482
2254
|
};
|
|
2483
2255
|
},
|
|
2484
2256
|
get: async ({ input, client }) => {
|
|
2485
|
-
if (isLocal(client)) return {
|
|
2486
|
-
id: input.id,
|
|
2487
|
-
type: chatTypeFromGuid(input.id),
|
|
2488
|
-
phone: ""
|
|
2489
|
-
};
|
|
2490
2257
|
if (client.length === 0) throw new Error("No iMessage clients configured");
|
|
2491
2258
|
const phone = isSharedMode(client) ? SHARED_PHONE : input.params?.phone ?? (client.length === 1 ? client[0]?.phone : void 0);
|
|
2492
2259
|
if (!phone) throw new Error(`iMessage space.get requires params.phone when multiple clients are configured. Available: ${availablePhones(client).join(", ")}`);
|
|
@@ -2506,7 +2273,7 @@ const imessage = definePlatform("iMessage", {
|
|
|
2506
2273
|
}
|
|
2507
2274
|
},
|
|
2508
2275
|
message: { schema: messageSchema },
|
|
2509
|
-
messages: ({ client, projectConfig }) =>
|
|
2276
|
+
messages: ({ client, projectConfig }) => messages(client, projectConfig),
|
|
2510
2277
|
send: async ({ space, content, client }) => {
|
|
2511
2278
|
if (content.type === "reply") {
|
|
2512
2279
|
const remote = remoteForMessageTarget(client, space, content.target, "reply", "replies");
|
|
@@ -2556,35 +2323,23 @@ const imessage = definePlatform("iMessage", {
|
|
|
2556
2323
|
if (content.type === "app") return await handleApp(client, space, content);
|
|
2557
2324
|
if (await handleProviderControlSignal(client, space, content)) return;
|
|
2558
2325
|
if (isCustomizedMiniApp(content)) return await handleCustomizedMiniApp(client, space, content);
|
|
2559
|
-
if (isLocal(client)) return await send$2(client, space.id, content);
|
|
2560
2326
|
const remote = clientForPhone(client, space.phone);
|
|
2561
2327
|
return cacheRemoteOutbound(remote, space, await send(remote, space.id, content));
|
|
2562
2328
|
},
|
|
2563
2329
|
actions: {
|
|
2564
2330
|
getMessage: async ({ client }, space, messageId) => {
|
|
2565
|
-
if (isLocal(client)) return getMessage$2(client, messageId);
|
|
2566
2331
|
return getMessage(clientForPhone(client, space.phone), space.id, messageId, space.phone);
|
|
2567
2332
|
},
|
|
2568
2333
|
getMembers: async ({ client }, space) => {
|
|
2569
|
-
return await listParticipants(remoteGroupClient(client, space, "getMembers",
|
|
2570
|
-
dm: "only group chats support listing members (this space is a DM)",
|
|
2571
|
-
local: "listing members requires remote iMessage"
|
|
2572
|
-
}), space.id, space.phone);
|
|
2334
|
+
return await listParticipants(remoteGroupClient(client, space, "getMembers", "only group chats support listing members (this space is a DM)"), space.id, space.phone);
|
|
2573
2335
|
},
|
|
2574
2336
|
getAvatar: async ({ client }, space) => {
|
|
2575
|
-
return await getIcon(remoteGroupClient(client, space, "getAvatar",
|
|
2576
|
-
dm: "only group chats have avatars (this space is a DM)",
|
|
2577
|
-
local: "fetching group avatars requires remote iMessage"
|
|
2578
|
-
}), space.id);
|
|
2337
|
+
return await getIcon(remoteGroupClient(client, space, "getAvatar", "only group chats have avatars (this space is a DM)"), space.id);
|
|
2579
2338
|
},
|
|
2580
2339
|
getDisplayName: async ({ client }, space) => {
|
|
2581
|
-
return await getDisplayName(remoteGroupClient(client, space, "getDisplayName",
|
|
2582
|
-
dm: "only group chats have display names (this space is a DM)",
|
|
2583
|
-
local: "reading chat display names requires remote iMessage"
|
|
2584
|
-
}), space.id);
|
|
2340
|
+
return await getDisplayName(remoteGroupClient(client, space, "getDisplayName", "only group chats have display names (this space is a DM)"), space.id);
|
|
2585
2341
|
},
|
|
2586
2342
|
getAttachment: async ({ client }, guid, phone) => {
|
|
2587
|
-
if (isLocal(client)) throw UnsupportedError.action("getAttachment", "iMessage (local mode)", "fetching attachments by GUID requires remote iMessage");
|
|
2588
2343
|
if (client.length === 0) throw new Error("No iMessage clients configured");
|
|
2589
2344
|
const routedPhone = (() => {
|
|
2590
2345
|
if (isSharedMode(client)) return SHARED_PHONE;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spectrum-ts/imessage",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "iMessage provider for spectrum-ts
|
|
3
|
+
"version": "11.0.0",
|
|
4
|
+
"description": "iMessage provider for spectrum-ts.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/photon-hq/spectrum-ts.git",
|
|
@@ -32,14 +32,13 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@photon-ai/advanced-imessage": "^1.0.0",
|
|
35
|
-
"@photon-ai/imessage-kit": "^3.0.0",
|
|
36
35
|
"@photon-ai/otel": "^3.1.0",
|
|
37
36
|
"lru-cache": "^11.0.0",
|
|
38
37
|
"marked": "^18.0.5",
|
|
39
38
|
"zod": "^4.2.1"
|
|
40
39
|
},
|
|
41
40
|
"peerDependencies": {
|
|
42
|
-
"@spectrum-ts/core": "^
|
|
41
|
+
"@spectrum-ts/core": "^11.0.0",
|
|
43
42
|
"typescript": "^5 || ^6.0.0"
|
|
44
43
|
},
|
|
45
44
|
"license": "MIT"
|