@stage-labs/metro 0.1.0-beta.201 → 0.1.0-beta.202
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/claude.js +4 -15
- package/dist/cli.js +5 -3
- package/dist/hold.js +1 -2
- package/dist/runtime-install.js +4 -5
- package/dist/runtime.js +1 -4
- package/dist/serve.js +4 -5
- package/dist/service.js +82 -21
- package/dist/update.js +1 -1
- package/package.json +2 -2
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +1 -1
- package/runtime/node_modules/@metro-labs/core/src/events.ts +4 -2
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +12 -25
- package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +45 -22
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +0 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +8 -80
- package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +3 -5
- package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +4 -9
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +9 -39
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +9 -28
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +8 -116
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +11 -35
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +7 -9
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +3 -29
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +5 -8
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +5 -8
- package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +3 -6
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +25 -3
- package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +2 -3
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +6 -56
- package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +7 -6
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +7 -7
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +4 -4
- package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +2 -4
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +19 -22
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +1 -2
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +6 -8
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +3 -6
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +2 -3
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +28 -1
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +4 -4
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +3 -2
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +6 -6
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +7 -7
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +3 -2
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +10 -31
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +1 -2
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +5 -6
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +1 -4
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +3 -8
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +6 -7
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +5 -44
- package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +4 -4
- package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +6 -7
- package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +12 -13
- package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +5 -22
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +1 -19
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +7 -35
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +5 -10
- package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +1 -1
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +1 -2
- package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +1 -1
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +11 -12
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +2 -5
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +0 -1
- package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +1 -15
- package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +0 -6
- package/runtime/node_modules/@metro-labs/outlook/src/format.ts +0 -1
- package/runtime/node_modules/@metro-labs/outlook/src/state.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +4 -22
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +2 -2
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +1 -3
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +1 -2
- package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +4 -37
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +7 -23
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +1 -4
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +4 -9
- package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +3 -3
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +0 -11
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +2 -12
- package/runtime/node_modules/@metro-labs/threema/src/files.ts +4 -15
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +2 -7
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +1 -2
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +5 -26
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +5 -6
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +1 -3
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +1 -12
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +14 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +31 -78
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +3 -4
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +1 -6
- package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +2 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +0 -2
- package/runtime/runtime.json +1 -1
- package/runtime/node_modules/@metro-labs/core/src/identity.ts +0 -7
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/needs.ts +0 -2
- package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +0 -12
- package/runtime/node_modules/@metro-labs/daemon/src/channels/react-note.ts +0 -13
- package/runtime/node_modules/@metro-labs/daemon/src/channels/reply-id.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +0 -29
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/api.ts +0 -43
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/move.ts +0 -165
- package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +0 -1
- package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +0 -22
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +0 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +0 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +0 -22
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +0 -1
|
@@ -58,12 +58,12 @@ async function turnOn(user: AgentUser, dir: string): Promise<Record<string, stri
|
|
|
58
58
|
trustSystem(ca);
|
|
59
59
|
memory.browsers = trustBrowsers(user);
|
|
60
60
|
await runProxy(bin, configFile(dir));
|
|
61
|
-
applyFirewall(
|
|
61
|
+
applyFirewall();
|
|
62
62
|
return proxyEnvFor(TRUSTED_CA, SYSTEM_BUNDLE, secrets);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
function turnOff(
|
|
66
|
-
removeFirewall(
|
|
65
|
+
function turnOff(): void {
|
|
66
|
+
removeFirewall();
|
|
67
67
|
stopProxy();
|
|
68
68
|
untrustSystem();
|
|
69
69
|
}
|
|
@@ -72,7 +72,7 @@ export async function applyVault(user: AgentUser | null = agentUser(), dir = vau
|
|
|
72
72
|
if (user === null) return { status: vaultStatus(null, dir), envChanged: false };
|
|
73
73
|
const enabled = readVault(dir).enabled;
|
|
74
74
|
try {
|
|
75
|
-
const env = enabled ? await turnOn(user, dir) : (turnOff(
|
|
75
|
+
const env = enabled ? await turnOn(user, dir) : (turnOff(), {});
|
|
76
76
|
memory.problem = null;
|
|
77
77
|
const envChanged = setEnv(env);
|
|
78
78
|
if (envChanged || enabled) exportForJobs(user, env);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
|
+
import { stringOf } from '@metro-labs/http/api-http';
|
|
2
3
|
import { randomBytes } from 'node:crypto';
|
|
3
4
|
import { basename } from 'node:path';
|
|
4
5
|
import { createInterface } from 'node:readline';
|
|
@@ -30,8 +31,6 @@ const state: { child: ChildProcess | null; wanted: { bin: string; config: string
|
|
|
30
31
|
startedAt: 0,
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
const text = (v: unknown): string => (typeof v === 'string' ? v : '');
|
|
34
|
-
|
|
35
34
|
function swappedIds(transforms: unknown): string[] {
|
|
36
35
|
if (!Array.isArray(transforms)) return [];
|
|
37
36
|
return transforms.flatMap((t) => {
|
|
@@ -51,12 +50,12 @@ export function requestOf(line: string): ProxyRequest | null {
|
|
|
51
50
|
const audit = parsed.audit;
|
|
52
51
|
if (audit.method === 'CONNECT') return null;
|
|
53
52
|
return {
|
|
54
|
-
at:
|
|
55
|
-
method:
|
|
56
|
-
host:
|
|
57
|
-
path:
|
|
53
|
+
at: stringOf(parsed.time),
|
|
54
|
+
method: stringOf(audit.method),
|
|
55
|
+
host: stringOf(audit.host),
|
|
56
|
+
path: stringOf(audit.path),
|
|
58
57
|
status: typeof audit.status_code === 'number' ? audit.status_code : null,
|
|
59
|
-
action:
|
|
58
|
+
action: stringOf(audit.action),
|
|
60
59
|
swapped: swappedIds(parsed.request_transforms),
|
|
61
60
|
};
|
|
62
61
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, rmSync } from 'node:fs';
|
|
2
|
+
import { stringOf } from '@metro-labs/http/api-http';
|
|
2
3
|
import { ApiError } from '@metro-labs/http/api-error';
|
|
3
4
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
4
5
|
import { newId } from '@metro-labs/core/ids';
|
|
@@ -34,12 +35,10 @@ const MAX_SECRETS = 100;
|
|
|
34
35
|
const MAX_HOSTS = 20;
|
|
35
36
|
const MAX_VALUE = 16 * 1024;
|
|
36
37
|
|
|
37
|
-
const str = (v: unknown): string => (typeof v === 'string' ? v : '');
|
|
38
|
-
|
|
39
38
|
function secretOf(raw: unknown): VaultSecret | null {
|
|
40
|
-
if (!isRecord(raw) || typeof raw.id !== 'string' || !ENV_RE.test(
|
|
39
|
+
if (!isRecord(raw) || typeof raw.id !== 'string' || !ENV_RE.test(stringOf(raw.env))) return null;
|
|
41
40
|
const hosts = Array.isArray(raw.hosts) ? raw.hosts.filter((h): h is string => typeof h === 'string' && HOST_RE.test(h)) : [];
|
|
42
|
-
return { id: raw.id, name:
|
|
41
|
+
return { id: raw.id, name: stringOf(raw.name) || stringOf(raw.env), env: stringOf(raw.env), hosts, createdAt: stringOf(raw.createdAt), updatedAt: stringOf(raw.updatedAt) };
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
export function readVault(dir = vaultDir()): VaultState {
|
|
@@ -49,35 +48,35 @@ export function readVault(dir = vaultDir()): VaultState {
|
|
|
49
48
|
return { version: 1, enabled: raw.enabled === true, secrets };
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
function writeVault(state: VaultState, dir = vaultDir()): void {
|
|
53
52
|
ensureSecureDir(dir);
|
|
54
53
|
writeSecure(stateFile(dir), `${JSON.stringify(state, null, 2)}\n`);
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
const name =
|
|
56
|
+
function normalizeName(raw: unknown): string {
|
|
57
|
+
const name = stringOf(raw).trim();
|
|
59
58
|
if (name === '' || name.length > 80) throw new ApiError('a name of 1 to 80 characters is required', 400);
|
|
60
59
|
return name;
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
const env =
|
|
62
|
+
function normalizeEnv(raw: unknown): string {
|
|
63
|
+
const env = stringOf(raw).trim();
|
|
65
64
|
if (!ENV_RE.test(env) || RESERVED_ENV.has(env))
|
|
66
65
|
throw new ApiError('the variable name must be capital letters, digits and _, 3 to 64 long, like OPENAI_API_KEY', 400);
|
|
67
66
|
return env;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
function normalizeHosts(raw: unknown): string[] {
|
|
71
70
|
if (!Array.isArray(raw)) throw new ApiError('hosts must be a list of websites, like api.openai.com', 400);
|
|
72
|
-
const hosts = [...new Set(raw.map((h) =>
|
|
71
|
+
const hosts = [...new Set(raw.map((h) => stringOf(h).trim().toLowerCase().replace(/^https?:\/\//, '').replace(/\/.*$/, '')).filter((h) => h !== ''))];
|
|
73
72
|
if (hosts.length === 0 || hosts.length > MAX_HOSTS) throw new ApiError(`a secret needs 1 to ${String(MAX_HOSTS)} websites`, 400);
|
|
74
73
|
const bad = hosts.find((h) => !HOST_RE.test(h));
|
|
75
74
|
if (bad !== undefined) throw new ApiError(`'${bad}' is not a website name; write it as api.example.com or *.example.com`, 400);
|
|
76
75
|
return hosts;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
const value =
|
|
78
|
+
function normalizeValue(raw: unknown): string {
|
|
79
|
+
const value = stringOf(raw).trim();
|
|
81
80
|
if (value === '' || value.length > MAX_VALUE) throw new ApiError('a value of 1 to 16384 characters is required', 400);
|
|
82
81
|
return value;
|
|
83
82
|
}
|
|
@@ -1,33 +1,19 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { errMsg, log } from '@metro-labs/core/log';
|
|
4
4
|
import { asUser, type AgentUser } from '../agent-user/user.js';
|
|
5
5
|
import { TRUSTED_CA } from './paths.js';
|
|
6
|
-
import { mustHelper, rootHelper
|
|
6
|
+
import { mustHelper, rootHelper } from '../metro-user/privilege.js';
|
|
7
7
|
|
|
8
8
|
const has = (bin: string): boolean => spawnSync('sh', ['-c', `command -v ${bin}`], { stdio: 'ignore' }).status === 0;
|
|
9
9
|
|
|
10
10
|
export function trustSystem(caFile: string): void {
|
|
11
11
|
const same = existsSync(TRUSTED_CA) && readFileSync(TRUSTED_CA, 'utf8') === readFileSync(caFile, 'utf8');
|
|
12
|
-
if (same)
|
|
13
|
-
if (runningAsMetro()) {
|
|
14
|
-
mustHelper(['trust-ca']);
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
copyFileSync(caFile, TRUSTED_CA);
|
|
18
|
-
chmodSync(TRUSTED_CA, 0o644);
|
|
19
|
-
const run = spawnSync('update-ca-certificates', [], { encoding: 'utf8' });
|
|
20
|
-
if (run.status !== 0) throw new Error(`update-ca-certificates failed: ${run.stderr.trim()}`);
|
|
12
|
+
if (!same) mustHelper(['trust-ca']);
|
|
21
13
|
}
|
|
22
14
|
|
|
23
15
|
export function untrustSystem(): void {
|
|
24
|
-
if (
|
|
25
|
-
if (runningAsMetro()) {
|
|
26
|
-
mustHelper(['untrust-ca']);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
rmSync(TRUSTED_CA, { force: true });
|
|
30
|
-
spawnSync('update-ca-certificates', ['--fresh'], { stdio: 'ignore' });
|
|
16
|
+
if (existsSync(TRUSTED_CA)) mustHelper(['untrust-ca']);
|
|
31
17
|
}
|
|
32
18
|
|
|
33
19
|
const NSS_SCRIPT = [
|
|
@@ -41,10 +27,7 @@ const NSS_SCRIPT = [
|
|
|
41
27
|
|
|
42
28
|
function ensureCertutil(): boolean {
|
|
43
29
|
if (has('certutil')) return true;
|
|
44
|
-
|
|
45
|
-
if (!has('apt-get')) return false;
|
|
46
|
-
const run = spawnSync('apt-get', ['install', '-y', '-q', 'libnss3-tools'], { stdio: 'ignore', timeout: 180_000, env: { ...process.env, DEBIAN_FRONTEND: 'noninteractive' } });
|
|
47
|
-
return run.status === 0 && has('certutil');
|
|
30
|
+
return rootHelper(['install-nss-tools']).status === 0 && has('certutil');
|
|
48
31
|
}
|
|
49
32
|
|
|
50
33
|
export function trustBrowsers(user: AgentUser): string | null {
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { Client } from 'discord.js';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
2
|
import {
|
|
5
3
|
makeAccountStore,
|
|
6
4
|
resolveAccountId,
|
|
7
5
|
} from '@metro-labs/core/stations/account-store';
|
|
8
6
|
import { Line } from '@metro-labs/core/lines';
|
|
9
|
-
import { emit } from './wire.js';
|
|
10
7
|
import { API } from './api-base.js';
|
|
11
8
|
|
|
12
|
-
const ACCOUNTS_FILE =
|
|
13
|
-
process.env.DISCORD_BOT_ACCOUNTS_FILE ??
|
|
14
|
-
join(homedir(), '.metro', 'discord-bot-accounts.json');
|
|
15
|
-
|
|
16
9
|
export interface AccountConfig {
|
|
17
10
|
id: string;
|
|
18
11
|
token: string;
|
|
@@ -20,16 +13,10 @@ export interface AccountConfig {
|
|
|
20
13
|
|
|
21
14
|
export const { loadAccounts } = makeAccountStore<AccountConfig>({
|
|
22
15
|
prefix: 'discord-bot',
|
|
23
|
-
file: ACCOUNTS_FILE,
|
|
24
16
|
validate(raw, die) {
|
|
25
|
-
const
|
|
26
|
-
for (const a of raw) {
|
|
27
|
-
if (!a.id) die('account missing id');
|
|
17
|
+
for (const a of raw)
|
|
28
18
|
if (!a.token || typeof a.token !== 'string')
|
|
29
19
|
die(`account '${a.id}' missing token`);
|
|
30
|
-
if (seen.has(a.id)) die(`duplicate account id '${a.id}'`);
|
|
31
|
-
seen.add(a.id);
|
|
32
|
-
}
|
|
33
20
|
},
|
|
34
21
|
});
|
|
35
22
|
|
|
@@ -69,17 +56,12 @@ export async function rest<T = unknown>(
|
|
|
69
56
|
};
|
|
70
57
|
if (body !== undefined && !isForm)
|
|
71
58
|
headers['Content-Type'] = 'application/json';
|
|
72
|
-
emit({ op: 'log', text: `discord-bot[${accountId}] api ${method} ${path}` });
|
|
73
59
|
const res = await fetch(`${API}${path}`, {
|
|
74
60
|
method,
|
|
75
61
|
headers,
|
|
76
62
|
body: restBody(body, isForm),
|
|
77
63
|
signal: AbortSignal.timeout(30_000),
|
|
78
64
|
});
|
|
79
|
-
emit({
|
|
80
|
-
op: 'log',
|
|
81
|
-
text: `discord-bot[${accountId}] api ${method} ${path} -> ${res.status}`,
|
|
82
|
-
});
|
|
83
65
|
if (!res.ok) {
|
|
84
66
|
const text = await res.text().catch(() => '');
|
|
85
67
|
throw new Error(`discord-bot ${method} ${path}: ${res.status} ${text}`);
|
|
@@ -6,18 +6,13 @@ import {
|
|
|
6
6
|
routeOf,
|
|
7
7
|
} from './accounts.js';
|
|
8
8
|
import { emitOutbound, emitOutboundEdit, emitOutboundReact } from './format.js';
|
|
9
|
-
import {
|
|
10
|
-
import { normalizeDiscord } from '@metro-labs/core/stations/messaging-normalize';
|
|
9
|
+
import { messagingAliases } from '@metro-labs/core/stations/messaging-normalize';
|
|
11
10
|
import {
|
|
12
11
|
appendFiles,
|
|
13
12
|
outgoingFiles,
|
|
14
13
|
type OutgoingFile,
|
|
15
14
|
} from './send-files.js';
|
|
16
|
-
import {
|
|
17
|
-
makeStation,
|
|
18
|
-
type CallMsg,
|
|
19
|
-
type StationHandler,
|
|
20
|
-
} from '@metro-labs/core/stations/station-runtime';
|
|
15
|
+
import { makeStation, respond, type CallMsg, type StationHandler } from '@metro-labs/core/stations/station-runtime';
|
|
21
16
|
import { readProfile, setProfile } from './profile.js';
|
|
22
17
|
import { discordMembers } from './members.js';
|
|
23
18
|
import {
|
|
@@ -57,40 +52,17 @@ async function sendMessage(
|
|
|
57
52
|
export type { CallMsg };
|
|
58
53
|
|
|
59
54
|
async function send(id: string, args: Record<string, unknown>): Promise<void> {
|
|
60
|
-
const {
|
|
61
|
-
line,
|
|
62
|
-
text,
|
|
63
|
-
replyTo,
|
|
64
|
-
embeds,
|
|
65
|
-
stickerIds,
|
|
66
|
-
images,
|
|
67
|
-
files,
|
|
68
|
-
account,
|
|
69
|
-
attachmentKinds,
|
|
70
|
-
attachmentNames,
|
|
71
|
-
} = args as {
|
|
55
|
+
const { line, text, replyTo, account } = args as {
|
|
72
56
|
line: string;
|
|
73
57
|
text?: string;
|
|
74
58
|
replyTo?: string;
|
|
75
|
-
embeds?: unknown[];
|
|
76
|
-
stickerIds?: string[];
|
|
77
|
-
images?: string[];
|
|
78
|
-
files?: string[];
|
|
79
59
|
account?: string;
|
|
80
|
-
attachmentKinds?: string[];
|
|
81
|
-
attachmentNames?: string[];
|
|
82
60
|
};
|
|
83
61
|
const { accountId, channelId } = routeOf(line, account);
|
|
84
62
|
const body: Record<string, unknown> = { flags: 4 };
|
|
85
63
|
if (text !== undefined) body.content = text;
|
|
86
64
|
if (replyTo) body.message_reference = { message_id: replyTo };
|
|
87
|
-
|
|
88
|
-
if (stickerIds) body.sticker_ids = stickerIds;
|
|
89
|
-
const outgoing = outgoingFiles(
|
|
90
|
-
[...(images ?? []), ...(files ?? [])],
|
|
91
|
-
attachmentNames,
|
|
92
|
-
attachmentKinds,
|
|
93
|
-
);
|
|
65
|
+
const outgoing = outgoingFiles(args.attachments);
|
|
94
66
|
const res = await sendMessage(accountId, channelId, body, outgoing);
|
|
95
67
|
emitOutbound(accountId, line, res.id, text ?? '', replyTo);
|
|
96
68
|
respond(id, {
|
|
@@ -175,7 +147,7 @@ async function remove(
|
|
|
175
147
|
respond(id, { result: { ok: true, account: accountId } });
|
|
176
148
|
}
|
|
177
149
|
|
|
178
|
-
async function
|
|
150
|
+
async function read(
|
|
179
151
|
id: string,
|
|
180
152
|
args: Record<string, unknown>,
|
|
181
153
|
): Promise<void> {
|
|
@@ -228,7 +200,7 @@ const HANDLERS: Record<string, StationHandler> = {
|
|
|
228
200
|
react,
|
|
229
201
|
edit,
|
|
230
202
|
delete: remove,
|
|
231
|
-
|
|
203
|
+
read,
|
|
232
204
|
listMembers,
|
|
233
205
|
set_profile: setProfile,
|
|
234
206
|
profile: readProfile,
|
|
@@ -236,5 +208,5 @@ const HANDLERS: Record<string, StationHandler> = {
|
|
|
236
208
|
|
|
237
209
|
export const handleCall = makeStation({
|
|
238
210
|
handlers: HANDLERS,
|
|
239
|
-
normalize:
|
|
211
|
+
normalize: messagingAliases(),
|
|
240
212
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MessageFlags, type Message, type MessageReaction, type User } from 'discord.js';
|
|
2
2
|
import { reportAttachment, selfUri } from '@metro-labs/core/stations/train-events';
|
|
3
3
|
import { accounts, lineOf } from './accounts.js';
|
|
4
|
-
import { emit, mintId } from '
|
|
4
|
+
import { emit, mintId } from '@metro-labs/core/stations/station-runtime';
|
|
5
5
|
import { saveDiscordAttachment } from './attachments.js';
|
|
6
6
|
|
|
7
7
|
const AV_TAG: Record<string, string> = { audio: 'audio', video: 'video' };
|
|
@@ -63,7 +63,6 @@ export function messageEnvelope(
|
|
|
63
63
|
);
|
|
64
64
|
});
|
|
65
65
|
return {
|
|
66
|
-
kind: 'inbound',
|
|
67
66
|
id: envId,
|
|
68
67
|
ts: new Date(m.createdTimestamp).toISOString(),
|
|
69
68
|
station: 'discord-bot',
|
|
@@ -90,7 +89,6 @@ export function reactionEnvelope(
|
|
|
90
89
|
): Record<string, unknown> | null {
|
|
91
90
|
if (u.bot) return null;
|
|
92
91
|
return {
|
|
93
|
-
kind: 'react',
|
|
94
92
|
id: mintId(),
|
|
95
93
|
ts: new Date().toISOString(),
|
|
96
94
|
station: 'discord-bot',
|
|
@@ -116,23 +114,20 @@ export function reactionEnvelope(
|
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
function outbound(
|
|
119
|
-
kind: string,
|
|
120
117
|
accountId: string,
|
|
121
118
|
line: string,
|
|
122
119
|
messageId: string,
|
|
123
120
|
extra: object,
|
|
124
121
|
): void {
|
|
125
122
|
emit({
|
|
126
|
-
kind,
|
|
127
123
|
id: mintId(),
|
|
128
124
|
ts: new Date().toISOString(),
|
|
129
125
|
station: 'discord-bot',
|
|
130
126
|
line,
|
|
131
|
-
from: selfUri(
|
|
127
|
+
from: selfUri(),
|
|
132
128
|
to: line,
|
|
133
129
|
message_id: messageId,
|
|
134
130
|
...extra,
|
|
135
|
-
account: accountId,
|
|
136
131
|
payload: { account: accountId },
|
|
137
132
|
});
|
|
138
133
|
}
|
|
@@ -144,7 +139,7 @@ export function emitOutbound(
|
|
|
144
139
|
text: string,
|
|
145
140
|
replyTo?: string,
|
|
146
141
|
): void {
|
|
147
|
-
outbound(
|
|
142
|
+
outbound(accountId, line, messageId, {
|
|
148
143
|
text,
|
|
149
144
|
reply_to: replyTo,
|
|
150
145
|
...(replyTo ? { event: { type: 'reply', replyTo } } : {}),
|
|
@@ -156,7 +151,7 @@ export function emitOutboundReact(
|
|
|
156
151
|
messageId: string,
|
|
157
152
|
emoji: string,
|
|
158
153
|
): void {
|
|
159
|
-
outbound(
|
|
154
|
+
outbound(accountId, line, messageId, {
|
|
160
155
|
emoji,
|
|
161
156
|
event: { type: 'react', emoji, targetId: messageId },
|
|
162
157
|
});
|
|
@@ -167,7 +162,7 @@ export function emitOutboundEdit(
|
|
|
167
162
|
messageId: string,
|
|
168
163
|
text: string,
|
|
169
164
|
): void {
|
|
170
|
-
outbound(
|
|
165
|
+
outbound(accountId, line, messageId, {
|
|
171
166
|
text,
|
|
172
167
|
event: { type: 'edit', targetId: messageId },
|
|
173
168
|
});
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from './accounts.js';
|
|
17
17
|
import { emitInbound } from '@metro-labs/core/stations/train-events';
|
|
18
18
|
import { messageEnvelope, reactionEnvelope } from './format.js';
|
|
19
|
-
import { mintId } from '
|
|
19
|
+
import { mintId } from '@metro-labs/core/stations/station-runtime';
|
|
20
20
|
import { readCalls } from '@metro-labs/core/trains/protocol';
|
|
21
21
|
import { announceAccounts } from '@metro-labs/core/stations/train-boot';
|
|
22
22
|
import { handleCall } from './actions.js';
|
|
@@ -45,7 +45,6 @@ function makeClient(): Client {
|
|
|
45
45
|
function onEdit(accountId: string, m: Message): void {
|
|
46
46
|
if (m.author.bot) return;
|
|
47
47
|
emitInbound(accountId, {
|
|
48
|
-
kind: 'edit',
|
|
49
48
|
id: mintId(),
|
|
50
49
|
ts: new Date(m.editedTimestamp ?? Date.now()).toISOString(),
|
|
51
50
|
station: 'discord-bot',
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '@metro-labs/core/stations/profile';
|
|
11
11
|
import type { SenderProfile } from '@metro-labs/core/stations/sender-profile';
|
|
12
12
|
import { accountFor, accounts } from './accounts.js';
|
|
13
|
-
import { respond } from '
|
|
13
|
+
import { respond } from '@metro-labs/core/stations/station-runtime';
|
|
14
14
|
|
|
15
15
|
export async function applyProfile(
|
|
16
16
|
client: Client,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { appendFile } from '@metro-labs/core/stations/attachments';
|
|
2
|
+
import type { CanonicalAttachment } from '@metro-labs/core/stations/types';
|
|
2
3
|
|
|
3
4
|
export interface OutgoingFile {
|
|
4
5
|
path: string;
|
|
@@ -6,25 +7,23 @@ export interface OutgoingFile {
|
|
|
6
7
|
kind: string;
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
type WireAttachment = CanonicalAttachment & { kind?: string };
|
|
11
|
+
|
|
9
12
|
const basenameOf = (path: string, index: number): string =>
|
|
10
13
|
path.split('/').pop() ?? `file-${index}`;
|
|
11
14
|
|
|
12
|
-
export function outgoingFiles(
|
|
13
|
-
|
|
14
|
-
names: string[] | undefined,
|
|
15
|
-
kinds: string[] | undefined,
|
|
16
|
-
): OutgoingFile[] {
|
|
15
|
+
export function outgoingFiles(attachments: unknown): OutgoingFile[] {
|
|
16
|
+
if (!Array.isArray(attachments)) return [];
|
|
17
17
|
const out: OutgoingFile[] = [];
|
|
18
|
-
|
|
19
|
-
const path =
|
|
20
|
-
if (path === undefined || path === '')
|
|
21
|
-
const given = names?.[i];
|
|
18
|
+
(attachments as WireAttachment[]).forEach((a, i) => {
|
|
19
|
+
const path = a.path;
|
|
20
|
+
if (path === undefined || path === '') return;
|
|
22
21
|
out.push({
|
|
23
22
|
path,
|
|
24
|
-
name:
|
|
25
|
-
kind:
|
|
23
|
+
name: a.name !== undefined && a.name !== '' ? a.name : basenameOf(path, i),
|
|
24
|
+
kind: a.kind ?? 'file',
|
|
26
25
|
});
|
|
27
|
-
}
|
|
26
|
+
});
|
|
28
27
|
return out;
|
|
29
28
|
}
|
|
30
29
|
|
|
@@ -52,11 +52,6 @@ export function apiSession(req: IncomingMessage): Promise<ApiSession | null> {
|
|
|
52
52
|
return bearerSessions === null ? Promise.resolve(null) : bearerSessions(req);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export interface AgentIdentity {
|
|
56
|
-
subject: string;
|
|
57
|
-
agentId: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
55
|
export async function readJsonBody(req: IncomingMessage, max = BODY_MAX): Promise<unknown> {
|
|
61
56
|
const chunks: Buffer[] = [];
|
|
62
57
|
let total = 0;
|
|
@@ -136,3 +131,5 @@ export function bodyField(body: unknown, key: string): unknown {
|
|
|
136
131
|
? (body as Record<string, unknown>)[key]
|
|
137
132
|
: undefined;
|
|
138
133
|
}
|
|
134
|
+
|
|
135
|
+
export const stringOf = (value: unknown): string => (typeof value === 'string' ? value : '');
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
1
|
import { makeAccountStore, resolveAccountId, type Die } from '@metro-labs/core/stations/account-store';
|
|
4
2
|
import { TrainError } from '@metro-labs/core/train-error';
|
|
5
3
|
import { refreshTokens, type FetchLike } from './auth.js';
|
|
6
4
|
import { conversationOfLine } from './format.js';
|
|
7
5
|
import { loadState, saveState, type AccountState } from './state.js';
|
|
8
6
|
|
|
9
|
-
const ACCOUNTS_FILE = process.env.OUTLOOK_ACCOUNTS_FILE ?? join(homedir(), '.metro', 'outlook-accounts.json');
|
|
10
|
-
|
|
11
7
|
const RENEW_BEFORE_MS = 5 * 60_000;
|
|
12
8
|
|
|
13
9
|
export interface AccountConfig {
|
|
@@ -16,27 +12,18 @@ export interface AccountConfig {
|
|
|
16
12
|
refreshToken: string;
|
|
17
13
|
accessToken?: string;
|
|
18
14
|
expiresAt?: number;
|
|
19
|
-
tenantId?: string | null;
|
|
20
|
-
deltaLink?: string | null;
|
|
21
15
|
includeAutomated?: boolean;
|
|
22
16
|
}
|
|
23
17
|
|
|
24
18
|
function checkAccount(a: AccountConfig, die: Die): void {
|
|
25
|
-
if (!a.id) die('account missing id');
|
|
26
19
|
if (typeof a.accountEmail !== 'string' || !a.accountEmail.includes('@')) die(`account '${a.id}' has no mailbox address`);
|
|
27
20
|
if (typeof a.refreshToken !== 'string' || a.refreshToken === '') die(`account '${a.id}' has no refresh token`);
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
export const { loadAccounts } = makeAccountStore<AccountConfig>({
|
|
31
24
|
prefix: 'outlook',
|
|
32
|
-
file: ACCOUNTS_FILE,
|
|
33
25
|
validate(raw, die) {
|
|
34
|
-
const
|
|
35
|
-
for (const a of raw) {
|
|
36
|
-
checkAccount(a, die);
|
|
37
|
-
if (seen.has(a.id)) die(`duplicate account id '${a.id}'`);
|
|
38
|
-
seen.add(a.id);
|
|
39
|
-
}
|
|
26
|
+
for (const a of raw) checkAccount(a, die);
|
|
40
27
|
},
|
|
41
28
|
});
|
|
42
29
|
|
|
@@ -54,7 +41,6 @@ export class Account {
|
|
|
54
41
|
refreshToken: cfg.refreshToken,
|
|
55
42
|
accessToken: cfg.accessToken ?? '',
|
|
56
43
|
expiresAt: cfg.expiresAt ?? 0,
|
|
57
|
-
deltaLink: cfg.deltaLink ?? null,
|
|
58
44
|
});
|
|
59
45
|
}
|
|
60
46
|
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { makeStation, respond } from '@metro-labs/core/stations/station-runtime';
|
|
2
|
-
import type { Normalized } from '@metro-labs/core/stations/messaging-normalize';
|
|
3
2
|
import { accounts } from './accounts.js';
|
|
4
3
|
import { MAILBOX_URL } from './config.js';
|
|
5
4
|
import { reply, send } from './outbound.js';
|
|
6
5
|
import { read } from './query.js';
|
|
7
6
|
|
|
8
|
-
type Args = Record<string, unknown>;
|
|
9
|
-
|
|
10
7
|
function listAccounts(id: string): void {
|
|
11
8
|
const list = [...accounts.values()].map((a) => ({ id: a.id, handle: a.email, url: MAILBOX_URL, email: a.email }));
|
|
12
9
|
respond(id, { result: { accounts: list } });
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
const normalizeOutlook = (action: string, args: Args): Normalized => ({ action, args });
|
|
16
|
-
|
|
17
12
|
export const handleCall = makeStation({
|
|
18
13
|
handlers: { accounts: listAccounts, send, reply, read },
|
|
19
|
-
normalize: normalizeOutlook,
|
|
20
14
|
});
|
|
@@ -86,7 +86,6 @@ export function inboundEnvelope(accountId: string, self: string, m: GraphMessage
|
|
|
86
86
|
const from = addressOf(m.from);
|
|
87
87
|
const title = titleOf(m);
|
|
88
88
|
return {
|
|
89
|
-
kind: 'inbound',
|
|
90
89
|
id: mintId(),
|
|
91
90
|
ts: m.receivedDateTime ?? new Date().toISOString(),
|
|
92
91
|
station: 'outlook',
|
|
@@ -27,7 +27,7 @@ export function loadState(accountId: string, seed: Partial<AccountState>): Accou
|
|
|
27
27
|
refreshToken,
|
|
28
28
|
accessToken: fromFile ? text(saved.accessToken) : text(seed.accessToken),
|
|
29
29
|
expiresAt: fromFile ? Number(saved.expiresAt) || 0 : Number(seed.expiresAt) || 0,
|
|
30
|
-
deltaLink: nullable(saved.deltaLink)
|
|
30
|
+
deltaLink: nullable(saved.deltaLink),
|
|
31
31
|
syncedAt: nullable(saved.syncedAt),
|
|
32
32
|
seen: Array.isArray(saved.seen) ? saved.seen.filter((s): s is string => typeof s === 'string') : [],
|
|
33
33
|
};
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
1
|
import {
|
|
4
2
|
makeAccountStore,
|
|
5
3
|
resolveAccountId,
|
|
@@ -7,31 +5,22 @@ import {
|
|
|
7
5
|
} from '@metro-labs/core/stations/account-store';
|
|
8
6
|
import type { UserAccount } from './types.js';
|
|
9
7
|
|
|
10
|
-
const ACCOUNTS_FILE =
|
|
11
|
-
process.env.TELEGRAM_ACCOUNTS_FILE ??
|
|
12
|
-
join(homedir(), '.metro', 'telegram-accounts.json');
|
|
13
|
-
|
|
14
8
|
const isSignedInt = (s: string): boolean => /^-?\d+$/.test(s);
|
|
15
9
|
const isTopic = (s: string): boolean => /^\d+$/.test(s);
|
|
16
10
|
|
|
17
|
-
function validateAccount(a: UserAccount,
|
|
18
|
-
if (!a.id) die('account missing id');
|
|
11
|
+
function validateAccount(a: UserAccount, die: Die): void {
|
|
19
12
|
if (!a.session || typeof a.session !== 'string')
|
|
20
13
|
die(`account '${a.id}' missing session`);
|
|
21
14
|
if (!Number.isInteger(a.apiId) || (a.apiId ?? 0) <= 0)
|
|
22
15
|
die(`account '${a.id}' missing apiId`);
|
|
23
16
|
if (!a.apiHash || typeof a.apiHash !== 'string')
|
|
24
17
|
die(`account '${a.id}' missing apiHash`);
|
|
25
|
-
if (seen.has(a.id)) die(`duplicate account id '${a.id}'`);
|
|
26
|
-
seen.add(a.id);
|
|
27
18
|
}
|
|
28
19
|
|
|
29
20
|
export const { loadAccounts } = makeAccountStore<UserAccount>({
|
|
30
21
|
prefix: 'telegram',
|
|
31
|
-
file: ACCOUNTS_FILE,
|
|
32
22
|
validate(raw, die) {
|
|
33
|
-
const
|
|
34
|
-
for (const a of raw) validateAccount(a, seen, die);
|
|
23
|
+
for (const a of raw) validateAccount(a, die);
|
|
35
24
|
},
|
|
36
25
|
});
|
|
37
26
|
|
|
@@ -56,20 +45,13 @@ interface Target {
|
|
|
56
45
|
topicId?: number;
|
|
57
46
|
}
|
|
58
47
|
|
|
59
|
-
function splitScoped(path: string[]): { accountId: string; rest: string[] } {
|
|
60
|
-
const first = path[0];
|
|
61
|
-
if (path.length >= 2 && first !== undefined && !isSignedInt(first))
|
|
62
|
-
return { accountId: first, rest: path.slice(1) };
|
|
63
|
-
return { accountId: 'default', rest: path };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
48
|
export function targetOf(line: string): Target | undefined {
|
|
67
49
|
const prefix = 'metro://telegram/';
|
|
68
50
|
if (!line.startsWith(prefix)) return undefined;
|
|
69
51
|
const path = line.slice(prefix.length).split('/').filter(Boolean);
|
|
70
|
-
const
|
|
52
|
+
const [accountId, ...rest] = path;
|
|
71
53
|
const [chatId, topicId] = rest;
|
|
72
|
-
if (
|
|
54
|
+
if (accountId === undefined || chatId === undefined || rest.length > 2) return undefined;
|
|
73
55
|
if (!isSignedInt(chatId)) return undefined;
|
|
74
56
|
if (topicId !== undefined && !isTopic(topicId)) return undefined;
|
|
75
57
|
return {
|