spectrum-ts 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{attachment-B4nSrKVd.d.ts → attachment-WePAHfcH.d.ts} +1 -1
- package/dist/{authoring-BjE5BvlO.d.ts → authoring-DDh3muGT.d.ts} +61 -26
- package/dist/authoring.d.ts +3 -3
- package/dist/authoring.js +5 -5
- package/dist/{chunk-NNY6LMSC.js → chunk-77U6SH5A.js} +1 -1
- package/dist/{chunk-ATNAE7OR.js → chunk-AYCMTRVC.js} +549 -76
- package/dist/{chunk-6BI4PFTP.js → chunk-CHY5YLLV.js} +1 -1
- package/dist/{chunk-U3LXXT3W.js → chunk-EZ5SNNFS.js} +20 -8
- package/dist/{chunk-WXY5QP3M.js → chunk-FULEQIRQ.js} +27 -21
- package/dist/{chunk-2ILTJC35.js → chunk-LQMDV75O.js} +205 -11
- package/dist/{chunk-NGC4DJIX.js → chunk-LX437ZTY.js} +416 -135
- package/dist/{chunk-3B4QH4JG.js → chunk-MHGCPC2V.js} +1 -1
- package/dist/{chunk-U7AWXDH6.js → chunk-NZ5WCMTY.js} +1 -1
- package/dist/{chunk-5LT5J3NR.js → chunk-TXRWKSNH.js} +262 -30
- package/dist/{chunk-Q537JPTG.js → chunk-UXJ5OO6P.js} +10 -10
- package/dist/index.d.ts +107 -56
- package/dist/index.js +29 -182
- package/dist/providers/imessage/index.d.ts +6 -14
- package/dist/providers/imessage/index.js +6 -6
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +11 -11
- package/dist/providers/slack/index.d.ts +1 -2
- package/dist/providers/slack/index.js +3 -3
- package/dist/providers/telegram/index.d.ts +3 -5
- package/dist/providers/telegram/index.js +5 -5
- package/dist/providers/terminal/index.d.ts +2 -4
- package/dist/providers/terminal/index.js +5 -5
- package/dist/providers/whatsapp-business/index.d.ts +1 -1
- package/dist/providers/whatsapp-business/index.js +4 -4
- package/dist/{types-BD0-kKyv.d.ts → types-BujGKBin.d.ts} +1 -1
- package/dist/{types-Bje8aq1k.d.ts → types-YqCNUDIt.d.ts} +171 -23
- package/package.json +2 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
asRichlink
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CHY5YLLV.js";
|
|
5
5
|
import {
|
|
6
6
|
asGroup,
|
|
7
7
|
groupSchema
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MHGCPC2V.js";
|
|
9
9
|
import {
|
|
10
10
|
asPoll,
|
|
11
11
|
asPollOption
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./chunk-3GEJYGZK.js";
|
|
16
16
|
import {
|
|
17
17
|
asContact
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-NZ5WCMTY.js";
|
|
19
19
|
import {
|
|
20
20
|
mergeStreams,
|
|
21
21
|
stream
|
|
@@ -28,8 +28,9 @@ import {
|
|
|
28
28
|
UnsupportedError,
|
|
29
29
|
buildPhotoAction,
|
|
30
30
|
definePlatform,
|
|
31
|
+
markdownSchema,
|
|
31
32
|
photoActionSchema
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-LX437ZTY.js";
|
|
33
34
|
import {
|
|
34
35
|
asAttachment,
|
|
35
36
|
asCustom,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
reactionSchema,
|
|
39
40
|
text,
|
|
40
41
|
textSchema
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-LQMDV75O.js";
|
|
42
43
|
|
|
43
44
|
// src/providers/imessage/index.ts
|
|
44
45
|
import { createClient as createClient2, MessageEffect as MessageEffect2 } from "@photon-ai/advanced-imessage";
|
|
@@ -133,6 +134,7 @@ import {
|
|
|
133
134
|
import z3 from "zod";
|
|
134
135
|
var effectInnerSchema = z3.discriminatedUnion("type", [
|
|
135
136
|
textSchema,
|
|
137
|
+
markdownSchema,
|
|
136
138
|
attachmentSchema
|
|
137
139
|
]);
|
|
138
140
|
var messageEffectSchema = z3.object({
|
|
@@ -153,9 +155,9 @@ function effect(input, messageEffect) {
|
|
|
153
155
|
);
|
|
154
156
|
}
|
|
155
157
|
const inner = await resolveContent(input);
|
|
156
|
-
if (inner.type !== "text" && inner.type !== "attachment") {
|
|
158
|
+
if (inner.type !== "text" && inner.type !== "markdown" && inner.type !== "attachment") {
|
|
157
159
|
throw new Error(
|
|
158
|
-
`imessage effect() only supports text and attachment content, got "${inner.type}"`
|
|
160
|
+
`imessage effect() only supports text, markdown, and attachment content, got "${inner.type}"`
|
|
159
161
|
);
|
|
160
162
|
}
|
|
161
163
|
return messageEffectSchema.parse({
|
|
@@ -571,6 +573,7 @@ var getMessage2 = async (client, id) => getMessage(client, id);
|
|
|
571
573
|
// src/providers/imessage/remote/ids.ts
|
|
572
574
|
var PART_PREFIX = /^p:(\d+)\//;
|
|
573
575
|
var dmChatGuid = (address) => `any;-;${address}`;
|
|
576
|
+
var chatTypeFromGuid = (guid) => guid.includes(";+;") ? "group" : "dm";
|
|
574
577
|
var toChatGuid = (value) => value;
|
|
575
578
|
var toMessageGuid = (value) => value;
|
|
576
579
|
var formatChildId = (partIndex, parentGuid) => `p:${partIndex}/${parentGuid}`;
|
|
@@ -805,7 +808,7 @@ var buildMessageBase = (message, chatGuidHint, timestamp, phone) => {
|
|
|
805
808
|
sender: { id: resolveSenderId(message) },
|
|
806
809
|
space: {
|
|
807
810
|
id: chat,
|
|
808
|
-
type: chat
|
|
811
|
+
type: chatTypeFromGuid(chat),
|
|
809
812
|
phone
|
|
810
813
|
},
|
|
811
814
|
timestamp
|
|
@@ -1090,7 +1093,7 @@ var toReactionMessages = async (client, cache, event, phone) => {
|
|
|
1090
1093
|
sender: { id: senderAddress },
|
|
1091
1094
|
space: {
|
|
1092
1095
|
id: event.chatGuid,
|
|
1093
|
-
type: event.chatGuid
|
|
1096
|
+
type: chatTypeFromGuid(event.chatGuid),
|
|
1094
1097
|
phone
|
|
1095
1098
|
},
|
|
1096
1099
|
timestamp: event.occurredAt,
|
|
@@ -1099,23 +1102,39 @@ var toReactionMessages = async (client, cache, event, phone) => {
|
|
|
1099
1102
|
}
|
|
1100
1103
|
];
|
|
1101
1104
|
};
|
|
1105
|
+
var toSettableReaction = (emoji) => {
|
|
1106
|
+
const native = EMOJI_TO_TAPBACK[emoji];
|
|
1107
|
+
return native ? { kind: native } : { kind: "emoji", emoji };
|
|
1108
|
+
};
|
|
1109
|
+
var tapbackTarget = (target) => ({
|
|
1110
|
+
guid: toMessageGuid(target.parentId ?? target.id),
|
|
1111
|
+
opts: typeof target.partIndex === "number" ? { partIndex: target.partIndex } : void 0
|
|
1112
|
+
});
|
|
1102
1113
|
var reactToMessage = async (remote, spaceId, target, reaction) => {
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1114
|
+
const { guid, opts } = tapbackTarget(target);
|
|
1115
|
+
const sent = await remote.messages.setReaction(
|
|
1116
|
+
toChatGuid(spaceId),
|
|
1117
|
+
guid,
|
|
1118
|
+
toSettableReaction(reaction),
|
|
1119
|
+
true,
|
|
1120
|
+
opts
|
|
1121
|
+
);
|
|
1122
|
+
return {
|
|
1123
|
+
id: sent.guid,
|
|
1124
|
+
content: asProviderReaction(reaction, target),
|
|
1125
|
+
space: { id: spaceId },
|
|
1126
|
+
timestamp: sent.dateCreated
|
|
1127
|
+
};
|
|
1128
|
+
};
|
|
1129
|
+
var unsendReaction = async (remote, spaceId, target, reaction) => {
|
|
1130
|
+
const { guid, opts } = tapbackTarget(target);
|
|
1131
|
+
await remote.messages.setReaction(
|
|
1132
|
+
toChatGuid(spaceId),
|
|
1133
|
+
guid,
|
|
1134
|
+
toSettableReaction(reaction),
|
|
1135
|
+
false,
|
|
1136
|
+
opts
|
|
1137
|
+
);
|
|
1119
1138
|
};
|
|
1120
1139
|
|
|
1121
1140
|
// src/providers/imessage/remote/read.ts
|
|
@@ -1251,13 +1270,314 @@ var ensureM4a = async (buffer, mimeType) => {
|
|
|
1251
1270
|
return transcodeToM4a(buffer);
|
|
1252
1271
|
};
|
|
1253
1272
|
|
|
1273
|
+
// src/providers/imessage/remote/markdown.ts
|
|
1274
|
+
import { Marked } from "marked";
|
|
1275
|
+
var markdownLexer = new Marked();
|
|
1276
|
+
var BULLET = "\u2022 ";
|
|
1277
|
+
var HR_LINE = "\u2014\u2014\u2014";
|
|
1278
|
+
var NESTED_LIST_INDENT = " ";
|
|
1279
|
+
var BLOCK_SEPARATOR = "\n\n";
|
|
1280
|
+
var TABLE_CELL_SEPARATOR = " | ";
|
|
1281
|
+
var DEFAULT_LIST_START = 1;
|
|
1282
|
+
var LEADING_WHITESPACE = /^\s+/;
|
|
1283
|
+
var TRAILING_WHITESPACE = /\s+$/;
|
|
1284
|
+
var MONOSPACE_UPPER_A = 120432;
|
|
1285
|
+
var MONOSPACE_LOWER_A = 120458;
|
|
1286
|
+
var MONOSPACE_DIGIT_ZERO = 120822;
|
|
1287
|
+
var UPPER_A = 65;
|
|
1288
|
+
var UPPER_Z = 90;
|
|
1289
|
+
var LOWER_A = 97;
|
|
1290
|
+
var LOWER_Z = 122;
|
|
1291
|
+
var DIGIT_ZERO = 48;
|
|
1292
|
+
var DIGIT_NINE = 57;
|
|
1293
|
+
var monospaceCodePoint = (codePoint) => {
|
|
1294
|
+
if (codePoint >= UPPER_A && codePoint <= UPPER_Z) {
|
|
1295
|
+
return MONOSPACE_UPPER_A + (codePoint - UPPER_A);
|
|
1296
|
+
}
|
|
1297
|
+
if (codePoint >= LOWER_A && codePoint <= LOWER_Z) {
|
|
1298
|
+
return MONOSPACE_LOWER_A + (codePoint - LOWER_A);
|
|
1299
|
+
}
|
|
1300
|
+
if (codePoint >= DIGIT_ZERO && codePoint <= DIGIT_NINE) {
|
|
1301
|
+
return MONOSPACE_DIGIT_ZERO + (codePoint - DIGIT_ZERO);
|
|
1302
|
+
}
|
|
1303
|
+
return codePoint;
|
|
1304
|
+
};
|
|
1305
|
+
var toMonospace = (text2) => {
|
|
1306
|
+
let out = "";
|
|
1307
|
+
for (const char of text2) {
|
|
1308
|
+
const codePoint = char.codePointAt(0);
|
|
1309
|
+
out += codePoint === void 0 ? char : String.fromCodePoint(monospaceCodePoint(codePoint));
|
|
1310
|
+
}
|
|
1311
|
+
return out;
|
|
1312
|
+
};
|
|
1313
|
+
var STYLE_ORDER = ["bold", "italic", "strikethrough"];
|
|
1314
|
+
var plain = (text2) => ({ text: text2, styles: [] });
|
|
1315
|
+
var withStyle = (spans, style) => spans.map(
|
|
1316
|
+
(span) => span.styles.includes(style) ? span : { ...span, styles: [...span.styles, style] }
|
|
1317
|
+
);
|
|
1318
|
+
var asLink = (spans) => spans.map((span) => ({ ...span, link: true }));
|
|
1319
|
+
var spanText = (spans) => {
|
|
1320
|
+
let out = "";
|
|
1321
|
+
for (const span of spans) {
|
|
1322
|
+
out += span.text;
|
|
1323
|
+
}
|
|
1324
|
+
return out;
|
|
1325
|
+
};
|
|
1326
|
+
var joinSpans = (blocks, separator) => {
|
|
1327
|
+
const out = [];
|
|
1328
|
+
for (const [index, block] of blocks.entries()) {
|
|
1329
|
+
if (index > 0) {
|
|
1330
|
+
out.push(plain(separator));
|
|
1331
|
+
}
|
|
1332
|
+
out.push(...block);
|
|
1333
|
+
}
|
|
1334
|
+
return out;
|
|
1335
|
+
};
|
|
1336
|
+
var splitSpanLines = (spans) => {
|
|
1337
|
+
let current = [];
|
|
1338
|
+
const lines = [current];
|
|
1339
|
+
for (const span of spans) {
|
|
1340
|
+
const parts = span.text.split("\n");
|
|
1341
|
+
for (const [index, part] of parts.entries()) {
|
|
1342
|
+
if (index > 0) {
|
|
1343
|
+
current = [];
|
|
1344
|
+
lines.push(current);
|
|
1345
|
+
}
|
|
1346
|
+
if (part) {
|
|
1347
|
+
current.push({ ...span, text: part });
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
return lines;
|
|
1352
|
+
};
|
|
1353
|
+
var asMarkedToken = (token) => token;
|
|
1354
|
+
var checkboxPrefix = (item) => {
|
|
1355
|
+
if (!item.task) {
|
|
1356
|
+
return "";
|
|
1357
|
+
}
|
|
1358
|
+
return item.checked ? "[x] " : "[ ] ";
|
|
1359
|
+
};
|
|
1360
|
+
var listMarker = (list, index) => {
|
|
1361
|
+
if (!list.ordered) {
|
|
1362
|
+
return BULLET;
|
|
1363
|
+
}
|
|
1364
|
+
const start = list.start === "" ? DEFAULT_LIST_START : list.start;
|
|
1365
|
+
return `${start + index}. `;
|
|
1366
|
+
};
|
|
1367
|
+
var renderLink = (token) => {
|
|
1368
|
+
if (token.text === token.href) {
|
|
1369
|
+
return [{ text: token.href, styles: [], link: true }];
|
|
1370
|
+
}
|
|
1371
|
+
return [
|
|
1372
|
+
...asLink(renderInlineTokens(token.tokens)),
|
|
1373
|
+
{ text: ` (${token.href})`, styles: [], link: true }
|
|
1374
|
+
];
|
|
1375
|
+
};
|
|
1376
|
+
var renderImage = (token) => [
|
|
1377
|
+
{
|
|
1378
|
+
text: token.text ? `${token.text} (${token.href})` : token.href,
|
|
1379
|
+
styles: [],
|
|
1380
|
+
link: true
|
|
1381
|
+
}
|
|
1382
|
+
];
|
|
1383
|
+
var renderInlineToken = (token) => {
|
|
1384
|
+
switch (token.type) {
|
|
1385
|
+
case "strong":
|
|
1386
|
+
return withStyle(renderInlineTokens(token.tokens), "bold");
|
|
1387
|
+
case "em":
|
|
1388
|
+
return withStyle(renderInlineTokens(token.tokens), "italic");
|
|
1389
|
+
case "del":
|
|
1390
|
+
return withStyle(renderInlineTokens(token.tokens), "strikethrough");
|
|
1391
|
+
case "codespan":
|
|
1392
|
+
return [plain(toMonospace(token.text))];
|
|
1393
|
+
case "br":
|
|
1394
|
+
return [plain("\n")];
|
|
1395
|
+
case "link":
|
|
1396
|
+
return renderLink(token);
|
|
1397
|
+
case "image":
|
|
1398
|
+
return renderImage(token);
|
|
1399
|
+
case "escape":
|
|
1400
|
+
return [plain(token.text)];
|
|
1401
|
+
case "text":
|
|
1402
|
+
return token.tokens ? renderInlineTokens(token.tokens) : [plain(token.text)];
|
|
1403
|
+
// Raw HTML in markdown source stays literal — styled text has no markup.
|
|
1404
|
+
case "html":
|
|
1405
|
+
return [plain(token.text)];
|
|
1406
|
+
// Task-item checkboxes are rendered from `ListItem.task`/`checked`.
|
|
1407
|
+
case "checkbox":
|
|
1408
|
+
return [];
|
|
1409
|
+
default:
|
|
1410
|
+
return "raw" in token ? [plain(String(token.raw))] : [];
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
var renderInlineTokens = (tokens) => {
|
|
1414
|
+
const out = [];
|
|
1415
|
+
for (const token of tokens) {
|
|
1416
|
+
out.push(...renderInlineToken(asMarkedToken(token)));
|
|
1417
|
+
}
|
|
1418
|
+
return out;
|
|
1419
|
+
};
|
|
1420
|
+
var renderBlockquote = (quote) => {
|
|
1421
|
+
const lines = splitSpanLines(renderBlockTokens(quote.tokens));
|
|
1422
|
+
const out = [];
|
|
1423
|
+
for (const [index, line] of lines.entries()) {
|
|
1424
|
+
if (index > 0) {
|
|
1425
|
+
out.push(plain("\n"));
|
|
1426
|
+
}
|
|
1427
|
+
out.push(plain(line.length > 0 ? "> " : ">"), ...line);
|
|
1428
|
+
}
|
|
1429
|
+
return out;
|
|
1430
|
+
};
|
|
1431
|
+
var renderList = (list) => {
|
|
1432
|
+
const out = [];
|
|
1433
|
+
for (const [index, item] of list.items.entries()) {
|
|
1434
|
+
const prefix = `${listMarker(list, index)}${checkboxPrefix(item)}`;
|
|
1435
|
+
const blocks = [];
|
|
1436
|
+
for (const token of item.tokens) {
|
|
1437
|
+
const rendered = renderBlockToken(asMarkedToken(token));
|
|
1438
|
+
if (spanText(rendered)) {
|
|
1439
|
+
blocks.push(rendered);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
const [first = [], ...rest] = splitSpanLines(joinSpans(blocks, "\n"));
|
|
1443
|
+
if (out.length > 0) {
|
|
1444
|
+
out.push(plain("\n"));
|
|
1445
|
+
}
|
|
1446
|
+
out.push(plain(prefix), ...first);
|
|
1447
|
+
for (const line of rest) {
|
|
1448
|
+
out.push(plain(`
|
|
1449
|
+
${NESTED_LIST_INDENT}`), ...line);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return out;
|
|
1453
|
+
};
|
|
1454
|
+
var renderTable = (table) => {
|
|
1455
|
+
const out = [];
|
|
1456
|
+
const pushRow = (cells, rowIndex) => {
|
|
1457
|
+
if (rowIndex > 0) {
|
|
1458
|
+
out.push(plain("\n"));
|
|
1459
|
+
}
|
|
1460
|
+
for (const [cellIndex, cell] of cells.entries()) {
|
|
1461
|
+
if (cellIndex > 0) {
|
|
1462
|
+
out.push(plain(TABLE_CELL_SEPARATOR));
|
|
1463
|
+
}
|
|
1464
|
+
out.push(...renderInlineTokens(cell.tokens));
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
pushRow(table.header, 0);
|
|
1468
|
+
for (const [index, row] of table.rows.entries()) {
|
|
1469
|
+
pushRow(row, index + 1);
|
|
1470
|
+
}
|
|
1471
|
+
return out;
|
|
1472
|
+
};
|
|
1473
|
+
var renderBlockToken = (token) => {
|
|
1474
|
+
switch (token.type) {
|
|
1475
|
+
// iMessage formatting has no heading sizes; bold is the conventional
|
|
1476
|
+
// stand-in (Telegram precedent).
|
|
1477
|
+
case "heading":
|
|
1478
|
+
return withStyle(renderInlineTokens(token.tokens), "bold");
|
|
1479
|
+
case "paragraph":
|
|
1480
|
+
return renderInlineTokens(token.tokens);
|
|
1481
|
+
case "code":
|
|
1482
|
+
return [plain(toMonospace(token.text))];
|
|
1483
|
+
case "blockquote":
|
|
1484
|
+
return renderBlockquote(token);
|
|
1485
|
+
case "list":
|
|
1486
|
+
return renderList(token);
|
|
1487
|
+
case "table":
|
|
1488
|
+
return renderTable(token);
|
|
1489
|
+
case "hr":
|
|
1490
|
+
return [plain(HR_LINE)];
|
|
1491
|
+
case "space":
|
|
1492
|
+
case "def":
|
|
1493
|
+
return [];
|
|
1494
|
+
default:
|
|
1495
|
+
return renderInlineToken(token);
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
var renderBlockTokens = (tokens) => {
|
|
1499
|
+
const blocks = [];
|
|
1500
|
+
for (const token of tokens) {
|
|
1501
|
+
const rendered = renderBlockToken(asMarkedToken(token));
|
|
1502
|
+
if (spanText(rendered)) {
|
|
1503
|
+
blocks.push(rendered);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
return joinSpans(blocks, BLOCK_SEPARATOR);
|
|
1507
|
+
};
|
|
1508
|
+
var trimSpans = (spans) => {
|
|
1509
|
+
const trimmed = [...spans];
|
|
1510
|
+
while (trimmed.length > 0) {
|
|
1511
|
+
const first = trimmed.at(0);
|
|
1512
|
+
const text2 = first?.text.replace(LEADING_WHITESPACE, "");
|
|
1513
|
+
if (first && text2) {
|
|
1514
|
+
trimmed[0] = { ...first, text: text2 };
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
trimmed.shift();
|
|
1518
|
+
}
|
|
1519
|
+
while (trimmed.length > 0) {
|
|
1520
|
+
const last = trimmed.at(-1);
|
|
1521
|
+
const text2 = last?.text.replace(TRAILING_WHITESPACE, "");
|
|
1522
|
+
if (last && text2) {
|
|
1523
|
+
trimmed[trimmed.length - 1] = { ...last, text: text2 };
|
|
1524
|
+
break;
|
|
1525
|
+
}
|
|
1526
|
+
trimmed.pop();
|
|
1527
|
+
}
|
|
1528
|
+
return trimmed;
|
|
1529
|
+
};
|
|
1530
|
+
var finalize = (spans) => {
|
|
1531
|
+
let text2 = "";
|
|
1532
|
+
let hasLinks = false;
|
|
1533
|
+
const open = /* @__PURE__ */ new Map();
|
|
1534
|
+
const ranges = [];
|
|
1535
|
+
const close = (style, end) => {
|
|
1536
|
+
const start = open.get(style);
|
|
1537
|
+
open.delete(style);
|
|
1538
|
+
if (start !== void 0 && end > start) {
|
|
1539
|
+
ranges.push({ type: style, start, length: end - start });
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
for (const span of spans) {
|
|
1543
|
+
if (!span.text) {
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
hasLinks ||= span.link === true;
|
|
1547
|
+
const offset = text2.length;
|
|
1548
|
+
for (const style of STYLE_ORDER) {
|
|
1549
|
+
if (span.styles.includes(style)) {
|
|
1550
|
+
if (!open.has(style)) {
|
|
1551
|
+
open.set(style, offset);
|
|
1552
|
+
}
|
|
1553
|
+
} else {
|
|
1554
|
+
close(style, offset);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
text2 += span.text;
|
|
1558
|
+
}
|
|
1559
|
+
for (const style of STYLE_ORDER) {
|
|
1560
|
+
close(style, text2.length);
|
|
1561
|
+
}
|
|
1562
|
+
ranges.sort(
|
|
1563
|
+
(a, b) => a.start - b.start || STYLE_ORDER.indexOf(a.type) - STYLE_ORDER.indexOf(b.type)
|
|
1564
|
+
);
|
|
1565
|
+
return { text: text2, formatting: ranges, hasLinks };
|
|
1566
|
+
};
|
|
1567
|
+
var markdownToIMessageText = (markdown) => finalize(trimSpans(renderBlockTokens(markdownLexer.lexer(markdown))));
|
|
1568
|
+
|
|
1254
1569
|
// src/providers/imessage/remote/send.ts
|
|
1255
1570
|
var GROUP_ITEM_ALLOWED = /* @__PURE__ */ new Set([
|
|
1256
1571
|
"text",
|
|
1572
|
+
"markdown",
|
|
1257
1573
|
"attachment",
|
|
1258
1574
|
"contact",
|
|
1259
1575
|
"voice"
|
|
1260
1576
|
]);
|
|
1577
|
+
var GROUP_TEXT_TYPES = /* @__PURE__ */ new Set([
|
|
1578
|
+
"text",
|
|
1579
|
+
"markdown"
|
|
1580
|
+
]);
|
|
1261
1581
|
var MAX_GROUP_TEXT_ITEMS = 1;
|
|
1262
1582
|
var outboundRecord = (spaceId, id, content, timestamp, extras) => ({
|
|
1263
1583
|
id,
|
|
@@ -1274,6 +1594,18 @@ var providerGroup = (items) => asGroup({ items });
|
|
|
1274
1594
|
var withReply = (options, replyTo) => replyTo ? { ...options, replyTo } : options;
|
|
1275
1595
|
var replyOptions = (replyTo) => replyTo ? { replyTo } : void 0;
|
|
1276
1596
|
var effectOption = (effect2) => effect2 ? { effect: effect2 } : {};
|
|
1597
|
+
var formattingOption = (formatting) => formatting.length > 0 ? { formatting } : {};
|
|
1598
|
+
var dataDetectionOption = (hasLinks) => hasLinks ? { enableDataDetection: true } : {};
|
|
1599
|
+
var renderMarkdown = (markdown) => {
|
|
1600
|
+
const rendered = markdownToIMessageText(markdown);
|
|
1601
|
+
if (!rendered.text) {
|
|
1602
|
+
throw unsupportedRemoteContent(
|
|
1603
|
+
"markdown",
|
|
1604
|
+
"renders to empty text \u2014 nothing to send"
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
return rendered;
|
|
1608
|
+
};
|
|
1277
1609
|
var replyTargetFromId = (messageId) => {
|
|
1278
1610
|
const childRef = parseChildId(messageId);
|
|
1279
1611
|
if (childRef) {
|
|
@@ -1331,6 +1663,22 @@ var sendContent = async (remote, spaceId, chat, content, replyTo, effect2) => {
|
|
|
1331
1663
|
);
|
|
1332
1664
|
return outboundMessage(spaceId, message, content);
|
|
1333
1665
|
}
|
|
1666
|
+
case "markdown": {
|
|
1667
|
+
const rendered = renderMarkdown(content.markdown);
|
|
1668
|
+
const message = await remote.messages.sendText(
|
|
1669
|
+
chat,
|
|
1670
|
+
rendered.text,
|
|
1671
|
+
withReply(
|
|
1672
|
+
{
|
|
1673
|
+
...effectOption(effect2),
|
|
1674
|
+
...formattingOption(rendered.formatting),
|
|
1675
|
+
...dataDetectionOption(rendered.hasLinks)
|
|
1676
|
+
},
|
|
1677
|
+
replyTo
|
|
1678
|
+
)
|
|
1679
|
+
);
|
|
1680
|
+
return outboundMessage(spaceId, message, content);
|
|
1681
|
+
}
|
|
1334
1682
|
case "richlink": {
|
|
1335
1683
|
const message = await remote.messages.sendText(
|
|
1336
1684
|
chat,
|
|
@@ -1395,7 +1743,7 @@ var validateGroupContent = (content) => {
|
|
|
1395
1743
|
`"${itemType}" items are not supported inside a group`
|
|
1396
1744
|
);
|
|
1397
1745
|
}
|
|
1398
|
-
if (itemType
|
|
1746
|
+
if (GROUP_TEXT_TYPES.has(itemType) && ++textCount > MAX_GROUP_TEXT_ITEMS) {
|
|
1399
1747
|
throw unsupportedRemoteContent(
|
|
1400
1748
|
"group",
|
|
1401
1749
|
`groups can contain at most ${MAX_GROUP_TEXT_ITEMS} text item`
|
|
@@ -1407,6 +1755,10 @@ var resolvePart = async (remote, content) => {
|
|
|
1407
1755
|
switch (content.type) {
|
|
1408
1756
|
case "text":
|
|
1409
1757
|
return { text: content.text };
|
|
1758
|
+
case "markdown": {
|
|
1759
|
+
const rendered = renderMarkdown(content.markdown);
|
|
1760
|
+
return { text: rendered.text, ...formattingOption(rendered.formatting) };
|
|
1761
|
+
}
|
|
1410
1762
|
case "attachment": {
|
|
1411
1763
|
const { guid, name } = await uploadAttachment(remote, content);
|
|
1412
1764
|
return { attachmentGuid: guid, attachmentName: name };
|
|
@@ -1469,20 +1821,29 @@ var editMessage = async (remote, spaceId, msgId, content) => {
|
|
|
1469
1821
|
childRef ? { partIndex: childRef.partIndex } : void 0
|
|
1470
1822
|
);
|
|
1471
1823
|
};
|
|
1824
|
+
var unsendMessage = async (remote, spaceId, msgId) => {
|
|
1825
|
+
const childRef = parseChildId(msgId);
|
|
1826
|
+
await remote.messages.unsend(
|
|
1827
|
+
toChatGuid(spaceId),
|
|
1828
|
+
toMessageGuid(childRef?.parentGuid ?? msgId),
|
|
1829
|
+
childRef ? { partIndex: childRef.partIndex } : void 0
|
|
1830
|
+
);
|
|
1831
|
+
};
|
|
1472
1832
|
|
|
1473
1833
|
// src/providers/imessage/remote/stream.ts
|
|
1474
1834
|
import {
|
|
1475
|
-
AuthenticationError,
|
|
1476
|
-
IMessageError,
|
|
1477
|
-
NotFoundError as NotFoundError3,
|
|
1478
1835
|
ValidationError
|
|
1479
1836
|
} from "@photon-ai/advanced-imessage";
|
|
1837
|
+
import { sanitizePhone } from "@photon-ai/otel";
|
|
1480
1838
|
|
|
1481
1839
|
// src/utils/resumable-stream.ts
|
|
1840
|
+
import { createLogger } from "@photon-ai/otel";
|
|
1482
1841
|
var CATCH_UP_PAGE_SIZE = 100;
|
|
1483
1842
|
var MAX_BUFFERED_LIVE_EVENTS = 1e3;
|
|
1484
1843
|
var RECONNECT_INITIAL_DELAY_MS = 500;
|
|
1485
1844
|
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
1845
|
+
var PERSISTENT_FAILURE_ERROR_THRESHOLD = 5;
|
|
1846
|
+
var log = createLogger("spectrum.stream");
|
|
1486
1847
|
var RetryableStreamError = class extends Error {
|
|
1487
1848
|
constructor(message) {
|
|
1488
1849
|
super(message);
|
|
@@ -1495,6 +1856,12 @@ var LiveBufferOverflowError = class extends RetryableStreamError {
|
|
|
1495
1856
|
this.name = "LiveBufferOverflowError";
|
|
1496
1857
|
}
|
|
1497
1858
|
};
|
|
1859
|
+
var CursorRejectedError = class extends Error {
|
|
1860
|
+
constructor(cause) {
|
|
1861
|
+
super("Server rejected resume cursor", { cause });
|
|
1862
|
+
this.name = "CursorRejectedError";
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1498
1865
|
var closeIterable = async (iterable) => {
|
|
1499
1866
|
if (!iterable) {
|
|
1500
1867
|
return;
|
|
@@ -1502,7 +1869,15 @@ var closeIterable = async (iterable) => {
|
|
|
1502
1869
|
await iterable.close?.();
|
|
1503
1870
|
};
|
|
1504
1871
|
var ignoreCleanupError = () => void 0;
|
|
1505
|
-
var jitterDelay = (delayMs) => Math.random() *
|
|
1872
|
+
var jitterDelay = (delayMs) => delayMs * (0.5 + Math.random() * 0.5);
|
|
1873
|
+
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
1874
|
+
async function* throwOnCursorRejection(source, isCursorRejected) {
|
|
1875
|
+
try {
|
|
1876
|
+
yield* source;
|
|
1877
|
+
} catch (error) {
|
|
1878
|
+
throw isCursorRejected(error) ? new CursorRejectedError(error) : error;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1506
1881
|
var numericCursor = (cursor) => {
|
|
1507
1882
|
if (!cursor) {
|
|
1508
1883
|
return;
|
|
@@ -1520,15 +1895,23 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1520
1895
|
const bufferLimit = options.bufferLimit ?? MAX_BUFFERED_LIVE_EVENTS;
|
|
1521
1896
|
const initialRetryDelayMs = options.initialRetryDelayMs ?? RECONNECT_INITIAL_DELAY_MS;
|
|
1522
1897
|
const maxRetryDelayMs = options.maxRetryDelayMs ?? RECONNECT_MAX_DELAY_MS;
|
|
1898
|
+
const jitter = options.jitter ?? jitterDelay;
|
|
1899
|
+
const label = options.label;
|
|
1523
1900
|
let activeLive;
|
|
1524
1901
|
let closed = false;
|
|
1902
|
+
let failedAttempts = 0;
|
|
1525
1903
|
let lastCursor;
|
|
1526
1904
|
let retryDelayMs = initialRetryDelayMs;
|
|
1527
1905
|
let sleepTimer;
|
|
1528
1906
|
let wakeSleep;
|
|
1529
1907
|
const deliveredSinceCursor = /* @__PURE__ */ new Set();
|
|
1530
|
-
const
|
|
1908
|
+
const noteRecovery = () => {
|
|
1531
1909
|
retryDelayMs = initialRetryDelayMs;
|
|
1910
|
+
if (failedAttempts === 0) {
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
log.info("stream recovered", { attempts: failedAttempts, label });
|
|
1914
|
+
failedAttempts = 0;
|
|
1532
1915
|
};
|
|
1533
1916
|
const advanceCursor = (cursor, clearDelivered) => {
|
|
1534
1917
|
if (!cursor || cursor === lastCursor || isCursorRegression(cursor, lastCursor)) {
|
|
@@ -1549,17 +1932,17 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1549
1932
|
advanceCursor(item.cursor, clearOnCursorAdvance);
|
|
1550
1933
|
deliveredSinceCursor.add(item.id);
|
|
1551
1934
|
if (resetRetry) {
|
|
1552
|
-
|
|
1935
|
+
noteRecovery();
|
|
1553
1936
|
}
|
|
1554
1937
|
};
|
|
1555
|
-
const
|
|
1938
|
+
const isCursorRejected = (error) => options.isCursorRejectedError?.(error) === true;
|
|
1556
1939
|
const sleep2 = async (delayMs) => {
|
|
1557
1940
|
if (delayMs <= 0 || closed) {
|
|
1558
1941
|
return;
|
|
1559
1942
|
}
|
|
1560
1943
|
await new Promise((resolve) => {
|
|
1561
1944
|
wakeSleep = resolve;
|
|
1562
|
-
sleepTimer = setTimeout(resolve,
|
|
1945
|
+
sleepTimer = setTimeout(resolve, jitter(delayMs));
|
|
1563
1946
|
});
|
|
1564
1947
|
sleepTimer = void 0;
|
|
1565
1948
|
wakeSleep = void 0;
|
|
@@ -1577,6 +1960,37 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1577
1960
|
retryDelayMs = Math.min(retryDelayMs * 2, maxRetryDelayMs);
|
|
1578
1961
|
return delay;
|
|
1579
1962
|
};
|
|
1963
|
+
const handleFailure = (error) => {
|
|
1964
|
+
failedAttempts += 1;
|
|
1965
|
+
const delayMs = nextRetryDelay();
|
|
1966
|
+
if (error instanceof CursorRejectedError) {
|
|
1967
|
+
lastCursor = void 0;
|
|
1968
|
+
deliveredSinceCursor.clear();
|
|
1969
|
+
log.warn(
|
|
1970
|
+
"resume cursor rejected; accepting event gap and resuming live",
|
|
1971
|
+
{
|
|
1972
|
+
attempt: failedAttempts,
|
|
1973
|
+
delayMs,
|
|
1974
|
+
error: errorMessage(error.cause),
|
|
1975
|
+
label
|
|
1976
|
+
}
|
|
1977
|
+
);
|
|
1978
|
+
return delayMs;
|
|
1979
|
+
}
|
|
1980
|
+
const attrs = {
|
|
1981
|
+
attempt: failedAttempts,
|
|
1982
|
+
delayMs,
|
|
1983
|
+
error: errorMessage(error),
|
|
1984
|
+
hasCursor: lastCursor !== void 0,
|
|
1985
|
+
label
|
|
1986
|
+
};
|
|
1987
|
+
if (failedAttempts >= PERSISTENT_FAILURE_ERROR_THRESHOLD) {
|
|
1988
|
+
log.error("stream persistently failing; still retrying", attrs, error);
|
|
1989
|
+
return delayMs;
|
|
1990
|
+
}
|
|
1991
|
+
log.warn("stream interrupted; reconnecting", attrs);
|
|
1992
|
+
return delayMs;
|
|
1993
|
+
};
|
|
1580
1994
|
const consumeLive = async () => {
|
|
1581
1995
|
const live = options.subscribeLive(lastCursor);
|
|
1582
1996
|
activeLive = live;
|
|
@@ -1625,15 +2039,17 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1625
2039
|
};
|
|
1626
2040
|
};
|
|
1627
2041
|
const replayMissed = async (cursor, getLiveError) => {
|
|
1628
|
-
|
|
1629
|
-
limit: catchUpPageSize
|
|
1630
|
-
|
|
2042
|
+
const missed = throwOnCursorRejection(
|
|
2043
|
+
options.fetchMissed(cursor, { limit: catchUpPageSize }),
|
|
2044
|
+
isCursorRejected
|
|
2045
|
+
);
|
|
2046
|
+
for await (const event of missed) {
|
|
1631
2047
|
throwLiveError(getLiveError());
|
|
1632
2048
|
await deliverItem(await options.processMissed(event), false, false);
|
|
1633
2049
|
}
|
|
1634
2050
|
throwLiveError(getLiveError());
|
|
1635
2051
|
};
|
|
1636
|
-
const flushLiveBuffer = async (liveBuffer, getLiveError) => {
|
|
2052
|
+
const flushLiveBuffer = async (liveBuffer, getLiveError, stopBuffering) => {
|
|
1637
2053
|
let index = 0;
|
|
1638
2054
|
let lastFlushedId;
|
|
1639
2055
|
while (index < liveBuffer.length) {
|
|
@@ -1649,7 +2065,8 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1649
2065
|
}
|
|
1650
2066
|
liveBuffer.length = 0;
|
|
1651
2067
|
throwLiveError(getLiveError());
|
|
1652
|
-
|
|
2068
|
+
compactDeliveredIds(lastFlushedId);
|
|
2069
|
+
stopBuffering();
|
|
1653
2070
|
};
|
|
1654
2071
|
const compactDeliveredIds = (lastId) => {
|
|
1655
2072
|
if (!lastId) {
|
|
@@ -1666,13 +2083,10 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1666
2083
|
const livePump = startLivePump(live, () => buffering, liveBuffer);
|
|
1667
2084
|
try {
|
|
1668
2085
|
await replayMissed(cursor, livePump.getError);
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
);
|
|
1673
|
-
compactDeliveredIds(lastFlushedId);
|
|
1674
|
-
buffering = false;
|
|
1675
|
-
resetRetryDelay();
|
|
2086
|
+
await flushLiveBuffer(liveBuffer, livePump.getError, () => {
|
|
2087
|
+
buffering = false;
|
|
2088
|
+
});
|
|
2089
|
+
noteRecovery();
|
|
1676
2090
|
await livePump.pump;
|
|
1677
2091
|
throwLiveError(livePump.getError());
|
|
1678
2092
|
} finally {
|
|
@@ -1693,21 +2107,18 @@ var resumableOrderedStream = (options) => stream((emit, end) => {
|
|
|
1693
2107
|
await consumeLive();
|
|
1694
2108
|
}
|
|
1695
2109
|
} catch (error) {
|
|
1696
|
-
await closeIterable(activeLive);
|
|
2110
|
+
await closeIterable(activeLive).catch(ignoreCleanupError);
|
|
1697
2111
|
activeLive = void 0;
|
|
1698
2112
|
if (closed) {
|
|
1699
2113
|
break;
|
|
1700
2114
|
}
|
|
1701
|
-
|
|
1702
|
-
end(error);
|
|
1703
|
-
return;
|
|
1704
|
-
}
|
|
1705
|
-
await sleep2(nextRetryDelay());
|
|
2115
|
+
await sleep2(handleFailure(error));
|
|
1706
2116
|
}
|
|
1707
2117
|
}
|
|
1708
2118
|
end();
|
|
1709
2119
|
};
|
|
1710
2120
|
const pump = run().catch((error) => {
|
|
2121
|
+
log.error("resumable stream loop crashed", { label }, error);
|
|
1711
2122
|
if (!closed) {
|
|
1712
2123
|
end(error);
|
|
1713
2124
|
}
|
|
@@ -1840,7 +2251,7 @@ var buildPollOptionMessage = (input) => {
|
|
|
1840
2251
|
sender: { id: input.senderAddress },
|
|
1841
2252
|
space: {
|
|
1842
2253
|
id: input.chatGuid,
|
|
1843
|
-
type: input.chatGuid
|
|
2254
|
+
type: chatTypeFromGuid(input.chatGuid),
|
|
1844
2255
|
phone: input.phone
|
|
1845
2256
|
},
|
|
1846
2257
|
timestamp: input.event.occurredAt,
|
|
@@ -1896,15 +2307,8 @@ var toPollDeltaMessages = async (client, pollCache, event, phone) => {
|
|
|
1896
2307
|
};
|
|
1897
2308
|
|
|
1898
2309
|
// src/providers/imessage/remote/stream.ts
|
|
1899
|
-
var
|
|
1900
|
-
|
|
1901
|
-
return false;
|
|
1902
|
-
}
|
|
1903
|
-
if (error instanceof IMessageError) {
|
|
1904
|
-
return true;
|
|
1905
|
-
}
|
|
1906
|
-
return false;
|
|
1907
|
-
};
|
|
2310
|
+
var isCursorRejectedIMessageError = (error) => error instanceof ValidationError;
|
|
2311
|
+
var streamLabel = (kind, phone) => `imessage.${kind}:${phone === SHARED_PHONE ? phone : sanitizePhone(phone)}`;
|
|
1908
2312
|
var isEventFromCurrentAccount = (event, phone) => event.isFromMe || phone !== SHARED_PHONE && event.actor?.address !== void 0 && event.actor.address === phone;
|
|
1909
2313
|
var toMessageItem = async (client, event, phone, cursor, onInbound) => {
|
|
1910
2314
|
if (event.type === "message.received") {
|
|
@@ -2007,7 +2411,8 @@ var withClose = (source, cursor) => Object.assign(afterCursor(source, cursor), {
|
|
|
2007
2411
|
});
|
|
2008
2412
|
var messageStream = (client, phone, onInbound) => resumableOrderedStream({
|
|
2009
2413
|
fetchMissed: (cursor) => catchUpEvents(client, cursor, isMessageEvent),
|
|
2010
|
-
|
|
2414
|
+
isCursorRejectedError: isCursorRejectedIMessageError,
|
|
2415
|
+
label: streamLabel("messages", phone),
|
|
2011
2416
|
processLive: (event) => toMessageItem(client, event, phone, String(event.sequence), onInbound),
|
|
2012
2417
|
processMissed: (event) => event.type === "catchup.complete" ? Promise.resolve(toCatchUpCompleteItem(event)) : toMessageItem(
|
|
2013
2418
|
client,
|
|
@@ -2020,7 +2425,8 @@ var messageStream = (client, phone, onInbound) => resumableOrderedStream({
|
|
|
2020
2425
|
});
|
|
2021
2426
|
var pollStream = (client, pollCache, phone) => resumableOrderedStream({
|
|
2022
2427
|
fetchMissed: (cursor) => catchUpEvents(client, cursor, isPollEvent),
|
|
2023
|
-
|
|
2428
|
+
isCursorRejectedError: isCursorRejectedIMessageError,
|
|
2429
|
+
label: streamLabel("polls", phone),
|
|
2024
2430
|
processLive: (event) => toPollItem(client, pollCache, event, phone, String(event.sequence)),
|
|
2025
2431
|
processMissed: (event) => event.type === "catchup.complete" ? Promise.resolve(toCatchUpCompleteItem(event)) : toPollItem(client, pollCache, event, phone, String(event.sequence)),
|
|
2026
2432
|
subscribeLive: (cursor) => withClose(client.polls.subscribeEvents(), cursor)
|
|
@@ -2050,6 +2456,12 @@ var INITIAL_THROTTLE_MS = 1e3;
|
|
|
2050
2456
|
var BACKOFF_FACTOR = 2;
|
|
2051
2457
|
var MAX_EDITS = 5;
|
|
2052
2458
|
var sendStreamText = async (remote, spaceId, content) => {
|
|
2459
|
+
if (content.format === "markdown") {
|
|
2460
|
+
throw unsupportedRemoteContent(
|
|
2461
|
+
"streamText",
|
|
2462
|
+
"markdown-formatted streams have no native iMessage delivery"
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2053
2465
|
const chat = toChatGuid(spaceId);
|
|
2054
2466
|
let sent;
|
|
2055
2467
|
let full = "";
|
|
@@ -2121,9 +2533,9 @@ var send4 = async (remote, spaceId, content) => send3(remote, spaceId, content);
|
|
|
2121
2533
|
var sendStreamText2 = async (remote, spaceId, content) => sendStreamText(remote, spaceId, content);
|
|
2122
2534
|
var replyToMessage2 = async (remote, spaceId, msgId, content) => replyToMessage(remote, spaceId, msgId, content);
|
|
2123
2535
|
var editMessage2 = async (remote, spaceId, msgId, content) => editMessage(remote, spaceId, msgId, content);
|
|
2124
|
-
var reactToMessage2 = async (remote, spaceId, target, reaction) =>
|
|
2125
|
-
|
|
2126
|
-
|
|
2536
|
+
var reactToMessage2 = async (remote, spaceId, target, reaction) => reactToMessage(remote, spaceId, target, reaction);
|
|
2537
|
+
var unsendMessage2 = async (remote, spaceId, msgId) => unsendMessage(remote, spaceId, msgId);
|
|
2538
|
+
var unsendReaction2 = async (remote, spaceId, target, reaction) => unsendReaction(remote, spaceId, target, reaction);
|
|
2127
2539
|
var getMessage4 = async (remote, spaceId, msgId, phone) => getMessage3(remote, spaceId, msgId, phone);
|
|
2128
2540
|
|
|
2129
2541
|
// src/providers/imessage/remote/client.ts
|
|
@@ -2176,6 +2588,30 @@ var handleEdit = async (client, space, content) => {
|
|
|
2176
2588
|
const remote = clientForPhone(client, space.phone);
|
|
2177
2589
|
await editMessage2(remote, space.id, content.target.id, content.content);
|
|
2178
2590
|
};
|
|
2591
|
+
var handleUnsend = async (client, space, content) => {
|
|
2592
|
+
if (isLocal(client)) {
|
|
2593
|
+
throw UnsupportedError.action("unsend", "iMessage (local mode)");
|
|
2594
|
+
}
|
|
2595
|
+
if (isPollContent(content.target.content)) {
|
|
2596
|
+
throw UnsupportedError.action(
|
|
2597
|
+
"unsend",
|
|
2598
|
+
"iMessage",
|
|
2599
|
+
"iMessage polls cannot be unsent"
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
const remote = clientForPhone(client, space.phone);
|
|
2603
|
+
const targetContent = content.target.content;
|
|
2604
|
+
if (targetContent.type === "reaction") {
|
|
2605
|
+
await unsendReaction2(
|
|
2606
|
+
remote,
|
|
2607
|
+
space.id,
|
|
2608
|
+
targetContent.target,
|
|
2609
|
+
targetContent.emoji
|
|
2610
|
+
);
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
await unsendMessage2(remote, space.id, content.target.id);
|
|
2614
|
+
};
|
|
2179
2615
|
var handleStreamText = async (client, space, content) => {
|
|
2180
2616
|
if (isLocal(client)) {
|
|
2181
2617
|
throw UnsupportedError.action(
|
|
@@ -2316,10 +2752,10 @@ var imessage = definePlatform("iMessage", {
|
|
|
2316
2752
|
space: {
|
|
2317
2753
|
schema: spaceSchema,
|
|
2318
2754
|
params: spaceParamsSchema,
|
|
2319
|
-
|
|
2755
|
+
create: async ({ input, client }) => {
|
|
2320
2756
|
if (isLocal(client)) {
|
|
2321
2757
|
throw UnsupportedError.action(
|
|
2322
|
-
"
|
|
2758
|
+
"space.create",
|
|
2323
2759
|
"iMessage (local mode)",
|
|
2324
2760
|
"local mode only supports replying to existing messages"
|
|
2325
2761
|
);
|
|
@@ -2330,18 +2766,52 @@ var imessage = definePlatform("iMessage", {
|
|
|
2330
2766
|
if (client.length === 0) {
|
|
2331
2767
|
throw new Error("No iMessage clients configured");
|
|
2332
2768
|
}
|
|
2333
|
-
const phone = isSharedMode(client) ? SHARED_PHONE : input.params?.phone ?? randomPhone(client);
|
|
2334
|
-
const remote = clientForPhone(client, phone);
|
|
2335
2769
|
const addresses = input.users.map((u) => u.id);
|
|
2336
|
-
if (
|
|
2770
|
+
if (isSharedMode(client)) {
|
|
2771
|
+
if (addresses.length > 1) {
|
|
2772
|
+
throw UnsupportedError.action(
|
|
2773
|
+
"space.create",
|
|
2774
|
+
"iMessage (shared mode)",
|
|
2775
|
+
"shared mode cannot create group chats \u2014 use a dedicated number, or space.get(chatGuid) for an existing group"
|
|
2776
|
+
);
|
|
2777
|
+
}
|
|
2337
2778
|
return {
|
|
2338
2779
|
id: dmChatGuid(addresses[0] ?? ""),
|
|
2339
2780
|
type: "dm",
|
|
2340
|
-
phone
|
|
2781
|
+
phone: SHARED_PHONE
|
|
2341
2782
|
};
|
|
2342
2783
|
}
|
|
2784
|
+
const phone = input.params?.phone ?? randomPhone(client);
|
|
2785
|
+
const remote = clientForPhone(client, phone);
|
|
2343
2786
|
const { chat } = await remote.chats.create(addresses);
|
|
2344
|
-
return {
|
|
2787
|
+
return {
|
|
2788
|
+
id: chat.guid,
|
|
2789
|
+
type: chat.isGroup ? "group" : "dm",
|
|
2790
|
+
phone
|
|
2791
|
+
};
|
|
2792
|
+
},
|
|
2793
|
+
get: async ({ input, client }) => {
|
|
2794
|
+
if (isLocal(client)) {
|
|
2795
|
+
throw UnsupportedError.action(
|
|
2796
|
+
"space.get",
|
|
2797
|
+
"iMessage (local mode)",
|
|
2798
|
+
"local mode only supports replying to existing messages"
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
2801
|
+
if (client.length === 0) {
|
|
2802
|
+
throw new Error("No iMessage clients configured");
|
|
2803
|
+
}
|
|
2804
|
+
const phone = isSharedMode(client) ? SHARED_PHONE : input.params?.phone ?? (client.length === 1 ? client[0]?.phone : void 0);
|
|
2805
|
+
if (!phone) {
|
|
2806
|
+
throw new Error(
|
|
2807
|
+
`iMessage space.get requires params.phone when multiple clients are configured. Available: ${availablePhones(client).join(", ")}`
|
|
2808
|
+
);
|
|
2809
|
+
}
|
|
2810
|
+
return {
|
|
2811
|
+
id: input.id,
|
|
2812
|
+
type: chatTypeFromGuid(input.id),
|
|
2813
|
+
phone
|
|
2814
|
+
};
|
|
2345
2815
|
},
|
|
2346
2816
|
actions: {
|
|
2347
2817
|
// Sugar: `space.background(input, opts?)` →
|
|
@@ -2401,13 +2871,12 @@ var imessage = definePlatform("iMessage", {
|
|
|
2401
2871
|
);
|
|
2402
2872
|
}
|
|
2403
2873
|
const remote2 = clientForPhone(client, space.phone);
|
|
2404
|
-
await reactToMessage2(
|
|
2874
|
+
return await reactToMessage2(
|
|
2405
2875
|
remote2,
|
|
2406
2876
|
space.id,
|
|
2407
2877
|
content.target,
|
|
2408
2878
|
content.emoji
|
|
2409
2879
|
);
|
|
2410
|
-
return;
|
|
2411
2880
|
}
|
|
2412
2881
|
if (content.type === "typing") {
|
|
2413
2882
|
await handleTyping(client, space, content.state);
|
|
@@ -2417,6 +2886,10 @@ var imessage = definePlatform("iMessage", {
|
|
|
2417
2886
|
await handleEdit(client, space, content);
|
|
2418
2887
|
return;
|
|
2419
2888
|
}
|
|
2889
|
+
if (content.type === "unsend") {
|
|
2890
|
+
await handleUnsend(client, space, content);
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2420
2893
|
if (content.type === "streamText") {
|
|
2421
2894
|
return await handleStreamText(client, space, content);
|
|
2422
2895
|
}
|