opencode-router 0.11.113 → 0.11.115
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/README.md +1 -0
- package/dist/bridge.js +229 -14
- package/dist/config.js +14 -0
- package/dist/health.js +8 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ Important for direct sends and bindings:
|
|
|
63
63
|
- Telegram targets must use numeric `chat_id` values.
|
|
64
64
|
- `@username` values are not valid direct `peerId` targets for router sends.
|
|
65
65
|
- If a user has not started a chat with the bot yet, Telegram may return `chat not found`.
|
|
66
|
+
- Private Telegram identities can require first-chat pairing with `/pair <code>` before commands are accepted.
|
|
66
67
|
|
|
67
68
|
## Slack (Socket Mode)
|
|
68
69
|
|
package/dist/bridge.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { readFile, stat } from "node:fs/promises";
|
|
3
4
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
5
|
import { readConfigFile, writeConfigFile } from "./config.js";
|
|
@@ -90,6 +91,30 @@ function invalidTelegramPeerIdError() {
|
|
|
90
91
|
error.status = 400;
|
|
91
92
|
return error;
|
|
92
93
|
}
|
|
94
|
+
const PAIRING_CODE_HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
95
|
+
function normalizeTelegramAccess(value) {
|
|
96
|
+
const raw = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
97
|
+
return raw === "private" ? "private" : "public";
|
|
98
|
+
}
|
|
99
|
+
function normalizePairingCodeHash(value) {
|
|
100
|
+
const raw = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
101
|
+
if (!PAIRING_CODE_HASH_PATTERN.test(raw))
|
|
102
|
+
return "";
|
|
103
|
+
return raw;
|
|
104
|
+
}
|
|
105
|
+
function normalizePairingCodeValue(value) {
|
|
106
|
+
return value.trim().toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
107
|
+
}
|
|
108
|
+
function hashPairingCode(value) {
|
|
109
|
+
return createHash("sha256").update(normalizePairingCodeValue(value)).digest("hex");
|
|
110
|
+
}
|
|
111
|
+
function extractPairingCodeFromCommand(text) {
|
|
112
|
+
const trimmed = text.trim();
|
|
113
|
+
const match = trimmed.match(/^\/pair(?:@[A-Za-z0-9_]+)?\s+(.+)$/i);
|
|
114
|
+
if (!match?.[1])
|
|
115
|
+
return "";
|
|
116
|
+
return normalizePairingCodeValue(match[1]);
|
|
117
|
+
}
|
|
93
118
|
function normalizeIdentityId(value) {
|
|
94
119
|
const trimmed = (value ?? "").trim();
|
|
95
120
|
if (!trimmed)
|
|
@@ -194,6 +219,22 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
194
219
|
const app = config.slackApps.find((entry) => entry.id === id);
|
|
195
220
|
return typeof app?.directory === "string" ? String(app.directory).trim() : "";
|
|
196
221
|
};
|
|
222
|
+
const resolveTelegramIdentityAccess = (identityId) => {
|
|
223
|
+
const id = identityId.trim();
|
|
224
|
+
if (!id) {
|
|
225
|
+
return { access: "public", pairingCodeHash: "" };
|
|
226
|
+
}
|
|
227
|
+
const bot = config.telegramBots.find((entry) => entry.id === id);
|
|
228
|
+
if (!bot) {
|
|
229
|
+
return { access: "public", pairingCodeHash: "" };
|
|
230
|
+
}
|
|
231
|
+
const access = normalizeTelegramAccess(bot.access);
|
|
232
|
+
const pairingCodeHash = normalizePairingCodeHash(bot.pairingCodeHash);
|
|
233
|
+
if (access !== "private") {
|
|
234
|
+
return { access: "public", pairingCodeHash: "" };
|
|
235
|
+
}
|
|
236
|
+
return { access: "private", pairingCodeHash };
|
|
237
|
+
};
|
|
197
238
|
const listIdentityConfigs = (channel) => {
|
|
198
239
|
if (channel === "telegram") {
|
|
199
240
|
return config.telegramBots.map((bot) => ({ id: bot.id, directory: (bot.directory ?? "").trim() }));
|
|
@@ -465,6 +506,8 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
465
506
|
id: bot.id,
|
|
466
507
|
enabled: bot.enabled !== false,
|
|
467
508
|
running: adapters.has(adapterKey("telegram", bot.id)),
|
|
509
|
+
access: normalizeTelegramAccess(bot.access),
|
|
510
|
+
pairingRequired: normalizeTelegramAccess(bot.access) === "private",
|
|
468
511
|
})),
|
|
469
512
|
};
|
|
470
513
|
},
|
|
@@ -477,6 +520,8 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
477
520
|
throw new Error("identity id 'env' is reserved");
|
|
478
521
|
const enabled = input.enabled !== false;
|
|
479
522
|
const directoryInput = typeof input.directory === "string" ? input.directory.trim() : "";
|
|
523
|
+
const requestedAccess = typeof input.access === "string" && input.access.trim() ? normalizeTelegramAccess(input.access) : undefined;
|
|
524
|
+
const requestedPairingCodeHash = normalizePairingCodeHash(input.pairingCodeHash);
|
|
480
525
|
// Persist to config file.
|
|
481
526
|
const { config: current } = readConfigFile(config.configPath);
|
|
482
527
|
const telegram = current.channels?.telegram;
|
|
@@ -495,10 +540,36 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
495
540
|
found = true;
|
|
496
541
|
const existingDirectory = typeof record.directory === "string" ? record.directory.trim() : "";
|
|
497
542
|
const directory = directoryInput || existingDirectory;
|
|
498
|
-
|
|
543
|
+
const existingAccess = normalizeTelegramAccess(record.access);
|
|
544
|
+
const existingPairingCodeHash = normalizePairingCodeHash(record.pairingCodeHash);
|
|
545
|
+
const access = requestedAccess ?? existingAccess;
|
|
546
|
+
const pairingCodeHash = access === "private" ? requestedPairingCodeHash || existingPairingCodeHash : "";
|
|
547
|
+
if (access === "private" && !pairingCodeHash) {
|
|
548
|
+
throw new Error("pairingCodeHash is required when Telegram access is private");
|
|
549
|
+
}
|
|
550
|
+
nextBots.push({
|
|
551
|
+
id,
|
|
552
|
+
token,
|
|
553
|
+
enabled,
|
|
554
|
+
...(directory ? { directory } : {}),
|
|
555
|
+
access,
|
|
556
|
+
...(access === "private" ? { pairingCodeHash } : {}),
|
|
557
|
+
});
|
|
499
558
|
}
|
|
500
559
|
if (!found) {
|
|
501
|
-
|
|
560
|
+
const access = requestedAccess ?? "public";
|
|
561
|
+
const pairingCodeHash = access === "private" ? requestedPairingCodeHash : "";
|
|
562
|
+
if (access === "private" && !pairingCodeHash) {
|
|
563
|
+
throw new Error("pairingCodeHash is required when Telegram access is private");
|
|
564
|
+
}
|
|
565
|
+
nextBots.push({
|
|
566
|
+
id,
|
|
567
|
+
token,
|
|
568
|
+
enabled,
|
|
569
|
+
...(directoryInput ? { directory: directoryInput } : {}),
|
|
570
|
+
access,
|
|
571
|
+
...(access === "private" ? { pairingCodeHash } : {}),
|
|
572
|
+
});
|
|
502
573
|
}
|
|
503
574
|
const next = {
|
|
504
575
|
...current,
|
|
@@ -516,13 +587,41 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
516
587
|
config.configFile = next;
|
|
517
588
|
// Update runtime identity list.
|
|
518
589
|
const existingIdx = config.telegramBots.findIndex((bot) => bot.id === id);
|
|
590
|
+
let runtimeAccess = requestedAccess ?? "public";
|
|
591
|
+
let runtimePairingCodeHash = requestedPairingCodeHash;
|
|
519
592
|
if (existingIdx >= 0) {
|
|
520
593
|
const prev = config.telegramBots[existingIdx];
|
|
521
594
|
const nextDirectory = directoryInput || prev?.directory || undefined;
|
|
522
|
-
|
|
595
|
+
const prevAccess = normalizeTelegramAccess(prev?.access);
|
|
596
|
+
const prevPairingCodeHash = normalizePairingCodeHash(prev?.pairingCodeHash);
|
|
597
|
+
runtimeAccess = requestedAccess ?? prevAccess;
|
|
598
|
+
runtimePairingCodeHash = runtimeAccess === "private" ? requestedPairingCodeHash || prevPairingCodeHash : "";
|
|
599
|
+
if (runtimeAccess === "private" && !runtimePairingCodeHash) {
|
|
600
|
+
throw new Error("pairingCodeHash is required when Telegram access is private");
|
|
601
|
+
}
|
|
602
|
+
config.telegramBots[existingIdx] = {
|
|
603
|
+
id,
|
|
604
|
+
token,
|
|
605
|
+
enabled,
|
|
606
|
+
...(nextDirectory ? { directory: String(nextDirectory).trim() } : {}),
|
|
607
|
+
access: runtimeAccess,
|
|
608
|
+
...(runtimeAccess === "private" ? { pairingCodeHash: runtimePairingCodeHash } : {}),
|
|
609
|
+
};
|
|
523
610
|
}
|
|
524
611
|
else {
|
|
525
|
-
|
|
612
|
+
runtimeAccess = requestedAccess ?? "public";
|
|
613
|
+
runtimePairingCodeHash = runtimeAccess === "private" ? requestedPairingCodeHash : "";
|
|
614
|
+
if (runtimeAccess === "private" && !runtimePairingCodeHash) {
|
|
615
|
+
throw new Error("pairingCodeHash is required when Telegram access is private");
|
|
616
|
+
}
|
|
617
|
+
config.telegramBots.push({
|
|
618
|
+
id,
|
|
619
|
+
token,
|
|
620
|
+
enabled,
|
|
621
|
+
...(directoryInput ? { directory: directoryInput } : {}),
|
|
622
|
+
access: runtimeAccess,
|
|
623
|
+
...(runtimeAccess === "private" ? { pairingCodeHash: runtimePairingCodeHash } : {}),
|
|
624
|
+
});
|
|
526
625
|
}
|
|
527
626
|
// Start/stop adapter.
|
|
528
627
|
const key = adapterKey("telegram", id);
|
|
@@ -537,7 +636,13 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
537
636
|
}
|
|
538
637
|
adapters.delete(key);
|
|
539
638
|
}
|
|
540
|
-
return {
|
|
639
|
+
return {
|
|
640
|
+
id,
|
|
641
|
+
enabled: false,
|
|
642
|
+
access: runtimeAccess,
|
|
643
|
+
pairingRequired: runtimeAccess === "private",
|
|
644
|
+
applied: true,
|
|
645
|
+
};
|
|
541
646
|
}
|
|
542
647
|
if (existing) {
|
|
543
648
|
try {
|
|
@@ -548,7 +653,16 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
548
653
|
}
|
|
549
654
|
adapters.delete(key);
|
|
550
655
|
}
|
|
551
|
-
const base = createTelegramAdapter({
|
|
656
|
+
const base = createTelegramAdapter({
|
|
657
|
+
id,
|
|
658
|
+
token,
|
|
659
|
+
enabled,
|
|
660
|
+
...(directoryInput ? { directory: directoryInput } : {}),
|
|
661
|
+
access: runtimeAccess,
|
|
662
|
+
...(runtimeAccess === "private" && runtimePairingCodeHash
|
|
663
|
+
? { pairingCodeHash: runtimePairingCodeHash }
|
|
664
|
+
: {}),
|
|
665
|
+
}, config, logger, handleInbound);
|
|
552
666
|
const adapter = { ...base, key };
|
|
553
667
|
adapters.set(key, adapter);
|
|
554
668
|
const startResult = await startAdapterBounded(adapter, {
|
|
@@ -559,12 +673,32 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
559
673
|
},
|
|
560
674
|
});
|
|
561
675
|
if (startResult.status === "timeout") {
|
|
562
|
-
return {
|
|
676
|
+
return {
|
|
677
|
+
id,
|
|
678
|
+
enabled: true,
|
|
679
|
+
access: runtimeAccess,
|
|
680
|
+
pairingRequired: runtimeAccess === "private",
|
|
681
|
+
applied: false,
|
|
682
|
+
starting: true,
|
|
683
|
+
};
|
|
563
684
|
}
|
|
564
685
|
if (startResult.status === "error") {
|
|
565
|
-
return {
|
|
686
|
+
return {
|
|
687
|
+
id,
|
|
688
|
+
enabled: true,
|
|
689
|
+
access: runtimeAccess,
|
|
690
|
+
pairingRequired: runtimeAccess === "private",
|
|
691
|
+
applied: false,
|
|
692
|
+
error: String(startResult.error),
|
|
693
|
+
};
|
|
566
694
|
}
|
|
567
|
-
return {
|
|
695
|
+
return {
|
|
696
|
+
id,
|
|
697
|
+
enabled: true,
|
|
698
|
+
access: runtimeAccess,
|
|
699
|
+
pairingRequired: runtimeAccess === "private",
|
|
700
|
+
applied: true,
|
|
701
|
+
};
|
|
568
702
|
},
|
|
569
703
|
deleteTelegramIdentity: async (rawId) => {
|
|
570
704
|
const id = normalizeIdentityId(rawId);
|
|
@@ -1135,6 +1269,50 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
1135
1269
|
await adapter.sendText(peerId, chunk);
|
|
1136
1270
|
}
|
|
1137
1271
|
}
|
|
1272
|
+
async function handleTelegramPairingGate(input) {
|
|
1273
|
+
const access = resolveTelegramIdentityAccess(input.identityId);
|
|
1274
|
+
if (access.access !== "private") {
|
|
1275
|
+
return "continue";
|
|
1276
|
+
}
|
|
1277
|
+
const hasKnownBinding = Boolean(input.bindingDirectory?.trim() || input.sessionDirectory?.trim());
|
|
1278
|
+
if (hasKnownBinding) {
|
|
1279
|
+
return "continue";
|
|
1280
|
+
}
|
|
1281
|
+
const pairingCode = extractPairingCodeFromCommand(input.text);
|
|
1282
|
+
if (!pairingCode) {
|
|
1283
|
+
await sendText("telegram", input.identityId, input.peerId, "This Telegram bot is private. Ask your OpenWork host for the pairing code, then send /pair <code>.", { kind: "system" });
|
|
1284
|
+
return "handled";
|
|
1285
|
+
}
|
|
1286
|
+
if (!access.pairingCodeHash) {
|
|
1287
|
+
await sendText("telegram", input.identityId, input.peerId, "This Telegram bot is private but missing a pairing code. Ask your OpenWork host to reconnect it.", { kind: "system" });
|
|
1288
|
+
return "handled";
|
|
1289
|
+
}
|
|
1290
|
+
if (hashPairingCode(pairingCode) !== access.pairingCodeHash) {
|
|
1291
|
+
await sendText("telegram", input.identityId, input.peerId, "Invalid pairing code. Try again with /pair <code>.", {
|
|
1292
|
+
kind: "system",
|
|
1293
|
+
});
|
|
1294
|
+
return "handled";
|
|
1295
|
+
}
|
|
1296
|
+
const identityDirectory = resolveIdentityDirectory("telegram", input.identityId);
|
|
1297
|
+
const boundDirectoryCandidate = identityDirectory || defaultDirectory;
|
|
1298
|
+
const hasExplicitBinding = Boolean(identityDirectory);
|
|
1299
|
+
if (!boundDirectoryCandidate || (!hasExplicitBinding && isDangerousRootDirectory(boundDirectoryCandidate))) {
|
|
1300
|
+
await sendText("telegram", input.identityId, input.peerId, "No workspace directory configured for this identity. Ask your OpenWork host to set it, or reply with /dir <path>.", { kind: "system" });
|
|
1301
|
+
return "handled";
|
|
1302
|
+
}
|
|
1303
|
+
const scopedBound = resolveScopedDirectory(boundDirectoryCandidate);
|
|
1304
|
+
if (!scopedBound.ok) {
|
|
1305
|
+
await sendText("telegram", input.identityId, input.peerId, scopedBound.error, { kind: "system" });
|
|
1306
|
+
return "handled";
|
|
1307
|
+
}
|
|
1308
|
+
const boundDirectory = scopedBound.directory;
|
|
1309
|
+
store.upsertBinding("telegram", input.identityId, input.peerKey, boundDirectory);
|
|
1310
|
+
store.deleteSession("telegram", input.identityId, input.peerKey);
|
|
1311
|
+
ensureEventSubscription(boundDirectory);
|
|
1312
|
+
logger.info({ channel: "telegram", identityId: input.identityId, peerId: input.peerKey, directory: boundDirectory }, "telegram private identity paired");
|
|
1313
|
+
await sendText("telegram", input.identityId, input.peerId, "Pairing successful. This chat is now linked to your worker.", { kind: "system" });
|
|
1314
|
+
return "handled";
|
|
1315
|
+
}
|
|
1138
1316
|
async function handleInbound(message) {
|
|
1139
1317
|
const adapter = adapters.get(adapterKey(message.channel, message.identityId));
|
|
1140
1318
|
if (!adapter)
|
|
@@ -1151,8 +1329,24 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
1151
1329
|
}, "inbound received");
|
|
1152
1330
|
logger.info({ channel: inbound.channel, identityId: inbound.identityId, peerId: inbound.peerId, length: inbound.text.length }, "received message");
|
|
1153
1331
|
const peerKey = inbound.peerId;
|
|
1154
|
-
// Handle bot commands
|
|
1155
1332
|
const trimmedText = inbound.text.trim();
|
|
1333
|
+
let binding = store.getBinding(inbound.channel, inbound.identityId, peerKey);
|
|
1334
|
+
let session = store.getSession(inbound.channel, inbound.identityId, peerKey);
|
|
1335
|
+
if (inbound.channel === "telegram") {
|
|
1336
|
+
const pairingGate = await handleTelegramPairingGate({
|
|
1337
|
+
identityId: inbound.identityId,
|
|
1338
|
+
peerKey,
|
|
1339
|
+
peerId: inbound.peerId,
|
|
1340
|
+
text: trimmedText,
|
|
1341
|
+
...(binding?.directory?.trim() ? { bindingDirectory: binding.directory } : {}),
|
|
1342
|
+
...(session?.directory?.trim() ? { sessionDirectory: session.directory ?? undefined } : {}),
|
|
1343
|
+
});
|
|
1344
|
+
if (pairingGate === "handled")
|
|
1345
|
+
return;
|
|
1346
|
+
binding = store.getBinding(inbound.channel, inbound.identityId, peerKey);
|
|
1347
|
+
session = store.getSession(inbound.channel, inbound.identityId, peerKey);
|
|
1348
|
+
}
|
|
1349
|
+
// Handle bot commands
|
|
1156
1350
|
if (trimmedText.startsWith("/")) {
|
|
1157
1351
|
const commandHandled = await handleCommand(inbound.channel, inbound.identityId, peerKey, inbound.peerId, trimmedText);
|
|
1158
1352
|
if (commandHandled)
|
|
@@ -1165,8 +1359,6 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
1165
1359
|
text: inbound.text,
|
|
1166
1360
|
fromMe: inbound.fromMe,
|
|
1167
1361
|
});
|
|
1168
|
-
const binding = store.getBinding(inbound.channel, inbound.identityId, peerKey);
|
|
1169
|
-
const session = store.getSession(inbound.channel, inbound.identityId, peerKey);
|
|
1170
1362
|
const identityDirectory = resolveIdentityDirectory(inbound.channel, inbound.identityId);
|
|
1171
1363
|
const boundDirectoryCandidate = binding?.directory?.trim() || session?.directory?.trim() || identityDirectory || defaultDirectory;
|
|
1172
1364
|
const hasExplicitBinding = Boolean(binding?.directory?.trim() || session?.directory?.trim() || identityDirectory);
|
|
@@ -1180,7 +1372,8 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
1180
1372
|
return;
|
|
1181
1373
|
}
|
|
1182
1374
|
const boundDirectory = scopedBound.directory;
|
|
1183
|
-
|
|
1375
|
+
const shouldAutoBind = !(inbound.channel === "telegram" && resolveTelegramIdentityAccess(inbound.identityId).access === "private");
|
|
1376
|
+
if (shouldAutoBind && !binding?.directory?.trim()) {
|
|
1184
1377
|
store.upsertBinding(inbound.channel, inbound.identityId, peerKey, boundDirectory);
|
|
1185
1378
|
}
|
|
1186
1379
|
ensureEventSubscription(boundDirectory);
|
|
@@ -1353,6 +1546,28 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
1353
1546
|
logger.info({ channel, peerId: peerKey }, "session and model reset");
|
|
1354
1547
|
return true;
|
|
1355
1548
|
}
|
|
1549
|
+
if (command === "pair") {
|
|
1550
|
+
if (channel !== "telegram") {
|
|
1551
|
+
await sendText(channel, identityId, peerId, "Pairing is only available for Telegram private bots.", {
|
|
1552
|
+
kind: "system",
|
|
1553
|
+
});
|
|
1554
|
+
return true;
|
|
1555
|
+
}
|
|
1556
|
+
const binding = store.getBinding(channel, identityId, peerKey);
|
|
1557
|
+
const session = store.getSession(channel, identityId, peerKey);
|
|
1558
|
+
const pairingGate = await handleTelegramPairingGate({
|
|
1559
|
+
identityId,
|
|
1560
|
+
peerKey,
|
|
1561
|
+
peerId,
|
|
1562
|
+
text,
|
|
1563
|
+
...(binding?.directory?.trim() ? { bindingDirectory: binding.directory } : {}),
|
|
1564
|
+
...(session?.directory?.trim() ? { sessionDirectory: session.directory ?? undefined } : {}),
|
|
1565
|
+
});
|
|
1566
|
+
if (pairingGate === "handled")
|
|
1567
|
+
return true;
|
|
1568
|
+
await sendText(channel, identityId, peerId, "This chat is already paired.", { kind: "system" });
|
|
1569
|
+
return true;
|
|
1570
|
+
}
|
|
1356
1571
|
if (command === "dir" || command === "cd") {
|
|
1357
1572
|
const next = args.join(" ").trim();
|
|
1358
1573
|
if (!next) {
|
|
@@ -1385,7 +1600,7 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
1385
1600
|
}
|
|
1386
1601
|
// /help command
|
|
1387
1602
|
if (command === "help") {
|
|
1388
|
-
const helpText = `/opus - Claude Opus 4.5\n/codex - GPT 5.2 Codex\n/dir <path> - bind this chat to a workspace directory\n/dir - show current directory\n/agent - show workspace agent scope/path\n/model - show current\n/reset - start fresh\n/help - this`;
|
|
1603
|
+
const helpText = `/opus - Claude Opus 4.5\n/codex - GPT 5.2 Codex\n/pair <code> - pair this chat with a private Telegram bot\n/dir <path> - bind this chat to a workspace directory\n/dir - show current directory\n/agent - show workspace agent scope/path\n/model - show current\n/reset - start fresh\n/help - this`;
|
|
1389
1604
|
await sendText(channel, identityId, peerId, helpText, { kind: "system" });
|
|
1390
1605
|
return true;
|
|
1391
1606
|
}
|
package/dist/config.js
CHANGED
|
@@ -73,6 +73,17 @@ function normalizeId(value) {
|
|
|
73
73
|
const safe = trimmed.replace(/[^a-zA-Z0-9_.-]+/g, "-");
|
|
74
74
|
return safe.replace(/^-+|-+$/g, "").slice(0, 48) || "default";
|
|
75
75
|
}
|
|
76
|
+
const PAIRING_CODE_HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
77
|
+
function normalizeTelegramAccess(value) {
|
|
78
|
+
const raw = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
79
|
+
return raw === "private" ? "private" : "public";
|
|
80
|
+
}
|
|
81
|
+
function normalizePairingCodeHash(value) {
|
|
82
|
+
const raw = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
83
|
+
if (!PAIRING_CODE_HASH_PATTERN.test(raw))
|
|
84
|
+
return "";
|
|
85
|
+
return raw;
|
|
86
|
+
}
|
|
76
87
|
function coerceTelegramBots(file) {
|
|
77
88
|
const telegram = file.channels?.telegram;
|
|
78
89
|
const bots = Array.isArray(telegram?.bots) ? telegram.bots : [];
|
|
@@ -86,11 +97,14 @@ function coerceTelegramBots(file) {
|
|
|
86
97
|
continue;
|
|
87
98
|
const id = normalizeId(typeof record.id === "string" ? record.id : "default");
|
|
88
99
|
const directory = typeof record.directory === "string" ? record.directory.trim() : "";
|
|
100
|
+
const access = normalizeTelegramAccess(record.access);
|
|
101
|
+
const pairingCodeHash = normalizePairingCodeHash(record.pairingCodeHash);
|
|
89
102
|
normalized.push({
|
|
90
103
|
id,
|
|
91
104
|
token,
|
|
92
105
|
enabled: record.enabled === undefined ? true : record.enabled === true,
|
|
93
106
|
...(directory ? { directory } : {}),
|
|
107
|
+
...(access === "private" ? { access, ...(pairingCodeHash ? { pairingCodeHash } : {}) } : { access: "public" }),
|
|
94
108
|
});
|
|
95
109
|
}
|
|
96
110
|
if (normalized.length)
|
package/dist/health.js
CHANGED
|
@@ -155,6 +155,8 @@ export async function startHealthServer(port, getStatus, logger, handlers = {})
|
|
|
155
155
|
const token = typeof payload.token === "string" ? payload.token.trim() : "";
|
|
156
156
|
const id = typeof payload.id === "string" ? payload.id.trim() : undefined;
|
|
157
157
|
const directory = typeof payload.directory === "string" ? payload.directory.trim() : undefined;
|
|
158
|
+
const access = typeof payload.access === "string" ? payload.access.trim() : undefined;
|
|
159
|
+
const pairingCodeHash = typeof payload.pairingCodeHash === "string" ? payload.pairingCodeHash.trim() : undefined;
|
|
158
160
|
const enabled = payload.enabled === undefined ? undefined : payload.enabled === true || payload.enabled === "true";
|
|
159
161
|
if (!token) {
|
|
160
162
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
@@ -166,14 +168,18 @@ export async function startHealthServer(port, getStatus, logger, handlers = {})
|
|
|
166
168
|
token,
|
|
167
169
|
...(enabled === undefined ? {} : { enabled }),
|
|
168
170
|
...(directory ? { directory } : {}),
|
|
171
|
+
...(access ? { access: access } : {}),
|
|
172
|
+
...(pairingCodeHash ? { pairingCodeHash } : {}),
|
|
169
173
|
});
|
|
170
174
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
171
175
|
res.end(JSON.stringify({ ok: true, telegram: result }));
|
|
172
176
|
return;
|
|
173
177
|
}
|
|
174
178
|
catch (error) {
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
const statusRaw = error?.status;
|
|
180
|
+
const status = typeof statusRaw === "number" && statusRaw >= 400 && statusRaw < 600 ? statusRaw : 500;
|
|
181
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
182
|
+
res.end(JSON.stringify({ ok: false, error: String(error instanceof Error ? error.message : error) }));
|
|
177
183
|
return;
|
|
178
184
|
}
|
|
179
185
|
}
|