spectrum-ts 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4U4RINOV.js +2133 -0
- package/dist/{chunk-FF2R4EP3.js → chunk-L6LUFBLF.js} +4 -3
- package/dist/chunk-NIIJ6U34.js +843 -0
- package/dist/chunk-NNRUJOPT.js +849 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -4
- package/dist/providers/imessage/index.d.ts +3 -3
- package/dist/providers/imessage/index.js +6 -2074
- package/dist/providers/index.d.ts +12 -0
- package/dist/providers/index.js +18 -0
- package/dist/providers/terminal/index.d.ts +7 -7
- package/dist/providers/terminal/index.js +4 -838
- package/dist/providers/whatsapp-business/index.d.ts +3 -3
- package/dist/providers/whatsapp-business/index.js +4 -844
- package/dist/{types-BcCLW2VO.d.ts → types-D0QSU6kb.d.ts} +1 -1
- package/package.json +7 -3
|
@@ -1,2078 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
asPollOption,
|
|
9
|
-
cloud,
|
|
10
|
-
mergeStreams,
|
|
11
|
-
stream
|
|
12
|
-
} from "../../chunk-FF2R4EP3.js";
|
|
13
|
-
import {
|
|
14
|
-
UnsupportedError,
|
|
15
|
-
asAttachment,
|
|
16
|
-
asContact,
|
|
17
|
-
asCustom,
|
|
18
|
-
asText,
|
|
19
|
-
attachmentSchema,
|
|
20
|
-
definePlatform,
|
|
21
|
-
fromVCard,
|
|
22
|
-
reactionSchema,
|
|
23
|
-
text,
|
|
24
|
-
textSchema,
|
|
25
|
-
toVCard
|
|
26
|
-
} from "../../chunk-LH4YEBG3.js";
|
|
27
|
-
|
|
28
|
-
// src/providers/imessage/index.ts
|
|
29
|
-
import {
|
|
30
|
-
createClient as createClient2,
|
|
31
|
-
directChat,
|
|
32
|
-
MessageEffect as MessageEffect2
|
|
33
|
-
} from "@photon-ai/advanced-imessage";
|
|
34
|
-
import { IMessageSDK as IMessageSDK2 } from "@photon-ai/imessage-kit";
|
|
35
|
-
|
|
36
|
-
// src/providers/imessage/content/effect.ts
|
|
37
|
-
import {
|
|
38
|
-
MessageEffect
|
|
39
|
-
} from "@photon-ai/advanced-imessage";
|
|
40
|
-
|
|
41
|
-
// src/content/effect.ts
|
|
42
|
-
import z from "zod";
|
|
43
|
-
var effectInnerSchema = z.discriminatedUnion("type", [
|
|
44
|
-
textSchema,
|
|
45
|
-
attachmentSchema
|
|
46
|
-
]);
|
|
47
|
-
var messageEffectSchema = z.object({
|
|
48
|
-
type: z.literal("effect"),
|
|
49
|
-
content: effectInnerSchema,
|
|
50
|
-
effect: z.string().nonempty()
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// src/providers/imessage/content/effect.ts
|
|
54
|
-
var SUPPORTED_EFFECTS = new Set(Object.values(MessageEffect));
|
|
55
|
-
var resolveContent = (input) => typeof input === "string" ? text(input).build() : input.build();
|
|
56
|
-
function effect(input, messageEffect) {
|
|
57
|
-
return {
|
|
58
|
-
build: async () => {
|
|
59
|
-
if (!SUPPORTED_EFFECTS.has(messageEffect)) {
|
|
60
|
-
throw new Error(
|
|
61
|
-
`Unsupported iMessage message effect "${messageEffect}"`
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
const inner = await resolveContent(input);
|
|
65
|
-
if (inner.type !== "text" && inner.type !== "attachment") {
|
|
66
|
-
throw new Error(
|
|
67
|
-
`imessage effect() only supports text and attachment content, got "${inner.type}"`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
return messageEffectSchema.parse({
|
|
71
|
-
type: "effect",
|
|
72
|
-
content: inner,
|
|
73
|
-
effect: messageEffect
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/providers/imessage/auth.ts
|
|
80
|
-
import { createClient } from "@photon-ai/advanced-imessage";
|
|
81
|
-
var RENEWAL_RATIO = 0.8;
|
|
82
|
-
var EXPIRY_BUFFER_MS = 3e4;
|
|
83
|
-
var RETRY_DELAY_MS = 3e4;
|
|
84
|
-
var cloudAuthState = /* @__PURE__ */ new WeakMap();
|
|
85
|
-
var requirePhone = (data, instanceId) => {
|
|
86
|
-
const phone = data.numbers?.[instanceId];
|
|
87
|
-
if (!phone) {
|
|
88
|
-
throw new Error(`iMessage instance ${instanceId} has no phone assigned`);
|
|
89
|
-
}
|
|
90
|
-
return phone;
|
|
91
|
-
};
|
|
92
|
-
async function createCloudClients(projectId, projectSecret) {
|
|
93
|
-
let tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
|
|
94
|
-
let tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
|
|
95
|
-
let disposed = false;
|
|
96
|
-
let renewalTimer;
|
|
97
|
-
if (tokenData.type === "shared") {
|
|
98
|
-
throw UnsupportedError.action(
|
|
99
|
-
"multi-phone",
|
|
100
|
-
"iMessage shared mode",
|
|
101
|
-
"use dedicated-token cloud mode"
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
const records = [];
|
|
105
|
-
const syncPhones = (data) => {
|
|
106
|
-
for (const { entry, instanceId } of records) {
|
|
107
|
-
entry.phone = requirePhone(data, instanceId);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
const scheduleRenewal = () => {
|
|
111
|
-
if (disposed) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const ttlMs = tokenData.expiresIn * 1e3;
|
|
115
|
-
const renewInMs = Math.max(ttlMs * RENEWAL_RATIO, 5e3);
|
|
116
|
-
renewalTimer = setTimeout(async () => {
|
|
117
|
-
try {
|
|
118
|
-
tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
|
|
119
|
-
if (tokenData.type === "shared") {
|
|
120
|
-
throw UnsupportedError.action(
|
|
121
|
-
"multi-phone",
|
|
122
|
-
"iMessage shared mode",
|
|
123
|
-
"use dedicated-token cloud mode"
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
|
|
127
|
-
syncPhones(tokenData);
|
|
128
|
-
scheduleRenewal();
|
|
129
|
-
} catch {
|
|
130
|
-
renewalTimer = setTimeout(() => scheduleRenewal(), RETRY_DELAY_MS);
|
|
131
|
-
renewalTimer?.unref?.();
|
|
132
|
-
}
|
|
133
|
-
}, renewInMs);
|
|
134
|
-
renewalTimer?.unref?.();
|
|
135
|
-
};
|
|
136
|
-
scheduleRenewal();
|
|
137
|
-
const refreshIfNeeded = async () => {
|
|
138
|
-
if (Date.now() < tokenExpiresAt - EXPIRY_BUFFER_MS) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
|
|
142
|
-
if (tokenData.type === "shared") {
|
|
143
|
-
throw UnsupportedError.action(
|
|
144
|
-
"multi-phone",
|
|
145
|
-
"iMessage shared mode",
|
|
146
|
-
"use dedicated-token cloud mode"
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
|
|
150
|
-
syncPhones(tokenData);
|
|
151
|
-
scheduleRenewal();
|
|
152
|
-
};
|
|
153
|
-
const dedicated = tokenData;
|
|
154
|
-
for (const [instanceId, token] of Object.entries(dedicated.auth)) {
|
|
155
|
-
const entry = {
|
|
156
|
-
phone: requirePhone(dedicated, instanceId),
|
|
157
|
-
client: createClient({
|
|
158
|
-
address: `${instanceId}.imsg.photon.codes:443`,
|
|
159
|
-
tls: true,
|
|
160
|
-
token: async () => {
|
|
161
|
-
await refreshIfNeeded();
|
|
162
|
-
const data = tokenData;
|
|
163
|
-
return data.auth[instanceId] ?? token;
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
};
|
|
167
|
-
records.push({ entry, instanceId });
|
|
168
|
-
}
|
|
169
|
-
const entries = records.map((r) => r.entry);
|
|
170
|
-
cloudAuthState.set(entries, {
|
|
171
|
-
dispose: () => {
|
|
172
|
-
disposed = true;
|
|
173
|
-
if (renewalTimer !== void 0) {
|
|
174
|
-
clearTimeout(renewalTimer);
|
|
175
|
-
renewalTimer = void 0;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
return entries;
|
|
180
|
-
}
|
|
181
|
-
async function disposeCloudAuth(clients) {
|
|
182
|
-
const auth = cloudAuthState.get(clients);
|
|
183
|
-
if (auth) {
|
|
184
|
-
auth.dispose();
|
|
185
|
-
cloudAuthState.delete(clients);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// src/providers/imessage/local/attachments.ts
|
|
190
|
-
import { createReadStream } from "fs";
|
|
191
|
-
import { readFile } from "fs/promises";
|
|
192
|
-
import { Readable } from "stream";
|
|
193
|
-
|
|
194
|
-
// src/providers/imessage/shared/vcard.ts
|
|
195
|
-
var VCARD_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
196
|
-
"text/vcard",
|
|
197
|
-
"text/x-vcard",
|
|
198
|
-
"text/directory",
|
|
199
|
-
"application/vcard",
|
|
200
|
-
"application/x-vcard"
|
|
201
|
-
]);
|
|
202
|
-
var normalizeMimeType = (mimeType) => (mimeType.split(";")[0] ?? "").trim().toLowerCase();
|
|
203
|
-
var isVCardAttachment = (mimeType, fileName) => {
|
|
204
|
-
if (mimeType && VCARD_MIME_TYPES.has(normalizeMimeType(mimeType))) {
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
return Boolean(fileName?.toLowerCase().endsWith(".vcf"));
|
|
208
|
-
};
|
|
209
|
-
var vcardFileName = (contact) => {
|
|
210
|
-
const base = contact.name?.formatted ?? contact.user?.id ?? "contact";
|
|
211
|
-
return `${base.replace(/[^a-zA-Z0-9_\-.]/g, "_")}.vcf`;
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// src/providers/imessage/local/attachments.ts
|
|
215
|
-
var DEFAULT_ATTACHMENT_NAME = "attachment";
|
|
216
|
-
var readLocalAttachment = async (att) => {
|
|
217
|
-
if (!att.localPath) {
|
|
218
|
-
throw new Error(
|
|
219
|
-
`iMessage attachment ${att.id} has no local file available on disk`
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
return readFile(att.localPath);
|
|
223
|
-
};
|
|
224
|
-
var toAttachmentContent = (att) => {
|
|
225
|
-
const { localPath } = att;
|
|
226
|
-
return asAttachment({
|
|
227
|
-
name: att.fileName ?? DEFAULT_ATTACHMENT_NAME,
|
|
228
|
-
mimeType: att.mimeType,
|
|
229
|
-
size: att.sizeBytes,
|
|
230
|
-
read: () => readLocalAttachment(att),
|
|
231
|
-
stream: localPath ? async () => Readable.toWeb(
|
|
232
|
-
createReadStream(localPath)
|
|
233
|
-
) : void 0
|
|
234
|
-
});
|
|
235
|
-
};
|
|
236
|
-
var toVCardContent = async (att) => {
|
|
237
|
-
try {
|
|
238
|
-
const buf = await readLocalAttachment(att);
|
|
239
|
-
return asContact(fromVCard(buf.toString("utf8")));
|
|
240
|
-
} catch {
|
|
241
|
-
return toAttachmentContent(att);
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
var localAttachmentContent = async (att) => isVCardAttachment(att.mimeType, att.fileName) ? await toVCardContent(att) : toAttachmentContent(att);
|
|
245
|
-
|
|
246
|
-
// src/providers/imessage/local/inbound.ts
|
|
247
|
-
var toMessages = async (message) => {
|
|
248
|
-
const { chatId, chatKind } = message;
|
|
249
|
-
if (!chatId || chatKind === "unknown") {
|
|
250
|
-
return [];
|
|
251
|
-
}
|
|
252
|
-
if (message.reaction !== null || message.kind !== "text" || message.retractedAt !== null) {
|
|
253
|
-
return [];
|
|
254
|
-
}
|
|
255
|
-
const base = {
|
|
256
|
-
sender: { id: message.participant ?? "" },
|
|
257
|
-
// Local mode has no concept of "which-of-my-phones"; phone is empty.
|
|
258
|
-
space: {
|
|
259
|
-
id: chatId,
|
|
260
|
-
type: chatKind === "group" ? "group" : "dm",
|
|
261
|
-
phone: ""
|
|
262
|
-
},
|
|
263
|
-
timestamp: message.createdAt
|
|
264
|
-
};
|
|
265
|
-
if (message.attachments.length > 0) {
|
|
266
|
-
return Promise.all(
|
|
267
|
-
message.attachments.map(async (att) => ({
|
|
268
|
-
...base,
|
|
269
|
-
id: `${message.id}:${att.id}`,
|
|
270
|
-
content: await localAttachmentContent(att)
|
|
271
|
-
}))
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
return [
|
|
275
|
-
{
|
|
276
|
-
...base,
|
|
277
|
-
id: message.id,
|
|
278
|
-
content: { type: "text", text: message.text ?? "" }
|
|
279
|
-
}
|
|
280
|
-
];
|
|
281
|
-
};
|
|
282
|
-
var messages = (client) => stream((emit, end) => {
|
|
283
|
-
let lastPromise = Promise.resolve();
|
|
284
|
-
const startPromise = client.startWatching({
|
|
285
|
-
onIncomingMessage: (message) => {
|
|
286
|
-
lastPromise = lastPromise.then(() => toMessages(message)).then(async (ms) => {
|
|
287
|
-
for (const m of ms) {
|
|
288
|
-
await emit(m);
|
|
289
|
-
}
|
|
290
|
-
}).catch(end);
|
|
291
|
-
},
|
|
292
|
-
onError: end
|
|
293
|
-
}).catch(end);
|
|
294
|
-
return async () => {
|
|
295
|
-
await startPromise.catch(() => {
|
|
296
|
-
});
|
|
297
|
-
await client.stopWatching();
|
|
298
|
-
await lastPromise.catch(() => {
|
|
299
|
-
});
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
// src/providers/imessage/local/send.ts
|
|
304
|
-
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
305
|
-
import { tmpdir } from "os";
|
|
306
|
-
import { basename, join } from "path";
|
|
307
|
-
|
|
308
|
-
// src/providers/imessage/shared/errors.ts
|
|
309
|
-
var IMESSAGE_PLATFORM = "iMessage";
|
|
310
|
-
var LOCAL_IMESSAGE_PLATFORM = "iMessage (local mode)";
|
|
311
|
-
var unsupportedRemoteContent = (type, detail) => UnsupportedError.content(type, IMESSAGE_PLATFORM, detail);
|
|
312
|
-
var unsupportedLocalContent = (type, detail) => UnsupportedError.content(type, LOCAL_IMESSAGE_PLATFORM, detail);
|
|
313
|
-
|
|
314
|
-
// src/providers/imessage/local/send.ts
|
|
315
|
-
var synthRecord = (spaceId, content) => ({
|
|
316
|
-
id: crypto.randomUUID(),
|
|
317
|
-
content,
|
|
318
|
-
space: { id: spaceId },
|
|
319
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
320
|
-
});
|
|
321
|
-
var sendTempFile = async (client, spaceId, name, data) => {
|
|
322
|
-
const safeName = basename(name) || DEFAULT_ATTACHMENT_NAME;
|
|
323
|
-
const dir = await mkdtemp(join(tmpdir(), "spectrum-"));
|
|
324
|
-
const tmp = join(dir, safeName);
|
|
325
|
-
await writeFile(tmp, data);
|
|
326
|
-
try {
|
|
327
|
-
await client.send({ to: spaceId, attachments: [tmp] });
|
|
328
|
-
} finally {
|
|
329
|
-
await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
var send = async (client, spaceId, content) => {
|
|
334
|
-
switch (content.type) {
|
|
335
|
-
case "text":
|
|
336
|
-
await client.send({ to: spaceId, text: content.text });
|
|
337
|
-
return synthRecord(spaceId, content);
|
|
338
|
-
case "attachment":
|
|
339
|
-
await sendTempFile(client, spaceId, content.name, await content.read());
|
|
340
|
-
return synthRecord(spaceId, content);
|
|
341
|
-
case "contact": {
|
|
342
|
-
const vcf = await toVCard(content);
|
|
343
|
-
await sendTempFile(
|
|
344
|
-
client,
|
|
345
|
-
spaceId,
|
|
346
|
-
vcardFileName(content),
|
|
347
|
-
Buffer.from(vcf, "utf8")
|
|
348
|
-
);
|
|
349
|
-
return synthRecord(spaceId, content);
|
|
350
|
-
}
|
|
351
|
-
case "effect":
|
|
352
|
-
throw unsupportedLocalContent(
|
|
353
|
-
"effect",
|
|
354
|
-
"message effects require remote iMessage"
|
|
355
|
-
);
|
|
356
|
-
case "poll":
|
|
357
|
-
throw unsupportedLocalContent("poll");
|
|
358
|
-
default:
|
|
359
|
-
throw unsupportedLocalContent(content.type);
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
var getMessage = async (_client, _id) => void 0;
|
|
363
|
-
|
|
364
|
-
// src/providers/imessage/local/api.ts
|
|
365
|
-
var messages2 = (client) => messages(client);
|
|
366
|
-
var send2 = async (client, spaceId, content) => send(client, spaceId, content);
|
|
367
|
-
var getMessage2 = async (client, id) => getMessage(client, id);
|
|
368
|
-
|
|
369
|
-
// src/providers/imessage/remote/inbound.ts
|
|
370
|
-
import {
|
|
371
|
-
messageGuid,
|
|
372
|
-
NotFoundError
|
|
373
|
-
} from "@photon-ai/advanced-imessage";
|
|
374
|
-
|
|
375
|
-
// src/providers/imessage/cache.ts
|
|
376
|
-
var DEFAULT_MAX = 1e3;
|
|
377
|
-
var MessageCache = class {
|
|
378
|
-
map = /* @__PURE__ */ new Map();
|
|
379
|
-
max;
|
|
380
|
-
constructor(max = DEFAULT_MAX) {
|
|
381
|
-
this.max = max;
|
|
382
|
-
}
|
|
383
|
-
get(id) {
|
|
384
|
-
return this.map.get(id);
|
|
385
|
-
}
|
|
386
|
-
set(id, message) {
|
|
387
|
-
if (this.map.has(id)) {
|
|
388
|
-
this.map.delete(id);
|
|
389
|
-
}
|
|
390
|
-
this.map.set(id, message);
|
|
391
|
-
if (this.map.size > this.max) {
|
|
392
|
-
const first = this.map.keys().next().value;
|
|
393
|
-
if (first !== void 0) {
|
|
394
|
-
this.map.delete(first);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
clear() {
|
|
399
|
-
this.map.clear();
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
var PollCache = class {
|
|
403
|
-
map = /* @__PURE__ */ new Map();
|
|
404
|
-
max;
|
|
405
|
-
selectionEventTimesByPoll = /* @__PURE__ */ new Map();
|
|
406
|
-
selectionsByPoll = /* @__PURE__ */ new Map();
|
|
407
|
-
constructor(max = DEFAULT_MAX) {
|
|
408
|
-
this.max = max;
|
|
409
|
-
}
|
|
410
|
-
get(id) {
|
|
411
|
-
return this.map.get(id);
|
|
412
|
-
}
|
|
413
|
-
set(id, poll) {
|
|
414
|
-
if (this.map.has(id)) {
|
|
415
|
-
this.map.delete(id);
|
|
416
|
-
}
|
|
417
|
-
this.map.set(id, poll);
|
|
418
|
-
if (this.map.size > this.max) {
|
|
419
|
-
const first = this.map.keys().next().value;
|
|
420
|
-
if (first !== void 0) {
|
|
421
|
-
this.map.delete(first);
|
|
422
|
-
this.selectionEventTimesByPoll.delete(first);
|
|
423
|
-
this.selectionsByPoll.delete(first);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
clear() {
|
|
428
|
-
this.map.clear();
|
|
429
|
-
this.selectionEventTimesByPoll.clear();
|
|
430
|
-
this.selectionsByPoll.clear();
|
|
431
|
-
}
|
|
432
|
-
actorSelectionDeltas(pollId, actorId, optionIds) {
|
|
433
|
-
const previous = this.selectionsByPoll.get(pollId)?.get(actorId);
|
|
434
|
-
if (!previous) {
|
|
435
|
-
return optionIds.map((optionId) => ({ optionId, selected: true }));
|
|
436
|
-
}
|
|
437
|
-
const current = new Set(optionIds);
|
|
438
|
-
const selected = optionIds.filter((optionId) => !previous.has(optionId)).map((optionId) => ({ optionId, selected: true }));
|
|
439
|
-
const deselected = [...previous].filter((optionId) => !current.has(optionId)).map((optionId) => ({ optionId, selected: false }));
|
|
440
|
-
return [...selected, ...deselected];
|
|
441
|
-
}
|
|
442
|
-
clearedActorSelectionDeltas(pollId, actorId) {
|
|
443
|
-
const previous = this.selectionsByPoll.get(pollId)?.get(actorId);
|
|
444
|
-
if (!previous) {
|
|
445
|
-
return [];
|
|
446
|
-
}
|
|
447
|
-
return [...previous].map((optionId) => ({ optionId, selected: false }));
|
|
448
|
-
}
|
|
449
|
-
actorSelection(pollId, actorId) {
|
|
450
|
-
const selection = this.selectionsByPoll.get(pollId)?.get(actorId);
|
|
451
|
-
return selection ? [...selection] : void 0;
|
|
452
|
-
}
|
|
453
|
-
commitActorSelection(pollId, actorId, optionIds, at) {
|
|
454
|
-
let selections = this.selectionsByPoll.get(pollId);
|
|
455
|
-
if (!selections) {
|
|
456
|
-
selections = /* @__PURE__ */ new Map();
|
|
457
|
-
this.selectionsByPoll.set(pollId, selections);
|
|
458
|
-
}
|
|
459
|
-
selections.set(actorId, new Set(optionIds));
|
|
460
|
-
if (!at) {
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
let eventTimes = this.selectionEventTimesByPoll.get(pollId);
|
|
464
|
-
if (!eventTimes) {
|
|
465
|
-
eventTimes = /* @__PURE__ */ new Map();
|
|
466
|
-
this.selectionEventTimesByPoll.set(pollId, eventTimes);
|
|
467
|
-
}
|
|
468
|
-
const eventTime = at.getTime();
|
|
469
|
-
const previousTime = eventTimes.get(actorId);
|
|
470
|
-
if (previousTime === void 0 || eventTime >= previousTime) {
|
|
471
|
-
eventTimes.set(actorId, eventTime);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
isStaleActorSelectionEvent(pollId, actorId, at) {
|
|
475
|
-
const previousTime = this.selectionEventTimesByPoll.get(pollId)?.get(actorId);
|
|
476
|
-
return previousTime !== void 0 && at.getTime() < previousTime;
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
var messageCaches = /* @__PURE__ */ new WeakMap();
|
|
480
|
-
var pollCaches = /* @__PURE__ */ new WeakMap();
|
|
481
|
-
var getMessageCache = (owner) => {
|
|
482
|
-
let cache = messageCaches.get(owner);
|
|
483
|
-
if (!cache) {
|
|
484
|
-
cache = new MessageCache();
|
|
485
|
-
messageCaches.set(owner, cache);
|
|
486
|
-
}
|
|
487
|
-
return cache;
|
|
488
|
-
};
|
|
489
|
-
var getPollCache = (owner) => {
|
|
490
|
-
let cache = pollCaches.get(owner);
|
|
491
|
-
if (!cache) {
|
|
492
|
-
cache = new PollCache();
|
|
493
|
-
pollCaches.set(owner, cache);
|
|
494
|
-
}
|
|
495
|
-
return cache;
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
// src/providers/imessage/remote/ids.ts
|
|
499
|
-
var PART_PREFIX = /^p:(\d+)\//;
|
|
500
|
-
var formatChildId = (partIndex, parentGuid) => `p:${partIndex}/${parentGuid}`;
|
|
501
|
-
var parseTapbackTarget = (target) => {
|
|
502
|
-
const match = target.match(PART_PREFIX);
|
|
503
|
-
const guid = target.replace(PART_PREFIX, "");
|
|
504
|
-
const partIndex = match ? Number(match[1]) : 0;
|
|
505
|
-
return { guid, partIndex };
|
|
506
|
-
};
|
|
507
|
-
var parseChildId = (id) => {
|
|
508
|
-
const match = id.match(PART_PREFIX);
|
|
509
|
-
if (!match) {
|
|
510
|
-
return null;
|
|
511
|
-
}
|
|
512
|
-
return {
|
|
513
|
-
parentGuid: id.replace(PART_PREFIX, ""),
|
|
514
|
-
partIndex: Number(match[1])
|
|
515
|
-
};
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
// src/providers/imessage/remote/inbound.ts
|
|
519
|
-
var URL_BALLOON_BUNDLE_ID = "com.apple.messages.URLBalloonProvider";
|
|
520
|
-
var getBalloonBundleId = (message) => {
|
|
521
|
-
const raw = message._raw;
|
|
522
|
-
const id = raw?.balloonBundleId;
|
|
523
|
-
return typeof id === "string" ? id : void 0;
|
|
524
|
-
};
|
|
525
|
-
var resolveChatGuid = (message, hint) => {
|
|
526
|
-
if (hint) {
|
|
527
|
-
return hint;
|
|
528
|
-
}
|
|
529
|
-
const first = message.chatGuids?.[0];
|
|
530
|
-
return first ?? "";
|
|
531
|
-
};
|
|
532
|
-
var resolveSenderId = (message) => message.sender?.address ?? "";
|
|
533
|
-
var isIMessageMessage = (value) => {
|
|
534
|
-
if (typeof value !== "object" || value === null) {
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
const record = value;
|
|
538
|
-
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;
|
|
539
|
-
};
|
|
540
|
-
var asProviderGroup = (items) => groupSchema.parse({ type: "group", items });
|
|
541
|
-
var buildMessageBase = (message, chatGuidHint, timestamp, phone) => {
|
|
542
|
-
const chat = resolveChatGuid(message, chatGuidHint);
|
|
543
|
-
return {
|
|
544
|
-
sender: { id: resolveSenderId(message) },
|
|
545
|
-
space: {
|
|
546
|
-
id: chat,
|
|
547
|
-
type: chat.includes(";+;") ? "group" : "dm",
|
|
548
|
-
phone
|
|
549
|
-
},
|
|
550
|
-
timestamp
|
|
551
|
-
};
|
|
552
|
-
};
|
|
553
|
-
var receivedEventFromMessage = (message) => ({
|
|
554
|
-
chatGuid: resolveChatGuid(message, void 0),
|
|
555
|
-
message,
|
|
556
|
-
timestamp: message.dateCreated ?? /* @__PURE__ */ new Date(),
|
|
557
|
-
type: "message.received"
|
|
558
|
-
});
|
|
559
|
-
var toAttachmentContent2 = (client, info) => asAttachment({
|
|
560
|
-
name: info.fileName,
|
|
561
|
-
mimeType: info.mimeType,
|
|
562
|
-
size: info.totalBytes,
|
|
563
|
-
read: async () => Buffer.from(await client.attachments.downloadBuffer(info.guid)),
|
|
564
|
-
stream: async () => client.attachments.download(info.guid).stream
|
|
565
|
-
});
|
|
566
|
-
var toVCardContent2 = async (client, info) => {
|
|
567
|
-
try {
|
|
568
|
-
const buf = Buffer.from(await client.attachments.downloadBuffer(info.guid));
|
|
569
|
-
return asContact(fromVCard(buf.toString("utf8")));
|
|
570
|
-
} catch (err) {
|
|
571
|
-
console.warn(
|
|
572
|
-
"[spectrum-ts][imessage] failed to parse vCard attachment; falling back to attachment content",
|
|
573
|
-
{ error: err, guid: info.guid }
|
|
574
|
-
);
|
|
575
|
-
return toAttachmentContent2(client, info);
|
|
576
|
-
}
|
|
577
|
-
};
|
|
578
|
-
var attachmentContent = async (client, info) => isVCardAttachment(info.mimeType, info.fileName) ? await toVCardContent2(client, info) : toAttachmentContent2(client, info);
|
|
579
|
-
var buildAttachmentMessage = async (client, base, info, id, partIndex, parentId) => {
|
|
580
|
-
const content = await attachmentContent(client, info);
|
|
581
|
-
const msg = { ...base, id, content, partIndex };
|
|
582
|
-
if (parentId !== void 0) {
|
|
583
|
-
msg.parentId = parentId;
|
|
584
|
-
}
|
|
585
|
-
return msg;
|
|
586
|
-
};
|
|
587
|
-
var toRichlinkMessage = (message, base, id) => {
|
|
588
|
-
const url = message.text ?? "";
|
|
589
|
-
try {
|
|
590
|
-
return { ...base, id, content: asRichlink({ url }) };
|
|
591
|
-
} catch (err) {
|
|
592
|
-
console.warn(
|
|
593
|
-
"[spectrum-ts][imessage] failed to convert message to rich link; falling back to text/custom content",
|
|
594
|
-
{ error: err, message, url }
|
|
595
|
-
);
|
|
596
|
-
return {
|
|
597
|
-
...base,
|
|
598
|
-
id,
|
|
599
|
-
content: url ? asText(url) : asCustom(message)
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
};
|
|
603
|
-
var rebuildFromAppleMessage = async (client, message, phone, chatGuidHint) => {
|
|
604
|
-
const messageGuidStr = message.guid;
|
|
605
|
-
const timestamp = message.dateCreated ?? /* @__PURE__ */ new Date();
|
|
606
|
-
const base = buildMessageBase(message, chatGuidHint, timestamp, phone);
|
|
607
|
-
if (message.attachments.length === 1) {
|
|
608
|
-
const info = message.attachments[0];
|
|
609
|
-
if (!info) {
|
|
610
|
-
throw new Error("Unreachable: attachments.length === 1 but no element");
|
|
611
|
-
}
|
|
612
|
-
return buildAttachmentMessage(client, base, info, messageGuidStr, 0);
|
|
613
|
-
}
|
|
614
|
-
if (message.attachments.length > 1) {
|
|
615
|
-
const items = [];
|
|
616
|
-
for (let i = 0; i < message.attachments.length; i++) {
|
|
617
|
-
const info = message.attachments[i];
|
|
618
|
-
if (!info) {
|
|
619
|
-
continue;
|
|
620
|
-
}
|
|
621
|
-
items.push(
|
|
622
|
-
await buildAttachmentMessage(
|
|
623
|
-
client,
|
|
624
|
-
base,
|
|
625
|
-
info,
|
|
626
|
-
formatChildId(i, messageGuidStr),
|
|
627
|
-
i,
|
|
628
|
-
messageGuidStr
|
|
629
|
-
)
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
return {
|
|
633
|
-
...base,
|
|
634
|
-
id: messageGuidStr,
|
|
635
|
-
content: asProviderGroup(items)
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
if (getBalloonBundleId(message) === URL_BALLOON_BUNDLE_ID) {
|
|
639
|
-
return toRichlinkMessage(message, base, messageGuidStr);
|
|
640
|
-
}
|
|
641
|
-
const text2 = message.text;
|
|
642
|
-
return {
|
|
643
|
-
...base,
|
|
644
|
-
id: messageGuidStr,
|
|
645
|
-
content: text2 ? asText(text2) : asCustom(message)
|
|
646
|
-
};
|
|
647
|
-
};
|
|
648
|
-
var cacheMessage = (cache, message) => {
|
|
649
|
-
cache.set(message.id, message);
|
|
650
|
-
if (message.content.type === "group") {
|
|
651
|
-
for (const item of message.content.items) {
|
|
652
|
-
if (isIMessageMessage(item)) {
|
|
653
|
-
cache.set(item.id, item);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
};
|
|
658
|
-
var toInboundMessages = async (client, cache, event, phone) => {
|
|
659
|
-
const base = buildMessageBase(
|
|
660
|
-
event.message,
|
|
661
|
-
event.chatGuid,
|
|
662
|
-
event.timestamp,
|
|
663
|
-
phone
|
|
664
|
-
);
|
|
665
|
-
const messageGuidStr = event.message.guid;
|
|
666
|
-
if (getBalloonBundleId(event.message) === URL_BALLOON_BUNDLE_ID) {
|
|
667
|
-
const msg2 = toRichlinkMessage(event.message, base, messageGuidStr);
|
|
668
|
-
cacheMessage(cache, msg2);
|
|
669
|
-
return [msg2];
|
|
670
|
-
}
|
|
671
|
-
if (event.message.attachments.length === 1) {
|
|
672
|
-
const info = event.message.attachments[0];
|
|
673
|
-
if (!info) {
|
|
674
|
-
throw new Error("Unreachable: attachments.length === 1 but no element");
|
|
675
|
-
}
|
|
676
|
-
const msg2 = await buildAttachmentMessage(
|
|
677
|
-
client,
|
|
678
|
-
base,
|
|
679
|
-
info,
|
|
680
|
-
messageGuidStr,
|
|
681
|
-
0
|
|
682
|
-
);
|
|
683
|
-
cacheMessage(cache, msg2);
|
|
684
|
-
return [msg2];
|
|
685
|
-
}
|
|
686
|
-
if (event.message.attachments.length > 1) {
|
|
687
|
-
const items = [];
|
|
688
|
-
for (let i = 0; i < event.message.attachments.length; i++) {
|
|
689
|
-
const info = event.message.attachments[i];
|
|
690
|
-
if (!info) {
|
|
691
|
-
continue;
|
|
692
|
-
}
|
|
693
|
-
items.push(
|
|
694
|
-
await buildAttachmentMessage(
|
|
695
|
-
client,
|
|
696
|
-
base,
|
|
697
|
-
info,
|
|
698
|
-
formatChildId(i, messageGuidStr),
|
|
699
|
-
i,
|
|
700
|
-
messageGuidStr
|
|
701
|
-
)
|
|
702
|
-
);
|
|
703
|
-
}
|
|
704
|
-
const parent = {
|
|
705
|
-
...base,
|
|
706
|
-
id: messageGuidStr,
|
|
707
|
-
content: asProviderGroup(items)
|
|
708
|
-
};
|
|
709
|
-
cacheMessage(cache, parent);
|
|
710
|
-
return [parent];
|
|
711
|
-
}
|
|
712
|
-
const text2 = event.message.text;
|
|
713
|
-
const msg = {
|
|
714
|
-
...base,
|
|
715
|
-
id: messageGuidStr,
|
|
716
|
-
content: text2 ? asText(text2) : asCustom(event.message)
|
|
717
|
-
};
|
|
718
|
-
cacheMessage(cache, msg);
|
|
719
|
-
return [msg];
|
|
720
|
-
};
|
|
721
|
-
var getMessage3 = async (remote, spaceId, msgId, phone) => {
|
|
722
|
-
const cache = getMessageCache(remote);
|
|
723
|
-
const cached = cache.get(msgId);
|
|
724
|
-
if (cached) {
|
|
725
|
-
return cached;
|
|
726
|
-
}
|
|
727
|
-
const childRef = parseChildId(msgId);
|
|
728
|
-
if (childRef) {
|
|
729
|
-
try {
|
|
730
|
-
const fetched = await remote.messages.get(
|
|
731
|
-
messageGuid(childRef.parentGuid)
|
|
732
|
-
);
|
|
733
|
-
const parent = await rebuildFromAppleMessage(
|
|
734
|
-
remote,
|
|
735
|
-
fetched,
|
|
736
|
-
phone,
|
|
737
|
-
spaceId
|
|
738
|
-
);
|
|
739
|
-
cacheMessage(cache, parent);
|
|
740
|
-
if (parent.content.type !== "group") {
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
const item = parent.content.items[childRef.partIndex];
|
|
744
|
-
return isIMessageMessage(item) ? item : void 0;
|
|
745
|
-
} catch (err) {
|
|
746
|
-
if (err instanceof NotFoundError) {
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
throw err;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
try {
|
|
753
|
-
const fetched = await remote.messages.get(messageGuid(msgId));
|
|
754
|
-
const rebuilt = await rebuildFromAppleMessage(
|
|
755
|
-
remote,
|
|
756
|
-
fetched,
|
|
757
|
-
phone,
|
|
758
|
-
spaceId
|
|
759
|
-
);
|
|
760
|
-
cacheMessage(cache, rebuilt);
|
|
761
|
-
return rebuilt;
|
|
762
|
-
} catch (err) {
|
|
763
|
-
if (err instanceof NotFoundError) {
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
throw err;
|
|
767
|
-
}
|
|
768
|
-
};
|
|
769
|
-
|
|
770
|
-
// src/providers/imessage/remote/reactions.ts
|
|
771
|
-
import {
|
|
772
|
-
chatGuid,
|
|
773
|
-
messageGuid as messageGuid2,
|
|
774
|
-
Reaction
|
|
775
|
-
} from "@photon-ai/advanced-imessage";
|
|
776
|
-
var EMOJI_TO_TAPBACK = {
|
|
777
|
-
"\u2764\uFE0F": Reaction.love,
|
|
778
|
-
"\u{1F44D}": Reaction.like,
|
|
779
|
-
"\u{1F44E}": Reaction.dislike,
|
|
780
|
-
"\u{1F602}": Reaction.laugh,
|
|
781
|
-
"\u203C\uFE0F": Reaction.emphasize,
|
|
782
|
-
"\u2753": Reaction.question
|
|
783
|
-
};
|
|
784
|
-
var TAPBACK_TO_EMOJI = Object.fromEntries(
|
|
785
|
-
Object.entries(EMOJI_TO_TAPBACK).map(([emoji, kind]) => [kind, emoji])
|
|
786
|
-
);
|
|
787
|
-
var TAPBACK_CODE_TO_KIND = {
|
|
788
|
-
"2000": Reaction.love,
|
|
789
|
-
"2001": Reaction.like,
|
|
790
|
-
"2002": Reaction.dislike,
|
|
791
|
-
"2003": Reaction.laugh,
|
|
792
|
-
"2004": Reaction.emphasize,
|
|
793
|
-
"2005": Reaction.question,
|
|
794
|
-
"2006": Reaction.emoji,
|
|
795
|
-
"2007": Reaction.sticker
|
|
796
|
-
};
|
|
797
|
-
var isTapbackRemoval = (code) => code.startsWith("3");
|
|
798
|
-
var resolveReactionEmoji = (type, emoji) => {
|
|
799
|
-
if (emoji) {
|
|
800
|
-
return emoji;
|
|
801
|
-
}
|
|
802
|
-
if (!type) {
|
|
803
|
-
return null;
|
|
804
|
-
}
|
|
805
|
-
const kind = TAPBACK_CODE_TO_KIND[type] ?? type;
|
|
806
|
-
return TAPBACK_TO_EMOJI[kind] ?? null;
|
|
807
|
-
};
|
|
808
|
-
var getAssociatedMessageType = (message) => {
|
|
809
|
-
const direct = message.associatedMessageType;
|
|
810
|
-
if (typeof direct === "string") {
|
|
811
|
-
return direct;
|
|
812
|
-
}
|
|
813
|
-
const raw = message._raw;
|
|
814
|
-
const fromRaw = raw?.associatedMessageType;
|
|
815
|
-
return typeof fromRaw === "string" ? fromRaw : void 0;
|
|
816
|
-
};
|
|
817
|
-
var asProviderReaction = (emoji, target) => reactionSchema.parse({
|
|
818
|
-
emoji,
|
|
819
|
-
target,
|
|
820
|
-
type: "reaction"
|
|
821
|
-
});
|
|
822
|
-
var resolveReactionTarget = async (client, cache, strippedGuid, partIndex, phone) => {
|
|
823
|
-
let candidate = cache.get(strippedGuid);
|
|
824
|
-
if (!candidate) {
|
|
825
|
-
try {
|
|
826
|
-
const fetched = await client.messages.get(messageGuid2(strippedGuid));
|
|
827
|
-
candidate = await rebuildFromAppleMessage(client, fetched, phone);
|
|
828
|
-
cacheMessage(cache, candidate);
|
|
829
|
-
} catch {
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
if (candidate.content.type === "group") {
|
|
834
|
-
const items = candidate.content.items;
|
|
835
|
-
if (!Array.isArray(items)) {
|
|
836
|
-
return candidate;
|
|
837
|
-
}
|
|
838
|
-
const item = items[partIndex];
|
|
839
|
-
return isIMessageMessage(item) ? item : candidate;
|
|
840
|
-
}
|
|
841
|
-
return candidate;
|
|
842
|
-
};
|
|
843
|
-
var toReactionMessages = async (client, cache, event, target, phone) => {
|
|
844
|
-
const type = getAssociatedMessageType(event.message);
|
|
845
|
-
if (type && isTapbackRemoval(type)) {
|
|
846
|
-
return [];
|
|
847
|
-
}
|
|
848
|
-
const emoji = resolveReactionEmoji(
|
|
849
|
-
type,
|
|
850
|
-
event.message.associatedMessageEmoji
|
|
851
|
-
);
|
|
852
|
-
if (!emoji) {
|
|
853
|
-
return [];
|
|
854
|
-
}
|
|
855
|
-
const { guid: strippedGuid, partIndex } = parseTapbackTarget(target);
|
|
856
|
-
const resolved = await resolveReactionTarget(
|
|
857
|
-
client,
|
|
858
|
-
cache,
|
|
859
|
-
strippedGuid,
|
|
860
|
-
partIndex,
|
|
861
|
-
phone
|
|
862
|
-
);
|
|
863
|
-
if (!resolved) {
|
|
864
|
-
return [];
|
|
865
|
-
}
|
|
866
|
-
const messageId = event.message.guid;
|
|
867
|
-
if (typeof messageId !== "string" || messageId.length === 0) {
|
|
868
|
-
return [];
|
|
869
|
-
}
|
|
870
|
-
const base = buildMessageBase(
|
|
871
|
-
event.message,
|
|
872
|
-
event.chatGuid,
|
|
873
|
-
event.timestamp,
|
|
874
|
-
phone
|
|
875
|
-
);
|
|
876
|
-
return [
|
|
877
|
-
{
|
|
878
|
-
...base,
|
|
879
|
-
id: messageId,
|
|
880
|
-
content: asProviderReaction(emoji, resolved)
|
|
881
|
-
}
|
|
882
|
-
];
|
|
883
|
-
};
|
|
884
|
-
var reactToMessage = async (remote, spaceId, target, reaction) => {
|
|
885
|
-
const chat = chatGuid(spaceId);
|
|
886
|
-
const parentGuid = target.parentId ?? target.id;
|
|
887
|
-
const guid = messageGuid2(parentGuid);
|
|
888
|
-
const opts = typeof target.partIndex === "number" ? { partIndex: target.partIndex } : void 0;
|
|
889
|
-
const native = EMOJI_TO_TAPBACK[reaction];
|
|
890
|
-
if (native) {
|
|
891
|
-
await remote.messages.react(chat, guid, native, opts);
|
|
892
|
-
} else {
|
|
893
|
-
await remote.messages.reactEmoji(chat, guid, reaction, opts);
|
|
894
|
-
}
|
|
895
|
-
};
|
|
896
|
-
|
|
897
|
-
// src/providers/imessage/remote/send.ts
|
|
898
|
-
import {
|
|
899
|
-
chatGuid as chatGuid2,
|
|
900
|
-
messageGuid as messageGuid3
|
|
901
|
-
} from "@photon-ai/advanced-imessage";
|
|
902
|
-
|
|
903
|
-
// src/utils/audio.ts
|
|
904
|
-
import { spawn } from "child_process";
|
|
905
|
-
import { mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
906
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
907
|
-
import { join as join2 } from "path";
|
|
908
|
-
var M4A_BRANDS = /* @__PURE__ */ new Set([
|
|
909
|
-
"M4A ",
|
|
910
|
-
"M4B ",
|
|
911
|
-
"M4P ",
|
|
912
|
-
"mp42",
|
|
913
|
-
"mp41",
|
|
914
|
-
"isom",
|
|
915
|
-
"iso2"
|
|
916
|
-
]);
|
|
917
|
-
var M4A_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
918
|
-
"audio/mp4",
|
|
919
|
-
"audio/mp4a-latm",
|
|
920
|
-
"audio/x-m4a",
|
|
921
|
-
"audio/aac",
|
|
922
|
-
"audio/aacp"
|
|
923
|
-
]);
|
|
924
|
-
var FFMPEG_MISSING_MESSAGE = "voice content: input is not m4a/aac and ffmpeg is unavailable. Install `ffmpeg-static` or ensure `ffmpeg` is on PATH.";
|
|
925
|
-
var isM4a = (buffer) => {
|
|
926
|
-
if (buffer.length < 12) {
|
|
927
|
-
return false;
|
|
928
|
-
}
|
|
929
|
-
if (buffer.toString("ascii", 4, 8) !== "ftyp") {
|
|
930
|
-
return false;
|
|
931
|
-
}
|
|
932
|
-
return M4A_BRANDS.has(buffer.toString("ascii", 8, 12));
|
|
933
|
-
};
|
|
934
|
-
var isM4aMimeType = (mimeType) => M4A_MIME_TYPES.has(mimeType.toLowerCase());
|
|
935
|
-
var cachedFfmpegPath;
|
|
936
|
-
var tryStaticBinary = async () => {
|
|
937
|
-
try {
|
|
938
|
-
const mod = await import("ffmpeg-static");
|
|
939
|
-
return mod.default ?? void 0;
|
|
940
|
-
} catch {
|
|
941
|
-
return void 0;
|
|
942
|
-
}
|
|
943
|
-
};
|
|
944
|
-
var resolveFfmpegPath = async () => {
|
|
945
|
-
if (cachedFfmpegPath) {
|
|
946
|
-
return cachedFfmpegPath;
|
|
947
|
-
}
|
|
948
|
-
cachedFfmpegPath = await tryStaticBinary() ?? "ffmpeg";
|
|
949
|
-
return cachedFfmpegPath;
|
|
950
|
-
};
|
|
951
|
-
var collectStream = (stream2) => {
|
|
952
|
-
if (!stream2) {
|
|
953
|
-
return Promise.resolve("");
|
|
954
|
-
}
|
|
955
|
-
return new Promise((resolve, reject) => {
|
|
956
|
-
const chunks = [];
|
|
957
|
-
stream2.on("data", (chunk) => {
|
|
958
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
959
|
-
});
|
|
960
|
-
stream2.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
961
|
-
stream2.on("error", reject);
|
|
962
|
-
});
|
|
963
|
-
};
|
|
964
|
-
var isMissingBinaryError = (err) => err?.code === "ENOENT";
|
|
965
|
-
var runFfmpeg = (ffmpegPath, args) => {
|
|
966
|
-
const proc = spawn(ffmpegPath, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
967
|
-
const stderr = collectStream(proc.stderr);
|
|
968
|
-
const exit = new Promise((resolve, reject) => {
|
|
969
|
-
proc.on(
|
|
970
|
-
"error",
|
|
971
|
-
(err) => reject(
|
|
972
|
-
isMissingBinaryError(err) ? new Error(FFMPEG_MISSING_MESSAGE) : err
|
|
973
|
-
)
|
|
974
|
-
);
|
|
975
|
-
proc.on("exit", (code) => resolve(code ?? -1));
|
|
976
|
-
});
|
|
977
|
-
return Promise.all([exit, stderr]).then(([code, text2]) => ({
|
|
978
|
-
code,
|
|
979
|
-
stderr: text2
|
|
980
|
-
}));
|
|
981
|
-
};
|
|
982
|
-
var DURATION_PATTERN = /Duration:\s*(\d+):(\d{2}):(\d{2})(?:\.(\d{1,3}))?/;
|
|
983
|
-
var parseDuration = (stderr) => {
|
|
984
|
-
const match = stderr.match(DURATION_PATTERN);
|
|
985
|
-
if (!match) {
|
|
986
|
-
return void 0;
|
|
987
|
-
}
|
|
988
|
-
const [, hh, mm, ss, frac] = match;
|
|
989
|
-
const seconds = Number(hh) * 3600 + Number(mm) * 60 + Number(ss) + Number(`0.${frac ?? 0}`);
|
|
990
|
-
return Number.isFinite(seconds) ? seconds : void 0;
|
|
991
|
-
};
|
|
992
|
-
var transcodeToM4a = async (buffer) => {
|
|
993
|
-
const ffmpeg = await resolveFfmpegPath();
|
|
994
|
-
const dir = await mkdtemp2(join2(tmpdir2(), "spectrum-voice-"));
|
|
995
|
-
const inPath = join2(dir, "in");
|
|
996
|
-
const outPath = join2(dir, "out.m4a");
|
|
997
|
-
try {
|
|
998
|
-
await writeFile2(inPath, buffer);
|
|
999
|
-
const { code, stderr } = await runFfmpeg(ffmpeg, [
|
|
1000
|
-
"-y",
|
|
1001
|
-
"-i",
|
|
1002
|
-
inPath,
|
|
1003
|
-
"-f",
|
|
1004
|
-
"ipod",
|
|
1005
|
-
"-c:a",
|
|
1006
|
-
"aac",
|
|
1007
|
-
outPath
|
|
1008
|
-
]);
|
|
1009
|
-
if (code !== 0) {
|
|
1010
|
-
throw new Error(`ffmpeg conversion failed (exit ${code}): ${stderr}`);
|
|
1011
|
-
}
|
|
1012
|
-
const out = await readFile2(outPath);
|
|
1013
|
-
return { buffer: out, duration: parseDuration(stderr) };
|
|
1014
|
-
} finally {
|
|
1015
|
-
await rm2(dir, { recursive: true, force: true }).catch(() => {
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
};
|
|
1019
|
-
var ensureM4a = async (buffer, mimeType) => {
|
|
1020
|
-
if (isM4aMimeType(mimeType) || isM4a(buffer)) {
|
|
1021
|
-
return { buffer };
|
|
1022
|
-
}
|
|
1023
|
-
return transcodeToM4a(buffer);
|
|
1024
|
-
};
|
|
1025
|
-
|
|
1026
|
-
// src/providers/imessage/remote/send.ts
|
|
1027
|
-
var GROUP_ITEM_ALLOWED = /* @__PURE__ */ new Set([
|
|
1028
|
-
"text",
|
|
1029
|
-
"attachment",
|
|
1030
|
-
"contact",
|
|
1031
|
-
"voice"
|
|
1032
|
-
]);
|
|
1033
|
-
var MAX_GROUP_TEXT_ITEMS = 1;
|
|
1034
|
-
var toDate = (value) => {
|
|
1035
|
-
if (value instanceof Date) {
|
|
1036
|
-
return value;
|
|
1037
|
-
}
|
|
1038
|
-
if (typeof value === "number" || typeof value === "string") {
|
|
1039
|
-
const date = new Date(value);
|
|
1040
|
-
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
1041
|
-
}
|
|
1042
|
-
};
|
|
1043
|
-
var receiptTimestamp = (receipt) => toDate(receipt.timestamp) ?? toDate(receipt.date) ?? toDate(receipt.dateCreated) ?? /* @__PURE__ */ new Date();
|
|
1044
|
-
var receiptGuid = (receipt) => {
|
|
1045
|
-
if (typeof receipt.guid !== "string" || receipt.guid.length === 0) {
|
|
1046
|
-
throw new Error("iMessage send receipt is missing a message guid");
|
|
1047
|
-
}
|
|
1048
|
-
return receipt.guid;
|
|
1049
|
-
};
|
|
1050
|
-
var outboundRecord = (spaceId, id, content, timestamp, extras) => ({
|
|
1051
|
-
id,
|
|
1052
|
-
content,
|
|
1053
|
-
space: { id: spaceId },
|
|
1054
|
-
timestamp,
|
|
1055
|
-
...extras
|
|
1056
|
-
});
|
|
1057
|
-
var withReply = (options, replyTo) => replyTo ? { ...options, replyTo } : options;
|
|
1058
|
-
var replyOptions = (replyTo) => replyTo ? { replyTo } : void 0;
|
|
1059
|
-
var effectOption = (effect2) => effect2 ? { effect: effect2 } : {};
|
|
1060
|
-
var sendVCardAttachment = (remote, name, vcf) => remote.attachments.upload({
|
|
1061
|
-
data: Buffer.from(vcf, "utf8"),
|
|
1062
|
-
fileName: name,
|
|
1063
|
-
mimeType: "text/vcard"
|
|
1064
|
-
});
|
|
1065
|
-
var sendContactAttachment = async (remote, content) => {
|
|
1066
|
-
const vcf = await toVCard(content);
|
|
1067
|
-
const name = vcardFileName(content);
|
|
1068
|
-
const upload = await sendVCardAttachment(remote, name, vcf);
|
|
1069
|
-
return { guid: upload.guid, name };
|
|
1070
|
-
};
|
|
1071
|
-
var uploadAttachment = async (remote, content) => {
|
|
1072
|
-
const attachment = await remote.attachments.upload({
|
|
1073
|
-
data: await content.read(),
|
|
1074
|
-
fileName: content.name,
|
|
1075
|
-
mimeType: content.mimeType
|
|
1076
|
-
});
|
|
1077
|
-
return { guid: attachment.guid, name: content.name };
|
|
1078
|
-
};
|
|
1079
|
-
var uploadVoice = async (remote, content) => {
|
|
1080
|
-
const { buffer } = await ensureM4a(await content.read(), content.mimeType);
|
|
1081
|
-
const name = content.name ?? "voice.m4a";
|
|
1082
|
-
const attachment = await remote.attachments.upload({
|
|
1083
|
-
data: buffer,
|
|
1084
|
-
fileName: name,
|
|
1085
|
-
mimeType: "audio/x-m4a"
|
|
1086
|
-
});
|
|
1087
|
-
return { guid: attachment.guid, name };
|
|
1088
|
-
};
|
|
1089
|
-
var sendContent = async (remote, spaceId, chat, content, replyTo, effect2) => {
|
|
1090
|
-
switch (content.type) {
|
|
1091
|
-
case "effect":
|
|
1092
|
-
return sendContent(
|
|
1093
|
-
remote,
|
|
1094
|
-
spaceId,
|
|
1095
|
-
chat,
|
|
1096
|
-
content.content,
|
|
1097
|
-
replyTo,
|
|
1098
|
-
content.effect
|
|
1099
|
-
);
|
|
1100
|
-
case "text": {
|
|
1101
|
-
const receipt = await remote.messages.send(
|
|
1102
|
-
chat,
|
|
1103
|
-
content.text,
|
|
1104
|
-
withReply(effectOption(effect2), replyTo)
|
|
1105
|
-
);
|
|
1106
|
-
return outboundRecord(
|
|
1107
|
-
spaceId,
|
|
1108
|
-
receiptGuid(receipt),
|
|
1109
|
-
content,
|
|
1110
|
-
receiptTimestamp(receipt)
|
|
1111
|
-
);
|
|
1112
|
-
}
|
|
1113
|
-
case "richlink": {
|
|
1114
|
-
const receipt = await remote.messages.send(
|
|
1115
|
-
chat,
|
|
1116
|
-
content.url,
|
|
1117
|
-
withReply({ richLink: true }, replyTo)
|
|
1118
|
-
);
|
|
1119
|
-
return outboundRecord(
|
|
1120
|
-
spaceId,
|
|
1121
|
-
receiptGuid(receipt),
|
|
1122
|
-
content,
|
|
1123
|
-
receiptTimestamp(receipt)
|
|
1124
|
-
);
|
|
1125
|
-
}
|
|
1126
|
-
case "attachment": {
|
|
1127
|
-
const { guid } = await uploadAttachment(remote, content);
|
|
1128
|
-
const receipt = await remote.messages.send(chat, "", {
|
|
1129
|
-
attachment: guid,
|
|
1130
|
-
...effectOption(effect2),
|
|
1131
|
-
...replyOptions(replyTo)
|
|
1132
|
-
});
|
|
1133
|
-
return outboundRecord(
|
|
1134
|
-
spaceId,
|
|
1135
|
-
receiptGuid(receipt),
|
|
1136
|
-
content,
|
|
1137
|
-
receiptTimestamp(receipt)
|
|
1138
|
-
);
|
|
1139
|
-
}
|
|
1140
|
-
case "contact": {
|
|
1141
|
-
const { guid } = await sendContactAttachment(remote, content);
|
|
1142
|
-
const receipt = await remote.messages.send(chat, "", {
|
|
1143
|
-
attachment: guid,
|
|
1144
|
-
...replyOptions(replyTo)
|
|
1145
|
-
});
|
|
1146
|
-
return outboundRecord(
|
|
1147
|
-
spaceId,
|
|
1148
|
-
receiptGuid(receipt),
|
|
1149
|
-
content,
|
|
1150
|
-
receiptTimestamp(receipt)
|
|
1151
|
-
);
|
|
1152
|
-
}
|
|
1153
|
-
case "voice": {
|
|
1154
|
-
const { guid } = await uploadVoice(remote, content);
|
|
1155
|
-
const receipt = await remote.messages.send(chat, "", {
|
|
1156
|
-
attachment: guid,
|
|
1157
|
-
audioMessage: true,
|
|
1158
|
-
...replyOptions(replyTo)
|
|
1159
|
-
});
|
|
1160
|
-
return outboundRecord(
|
|
1161
|
-
spaceId,
|
|
1162
|
-
receiptGuid(receipt),
|
|
1163
|
-
content,
|
|
1164
|
-
receiptTimestamp(receipt)
|
|
1165
|
-
);
|
|
1166
|
-
}
|
|
1167
|
-
case "poll":
|
|
1168
|
-
if (replyTo) {
|
|
1169
|
-
throw unsupportedRemoteContent(
|
|
1170
|
-
"poll",
|
|
1171
|
-
"polls cannot be sent as replies"
|
|
1172
|
-
);
|
|
1173
|
-
}
|
|
1174
|
-
return outboundRecord(
|
|
1175
|
-
spaceId,
|
|
1176
|
-
receiptGuid(
|
|
1177
|
-
await remote.polls.create(
|
|
1178
|
-
chat,
|
|
1179
|
-
content.title,
|
|
1180
|
-
content.options.map((option) => option.title)
|
|
1181
|
-
)
|
|
1182
|
-
),
|
|
1183
|
-
content,
|
|
1184
|
-
/* @__PURE__ */ new Date()
|
|
1185
|
-
);
|
|
1186
|
-
default:
|
|
1187
|
-
throw unsupportedRemoteContent(content.type);
|
|
1188
|
-
}
|
|
1189
|
-
};
|
|
1190
|
-
var validateGroupContent = (content) => {
|
|
1191
|
-
let textCount = 0;
|
|
1192
|
-
for (const sub of content.items) {
|
|
1193
|
-
const itemType = sub.content.type;
|
|
1194
|
-
if (!GROUP_ITEM_ALLOWED.has(itemType)) {
|
|
1195
|
-
throw unsupportedRemoteContent(
|
|
1196
|
-
"group",
|
|
1197
|
-
`"${itemType}" items are not supported inside a group`
|
|
1198
|
-
);
|
|
1199
|
-
}
|
|
1200
|
-
if (itemType === "text" && ++textCount > MAX_GROUP_TEXT_ITEMS) {
|
|
1201
|
-
throw unsupportedRemoteContent(
|
|
1202
|
-
"group",
|
|
1203
|
-
`groups can contain at most ${MAX_GROUP_TEXT_ITEMS} text item`
|
|
1204
|
-
);
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
};
|
|
1208
|
-
var resolvePart = async (remote, content) => {
|
|
1209
|
-
switch (content.type) {
|
|
1210
|
-
case "text":
|
|
1211
|
-
return { text: content.text };
|
|
1212
|
-
case "attachment": {
|
|
1213
|
-
const { guid, name } = await uploadAttachment(remote, content);
|
|
1214
|
-
return { attachmentGuid: guid, attachmentName: name };
|
|
1215
|
-
}
|
|
1216
|
-
case "contact": {
|
|
1217
|
-
const { guid, name } = await sendContactAttachment(remote, content);
|
|
1218
|
-
return { attachmentGuid: guid, attachmentName: name };
|
|
1219
|
-
}
|
|
1220
|
-
case "voice": {
|
|
1221
|
-
const { guid, name } = await uploadVoice(remote, content);
|
|
1222
|
-
return { attachmentGuid: guid, attachmentName: name };
|
|
1223
|
-
}
|
|
1224
|
-
default:
|
|
1225
|
-
throw unsupportedRemoteContent(content.type);
|
|
1226
|
-
}
|
|
1227
|
-
};
|
|
1228
|
-
var send3 = async (remote, spaceId, content) => {
|
|
1229
|
-
const chat = chatGuid2(spaceId);
|
|
1230
|
-
if (content.type === "group") {
|
|
1231
|
-
validateGroupContent(content);
|
|
1232
|
-
const resolved = await Promise.all(
|
|
1233
|
-
content.items.map((sub) => resolvePart(remote, sub.content))
|
|
1234
|
-
);
|
|
1235
|
-
const receipt = await remote.messages.sendMultipart(
|
|
1236
|
-
chat,
|
|
1237
|
-
resolved.map((part, idx) => ({ ...part, partIndex: idx }))
|
|
1238
|
-
);
|
|
1239
|
-
const parentGuid = receiptGuid(receipt);
|
|
1240
|
-
const timestamp = receiptTimestamp(receipt);
|
|
1241
|
-
const items = content.items.map(
|
|
1242
|
-
(sub, idx) => outboundRecord(
|
|
1243
|
-
spaceId,
|
|
1244
|
-
formatChildId(idx, parentGuid),
|
|
1245
|
-
sub.content,
|
|
1246
|
-
timestamp,
|
|
1247
|
-
{ partIndex: idx, parentId: parentGuid }
|
|
1248
|
-
)
|
|
1249
|
-
);
|
|
1250
|
-
return outboundRecord(
|
|
1251
|
-
spaceId,
|
|
1252
|
-
parentGuid,
|
|
1253
|
-
asGroup({ items }),
|
|
1254
|
-
timestamp
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
return sendContent(remote, spaceId, chat, content);
|
|
1258
|
-
};
|
|
1259
|
-
var replyToMessage = async (remote, spaceId, msgId, content) => {
|
|
1260
|
-
const chat = chatGuid2(spaceId);
|
|
1261
|
-
const replyTo = messageGuid3(msgId);
|
|
1262
|
-
return sendContent(remote, spaceId, chat, content, replyTo);
|
|
1263
|
-
};
|
|
1264
|
-
var editMessage = async (remote, spaceId, msgId, content) => {
|
|
1265
|
-
if (content.type !== "text") {
|
|
1266
|
-
throw unsupportedRemoteContent(
|
|
1267
|
-
content.type,
|
|
1268
|
-
"only text content can be edited"
|
|
1269
|
-
);
|
|
1270
|
-
}
|
|
1271
|
-
await remote.messages.edit(
|
|
1272
|
-
chatGuid2(spaceId),
|
|
1273
|
-
messageGuid3(msgId),
|
|
1274
|
-
content.text
|
|
1275
|
-
);
|
|
1276
|
-
};
|
|
1277
|
-
|
|
1278
|
-
// src/providers/imessage/remote/stream.ts
|
|
1279
|
-
import {
|
|
1280
|
-
AuthenticationError,
|
|
1281
|
-
IMessageError,
|
|
1282
|
-
NotFoundError as NotFoundError2,
|
|
1283
|
-
ValidationError
|
|
1284
|
-
} from "@photon-ai/advanced-imessage";
|
|
1285
|
-
|
|
1286
|
-
// src/utils/resumable-stream.ts
|
|
1287
|
-
var CATCH_UP_PAGE_SIZE = 100;
|
|
1288
|
-
var MAX_BUFFERED_LIVE_EVENTS = 1e3;
|
|
1289
|
-
var RECONNECT_INITIAL_DELAY_MS = 500;
|
|
1290
|
-
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
1291
|
-
var RetryableStreamError = class extends Error {
|
|
1292
|
-
constructor(message) {
|
|
1293
|
-
super(message);
|
|
1294
|
-
this.name = "RetryableStreamError";
|
|
1295
|
-
}
|
|
1296
|
-
};
|
|
1297
|
-
var LiveBufferOverflowError = class extends RetryableStreamError {
|
|
1298
|
-
constructor(limit) {
|
|
1299
|
-
super(`Live stream buffer exceeded ${limit} events during catch-up`);
|
|
1300
|
-
this.name = "LiveBufferOverflowError";
|
|
1301
|
-
}
|
|
1302
|
-
};
|
|
1303
|
-
var closeIterable = async (iterable) => {
|
|
1304
|
-
if (!iterable) {
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
await iterable.close?.();
|
|
1308
|
-
};
|
|
1309
|
-
var jitterDelay = (delayMs) => Math.random() * delayMs;
|
|
1310
|
-
var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
1311
|
-
const catchUpPageSize = options.catchUpPageSize ?? CATCH_UP_PAGE_SIZE;
|
|
1312
|
-
const bufferLimit = options.bufferLimit ?? MAX_BUFFERED_LIVE_EVENTS;
|
|
1313
|
-
const initialRetryDelayMs = options.initialRetryDelayMs ?? RECONNECT_INITIAL_DELAY_MS;
|
|
1314
|
-
const maxRetryDelayMs = options.maxRetryDelayMs ?? RECONNECT_MAX_DELAY_MS;
|
|
1315
|
-
let activeLive;
|
|
1316
|
-
let closed = false;
|
|
1317
|
-
let lastCursor;
|
|
1318
|
-
let retryDelayMs = initialRetryDelayMs;
|
|
1319
|
-
let sleepTimer;
|
|
1320
|
-
let wakeSleep;
|
|
1321
|
-
const deliveredSinceCursor = /* @__PURE__ */ new Set();
|
|
1322
|
-
const resetRetryDelay = () => {
|
|
1323
|
-
retryDelayMs = initialRetryDelayMs;
|
|
1324
|
-
};
|
|
1325
|
-
const advanceCursor = (cursor, clearDelivered) => {
|
|
1326
|
-
if (!cursor || cursor === lastCursor) {
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
lastCursor = cursor;
|
|
1330
|
-
if (clearDelivered) {
|
|
1331
|
-
deliveredSinceCursor.clear();
|
|
1332
|
-
}
|
|
1333
|
-
};
|
|
1334
|
-
const deliverItem = async (item, resetRetry, clearOnCursorAdvance) => {
|
|
1335
|
-
const alreadyDelivered = deliveredSinceCursor.has(item.id);
|
|
1336
|
-
if (!alreadyDelivered) {
|
|
1337
|
-
for (const value of item.values) {
|
|
1338
|
-
await emit(value);
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
advanceCursor(item.cursor, clearOnCursorAdvance);
|
|
1342
|
-
deliveredSinceCursor.add(item.id);
|
|
1343
|
-
if (resetRetry) {
|
|
1344
|
-
resetRetryDelay();
|
|
1345
|
-
}
|
|
1346
|
-
};
|
|
1347
|
-
const retryable = (error) => error instanceof RetryableStreamError || options.isRetryableError(error);
|
|
1348
|
-
const sleep = async (delayMs) => {
|
|
1349
|
-
if (delayMs <= 0 || closed) {
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
await new Promise((resolve) => {
|
|
1353
|
-
wakeSleep = resolve;
|
|
1354
|
-
sleepTimer = setTimeout(resolve, jitterDelay(delayMs));
|
|
1355
|
-
});
|
|
1356
|
-
sleepTimer = void 0;
|
|
1357
|
-
wakeSleep = void 0;
|
|
1358
|
-
};
|
|
1359
|
-
const cancelSleep = () => {
|
|
1360
|
-
if (sleepTimer) {
|
|
1361
|
-
clearTimeout(sleepTimer);
|
|
1362
|
-
sleepTimer = void 0;
|
|
1363
|
-
}
|
|
1364
|
-
wakeSleep?.();
|
|
1365
|
-
wakeSleep = void 0;
|
|
1366
|
-
};
|
|
1367
|
-
const nextRetryDelay = () => {
|
|
1368
|
-
const delay = retryDelayMs;
|
|
1369
|
-
retryDelayMs = Math.min(retryDelayMs * 2, maxRetryDelayMs);
|
|
1370
|
-
return delay;
|
|
1371
|
-
};
|
|
1372
|
-
const consumeLive = async () => {
|
|
1373
|
-
const live = options.subscribeLive();
|
|
1374
|
-
activeLive = live;
|
|
1375
|
-
try {
|
|
1376
|
-
for await (const event of live) {
|
|
1377
|
-
await deliverItem(await options.processLive(event), true, true);
|
|
1378
|
-
}
|
|
1379
|
-
throw new RetryableStreamError("Live stream ended");
|
|
1380
|
-
} finally {
|
|
1381
|
-
if (activeLive === live) {
|
|
1382
|
-
activeLive = void 0;
|
|
1383
|
-
}
|
|
1384
|
-
await closeIterable(live);
|
|
1385
|
-
}
|
|
1386
|
-
};
|
|
1387
|
-
const throwLiveError = (liveError) => {
|
|
1388
|
-
if (liveError) {
|
|
1389
|
-
throw liveError;
|
|
1390
|
-
}
|
|
1391
|
-
};
|
|
1392
|
-
const bufferLiveEvent = (buffer, event) => {
|
|
1393
|
-
if (buffer.length >= bufferLimit) {
|
|
1394
|
-
throw new LiveBufferOverflowError(bufferLimit);
|
|
1395
|
-
}
|
|
1396
|
-
buffer.push(event);
|
|
1397
|
-
};
|
|
1398
|
-
const startLivePump = (live, isBuffering, liveBuffer) => {
|
|
1399
|
-
let liveError;
|
|
1400
|
-
const pump2 = (async () => {
|
|
1401
|
-
try {
|
|
1402
|
-
for await (const event of live) {
|
|
1403
|
-
if (isBuffering()) {
|
|
1404
|
-
bufferLiveEvent(liveBuffer, event);
|
|
1405
|
-
continue;
|
|
1406
|
-
}
|
|
1407
|
-
await deliverItem(await options.processLive(event), true, true);
|
|
1408
|
-
}
|
|
1409
|
-
throw new RetryableStreamError("Live stream ended");
|
|
1410
|
-
} catch (error) {
|
|
1411
|
-
liveError = error;
|
|
1412
|
-
}
|
|
1413
|
-
})();
|
|
1414
|
-
return {
|
|
1415
|
-
getError: () => liveError,
|
|
1416
|
-
pump: pump2
|
|
1417
|
-
};
|
|
1418
|
-
};
|
|
1419
|
-
const replayMissed = async (cursor, getLiveError) => {
|
|
1420
|
-
for await (const event of options.fetchMissed(cursor, {
|
|
1421
|
-
limit: catchUpPageSize
|
|
1422
|
-
})) {
|
|
1423
|
-
throwLiveError(getLiveError());
|
|
1424
|
-
await deliverItem(await options.processMissed(event), false, false);
|
|
1425
|
-
}
|
|
1426
|
-
throwLiveError(getLiveError());
|
|
1427
|
-
};
|
|
1428
|
-
const flushLiveBuffer = async (liveBuffer, getLiveError) => {
|
|
1429
|
-
let index = 0;
|
|
1430
|
-
let lastFlushedId;
|
|
1431
|
-
while (index < liveBuffer.length) {
|
|
1432
|
-
throwLiveError(getLiveError());
|
|
1433
|
-
const event = liveBuffer[index];
|
|
1434
|
-
if (event === void 0) {
|
|
1435
|
-
throw new RetryableStreamError("Live stream buffer index missing");
|
|
1436
|
-
}
|
|
1437
|
-
const item = await options.processLive(event);
|
|
1438
|
-
await deliverItem(item, true, false);
|
|
1439
|
-
lastFlushedId = item.id;
|
|
1440
|
-
index += 1;
|
|
1441
|
-
}
|
|
1442
|
-
liveBuffer.length = 0;
|
|
1443
|
-
throwLiveError(getLiveError());
|
|
1444
|
-
return lastFlushedId;
|
|
1445
|
-
};
|
|
1446
|
-
const compactDeliveredIds = (lastId) => {
|
|
1447
|
-
if (!lastId) {
|
|
1448
|
-
return;
|
|
1449
|
-
}
|
|
1450
|
-
deliveredSinceCursor.clear();
|
|
1451
|
-
deliveredSinceCursor.add(lastId);
|
|
1452
|
-
};
|
|
1453
|
-
const catchUpThenConsumeLive = async (cursor) => {
|
|
1454
|
-
const live = options.subscribeLive();
|
|
1455
|
-
activeLive = live;
|
|
1456
|
-
let buffering = true;
|
|
1457
|
-
const liveBuffer = [];
|
|
1458
|
-
const livePump = startLivePump(live, () => buffering, liveBuffer);
|
|
1459
|
-
try {
|
|
1460
|
-
await replayMissed(cursor, livePump.getError);
|
|
1461
|
-
const lastFlushedId = await flushLiveBuffer(
|
|
1462
|
-
liveBuffer,
|
|
1463
|
-
livePump.getError
|
|
1464
|
-
);
|
|
1465
|
-
compactDeliveredIds(lastFlushedId);
|
|
1466
|
-
buffering = false;
|
|
1467
|
-
resetRetryDelay();
|
|
1468
|
-
await livePump.pump;
|
|
1469
|
-
throwLiveError(livePump.getError());
|
|
1470
|
-
} finally {
|
|
1471
|
-
buffering = false;
|
|
1472
|
-
if (activeLive === live) {
|
|
1473
|
-
activeLive = void 0;
|
|
1474
|
-
}
|
|
1475
|
-
await closeIterable(live);
|
|
1476
|
-
await livePump.pump.catch(() => void 0);
|
|
1477
|
-
}
|
|
1478
|
-
};
|
|
1479
|
-
const run = async () => {
|
|
1480
|
-
while (!closed) {
|
|
1481
|
-
try {
|
|
1482
|
-
if (lastCursor) {
|
|
1483
|
-
await catchUpThenConsumeLive(lastCursor);
|
|
1484
|
-
} else {
|
|
1485
|
-
await consumeLive();
|
|
1486
|
-
}
|
|
1487
|
-
} catch (error) {
|
|
1488
|
-
await closeIterable(activeLive);
|
|
1489
|
-
activeLive = void 0;
|
|
1490
|
-
if (closed) {
|
|
1491
|
-
break;
|
|
1492
|
-
}
|
|
1493
|
-
if (!retryable(error)) {
|
|
1494
|
-
end(error);
|
|
1495
|
-
return;
|
|
1496
|
-
}
|
|
1497
|
-
await sleep(nextRetryDelay());
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
end();
|
|
1501
|
-
};
|
|
1502
|
-
const pump = run().catch((error) => {
|
|
1503
|
-
if (!closed) {
|
|
1504
|
-
end(error);
|
|
1505
|
-
}
|
|
1506
|
-
});
|
|
1507
|
-
return async () => {
|
|
1508
|
-
closed = true;
|
|
1509
|
-
cancelSleep();
|
|
1510
|
-
await closeIterable(activeLive);
|
|
1511
|
-
await pump;
|
|
1512
|
-
};
|
|
1513
|
-
});
|
|
1514
|
-
|
|
1515
|
-
// src/providers/imessage/remote/polls.ts
|
|
1516
|
-
var isVotedPollEvent = (event) => event.delta.type === "voted";
|
|
1517
|
-
var isUnvotedPollEvent = (event) => event.delta.type === "unvoted";
|
|
1518
|
-
var toCachedPoll = (input) => {
|
|
1519
|
-
const poll = asPoll({
|
|
1520
|
-
title: input.title,
|
|
1521
|
-
options: input.options.map((optionInfo) => ({
|
|
1522
|
-
title: optionInfo.text
|
|
1523
|
-
}))
|
|
1524
|
-
});
|
|
1525
|
-
const optionsByIdentifier = /* @__PURE__ */ new Map();
|
|
1526
|
-
for (const [index, optionInfo] of input.options.entries()) {
|
|
1527
|
-
const option = poll.options[index];
|
|
1528
|
-
if (option && optionInfo.optionIdentifier) {
|
|
1529
|
-
optionsByIdentifier.set(optionInfo.optionIdentifier, option);
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
return { poll, optionsByIdentifier };
|
|
1533
|
-
};
|
|
1534
|
-
var cachePollInfo = (cache, info) => {
|
|
1535
|
-
const cached = toCachedPoll(info);
|
|
1536
|
-
cache.set(info.messageGuid, cached);
|
|
1537
|
-
return cached;
|
|
1538
|
-
};
|
|
1539
|
-
var cachePollEvent = (cache, event) => {
|
|
1540
|
-
if (event.delta.type === "created" || event.delta.type === "optionAdded") {
|
|
1541
|
-
try {
|
|
1542
|
-
const cached = toCachedPoll({
|
|
1543
|
-
title: event.delta.title,
|
|
1544
|
-
options: event.delta.options
|
|
1545
|
-
});
|
|
1546
|
-
cache.set(event.pollMessageGuid, cached);
|
|
1547
|
-
return cached;
|
|
1548
|
-
} catch (e) {
|
|
1549
|
-
console.error("[spectrum-ts][imessage][poll] failed to cache poll", e);
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
};
|
|
1553
|
-
var fetchPollInfo = async (client, cache, event) => {
|
|
1554
|
-
try {
|
|
1555
|
-
const info = await client.polls.get(event.pollMessageGuid);
|
|
1556
|
-
cachePollInfo(cache, info);
|
|
1557
|
-
return info;
|
|
1558
|
-
} catch (e) {
|
|
1559
|
-
console.error("[spectrum-ts][imessage][poll] failed to fetch poll", e);
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1562
|
-
};
|
|
1563
|
-
var resolvePoll = async (client, cache, event) => {
|
|
1564
|
-
const pollId = event.pollMessageGuid;
|
|
1565
|
-
const cached = cache.get(pollId);
|
|
1566
|
-
if (cached) {
|
|
1567
|
-
return cached;
|
|
1568
|
-
}
|
|
1569
|
-
try {
|
|
1570
|
-
const info = await client.polls.get(event.pollMessageGuid);
|
|
1571
|
-
return cachePollInfo(cache, info);
|
|
1572
|
-
} catch (e) {
|
|
1573
|
-
console.error("[spectrum-ts][imessage][poll] failed to resolve poll", e);
|
|
1574
|
-
return;
|
|
1575
|
-
}
|
|
1576
|
-
};
|
|
1577
|
-
var buildPollOptionMessage = (input) => {
|
|
1578
|
-
const option = input.cached.optionsByIdentifier.get(input.optionId);
|
|
1579
|
-
if (!option) {
|
|
1580
|
-
return;
|
|
1581
|
-
}
|
|
1582
|
-
const action = input.selected ? "selected" : "deselected";
|
|
1583
|
-
return {
|
|
1584
|
-
id: `${input.event.pollMessageGuid}:${input.senderAddress}:${input.optionId}:${action}:${input.event.at.getTime()}`,
|
|
1585
|
-
sender: { id: input.senderAddress },
|
|
1586
|
-
space: {
|
|
1587
|
-
id: input.chatGuid,
|
|
1588
|
-
type: input.chatGuid.includes(";+;") ? "group" : "dm",
|
|
1589
|
-
phone: input.phone
|
|
1590
|
-
},
|
|
1591
|
-
timestamp: input.event.at,
|
|
1592
|
-
content: asPollOption({
|
|
1593
|
-
option,
|
|
1594
|
-
poll: input.cached.poll,
|
|
1595
|
-
selected: input.selected
|
|
1596
|
-
})
|
|
1597
|
-
};
|
|
1598
|
-
};
|
|
1599
|
-
var buildPollOptionMessages = (input) => {
|
|
1600
|
-
const messages5 = [];
|
|
1601
|
-
for (const delta of input.deltas) {
|
|
1602
|
-
const message = buildPollOptionMessage({
|
|
1603
|
-
cached: input.cached,
|
|
1604
|
-
chatGuid: input.chatGuid,
|
|
1605
|
-
event: input.event,
|
|
1606
|
-
optionId: delta.optionId,
|
|
1607
|
-
phone: input.phone,
|
|
1608
|
-
selected: delta.selected,
|
|
1609
|
-
senderAddress: input.senderAddress
|
|
1610
|
-
});
|
|
1611
|
-
if (message) {
|
|
1612
|
-
messages5.push(message);
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
return messages5;
|
|
1616
|
-
};
|
|
1617
|
-
var allOptionIdsKnown = (cached, optionIds) => optionIds.every((optionId) => cached.optionsByIdentifier.has(optionId));
|
|
1618
|
-
var refreshPollMetadata = async (client, pollCache, event, fallbackOptionIds) => {
|
|
1619
|
-
const info = await fetchPollInfo(client, pollCache, event);
|
|
1620
|
-
if (!info) {
|
|
1621
|
-
return;
|
|
1622
|
-
}
|
|
1623
|
-
const refreshed = pollCache.get(info.messageGuid);
|
|
1624
|
-
if (!refreshed) {
|
|
1625
|
-
return;
|
|
1626
|
-
}
|
|
1627
|
-
return {
|
|
1628
|
-
optionIds: [...fallbackOptionIds],
|
|
1629
|
-
poll: refreshed
|
|
1630
|
-
};
|
|
1631
|
-
};
|
|
1632
|
-
var toPollVoteMessages = async (client, pollCache, event, phone) => {
|
|
1633
|
-
const senderAddress = event.actor.address;
|
|
1634
|
-
if (!senderAddress) {
|
|
1635
|
-
return [];
|
|
1636
|
-
}
|
|
1637
|
-
const pollId = event.pollMessageGuid;
|
|
1638
|
-
if (pollCache.isStaleActorSelectionEvent(pollId, senderAddress, event.at)) {
|
|
1639
|
-
return [];
|
|
1640
|
-
}
|
|
1641
|
-
const cached = await resolvePoll(client, pollCache, event);
|
|
1642
|
-
if (!cached) {
|
|
1643
|
-
return [];
|
|
1644
|
-
}
|
|
1645
|
-
const chatGuidStr = event.chatGuid;
|
|
1646
|
-
let currentOptionIds = [...event.delta.optionIdentifiers];
|
|
1647
|
-
let resolvedPoll = cached;
|
|
1648
|
-
if (currentOptionIds.some(
|
|
1649
|
-
(optionId) => !resolvedPoll.optionsByIdentifier.has(optionId)
|
|
1650
|
-
)) {
|
|
1651
|
-
const snapshot = await refreshPollMetadata(
|
|
1652
|
-
client,
|
|
1653
|
-
pollCache,
|
|
1654
|
-
event,
|
|
1655
|
-
currentOptionIds
|
|
1656
|
-
);
|
|
1657
|
-
if (snapshot) {
|
|
1658
|
-
currentOptionIds = snapshot.optionIds;
|
|
1659
|
-
resolvedPoll = snapshot.poll;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
if (!allOptionIdsKnown(resolvedPoll, currentOptionIds)) {
|
|
1663
|
-
return [];
|
|
1664
|
-
}
|
|
1665
|
-
const deltas = pollCache.actorSelectionDeltas(
|
|
1666
|
-
pollId,
|
|
1667
|
-
senderAddress,
|
|
1668
|
-
currentOptionIds
|
|
1669
|
-
);
|
|
1670
|
-
const messages5 = buildPollOptionMessages({
|
|
1671
|
-
cached: resolvedPoll,
|
|
1672
|
-
chatGuid: chatGuidStr,
|
|
1673
|
-
deltas,
|
|
1674
|
-
event,
|
|
1675
|
-
phone,
|
|
1676
|
-
senderAddress
|
|
1677
|
-
});
|
|
1678
|
-
pollCache.commitActorSelection(
|
|
1679
|
-
pollId,
|
|
1680
|
-
senderAddress,
|
|
1681
|
-
currentOptionIds,
|
|
1682
|
-
event.at
|
|
1683
|
-
);
|
|
1684
|
-
return messages5;
|
|
1685
|
-
};
|
|
1686
|
-
var toPollUnvoteMessages = async (client, pollCache, event, phone) => {
|
|
1687
|
-
const senderAddress = event.actor.address;
|
|
1688
|
-
if (!senderAddress) {
|
|
1689
|
-
return [];
|
|
1690
|
-
}
|
|
1691
|
-
const pollId = event.pollMessageGuid;
|
|
1692
|
-
if (pollCache.isStaleActorSelectionEvent(pollId, senderAddress, event.at)) {
|
|
1693
|
-
return [];
|
|
1694
|
-
}
|
|
1695
|
-
const cached = await resolvePoll(client, pollCache, event);
|
|
1696
|
-
if (!cached) {
|
|
1697
|
-
return [];
|
|
1698
|
-
}
|
|
1699
|
-
const chatGuidStr = event.chatGuid;
|
|
1700
|
-
const deltas = pollCache.clearedActorSelectionDeltas(pollId, senderAddress);
|
|
1701
|
-
const messages5 = buildPollOptionMessages({
|
|
1702
|
-
cached,
|
|
1703
|
-
chatGuid: chatGuidStr,
|
|
1704
|
-
deltas,
|
|
1705
|
-
event,
|
|
1706
|
-
phone,
|
|
1707
|
-
senderAddress
|
|
1708
|
-
});
|
|
1709
|
-
pollCache.commitActorSelection(pollId, senderAddress, [], event.at);
|
|
1710
|
-
return messages5;
|
|
1711
|
-
};
|
|
1712
|
-
var toPollDeltaMessages = async (client, pollCache, event, phone) => {
|
|
1713
|
-
if (isVotedPollEvent(event)) {
|
|
1714
|
-
return toPollVoteMessages(client, pollCache, event, phone);
|
|
1715
|
-
}
|
|
1716
|
-
if (isUnvotedPollEvent(event)) {
|
|
1717
|
-
return toPollUnvoteMessages(client, pollCache, event, phone);
|
|
1718
|
-
}
|
|
1719
|
-
return [];
|
|
1720
|
-
};
|
|
1721
|
-
|
|
1722
|
-
// src/providers/imessage/remote/stream.ts
|
|
1723
|
-
var pollRetryDelay = (delayMs) => Math.random() * delayMs;
|
|
1724
|
-
var isRetryableIMessageStreamError = (error) => {
|
|
1725
|
-
if (error instanceof AuthenticationError || error instanceof NotFoundError2 || error instanceof ValidationError) {
|
|
1726
|
-
return false;
|
|
1727
|
-
}
|
|
1728
|
-
if (error instanceof IMessageError) {
|
|
1729
|
-
return true;
|
|
1730
|
-
}
|
|
1731
|
-
return false;
|
|
1732
|
-
};
|
|
1733
|
-
var toMessageItem = async (client, event, phone, cursor) => {
|
|
1734
|
-
const id = event.message.guid;
|
|
1735
|
-
if (event.message.isFromMe) {
|
|
1736
|
-
return { cursor, id, values: [] };
|
|
1737
|
-
}
|
|
1738
|
-
const cache = getMessageCache(client);
|
|
1739
|
-
const target = event.message.associatedMessageGuid;
|
|
1740
|
-
const values = target ? await toReactionMessages(client, cache, event, target, phone) : await toInboundMessages(client, cache, event, phone);
|
|
1741
|
-
return { cursor, id, values };
|
|
1742
|
-
};
|
|
1743
|
-
var messageStream = (client, phone) => resumableOrderedStream({
|
|
1744
|
-
fetchMissed: (cursor, { limit }) => client.messages.fetchMissed(cursor, { limit }),
|
|
1745
|
-
isRetryableError: isRetryableIMessageStreamError,
|
|
1746
|
-
processLive: (event) => toMessageItem(client, event, phone, event.cursor),
|
|
1747
|
-
processMissed: (message) => toMessageItem(client, receivedEventFromMessage(message), phone),
|
|
1748
|
-
subscribeLive: () => client.messages.subscribe("message.received")
|
|
1749
|
-
});
|
|
1750
|
-
var logPollStreamError = (error) => {
|
|
1751
|
-
console.error("[spectrum-ts][imessage][poll] stream failed", error);
|
|
1752
|
-
};
|
|
1753
|
-
var emitPollMessages = async (client, pollCache, event, phone, emit) => {
|
|
1754
|
-
cachePollEvent(pollCache, event);
|
|
1755
|
-
if (event.actor.isFromMe) {
|
|
1756
|
-
return;
|
|
1757
|
-
}
|
|
1758
|
-
const messages5 = await toPollDeltaMessages(client, pollCache, event, phone);
|
|
1759
|
-
for (const vote of messages5) {
|
|
1760
|
-
await emit(vote);
|
|
1761
|
-
}
|
|
1762
|
-
};
|
|
1763
|
-
var runPollSubscription = async (client, pollCache, subscription, phone, emit, onEvent) => {
|
|
1764
|
-
for await (const event of subscription) {
|
|
1765
|
-
onEvent();
|
|
1766
|
-
await emitPollMessages(client, pollCache, event, phone, emit);
|
|
1767
|
-
}
|
|
1768
|
-
};
|
|
1769
|
-
var pollStream = (client, pollCache, phone) => stream((emit, end) => {
|
|
1770
|
-
let active = client.polls.subscribe();
|
|
1771
|
-
let closed = false;
|
|
1772
|
-
let retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
|
|
1773
|
-
let sleepTimer;
|
|
1774
|
-
let wakeSleep;
|
|
1775
|
-
const sleep = async (delayMs) => {
|
|
1776
|
-
if (closed) {
|
|
1777
|
-
return;
|
|
1778
|
-
}
|
|
1779
|
-
await new Promise((resolve) => {
|
|
1780
|
-
wakeSleep = resolve;
|
|
1781
|
-
sleepTimer = setTimeout(resolve, pollRetryDelay(delayMs));
|
|
1782
|
-
});
|
|
1783
|
-
sleepTimer = void 0;
|
|
1784
|
-
wakeSleep = void 0;
|
|
1785
|
-
};
|
|
1786
|
-
const cancelSleep = () => {
|
|
1787
|
-
if (sleepTimer) {
|
|
1788
|
-
clearTimeout(sleepTimer);
|
|
1789
|
-
sleepTimer = void 0;
|
|
1790
|
-
}
|
|
1791
|
-
wakeSleep?.();
|
|
1792
|
-
wakeSleep = void 0;
|
|
1793
|
-
};
|
|
1794
|
-
const pump = (async () => {
|
|
1795
|
-
while (!closed) {
|
|
1796
|
-
try {
|
|
1797
|
-
await runPollSubscription(
|
|
1798
|
-
client,
|
|
1799
|
-
pollCache,
|
|
1800
|
-
active,
|
|
1801
|
-
phone,
|
|
1802
|
-
emit,
|
|
1803
|
-
() => {
|
|
1804
|
-
retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
|
|
1805
|
-
}
|
|
1806
|
-
);
|
|
1807
|
-
} catch (e) {
|
|
1808
|
-
if (!closed) {
|
|
1809
|
-
logPollStreamError(e);
|
|
1810
|
-
}
|
|
1811
|
-
} finally {
|
|
1812
|
-
await active.close();
|
|
1813
|
-
}
|
|
1814
|
-
if (!closed) {
|
|
1815
|
-
await sleep(retryDelayMs);
|
|
1816
|
-
retryDelayMs = Math.min(retryDelayMs * 2, RECONNECT_MAX_DELAY_MS);
|
|
1817
|
-
active = client.polls.subscribe();
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
end();
|
|
1821
|
-
})();
|
|
1822
|
-
return async () => {
|
|
1823
|
-
closed = true;
|
|
1824
|
-
cancelSleep();
|
|
1825
|
-
await active.close();
|
|
1826
|
-
await pump;
|
|
1827
|
-
};
|
|
1828
|
-
});
|
|
1829
|
-
var clientStream = (client, pollCache, phone) => {
|
|
1830
|
-
return mergeStreams([
|
|
1831
|
-
messageStream(client, phone),
|
|
1832
|
-
pollStream(client, pollCache, phone)
|
|
1833
|
-
]);
|
|
1834
|
-
};
|
|
1835
|
-
var messages3 = (clients) => {
|
|
1836
|
-
const pollCache = getPollCache(clients);
|
|
1837
|
-
return mergeStreams(
|
|
1838
|
-
clients.map((entry) => clientStream(entry.client, pollCache, entry.phone))
|
|
1839
|
-
);
|
|
1840
|
-
};
|
|
1841
|
-
|
|
1842
|
-
// src/providers/imessage/remote/typing.ts
|
|
1843
|
-
import { chatGuid as chatGuid3 } from "@photon-ai/advanced-imessage";
|
|
1844
|
-
var startTyping = async (remote, spaceId) => {
|
|
1845
|
-
await remote.chats.startTyping(chatGuid3(spaceId));
|
|
1846
|
-
};
|
|
1847
|
-
var stopTyping = async (remote, spaceId) => {
|
|
1848
|
-
await remote.chats.stopTyping(chatGuid3(spaceId));
|
|
1849
|
-
};
|
|
1850
|
-
|
|
1851
|
-
// src/providers/imessage/remote/api.ts
|
|
1852
|
-
var messages4 = (clients) => messages3(clients);
|
|
1853
|
-
var startTyping2 = async (remote, spaceId) => {
|
|
1854
|
-
await startTyping(remote, spaceId);
|
|
1855
|
-
};
|
|
1856
|
-
var stopTyping2 = async (remote, spaceId) => {
|
|
1857
|
-
await stopTyping(remote, spaceId);
|
|
1858
|
-
};
|
|
1859
|
-
var send4 = async (remote, spaceId, content) => send3(remote, spaceId, content);
|
|
1860
|
-
var replyToMessage2 = async (remote, spaceId, msgId, content) => replyToMessage(remote, spaceId, msgId, content);
|
|
1861
|
-
var editMessage2 = async (remote, spaceId, msgId, content) => editMessage(remote, spaceId, msgId, content);
|
|
1862
|
-
var reactToMessage2 = async (remote, spaceId, target, reaction) => {
|
|
1863
|
-
await reactToMessage(remote, spaceId, target, reaction);
|
|
1864
|
-
};
|
|
1865
|
-
var getMessage4 = async (remote, spaceId, msgId, phone) => getMessage3(remote, spaceId, msgId, phone);
|
|
1866
|
-
|
|
1867
|
-
// src/providers/imessage/remote/client.ts
|
|
1868
|
-
var availablePhones = (clients) => clients.map((c) => c.phone);
|
|
1869
|
-
var clientForPhone = (clients, phone) => {
|
|
1870
|
-
const entry = clients.find((c) => c.phone === phone);
|
|
1871
|
-
if (!entry) {
|
|
1872
|
-
const list = availablePhones(clients).join(", ") || "<none>";
|
|
1873
|
-
throw new Error(
|
|
1874
|
-
`No iMessage client serves phone ${phone}. Available: ${list}`
|
|
1875
|
-
);
|
|
1876
|
-
}
|
|
1877
|
-
return entry.client;
|
|
1878
|
-
};
|
|
1879
|
-
var randomPhone = (clients) => {
|
|
1880
|
-
if (clients.length === 0) {
|
|
1881
|
-
throw new Error("No iMessage phones configured for this account");
|
|
1882
|
-
}
|
|
1883
|
-
const entry = clients[Math.floor(Math.random() * clients.length)];
|
|
1884
|
-
if (!entry) {
|
|
1885
|
-
throw new Error("No iMessage phones configured for this account");
|
|
1886
|
-
}
|
|
1887
|
-
return entry.phone;
|
|
1888
|
-
};
|
|
1889
|
-
|
|
1890
|
-
// src/providers/imessage/types.ts
|
|
1891
|
-
import { IMessageSDK } from "@photon-ai/imessage-kit";
|
|
1892
|
-
import z2 from "zod";
|
|
1893
|
-
var isLocal = (client) => client instanceof IMessageSDK;
|
|
1894
|
-
var clientEntry = z2.object({
|
|
1895
|
-
address: z2.string(),
|
|
1896
|
-
token: z2.string(),
|
|
1897
|
-
phone: z2.string()
|
|
1898
|
-
});
|
|
1899
|
-
var configSchema = z2.union([
|
|
1900
|
-
z2.object({ local: z2.literal(true) }),
|
|
1901
|
-
z2.object({
|
|
1902
|
-
local: z2.literal(false).optional().default(false),
|
|
1903
|
-
clients: clientEntry.or(z2.array(clientEntry)).optional()
|
|
1904
|
-
})
|
|
1905
|
-
]);
|
|
1906
|
-
var userSchema = z2.object({});
|
|
1907
|
-
var spaceSchema = z2.object({
|
|
1908
|
-
id: z2.string(),
|
|
1909
|
-
type: z2.enum(["dm", "group"]),
|
|
1910
|
-
phone: z2.string()
|
|
1911
|
-
});
|
|
1912
|
-
var spaceParamsSchema = z2.object({
|
|
1913
|
-
phone: z2.string().optional()
|
|
1914
|
-
});
|
|
1915
|
-
var messageSchema = z2.object({
|
|
1916
|
-
partIndex: z2.number().int().nonnegative().optional(),
|
|
1917
|
-
parentId: z2.string().optional()
|
|
1918
|
-
});
|
|
1919
|
-
|
|
1920
|
-
// src/providers/imessage/index.ts
|
|
1921
|
-
var isPollContent = (content) => content.type === "poll" || content.type === "poll_option";
|
|
1922
|
-
var imessage = definePlatform("iMessage", {
|
|
1923
|
-
config: configSchema,
|
|
1924
|
-
static: {
|
|
1925
|
-
effect: {
|
|
1926
|
-
message: MessageEffect2
|
|
1927
|
-
}
|
|
1928
|
-
},
|
|
1929
|
-
lifecycle: {
|
|
1930
|
-
createClient: async ({
|
|
1931
|
-
config,
|
|
1932
|
-
projectId,
|
|
1933
|
-
projectSecret
|
|
1934
|
-
}) => {
|
|
1935
|
-
if (config.local) {
|
|
1936
|
-
return new IMessageSDK2();
|
|
1937
|
-
}
|
|
1938
|
-
if (config.clients) {
|
|
1939
|
-
const entries = Array.isArray(config.clients) ? config.clients : [config.clients];
|
|
1940
|
-
return entries.map((e) => ({
|
|
1941
|
-
phone: e.phone,
|
|
1942
|
-
client: createClient2({
|
|
1943
|
-
address: e.address,
|
|
1944
|
-
tls: true,
|
|
1945
|
-
token: e.token
|
|
1946
|
-
})
|
|
1947
|
-
}));
|
|
1948
|
-
}
|
|
1949
|
-
if (!(projectId && projectSecret)) {
|
|
1950
|
-
throw new Error(
|
|
1951
|
-
"iMessage requires projectId and projectSecret. Either pass credentials to Spectrum(), use local mode: imessage.config({ local: true }), or provide explicit client config: imessage.config({ clients: [...] })"
|
|
1952
|
-
);
|
|
1953
|
-
}
|
|
1954
|
-
return await createCloudClients(projectId, projectSecret);
|
|
1955
|
-
},
|
|
1956
|
-
destroyClient: async ({ client }) => {
|
|
1957
|
-
if (isLocal(client)) {
|
|
1958
|
-
await client.close();
|
|
1959
|
-
return;
|
|
1960
|
-
}
|
|
1961
|
-
await disposeCloudAuth(client);
|
|
1962
|
-
await Promise.all(client.map((entry) => entry.client.close()));
|
|
1963
|
-
}
|
|
1964
|
-
},
|
|
1965
|
-
user: {
|
|
1966
|
-
resolve: async ({ input }) => ({ id: input.userID })
|
|
1967
|
-
},
|
|
1968
|
-
space: {
|
|
1969
|
-
schema: spaceSchema,
|
|
1970
|
-
params: spaceParamsSchema,
|
|
1971
|
-
resolve: async ({ input, client }) => {
|
|
1972
|
-
if (isLocal(client)) {
|
|
1973
|
-
throw UnsupportedError.action(
|
|
1974
|
-
"createSpace",
|
|
1975
|
-
"iMessage (local mode)",
|
|
1976
|
-
"local mode only supports replying to existing messages"
|
|
1977
|
-
);
|
|
1978
|
-
}
|
|
1979
|
-
if (input.users.length === 0) {
|
|
1980
|
-
throw new Error("iMessage space creation requires at least one user");
|
|
1981
|
-
}
|
|
1982
|
-
if (client.length === 0) {
|
|
1983
|
-
throw new Error("No iMessage clients configured");
|
|
1984
|
-
}
|
|
1985
|
-
const phone = input.params?.phone ?? randomPhone(client);
|
|
1986
|
-
const remote = clientForPhone(client, phone);
|
|
1987
|
-
const addresses = input.users.map((u) => u.id);
|
|
1988
|
-
if (input.users.length === 1) {
|
|
1989
|
-
return {
|
|
1990
|
-
id: directChat(addresses[0] ?? ""),
|
|
1991
|
-
type: "dm",
|
|
1992
|
-
phone
|
|
1993
|
-
};
|
|
1994
|
-
}
|
|
1995
|
-
const { chat } = await remote.chats.create(addresses);
|
|
1996
|
-
return { id: chat.guid, type: "group", phone };
|
|
1997
|
-
}
|
|
1998
|
-
},
|
|
1999
|
-
message: {
|
|
2000
|
-
schema: messageSchema
|
|
2001
|
-
},
|
|
2002
|
-
events: {
|
|
2003
|
-
messages: ({ client }) => isLocal(client) ? messages2(client) : messages4(client)
|
|
2004
|
-
},
|
|
2005
|
-
actions: {
|
|
2006
|
-
send: async ({ space, content, client }) => {
|
|
2007
|
-
if (isLocal(client)) {
|
|
2008
|
-
return await send2(client, space.id, content);
|
|
2009
|
-
}
|
|
2010
|
-
const remote = clientForPhone(client, space.phone);
|
|
2011
|
-
return await send4(remote, space.id, content);
|
|
2012
|
-
},
|
|
2013
|
-
startTyping: async ({ space, client }) => {
|
|
2014
|
-
if (isLocal(client)) {
|
|
2015
|
-
return;
|
|
2016
|
-
}
|
|
2017
|
-
const remote = clientForPhone(client, space.phone);
|
|
2018
|
-
await startTyping2(remote, space.id);
|
|
2019
|
-
},
|
|
2020
|
-
stopTyping: async ({ space, client }) => {
|
|
2021
|
-
if (isLocal(client)) {
|
|
2022
|
-
return;
|
|
2023
|
-
}
|
|
2024
|
-
const remote = clientForPhone(client, space.phone);
|
|
2025
|
-
await stopTyping2(remote, space.id);
|
|
2026
|
-
},
|
|
2027
|
-
reactToMessage: async ({ space, target, reaction, client }) => {
|
|
2028
|
-
if (isLocal(client)) {
|
|
2029
|
-
throw UnsupportedError.action("react", "iMessage (local mode)");
|
|
2030
|
-
}
|
|
2031
|
-
if (isPollContent(target.content)) {
|
|
2032
|
-
throw UnsupportedError.action(
|
|
2033
|
-
"react",
|
|
2034
|
-
"iMessage",
|
|
2035
|
-
"iMessage polls do not support reactions"
|
|
2036
|
-
);
|
|
2037
|
-
}
|
|
2038
|
-
const remote = clientForPhone(client, space.phone);
|
|
2039
|
-
await reactToMessage2(
|
|
2040
|
-
remote,
|
|
2041
|
-
space.id,
|
|
2042
|
-
target,
|
|
2043
|
-
reaction
|
|
2044
|
-
);
|
|
2045
|
-
},
|
|
2046
|
-
replyToMessage: async ({ space, messageId, target, content, client }) => {
|
|
2047
|
-
if (isLocal(client)) {
|
|
2048
|
-
throw UnsupportedError.action("reply", "iMessage (local mode)");
|
|
2049
|
-
}
|
|
2050
|
-
if (isPollContent(target.content)) {
|
|
2051
|
-
throw UnsupportedError.action(
|
|
2052
|
-
"reply",
|
|
2053
|
-
"iMessage",
|
|
2054
|
-
"iMessage polls do not support replies"
|
|
2055
|
-
);
|
|
2056
|
-
}
|
|
2057
|
-
const remote = clientForPhone(client, space.phone);
|
|
2058
|
-
return await replyToMessage2(remote, space.id, messageId, content);
|
|
2059
|
-
},
|
|
2060
|
-
editMessage: async ({ space, messageId, content, client }) => {
|
|
2061
|
-
if (isLocal(client)) {
|
|
2062
|
-
throw UnsupportedError.action("edit", "iMessage (local mode)");
|
|
2063
|
-
}
|
|
2064
|
-
const remote = clientForPhone(client, space.phone);
|
|
2065
|
-
await editMessage2(remote, space.id, messageId, content);
|
|
2066
|
-
},
|
|
2067
|
-
getMessage: async ({ space, messageId, client }) => {
|
|
2068
|
-
if (isLocal(client)) {
|
|
2069
|
-
return getMessage2(client, messageId);
|
|
2070
|
-
}
|
|
2071
|
-
const remote = clientForPhone(client, space.phone);
|
|
2072
|
-
return getMessage4(remote, space.id, messageId, space.phone);
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
});
|
|
2
|
+
effect,
|
|
3
|
+
imessage
|
|
4
|
+
} from "../../chunk-4U4RINOV.js";
|
|
5
|
+
import "../../chunk-66GJ45ZZ.js";
|
|
6
|
+
import "../../chunk-L6LUFBLF.js";
|
|
7
|
+
import "../../chunk-LH4YEBG3.js";
|
|
2076
8
|
export {
|
|
2077
9
|
effect,
|
|
2078
10
|
imessage
|