spectrum-ts 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-XMAI2AAN.js → chunk-7D6FHYKT.js} +51 -36
- package/dist/{chunk-7Q7KJKGL.js → chunk-7VSE6V3Q.js} +1 -1
- package/dist/{chunk-LAGNM6I7.js → chunk-TY3RT4OB.js} +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/providers/imessage/index.d.ts +1 -1
- package/dist/providers/imessage/index.js +990 -614
- package/dist/providers/terminal/index.d.ts +1 -1
- package/dist/providers/terminal/index.js +2 -2
- package/dist/providers/whatsapp-business/index.d.ts +1 -1
- package/dist/providers/whatsapp-business/index.js +1 -1
- package/dist/{types-DJQLFwWW.d.ts → types-BZhWdyLk.d.ts} +1 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "../../chunk-
|
|
2
|
+
asRichlink,
|
|
3
|
+
groupSchema
|
|
4
|
+
} from "../../chunk-TY3RT4OB.js";
|
|
5
5
|
import {
|
|
6
6
|
asPoll,
|
|
7
7
|
asPollOption,
|
|
@@ -14,12 +14,12 @@ import {
|
|
|
14
14
|
asAttachment,
|
|
15
15
|
asContact,
|
|
16
16
|
asCustom,
|
|
17
|
-
asReaction,
|
|
18
17
|
asText,
|
|
19
18
|
definePlatform,
|
|
20
19
|
fromVCard,
|
|
20
|
+
reactionSchema,
|
|
21
21
|
toVCard
|
|
22
|
-
} from "../../chunk-
|
|
22
|
+
} from "../../chunk-7D6FHYKT.js";
|
|
23
23
|
|
|
24
24
|
// src/providers/imessage/index.ts
|
|
25
25
|
import { createClient as createClient2, directChat } from "@photon-ai/advanced-imessage";
|
|
@@ -111,17 +111,12 @@ async function disposeCloudAuth(clients) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
// src/providers/imessage/local.ts
|
|
114
|
+
// src/providers/imessage/local/attachments.ts
|
|
115
115
|
import { createReadStream } from "fs";
|
|
116
|
-
import {
|
|
117
|
-
import { tmpdir } from "os";
|
|
118
|
-
import { basename, join } from "path";
|
|
116
|
+
import { readFile } from "fs/promises";
|
|
119
117
|
import { Readable } from "stream";
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
123
|
-
});
|
|
124
|
-
var DEFAULT_ATTACHMENT_NAME = "attachment";
|
|
118
|
+
|
|
119
|
+
// src/providers/imessage/shared/vcard.ts
|
|
125
120
|
var VCARD_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
126
121
|
"text/vcard",
|
|
127
122
|
"text/x-vcard",
|
|
@@ -136,6 +131,13 @@ var isVCardAttachment = (mimeType, fileName) => {
|
|
|
136
131
|
}
|
|
137
132
|
return Boolean(fileName?.toLowerCase().endsWith(".vcf"));
|
|
138
133
|
};
|
|
134
|
+
var vcardFileName = (contact) => {
|
|
135
|
+
const base = contact.name?.formatted ?? contact.user?.id ?? "contact";
|
|
136
|
+
return `${base.replace(/[^a-zA-Z0-9_\-.]/g, "_")}.vcf`;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/providers/imessage/local/attachments.ts
|
|
140
|
+
var DEFAULT_ATTACHMENT_NAME = "attachment";
|
|
139
141
|
var readLocalAttachment = async (att) => {
|
|
140
142
|
if (!att.localPath) {
|
|
141
143
|
throw new Error(
|
|
@@ -164,6 +166,9 @@ var toVCardContent = async (att) => {
|
|
|
164
166
|
return toAttachmentContent(att);
|
|
165
167
|
}
|
|
166
168
|
};
|
|
169
|
+
var localAttachmentContent = async (att) => isVCardAttachment(att.mimeType, att.fileName) ? await toVCardContent(att) : toAttachmentContent(att);
|
|
170
|
+
|
|
171
|
+
// src/providers/imessage/local/inbound.ts
|
|
167
172
|
var toMessages = async (message) => {
|
|
168
173
|
const { chatId, chatKind } = message;
|
|
169
174
|
if (!chatId || chatKind === "unknown") {
|
|
@@ -182,7 +187,7 @@ var toMessages = async (message) => {
|
|
|
182
187
|
message.attachments.map(async (att) => ({
|
|
183
188
|
...base,
|
|
184
189
|
id: `${message.id}:${att.id}`,
|
|
185
|
-
content:
|
|
190
|
+
content: await localAttachmentContent(att)
|
|
186
191
|
}))
|
|
187
192
|
);
|
|
188
193
|
}
|
|
@@ -214,10 +219,23 @@ var messages = (client) => stream((emit, end) => {
|
|
|
214
219
|
});
|
|
215
220
|
};
|
|
216
221
|
});
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
};
|
|
222
|
+
|
|
223
|
+
// src/providers/imessage/local/send.ts
|
|
224
|
+
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
225
|
+
import { tmpdir } from "os";
|
|
226
|
+
import { basename, join } from "path";
|
|
227
|
+
|
|
228
|
+
// src/providers/imessage/shared/errors.ts
|
|
229
|
+
var IMESSAGE_PLATFORM = "iMessage";
|
|
230
|
+
var LOCAL_IMESSAGE_PLATFORM = "iMessage (local mode)";
|
|
231
|
+
var unsupportedRemoteContent = (type, detail) => UnsupportedError.content(type, IMESSAGE_PLATFORM, detail);
|
|
232
|
+
var unsupportedLocalContent = (type) => UnsupportedError.content(type, LOCAL_IMESSAGE_PLATFORM);
|
|
233
|
+
|
|
234
|
+
// src/providers/imessage/local/send.ts
|
|
235
|
+
var synthSendResult = () => ({
|
|
236
|
+
id: crypto.randomUUID(),
|
|
237
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
238
|
+
});
|
|
221
239
|
var sendTempFile = async (client, spaceId, name, data) => {
|
|
222
240
|
const safeName = basename(name) || DEFAULT_ATTACHMENT_NAME;
|
|
223
241
|
const dir = await mkdtemp(join(tmpdir(), "spectrum-"));
|
|
@@ -249,143 +267,35 @@ var send = async (client, spaceId, content) => {
|
|
|
249
267
|
return synthSendResult();
|
|
250
268
|
}
|
|
251
269
|
case "poll":
|
|
252
|
-
throw
|
|
270
|
+
throw unsupportedLocalContent("poll");
|
|
253
271
|
default:
|
|
254
|
-
throw
|
|
272
|
+
throw unsupportedLocalContent(content.type);
|
|
255
273
|
}
|
|
256
274
|
};
|
|
257
275
|
var getMessage = async (_client, _id) => void 0;
|
|
258
276
|
|
|
259
|
-
// src/providers/imessage/
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Reaction
|
|
264
|
-
} from "@photon-ai/advanced-imessage";
|
|
277
|
+
// src/providers/imessage/local/api.ts
|
|
278
|
+
var messages2 = (client) => messages(client);
|
|
279
|
+
var send2 = async (client, spaceId, content) => send(client, spaceId, content);
|
|
280
|
+
var getMessage2 = async (client, id) => getMessage(client, id);
|
|
265
281
|
|
|
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 };
|
|
282
|
+
// src/providers/imessage/remote/client.ts
|
|
283
|
+
var REMOTE_CLIENT_MISSING = "No remote iMessage client available";
|
|
284
|
+
var firstRemoteClient = (clients) => clients[0];
|
|
285
|
+
var primaryRemoteClient = (clients) => {
|
|
286
|
+
const remote = firstRemoteClient(clients);
|
|
287
|
+
if (!remote) {
|
|
288
|
+
throw new Error(REMOTE_CLIENT_MISSING);
|
|
385
289
|
}
|
|
386
|
-
return
|
|
290
|
+
return remote;
|
|
387
291
|
};
|
|
388
292
|
|
|
293
|
+
// src/providers/imessage/remote/inbound.ts
|
|
294
|
+
import {
|
|
295
|
+
messageGuid,
|
|
296
|
+
NotFoundError
|
|
297
|
+
} from "@photon-ai/advanced-imessage";
|
|
298
|
+
|
|
389
299
|
// src/providers/imessage/cache.ts
|
|
390
300
|
var DEFAULT_MAX = 1e3;
|
|
391
301
|
var MessageCache = class {
|
|
@@ -509,73 +419,28 @@ var getPollCache = (owner) => {
|
|
|
509
419
|
return cache;
|
|
510
420
|
};
|
|
511
421
|
|
|
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
|
|
422
|
+
// src/providers/imessage/remote/ids.ts
|
|
423
|
+
var PART_PREFIX = /^p:(\d+)\//;
|
|
424
|
+
var formatChildId = (partIndex, parentGuid) => `p:${partIndex}/${parentGuid}`;
|
|
425
|
+
var parseTapbackTarget = (target) => {
|
|
426
|
+
const match = target.match(PART_PREFIX);
|
|
427
|
+
const guid = target.replace(PART_PREFIX, "");
|
|
428
|
+
const partIndex = match ? Number(match[1]) : 0;
|
|
429
|
+
return { guid, partIndex };
|
|
558
430
|
};
|
|
559
|
-
var
|
|
560
|
-
|
|
561
|
-
if (
|
|
562
|
-
return emoji;
|
|
563
|
-
}
|
|
564
|
-
if (!type) {
|
|
431
|
+
var parseChildId = (id) => {
|
|
432
|
+
const match = id.match(PART_PREFIX);
|
|
433
|
+
if (!match) {
|
|
565
434
|
return null;
|
|
566
435
|
}
|
|
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;
|
|
436
|
+
return {
|
|
437
|
+
parentGuid: id.replace(PART_PREFIX, ""),
|
|
438
|
+
partIndex: Number(match[1])
|
|
439
|
+
};
|
|
578
440
|
};
|
|
441
|
+
|
|
442
|
+
// src/providers/imessage/remote/inbound.ts
|
|
443
|
+
var URL_BALLOON_BUNDLE_ID = "com.apple.messages.URLBalloonProvider";
|
|
579
444
|
var getBalloonBundleId = (message) => {
|
|
580
445
|
const raw = message._raw;
|
|
581
446
|
const id = raw?.balloonBundleId;
|
|
@@ -589,6 +454,31 @@ var resolveChatGuid = (message, hint) => {
|
|
|
589
454
|
return first ?? "";
|
|
590
455
|
};
|
|
591
456
|
var resolveSenderId = (message) => message.sender?.address ?? "";
|
|
457
|
+
var isIMessageMessage = (value) => {
|
|
458
|
+
if (typeof value !== "object" || value === null) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
const record = value;
|
|
462
|
+
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;
|
|
463
|
+
};
|
|
464
|
+
var asProviderGroup = (items) => groupSchema.parse({ type: "group", items });
|
|
465
|
+
var buildMessageBase = (message, chatGuidHint, timestamp) => {
|
|
466
|
+
const chat = resolveChatGuid(message, chatGuidHint);
|
|
467
|
+
return {
|
|
468
|
+
sender: { id: resolveSenderId(message) },
|
|
469
|
+
space: {
|
|
470
|
+
id: chat,
|
|
471
|
+
type: chat.includes(";+;") ? "group" : "dm"
|
|
472
|
+
},
|
|
473
|
+
timestamp
|
|
474
|
+
};
|
|
475
|
+
};
|
|
476
|
+
var receivedEventFromMessage = (message) => ({
|
|
477
|
+
chatGuid: resolveChatGuid(message, void 0),
|
|
478
|
+
message,
|
|
479
|
+
timestamp: message.dateCreated ?? /* @__PURE__ */ new Date(),
|
|
480
|
+
type: "message.received"
|
|
481
|
+
});
|
|
592
482
|
var toAttachmentContent2 = (client, info) => asAttachment({
|
|
593
483
|
name: info.fileName,
|
|
594
484
|
mimeType: info.mimeType,
|
|
@@ -600,22 +490,15 @@ var toVCardContent2 = async (client, info) => {
|
|
|
600
490
|
try {
|
|
601
491
|
const buf = Buffer.from(await client.attachments.downloadBuffer(info.guid));
|
|
602
492
|
return asContact(fromVCard(buf.toString("utf8")));
|
|
603
|
-
} catch {
|
|
493
|
+
} catch (err) {
|
|
494
|
+
console.warn(
|
|
495
|
+
"[spectrum-ts][imessage] failed to parse vCard attachment; falling back to attachment content",
|
|
496
|
+
{ error: err, guid: info.guid }
|
|
497
|
+
);
|
|
604
498
|
return toAttachmentContent2(client, info);
|
|
605
499
|
}
|
|
606
500
|
};
|
|
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
|
-
};
|
|
501
|
+
var attachmentContent = async (client, info) => isVCardAttachment(info.mimeType, info.fileName) ? await toVCardContent2(client, info) : toAttachmentContent2(client, info);
|
|
619
502
|
var buildAttachmentMessage = async (client, base, info, id, partIndex, parentId) => {
|
|
620
503
|
const content = await attachmentContent(client, info);
|
|
621
504
|
const msg = { ...base, id, content, partIndex };
|
|
@@ -624,11 +507,27 @@ var buildAttachmentMessage = async (client, base, info, id, partIndex, parentId)
|
|
|
624
507
|
}
|
|
625
508
|
return msg;
|
|
626
509
|
};
|
|
627
|
-
var
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
510
|
+
var toRichlinkMessage = (message, base, id) => {
|
|
511
|
+
const url = message.text ?? "";
|
|
512
|
+
try {
|
|
513
|
+
return { ...base, id, content: asRichlink({ url }) };
|
|
514
|
+
} catch (err) {
|
|
515
|
+
console.warn(
|
|
516
|
+
"[spectrum-ts][imessage] failed to convert message to rich link; falling back to text/custom content",
|
|
517
|
+
{ error: err, message, url }
|
|
518
|
+
);
|
|
519
|
+
return {
|
|
520
|
+
...base,
|
|
521
|
+
id,
|
|
522
|
+
content: url ? asText(url) : asCustom(message)
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
var rebuildFromAppleMessage = async (client, message, chatGuidHint) => {
|
|
527
|
+
const messageGuidStr = message.guid;
|
|
528
|
+
const timestamp = message.dateCreated ?? /* @__PURE__ */ new Date();
|
|
529
|
+
const base = buildMessageBase(message, chatGuidHint, timestamp);
|
|
530
|
+
if (message.attachments.length === 1) {
|
|
632
531
|
const info = message.attachments[0];
|
|
633
532
|
if (!info) {
|
|
634
533
|
throw new Error("Unreachable: attachments.length === 1 but no element");
|
|
@@ -656,22 +555,13 @@ var rebuildFromAppleMessage = async (client, message, chatGuidHint) => {
|
|
|
656
555
|
return {
|
|
657
556
|
...base,
|
|
658
557
|
id: messageGuidStr,
|
|
659
|
-
content:
|
|
558
|
+
content: asProviderGroup(items)
|
|
660
559
|
};
|
|
661
560
|
}
|
|
662
|
-
const text = message.text;
|
|
663
561
|
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
|
-
}
|
|
562
|
+
return toRichlinkMessage(message, base, messageGuidStr);
|
|
674
563
|
}
|
|
564
|
+
const text = message.text;
|
|
675
565
|
return {
|
|
676
566
|
...base,
|
|
677
567
|
id: messageGuidStr,
|
|
@@ -682,45 +572,166 @@ var cacheMessage = (cache, message) => {
|
|
|
682
572
|
cache.set(message.id, message);
|
|
683
573
|
if (message.content.type === "group") {
|
|
684
574
|
for (const item of message.content.items) {
|
|
685
|
-
|
|
575
|
+
if (isIMessageMessage(item)) {
|
|
576
|
+
cache.set(item.id, item);
|
|
577
|
+
}
|
|
686
578
|
}
|
|
687
579
|
}
|
|
688
580
|
};
|
|
689
|
-
var
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
581
|
+
var toInboundMessages = async (client, cache, event) => {
|
|
582
|
+
const base = buildMessageBase(event.message, event.chatGuid, event.timestamp);
|
|
583
|
+
const messageGuidStr = event.message.guid;
|
|
584
|
+
if (getBalloonBundleId(event.message) === URL_BALLOON_BUNDLE_ID) {
|
|
585
|
+
const msg2 = toRichlinkMessage(event.message, base, messageGuidStr);
|
|
586
|
+
cacheMessage(cache, msg2);
|
|
587
|
+
return [msg2];
|
|
588
|
+
}
|
|
589
|
+
if (event.message.attachments.length === 1) {
|
|
590
|
+
const info = event.message.attachments[0];
|
|
591
|
+
if (!info) {
|
|
592
|
+
throw new Error("Unreachable: attachments.length === 1 but no element");
|
|
593
|
+
}
|
|
594
|
+
const msg2 = await buildAttachmentMessage(
|
|
595
|
+
client,
|
|
596
|
+
base,
|
|
597
|
+
info,
|
|
598
|
+
messageGuidStr,
|
|
599
|
+
0
|
|
600
|
+
);
|
|
601
|
+
cacheMessage(cache, msg2);
|
|
602
|
+
return [msg2];
|
|
603
|
+
}
|
|
604
|
+
if (event.message.attachments.length > 1) {
|
|
605
|
+
const items = [];
|
|
606
|
+
for (let i = 0; i < event.message.attachments.length; i++) {
|
|
607
|
+
const info = event.message.attachments[i];
|
|
608
|
+
if (!info) {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
items.push(
|
|
612
|
+
await buildAttachmentMessage(
|
|
613
|
+
client,
|
|
614
|
+
base,
|
|
615
|
+
info,
|
|
616
|
+
formatChildId(i, messageGuidStr),
|
|
617
|
+
i,
|
|
618
|
+
messageGuidStr
|
|
619
|
+
)
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
const parent = {
|
|
695
623
|
...base,
|
|
696
|
-
id,
|
|
697
|
-
content:
|
|
624
|
+
id: messageGuidStr,
|
|
625
|
+
content: asProviderGroup(items)
|
|
698
626
|
};
|
|
627
|
+
cacheMessage(cache, parent);
|
|
628
|
+
return [parent];
|
|
699
629
|
}
|
|
630
|
+
const text = event.message.text;
|
|
631
|
+
const msg = {
|
|
632
|
+
...base,
|
|
633
|
+
id: messageGuidStr,
|
|
634
|
+
content: text ? asText(text) : asCustom(event.message)
|
|
635
|
+
};
|
|
636
|
+
cacheMessage(cache, msg);
|
|
637
|
+
return [msg];
|
|
700
638
|
};
|
|
701
|
-
var
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
639
|
+
var getMessage3 = async (remote, spaceId, msgId) => {
|
|
640
|
+
const cache = getMessageCache(remote);
|
|
641
|
+
const cached = cache.get(msgId);
|
|
642
|
+
if (cached) {
|
|
643
|
+
return cached;
|
|
644
|
+
}
|
|
645
|
+
const childRef = parseChildId(msgId);
|
|
646
|
+
if (childRef) {
|
|
647
|
+
try {
|
|
648
|
+
const fetched = await remote.messages.get(
|
|
649
|
+
messageGuid(childRef.parentGuid)
|
|
650
|
+
);
|
|
651
|
+
const parent = await rebuildFromAppleMessage(remote, fetched, spaceId);
|
|
652
|
+
cacheMessage(cache, parent);
|
|
653
|
+
if (parent.content.type !== "group") {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const item = parent.content.items[childRef.partIndex];
|
|
657
|
+
return isIMessageMessage(item) ? item : void 0;
|
|
658
|
+
} catch (err) {
|
|
659
|
+
if (err instanceof NotFoundError) {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
throw err;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
const fetched = await remote.messages.get(messageGuid(msgId));
|
|
667
|
+
const rebuilt = await rebuildFromAppleMessage(remote, fetched, spaceId);
|
|
668
|
+
cacheMessage(cache, rebuilt);
|
|
669
|
+
return rebuilt;
|
|
670
|
+
} catch (err) {
|
|
671
|
+
if (err instanceof NotFoundError) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
throw err;
|
|
675
|
+
}
|
|
708
676
|
};
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
677
|
+
|
|
678
|
+
// src/providers/imessage/remote/reactions.ts
|
|
679
|
+
import {
|
|
680
|
+
chatGuid,
|
|
681
|
+
messageGuid as messageGuid2,
|
|
682
|
+
Reaction
|
|
683
|
+
} from "@photon-ai/advanced-imessage";
|
|
684
|
+
var EMOJI_TO_TAPBACK = {
|
|
685
|
+
"\u2764\uFE0F": Reaction.love,
|
|
686
|
+
"\u{1F44D}": Reaction.like,
|
|
687
|
+
"\u{1F44E}": Reaction.dislike,
|
|
688
|
+
"\u{1F602}": Reaction.laugh,
|
|
689
|
+
"\u203C\uFE0F": Reaction.emphasize,
|
|
690
|
+
"\u2753": Reaction.question
|
|
691
|
+
};
|
|
692
|
+
var TAPBACK_TO_EMOJI = Object.fromEntries(
|
|
693
|
+
Object.entries(EMOJI_TO_TAPBACK).map(([emoji, kind]) => [kind, emoji])
|
|
694
|
+
);
|
|
695
|
+
var TAPBACK_CODE_TO_KIND = {
|
|
696
|
+
"2000": Reaction.love,
|
|
697
|
+
"2001": Reaction.like,
|
|
698
|
+
"2002": Reaction.dislike,
|
|
699
|
+
"2003": Reaction.laugh,
|
|
700
|
+
"2004": Reaction.emphasize,
|
|
701
|
+
"2005": Reaction.question,
|
|
702
|
+
"2006": Reaction.emoji,
|
|
703
|
+
"2007": Reaction.sticker
|
|
704
|
+
};
|
|
705
|
+
var isTapbackRemoval = (code) => code.startsWith("3");
|
|
706
|
+
var resolveReactionEmoji = (type, emoji) => {
|
|
707
|
+
if (emoji) {
|
|
708
|
+
return emoji;
|
|
709
|
+
}
|
|
710
|
+
if (!type) {
|
|
712
711
|
return null;
|
|
713
712
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
partIndex: Number(match[1])
|
|
717
|
-
};
|
|
713
|
+
const kind = TAPBACK_CODE_TO_KIND[type] ?? type;
|
|
714
|
+
return TAPBACK_TO_EMOJI[kind] ?? null;
|
|
718
715
|
};
|
|
716
|
+
var getAssociatedMessageType = (message) => {
|
|
717
|
+
const direct = message.associatedMessageType;
|
|
718
|
+
if (typeof direct === "string") {
|
|
719
|
+
return direct;
|
|
720
|
+
}
|
|
721
|
+
const raw = message._raw;
|
|
722
|
+
const fromRaw = raw?.associatedMessageType;
|
|
723
|
+
return typeof fromRaw === "string" ? fromRaw : void 0;
|
|
724
|
+
};
|
|
725
|
+
var asProviderReaction = (emoji, target) => reactionSchema.parse({
|
|
726
|
+
emoji,
|
|
727
|
+
target,
|
|
728
|
+
type: "reaction"
|
|
729
|
+
});
|
|
719
730
|
var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
|
|
720
731
|
let candidate = cache.get(strippedGuid);
|
|
721
732
|
if (!candidate) {
|
|
722
733
|
try {
|
|
723
|
-
const fetched = await client.messages.get(
|
|
734
|
+
const fetched = await client.messages.get(messageGuid2(strippedGuid));
|
|
724
735
|
candidate = await rebuildFromAppleMessage(client, fetched);
|
|
725
736
|
cacheMessage(cache, candidate);
|
|
726
737
|
} catch {
|
|
@@ -728,12 +739,16 @@ var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
|
|
|
728
739
|
}
|
|
729
740
|
}
|
|
730
741
|
if (candidate.content.type === "group") {
|
|
731
|
-
const
|
|
732
|
-
|
|
742
|
+
const items = candidate.content.items;
|
|
743
|
+
if (!Array.isArray(items)) {
|
|
744
|
+
return candidate;
|
|
745
|
+
}
|
|
746
|
+
const item = items[partIndex];
|
|
747
|
+
return isIMessageMessage(item) ? item : candidate;
|
|
733
748
|
}
|
|
734
749
|
return candidate;
|
|
735
750
|
};
|
|
736
|
-
var
|
|
751
|
+
var toReactionMessages = async (client, cache, event, target) => {
|
|
737
752
|
const type = getAssociatedMessageType(event.message);
|
|
738
753
|
if (type && isTapbackRemoval(type)) {
|
|
739
754
|
return [];
|
|
@@ -755,76 +770,567 @@ var toReactionMessage = async (client, cache, event, base, id, target) => {
|
|
|
755
770
|
if (!resolved) {
|
|
756
771
|
return [];
|
|
757
772
|
}
|
|
773
|
+
const messageId = event.message.guid;
|
|
774
|
+
if (typeof messageId !== "string" || messageId.length === 0) {
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
const base = buildMessageBase(event.message, event.chatGuid, event.timestamp);
|
|
758
778
|
return [
|
|
759
779
|
{
|
|
760
780
|
...base,
|
|
761
|
-
id,
|
|
762
|
-
content:
|
|
781
|
+
id: messageId,
|
|
782
|
+
content: asProviderReaction(emoji, resolved)
|
|
763
783
|
}
|
|
764
784
|
];
|
|
765
785
|
};
|
|
766
|
-
var
|
|
767
|
-
const
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
786
|
+
var reactToMessage = async (remote, spaceId, target, reaction) => {
|
|
787
|
+
const chat = chatGuid(spaceId);
|
|
788
|
+
const parentGuid = target.parentId ?? target.id;
|
|
789
|
+
const guid = messageGuid2(parentGuid);
|
|
790
|
+
const opts = typeof target.partIndex === "number" ? { partIndex: target.partIndex } : void 0;
|
|
791
|
+
const native = EMOJI_TO_TAPBACK[reaction];
|
|
792
|
+
if (native) {
|
|
793
|
+
await remote.messages.react(chat, guid, native, opts);
|
|
794
|
+
} else {
|
|
795
|
+
await remote.messages.reactEmoji(chat, guid, reaction, opts);
|
|
772
796
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// src/providers/imessage/remote/send.ts
|
|
800
|
+
import {
|
|
801
|
+
chatGuid as chatGuid2,
|
|
802
|
+
messageGuid as messageGuid3
|
|
803
|
+
} from "@photon-ai/advanced-imessage";
|
|
804
|
+
|
|
805
|
+
// src/utils/audio.ts
|
|
806
|
+
import { spawn } from "child_process";
|
|
807
|
+
import { mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
808
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
809
|
+
import { join as join2 } from "path";
|
|
810
|
+
var M4A_BRANDS = /* @__PURE__ */ new Set([
|
|
811
|
+
"M4A ",
|
|
812
|
+
"M4B ",
|
|
813
|
+
"M4P ",
|
|
814
|
+
"mp42",
|
|
815
|
+
"mp41",
|
|
816
|
+
"isom",
|
|
817
|
+
"iso2"
|
|
818
|
+
]);
|
|
819
|
+
var M4A_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
820
|
+
"audio/mp4",
|
|
821
|
+
"audio/mp4a-latm",
|
|
822
|
+
"audio/x-m4a",
|
|
823
|
+
"audio/aac",
|
|
824
|
+
"audio/aacp"
|
|
825
|
+
]);
|
|
826
|
+
var FFMPEG_MISSING_MESSAGE = "voice content: input is not m4a/aac and ffmpeg is unavailable. Install `ffmpeg-static` or ensure `ffmpeg` is on PATH.";
|
|
827
|
+
var isM4a = (buffer) => {
|
|
828
|
+
if (buffer.length < 12) {
|
|
829
|
+
return false;
|
|
777
830
|
}
|
|
778
|
-
if (
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
831
|
+
if (buffer.toString("ascii", 4, 8) !== "ftyp") {
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
return M4A_BRANDS.has(buffer.toString("ascii", 8, 12));
|
|
835
|
+
};
|
|
836
|
+
var isM4aMimeType = (mimeType) => M4A_MIME_TYPES.has(mimeType.toLowerCase());
|
|
837
|
+
var cachedFfmpegPath;
|
|
838
|
+
var tryStaticBinary = async () => {
|
|
839
|
+
try {
|
|
840
|
+
const mod = await import("ffmpeg-static");
|
|
841
|
+
return mod.default ?? void 0;
|
|
842
|
+
} catch {
|
|
843
|
+
return void 0;
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
var resolveFfmpegPath = async () => {
|
|
847
|
+
if (cachedFfmpegPath) {
|
|
848
|
+
return cachedFfmpegPath;
|
|
849
|
+
}
|
|
850
|
+
cachedFfmpegPath = await tryStaticBinary() ?? "ffmpeg";
|
|
851
|
+
return cachedFfmpegPath;
|
|
852
|
+
};
|
|
853
|
+
var collectStream = (stream2) => {
|
|
854
|
+
if (!stream2) {
|
|
855
|
+
return Promise.resolve("");
|
|
856
|
+
}
|
|
857
|
+
return new Promise((resolve, reject) => {
|
|
858
|
+
const chunks = [];
|
|
859
|
+
stream2.on("data", (chunk) => {
|
|
860
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
861
|
+
});
|
|
862
|
+
stream2.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
863
|
+
stream2.on("error", reject);
|
|
864
|
+
});
|
|
865
|
+
};
|
|
866
|
+
var isMissingBinaryError = (err) => err?.code === "ENOENT";
|
|
867
|
+
var runFfmpeg = (ffmpegPath, args) => {
|
|
868
|
+
const proc = spawn(ffmpegPath, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
869
|
+
const stderr = collectStream(proc.stderr);
|
|
870
|
+
const exit = new Promise((resolve, reject) => {
|
|
871
|
+
proc.on(
|
|
872
|
+
"error",
|
|
873
|
+
(err) => reject(
|
|
874
|
+
isMissingBinaryError(err) ? new Error(FFMPEG_MISSING_MESSAGE) : err
|
|
875
|
+
)
|
|
876
|
+
);
|
|
877
|
+
proc.on("exit", (code) => resolve(code ?? -1));
|
|
878
|
+
});
|
|
879
|
+
return Promise.all([exit, stderr]).then(([code, text]) => ({
|
|
880
|
+
code,
|
|
881
|
+
stderr: text
|
|
882
|
+
}));
|
|
883
|
+
};
|
|
884
|
+
var DURATION_PATTERN = /Duration:\s*(\d+):(\d{2}):(\d{2})(?:\.(\d{1,3}))?/;
|
|
885
|
+
var parseDuration = (stderr) => {
|
|
886
|
+
const match = stderr.match(DURATION_PATTERN);
|
|
887
|
+
if (!match) {
|
|
888
|
+
return void 0;
|
|
889
|
+
}
|
|
890
|
+
const [, hh, mm, ss, frac] = match;
|
|
891
|
+
const seconds = Number(hh) * 3600 + Number(mm) * 60 + Number(ss) + Number(`0.${frac ?? 0}`);
|
|
892
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
893
|
+
};
|
|
894
|
+
var transcodeToM4a = async (buffer) => {
|
|
895
|
+
const ffmpeg = await resolveFfmpegPath();
|
|
896
|
+
const dir = await mkdtemp2(join2(tmpdir2(), "spectrum-voice-"));
|
|
897
|
+
const inPath = join2(dir, "in");
|
|
898
|
+
const outPath = join2(dir, "out.m4a");
|
|
899
|
+
try {
|
|
900
|
+
await writeFile2(inPath, buffer);
|
|
901
|
+
const { code, stderr } = await runFfmpeg(ffmpeg, [
|
|
902
|
+
"-y",
|
|
903
|
+
"-i",
|
|
904
|
+
inPath,
|
|
905
|
+
"-f",
|
|
906
|
+
"ipod",
|
|
907
|
+
"-c:a",
|
|
908
|
+
"aac",
|
|
909
|
+
outPath
|
|
910
|
+
]);
|
|
911
|
+
if (code !== 0) {
|
|
912
|
+
throw new Error(`ffmpeg conversion failed (exit ${code}): ${stderr}`);
|
|
782
913
|
}
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
914
|
+
const out = await readFile2(outPath);
|
|
915
|
+
return { buffer: out, duration: parseDuration(stderr) };
|
|
916
|
+
} finally {
|
|
917
|
+
await rm2(dir, { recursive: true, force: true }).catch(() => {
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
var ensureM4a = async (buffer, mimeType) => {
|
|
922
|
+
if (isM4aMimeType(mimeType) || isM4a(buffer)) {
|
|
923
|
+
return { buffer };
|
|
924
|
+
}
|
|
925
|
+
return transcodeToM4a(buffer);
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
// src/providers/imessage/remote/send.ts
|
|
929
|
+
var GROUP_ITEM_ALLOWED = /* @__PURE__ */ new Set([
|
|
930
|
+
"attachment",
|
|
931
|
+
"contact",
|
|
932
|
+
"voice"
|
|
933
|
+
]);
|
|
934
|
+
var PartialGroupSendError = class extends Error {
|
|
935
|
+
cause;
|
|
936
|
+
groupMembers;
|
|
937
|
+
constructor(groupMembers, cause) {
|
|
938
|
+
super("iMessage group send failed after one or more items were sent");
|
|
939
|
+
this.name = "PartialGroupSendError";
|
|
940
|
+
this.cause = cause;
|
|
941
|
+
this.groupMembers = groupMembers;
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
var toDate = (value) => {
|
|
945
|
+
if (value instanceof Date) {
|
|
946
|
+
return value;
|
|
947
|
+
}
|
|
948
|
+
if (typeof value === "number" || typeof value === "string") {
|
|
949
|
+
const date = new Date(value);
|
|
950
|
+
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
var receiptTimestamp = (receipt) => toDate(receipt.timestamp) ?? toDate(receipt.date) ?? toDate(receipt.dateCreated) ?? /* @__PURE__ */ new Date();
|
|
954
|
+
var toSendResult = (receipt) => {
|
|
955
|
+
if (typeof receipt.guid !== "string" || receipt.guid.length === 0) {
|
|
956
|
+
throw new Error("iMessage send receipt is missing a message guid");
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
id: receipt.guid,
|
|
960
|
+
timestamp: receiptTimestamp(receipt)
|
|
961
|
+
};
|
|
962
|
+
};
|
|
963
|
+
var withReply = (options, replyTo) => replyTo ? { ...options, replyTo } : options;
|
|
964
|
+
var replyOptions = (replyTo) => replyTo ? { replyTo } : void 0;
|
|
965
|
+
var sendVCardAttachment = (remote, name, vcf) => remote.attachments.upload({
|
|
966
|
+
data: Buffer.from(vcf, "utf8"),
|
|
967
|
+
fileName: name,
|
|
968
|
+
mimeType: "text/vcard"
|
|
969
|
+
});
|
|
970
|
+
var sendContactAttachment = async (remote, content) => {
|
|
971
|
+
const vcf = await toVCard(content);
|
|
972
|
+
const upload = await sendVCardAttachment(remote, vcardFileName(content), vcf);
|
|
973
|
+
return upload.guid;
|
|
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 attachment.guid;
|
|
982
|
+
};
|
|
983
|
+
var uploadVoice = async (remote, content) => {
|
|
984
|
+
const { buffer } = await ensureM4a(await content.read(), content.mimeType);
|
|
985
|
+
const attachment = await remote.attachments.upload({
|
|
986
|
+
data: buffer,
|
|
987
|
+
fileName: content.name ?? "voice.m4a",
|
|
988
|
+
mimeType: "audio/x-m4a"
|
|
989
|
+
});
|
|
990
|
+
return attachment.guid;
|
|
991
|
+
};
|
|
992
|
+
var sendContent = async (remote, chat, content, replyTo) => {
|
|
993
|
+
switch (content.type) {
|
|
994
|
+
case "text":
|
|
995
|
+
return toSendResult(
|
|
996
|
+
await remote.messages.send(chat, content.text, withReply({}, replyTo))
|
|
997
|
+
);
|
|
998
|
+
case "richlink":
|
|
999
|
+
return toSendResult(
|
|
1000
|
+
await remote.messages.send(
|
|
1001
|
+
chat,
|
|
1002
|
+
content.url,
|
|
1003
|
+
withReply({ richLink: true }, replyTo)
|
|
1004
|
+
)
|
|
1005
|
+
);
|
|
1006
|
+
case "attachment":
|
|
1007
|
+
return toSendResult(
|
|
1008
|
+
await remote.messages.send(chat, "", {
|
|
1009
|
+
attachment: await uploadAttachment(remote, content),
|
|
1010
|
+
...replyOptions(replyTo)
|
|
1011
|
+
})
|
|
1012
|
+
);
|
|
1013
|
+
case "contact":
|
|
1014
|
+
return toSendResult(
|
|
1015
|
+
await remote.messages.send(chat, "", {
|
|
1016
|
+
attachment: await sendContactAttachment(remote, content),
|
|
1017
|
+
...replyOptions(replyTo)
|
|
1018
|
+
})
|
|
1019
|
+
);
|
|
1020
|
+
case "voice":
|
|
1021
|
+
return toSendResult(
|
|
1022
|
+
await remote.messages.send(chat, "", {
|
|
1023
|
+
attachment: await uploadVoice(remote, content),
|
|
1024
|
+
audioMessage: true,
|
|
1025
|
+
...replyOptions(replyTo)
|
|
1026
|
+
})
|
|
1027
|
+
);
|
|
1028
|
+
case "poll":
|
|
1029
|
+
if (replyTo) {
|
|
1030
|
+
throw unsupportedRemoteContent(
|
|
1031
|
+
"poll",
|
|
1032
|
+
"polls cannot be sent as replies"
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
return toSendResult(
|
|
1036
|
+
await remote.polls.create(
|
|
1037
|
+
chat,
|
|
1038
|
+
content.title,
|
|
1039
|
+
content.options.map((option) => option.title)
|
|
1040
|
+
)
|
|
1041
|
+
);
|
|
1042
|
+
default:
|
|
1043
|
+
throw unsupportedRemoteContent(content.type);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
var validateGroupContent = (content) => {
|
|
1047
|
+
for (const sub of content.items) {
|
|
1048
|
+
const itemType = sub.content.type;
|
|
1049
|
+
if (!GROUP_ITEM_ALLOWED.has(itemType)) {
|
|
1050
|
+
throw unsupportedRemoteContent(
|
|
1051
|
+
"group",
|
|
1052
|
+
`"${itemType}" items are not supported inside a group`
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
var send3 = async (remote, spaceId, content) => {
|
|
1058
|
+
const chat = chatGuid2(spaceId);
|
|
1059
|
+
if (content.type === "group") {
|
|
1060
|
+
validateGroupContent(content);
|
|
1061
|
+
const groupMembers = [];
|
|
1062
|
+
try {
|
|
1063
|
+
for (const sub of content.items) {
|
|
1064
|
+
groupMembers.push(await sendContent(remote, chat, sub.content));
|
|
1065
|
+
}
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
throw new PartialGroupSendError(groupMembers, err);
|
|
1068
|
+
}
|
|
1069
|
+
const first = groupMembers[0];
|
|
1070
|
+
if (!first) {
|
|
1071
|
+
throw new Error("Empty group");
|
|
1072
|
+
}
|
|
1073
|
+
return { ...first, groupMembers };
|
|
1074
|
+
}
|
|
1075
|
+
return sendContent(remote, chat, content);
|
|
1076
|
+
};
|
|
1077
|
+
var replyToMessage = async (remote, spaceId, msgId, content) => {
|
|
1078
|
+
const chat = chatGuid2(spaceId);
|
|
1079
|
+
const replyTo = messageGuid3(msgId);
|
|
1080
|
+
return sendContent(remote, chat, content, replyTo);
|
|
1081
|
+
};
|
|
1082
|
+
var editMessage = async (remote, spaceId, msgId, content) => {
|
|
1083
|
+
if (content.type !== "text") {
|
|
1084
|
+
throw unsupportedRemoteContent(
|
|
1085
|
+
content.type,
|
|
1086
|
+
"only text content can be edited"
|
|
789
1087
|
);
|
|
790
|
-
cacheMessage(cache, msg2);
|
|
791
|
-
return [msg2];
|
|
792
1088
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1089
|
+
await remote.messages.edit(
|
|
1090
|
+
chatGuid2(spaceId),
|
|
1091
|
+
messageGuid3(msgId),
|
|
1092
|
+
content.text
|
|
1093
|
+
);
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
// src/providers/imessage/remote/stream.ts
|
|
1097
|
+
import {
|
|
1098
|
+
AuthenticationError,
|
|
1099
|
+
IMessageError,
|
|
1100
|
+
NotFoundError as NotFoundError2,
|
|
1101
|
+
ValidationError
|
|
1102
|
+
} from "@photon-ai/advanced-imessage";
|
|
1103
|
+
|
|
1104
|
+
// src/utils/resumable-stream.ts
|
|
1105
|
+
var CATCH_UP_PAGE_SIZE = 100;
|
|
1106
|
+
var MAX_BUFFERED_LIVE_EVENTS = 1e3;
|
|
1107
|
+
var RECONNECT_INITIAL_DELAY_MS = 500;
|
|
1108
|
+
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
1109
|
+
var RetryableStreamError = class extends Error {
|
|
1110
|
+
constructor(message) {
|
|
1111
|
+
super(message);
|
|
1112
|
+
this.name = "RetryableStreamError";
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
var LiveBufferOverflowError = class extends RetryableStreamError {
|
|
1116
|
+
constructor(limit) {
|
|
1117
|
+
super(`Live stream buffer exceeded ${limit} events during catch-up`);
|
|
1118
|
+
this.name = "LiveBufferOverflowError";
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
var closeIterable = async (iterable) => {
|
|
1122
|
+
if (!iterable) {
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
await iterable.close?.();
|
|
1126
|
+
};
|
|
1127
|
+
var jitterDelay = (delayMs) => Math.random() * delayMs;
|
|
1128
|
+
var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
1129
|
+
const catchUpPageSize = options.catchUpPageSize ?? CATCH_UP_PAGE_SIZE;
|
|
1130
|
+
const bufferLimit = options.bufferLimit ?? MAX_BUFFERED_LIVE_EVENTS;
|
|
1131
|
+
const initialRetryDelayMs = options.initialRetryDelayMs ?? RECONNECT_INITIAL_DELAY_MS;
|
|
1132
|
+
const maxRetryDelayMs = options.maxRetryDelayMs ?? RECONNECT_MAX_DELAY_MS;
|
|
1133
|
+
let activeLive;
|
|
1134
|
+
let closed = false;
|
|
1135
|
+
let lastCursor;
|
|
1136
|
+
let retryDelayMs = initialRetryDelayMs;
|
|
1137
|
+
let sleepTimer;
|
|
1138
|
+
let wakeSleep;
|
|
1139
|
+
const deliveredSinceCursor = /* @__PURE__ */ new Set();
|
|
1140
|
+
const resetRetryDelay = () => {
|
|
1141
|
+
retryDelayMs = initialRetryDelayMs;
|
|
1142
|
+
};
|
|
1143
|
+
const advanceCursor = (cursor, clearDelivered) => {
|
|
1144
|
+
if (!cursor || cursor === lastCursor) {
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
lastCursor = cursor;
|
|
1148
|
+
if (clearDelivered) {
|
|
1149
|
+
deliveredSinceCursor.clear();
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
const deliverItem = async (item, resetRetry, clearOnCursorAdvance) => {
|
|
1153
|
+
const alreadyDelivered = deliveredSinceCursor.has(item.id);
|
|
1154
|
+
if (!alreadyDelivered) {
|
|
1155
|
+
for (const value of item.values) {
|
|
1156
|
+
await emit(value);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
advanceCursor(item.cursor, clearOnCursorAdvance);
|
|
1160
|
+
deliveredSinceCursor.add(item.id);
|
|
1161
|
+
if (resetRetry) {
|
|
1162
|
+
resetRetryDelay();
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
const retryable = (error) => error instanceof RetryableStreamError || options.isRetryableError(error);
|
|
1166
|
+
const sleep = async (delayMs) => {
|
|
1167
|
+
if (delayMs <= 0 || closed) {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
await new Promise((resolve) => {
|
|
1171
|
+
wakeSleep = resolve;
|
|
1172
|
+
sleepTimer = setTimeout(resolve, jitterDelay(delayMs));
|
|
1173
|
+
});
|
|
1174
|
+
sleepTimer = void 0;
|
|
1175
|
+
wakeSleep = void 0;
|
|
1176
|
+
};
|
|
1177
|
+
const cancelSleep = () => {
|
|
1178
|
+
if (sleepTimer) {
|
|
1179
|
+
clearTimeout(sleepTimer);
|
|
1180
|
+
sleepTimer = void 0;
|
|
1181
|
+
}
|
|
1182
|
+
wakeSleep?.();
|
|
1183
|
+
wakeSleep = void 0;
|
|
1184
|
+
};
|
|
1185
|
+
const nextRetryDelay = () => {
|
|
1186
|
+
const delay = retryDelayMs;
|
|
1187
|
+
retryDelayMs = Math.min(retryDelayMs * 2, maxRetryDelayMs);
|
|
1188
|
+
return delay;
|
|
1189
|
+
};
|
|
1190
|
+
const consumeLive = async () => {
|
|
1191
|
+
const live = options.subscribeLive();
|
|
1192
|
+
activeLive = live;
|
|
1193
|
+
try {
|
|
1194
|
+
for await (const event of live) {
|
|
1195
|
+
await deliverItem(await options.processLive(event), true, true);
|
|
1196
|
+
}
|
|
1197
|
+
throw new RetryableStreamError("Live stream ended");
|
|
1198
|
+
} finally {
|
|
1199
|
+
if (activeLive === live) {
|
|
1200
|
+
activeLive = void 0;
|
|
1201
|
+
}
|
|
1202
|
+
await closeIterable(live);
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
const throwLiveError = (liveError) => {
|
|
1206
|
+
if (liveError) {
|
|
1207
|
+
throw liveError;
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
const bufferLiveEvent = (buffer, event) => {
|
|
1211
|
+
if (buffer.length >= bufferLimit) {
|
|
1212
|
+
throw new LiveBufferOverflowError(bufferLimit);
|
|
1213
|
+
}
|
|
1214
|
+
buffer.push(event);
|
|
1215
|
+
};
|
|
1216
|
+
const startLivePump = (live, isBuffering, liveBuffer) => {
|
|
1217
|
+
let liveError;
|
|
1218
|
+
const pump2 = (async () => {
|
|
1219
|
+
try {
|
|
1220
|
+
for await (const event of live) {
|
|
1221
|
+
if (isBuffering()) {
|
|
1222
|
+
bufferLiveEvent(liveBuffer, event);
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
await deliverItem(await options.processLive(event), true, true);
|
|
1226
|
+
}
|
|
1227
|
+
throw new RetryableStreamError("Live stream ended");
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
liveError = error;
|
|
799
1230
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1231
|
+
})();
|
|
1232
|
+
return {
|
|
1233
|
+
getError: () => liveError,
|
|
1234
|
+
pump: pump2
|
|
1235
|
+
};
|
|
1236
|
+
};
|
|
1237
|
+
const replayMissed = async (cursor, getLiveError) => {
|
|
1238
|
+
for await (const event of options.fetchMissed(cursor, {
|
|
1239
|
+
limit: catchUpPageSize
|
|
1240
|
+
})) {
|
|
1241
|
+
throwLiveError(getLiveError());
|
|
1242
|
+
await deliverItem(await options.processMissed(event), false, false);
|
|
1243
|
+
}
|
|
1244
|
+
throwLiveError(getLiveError());
|
|
1245
|
+
};
|
|
1246
|
+
const flushLiveBuffer = async (liveBuffer, getLiveError) => {
|
|
1247
|
+
let index = 0;
|
|
1248
|
+
let lastFlushedId;
|
|
1249
|
+
while (index < liveBuffer.length) {
|
|
1250
|
+
throwLiveError(getLiveError());
|
|
1251
|
+
const event = liveBuffer[index];
|
|
1252
|
+
if (event === void 0) {
|
|
1253
|
+
throw new RetryableStreamError("Live stream buffer index missing");
|
|
1254
|
+
}
|
|
1255
|
+
const item = await options.processLive(event);
|
|
1256
|
+
await deliverItem(item, true, false);
|
|
1257
|
+
lastFlushedId = item.id;
|
|
1258
|
+
index += 1;
|
|
1259
|
+
}
|
|
1260
|
+
liveBuffer.length = 0;
|
|
1261
|
+
throwLiveError(getLiveError());
|
|
1262
|
+
return lastFlushedId;
|
|
1263
|
+
};
|
|
1264
|
+
const compactDeliveredIds = (lastId) => {
|
|
1265
|
+
if (!lastId) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
deliveredSinceCursor.clear();
|
|
1269
|
+
deliveredSinceCursor.add(lastId);
|
|
1270
|
+
};
|
|
1271
|
+
const catchUpThenConsumeLive = async (cursor) => {
|
|
1272
|
+
const live = options.subscribeLive();
|
|
1273
|
+
activeLive = live;
|
|
1274
|
+
let buffering = true;
|
|
1275
|
+
const liveBuffer = [];
|
|
1276
|
+
const livePump = startLivePump(live, () => buffering, liveBuffer);
|
|
1277
|
+
try {
|
|
1278
|
+
await replayMissed(cursor, livePump.getError);
|
|
1279
|
+
const lastFlushedId = await flushLiveBuffer(
|
|
1280
|
+
liveBuffer,
|
|
1281
|
+
livePump.getError
|
|
809
1282
|
);
|
|
1283
|
+
compactDeliveredIds(lastFlushedId);
|
|
1284
|
+
buffering = false;
|
|
1285
|
+
resetRetryDelay();
|
|
1286
|
+
await livePump.pump;
|
|
1287
|
+
throwLiveError(livePump.getError());
|
|
1288
|
+
} finally {
|
|
1289
|
+
buffering = false;
|
|
1290
|
+
if (activeLive === live) {
|
|
1291
|
+
activeLive = void 0;
|
|
1292
|
+
}
|
|
1293
|
+
await closeIterable(live);
|
|
1294
|
+
await livePump.pump.catch(() => void 0);
|
|
810
1295
|
}
|
|
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
1296
|
};
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1297
|
+
const run = async () => {
|
|
1298
|
+
while (!closed) {
|
|
1299
|
+
try {
|
|
1300
|
+
if (lastCursor) {
|
|
1301
|
+
await catchUpThenConsumeLive(lastCursor);
|
|
1302
|
+
} else {
|
|
1303
|
+
await consumeLive();
|
|
1304
|
+
}
|
|
1305
|
+
} catch (error) {
|
|
1306
|
+
await closeIterable(activeLive);
|
|
1307
|
+
activeLive = void 0;
|
|
1308
|
+
if (closed) {
|
|
1309
|
+
break;
|
|
1310
|
+
}
|
|
1311
|
+
if (!retryable(error)) {
|
|
1312
|
+
end(error);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
await sleep(nextRetryDelay());
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
end();
|
|
1319
|
+
};
|
|
1320
|
+
const pump = run().catch((error) => {
|
|
1321
|
+
if (!closed) {
|
|
1322
|
+
end(error);
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
return async () => {
|
|
1326
|
+
closed = true;
|
|
1327
|
+
cancelSleep();
|
|
1328
|
+
await closeIterable(activeLive);
|
|
1329
|
+
await pump;
|
|
1330
|
+
};
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
// src/providers/imessage/remote/polls.ts
|
|
828
1334
|
var isVotedPollEvent = (event) => event.delta.type === "voted";
|
|
829
1335
|
var isUnvotedPollEvent = (event) => event.delta.type === "unvoted";
|
|
830
1336
|
var toCachedPoll = (input) => {
|
|
@@ -908,7 +1414,7 @@ var buildPollOptionMessage = (input) => {
|
|
|
908
1414
|
};
|
|
909
1415
|
};
|
|
910
1416
|
var buildPollOptionMessages = (input) => {
|
|
911
|
-
const
|
|
1417
|
+
const messages5 = [];
|
|
912
1418
|
for (const delta of input.deltas) {
|
|
913
1419
|
const message = buildPollOptionMessage({
|
|
914
1420
|
cached: input.cached,
|
|
@@ -919,10 +1425,10 @@ var buildPollOptionMessages = (input) => {
|
|
|
919
1425
|
senderAddress: input.senderAddress
|
|
920
1426
|
});
|
|
921
1427
|
if (message) {
|
|
922
|
-
|
|
1428
|
+
messages5.push(message);
|
|
923
1429
|
}
|
|
924
1430
|
}
|
|
925
|
-
return
|
|
1431
|
+
return messages5;
|
|
926
1432
|
};
|
|
927
1433
|
var allOptionIdsKnown = (cached, optionIds) => optionIds.every((optionId) => cached.optionsByIdentifier.has(optionId));
|
|
928
1434
|
var refreshPollMetadata = async (client, pollCache, event, fallbackOptionIds) => {
|
|
@@ -977,7 +1483,7 @@ var toPollVoteMessages = async (client, pollCache, event) => {
|
|
|
977
1483
|
senderAddress,
|
|
978
1484
|
currentOptionIds
|
|
979
1485
|
);
|
|
980
|
-
const
|
|
1486
|
+
const messages5 = buildPollOptionMessages({
|
|
981
1487
|
cached: resolvedPoll,
|
|
982
1488
|
chatGuid: chatGuidStr,
|
|
983
1489
|
deltas,
|
|
@@ -990,7 +1496,7 @@ var toPollVoteMessages = async (client, pollCache, event) => {
|
|
|
990
1496
|
currentOptionIds,
|
|
991
1497
|
event.at
|
|
992
1498
|
);
|
|
993
|
-
return
|
|
1499
|
+
return messages5;
|
|
994
1500
|
};
|
|
995
1501
|
var toPollUnvoteMessages = async (client, pollCache, event) => {
|
|
996
1502
|
const senderAddress = event.actor.address;
|
|
@@ -1006,23 +1512,16 @@ var toPollUnvoteMessages = async (client, pollCache, event) => {
|
|
|
1006
1512
|
return [];
|
|
1007
1513
|
}
|
|
1008
1514
|
const chatGuidStr = event.chatGuid;
|
|
1009
|
-
const messages3 = [];
|
|
1010
1515
|
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
|
-
}
|
|
1516
|
+
const messages5 = buildPollOptionMessages({
|
|
1517
|
+
cached,
|
|
1518
|
+
chatGuid: chatGuidStr,
|
|
1519
|
+
deltas,
|
|
1520
|
+
event,
|
|
1521
|
+
senderAddress
|
|
1522
|
+
});
|
|
1024
1523
|
pollCache.commitActorSelection(pollId, senderAddress, [], event.at);
|
|
1025
|
-
return
|
|
1524
|
+
return messages5;
|
|
1026
1525
|
};
|
|
1027
1526
|
var toPollDeltaMessages = async (client, pollCache, event) => {
|
|
1028
1527
|
if (isVotedPollEvent(event)) {
|
|
@@ -1033,294 +1532,156 @@ var toPollDeltaMessages = async (client, pollCache, event) => {
|
|
|
1033
1532
|
}
|
|
1034
1533
|
return [];
|
|
1035
1534
|
};
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1535
|
+
|
|
1536
|
+
// src/providers/imessage/remote/stream.ts
|
|
1537
|
+
var pollRetryDelay = (delayMs) => Math.random() * delayMs;
|
|
1538
|
+
var isRetryableIMessageStreamError = (error) => {
|
|
1539
|
+
if (error instanceof AuthenticationError || error instanceof NotFoundError2 || error instanceof ValidationError) {
|
|
1540
|
+
return false;
|
|
1541
|
+
}
|
|
1542
|
+
if (error instanceof IMessageError) {
|
|
1543
|
+
return true;
|
|
1544
|
+
}
|
|
1545
|
+
return false;
|
|
1546
|
+
};
|
|
1547
|
+
var toMessageItem = async (client, event, cursor) => {
|
|
1548
|
+
const id = event.message.guid;
|
|
1549
|
+
if (event.message.isFromMe) {
|
|
1550
|
+
return { cursor, id, values: [] };
|
|
1551
|
+
}
|
|
1039
1552
|
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
|
-
});
|
|
1553
|
+
const target = event.message.associatedMessageGuid;
|
|
1554
|
+
const values = target ? await toReactionMessages(client, cache, event, target) : await toInboundMessages(client, cache, event);
|
|
1555
|
+
return { cursor, id, values };
|
|
1077
1556
|
};
|
|
1078
|
-
var
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1557
|
+
var messageStream = (client) => resumableOrderedStream({
|
|
1558
|
+
fetchMissed: (cursor, { limit }) => client.messages.fetchMissed(cursor, { limit }),
|
|
1559
|
+
isRetryableError: isRetryableIMessageStreamError,
|
|
1560
|
+
processLive: (event) => toMessageItem(client, event, event.cursor),
|
|
1561
|
+
processMissed: (message) => toMessageItem(client, receivedEventFromMessage(message)),
|
|
1562
|
+
subscribeLive: () => client.messages.subscribe("message.received")
|
|
1082
1563
|
});
|
|
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)));
|
|
1564
|
+
var logPollStreamError = (error) => {
|
|
1565
|
+
console.error("[spectrum-ts][imessage][poll] stream failed", error);
|
|
1095
1566
|
};
|
|
1096
|
-
var
|
|
1097
|
-
|
|
1098
|
-
if (
|
|
1567
|
+
var emitPollMessages = async (client, pollCache, event, emit) => {
|
|
1568
|
+
cachePollEvent(pollCache, event);
|
|
1569
|
+
if (event.actor.isFromMe) {
|
|
1099
1570
|
return;
|
|
1100
1571
|
}
|
|
1101
|
-
await
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
const remote = clients[0];
|
|
1105
|
-
if (!remote) {
|
|
1106
|
-
return;
|
|
1572
|
+
const messages5 = await toPollDeltaMessages(client, pollCache, event);
|
|
1573
|
+
for (const vote of messages5) {
|
|
1574
|
+
await emit(vote);
|
|
1107
1575
|
}
|
|
1108
|
-
await remote.chats.stopTyping(chatGuid(spaceId));
|
|
1109
1576
|
};
|
|
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);
|
|
1577
|
+
var runPollSubscription = async (client, pollCache, subscription, emit, onEvent) => {
|
|
1578
|
+
for await (const event of subscription) {
|
|
1579
|
+
onEvent();
|
|
1580
|
+
await emitPollMessages(client, pollCache, event, emit);
|
|
1159
1581
|
}
|
|
1160
1582
|
};
|
|
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
|
-
}
|
|
1583
|
+
var pollStream = (client, pollCache) => stream((emit, end) => {
|
|
1584
|
+
let active = client.polls.subscribe();
|
|
1585
|
+
let closed = false;
|
|
1586
|
+
let retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
|
|
1587
|
+
let sleepTimer;
|
|
1588
|
+
let wakeSleep;
|
|
1589
|
+
const sleep = async (delayMs) => {
|
|
1590
|
+
if (closed) {
|
|
1591
|
+
return;
|
|
1176
1592
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1593
|
+
await new Promise((resolve) => {
|
|
1594
|
+
wakeSleep = resolve;
|
|
1595
|
+
sleepTimer = setTimeout(resolve, pollRetryDelay(delayMs));
|
|
1596
|
+
});
|
|
1597
|
+
sleepTimer = void 0;
|
|
1598
|
+
wakeSleep = void 0;
|
|
1599
|
+
};
|
|
1600
|
+
const cancelSleep = () => {
|
|
1601
|
+
if (sleepTimer) {
|
|
1602
|
+
clearTimeout(sleepTimer);
|
|
1603
|
+
sleepTimer = void 0;
|
|
1180
1604
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1605
|
+
wakeSleep?.();
|
|
1606
|
+
wakeSleep = void 0;
|
|
1607
|
+
};
|
|
1608
|
+
const pump = (async () => {
|
|
1609
|
+
while (!closed) {
|
|
1610
|
+
try {
|
|
1611
|
+
await runPollSubscription(client, pollCache, active, emit, () => {
|
|
1612
|
+
retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
|
|
1613
|
+
});
|
|
1614
|
+
} catch (e) {
|
|
1615
|
+
if (!closed) {
|
|
1616
|
+
logPollStreamError(e);
|
|
1617
|
+
}
|
|
1618
|
+
} finally {
|
|
1619
|
+
await active.close();
|
|
1620
|
+
}
|
|
1621
|
+
if (!closed) {
|
|
1622
|
+
await sleep(retryDelayMs);
|
|
1623
|
+
retryDelayMs = Math.min(retryDelayMs * 2, RECONNECT_MAX_DELAY_MS);
|
|
1624
|
+
active = client.polls.subscribe();
|
|
1625
|
+
}
|
|
1184
1626
|
}
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1187
|
-
return
|
|
1627
|
+
end();
|
|
1628
|
+
})();
|
|
1629
|
+
return async () => {
|
|
1630
|
+
closed = true;
|
|
1631
|
+
cancelSleep();
|
|
1632
|
+
await active.close();
|
|
1633
|
+
await pump;
|
|
1634
|
+
};
|
|
1635
|
+
});
|
|
1636
|
+
var clientStream = (client, pollCache) => {
|
|
1637
|
+
return mergeStreams([messageStream(client), pollStream(client, pollCache)]);
|
|
1188
1638
|
};
|
|
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
|
-
}
|
|
1639
|
+
var messages3 = (clients) => {
|
|
1640
|
+
const pollCache = getPollCache(clients);
|
|
1641
|
+
return mergeStreams(clients.map((client) => clientStream(client, pollCache)));
|
|
1254
1642
|
};
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1643
|
+
|
|
1644
|
+
// src/providers/imessage/remote/typing.ts
|
|
1645
|
+
import { chatGuid as chatGuid3 } from "@photon-ai/advanced-imessage";
|
|
1646
|
+
var startTyping = async (remote, spaceId) => {
|
|
1647
|
+
await remote.chats.startTyping(chatGuid3(spaceId));
|
|
1648
|
+
};
|
|
1649
|
+
var stopTyping = async (remote, spaceId) => {
|
|
1650
|
+
await remote.chats.stopTyping(chatGuid3(spaceId));
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
// src/providers/imessage/remote/api.ts
|
|
1654
|
+
var messages4 = (clients) => messages3(clients);
|
|
1655
|
+
var startTyping2 = async (clients, spaceId) => {
|
|
1656
|
+
const remote = firstRemoteClient(clients);
|
|
1264
1657
|
if (!remote) {
|
|
1265
|
-
|
|
1658
|
+
return;
|
|
1266
1659
|
}
|
|
1267
|
-
await remote
|
|
1268
|
-
chatGuid(spaceId),
|
|
1269
|
-
messageGuid(msgId),
|
|
1270
|
-
content.text
|
|
1271
|
-
);
|
|
1660
|
+
await startTyping(remote, spaceId);
|
|
1272
1661
|
};
|
|
1273
|
-
var
|
|
1274
|
-
const remote = clients
|
|
1662
|
+
var stopTyping2 = async (clients, spaceId) => {
|
|
1663
|
+
const remote = firstRemoteClient(clients);
|
|
1275
1664
|
if (!remote) {
|
|
1276
1665
|
return;
|
|
1277
1666
|
}
|
|
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
|
-
}
|
|
1667
|
+
await stopTyping(remote, spaceId);
|
|
1288
1668
|
};
|
|
1289
|
-
var
|
|
1290
|
-
|
|
1669
|
+
var send4 = async (clients, spaceId, content) => send3(primaryRemoteClient(clients), spaceId, content);
|
|
1670
|
+
var replyToMessage2 = async (clients, spaceId, msgId, content) => replyToMessage(primaryRemoteClient(clients), spaceId, msgId, content);
|
|
1671
|
+
var editMessage2 = async (clients, spaceId, msgId, content) => editMessage(primaryRemoteClient(clients), spaceId, msgId, content);
|
|
1672
|
+
var reactToMessage2 = async (clients, spaceId, target, reaction) => {
|
|
1673
|
+
const remote = firstRemoteClient(clients);
|
|
1291
1674
|
if (!remote) {
|
|
1292
1675
|
return;
|
|
1293
1676
|
}
|
|
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 {
|
|
1677
|
+
await reactToMessage(remote, spaceId, target, reaction);
|
|
1678
|
+
};
|
|
1679
|
+
var getMessage4 = async (clients, spaceId, msgId) => {
|
|
1680
|
+
const remote = firstRemoteClient(clients);
|
|
1681
|
+
if (!remote) {
|
|
1322
1682
|
return;
|
|
1323
1683
|
}
|
|
1684
|
+
return getMessage3(remote, spaceId, msgId);
|
|
1324
1685
|
};
|
|
1325
1686
|
|
|
1326
1687
|
// src/providers/imessage/types.ts
|
|
@@ -1346,6 +1707,7 @@ var messageSchema = z.object({
|
|
|
1346
1707
|
});
|
|
1347
1708
|
|
|
1348
1709
|
// src/providers/imessage/index.ts
|
|
1710
|
+
var isPollContent = (content) => content.type === "poll" || content.type === "poll_option";
|
|
1349
1711
|
var imessage = definePlatform("iMessage", {
|
|
1350
1712
|
config: configSchema,
|
|
1351
1713
|
user: {
|
|
@@ -1414,55 +1776,69 @@ var imessage = definePlatform("iMessage", {
|
|
|
1414
1776
|
}
|
|
1415
1777
|
},
|
|
1416
1778
|
events: {
|
|
1417
|
-
messages: ({ client }) => isLocal(client) ?
|
|
1779
|
+
messages: ({ client }) => isLocal(client) ? messages2(client) : messages4(client)
|
|
1418
1780
|
},
|
|
1419
1781
|
actions: {
|
|
1420
1782
|
send: async ({ space, content, client }) => {
|
|
1421
1783
|
if (isLocal(client)) {
|
|
1422
|
-
return await
|
|
1784
|
+
return await send2(client, space.id, content);
|
|
1423
1785
|
}
|
|
1424
|
-
return await
|
|
1786
|
+
return await send4(client, space.id, content);
|
|
1425
1787
|
},
|
|
1426
1788
|
startTyping: async ({ space, client }) => {
|
|
1427
1789
|
if (isLocal(client)) {
|
|
1428
1790
|
return;
|
|
1429
1791
|
}
|
|
1430
|
-
await
|
|
1792
|
+
await startTyping2(client, space.id);
|
|
1431
1793
|
},
|
|
1432
1794
|
stopTyping: async ({ space, client }) => {
|
|
1433
1795
|
if (isLocal(client)) {
|
|
1434
1796
|
return;
|
|
1435
1797
|
}
|
|
1436
|
-
await
|
|
1798
|
+
await stopTyping2(client, space.id);
|
|
1437
1799
|
},
|
|
1438
1800
|
reactToMessage: async ({ space, target, reaction, client }) => {
|
|
1439
1801
|
if (isLocal(client)) {
|
|
1440
1802
|
throw UnsupportedError.action("react", "iMessage (local mode)");
|
|
1441
1803
|
}
|
|
1442
|
-
|
|
1804
|
+
if (isPollContent(target.content)) {
|
|
1805
|
+
throw UnsupportedError.action(
|
|
1806
|
+
"react",
|
|
1807
|
+
"iMessage",
|
|
1808
|
+
"iMessage polls do not support reactions"
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
await reactToMessage2(
|
|
1443
1812
|
client,
|
|
1444
1813
|
space.id,
|
|
1445
1814
|
target,
|
|
1446
1815
|
reaction
|
|
1447
1816
|
);
|
|
1448
1817
|
},
|
|
1449
|
-
replyToMessage: async ({ space, messageId, content, client }) => {
|
|
1818
|
+
replyToMessage: async ({ space, messageId, target, content, client }) => {
|
|
1450
1819
|
if (isLocal(client)) {
|
|
1451
1820
|
throw UnsupportedError.action("reply", "iMessage (local mode)");
|
|
1452
1821
|
}
|
|
1453
|
-
|
|
1822
|
+
if (isPollContent(target.content)) {
|
|
1823
|
+
throw UnsupportedError.action(
|
|
1824
|
+
"reply",
|
|
1825
|
+
"iMessage",
|
|
1826
|
+
"iMessage polls do not support replies"
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
return await replyToMessage2(client, space.id, messageId, content);
|
|
1454
1830
|
},
|
|
1455
1831
|
editMessage: async ({ space, messageId, content, client }) => {
|
|
1456
1832
|
if (isLocal(client)) {
|
|
1457
1833
|
throw UnsupportedError.action("edit", "iMessage (local mode)");
|
|
1458
1834
|
}
|
|
1459
|
-
await
|
|
1835
|
+
await editMessage2(client, space.id, messageId, content);
|
|
1460
1836
|
},
|
|
1461
1837
|
getMessage: async ({ space, messageId, client }) => {
|
|
1462
1838
|
if (isLocal(client)) {
|
|
1463
|
-
return
|
|
1839
|
+
return getMessage2(client, messageId);
|
|
1464
1840
|
}
|
|
1465
|
-
return
|
|
1841
|
+
return getMessage4(client, space.id, messageId);
|
|
1466
1842
|
}
|
|
1467
1843
|
}
|
|
1468
1844
|
});
|