spectrum-ts 1.1.0 → 1.1.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/{chunk-XMAI2AAN.js → chunk-5GQ2OMFY.js} +84 -91
- package/dist/{chunk-PXX7ISZ6.js → chunk-FF2R4EP3.js} +82 -0
- package/dist/{chunk-7Q7KJKGL.js → chunk-QZ5DJ7VR.js} +1 -1
- package/dist/{chunk-LAGNM6I7.js → chunk-UWUPCIB4.js} +2 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +60 -27
- package/dist/providers/imessage/index.d.ts +1 -2
- package/dist/providers/imessage/index.js +1073 -621
- package/dist/providers/terminal/index.d.ts +1 -1
- package/dist/providers/terminal/index.js +12 -4
- package/dist/providers/whatsapp-business/index.d.ts +1 -2
- package/dist/providers/whatsapp-business/index.js +41 -22
- package/dist/{types-DJQLFwWW.d.ts → types-GhOtIIqj.d.ts} +50 -21
- package/package.json +2 -2
- package/dist/stream-B55k7W8-.d.ts +0 -8
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
asGroup,
|
|
3
|
-
asRichlink
|
|
4
|
-
|
|
3
|
+
asRichlink,
|
|
4
|
+
groupSchema
|
|
5
|
+
} from "../../chunk-UWUPCIB4.js";
|
|
5
6
|
import {
|
|
6
7
|
asPoll,
|
|
7
8
|
asPollOption,
|
|
8
9
|
cloud,
|
|
9
10
|
mergeStreams,
|
|
10
11
|
stream
|
|
11
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-FF2R4EP3.js";
|
|
12
13
|
import {
|
|
13
14
|
UnsupportedError,
|
|
14
15
|
asAttachment,
|
|
15
16
|
asContact,
|
|
16
17
|
asCustom,
|
|
17
|
-
asReaction,
|
|
18
18
|
asText,
|
|
19
19
|
definePlatform,
|
|
20
20
|
fromVCard,
|
|
21
|
+
reactionSchema,
|
|
21
22
|
toVCard
|
|
22
|
-
} from "../../chunk-
|
|
23
|
+
} from "../../chunk-5GQ2OMFY.js";
|
|
23
24
|
|
|
24
25
|
// src/providers/imessage/index.ts
|
|
25
26
|
import { createClient as createClient2, directChat } from "@photon-ai/advanced-imessage";
|
|
@@ -111,17 +112,12 @@ async function disposeCloudAuth(clients) {
|
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
// src/providers/imessage/local.ts
|
|
115
|
+
// src/providers/imessage/local/attachments.ts
|
|
115
116
|
import { createReadStream } from "fs";
|
|
116
|
-
import {
|
|
117
|
-
import { tmpdir } from "os";
|
|
118
|
-
import { basename, join } from "path";
|
|
117
|
+
import { readFile } from "fs/promises";
|
|
119
118
|
import { Readable } from "stream";
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
123
|
-
});
|
|
124
|
-
var DEFAULT_ATTACHMENT_NAME = "attachment";
|
|
119
|
+
|
|
120
|
+
// src/providers/imessage/shared/vcard.ts
|
|
125
121
|
var VCARD_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
126
122
|
"text/vcard",
|
|
127
123
|
"text/x-vcard",
|
|
@@ -136,6 +132,13 @@ var isVCardAttachment = (mimeType, fileName) => {
|
|
|
136
132
|
}
|
|
137
133
|
return Boolean(fileName?.toLowerCase().endsWith(".vcf"));
|
|
138
134
|
};
|
|
135
|
+
var vcardFileName = (contact) => {
|
|
136
|
+
const base = contact.name?.formatted ?? contact.user?.id ?? "contact";
|
|
137
|
+
return `${base.replace(/[^a-zA-Z0-9_\-.]/g, "_")}.vcf`;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/providers/imessage/local/attachments.ts
|
|
141
|
+
var DEFAULT_ATTACHMENT_NAME = "attachment";
|
|
139
142
|
var readLocalAttachment = async (att) => {
|
|
140
143
|
if (!att.localPath) {
|
|
141
144
|
throw new Error(
|
|
@@ -164,6 +167,9 @@ var toVCardContent = async (att) => {
|
|
|
164
167
|
return toAttachmentContent(att);
|
|
165
168
|
}
|
|
166
169
|
};
|
|
170
|
+
var localAttachmentContent = async (att) => isVCardAttachment(att.mimeType, att.fileName) ? await toVCardContent(att) : toAttachmentContent(att);
|
|
171
|
+
|
|
172
|
+
// src/providers/imessage/local/inbound.ts
|
|
167
173
|
var toMessages = async (message) => {
|
|
168
174
|
const { chatId, chatKind } = message;
|
|
169
175
|
if (!chatId || chatKind === "unknown") {
|
|
@@ -182,7 +188,7 @@ var toMessages = async (message) => {
|
|
|
182
188
|
message.attachments.map(async (att) => ({
|
|
183
189
|
...base,
|
|
184
190
|
id: `${message.id}:${att.id}`,
|
|
185
|
-
content:
|
|
191
|
+
content: await localAttachmentContent(att)
|
|
186
192
|
}))
|
|
187
193
|
);
|
|
188
194
|
}
|
|
@@ -214,10 +220,25 @@ var messages = (client) => stream((emit, end) => {
|
|
|
214
220
|
});
|
|
215
221
|
};
|
|
216
222
|
});
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
};
|
|
223
|
+
|
|
224
|
+
// src/providers/imessage/local/send.ts
|
|
225
|
+
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
226
|
+
import { tmpdir } from "os";
|
|
227
|
+
import { basename, join } from "path";
|
|
228
|
+
|
|
229
|
+
// src/providers/imessage/shared/errors.ts
|
|
230
|
+
var IMESSAGE_PLATFORM = "iMessage";
|
|
231
|
+
var LOCAL_IMESSAGE_PLATFORM = "iMessage (local mode)";
|
|
232
|
+
var unsupportedRemoteContent = (type, detail) => UnsupportedError.content(type, IMESSAGE_PLATFORM, detail);
|
|
233
|
+
var unsupportedLocalContent = (type) => UnsupportedError.content(type, LOCAL_IMESSAGE_PLATFORM);
|
|
234
|
+
|
|
235
|
+
// src/providers/imessage/local/send.ts
|
|
236
|
+
var synthRecord = (spaceId, content) => ({
|
|
237
|
+
id: crypto.randomUUID(),
|
|
238
|
+
content,
|
|
239
|
+
space: { id: spaceId },
|
|
240
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
241
|
+
});
|
|
221
242
|
var sendTempFile = async (client, spaceId, name, data) => {
|
|
222
243
|
const safeName = basename(name) || DEFAULT_ATTACHMENT_NAME;
|
|
223
244
|
const dir = await mkdtemp(join(tmpdir(), "spectrum-"));
|
|
@@ -234,10 +255,10 @@ var send = async (client, spaceId, content) => {
|
|
|
234
255
|
switch (content.type) {
|
|
235
256
|
case "text":
|
|
236
257
|
await client.send({ to: spaceId, text: content.text });
|
|
237
|
-
return
|
|
258
|
+
return synthRecord(spaceId, content);
|
|
238
259
|
case "attachment":
|
|
239
260
|
await sendTempFile(client, spaceId, content.name, await content.read());
|
|
240
|
-
return
|
|
261
|
+
return synthRecord(spaceId, content);
|
|
241
262
|
case "contact": {
|
|
242
263
|
const vcf = await toVCard(content);
|
|
243
264
|
await sendTempFile(
|
|
@@ -246,146 +267,38 @@ var send = async (client, spaceId, content) => {
|
|
|
246
267
|
vcardFileName(content),
|
|
247
268
|
Buffer.from(vcf, "utf8")
|
|
248
269
|
);
|
|
249
|
-
return
|
|
270
|
+
return synthRecord(spaceId, content);
|
|
250
271
|
}
|
|
251
272
|
case "poll":
|
|
252
|
-
throw
|
|
273
|
+
throw unsupportedLocalContent("poll");
|
|
253
274
|
default:
|
|
254
|
-
throw
|
|
275
|
+
throw unsupportedLocalContent(content.type);
|
|
255
276
|
}
|
|
256
277
|
};
|
|
257
278
|
var getMessage = async (_client, _id) => void 0;
|
|
258
279
|
|
|
259
|
-
// src/providers/imessage/
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Reaction
|
|
264
|
-
} from "@photon-ai/advanced-imessage";
|
|
280
|
+
// src/providers/imessage/local/api.ts
|
|
281
|
+
var messages2 = (client) => messages(client);
|
|
282
|
+
var send2 = async (client, spaceId, content) => send(client, spaceId, content);
|
|
283
|
+
var getMessage2 = async (client, id) => getMessage(client, id);
|
|
265
284
|
|
|
266
|
-
// src/
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
"M4B ",
|
|
274
|
-
"M4P ",
|
|
275
|
-
"mp42",
|
|
276
|
-
"mp41",
|
|
277
|
-
"isom",
|
|
278
|
-
"iso2"
|
|
279
|
-
]);
|
|
280
|
-
var M4A_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
281
|
-
"audio/mp4",
|
|
282
|
-
"audio/mp4a-latm",
|
|
283
|
-
"audio/x-m4a",
|
|
284
|
-
"audio/aac",
|
|
285
|
-
"audio/aacp"
|
|
286
|
-
]);
|
|
287
|
-
var FFMPEG_MISSING_MESSAGE = "voice content: input is not m4a/aac and ffmpeg is unavailable. Install `ffmpeg-static` or ensure `ffmpeg` is on PATH.";
|
|
288
|
-
var isM4a = (buffer) => {
|
|
289
|
-
if (buffer.length < 12) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
if (buffer.toString("ascii", 4, 8) !== "ftyp") {
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
return M4A_BRANDS.has(buffer.toString("ascii", 8, 12));
|
|
296
|
-
};
|
|
297
|
-
var isM4aMimeType = (mimeType) => M4A_MIME_TYPES.has(mimeType.toLowerCase());
|
|
298
|
-
var cachedFfmpegPath;
|
|
299
|
-
var tryStaticBinary = async () => {
|
|
300
|
-
try {
|
|
301
|
-
const mod = await import("ffmpeg-static");
|
|
302
|
-
return mod.default ?? void 0;
|
|
303
|
-
} catch {
|
|
304
|
-
return void 0;
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
var resolveFfmpegPath = async () => {
|
|
308
|
-
if (cachedFfmpegPath) {
|
|
309
|
-
return cachedFfmpegPath;
|
|
310
|
-
}
|
|
311
|
-
cachedFfmpegPath = await tryStaticBinary() ?? "ffmpeg";
|
|
312
|
-
return cachedFfmpegPath;
|
|
313
|
-
};
|
|
314
|
-
var collectStream = (stream2) => {
|
|
315
|
-
if (!stream2) {
|
|
316
|
-
return Promise.resolve("");
|
|
317
|
-
}
|
|
318
|
-
return new Promise((resolve, reject) => {
|
|
319
|
-
const chunks = [];
|
|
320
|
-
stream2.on("data", (chunk) => {
|
|
321
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
322
|
-
});
|
|
323
|
-
stream2.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
324
|
-
stream2.on("error", reject);
|
|
325
|
-
});
|
|
326
|
-
};
|
|
327
|
-
var isMissingBinaryError = (err) => err?.code === "ENOENT";
|
|
328
|
-
var runFfmpeg = (ffmpegPath, args) => {
|
|
329
|
-
const proc = spawn(ffmpegPath, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
330
|
-
const stderr = collectStream(proc.stderr);
|
|
331
|
-
const exit = new Promise((resolve, reject) => {
|
|
332
|
-
proc.on(
|
|
333
|
-
"error",
|
|
334
|
-
(err) => reject(
|
|
335
|
-
isMissingBinaryError(err) ? new Error(FFMPEG_MISSING_MESSAGE) : err
|
|
336
|
-
)
|
|
337
|
-
);
|
|
338
|
-
proc.on("exit", (code) => resolve(code ?? -1));
|
|
339
|
-
});
|
|
340
|
-
return Promise.all([exit, stderr]).then(([code, text]) => ({
|
|
341
|
-
code,
|
|
342
|
-
stderr: text
|
|
343
|
-
}));
|
|
344
|
-
};
|
|
345
|
-
var DURATION_PATTERN = /Duration:\s*(\d+):(\d{2}):(\d{2})(?:\.(\d{1,3}))?/;
|
|
346
|
-
var parseDuration = (stderr) => {
|
|
347
|
-
const match = stderr.match(DURATION_PATTERN);
|
|
348
|
-
if (!match) {
|
|
349
|
-
return void 0;
|
|
350
|
-
}
|
|
351
|
-
const [, hh, mm, ss, frac] = match;
|
|
352
|
-
const seconds = Number(hh) * 3600 + Number(mm) * 60 + Number(ss) + Number(`0.${frac ?? 0}`);
|
|
353
|
-
return Number.isFinite(seconds) ? seconds : void 0;
|
|
354
|
-
};
|
|
355
|
-
var transcodeToM4a = async (buffer) => {
|
|
356
|
-
const ffmpeg = await resolveFfmpegPath();
|
|
357
|
-
const dir = await mkdtemp2(join2(tmpdir2(), "spectrum-voice-"));
|
|
358
|
-
const inPath = join2(dir, "in");
|
|
359
|
-
const outPath = join2(dir, "out.m4a");
|
|
360
|
-
try {
|
|
361
|
-
await writeFile2(inPath, buffer);
|
|
362
|
-
const { code, stderr } = await runFfmpeg(ffmpeg, [
|
|
363
|
-
"-y",
|
|
364
|
-
"-i",
|
|
365
|
-
inPath,
|
|
366
|
-
"-f",
|
|
367
|
-
"ipod",
|
|
368
|
-
"-c:a",
|
|
369
|
-
"aac",
|
|
370
|
-
outPath
|
|
371
|
-
]);
|
|
372
|
-
if (code !== 0) {
|
|
373
|
-
throw new Error(`ffmpeg conversion failed (exit ${code}): ${stderr}`);
|
|
374
|
-
}
|
|
375
|
-
const out = await readFile2(outPath);
|
|
376
|
-
return { buffer: out, duration: parseDuration(stderr) };
|
|
377
|
-
} finally {
|
|
378
|
-
await rm2(dir, { recursive: true, force: true }).catch(() => {
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
var ensureM4a = async (buffer, mimeType) => {
|
|
383
|
-
if (isM4aMimeType(mimeType) || isM4a(buffer)) {
|
|
384
|
-
return { buffer };
|
|
285
|
+
// src/providers/imessage/remote/client.ts
|
|
286
|
+
var REMOTE_CLIENT_MISSING = "No remote iMessage client available";
|
|
287
|
+
var firstRemoteClient = (clients) => clients[0];
|
|
288
|
+
var primaryRemoteClient = (clients) => {
|
|
289
|
+
const remote = firstRemoteClient(clients);
|
|
290
|
+
if (!remote) {
|
|
291
|
+
throw new Error(REMOTE_CLIENT_MISSING);
|
|
385
292
|
}
|
|
386
|
-
return
|
|
293
|
+
return remote;
|
|
387
294
|
};
|
|
388
295
|
|
|
296
|
+
// src/providers/imessage/remote/inbound.ts
|
|
297
|
+
import {
|
|
298
|
+
messageGuid,
|
|
299
|
+
NotFoundError
|
|
300
|
+
} from "@photon-ai/advanced-imessage";
|
|
301
|
+
|
|
389
302
|
// src/providers/imessage/cache.ts
|
|
390
303
|
var DEFAULT_MAX = 1e3;
|
|
391
304
|
var MessageCache = class {
|
|
@@ -509,73 +422,28 @@ var getPollCache = (owner) => {
|
|
|
509
422
|
return cache;
|
|
510
423
|
};
|
|
511
424
|
|
|
512
|
-
// src/providers/imessage/remote.ts
|
|
513
|
-
var
|
|
514
|
-
var
|
|
515
|
-
var
|
|
516
|
-
|
|
517
|
-
"
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
var unsupportedContent = (type, detail) => UnsupportedError.content(type, PLATFORM, detail);
|
|
521
|
-
var toSendResult = (receipt) => ({
|
|
522
|
-
id: receipt.guid,
|
|
523
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
524
|
-
});
|
|
525
|
-
var VCARD_MIME_TYPES2 = /* @__PURE__ */ new Set([
|
|
526
|
-
"text/vcard",
|
|
527
|
-
"text/x-vcard",
|
|
528
|
-
"text/directory",
|
|
529
|
-
"application/vcard",
|
|
530
|
-
"application/x-vcard"
|
|
531
|
-
]);
|
|
532
|
-
var isVCardAttachment2 = (mimeType, fileName) => {
|
|
533
|
-
if (mimeType && VCARD_MIME_TYPES2.has(mimeType.toLowerCase())) {
|
|
534
|
-
return true;
|
|
535
|
-
}
|
|
536
|
-
return Boolean(fileName?.toLowerCase().endsWith(".vcf"));
|
|
537
|
-
};
|
|
538
|
-
var EMOJI_TO_TAPBACK = {
|
|
539
|
-
"\u2764\uFE0F": Reaction.love,
|
|
540
|
-
"\u{1F44D}": Reaction.like,
|
|
541
|
-
"\u{1F44E}": Reaction.dislike,
|
|
542
|
-
"\u{1F602}": Reaction.laugh,
|
|
543
|
-
"\u203C\uFE0F": Reaction.emphasize,
|
|
544
|
-
"\u2753": Reaction.question
|
|
545
|
-
};
|
|
546
|
-
var TAPBACK_TO_EMOJI = Object.fromEntries(
|
|
547
|
-
Object.entries(EMOJI_TO_TAPBACK).map(([emoji, kind]) => [kind, emoji])
|
|
548
|
-
);
|
|
549
|
-
var TAPBACK_CODE_TO_KIND = {
|
|
550
|
-
"2000": Reaction.love,
|
|
551
|
-
"2001": Reaction.like,
|
|
552
|
-
"2002": Reaction.dislike,
|
|
553
|
-
"2003": Reaction.laugh,
|
|
554
|
-
"2004": Reaction.emphasize,
|
|
555
|
-
"2005": Reaction.question,
|
|
556
|
-
"2006": Reaction.emoji,
|
|
557
|
-
"2007": Reaction.sticker
|
|
425
|
+
// src/providers/imessage/remote/ids.ts
|
|
426
|
+
var PART_PREFIX = /^p:(\d+)\//;
|
|
427
|
+
var formatChildId = (partIndex, parentGuid) => `p:${partIndex}/${parentGuid}`;
|
|
428
|
+
var parseTapbackTarget = (target) => {
|
|
429
|
+
const match = target.match(PART_PREFIX);
|
|
430
|
+
const guid = target.replace(PART_PREFIX, "");
|
|
431
|
+
const partIndex = match ? Number(match[1]) : 0;
|
|
432
|
+
return { guid, partIndex };
|
|
558
433
|
};
|
|
559
|
-
var
|
|
560
|
-
|
|
561
|
-
if (
|
|
562
|
-
return emoji;
|
|
563
|
-
}
|
|
564
|
-
if (!type) {
|
|
434
|
+
var parseChildId = (id) => {
|
|
435
|
+
const match = id.match(PART_PREFIX);
|
|
436
|
+
if (!match) {
|
|
565
437
|
return null;
|
|
566
438
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const direct = message.associatedMessageType;
|
|
572
|
-
if (typeof direct === "string") {
|
|
573
|
-
return direct;
|
|
574
|
-
}
|
|
575
|
-
const raw = message._raw;
|
|
576
|
-
const fromRaw = raw?.associatedMessageType;
|
|
577
|
-
return typeof fromRaw === "string" ? fromRaw : void 0;
|
|
439
|
+
return {
|
|
440
|
+
parentGuid: id.replace(PART_PREFIX, ""),
|
|
441
|
+
partIndex: Number(match[1])
|
|
442
|
+
};
|
|
578
443
|
};
|
|
444
|
+
|
|
445
|
+
// src/providers/imessage/remote/inbound.ts
|
|
446
|
+
var URL_BALLOON_BUNDLE_ID = "com.apple.messages.URLBalloonProvider";
|
|
579
447
|
var getBalloonBundleId = (message) => {
|
|
580
448
|
const raw = message._raw;
|
|
581
449
|
const id = raw?.balloonBundleId;
|
|
@@ -589,6 +457,31 @@ var resolveChatGuid = (message, hint) => {
|
|
|
589
457
|
return first ?? "";
|
|
590
458
|
};
|
|
591
459
|
var resolveSenderId = (message) => message.sender?.address ?? "";
|
|
460
|
+
var isIMessageMessage = (value) => {
|
|
461
|
+
if (typeof value !== "object" || value === null) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
const record = value;
|
|
465
|
+
return typeof record.id === "string" && record.id.length > 0 && typeof record.content === "object" && record.content !== null && typeof record.sender === "object" && record.sender !== null && typeof record.space === "object" && record.space !== null;
|
|
466
|
+
};
|
|
467
|
+
var asProviderGroup = (items) => groupSchema.parse({ type: "group", items });
|
|
468
|
+
var buildMessageBase = (message, chatGuidHint, timestamp) => {
|
|
469
|
+
const chat = resolveChatGuid(message, chatGuidHint);
|
|
470
|
+
return {
|
|
471
|
+
sender: { id: resolveSenderId(message) },
|
|
472
|
+
space: {
|
|
473
|
+
id: chat,
|
|
474
|
+
type: chat.includes(";+;") ? "group" : "dm"
|
|
475
|
+
},
|
|
476
|
+
timestamp
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
var receivedEventFromMessage = (message) => ({
|
|
480
|
+
chatGuid: resolveChatGuid(message, void 0),
|
|
481
|
+
message,
|
|
482
|
+
timestamp: message.dateCreated ?? /* @__PURE__ */ new Date(),
|
|
483
|
+
type: "message.received"
|
|
484
|
+
});
|
|
592
485
|
var toAttachmentContent2 = (client, info) => asAttachment({
|
|
593
486
|
name: info.fileName,
|
|
594
487
|
mimeType: info.mimeType,
|
|
@@ -600,22 +493,15 @@ var toVCardContent2 = async (client, info) => {
|
|
|
600
493
|
try {
|
|
601
494
|
const buf = Buffer.from(await client.attachments.downloadBuffer(info.guid));
|
|
602
495
|
return asContact(fromVCard(buf.toString("utf8")));
|
|
603
|
-
} catch {
|
|
496
|
+
} catch (err) {
|
|
497
|
+
console.warn(
|
|
498
|
+
"[spectrum-ts][imessage] failed to parse vCard attachment; falling back to attachment content",
|
|
499
|
+
{ error: err, guid: info.guid }
|
|
500
|
+
);
|
|
604
501
|
return toAttachmentContent2(client, info);
|
|
605
502
|
}
|
|
606
503
|
};
|
|
607
|
-
var attachmentContent = async (client, info) =>
|
|
608
|
-
var baseShape = (message, chatGuidHint, timestamp) => {
|
|
609
|
-
const chat = resolveChatGuid(message, chatGuidHint);
|
|
610
|
-
return {
|
|
611
|
-
sender: { id: resolveSenderId(message) },
|
|
612
|
-
space: {
|
|
613
|
-
id: chat,
|
|
614
|
-
type: chat.includes(";+;") ? "group" : "dm"
|
|
615
|
-
},
|
|
616
|
-
timestamp
|
|
617
|
-
};
|
|
618
|
-
};
|
|
504
|
+
var attachmentContent = async (client, info) => isVCardAttachment(info.mimeType, info.fileName) ? await toVCardContent2(client, info) : toAttachmentContent2(client, info);
|
|
619
505
|
var buildAttachmentMessage = async (client, base, info, id, partIndex, parentId) => {
|
|
620
506
|
const content = await attachmentContent(client, info);
|
|
621
507
|
const msg = { ...base, id, content, partIndex };
|
|
@@ -624,13 +510,29 @@ var buildAttachmentMessage = async (client, base, info, id, partIndex, parentId)
|
|
|
624
510
|
}
|
|
625
511
|
return msg;
|
|
626
512
|
};
|
|
627
|
-
var
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
513
|
+
var toRichlinkMessage = (message, base, id) => {
|
|
514
|
+
const url = message.text ?? "";
|
|
515
|
+
try {
|
|
516
|
+
return { ...base, id, content: asRichlink({ url }) };
|
|
517
|
+
} catch (err) {
|
|
518
|
+
console.warn(
|
|
519
|
+
"[spectrum-ts][imessage] failed to convert message to rich link; falling back to text/custom content",
|
|
520
|
+
{ error: err, message, url }
|
|
521
|
+
);
|
|
522
|
+
return {
|
|
523
|
+
...base,
|
|
524
|
+
id,
|
|
525
|
+
content: url ? asText(url) : asCustom(message)
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
var rebuildFromAppleMessage = async (client, message, chatGuidHint) => {
|
|
530
|
+
const messageGuidStr = message.guid;
|
|
531
|
+
const timestamp = message.dateCreated ?? /* @__PURE__ */ new Date();
|
|
532
|
+
const base = buildMessageBase(message, chatGuidHint, timestamp);
|
|
533
|
+
if (message.attachments.length === 1) {
|
|
534
|
+
const info = message.attachments[0];
|
|
535
|
+
if (!info) {
|
|
634
536
|
throw new Error("Unreachable: attachments.length === 1 but no element");
|
|
635
537
|
}
|
|
636
538
|
return buildAttachmentMessage(client, base, info, messageGuidStr, 0);
|
|
@@ -656,22 +558,13 @@ var rebuildFromAppleMessage = async (client, message, chatGuidHint) => {
|
|
|
656
558
|
return {
|
|
657
559
|
...base,
|
|
658
560
|
id: messageGuidStr,
|
|
659
|
-
content:
|
|
561
|
+
content: asProviderGroup(items)
|
|
660
562
|
};
|
|
661
563
|
}
|
|
662
|
-
const text = message.text;
|
|
663
564
|
if (getBalloonBundleId(message) === URL_BALLOON_BUNDLE_ID) {
|
|
664
|
-
|
|
665
|
-
try {
|
|
666
|
-
return { ...base, id: messageGuidStr, content: asRichlink({ url }) };
|
|
667
|
-
} catch {
|
|
668
|
-
return {
|
|
669
|
-
...base,
|
|
670
|
-
id: messageGuidStr,
|
|
671
|
-
content: url ? asText(url) : asCustom(message)
|
|
672
|
-
};
|
|
673
|
-
}
|
|
565
|
+
return toRichlinkMessage(message, base, messageGuidStr);
|
|
674
566
|
}
|
|
567
|
+
const text = message.text;
|
|
675
568
|
return {
|
|
676
569
|
...base,
|
|
677
570
|
id: messageGuidStr,
|
|
@@ -682,45 +575,166 @@ var cacheMessage = (cache, message) => {
|
|
|
682
575
|
cache.set(message.id, message);
|
|
683
576
|
if (message.content.type === "group") {
|
|
684
577
|
for (const item of message.content.items) {
|
|
685
|
-
|
|
578
|
+
if (isIMessageMessage(item)) {
|
|
579
|
+
cache.set(item.id, item);
|
|
580
|
+
}
|
|
686
581
|
}
|
|
687
582
|
}
|
|
688
583
|
};
|
|
689
|
-
var
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
584
|
+
var toInboundMessages = async (client, cache, event) => {
|
|
585
|
+
const base = buildMessageBase(event.message, event.chatGuid, event.timestamp);
|
|
586
|
+
const messageGuidStr = event.message.guid;
|
|
587
|
+
if (getBalloonBundleId(event.message) === URL_BALLOON_BUNDLE_ID) {
|
|
588
|
+
const msg2 = toRichlinkMessage(event.message, base, messageGuidStr);
|
|
589
|
+
cacheMessage(cache, msg2);
|
|
590
|
+
return [msg2];
|
|
591
|
+
}
|
|
592
|
+
if (event.message.attachments.length === 1) {
|
|
593
|
+
const info = event.message.attachments[0];
|
|
594
|
+
if (!info) {
|
|
595
|
+
throw new Error("Unreachable: attachments.length === 1 but no element");
|
|
596
|
+
}
|
|
597
|
+
const msg2 = await buildAttachmentMessage(
|
|
598
|
+
client,
|
|
599
|
+
base,
|
|
600
|
+
info,
|
|
601
|
+
messageGuidStr,
|
|
602
|
+
0
|
|
603
|
+
);
|
|
604
|
+
cacheMessage(cache, msg2);
|
|
605
|
+
return [msg2];
|
|
606
|
+
}
|
|
607
|
+
if (event.message.attachments.length > 1) {
|
|
608
|
+
const items = [];
|
|
609
|
+
for (let i = 0; i < event.message.attachments.length; i++) {
|
|
610
|
+
const info = event.message.attachments[i];
|
|
611
|
+
if (!info) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
items.push(
|
|
615
|
+
await buildAttachmentMessage(
|
|
616
|
+
client,
|
|
617
|
+
base,
|
|
618
|
+
info,
|
|
619
|
+
formatChildId(i, messageGuidStr),
|
|
620
|
+
i,
|
|
621
|
+
messageGuidStr
|
|
622
|
+
)
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
const parent = {
|
|
695
626
|
...base,
|
|
696
|
-
id,
|
|
697
|
-
content:
|
|
627
|
+
id: messageGuidStr,
|
|
628
|
+
content: asProviderGroup(items)
|
|
698
629
|
};
|
|
630
|
+
cacheMessage(cache, parent);
|
|
631
|
+
return [parent];
|
|
699
632
|
}
|
|
633
|
+
const text = event.message.text;
|
|
634
|
+
const msg = {
|
|
635
|
+
...base,
|
|
636
|
+
id: messageGuidStr,
|
|
637
|
+
content: text ? asText(text) : asCustom(event.message)
|
|
638
|
+
};
|
|
639
|
+
cacheMessage(cache, msg);
|
|
640
|
+
return [msg];
|
|
700
641
|
};
|
|
701
|
-
var
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
642
|
+
var getMessage3 = async (remote, spaceId, msgId) => {
|
|
643
|
+
const cache = getMessageCache(remote);
|
|
644
|
+
const cached = cache.get(msgId);
|
|
645
|
+
if (cached) {
|
|
646
|
+
return cached;
|
|
647
|
+
}
|
|
648
|
+
const childRef = parseChildId(msgId);
|
|
649
|
+
if (childRef) {
|
|
650
|
+
try {
|
|
651
|
+
const fetched = await remote.messages.get(
|
|
652
|
+
messageGuid(childRef.parentGuid)
|
|
653
|
+
);
|
|
654
|
+
const parent = await rebuildFromAppleMessage(remote, fetched, spaceId);
|
|
655
|
+
cacheMessage(cache, parent);
|
|
656
|
+
if (parent.content.type !== "group") {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const item = parent.content.items[childRef.partIndex];
|
|
660
|
+
return isIMessageMessage(item) ? item : void 0;
|
|
661
|
+
} catch (err) {
|
|
662
|
+
if (err instanceof NotFoundError) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
throw err;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
try {
|
|
669
|
+
const fetched = await remote.messages.get(messageGuid(msgId));
|
|
670
|
+
const rebuilt = await rebuildFromAppleMessage(remote, fetched, spaceId);
|
|
671
|
+
cacheMessage(cache, rebuilt);
|
|
672
|
+
return rebuilt;
|
|
673
|
+
} catch (err) {
|
|
674
|
+
if (err instanceof NotFoundError) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
throw err;
|
|
678
|
+
}
|
|
708
679
|
};
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
680
|
+
|
|
681
|
+
// src/providers/imessage/remote/reactions.ts
|
|
682
|
+
import {
|
|
683
|
+
chatGuid,
|
|
684
|
+
messageGuid as messageGuid2,
|
|
685
|
+
Reaction
|
|
686
|
+
} from "@photon-ai/advanced-imessage";
|
|
687
|
+
var EMOJI_TO_TAPBACK = {
|
|
688
|
+
"\u2764\uFE0F": Reaction.love,
|
|
689
|
+
"\u{1F44D}": Reaction.like,
|
|
690
|
+
"\u{1F44E}": Reaction.dislike,
|
|
691
|
+
"\u{1F602}": Reaction.laugh,
|
|
692
|
+
"\u203C\uFE0F": Reaction.emphasize,
|
|
693
|
+
"\u2753": Reaction.question
|
|
694
|
+
};
|
|
695
|
+
var TAPBACK_TO_EMOJI = Object.fromEntries(
|
|
696
|
+
Object.entries(EMOJI_TO_TAPBACK).map(([emoji, kind]) => [kind, emoji])
|
|
697
|
+
);
|
|
698
|
+
var TAPBACK_CODE_TO_KIND = {
|
|
699
|
+
"2000": Reaction.love,
|
|
700
|
+
"2001": Reaction.like,
|
|
701
|
+
"2002": Reaction.dislike,
|
|
702
|
+
"2003": Reaction.laugh,
|
|
703
|
+
"2004": Reaction.emphasize,
|
|
704
|
+
"2005": Reaction.question,
|
|
705
|
+
"2006": Reaction.emoji,
|
|
706
|
+
"2007": Reaction.sticker
|
|
707
|
+
};
|
|
708
|
+
var isTapbackRemoval = (code) => code.startsWith("3");
|
|
709
|
+
var resolveReactionEmoji = (type, emoji) => {
|
|
710
|
+
if (emoji) {
|
|
711
|
+
return emoji;
|
|
712
|
+
}
|
|
713
|
+
if (!type) {
|
|
712
714
|
return null;
|
|
713
715
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
716
|
+
const kind = TAPBACK_CODE_TO_KIND[type] ?? type;
|
|
717
|
+
return TAPBACK_TO_EMOJI[kind] ?? null;
|
|
718
|
+
};
|
|
719
|
+
var getAssociatedMessageType = (message) => {
|
|
720
|
+
const direct = message.associatedMessageType;
|
|
721
|
+
if (typeof direct === "string") {
|
|
722
|
+
return direct;
|
|
723
|
+
}
|
|
724
|
+
const raw = message._raw;
|
|
725
|
+
const fromRaw = raw?.associatedMessageType;
|
|
726
|
+
return typeof fromRaw === "string" ? fromRaw : void 0;
|
|
718
727
|
};
|
|
728
|
+
var asProviderReaction = (emoji, target) => reactionSchema.parse({
|
|
729
|
+
emoji,
|
|
730
|
+
target,
|
|
731
|
+
type: "reaction"
|
|
732
|
+
});
|
|
719
733
|
var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
|
|
720
734
|
let candidate = cache.get(strippedGuid);
|
|
721
735
|
if (!candidate) {
|
|
722
736
|
try {
|
|
723
|
-
const fetched = await client.messages.get(
|
|
737
|
+
const fetched = await client.messages.get(messageGuid2(strippedGuid));
|
|
724
738
|
candidate = await rebuildFromAppleMessage(client, fetched);
|
|
725
739
|
cacheMessage(cache, candidate);
|
|
726
740
|
} catch {
|
|
@@ -728,12 +742,16 @@ var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
|
|
|
728
742
|
}
|
|
729
743
|
}
|
|
730
744
|
if (candidate.content.type === "group") {
|
|
731
|
-
const
|
|
732
|
-
|
|
745
|
+
const items = candidate.content.items;
|
|
746
|
+
if (!Array.isArray(items)) {
|
|
747
|
+
return candidate;
|
|
748
|
+
}
|
|
749
|
+
const item = items[partIndex];
|
|
750
|
+
return isIMessageMessage(item) ? item : candidate;
|
|
733
751
|
}
|
|
734
752
|
return candidate;
|
|
735
753
|
};
|
|
736
|
-
var
|
|
754
|
+
var toReactionMessages = async (client, cache, event, target) => {
|
|
737
755
|
const type = getAssociatedMessageType(event.message);
|
|
738
756
|
if (type && isTapbackRemoval(type)) {
|
|
739
757
|
return [];
|
|
@@ -755,76 +773,640 @@ var toReactionMessage = async (client, cache, event, base, id, target) => {
|
|
|
755
773
|
if (!resolved) {
|
|
756
774
|
return [];
|
|
757
775
|
}
|
|
776
|
+
const messageId = event.message.guid;
|
|
777
|
+
if (typeof messageId !== "string" || messageId.length === 0) {
|
|
778
|
+
return [];
|
|
779
|
+
}
|
|
780
|
+
const base = buildMessageBase(event.message, event.chatGuid, event.timestamp);
|
|
758
781
|
return [
|
|
759
782
|
{
|
|
760
783
|
...base,
|
|
761
|
-
id,
|
|
762
|
-
content:
|
|
784
|
+
id: messageId,
|
|
785
|
+
content: asProviderReaction(emoji, resolved)
|
|
763
786
|
}
|
|
764
787
|
];
|
|
765
788
|
};
|
|
766
|
-
var
|
|
767
|
-
const
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
789
|
+
var reactToMessage = async (remote, spaceId, target, reaction) => {
|
|
790
|
+
const chat = chatGuid(spaceId);
|
|
791
|
+
const parentGuid = target.parentId ?? target.id;
|
|
792
|
+
const guid = messageGuid2(parentGuid);
|
|
793
|
+
const opts = typeof target.partIndex === "number" ? { partIndex: target.partIndex } : void 0;
|
|
794
|
+
const native = EMOJI_TO_TAPBACK[reaction];
|
|
795
|
+
if (native) {
|
|
796
|
+
await remote.messages.react(chat, guid, native, opts);
|
|
797
|
+
} else {
|
|
798
|
+
await remote.messages.reactEmoji(chat, guid, reaction, opts);
|
|
772
799
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// src/providers/imessage/remote/send.ts
|
|
803
|
+
import {
|
|
804
|
+
chatGuid as chatGuid2,
|
|
805
|
+
messageGuid as messageGuid3
|
|
806
|
+
} from "@photon-ai/advanced-imessage";
|
|
807
|
+
|
|
808
|
+
// src/utils/audio.ts
|
|
809
|
+
import { spawn } from "child_process";
|
|
810
|
+
import { mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
811
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
812
|
+
import { join as join2 } from "path";
|
|
813
|
+
var M4A_BRANDS = /* @__PURE__ */ new Set([
|
|
814
|
+
"M4A ",
|
|
815
|
+
"M4B ",
|
|
816
|
+
"M4P ",
|
|
817
|
+
"mp42",
|
|
818
|
+
"mp41",
|
|
819
|
+
"isom",
|
|
820
|
+
"iso2"
|
|
821
|
+
]);
|
|
822
|
+
var M4A_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
823
|
+
"audio/mp4",
|
|
824
|
+
"audio/mp4a-latm",
|
|
825
|
+
"audio/x-m4a",
|
|
826
|
+
"audio/aac",
|
|
827
|
+
"audio/aacp"
|
|
828
|
+
]);
|
|
829
|
+
var FFMPEG_MISSING_MESSAGE = "voice content: input is not m4a/aac and ffmpeg is unavailable. Install `ffmpeg-static` or ensure `ffmpeg` is on PATH.";
|
|
830
|
+
var isM4a = (buffer) => {
|
|
831
|
+
if (buffer.length < 12) {
|
|
832
|
+
return false;
|
|
777
833
|
}
|
|
778
|
-
if (
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
834
|
+
if (buffer.toString("ascii", 4, 8) !== "ftyp") {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
return M4A_BRANDS.has(buffer.toString("ascii", 8, 12));
|
|
838
|
+
};
|
|
839
|
+
var isM4aMimeType = (mimeType) => M4A_MIME_TYPES.has(mimeType.toLowerCase());
|
|
840
|
+
var cachedFfmpegPath;
|
|
841
|
+
var tryStaticBinary = async () => {
|
|
842
|
+
try {
|
|
843
|
+
const mod = await import("ffmpeg-static");
|
|
844
|
+
return mod.default ?? void 0;
|
|
845
|
+
} catch {
|
|
846
|
+
return void 0;
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
var resolveFfmpegPath = async () => {
|
|
850
|
+
if (cachedFfmpegPath) {
|
|
851
|
+
return cachedFfmpegPath;
|
|
852
|
+
}
|
|
853
|
+
cachedFfmpegPath = await tryStaticBinary() ?? "ffmpeg";
|
|
854
|
+
return cachedFfmpegPath;
|
|
855
|
+
};
|
|
856
|
+
var collectStream = (stream2) => {
|
|
857
|
+
if (!stream2) {
|
|
858
|
+
return Promise.resolve("");
|
|
859
|
+
}
|
|
860
|
+
return new Promise((resolve, reject) => {
|
|
861
|
+
const chunks = [];
|
|
862
|
+
stream2.on("data", (chunk) => {
|
|
863
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
864
|
+
});
|
|
865
|
+
stream2.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
866
|
+
stream2.on("error", reject);
|
|
867
|
+
});
|
|
868
|
+
};
|
|
869
|
+
var isMissingBinaryError = (err) => err?.code === "ENOENT";
|
|
870
|
+
var runFfmpeg = (ffmpegPath, args) => {
|
|
871
|
+
const proc = spawn(ffmpegPath, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
872
|
+
const stderr = collectStream(proc.stderr);
|
|
873
|
+
const exit = new Promise((resolve, reject) => {
|
|
874
|
+
proc.on(
|
|
875
|
+
"error",
|
|
876
|
+
(err) => reject(
|
|
877
|
+
isMissingBinaryError(err) ? new Error(FFMPEG_MISSING_MESSAGE) : err
|
|
878
|
+
)
|
|
879
|
+
);
|
|
880
|
+
proc.on("exit", (code) => resolve(code ?? -1));
|
|
881
|
+
});
|
|
882
|
+
return Promise.all([exit, stderr]).then(([code, text]) => ({
|
|
883
|
+
code,
|
|
884
|
+
stderr: text
|
|
885
|
+
}));
|
|
886
|
+
};
|
|
887
|
+
var DURATION_PATTERN = /Duration:\s*(\d+):(\d{2}):(\d{2})(?:\.(\d{1,3}))?/;
|
|
888
|
+
var parseDuration = (stderr) => {
|
|
889
|
+
const match = stderr.match(DURATION_PATTERN);
|
|
890
|
+
if (!match) {
|
|
891
|
+
return void 0;
|
|
892
|
+
}
|
|
893
|
+
const [, hh, mm, ss, frac] = match;
|
|
894
|
+
const seconds = Number(hh) * 3600 + Number(mm) * 60 + Number(ss) + Number(`0.${frac ?? 0}`);
|
|
895
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
896
|
+
};
|
|
897
|
+
var transcodeToM4a = async (buffer) => {
|
|
898
|
+
const ffmpeg = await resolveFfmpegPath();
|
|
899
|
+
const dir = await mkdtemp2(join2(tmpdir2(), "spectrum-voice-"));
|
|
900
|
+
const inPath = join2(dir, "in");
|
|
901
|
+
const outPath = join2(dir, "out.m4a");
|
|
902
|
+
try {
|
|
903
|
+
await writeFile2(inPath, buffer);
|
|
904
|
+
const { code, stderr } = await runFfmpeg(ffmpeg, [
|
|
905
|
+
"-y",
|
|
906
|
+
"-i",
|
|
907
|
+
inPath,
|
|
908
|
+
"-f",
|
|
909
|
+
"ipod",
|
|
910
|
+
"-c:a",
|
|
911
|
+
"aac",
|
|
912
|
+
outPath
|
|
913
|
+
]);
|
|
914
|
+
if (code !== 0) {
|
|
915
|
+
throw new Error(`ffmpeg conversion failed (exit ${code}): ${stderr}`);
|
|
916
|
+
}
|
|
917
|
+
const out = await readFile2(outPath);
|
|
918
|
+
return { buffer: out, duration: parseDuration(stderr) };
|
|
919
|
+
} finally {
|
|
920
|
+
await rm2(dir, { recursive: true, force: true }).catch(() => {
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
var ensureM4a = async (buffer, mimeType) => {
|
|
925
|
+
if (isM4aMimeType(mimeType) || isM4a(buffer)) {
|
|
926
|
+
return { buffer };
|
|
927
|
+
}
|
|
928
|
+
return transcodeToM4a(buffer);
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
// src/providers/imessage/remote/send.ts
|
|
932
|
+
var GROUP_ITEM_ALLOWED = /* @__PURE__ */ new Set([
|
|
933
|
+
"text",
|
|
934
|
+
"attachment",
|
|
935
|
+
"contact",
|
|
936
|
+
"voice"
|
|
937
|
+
]);
|
|
938
|
+
var MAX_GROUP_TEXT_ITEMS = 1;
|
|
939
|
+
var toDate = (value) => {
|
|
940
|
+
if (value instanceof Date) {
|
|
941
|
+
return value;
|
|
942
|
+
}
|
|
943
|
+
if (typeof value === "number" || typeof value === "string") {
|
|
944
|
+
const date = new Date(value);
|
|
945
|
+
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
var receiptTimestamp = (receipt) => toDate(receipt.timestamp) ?? toDate(receipt.date) ?? toDate(receipt.dateCreated) ?? /* @__PURE__ */ new Date();
|
|
949
|
+
var receiptGuid = (receipt) => {
|
|
950
|
+
if (typeof receipt.guid !== "string" || receipt.guid.length === 0) {
|
|
951
|
+
throw new Error("iMessage send receipt is missing a message guid");
|
|
952
|
+
}
|
|
953
|
+
return receipt.guid;
|
|
954
|
+
};
|
|
955
|
+
var outboundRecord = (spaceId, id, content, timestamp, extras) => ({
|
|
956
|
+
id,
|
|
957
|
+
content,
|
|
958
|
+
space: { id: spaceId },
|
|
959
|
+
timestamp,
|
|
960
|
+
...extras
|
|
961
|
+
});
|
|
962
|
+
var withReply = (options, replyTo) => replyTo ? { ...options, replyTo } : options;
|
|
963
|
+
var replyOptions = (replyTo) => replyTo ? { replyTo } : void 0;
|
|
964
|
+
var sendVCardAttachment = (remote, name, vcf) => remote.attachments.upload({
|
|
965
|
+
data: Buffer.from(vcf, "utf8"),
|
|
966
|
+
fileName: name,
|
|
967
|
+
mimeType: "text/vcard"
|
|
968
|
+
});
|
|
969
|
+
var sendContactAttachment = async (remote, content) => {
|
|
970
|
+
const vcf = await toVCard(content);
|
|
971
|
+
const name = vcardFileName(content);
|
|
972
|
+
const upload = await sendVCardAttachment(remote, name, vcf);
|
|
973
|
+
return { guid: upload.guid, name };
|
|
974
|
+
};
|
|
975
|
+
var uploadAttachment = async (remote, content) => {
|
|
976
|
+
const attachment = await remote.attachments.upload({
|
|
977
|
+
data: await content.read(),
|
|
978
|
+
fileName: content.name,
|
|
979
|
+
mimeType: content.mimeType
|
|
980
|
+
});
|
|
981
|
+
return { guid: attachment.guid, name: content.name };
|
|
982
|
+
};
|
|
983
|
+
var uploadVoice = async (remote, content) => {
|
|
984
|
+
const { buffer } = await ensureM4a(await content.read(), content.mimeType);
|
|
985
|
+
const name = content.name ?? "voice.m4a";
|
|
986
|
+
const attachment = await remote.attachments.upload({
|
|
987
|
+
data: buffer,
|
|
988
|
+
fileName: name,
|
|
989
|
+
mimeType: "audio/x-m4a"
|
|
990
|
+
});
|
|
991
|
+
return { guid: attachment.guid, name };
|
|
992
|
+
};
|
|
993
|
+
var sendContent = async (remote, spaceId, chat, content, replyTo) => {
|
|
994
|
+
switch (content.type) {
|
|
995
|
+
case "text": {
|
|
996
|
+
const receipt = await remote.messages.send(
|
|
997
|
+
chat,
|
|
998
|
+
content.text,
|
|
999
|
+
withReply({}, replyTo)
|
|
1000
|
+
);
|
|
1001
|
+
return outboundRecord(
|
|
1002
|
+
spaceId,
|
|
1003
|
+
receiptGuid(receipt),
|
|
1004
|
+
content,
|
|
1005
|
+
receiptTimestamp(receipt)
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
case "richlink": {
|
|
1009
|
+
const receipt = await remote.messages.send(
|
|
1010
|
+
chat,
|
|
1011
|
+
content.url,
|
|
1012
|
+
withReply({ richLink: true }, replyTo)
|
|
1013
|
+
);
|
|
1014
|
+
return outboundRecord(
|
|
1015
|
+
spaceId,
|
|
1016
|
+
receiptGuid(receipt),
|
|
1017
|
+
content,
|
|
1018
|
+
receiptTimestamp(receipt)
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
case "attachment": {
|
|
1022
|
+
const { guid } = await uploadAttachment(remote, content);
|
|
1023
|
+
const receipt = await remote.messages.send(chat, "", {
|
|
1024
|
+
attachment: guid,
|
|
1025
|
+
...replyOptions(replyTo)
|
|
1026
|
+
});
|
|
1027
|
+
return outboundRecord(
|
|
1028
|
+
spaceId,
|
|
1029
|
+
receiptGuid(receipt),
|
|
1030
|
+
content,
|
|
1031
|
+
receiptTimestamp(receipt)
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
case "contact": {
|
|
1035
|
+
const { guid } = await sendContactAttachment(remote, content);
|
|
1036
|
+
const receipt = await remote.messages.send(chat, "", {
|
|
1037
|
+
attachment: guid,
|
|
1038
|
+
...replyOptions(replyTo)
|
|
1039
|
+
});
|
|
1040
|
+
return outboundRecord(
|
|
1041
|
+
spaceId,
|
|
1042
|
+
receiptGuid(receipt),
|
|
1043
|
+
content,
|
|
1044
|
+
receiptTimestamp(receipt)
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
case "voice": {
|
|
1048
|
+
const { guid } = await uploadVoice(remote, content);
|
|
1049
|
+
const receipt = await remote.messages.send(chat, "", {
|
|
1050
|
+
attachment: guid,
|
|
1051
|
+
audioMessage: true,
|
|
1052
|
+
...replyOptions(replyTo)
|
|
1053
|
+
});
|
|
1054
|
+
return outboundRecord(
|
|
1055
|
+
spaceId,
|
|
1056
|
+
receiptGuid(receipt),
|
|
1057
|
+
content,
|
|
1058
|
+
receiptTimestamp(receipt)
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
case "poll":
|
|
1062
|
+
if (replyTo) {
|
|
1063
|
+
throw unsupportedRemoteContent(
|
|
1064
|
+
"poll",
|
|
1065
|
+
"polls cannot be sent as replies"
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
return outboundRecord(
|
|
1069
|
+
spaceId,
|
|
1070
|
+
receiptGuid(
|
|
1071
|
+
await remote.polls.create(
|
|
1072
|
+
chat,
|
|
1073
|
+
content.title,
|
|
1074
|
+
content.options.map((option) => option.title)
|
|
1075
|
+
)
|
|
1076
|
+
),
|
|
1077
|
+
content,
|
|
1078
|
+
/* @__PURE__ */ new Date()
|
|
1079
|
+
);
|
|
1080
|
+
default:
|
|
1081
|
+
throw unsupportedRemoteContent(content.type);
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
var validateGroupContent = (content) => {
|
|
1085
|
+
let textCount = 0;
|
|
1086
|
+
for (const sub of content.items) {
|
|
1087
|
+
const itemType = sub.content.type;
|
|
1088
|
+
if (!GROUP_ITEM_ALLOWED.has(itemType)) {
|
|
1089
|
+
throw unsupportedRemoteContent(
|
|
1090
|
+
"group",
|
|
1091
|
+
`"${itemType}" items are not supported inside a group`
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
if (itemType === "text" && ++textCount > MAX_GROUP_TEXT_ITEMS) {
|
|
1095
|
+
throw unsupportedRemoteContent(
|
|
1096
|
+
"group",
|
|
1097
|
+
`groups can contain at most ${MAX_GROUP_TEXT_ITEMS} text item`
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
var resolvePart = async (remote, content) => {
|
|
1103
|
+
switch (content.type) {
|
|
1104
|
+
case "text":
|
|
1105
|
+
return { text: content.text };
|
|
1106
|
+
case "attachment": {
|
|
1107
|
+
const { guid, name } = await uploadAttachment(remote, content);
|
|
1108
|
+
return { attachmentGuid: guid, attachmentName: name };
|
|
1109
|
+
}
|
|
1110
|
+
case "contact": {
|
|
1111
|
+
const { guid, name } = await sendContactAttachment(remote, content);
|
|
1112
|
+
return { attachmentGuid: guid, attachmentName: name };
|
|
1113
|
+
}
|
|
1114
|
+
case "voice": {
|
|
1115
|
+
const { guid, name } = await uploadVoice(remote, content);
|
|
1116
|
+
return { attachmentGuid: guid, attachmentName: name };
|
|
1117
|
+
}
|
|
1118
|
+
default:
|
|
1119
|
+
throw unsupportedRemoteContent(content.type);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
var send3 = async (remote, spaceId, content) => {
|
|
1123
|
+
const chat = chatGuid2(spaceId);
|
|
1124
|
+
if (content.type === "group") {
|
|
1125
|
+
validateGroupContent(content);
|
|
1126
|
+
const resolved = await Promise.all(
|
|
1127
|
+
content.items.map((sub) => resolvePart(remote, sub.content))
|
|
1128
|
+
);
|
|
1129
|
+
const receipt = await remote.messages.sendMultipart(
|
|
1130
|
+
chat,
|
|
1131
|
+
resolved.map((part, idx) => ({ ...part, partIndex: idx }))
|
|
1132
|
+
);
|
|
1133
|
+
const parentGuid = receiptGuid(receipt);
|
|
1134
|
+
const timestamp = receiptTimestamp(receipt);
|
|
1135
|
+
const items = content.items.map(
|
|
1136
|
+
(sub, idx) => outboundRecord(
|
|
1137
|
+
spaceId,
|
|
1138
|
+
formatChildId(idx, parentGuid),
|
|
1139
|
+
sub.content,
|
|
1140
|
+
timestamp,
|
|
1141
|
+
{ partIndex: idx, parentId: parentGuid }
|
|
1142
|
+
)
|
|
1143
|
+
);
|
|
1144
|
+
return outboundRecord(
|
|
1145
|
+
spaceId,
|
|
1146
|
+
parentGuid,
|
|
1147
|
+
asGroup({ items }),
|
|
1148
|
+
timestamp
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
return sendContent(remote, spaceId, chat, content);
|
|
1152
|
+
};
|
|
1153
|
+
var replyToMessage = async (remote, spaceId, msgId, content) => {
|
|
1154
|
+
const chat = chatGuid2(spaceId);
|
|
1155
|
+
const replyTo = messageGuid3(msgId);
|
|
1156
|
+
return sendContent(remote, spaceId, chat, content, replyTo);
|
|
1157
|
+
};
|
|
1158
|
+
var editMessage = async (remote, spaceId, msgId, content) => {
|
|
1159
|
+
if (content.type !== "text") {
|
|
1160
|
+
throw unsupportedRemoteContent(
|
|
1161
|
+
content.type,
|
|
1162
|
+
"only text content can be edited"
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
await remote.messages.edit(
|
|
1166
|
+
chatGuid2(spaceId),
|
|
1167
|
+
messageGuid3(msgId),
|
|
1168
|
+
content.text
|
|
1169
|
+
);
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
// src/providers/imessage/remote/stream.ts
|
|
1173
|
+
import {
|
|
1174
|
+
AuthenticationError,
|
|
1175
|
+
IMessageError,
|
|
1176
|
+
NotFoundError as NotFoundError2,
|
|
1177
|
+
ValidationError
|
|
1178
|
+
} from "@photon-ai/advanced-imessage";
|
|
1179
|
+
|
|
1180
|
+
// src/utils/resumable-stream.ts
|
|
1181
|
+
var CATCH_UP_PAGE_SIZE = 100;
|
|
1182
|
+
var MAX_BUFFERED_LIVE_EVENTS = 1e3;
|
|
1183
|
+
var RECONNECT_INITIAL_DELAY_MS = 500;
|
|
1184
|
+
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
1185
|
+
var RetryableStreamError = class extends Error {
|
|
1186
|
+
constructor(message) {
|
|
1187
|
+
super(message);
|
|
1188
|
+
this.name = "RetryableStreamError";
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
var LiveBufferOverflowError = class extends RetryableStreamError {
|
|
1192
|
+
constructor(limit) {
|
|
1193
|
+
super(`Live stream buffer exceeded ${limit} events during catch-up`);
|
|
1194
|
+
this.name = "LiveBufferOverflowError";
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
var closeIterable = async (iterable) => {
|
|
1198
|
+
if (!iterable) {
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
await iterable.close?.();
|
|
1202
|
+
};
|
|
1203
|
+
var jitterDelay = (delayMs) => Math.random() * delayMs;
|
|
1204
|
+
var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
1205
|
+
const catchUpPageSize = options.catchUpPageSize ?? CATCH_UP_PAGE_SIZE;
|
|
1206
|
+
const bufferLimit = options.bufferLimit ?? MAX_BUFFERED_LIVE_EVENTS;
|
|
1207
|
+
const initialRetryDelayMs = options.initialRetryDelayMs ?? RECONNECT_INITIAL_DELAY_MS;
|
|
1208
|
+
const maxRetryDelayMs = options.maxRetryDelayMs ?? RECONNECT_MAX_DELAY_MS;
|
|
1209
|
+
let activeLive;
|
|
1210
|
+
let closed = false;
|
|
1211
|
+
let lastCursor;
|
|
1212
|
+
let retryDelayMs = initialRetryDelayMs;
|
|
1213
|
+
let sleepTimer;
|
|
1214
|
+
let wakeSleep;
|
|
1215
|
+
const deliveredSinceCursor = /* @__PURE__ */ new Set();
|
|
1216
|
+
const resetRetryDelay = () => {
|
|
1217
|
+
retryDelayMs = initialRetryDelayMs;
|
|
1218
|
+
};
|
|
1219
|
+
const advanceCursor = (cursor, clearDelivered) => {
|
|
1220
|
+
if (!cursor || cursor === lastCursor) {
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
lastCursor = cursor;
|
|
1224
|
+
if (clearDelivered) {
|
|
1225
|
+
deliveredSinceCursor.clear();
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
const deliverItem = async (item, resetRetry, clearOnCursorAdvance) => {
|
|
1229
|
+
const alreadyDelivered = deliveredSinceCursor.has(item.id);
|
|
1230
|
+
if (!alreadyDelivered) {
|
|
1231
|
+
for (const value of item.values) {
|
|
1232
|
+
await emit(value);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
advanceCursor(item.cursor, clearOnCursorAdvance);
|
|
1236
|
+
deliveredSinceCursor.add(item.id);
|
|
1237
|
+
if (resetRetry) {
|
|
1238
|
+
resetRetryDelay();
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
const retryable = (error) => error instanceof RetryableStreamError || options.isRetryableError(error);
|
|
1242
|
+
const sleep = async (delayMs) => {
|
|
1243
|
+
if (delayMs <= 0 || closed) {
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
await new Promise((resolve) => {
|
|
1247
|
+
wakeSleep = resolve;
|
|
1248
|
+
sleepTimer = setTimeout(resolve, jitterDelay(delayMs));
|
|
1249
|
+
});
|
|
1250
|
+
sleepTimer = void 0;
|
|
1251
|
+
wakeSleep = void 0;
|
|
1252
|
+
};
|
|
1253
|
+
const cancelSleep = () => {
|
|
1254
|
+
if (sleepTimer) {
|
|
1255
|
+
clearTimeout(sleepTimer);
|
|
1256
|
+
sleepTimer = void 0;
|
|
1257
|
+
}
|
|
1258
|
+
wakeSleep?.();
|
|
1259
|
+
wakeSleep = void 0;
|
|
1260
|
+
};
|
|
1261
|
+
const nextRetryDelay = () => {
|
|
1262
|
+
const delay = retryDelayMs;
|
|
1263
|
+
retryDelayMs = Math.min(retryDelayMs * 2, maxRetryDelayMs);
|
|
1264
|
+
return delay;
|
|
1265
|
+
};
|
|
1266
|
+
const consumeLive = async () => {
|
|
1267
|
+
const live = options.subscribeLive();
|
|
1268
|
+
activeLive = live;
|
|
1269
|
+
try {
|
|
1270
|
+
for await (const event of live) {
|
|
1271
|
+
await deliverItem(await options.processLive(event), true, true);
|
|
1272
|
+
}
|
|
1273
|
+
throw new RetryableStreamError("Live stream ended");
|
|
1274
|
+
} finally {
|
|
1275
|
+
if (activeLive === live) {
|
|
1276
|
+
activeLive = void 0;
|
|
1277
|
+
}
|
|
1278
|
+
await closeIterable(live);
|
|
782
1279
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1280
|
+
};
|
|
1281
|
+
const throwLiveError = (liveError) => {
|
|
1282
|
+
if (liveError) {
|
|
1283
|
+
throw liveError;
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
const bufferLiveEvent = (buffer, event) => {
|
|
1287
|
+
if (buffer.length >= bufferLimit) {
|
|
1288
|
+
throw new LiveBufferOverflowError(bufferLimit);
|
|
1289
|
+
}
|
|
1290
|
+
buffer.push(event);
|
|
1291
|
+
};
|
|
1292
|
+
const startLivePump = (live, isBuffering, liveBuffer) => {
|
|
1293
|
+
let liveError;
|
|
1294
|
+
const pump2 = (async () => {
|
|
1295
|
+
try {
|
|
1296
|
+
for await (const event of live) {
|
|
1297
|
+
if (isBuffering()) {
|
|
1298
|
+
bufferLiveEvent(liveBuffer, event);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
await deliverItem(await options.processLive(event), true, true);
|
|
1302
|
+
}
|
|
1303
|
+
throw new RetryableStreamError("Live stream ended");
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
liveError = error;
|
|
799
1306
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1307
|
+
})();
|
|
1308
|
+
return {
|
|
1309
|
+
getError: () => liveError,
|
|
1310
|
+
pump: pump2
|
|
1311
|
+
};
|
|
1312
|
+
};
|
|
1313
|
+
const replayMissed = async (cursor, getLiveError) => {
|
|
1314
|
+
for await (const event of options.fetchMissed(cursor, {
|
|
1315
|
+
limit: catchUpPageSize
|
|
1316
|
+
})) {
|
|
1317
|
+
throwLiveError(getLiveError());
|
|
1318
|
+
await deliverItem(await options.processMissed(event), false, false);
|
|
1319
|
+
}
|
|
1320
|
+
throwLiveError(getLiveError());
|
|
1321
|
+
};
|
|
1322
|
+
const flushLiveBuffer = async (liveBuffer, getLiveError) => {
|
|
1323
|
+
let index = 0;
|
|
1324
|
+
let lastFlushedId;
|
|
1325
|
+
while (index < liveBuffer.length) {
|
|
1326
|
+
throwLiveError(getLiveError());
|
|
1327
|
+
const event = liveBuffer[index];
|
|
1328
|
+
if (event === void 0) {
|
|
1329
|
+
throw new RetryableStreamError("Live stream buffer index missing");
|
|
1330
|
+
}
|
|
1331
|
+
const item = await options.processLive(event);
|
|
1332
|
+
await deliverItem(item, true, false);
|
|
1333
|
+
lastFlushedId = item.id;
|
|
1334
|
+
index += 1;
|
|
1335
|
+
}
|
|
1336
|
+
liveBuffer.length = 0;
|
|
1337
|
+
throwLiveError(getLiveError());
|
|
1338
|
+
return lastFlushedId;
|
|
1339
|
+
};
|
|
1340
|
+
const compactDeliveredIds = (lastId) => {
|
|
1341
|
+
if (!lastId) {
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
deliveredSinceCursor.clear();
|
|
1345
|
+
deliveredSinceCursor.add(lastId);
|
|
1346
|
+
};
|
|
1347
|
+
const catchUpThenConsumeLive = async (cursor) => {
|
|
1348
|
+
const live = options.subscribeLive();
|
|
1349
|
+
activeLive = live;
|
|
1350
|
+
let buffering = true;
|
|
1351
|
+
const liveBuffer = [];
|
|
1352
|
+
const livePump = startLivePump(live, () => buffering, liveBuffer);
|
|
1353
|
+
try {
|
|
1354
|
+
await replayMissed(cursor, livePump.getError);
|
|
1355
|
+
const lastFlushedId = await flushLiveBuffer(
|
|
1356
|
+
liveBuffer,
|
|
1357
|
+
livePump.getError
|
|
809
1358
|
);
|
|
1359
|
+
compactDeliveredIds(lastFlushedId);
|
|
1360
|
+
buffering = false;
|
|
1361
|
+
resetRetryDelay();
|
|
1362
|
+
await livePump.pump;
|
|
1363
|
+
throwLiveError(livePump.getError());
|
|
1364
|
+
} finally {
|
|
1365
|
+
buffering = false;
|
|
1366
|
+
if (activeLive === live) {
|
|
1367
|
+
activeLive = void 0;
|
|
1368
|
+
}
|
|
1369
|
+
await closeIterable(live);
|
|
1370
|
+
await livePump.pump.catch(() => void 0);
|
|
810
1371
|
}
|
|
811
|
-
const parent = {
|
|
812
|
-
...base,
|
|
813
|
-
id: messageGuidStr,
|
|
814
|
-
content: asGroup({ items })
|
|
815
|
-
};
|
|
816
|
-
cacheMessage(cache, parent);
|
|
817
|
-
return [parent];
|
|
818
|
-
}
|
|
819
|
-
const text = event.message.text;
|
|
820
|
-
const msg = {
|
|
821
|
-
...base,
|
|
822
|
-
id: messageGuidStr,
|
|
823
|
-
content: text ? asText(text) : asCustom(event.message)
|
|
824
1372
|
};
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1373
|
+
const run = async () => {
|
|
1374
|
+
while (!closed) {
|
|
1375
|
+
try {
|
|
1376
|
+
if (lastCursor) {
|
|
1377
|
+
await catchUpThenConsumeLive(lastCursor);
|
|
1378
|
+
} else {
|
|
1379
|
+
await consumeLive();
|
|
1380
|
+
}
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
await closeIterable(activeLive);
|
|
1383
|
+
activeLive = void 0;
|
|
1384
|
+
if (closed) {
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
if (!retryable(error)) {
|
|
1388
|
+
end(error);
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
await sleep(nextRetryDelay());
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
end();
|
|
1395
|
+
};
|
|
1396
|
+
const pump = run().catch((error) => {
|
|
1397
|
+
if (!closed) {
|
|
1398
|
+
end(error);
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
return async () => {
|
|
1402
|
+
closed = true;
|
|
1403
|
+
cancelSleep();
|
|
1404
|
+
await closeIterable(activeLive);
|
|
1405
|
+
await pump;
|
|
1406
|
+
};
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
// src/providers/imessage/remote/polls.ts
|
|
828
1410
|
var isVotedPollEvent = (event) => event.delta.type === "voted";
|
|
829
1411
|
var isUnvotedPollEvent = (event) => event.delta.type === "unvoted";
|
|
830
1412
|
var toCachedPoll = (input) => {
|
|
@@ -908,7 +1490,7 @@ var buildPollOptionMessage = (input) => {
|
|
|
908
1490
|
};
|
|
909
1491
|
};
|
|
910
1492
|
var buildPollOptionMessages = (input) => {
|
|
911
|
-
const
|
|
1493
|
+
const messages5 = [];
|
|
912
1494
|
for (const delta of input.deltas) {
|
|
913
1495
|
const message = buildPollOptionMessage({
|
|
914
1496
|
cached: input.cached,
|
|
@@ -919,10 +1501,10 @@ var buildPollOptionMessages = (input) => {
|
|
|
919
1501
|
senderAddress: input.senderAddress
|
|
920
1502
|
});
|
|
921
1503
|
if (message) {
|
|
922
|
-
|
|
1504
|
+
messages5.push(message);
|
|
923
1505
|
}
|
|
924
1506
|
}
|
|
925
|
-
return
|
|
1507
|
+
return messages5;
|
|
926
1508
|
};
|
|
927
1509
|
var allOptionIdsKnown = (cached, optionIds) => optionIds.every((optionId) => cached.optionsByIdentifier.has(optionId));
|
|
928
1510
|
var refreshPollMetadata = async (client, pollCache, event, fallbackOptionIds) => {
|
|
@@ -977,7 +1559,7 @@ var toPollVoteMessages = async (client, pollCache, event) => {
|
|
|
977
1559
|
senderAddress,
|
|
978
1560
|
currentOptionIds
|
|
979
1561
|
);
|
|
980
|
-
const
|
|
1562
|
+
const messages5 = buildPollOptionMessages({
|
|
981
1563
|
cached: resolvedPoll,
|
|
982
1564
|
chatGuid: chatGuidStr,
|
|
983
1565
|
deltas,
|
|
@@ -990,7 +1572,7 @@ var toPollVoteMessages = async (client, pollCache, event) => {
|
|
|
990
1572
|
currentOptionIds,
|
|
991
1573
|
event.at
|
|
992
1574
|
);
|
|
993
|
-
return
|
|
1575
|
+
return messages5;
|
|
994
1576
|
};
|
|
995
1577
|
var toPollUnvoteMessages = async (client, pollCache, event) => {
|
|
996
1578
|
const senderAddress = event.actor.address;
|
|
@@ -1006,23 +1588,16 @@ var toPollUnvoteMessages = async (client, pollCache, event) => {
|
|
|
1006
1588
|
return [];
|
|
1007
1589
|
}
|
|
1008
1590
|
const chatGuidStr = event.chatGuid;
|
|
1009
|
-
const messages3 = [];
|
|
1010
1591
|
const deltas = pollCache.clearedActorSelectionDeltas(pollId, senderAddress);
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
senderAddress
|
|
1019
|
-
});
|
|
1020
|
-
if (message) {
|
|
1021
|
-
messages3.push(message);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1592
|
+
const messages5 = buildPollOptionMessages({
|
|
1593
|
+
cached,
|
|
1594
|
+
chatGuid: chatGuidStr,
|
|
1595
|
+
deltas,
|
|
1596
|
+
event,
|
|
1597
|
+
senderAddress
|
|
1598
|
+
});
|
|
1024
1599
|
pollCache.commitActorSelection(pollId, senderAddress, [], event.at);
|
|
1025
|
-
return
|
|
1600
|
+
return messages5;
|
|
1026
1601
|
};
|
|
1027
1602
|
var toPollDeltaMessages = async (client, pollCache, event) => {
|
|
1028
1603
|
if (isVotedPollEvent(event)) {
|
|
@@ -1033,294 +1608,156 @@ var toPollDeltaMessages = async (client, pollCache, event) => {
|
|
|
1033
1608
|
}
|
|
1034
1609
|
return [];
|
|
1035
1610
|
};
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1611
|
+
|
|
1612
|
+
// src/providers/imessage/remote/stream.ts
|
|
1613
|
+
var pollRetryDelay = (delayMs) => Math.random() * delayMs;
|
|
1614
|
+
var isRetryableIMessageStreamError = (error) => {
|
|
1615
|
+
if (error instanceof AuthenticationError || error instanceof NotFoundError2 || error instanceof ValidationError) {
|
|
1616
|
+
return false;
|
|
1617
|
+
}
|
|
1618
|
+
if (error instanceof IMessageError) {
|
|
1619
|
+
return true;
|
|
1620
|
+
}
|
|
1621
|
+
return false;
|
|
1622
|
+
};
|
|
1623
|
+
var toMessageItem = async (client, event, cursor) => {
|
|
1624
|
+
const id = event.message.guid;
|
|
1625
|
+
if (event.message.isFromMe) {
|
|
1626
|
+
return { cursor, id, values: [] };
|
|
1627
|
+
}
|
|
1039
1628
|
const cache = getMessageCache(client);
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
for await (const event of messageSub) {
|
|
1044
|
-
if (event.message.isFromMe) {
|
|
1045
|
-
continue;
|
|
1046
|
-
}
|
|
1047
|
-
for (const message of await toMessages2(client, cache, event)) {
|
|
1048
|
-
await emit(message);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
} catch (e) {
|
|
1052
|
-
end(e);
|
|
1053
|
-
}
|
|
1054
|
-
})();
|
|
1055
|
-
const pollPump = (async () => {
|
|
1056
|
-
try {
|
|
1057
|
-
for await (const event of pollSub) {
|
|
1058
|
-
cachePollEvent(pollCache, event);
|
|
1059
|
-
if (event.actor.isFromMe) {
|
|
1060
|
-
continue;
|
|
1061
|
-
}
|
|
1062
|
-
const messages3 = await toPollDeltaMessages(client, pollCache, event);
|
|
1063
|
-
for (const vote of messages3) {
|
|
1064
|
-
await emit(vote);
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
} catch (e) {
|
|
1068
|
-
console.error("[spectrum-ts][imessage][poll] stream failed", e);
|
|
1069
|
-
}
|
|
1070
|
-
})();
|
|
1071
|
-
return async () => {
|
|
1072
|
-
messageSub.close();
|
|
1073
|
-
pollSub.close();
|
|
1074
|
-
await Promise.all([messagePump, pollPump]);
|
|
1075
|
-
};
|
|
1076
|
-
});
|
|
1629
|
+
const target = event.message.associatedMessageGuid;
|
|
1630
|
+
const values = target ? await toReactionMessages(client, cache, event, target) : await toInboundMessages(client, cache, event);
|
|
1631
|
+
return { cursor, id, values };
|
|
1077
1632
|
};
|
|
1078
|
-
var
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1633
|
+
var messageStream = (client) => resumableOrderedStream({
|
|
1634
|
+
fetchMissed: (cursor, { limit }) => client.messages.fetchMissed(cursor, { limit }),
|
|
1635
|
+
isRetryableError: isRetryableIMessageStreamError,
|
|
1636
|
+
processLive: (event) => toMessageItem(client, event, event.cursor),
|
|
1637
|
+
processMissed: (message) => toMessageItem(client, receivedEventFromMessage(message)),
|
|
1638
|
+
subscribeLive: () => client.messages.subscribe("message.received")
|
|
1082
1639
|
});
|
|
1083
|
-
var
|
|
1084
|
-
|
|
1085
|
-
return `${base.replace(/[^a-zA-Z0-9_\-.]/g, "_")}.vcf`;
|
|
1086
|
-
};
|
|
1087
|
-
var sendContactAttachment = async (remote, content) => {
|
|
1088
|
-
const vcf = await toVCard(content);
|
|
1089
|
-
const upload = await sendVCardAttachment(remote, vcardFileName2(content), vcf);
|
|
1090
|
-
return upload.guid;
|
|
1091
|
-
};
|
|
1092
|
-
var messages2 = (clients) => {
|
|
1093
|
-
const pollCache = getPollCache(clients);
|
|
1094
|
-
return mergeStreams(clients.map((client) => clientStream(client, pollCache)));
|
|
1640
|
+
var logPollStreamError = (error) => {
|
|
1641
|
+
console.error("[spectrum-ts][imessage][poll] stream failed", error);
|
|
1095
1642
|
};
|
|
1096
|
-
var
|
|
1097
|
-
|
|
1098
|
-
if (
|
|
1643
|
+
var emitPollMessages = async (client, pollCache, event, emit) => {
|
|
1644
|
+
cachePollEvent(pollCache, event);
|
|
1645
|
+
if (event.actor.isFromMe) {
|
|
1099
1646
|
return;
|
|
1100
1647
|
}
|
|
1101
|
-
await
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
const remote = clients[0];
|
|
1105
|
-
if (!remote) {
|
|
1106
|
-
return;
|
|
1648
|
+
const messages5 = await toPollDeltaMessages(client, pollCache, event);
|
|
1649
|
+
for (const vote of messages5) {
|
|
1650
|
+
await emit(vote);
|
|
1107
1651
|
}
|
|
1108
|
-
await remote.chats.stopTyping(chatGuid(spaceId));
|
|
1109
1652
|
};
|
|
1110
|
-
var
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
case "richlink":
|
|
1115
|
-
return toSendResult(
|
|
1116
|
-
await remote.messages.send(chat, content.url, { richLink: true })
|
|
1117
|
-
);
|
|
1118
|
-
case "attachment": {
|
|
1119
|
-
const attachment = await remote.attachments.upload({
|
|
1120
|
-
data: await content.read(),
|
|
1121
|
-
fileName: content.name,
|
|
1122
|
-
mimeType: content.mimeType
|
|
1123
|
-
});
|
|
1124
|
-
return toSendResult(
|
|
1125
|
-
await remote.messages.send(chat, "", { attachment: attachment.guid })
|
|
1126
|
-
);
|
|
1127
|
-
}
|
|
1128
|
-
case "contact": {
|
|
1129
|
-
const attachment = await sendContactAttachment(remote, content);
|
|
1130
|
-
return toSendResult(await remote.messages.send(chat, "", { attachment }));
|
|
1131
|
-
}
|
|
1132
|
-
case "voice": {
|
|
1133
|
-
const { buffer } = await ensureM4a(
|
|
1134
|
-
await content.read(),
|
|
1135
|
-
content.mimeType
|
|
1136
|
-
);
|
|
1137
|
-
const attachment = await remote.attachments.upload({
|
|
1138
|
-
data: buffer,
|
|
1139
|
-
fileName: content.name ?? "voice.m4a",
|
|
1140
|
-
mimeType: "audio/x-m4a"
|
|
1141
|
-
});
|
|
1142
|
-
return toSendResult(
|
|
1143
|
-
await remote.messages.send(chat, "", {
|
|
1144
|
-
attachment: attachment.guid,
|
|
1145
|
-
audioMessage: true
|
|
1146
|
-
})
|
|
1147
|
-
);
|
|
1148
|
-
}
|
|
1149
|
-
case "poll":
|
|
1150
|
-
return toSendResult(
|
|
1151
|
-
await remote.polls.create(
|
|
1152
|
-
chat,
|
|
1153
|
-
content.title,
|
|
1154
|
-
content.options.map((o) => o.title)
|
|
1155
|
-
)
|
|
1156
|
-
);
|
|
1157
|
-
default:
|
|
1158
|
-
throw unsupportedContent(content.type);
|
|
1653
|
+
var runPollSubscription = async (client, pollCache, subscription, emit, onEvent) => {
|
|
1654
|
+
for await (const event of subscription) {
|
|
1655
|
+
onEvent();
|
|
1656
|
+
await emitPollMessages(client, pollCache, event, emit);
|
|
1159
1657
|
}
|
|
1160
1658
|
};
|
|
1161
|
-
var
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
if (!GROUP_ITEM_ALLOWED.has(itemType)) {
|
|
1171
|
-
throw unsupportedContent(
|
|
1172
|
-
"group",
|
|
1173
|
-
`"${itemType}" items are not supported inside a group`
|
|
1174
|
-
);
|
|
1175
|
-
}
|
|
1659
|
+
var pollStream = (client, pollCache) => stream((emit, end) => {
|
|
1660
|
+
let active = client.polls.subscribe();
|
|
1661
|
+
let closed = false;
|
|
1662
|
+
let retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
|
|
1663
|
+
let sleepTimer;
|
|
1664
|
+
let wakeSleep;
|
|
1665
|
+
const sleep = async (delayMs) => {
|
|
1666
|
+
if (closed) {
|
|
1667
|
+
return;
|
|
1176
1668
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1669
|
+
await new Promise((resolve) => {
|
|
1670
|
+
wakeSleep = resolve;
|
|
1671
|
+
sleepTimer = setTimeout(resolve, pollRetryDelay(delayMs));
|
|
1672
|
+
});
|
|
1673
|
+
sleepTimer = void 0;
|
|
1674
|
+
wakeSleep = void 0;
|
|
1675
|
+
};
|
|
1676
|
+
const cancelSleep = () => {
|
|
1677
|
+
if (sleepTimer) {
|
|
1678
|
+
clearTimeout(sleepTimer);
|
|
1679
|
+
sleepTimer = void 0;
|
|
1180
1680
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1681
|
+
wakeSleep?.();
|
|
1682
|
+
wakeSleep = void 0;
|
|
1683
|
+
};
|
|
1684
|
+
const pump = (async () => {
|
|
1685
|
+
while (!closed) {
|
|
1686
|
+
try {
|
|
1687
|
+
await runPollSubscription(client, pollCache, active, emit, () => {
|
|
1688
|
+
retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
|
|
1689
|
+
});
|
|
1690
|
+
} catch (e) {
|
|
1691
|
+
if (!closed) {
|
|
1692
|
+
logPollStreamError(e);
|
|
1693
|
+
}
|
|
1694
|
+
} finally {
|
|
1695
|
+
await active.close();
|
|
1696
|
+
}
|
|
1697
|
+
if (!closed) {
|
|
1698
|
+
await sleep(retryDelayMs);
|
|
1699
|
+
retryDelayMs = Math.min(retryDelayMs * 2, RECONNECT_MAX_DELAY_MS);
|
|
1700
|
+
active = client.polls.subscribe();
|
|
1701
|
+
}
|
|
1184
1702
|
}
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1187
|
-
return
|
|
1703
|
+
end();
|
|
1704
|
+
})();
|
|
1705
|
+
return async () => {
|
|
1706
|
+
closed = true;
|
|
1707
|
+
cancelSleep();
|
|
1708
|
+
await active.close();
|
|
1709
|
+
await pump;
|
|
1710
|
+
};
|
|
1711
|
+
});
|
|
1712
|
+
var clientStream = (client, pollCache) => {
|
|
1713
|
+
return mergeStreams([messageStream(client), pollStream(client, pollCache)]);
|
|
1188
1714
|
};
|
|
1189
|
-
var
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
throw new Error("No remote iMessage client available");
|
|
1193
|
-
}
|
|
1194
|
-
const chat = chatGuid(spaceId);
|
|
1195
|
-
const replyTo = messageGuid(msgId);
|
|
1196
|
-
switch (content.type) {
|
|
1197
|
-
case "text":
|
|
1198
|
-
return toSendResult(
|
|
1199
|
-
await remote.messages.send(chat, content.text, { replyTo })
|
|
1200
|
-
);
|
|
1201
|
-
case "richlink":
|
|
1202
|
-
return toSendResult(
|
|
1203
|
-
await remote.messages.send(chat, content.url, {
|
|
1204
|
-
richLink: true,
|
|
1205
|
-
replyTo
|
|
1206
|
-
})
|
|
1207
|
-
);
|
|
1208
|
-
case "attachment": {
|
|
1209
|
-
const attachment = await remote.attachments.upload({
|
|
1210
|
-
data: await content.read(),
|
|
1211
|
-
fileName: content.name,
|
|
1212
|
-
mimeType: content.mimeType
|
|
1213
|
-
});
|
|
1214
|
-
return toSendResult(
|
|
1215
|
-
await remote.messages.send(chat, "", {
|
|
1216
|
-
attachment: attachment.guid,
|
|
1217
|
-
replyTo
|
|
1218
|
-
})
|
|
1219
|
-
);
|
|
1220
|
-
}
|
|
1221
|
-
case "contact": {
|
|
1222
|
-
const attachment = await sendContactAttachment(remote, content);
|
|
1223
|
-
return toSendResult(
|
|
1224
|
-
await remote.messages.send(chat, "", { attachment, replyTo })
|
|
1225
|
-
);
|
|
1226
|
-
}
|
|
1227
|
-
case "voice": {
|
|
1228
|
-
const { buffer } = await ensureM4a(
|
|
1229
|
-
await content.read(),
|
|
1230
|
-
content.mimeType
|
|
1231
|
-
);
|
|
1232
|
-
const attachment = await remote.attachments.upload({
|
|
1233
|
-
data: buffer,
|
|
1234
|
-
fileName: content.name ?? "voice.m4a",
|
|
1235
|
-
mimeType: "audio/x-m4a"
|
|
1236
|
-
});
|
|
1237
|
-
return toSendResult(
|
|
1238
|
-
await remote.messages.send(chat, "", {
|
|
1239
|
-
attachment: attachment.guid,
|
|
1240
|
-
audioMessage: true,
|
|
1241
|
-
replyTo
|
|
1242
|
-
})
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
case "poll":
|
|
1246
|
-
throw UnsupportedError.content(
|
|
1247
|
-
"poll",
|
|
1248
|
-
PLATFORM,
|
|
1249
|
-
"polls cannot be sent as replies"
|
|
1250
|
-
);
|
|
1251
|
-
default:
|
|
1252
|
-
throw unsupportedContent(content.type);
|
|
1253
|
-
}
|
|
1715
|
+
var messages3 = (clients) => {
|
|
1716
|
+
const pollCache = getPollCache(clients);
|
|
1717
|
+
return mergeStreams(clients.map((client) => clientStream(client, pollCache)));
|
|
1254
1718
|
};
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1719
|
+
|
|
1720
|
+
// src/providers/imessage/remote/typing.ts
|
|
1721
|
+
import { chatGuid as chatGuid3 } from "@photon-ai/advanced-imessage";
|
|
1722
|
+
var startTyping = async (remote, spaceId) => {
|
|
1723
|
+
await remote.chats.startTyping(chatGuid3(spaceId));
|
|
1724
|
+
};
|
|
1725
|
+
var stopTyping = async (remote, spaceId) => {
|
|
1726
|
+
await remote.chats.stopTyping(chatGuid3(spaceId));
|
|
1727
|
+
};
|
|
1728
|
+
|
|
1729
|
+
// src/providers/imessage/remote/api.ts
|
|
1730
|
+
var messages4 = (clients) => messages3(clients);
|
|
1731
|
+
var startTyping2 = async (clients, spaceId) => {
|
|
1732
|
+
const remote = firstRemoteClient(clients);
|
|
1264
1733
|
if (!remote) {
|
|
1265
|
-
|
|
1734
|
+
return;
|
|
1266
1735
|
}
|
|
1267
|
-
await remote
|
|
1268
|
-
chatGuid(spaceId),
|
|
1269
|
-
messageGuid(msgId),
|
|
1270
|
-
content.text
|
|
1271
|
-
);
|
|
1736
|
+
await startTyping(remote, spaceId);
|
|
1272
1737
|
};
|
|
1273
|
-
var
|
|
1274
|
-
const remote = clients
|
|
1738
|
+
var stopTyping2 = async (clients, spaceId) => {
|
|
1739
|
+
const remote = firstRemoteClient(clients);
|
|
1275
1740
|
if (!remote) {
|
|
1276
1741
|
return;
|
|
1277
1742
|
}
|
|
1278
|
-
|
|
1279
|
-
const parentGuid = target.parentId ?? target.id;
|
|
1280
|
-
const guid = messageGuid(parentGuid);
|
|
1281
|
-
const opts = typeof target.partIndex === "number" ? { partIndex: target.partIndex } : void 0;
|
|
1282
|
-
const native = EMOJI_TO_TAPBACK[reaction];
|
|
1283
|
-
if (native) {
|
|
1284
|
-
await remote.messages.react(chat, guid, native, opts);
|
|
1285
|
-
} else {
|
|
1286
|
-
await remote.messages.reactEmoji(chat, guid, reaction, opts);
|
|
1287
|
-
}
|
|
1743
|
+
await stopTyping(remote, spaceId);
|
|
1288
1744
|
};
|
|
1289
|
-
var
|
|
1290
|
-
|
|
1745
|
+
var send4 = async (clients, spaceId, content) => send3(primaryRemoteClient(clients), spaceId, content);
|
|
1746
|
+
var replyToMessage2 = async (clients, spaceId, msgId, content) => replyToMessage(primaryRemoteClient(clients), spaceId, msgId, content);
|
|
1747
|
+
var editMessage2 = async (clients, spaceId, msgId, content) => editMessage(primaryRemoteClient(clients), spaceId, msgId, content);
|
|
1748
|
+
var reactToMessage2 = async (clients, spaceId, target, reaction) => {
|
|
1749
|
+
const remote = firstRemoteClient(clients);
|
|
1291
1750
|
if (!remote) {
|
|
1292
1751
|
return;
|
|
1293
1752
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
const childRef = parseChildId(msgId);
|
|
1300
|
-
if (childRef) {
|
|
1301
|
-
try {
|
|
1302
|
-
const fetched = await remote.messages.get(
|
|
1303
|
-
messageGuid(childRef.parentGuid)
|
|
1304
|
-
);
|
|
1305
|
-
const parent = await rebuildFromAppleMessage(remote, fetched, spaceId);
|
|
1306
|
-
cacheMessage(cache, parent);
|
|
1307
|
-
if (parent.content.type !== "group") {
|
|
1308
|
-
return;
|
|
1309
|
-
}
|
|
1310
|
-
const items = parent.content.items;
|
|
1311
|
-
return items[childRef.partIndex];
|
|
1312
|
-
} catch {
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
try {
|
|
1317
|
-
const fetched = await remote.messages.get(messageGuid(msgId));
|
|
1318
|
-
const rebuilt = await rebuildFromAppleMessage(remote, fetched, spaceId);
|
|
1319
|
-
cacheMessage(cache, rebuilt);
|
|
1320
|
-
return rebuilt;
|
|
1321
|
-
} catch {
|
|
1753
|
+
await reactToMessage(remote, spaceId, target, reaction);
|
|
1754
|
+
};
|
|
1755
|
+
var getMessage4 = async (clients, spaceId, msgId) => {
|
|
1756
|
+
const remote = firstRemoteClient(clients);
|
|
1757
|
+
if (!remote) {
|
|
1322
1758
|
return;
|
|
1323
1759
|
}
|
|
1760
|
+
return getMessage3(remote, spaceId, msgId);
|
|
1324
1761
|
};
|
|
1325
1762
|
|
|
1326
1763
|
// src/providers/imessage/types.ts
|
|
@@ -1346,6 +1783,7 @@ var messageSchema = z.object({
|
|
|
1346
1783
|
});
|
|
1347
1784
|
|
|
1348
1785
|
// src/providers/imessage/index.ts
|
|
1786
|
+
var isPollContent = (content) => content.type === "poll" || content.type === "poll_option";
|
|
1349
1787
|
var imessage = definePlatform("iMessage", {
|
|
1350
1788
|
config: configSchema,
|
|
1351
1789
|
user: {
|
|
@@ -1414,55 +1852,69 @@ var imessage = definePlatform("iMessage", {
|
|
|
1414
1852
|
}
|
|
1415
1853
|
},
|
|
1416
1854
|
events: {
|
|
1417
|
-
messages: ({ client }) => isLocal(client) ?
|
|
1855
|
+
messages: ({ client }) => isLocal(client) ? messages2(client) : messages4(client)
|
|
1418
1856
|
},
|
|
1419
1857
|
actions: {
|
|
1420
1858
|
send: async ({ space, content, client }) => {
|
|
1421
1859
|
if (isLocal(client)) {
|
|
1422
|
-
return await
|
|
1860
|
+
return await send2(client, space.id, content);
|
|
1423
1861
|
}
|
|
1424
|
-
return await
|
|
1862
|
+
return await send4(client, space.id, content);
|
|
1425
1863
|
},
|
|
1426
1864
|
startTyping: async ({ space, client }) => {
|
|
1427
1865
|
if (isLocal(client)) {
|
|
1428
1866
|
return;
|
|
1429
1867
|
}
|
|
1430
|
-
await
|
|
1868
|
+
await startTyping2(client, space.id);
|
|
1431
1869
|
},
|
|
1432
1870
|
stopTyping: async ({ space, client }) => {
|
|
1433
1871
|
if (isLocal(client)) {
|
|
1434
1872
|
return;
|
|
1435
1873
|
}
|
|
1436
|
-
await
|
|
1874
|
+
await stopTyping2(client, space.id);
|
|
1437
1875
|
},
|
|
1438
1876
|
reactToMessage: async ({ space, target, reaction, client }) => {
|
|
1439
1877
|
if (isLocal(client)) {
|
|
1440
1878
|
throw UnsupportedError.action("react", "iMessage (local mode)");
|
|
1441
1879
|
}
|
|
1442
|
-
|
|
1880
|
+
if (isPollContent(target.content)) {
|
|
1881
|
+
throw UnsupportedError.action(
|
|
1882
|
+
"react",
|
|
1883
|
+
"iMessage",
|
|
1884
|
+
"iMessage polls do not support reactions"
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
await reactToMessage2(
|
|
1443
1888
|
client,
|
|
1444
1889
|
space.id,
|
|
1445
1890
|
target,
|
|
1446
1891
|
reaction
|
|
1447
1892
|
);
|
|
1448
1893
|
},
|
|
1449
|
-
replyToMessage: async ({ space, messageId, content, client }) => {
|
|
1894
|
+
replyToMessage: async ({ space, messageId, target, content, client }) => {
|
|
1450
1895
|
if (isLocal(client)) {
|
|
1451
1896
|
throw UnsupportedError.action("reply", "iMessage (local mode)");
|
|
1452
1897
|
}
|
|
1453
|
-
|
|
1898
|
+
if (isPollContent(target.content)) {
|
|
1899
|
+
throw UnsupportedError.action(
|
|
1900
|
+
"reply",
|
|
1901
|
+
"iMessage",
|
|
1902
|
+
"iMessage polls do not support replies"
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1905
|
+
return await replyToMessage2(client, space.id, messageId, content);
|
|
1454
1906
|
},
|
|
1455
1907
|
editMessage: async ({ space, messageId, content, client }) => {
|
|
1456
1908
|
if (isLocal(client)) {
|
|
1457
1909
|
throw UnsupportedError.action("edit", "iMessage (local mode)");
|
|
1458
1910
|
}
|
|
1459
|
-
await
|
|
1911
|
+
await editMessage2(client, space.id, messageId, content);
|
|
1460
1912
|
},
|
|
1461
1913
|
getMessage: async ({ space, messageId, client }) => {
|
|
1462
1914
|
if (isLocal(client)) {
|
|
1463
|
-
return
|
|
1915
|
+
return getMessage2(client, messageId);
|
|
1464
1916
|
}
|
|
1465
|
-
return
|
|
1917
|
+
return getMessage4(client, space.id, messageId);
|
|
1466
1918
|
}
|
|
1467
1919
|
}
|
|
1468
1920
|
});
|