spectrum-ts 1.0.1 → 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-2Y5GBI6W.js → chunk-PXX7ISZ6.js} +59 -0
- package/dist/{chunk-LAGNM6I7.js → chunk-TY3RT4OB.js} +2 -2
- package/dist/index.d.ts +38 -3
- package/dist/index.js +8 -4
- package/dist/providers/imessage/index.d.ts +1 -1
- package/dist/providers/imessage/index.js +1297 -589
- package/dist/providers/terminal/index.d.ts +21 -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 +94 -6
- package/dist/{types-D5KhSXLy.d.ts → types-BZhWdyLk.d.ts} +21 -0
- package/package.json +2 -2
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "../../chunk-
|
|
2
|
+
asRichlink,
|
|
3
|
+
groupSchema
|
|
4
|
+
} from "../../chunk-TY3RT4OB.js";
|
|
5
5
|
import {
|
|
6
|
+
asPoll,
|
|
7
|
+
asPollOption,
|
|
6
8
|
cloud,
|
|
7
9
|
mergeStreams,
|
|
8
10
|
stream
|
|
9
|
-
} from "../../chunk-
|
|
11
|
+
} from "../../chunk-PXX7ISZ6.js";
|
|
10
12
|
import {
|
|
11
13
|
UnsupportedError,
|
|
12
14
|
asAttachment,
|
|
13
15
|
asContact,
|
|
14
16
|
asCustom,
|
|
15
|
-
asReaction,
|
|
16
17
|
asText,
|
|
17
18
|
definePlatform,
|
|
18
19
|
fromVCard,
|
|
20
|
+
reactionSchema,
|
|
19
21
|
toVCard
|
|
20
|
-
} from "../../chunk-
|
|
22
|
+
} from "../../chunk-7D6FHYKT.js";
|
|
21
23
|
|
|
22
24
|
// src/providers/imessage/index.ts
|
|
23
25
|
import { createClient as createClient2, directChat } from "@photon-ai/advanced-imessage";
|
|
@@ -109,17 +111,12 @@ async function disposeCloudAuth(clients) {
|
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
// src/providers/imessage/local.ts
|
|
114
|
+
// src/providers/imessage/local/attachments.ts
|
|
113
115
|
import { createReadStream } from "fs";
|
|
114
|
-
import {
|
|
115
|
-
import { tmpdir } from "os";
|
|
116
|
-
import { basename, join } from "path";
|
|
116
|
+
import { readFile } from "fs/promises";
|
|
117
117
|
import { Readable } from "stream";
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
121
|
-
});
|
|
122
|
-
var DEFAULT_ATTACHMENT_NAME = "attachment";
|
|
118
|
+
|
|
119
|
+
// src/providers/imessage/shared/vcard.ts
|
|
123
120
|
var VCARD_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
124
121
|
"text/vcard",
|
|
125
122
|
"text/x-vcard",
|
|
@@ -134,6 +131,13 @@ var isVCardAttachment = (mimeType, fileName) => {
|
|
|
134
131
|
}
|
|
135
132
|
return Boolean(fileName?.toLowerCase().endsWith(".vcf"));
|
|
136
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";
|
|
137
141
|
var readLocalAttachment = async (att) => {
|
|
138
142
|
if (!att.localPath) {
|
|
139
143
|
throw new Error(
|
|
@@ -162,6 +166,9 @@ var toVCardContent = async (att) => {
|
|
|
162
166
|
return toAttachmentContent(att);
|
|
163
167
|
}
|
|
164
168
|
};
|
|
169
|
+
var localAttachmentContent = async (att) => isVCardAttachment(att.mimeType, att.fileName) ? await toVCardContent(att) : toAttachmentContent(att);
|
|
170
|
+
|
|
171
|
+
// src/providers/imessage/local/inbound.ts
|
|
165
172
|
var toMessages = async (message) => {
|
|
166
173
|
const { chatId, chatKind } = message;
|
|
167
174
|
if (!chatId || chatKind === "unknown") {
|
|
@@ -180,7 +187,7 @@ var toMessages = async (message) => {
|
|
|
180
187
|
message.attachments.map(async (att) => ({
|
|
181
188
|
...base,
|
|
182
189
|
id: `${message.id}:${att.id}`,
|
|
183
|
-
content:
|
|
190
|
+
content: await localAttachmentContent(att)
|
|
184
191
|
}))
|
|
185
192
|
);
|
|
186
193
|
}
|
|
@@ -212,10 +219,23 @@ var messages = (client) => stream((emit, end) => {
|
|
|
212
219
|
});
|
|
213
220
|
};
|
|
214
221
|
});
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
};
|
|
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
|
+
});
|
|
219
239
|
var sendTempFile = async (client, spaceId, name, data) => {
|
|
220
240
|
const safeName = basename(name) || DEFAULT_ATTACHMENT_NAME;
|
|
221
241
|
const dir = await mkdtemp(join(tmpdir(), "spectrum-"));
|
|
@@ -246,246 +266,181 @@ var send = async (client, spaceId, content) => {
|
|
|
246
266
|
);
|
|
247
267
|
return synthSendResult();
|
|
248
268
|
}
|
|
269
|
+
case "poll":
|
|
270
|
+
throw unsupportedLocalContent("poll");
|
|
249
271
|
default:
|
|
250
|
-
throw
|
|
272
|
+
throw unsupportedLocalContent(content.type);
|
|
251
273
|
}
|
|
252
274
|
};
|
|
253
275
|
var getMessage = async (_client, _id) => void 0;
|
|
254
276
|
|
|
255
|
-
// src/providers/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);
|
|
281
|
+
|
|
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);
|
|
289
|
+
}
|
|
290
|
+
return remote;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/providers/imessage/remote/inbound.ts
|
|
256
294
|
import {
|
|
257
|
-
chatGuid,
|
|
258
295
|
messageGuid,
|
|
259
|
-
|
|
296
|
+
NotFoundError
|
|
260
297
|
} from "@photon-ai/advanced-imessage";
|
|
261
298
|
|
|
262
|
-
// src/
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
"M4B ",
|
|
270
|
-
"M4P ",
|
|
271
|
-
"mp42",
|
|
272
|
-
"mp41",
|
|
273
|
-
"isom",
|
|
274
|
-
"iso2"
|
|
275
|
-
]);
|
|
276
|
-
var M4A_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
277
|
-
"audio/mp4",
|
|
278
|
-
"audio/mp4a-latm",
|
|
279
|
-
"audio/x-m4a",
|
|
280
|
-
"audio/aac",
|
|
281
|
-
"audio/aacp"
|
|
282
|
-
]);
|
|
283
|
-
var FFMPEG_MISSING_MESSAGE = "voice content: input is not m4a/aac and ffmpeg is unavailable. Install `ffmpeg-static` or ensure `ffmpeg` is on PATH.";
|
|
284
|
-
var isM4a = (buffer) => {
|
|
285
|
-
if (buffer.length < 12) {
|
|
286
|
-
return false;
|
|
287
|
-
}
|
|
288
|
-
if (buffer.toString("ascii", 4, 8) !== "ftyp") {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
return M4A_BRANDS.has(buffer.toString("ascii", 8, 12));
|
|
292
|
-
};
|
|
293
|
-
var isM4aMimeType = (mimeType) => M4A_MIME_TYPES.has(mimeType.toLowerCase());
|
|
294
|
-
var cachedFfmpegPath;
|
|
295
|
-
var tryStaticBinary = async () => {
|
|
296
|
-
try {
|
|
297
|
-
const mod = await import("ffmpeg-static");
|
|
298
|
-
return mod.default ?? void 0;
|
|
299
|
-
} catch {
|
|
300
|
-
return void 0;
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
var resolveFfmpegPath = async () => {
|
|
304
|
-
if (cachedFfmpegPath) {
|
|
305
|
-
return cachedFfmpegPath;
|
|
306
|
-
}
|
|
307
|
-
cachedFfmpegPath = await tryStaticBinary() ?? "ffmpeg";
|
|
308
|
-
return cachedFfmpegPath;
|
|
309
|
-
};
|
|
310
|
-
var collectStream = (stream2) => {
|
|
311
|
-
if (!stream2) {
|
|
312
|
-
return Promise.resolve("");
|
|
299
|
+
// src/providers/imessage/cache.ts
|
|
300
|
+
var DEFAULT_MAX = 1e3;
|
|
301
|
+
var MessageCache = class {
|
|
302
|
+
map = /* @__PURE__ */ new Map();
|
|
303
|
+
max;
|
|
304
|
+
constructor(max = DEFAULT_MAX) {
|
|
305
|
+
this.max = max;
|
|
313
306
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
stream2.on("data", (chunk) => {
|
|
317
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
318
|
-
});
|
|
319
|
-
stream2.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
320
|
-
stream2.on("error", reject);
|
|
321
|
-
});
|
|
322
|
-
};
|
|
323
|
-
var isMissingBinaryError = (err) => err?.code === "ENOENT";
|
|
324
|
-
var runFfmpeg = (ffmpegPath, args) => {
|
|
325
|
-
const proc = spawn(ffmpegPath, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
326
|
-
const stderr = collectStream(proc.stderr);
|
|
327
|
-
const exit = new Promise((resolve, reject) => {
|
|
328
|
-
proc.on(
|
|
329
|
-
"error",
|
|
330
|
-
(err) => reject(
|
|
331
|
-
isMissingBinaryError(err) ? new Error(FFMPEG_MISSING_MESSAGE) : err
|
|
332
|
-
)
|
|
333
|
-
);
|
|
334
|
-
proc.on("exit", (code) => resolve(code ?? -1));
|
|
335
|
-
});
|
|
336
|
-
return Promise.all([exit, stderr]).then(([code, text]) => ({
|
|
337
|
-
code,
|
|
338
|
-
stderr: text
|
|
339
|
-
}));
|
|
340
|
-
};
|
|
341
|
-
var DURATION_PATTERN = /Duration:\s*(\d+):(\d{2}):(\d{2})(?:\.(\d{1,3}))?/;
|
|
342
|
-
var parseDuration = (stderr) => {
|
|
343
|
-
const match = stderr.match(DURATION_PATTERN);
|
|
344
|
-
if (!match) {
|
|
345
|
-
return void 0;
|
|
307
|
+
get(id) {
|
|
308
|
+
return this.map.get(id);
|
|
346
309
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
await writeFile2(inPath, buffer);
|
|
358
|
-
const { code, stderr } = await runFfmpeg(ffmpeg, [
|
|
359
|
-
"-y",
|
|
360
|
-
"-i",
|
|
361
|
-
inPath,
|
|
362
|
-
"-f",
|
|
363
|
-
"ipod",
|
|
364
|
-
"-c:a",
|
|
365
|
-
"aac",
|
|
366
|
-
outPath
|
|
367
|
-
]);
|
|
368
|
-
if (code !== 0) {
|
|
369
|
-
throw new Error(`ffmpeg conversion failed (exit ${code}): ${stderr}`);
|
|
310
|
+
set(id, message) {
|
|
311
|
+
if (this.map.has(id)) {
|
|
312
|
+
this.map.delete(id);
|
|
313
|
+
}
|
|
314
|
+
this.map.set(id, message);
|
|
315
|
+
if (this.map.size > this.max) {
|
|
316
|
+
const first = this.map.keys().next().value;
|
|
317
|
+
if (first !== void 0) {
|
|
318
|
+
this.map.delete(first);
|
|
319
|
+
}
|
|
370
320
|
}
|
|
371
|
-
const out = await readFile2(outPath);
|
|
372
|
-
return { buffer: out, duration: parseDuration(stderr) };
|
|
373
|
-
} finally {
|
|
374
|
-
await rm2(dir, { recursive: true, force: true }).catch(() => {
|
|
375
|
-
});
|
|
376
321
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (isM4aMimeType(mimeType) || isM4a(buffer)) {
|
|
380
|
-
return { buffer };
|
|
322
|
+
clear() {
|
|
323
|
+
this.map.clear();
|
|
381
324
|
}
|
|
382
|
-
return transcodeToM4a(buffer);
|
|
383
325
|
};
|
|
384
|
-
|
|
385
|
-
// src/providers/imessage/cache.ts
|
|
386
|
-
var DEFAULT_MAX = 1e3;
|
|
387
|
-
var MessageCache = class {
|
|
326
|
+
var PollCache = class {
|
|
388
327
|
map = /* @__PURE__ */ new Map();
|
|
389
328
|
max;
|
|
329
|
+
selectionEventTimesByPoll = /* @__PURE__ */ new Map();
|
|
330
|
+
selectionsByPoll = /* @__PURE__ */ new Map();
|
|
390
331
|
constructor(max = DEFAULT_MAX) {
|
|
391
332
|
this.max = max;
|
|
392
333
|
}
|
|
393
334
|
get(id) {
|
|
394
335
|
return this.map.get(id);
|
|
395
336
|
}
|
|
396
|
-
set(id,
|
|
337
|
+
set(id, poll) {
|
|
397
338
|
if (this.map.has(id)) {
|
|
398
339
|
this.map.delete(id);
|
|
399
340
|
}
|
|
400
|
-
this.map.set(id,
|
|
341
|
+
this.map.set(id, poll);
|
|
401
342
|
if (this.map.size > this.max) {
|
|
402
343
|
const first = this.map.keys().next().value;
|
|
403
344
|
if (first !== void 0) {
|
|
404
345
|
this.map.delete(first);
|
|
346
|
+
this.selectionEventTimesByPoll.delete(first);
|
|
347
|
+
this.selectionsByPoll.delete(first);
|
|
405
348
|
}
|
|
406
349
|
}
|
|
407
350
|
}
|
|
408
351
|
clear() {
|
|
409
352
|
this.map.clear();
|
|
353
|
+
this.selectionEventTimesByPoll.clear();
|
|
354
|
+
this.selectionsByPoll.clear();
|
|
355
|
+
}
|
|
356
|
+
actorSelectionDeltas(pollId, actorId, optionIds) {
|
|
357
|
+
const previous = this.selectionsByPoll.get(pollId)?.get(actorId);
|
|
358
|
+
if (!previous) {
|
|
359
|
+
return optionIds.map((optionId) => ({ optionId, selected: true }));
|
|
360
|
+
}
|
|
361
|
+
const current = new Set(optionIds);
|
|
362
|
+
const selected = optionIds.filter((optionId) => !previous.has(optionId)).map((optionId) => ({ optionId, selected: true }));
|
|
363
|
+
const deselected = [...previous].filter((optionId) => !current.has(optionId)).map((optionId) => ({ optionId, selected: false }));
|
|
364
|
+
return [...selected, ...deselected];
|
|
365
|
+
}
|
|
366
|
+
clearedActorSelectionDeltas(pollId, actorId) {
|
|
367
|
+
const previous = this.selectionsByPoll.get(pollId)?.get(actorId);
|
|
368
|
+
if (!previous) {
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
return [...previous].map((optionId) => ({ optionId, selected: false }));
|
|
372
|
+
}
|
|
373
|
+
actorSelection(pollId, actorId) {
|
|
374
|
+
const selection = this.selectionsByPoll.get(pollId)?.get(actorId);
|
|
375
|
+
return selection ? [...selection] : void 0;
|
|
376
|
+
}
|
|
377
|
+
commitActorSelection(pollId, actorId, optionIds, at) {
|
|
378
|
+
let selections = this.selectionsByPoll.get(pollId);
|
|
379
|
+
if (!selections) {
|
|
380
|
+
selections = /* @__PURE__ */ new Map();
|
|
381
|
+
this.selectionsByPoll.set(pollId, selections);
|
|
382
|
+
}
|
|
383
|
+
selections.set(actorId, new Set(optionIds));
|
|
384
|
+
if (!at) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
let eventTimes = this.selectionEventTimesByPoll.get(pollId);
|
|
388
|
+
if (!eventTimes) {
|
|
389
|
+
eventTimes = /* @__PURE__ */ new Map();
|
|
390
|
+
this.selectionEventTimesByPoll.set(pollId, eventTimes);
|
|
391
|
+
}
|
|
392
|
+
const eventTime = at.getTime();
|
|
393
|
+
const previousTime = eventTimes.get(actorId);
|
|
394
|
+
if (previousTime === void 0 || eventTime >= previousTime) {
|
|
395
|
+
eventTimes.set(actorId, eventTime);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
isStaleActorSelectionEvent(pollId, actorId, at) {
|
|
399
|
+
const previousTime = this.selectionEventTimesByPoll.get(pollId)?.get(actorId);
|
|
400
|
+
return previousTime !== void 0 && at.getTime() < previousTime;
|
|
410
401
|
}
|
|
411
402
|
};
|
|
412
|
-
var
|
|
403
|
+
var messageCaches = /* @__PURE__ */ new WeakMap();
|
|
404
|
+
var pollCaches = /* @__PURE__ */ new WeakMap();
|
|
413
405
|
var getMessageCache = (owner) => {
|
|
414
|
-
let cache =
|
|
406
|
+
let cache = messageCaches.get(owner);
|
|
415
407
|
if (!cache) {
|
|
416
408
|
cache = new MessageCache();
|
|
417
|
-
|
|
409
|
+
messageCaches.set(owner, cache);
|
|
418
410
|
}
|
|
419
411
|
return cache;
|
|
420
412
|
};
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
"attachment",
|
|
427
|
-
"contact",
|
|
428
|
-
"voice"
|
|
429
|
-
]);
|
|
430
|
-
var unsupportedContent = (type, detail) => UnsupportedError.content(type, PLATFORM, detail);
|
|
431
|
-
var toSendResult = (receipt) => ({
|
|
432
|
-
id: receipt.guid,
|
|
433
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
434
|
-
});
|
|
435
|
-
var VCARD_MIME_TYPES2 = /* @__PURE__ */ new Set([
|
|
436
|
-
"text/vcard",
|
|
437
|
-
"text/x-vcard",
|
|
438
|
-
"text/directory",
|
|
439
|
-
"application/vcard",
|
|
440
|
-
"application/x-vcard"
|
|
441
|
-
]);
|
|
442
|
-
var isVCardAttachment2 = (mimeType, fileName) => {
|
|
443
|
-
if (mimeType && VCARD_MIME_TYPES2.has(mimeType.toLowerCase())) {
|
|
444
|
-
return true;
|
|
413
|
+
var getPollCache = (owner) => {
|
|
414
|
+
let cache = pollCaches.get(owner);
|
|
415
|
+
if (!cache) {
|
|
416
|
+
cache = new PollCache();
|
|
417
|
+
pollCaches.set(owner, cache);
|
|
445
418
|
}
|
|
446
|
-
return
|
|
447
|
-
};
|
|
448
|
-
var EMOJI_TO_TAPBACK = {
|
|
449
|
-
"\u2764\uFE0F": Reaction.love,
|
|
450
|
-
"\u{1F44D}": Reaction.like,
|
|
451
|
-
"\u{1F44E}": Reaction.dislike,
|
|
452
|
-
"\u{1F602}": Reaction.laugh,
|
|
453
|
-
"\u203C\uFE0F": Reaction.emphasize,
|
|
454
|
-
"\u2753": Reaction.question
|
|
419
|
+
return cache;
|
|
455
420
|
};
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
)
|
|
459
|
-
var
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
"2005": Reaction.question,
|
|
466
|
-
"2006": Reaction.emoji,
|
|
467
|
-
"2007": Reaction.sticker
|
|
421
|
+
|
|
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 };
|
|
468
430
|
};
|
|
469
|
-
var
|
|
470
|
-
|
|
471
|
-
if (
|
|
472
|
-
return emoji;
|
|
473
|
-
}
|
|
474
|
-
if (!type) {
|
|
431
|
+
var parseChildId = (id) => {
|
|
432
|
+
const match = id.match(PART_PREFIX);
|
|
433
|
+
if (!match) {
|
|
475
434
|
return null;
|
|
476
435
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const direct = message.associatedMessageType;
|
|
482
|
-
if (typeof direct === "string") {
|
|
483
|
-
return direct;
|
|
484
|
-
}
|
|
485
|
-
const raw = message._raw;
|
|
486
|
-
const fromRaw = raw?.associatedMessageType;
|
|
487
|
-
return typeof fromRaw === "string" ? fromRaw : void 0;
|
|
436
|
+
return {
|
|
437
|
+
parentGuid: id.replace(PART_PREFIX, ""),
|
|
438
|
+
partIndex: Number(match[1])
|
|
439
|
+
};
|
|
488
440
|
};
|
|
441
|
+
|
|
442
|
+
// src/providers/imessage/remote/inbound.ts
|
|
443
|
+
var URL_BALLOON_BUNDLE_ID = "com.apple.messages.URLBalloonProvider";
|
|
489
444
|
var getBalloonBundleId = (message) => {
|
|
490
445
|
const raw = message._raw;
|
|
491
446
|
const id = raw?.balloonBundleId;
|
|
@@ -499,6 +454,31 @@ var resolveChatGuid = (message, hint) => {
|
|
|
499
454
|
return first ?? "";
|
|
500
455
|
};
|
|
501
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
|
+
});
|
|
502
482
|
var toAttachmentContent2 = (client, info) => asAttachment({
|
|
503
483
|
name: info.fileName,
|
|
504
484
|
mimeType: info.mimeType,
|
|
@@ -510,22 +490,15 @@ var toVCardContent2 = async (client, info) => {
|
|
|
510
490
|
try {
|
|
511
491
|
const buf = Buffer.from(await client.attachments.downloadBuffer(info.guid));
|
|
512
492
|
return asContact(fromVCard(buf.toString("utf8")));
|
|
513
|
-
} 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
|
+
);
|
|
514
498
|
return toAttachmentContent2(client, info);
|
|
515
499
|
}
|
|
516
500
|
};
|
|
517
|
-
var attachmentContent = async (client, info) =>
|
|
518
|
-
var baseShape = (message, chatGuidHint, timestamp) => {
|
|
519
|
-
const chat = resolveChatGuid(message, chatGuidHint);
|
|
520
|
-
return {
|
|
521
|
-
sender: { id: resolveSenderId(message) },
|
|
522
|
-
space: {
|
|
523
|
-
id: chat,
|
|
524
|
-
type: chat.includes(";+;") ? "group" : "dm"
|
|
525
|
-
},
|
|
526
|
-
timestamp
|
|
527
|
-
};
|
|
528
|
-
};
|
|
501
|
+
var attachmentContent = async (client, info) => isVCardAttachment(info.mimeType, info.fileName) ? await toVCardContent2(client, info) : toAttachmentContent2(client, info);
|
|
529
502
|
var buildAttachmentMessage = async (client, base, info, id, partIndex, parentId) => {
|
|
530
503
|
const content = await attachmentContent(client, info);
|
|
531
504
|
const msg = { ...base, id, content, partIndex };
|
|
@@ -534,10 +507,26 @@ var buildAttachmentMessage = async (client, base, info, id, partIndex, parentId)
|
|
|
534
507
|
}
|
|
535
508
|
return msg;
|
|
536
509
|
};
|
|
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
|
+
};
|
|
537
526
|
var rebuildFromAppleMessage = async (client, message, chatGuidHint) => {
|
|
538
527
|
const messageGuidStr = message.guid;
|
|
539
528
|
const timestamp = message.dateCreated ?? /* @__PURE__ */ new Date();
|
|
540
|
-
const base =
|
|
529
|
+
const base = buildMessageBase(message, chatGuidHint, timestamp);
|
|
541
530
|
if (message.attachments.length === 1) {
|
|
542
531
|
const info = message.attachments[0];
|
|
543
532
|
if (!info) {
|
|
@@ -566,22 +555,13 @@ var rebuildFromAppleMessage = async (client, message, chatGuidHint) => {
|
|
|
566
555
|
return {
|
|
567
556
|
...base,
|
|
568
557
|
id: messageGuidStr,
|
|
569
|
-
content:
|
|
558
|
+
content: asProviderGroup(items)
|
|
570
559
|
};
|
|
571
560
|
}
|
|
572
|
-
const text = message.text;
|
|
573
561
|
if (getBalloonBundleId(message) === URL_BALLOON_BUNDLE_ID) {
|
|
574
|
-
|
|
575
|
-
try {
|
|
576
|
-
return { ...base, id: messageGuidStr, content: asRichlink({ url }) };
|
|
577
|
-
} catch {
|
|
578
|
-
return {
|
|
579
|
-
...base,
|
|
580
|
-
id: messageGuidStr,
|
|
581
|
-
content: url ? asText(url) : asCustom(message)
|
|
582
|
-
};
|
|
583
|
-
}
|
|
562
|
+
return toRichlinkMessage(message, base, messageGuidStr);
|
|
584
563
|
}
|
|
564
|
+
const text = message.text;
|
|
585
565
|
return {
|
|
586
566
|
...base,
|
|
587
567
|
id: messageGuidStr,
|
|
@@ -592,403 +572,1116 @@ var cacheMessage = (cache, message) => {
|
|
|
592
572
|
cache.set(message.id, message);
|
|
593
573
|
if (message.content.type === "group") {
|
|
594
574
|
for (const item of message.content.items) {
|
|
595
|
-
|
|
575
|
+
if (isIMessageMessage(item)) {
|
|
576
|
+
cache.set(item.id, item);
|
|
577
|
+
}
|
|
596
578
|
}
|
|
597
579
|
}
|
|
598
580
|
};
|
|
599
|
-
var
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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 = {
|
|
605
623
|
...base,
|
|
606
|
-
id,
|
|
607
|
-
content:
|
|
624
|
+
id: messageGuidStr,
|
|
625
|
+
content: asProviderGroup(items)
|
|
608
626
|
};
|
|
627
|
+
cacheMessage(cache, parent);
|
|
628
|
+
return [parent];
|
|
609
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];
|
|
610
638
|
};
|
|
611
|
-
var
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
+
}
|
|
618
676
|
};
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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) {
|
|
622
711
|
return null;
|
|
623
712
|
}
|
|
713
|
+
const kind = TAPBACK_CODE_TO_KIND[type] ?? type;
|
|
714
|
+
return TAPBACK_TO_EMOJI[kind] ?? null;
|
|
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
|
+
});
|
|
730
|
+
var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
|
|
731
|
+
let candidate = cache.get(strippedGuid);
|
|
732
|
+
if (!candidate) {
|
|
733
|
+
try {
|
|
734
|
+
const fetched = await client.messages.get(messageGuid2(strippedGuid));
|
|
735
|
+
candidate = await rebuildFromAppleMessage(client, fetched);
|
|
736
|
+
cacheMessage(cache, candidate);
|
|
737
|
+
} catch {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (candidate.content.type === "group") {
|
|
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;
|
|
748
|
+
}
|
|
749
|
+
return candidate;
|
|
750
|
+
};
|
|
751
|
+
var toReactionMessages = async (client, cache, event, target) => {
|
|
752
|
+
const type = getAssociatedMessageType(event.message);
|
|
753
|
+
if (type && isTapbackRemoval(type)) {
|
|
754
|
+
return [];
|
|
755
|
+
}
|
|
756
|
+
const emoji = resolveReactionEmoji(
|
|
757
|
+
type,
|
|
758
|
+
event.message.associatedMessageEmoji
|
|
759
|
+
);
|
|
760
|
+
if (!emoji) {
|
|
761
|
+
return [];
|
|
762
|
+
}
|
|
763
|
+
const { guid: strippedGuid, partIndex } = parseTapbackTarget(target);
|
|
764
|
+
const resolved = await resolveReactionTarget(
|
|
765
|
+
client,
|
|
766
|
+
cache,
|
|
767
|
+
strippedGuid,
|
|
768
|
+
partIndex
|
|
769
|
+
);
|
|
770
|
+
if (!resolved) {
|
|
771
|
+
return [];
|
|
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);
|
|
778
|
+
return [
|
|
779
|
+
{
|
|
780
|
+
...base,
|
|
781
|
+
id: messageId,
|
|
782
|
+
content: asProviderReaction(emoji, resolved)
|
|
783
|
+
}
|
|
784
|
+
];
|
|
785
|
+
};
|
|
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);
|
|
796
|
+
}
|
|
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;
|
|
830
|
+
}
|
|
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}`);
|
|
913
|
+
}
|
|
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"
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
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;
|
|
1230
|
+
}
|
|
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
|
|
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);
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
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
|
|
1334
|
+
var isVotedPollEvent = (event) => event.delta.type === "voted";
|
|
1335
|
+
var isUnvotedPollEvent = (event) => event.delta.type === "unvoted";
|
|
1336
|
+
var toCachedPoll = (input) => {
|
|
1337
|
+
const poll = asPoll({
|
|
1338
|
+
title: input.title,
|
|
1339
|
+
options: input.options.map((optionInfo) => ({
|
|
1340
|
+
title: optionInfo.text
|
|
1341
|
+
}))
|
|
1342
|
+
});
|
|
1343
|
+
const optionsByIdentifier = /* @__PURE__ */ new Map();
|
|
1344
|
+
for (const [index, optionInfo] of input.options.entries()) {
|
|
1345
|
+
const option = poll.options[index];
|
|
1346
|
+
if (option && optionInfo.optionIdentifier) {
|
|
1347
|
+
optionsByIdentifier.set(optionInfo.optionIdentifier, option);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return { poll, optionsByIdentifier };
|
|
1351
|
+
};
|
|
1352
|
+
var cachePollInfo = (cache, info) => {
|
|
1353
|
+
const cached = toCachedPoll(info);
|
|
1354
|
+
cache.set(info.messageGuid, cached);
|
|
1355
|
+
return cached;
|
|
1356
|
+
};
|
|
1357
|
+
var cachePollEvent = (cache, event) => {
|
|
1358
|
+
if (event.delta.type === "created" || event.delta.type === "optionAdded") {
|
|
1359
|
+
try {
|
|
1360
|
+
const cached = toCachedPoll({
|
|
1361
|
+
title: event.delta.title,
|
|
1362
|
+
options: event.delta.options
|
|
1363
|
+
});
|
|
1364
|
+
cache.set(event.pollMessageGuid, cached);
|
|
1365
|
+
return cached;
|
|
1366
|
+
} catch (e) {
|
|
1367
|
+
console.error("[spectrum-ts][imessage][poll] failed to cache poll", e);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
var fetchPollInfo = async (client, cache, event) => {
|
|
1372
|
+
try {
|
|
1373
|
+
const info = await client.polls.get(event.pollMessageGuid);
|
|
1374
|
+
cachePollInfo(cache, info);
|
|
1375
|
+
return info;
|
|
1376
|
+
} catch (e) {
|
|
1377
|
+
console.error("[spectrum-ts][imessage][poll] failed to fetch poll", e);
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
var resolvePoll = async (client, cache, event) => {
|
|
1382
|
+
const pollId = event.pollMessageGuid;
|
|
1383
|
+
const cached = cache.get(pollId);
|
|
1384
|
+
if (cached) {
|
|
1385
|
+
return cached;
|
|
1386
|
+
}
|
|
1387
|
+
try {
|
|
1388
|
+
const info = await client.polls.get(event.pollMessageGuid);
|
|
1389
|
+
return cachePollInfo(cache, info);
|
|
1390
|
+
} catch (e) {
|
|
1391
|
+
console.error("[spectrum-ts][imessage][poll] failed to resolve poll", e);
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
var buildPollOptionMessage = (input) => {
|
|
1396
|
+
const option = input.cached.optionsByIdentifier.get(input.optionId);
|
|
1397
|
+
if (!option) {
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
const action = input.selected ? "selected" : "deselected";
|
|
1401
|
+
return {
|
|
1402
|
+
id: `${input.event.pollMessageGuid}:${input.senderAddress}:${input.optionId}:${action}:${input.event.at.getTime()}`,
|
|
1403
|
+
sender: { id: input.senderAddress },
|
|
1404
|
+
space: {
|
|
1405
|
+
id: input.chatGuid,
|
|
1406
|
+
type: input.chatGuid.includes(";+;") ? "group" : "dm"
|
|
1407
|
+
},
|
|
1408
|
+
timestamp: input.event.at,
|
|
1409
|
+
content: asPollOption({
|
|
1410
|
+
option,
|
|
1411
|
+
poll: input.cached.poll,
|
|
1412
|
+
selected: input.selected
|
|
1413
|
+
})
|
|
1414
|
+
};
|
|
1415
|
+
};
|
|
1416
|
+
var buildPollOptionMessages = (input) => {
|
|
1417
|
+
const messages5 = [];
|
|
1418
|
+
for (const delta of input.deltas) {
|
|
1419
|
+
const message = buildPollOptionMessage({
|
|
1420
|
+
cached: input.cached,
|
|
1421
|
+
chatGuid: input.chatGuid,
|
|
1422
|
+
event: input.event,
|
|
1423
|
+
optionId: delta.optionId,
|
|
1424
|
+
selected: delta.selected,
|
|
1425
|
+
senderAddress: input.senderAddress
|
|
1426
|
+
});
|
|
1427
|
+
if (message) {
|
|
1428
|
+
messages5.push(message);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
return messages5;
|
|
1432
|
+
};
|
|
1433
|
+
var allOptionIdsKnown = (cached, optionIds) => optionIds.every((optionId) => cached.optionsByIdentifier.has(optionId));
|
|
1434
|
+
var refreshPollMetadata = async (client, pollCache, event, fallbackOptionIds) => {
|
|
1435
|
+
const info = await fetchPollInfo(client, pollCache, event);
|
|
1436
|
+
if (!info) {
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
const refreshed = pollCache.get(info.messageGuid);
|
|
1440
|
+
if (!refreshed) {
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
624
1443
|
return {
|
|
625
|
-
|
|
626
|
-
|
|
1444
|
+
optionIds: [...fallbackOptionIds],
|
|
1445
|
+
poll: refreshed
|
|
627
1446
|
};
|
|
628
1447
|
};
|
|
629
|
-
var
|
|
630
|
-
|
|
631
|
-
if (!
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1448
|
+
var toPollVoteMessages = async (client, pollCache, event) => {
|
|
1449
|
+
const senderAddress = event.actor.address;
|
|
1450
|
+
if (!senderAddress) {
|
|
1451
|
+
return [];
|
|
1452
|
+
}
|
|
1453
|
+
const pollId = event.pollMessageGuid;
|
|
1454
|
+
if (pollCache.isStaleActorSelectionEvent(pollId, senderAddress, event.at)) {
|
|
1455
|
+
return [];
|
|
1456
|
+
}
|
|
1457
|
+
const cached = await resolvePoll(client, pollCache, event);
|
|
1458
|
+
if (!cached) {
|
|
1459
|
+
return [];
|
|
1460
|
+
}
|
|
1461
|
+
const chatGuidStr = event.chatGuid;
|
|
1462
|
+
let currentOptionIds = [...event.delta.optionIdentifiers];
|
|
1463
|
+
let resolvedPoll = cached;
|
|
1464
|
+
if (currentOptionIds.some(
|
|
1465
|
+
(optionId) => !resolvedPoll.optionsByIdentifier.has(optionId)
|
|
1466
|
+
)) {
|
|
1467
|
+
const snapshot = await refreshPollMetadata(
|
|
1468
|
+
client,
|
|
1469
|
+
pollCache,
|
|
1470
|
+
event,
|
|
1471
|
+
currentOptionIds
|
|
1472
|
+
);
|
|
1473
|
+
if (snapshot) {
|
|
1474
|
+
currentOptionIds = snapshot.optionIds;
|
|
1475
|
+
resolvedPoll = snapshot.poll;
|
|
638
1476
|
}
|
|
639
1477
|
}
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
return item ?? candidate;
|
|
1478
|
+
if (!allOptionIdsKnown(resolvedPoll, currentOptionIds)) {
|
|
1479
|
+
return [];
|
|
643
1480
|
}
|
|
644
|
-
|
|
1481
|
+
const deltas = pollCache.actorSelectionDeltas(
|
|
1482
|
+
pollId,
|
|
1483
|
+
senderAddress,
|
|
1484
|
+
currentOptionIds
|
|
1485
|
+
);
|
|
1486
|
+
const messages5 = buildPollOptionMessages({
|
|
1487
|
+
cached: resolvedPoll,
|
|
1488
|
+
chatGuid: chatGuidStr,
|
|
1489
|
+
deltas,
|
|
1490
|
+
event,
|
|
1491
|
+
senderAddress
|
|
1492
|
+
});
|
|
1493
|
+
pollCache.commitActorSelection(
|
|
1494
|
+
pollId,
|
|
1495
|
+
senderAddress,
|
|
1496
|
+
currentOptionIds,
|
|
1497
|
+
event.at
|
|
1498
|
+
);
|
|
1499
|
+
return messages5;
|
|
645
1500
|
};
|
|
646
|
-
var
|
|
647
|
-
const
|
|
648
|
-
if (
|
|
1501
|
+
var toPollUnvoteMessages = async (client, pollCache, event) => {
|
|
1502
|
+
const senderAddress = event.actor.address;
|
|
1503
|
+
if (!senderAddress) {
|
|
649
1504
|
return [];
|
|
650
1505
|
}
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
event.message.associatedMessageEmoji
|
|
654
|
-
);
|
|
655
|
-
if (!emoji) {
|
|
1506
|
+
const pollId = event.pollMessageGuid;
|
|
1507
|
+
if (pollCache.isStaleActorSelectionEvent(pollId, senderAddress, event.at)) {
|
|
656
1508
|
return [];
|
|
657
1509
|
}
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
client,
|
|
661
|
-
cache,
|
|
662
|
-
strippedGuid,
|
|
663
|
-
partIndex
|
|
664
|
-
);
|
|
665
|
-
if (!resolved) {
|
|
1510
|
+
const cached = await resolvePoll(client, pollCache, event);
|
|
1511
|
+
if (!cached) {
|
|
666
1512
|
return [];
|
|
667
1513
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1514
|
+
const chatGuidStr = event.chatGuid;
|
|
1515
|
+
const deltas = pollCache.clearedActorSelectionDeltas(pollId, senderAddress);
|
|
1516
|
+
const messages5 = buildPollOptionMessages({
|
|
1517
|
+
cached,
|
|
1518
|
+
chatGuid: chatGuidStr,
|
|
1519
|
+
deltas,
|
|
1520
|
+
event,
|
|
1521
|
+
senderAddress
|
|
1522
|
+
});
|
|
1523
|
+
pollCache.commitActorSelection(pollId, senderAddress, [], event.at);
|
|
1524
|
+
return messages5;
|
|
675
1525
|
};
|
|
676
|
-
var
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const assoc = event.message.associatedMessageGuid;
|
|
680
|
-
if (assoc) {
|
|
681
|
-
return toReactionMessage(client, cache, event, base, messageGuidStr, assoc);
|
|
1526
|
+
var toPollDeltaMessages = async (client, pollCache, event) => {
|
|
1527
|
+
if (isVotedPollEvent(event)) {
|
|
1528
|
+
return toPollVoteMessages(client, pollCache, event);
|
|
682
1529
|
}
|
|
683
|
-
if (
|
|
684
|
-
|
|
685
|
-
cacheMessage(cache, msg2);
|
|
686
|
-
return [msg2];
|
|
1530
|
+
if (isUnvotedPollEvent(event)) {
|
|
1531
|
+
return toPollUnvoteMessages(client, pollCache, event);
|
|
687
1532
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
info,
|
|
697
|
-
messageGuidStr,
|
|
698
|
-
0
|
|
699
|
-
);
|
|
700
|
-
cacheMessage(cache, msg2);
|
|
701
|
-
return [msg2];
|
|
1533
|
+
return [];
|
|
1534
|
+
};
|
|
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;
|
|
702
1541
|
}
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
for (let i = 0; i < event.message.attachments.length; i++) {
|
|
706
|
-
const info = event.message.attachments[i];
|
|
707
|
-
if (!info) {
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
|
-
items.push(
|
|
711
|
-
await buildAttachmentMessage(
|
|
712
|
-
client,
|
|
713
|
-
base,
|
|
714
|
-
info,
|
|
715
|
-
formatChildId(i, messageGuidStr),
|
|
716
|
-
i,
|
|
717
|
-
messageGuidStr
|
|
718
|
-
)
|
|
719
|
-
);
|
|
720
|
-
}
|
|
721
|
-
const parent = {
|
|
722
|
-
...base,
|
|
723
|
-
id: messageGuidStr,
|
|
724
|
-
content: asGroup({ items })
|
|
725
|
-
};
|
|
726
|
-
cacheMessage(cache, parent);
|
|
727
|
-
return [parent];
|
|
1542
|
+
if (error instanceof IMessageError) {
|
|
1543
|
+
return true;
|
|
728
1544
|
}
|
|
729
|
-
|
|
730
|
-
const msg = {
|
|
731
|
-
...base,
|
|
732
|
-
id: messageGuidStr,
|
|
733
|
-
content: text ? asText(text) : asCustom(event.message)
|
|
734
|
-
};
|
|
735
|
-
cacheMessage(cache, msg);
|
|
736
|
-
return [msg];
|
|
1545
|
+
return false;
|
|
737
1546
|
};
|
|
738
|
-
var
|
|
739
|
-
const
|
|
1547
|
+
var toMessageItem = async (client, event, cursor) => {
|
|
1548
|
+
const id = event.message.guid;
|
|
1549
|
+
if (event.message.isFromMe) {
|
|
1550
|
+
return { cursor, id, values: [] };
|
|
1551
|
+
}
|
|
740
1552
|
const cache = getMessageCache(client);
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
for await (const event of sub) {
|
|
745
|
-
if (event.message.isFromMe) {
|
|
746
|
-
continue;
|
|
747
|
-
}
|
|
748
|
-
for (const message of await toMessages2(client, cache, event)) {
|
|
749
|
-
await emit(message);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
end();
|
|
753
|
-
} catch (e) {
|
|
754
|
-
end(e);
|
|
755
|
-
}
|
|
756
|
-
})();
|
|
757
|
-
return async () => {
|
|
758
|
-
sub.close();
|
|
759
|
-
await pump;
|
|
760
|
-
};
|
|
761
|
-
});
|
|
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 };
|
|
762
1556
|
};
|
|
763
|
-
var
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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")
|
|
767
1563
|
});
|
|
768
|
-
var
|
|
769
|
-
|
|
770
|
-
return `${base.replace(/[^a-zA-Z0-9_\-.]/g, "_")}.vcf`;
|
|
771
|
-
};
|
|
772
|
-
var sendContactAttachment = async (remote, content) => {
|
|
773
|
-
const vcf = await toVCard(content);
|
|
774
|
-
const upload = await sendVCardAttachment(remote, vcardFileName2(content), vcf);
|
|
775
|
-
return upload.guid;
|
|
1564
|
+
var logPollStreamError = (error) => {
|
|
1565
|
+
console.error("[spectrum-ts][imessage][poll] stream failed", error);
|
|
776
1566
|
};
|
|
777
|
-
var
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (!remote) {
|
|
1567
|
+
var emitPollMessages = async (client, pollCache, event, emit) => {
|
|
1568
|
+
cachePollEvent(pollCache, event);
|
|
1569
|
+
if (event.actor.isFromMe) {
|
|
781
1570
|
return;
|
|
782
1571
|
}
|
|
783
|
-
await
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
const remote = clients[0];
|
|
787
|
-
if (!remote) {
|
|
788
|
-
return;
|
|
1572
|
+
const messages5 = await toPollDeltaMessages(client, pollCache, event);
|
|
1573
|
+
for (const vote of messages5) {
|
|
1574
|
+
await emit(vote);
|
|
789
1575
|
}
|
|
790
|
-
await remote.chats.stopTyping(chatGuid(spaceId));
|
|
791
1576
|
};
|
|
792
|
-
var
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
case "richlink":
|
|
797
|
-
return toSendResult(
|
|
798
|
-
await remote.messages.send(chat, content.url, { richLink: true })
|
|
799
|
-
);
|
|
800
|
-
case "attachment": {
|
|
801
|
-
const attachment = await remote.attachments.upload({
|
|
802
|
-
data: await content.read(),
|
|
803
|
-
fileName: content.name,
|
|
804
|
-
mimeType: content.mimeType
|
|
805
|
-
});
|
|
806
|
-
return toSendResult(
|
|
807
|
-
await remote.messages.send(chat, "", { attachment: attachment.guid })
|
|
808
|
-
);
|
|
809
|
-
}
|
|
810
|
-
case "contact": {
|
|
811
|
-
const attachment = await sendContactAttachment(remote, content);
|
|
812
|
-
return toSendResult(await remote.messages.send(chat, "", { attachment }));
|
|
813
|
-
}
|
|
814
|
-
case "voice": {
|
|
815
|
-
const { buffer } = await ensureM4a(
|
|
816
|
-
await content.read(),
|
|
817
|
-
content.mimeType
|
|
818
|
-
);
|
|
819
|
-
const attachment = await remote.attachments.upload({
|
|
820
|
-
data: buffer,
|
|
821
|
-
fileName: content.name ?? "voice.m4a",
|
|
822
|
-
mimeType: "audio/x-m4a"
|
|
823
|
-
});
|
|
824
|
-
return toSendResult(
|
|
825
|
-
await remote.messages.send(chat, "", {
|
|
826
|
-
attachment: attachment.guid,
|
|
827
|
-
audioMessage: true
|
|
828
|
-
})
|
|
829
|
-
);
|
|
830
|
-
}
|
|
831
|
-
default:
|
|
832
|
-
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);
|
|
833
1581
|
}
|
|
834
1582
|
};
|
|
835
|
-
var
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
if (!GROUP_ITEM_ALLOWED.has(itemType)) {
|
|
845
|
-
throw unsupportedContent(
|
|
846
|
-
"group",
|
|
847
|
-
`"${itemType}" items are not supported inside a group`
|
|
848
|
-
);
|
|
849
|
-
}
|
|
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;
|
|
850
1592
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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;
|
|
854
1604
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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
|
+
}
|
|
858
1626
|
}
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
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)]);
|
|
862
1638
|
};
|
|
863
|
-
var
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
throw new Error("No remote iMessage client available");
|
|
867
|
-
}
|
|
868
|
-
const chat = chatGuid(spaceId);
|
|
869
|
-
const replyTo = messageGuid(msgId);
|
|
870
|
-
switch (content.type) {
|
|
871
|
-
case "text":
|
|
872
|
-
return toSendResult(
|
|
873
|
-
await remote.messages.send(chat, content.text, { replyTo })
|
|
874
|
-
);
|
|
875
|
-
case "richlink":
|
|
876
|
-
return toSendResult(
|
|
877
|
-
await remote.messages.send(chat, content.url, {
|
|
878
|
-
richLink: true,
|
|
879
|
-
replyTo
|
|
880
|
-
})
|
|
881
|
-
);
|
|
882
|
-
case "attachment": {
|
|
883
|
-
const attachment = await remote.attachments.upload({
|
|
884
|
-
data: await content.read(),
|
|
885
|
-
fileName: content.name,
|
|
886
|
-
mimeType: content.mimeType
|
|
887
|
-
});
|
|
888
|
-
return toSendResult(
|
|
889
|
-
await remote.messages.send(chat, "", {
|
|
890
|
-
attachment: attachment.guid,
|
|
891
|
-
replyTo
|
|
892
|
-
})
|
|
893
|
-
);
|
|
894
|
-
}
|
|
895
|
-
case "contact": {
|
|
896
|
-
const attachment = await sendContactAttachment(remote, content);
|
|
897
|
-
return toSendResult(
|
|
898
|
-
await remote.messages.send(chat, "", { attachment, replyTo })
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
case "voice": {
|
|
902
|
-
const { buffer } = await ensureM4a(
|
|
903
|
-
await content.read(),
|
|
904
|
-
content.mimeType
|
|
905
|
-
);
|
|
906
|
-
const attachment = await remote.attachments.upload({
|
|
907
|
-
data: buffer,
|
|
908
|
-
fileName: content.name ?? "voice.m4a",
|
|
909
|
-
mimeType: "audio/x-m4a"
|
|
910
|
-
});
|
|
911
|
-
return toSendResult(
|
|
912
|
-
await remote.messages.send(chat, "", {
|
|
913
|
-
attachment: attachment.guid,
|
|
914
|
-
audioMessage: true,
|
|
915
|
-
replyTo
|
|
916
|
-
})
|
|
917
|
-
);
|
|
918
|
-
}
|
|
919
|
-
default:
|
|
920
|
-
throw unsupportedContent(content.type);
|
|
921
|
-
}
|
|
1639
|
+
var messages3 = (clients) => {
|
|
1640
|
+
const pollCache = getPollCache(clients);
|
|
1641
|
+
return mergeStreams(clients.map((client) => clientStream(client, pollCache)));
|
|
922
1642
|
};
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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);
|
|
932
1657
|
if (!remote) {
|
|
933
|
-
|
|
1658
|
+
return;
|
|
934
1659
|
}
|
|
935
|
-
await remote
|
|
936
|
-
chatGuid(spaceId),
|
|
937
|
-
messageGuid(msgId),
|
|
938
|
-
content.text
|
|
939
|
-
);
|
|
1660
|
+
await startTyping(remote, spaceId);
|
|
940
1661
|
};
|
|
941
|
-
var
|
|
942
|
-
const remote = clients
|
|
1662
|
+
var stopTyping2 = async (clients, spaceId) => {
|
|
1663
|
+
const remote = firstRemoteClient(clients);
|
|
943
1664
|
if (!remote) {
|
|
944
1665
|
return;
|
|
945
1666
|
}
|
|
946
|
-
|
|
947
|
-
const parentGuid = target.parentId ?? target.id;
|
|
948
|
-
const guid = messageGuid(parentGuid);
|
|
949
|
-
const opts = typeof target.partIndex === "number" ? { partIndex: target.partIndex } : void 0;
|
|
950
|
-
const native = EMOJI_TO_TAPBACK[reaction];
|
|
951
|
-
if (native) {
|
|
952
|
-
await remote.messages.react(chat, guid, native, opts);
|
|
953
|
-
} else {
|
|
954
|
-
await remote.messages.reactEmoji(chat, guid, reaction, opts);
|
|
955
|
-
}
|
|
1667
|
+
await stopTyping(remote, spaceId);
|
|
956
1668
|
};
|
|
957
|
-
var
|
|
958
|
-
|
|
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);
|
|
959
1674
|
if (!remote) {
|
|
960
1675
|
return;
|
|
961
1676
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
const childRef = parseChildId(msgId);
|
|
968
|
-
if (childRef) {
|
|
969
|
-
try {
|
|
970
|
-
const fetched = await remote.messages.get(
|
|
971
|
-
messageGuid(childRef.parentGuid)
|
|
972
|
-
);
|
|
973
|
-
const parent = await rebuildFromAppleMessage(remote, fetched, spaceId);
|
|
974
|
-
cacheMessage(cache, parent);
|
|
975
|
-
if (parent.content.type !== "group") {
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
const items = parent.content.items;
|
|
979
|
-
return items[childRef.partIndex];
|
|
980
|
-
} catch {
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
try {
|
|
985
|
-
const fetched = await remote.messages.get(messageGuid(msgId));
|
|
986
|
-
const rebuilt = await rebuildFromAppleMessage(remote, fetched, spaceId);
|
|
987
|
-
cacheMessage(cache, rebuilt);
|
|
988
|
-
return rebuilt;
|
|
989
|
-
} catch {
|
|
1677
|
+
await reactToMessage(remote, spaceId, target, reaction);
|
|
1678
|
+
};
|
|
1679
|
+
var getMessage4 = async (clients, spaceId, msgId) => {
|
|
1680
|
+
const remote = firstRemoteClient(clients);
|
|
1681
|
+
if (!remote) {
|
|
990
1682
|
return;
|
|
991
1683
|
}
|
|
1684
|
+
return getMessage3(remote, spaceId, msgId);
|
|
992
1685
|
};
|
|
993
1686
|
|
|
994
1687
|
// src/providers/imessage/types.ts
|
|
@@ -1014,6 +1707,7 @@ var messageSchema = z.object({
|
|
|
1014
1707
|
});
|
|
1015
1708
|
|
|
1016
1709
|
// src/providers/imessage/index.ts
|
|
1710
|
+
var isPollContent = (content) => content.type === "poll" || content.type === "poll_option";
|
|
1017
1711
|
var imessage = definePlatform("iMessage", {
|
|
1018
1712
|
config: configSchema,
|
|
1019
1713
|
user: {
|
|
@@ -1082,55 +1776,69 @@ var imessage = definePlatform("iMessage", {
|
|
|
1082
1776
|
}
|
|
1083
1777
|
},
|
|
1084
1778
|
events: {
|
|
1085
|
-
messages: ({ client }) => isLocal(client) ?
|
|
1779
|
+
messages: ({ client }) => isLocal(client) ? messages2(client) : messages4(client)
|
|
1086
1780
|
},
|
|
1087
1781
|
actions: {
|
|
1088
1782
|
send: async ({ space, content, client }) => {
|
|
1089
1783
|
if (isLocal(client)) {
|
|
1090
|
-
return await
|
|
1784
|
+
return await send2(client, space.id, content);
|
|
1091
1785
|
}
|
|
1092
|
-
return await
|
|
1786
|
+
return await send4(client, space.id, content);
|
|
1093
1787
|
},
|
|
1094
1788
|
startTyping: async ({ space, client }) => {
|
|
1095
1789
|
if (isLocal(client)) {
|
|
1096
1790
|
return;
|
|
1097
1791
|
}
|
|
1098
|
-
await
|
|
1792
|
+
await startTyping2(client, space.id);
|
|
1099
1793
|
},
|
|
1100
1794
|
stopTyping: async ({ space, client }) => {
|
|
1101
1795
|
if (isLocal(client)) {
|
|
1102
1796
|
return;
|
|
1103
1797
|
}
|
|
1104
|
-
await
|
|
1798
|
+
await stopTyping2(client, space.id);
|
|
1105
1799
|
},
|
|
1106
1800
|
reactToMessage: async ({ space, target, reaction, client }) => {
|
|
1107
1801
|
if (isLocal(client)) {
|
|
1108
1802
|
throw UnsupportedError.action("react", "iMessage (local mode)");
|
|
1109
1803
|
}
|
|
1110
|
-
|
|
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(
|
|
1111
1812
|
client,
|
|
1112
1813
|
space.id,
|
|
1113
1814
|
target,
|
|
1114
1815
|
reaction
|
|
1115
1816
|
);
|
|
1116
1817
|
},
|
|
1117
|
-
replyToMessage: async ({ space, messageId, content, client }) => {
|
|
1818
|
+
replyToMessage: async ({ space, messageId, target, content, client }) => {
|
|
1118
1819
|
if (isLocal(client)) {
|
|
1119
1820
|
throw UnsupportedError.action("reply", "iMessage (local mode)");
|
|
1120
1821
|
}
|
|
1121
|
-
|
|
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);
|
|
1122
1830
|
},
|
|
1123
1831
|
editMessage: async ({ space, messageId, content, client }) => {
|
|
1124
1832
|
if (isLocal(client)) {
|
|
1125
1833
|
throw UnsupportedError.action("edit", "iMessage (local mode)");
|
|
1126
1834
|
}
|
|
1127
|
-
await
|
|
1835
|
+
await editMessage2(client, space.id, messageId, content);
|
|
1128
1836
|
},
|
|
1129
1837
|
getMessage: async ({ space, messageId, client }) => {
|
|
1130
1838
|
if (isLocal(client)) {
|
|
1131
|
-
return
|
|
1839
|
+
return getMessage2(client, messageId);
|
|
1132
1840
|
}
|
|
1133
|
-
return
|
|
1841
|
+
return getMessage4(client, space.id, messageId);
|
|
1134
1842
|
}
|
|
1135
1843
|
}
|
|
1136
1844
|
});
|