@stage-labs/metro 0.1.0-beta.324 → 0.1.0-beta.326
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/agent.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/lock.js +15 -3
- package/dist/root-release-io.js +1 -2
- package/dist/root-release.js +2 -3
- package/dist/route.js +1 -1
- package/dist/service.js +4 -4
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runtime/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +2 -8
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +0 -6
- package/runtime/node_modules/@metro-labs/core/src/skill-source.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
- package/runtime/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
- package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
- package/runtime/node_modules/@metro-labs/core/src/train-error.ts +4 -4
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +2 -6
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +6 -6
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +0 -24
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +3 -4
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +4 -9
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +1 -6
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +3 -2
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +3 -7
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +10 -19
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +3 -4
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +1 -2
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +2 -4
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +5 -6
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/server-approvals.ts +2 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +2 -4
- package/runtime/node_modules/@metro-labs/daemon/src/mcp-server/announced.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/tailscale-guard.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +1 -3
- package/runtime/node_modules/@metro-labs/daemon/src/routes/gmail-attach.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +6 -21
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +6 -6
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +12 -61
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +13 -25
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +5 -5
- package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +2 -7
- package/runtime/node_modules/@metro-labs/daemon/src/voice/call.ts +2 -6
- package/runtime/node_modules/@metro-labs/daemon/src/voice/calls.ts +12 -12
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +5 -23
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +1 -7
- package/runtime/node_modules/@metro-labs/discord-bot/src/channels.ts +2 -2
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +10 -35
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +5 -11
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +6 -34
- package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +11 -14
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +1 -3
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +2 -3
- package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +3 -3
- package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +8 -8
- package/runtime/node_modules/@metro-labs/gmail/src/managed.ts +3 -2
- package/runtime/node_modules/@metro-labs/http/src/agent-signature.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +3 -4
- package/runtime/node_modules/@metro-labs/http/src/connector-discovery.ts +0 -113
- package/runtime/node_modules/@metro-labs/http/src/connector-input.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/connector-mcp.ts +12 -13
- package/runtime/node_modules/@metro-labs/http/src/connector-oauth.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/connector-policy.ts +1 -6
- package/runtime/node_modules/@metro-labs/http/src/connector-public-url.ts +4 -2
- package/runtime/node_modules/@metro-labs/http/src/connector-tools.ts +1 -11
- package/runtime/node_modules/@metro-labs/http/src/connector-url.ts +0 -6
- package/runtime/node_modules/@metro-labs/http/src/mcp-server-protocol.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/mcp-server-tools.ts +0 -2
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +2 -2
- package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +6 -7
- package/runtime/node_modules/@metro-labs/inference/package.json +1 -0
- package/runtime/node_modules/@metro-labs/inference/src/attempts.ts +3 -3
- package/runtime/node_modules/@metro-labs/inference/src/bedrock.ts +1 -1
- package/runtime/node_modules/@metro-labs/inference/src/codex-translate.ts +2 -2
- package/runtime/node_modules/@metro-labs/inference/src/delivered.ts +1 -1
- package/runtime/node_modules/@metro-labs/inference/src/effort.ts +0 -6
- package/runtime/node_modules/@metro-labs/inference/src/fallback.ts +7 -5
- package/runtime/node_modules/@metro-labs/inference/src/forward.ts +5 -5
- package/runtime/node_modules/@metro-labs/inference/src/measure.ts +2 -3
- package/runtime/node_modules/@metro-labs/inference/src/named.ts +3 -3
- package/runtime/node_modules/@metro-labs/inference/src/probes.ts +98 -0
- package/runtime/node_modules/@metro-labs/inference/src/usage.ts +5 -5
- package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +6 -8
- package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +2 -1
- package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +3 -5
- package/runtime/node_modules/@metro-labs/outlook/src/me.ts +6 -6
- package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +10 -13
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +7 -26
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +3 -6
- package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +3 -2
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +6 -20
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +1 -7
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +0 -2
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +1 -16
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -8
- package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +2 -1
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +1 -8
- package/runtime/node_modules/@metro-labs/threema/src/chats.ts +4 -3
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +15 -17
- package/runtime/node_modules/@metro-labs/threema/src/groups.ts +2 -4
- package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +2 -1
- package/runtime/node_modules/@metro-labs/threema/src/messages.ts +2 -1
- package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +7 -7
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +1 -3
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +0 -2
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +7 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +4 -10
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +4 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +14 -26
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +1 -7
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +17 -58
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +1 -6
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +10 -31
- package/runtime/node_modules/@metro-labs/xmtp/src/frames.ts +12 -14
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +3 -9
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +8 -9
- package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +4 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/network.ts +2 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +2 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/read-attachments.ts +2 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +2 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +23 -46
- package/runtime/node_modules/@metro-labs/xmtp/src/wallet.ts +5 -4
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +12 -15
- package/runtime/runtime.json +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/lines.ts +2 -8
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/protocol.ts +0 -6
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/skill-source.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/train-error.ts +4 -4
- package/runtime/sdk-runner/runner.json +1 -1
- package/runtime/sdk-runner/src/approvals.ts +2 -2
- package/runtime/sdk-runner/src/automation-command.ts +2 -2
- package/runtime/sdk-runner/src/calls.ts +0 -6
- package/runtime/sdk-runner/src/errors.ts +1 -1
- package/runtime/sdk-runner/src/link.ts +1 -1
- package/runtime/sdk-runner/src/runner.ts +2 -2
- package/runtime/sdk-runner/src/session-watch.ts +5 -34
- package/runtime/sdk-runner/src/task-state.ts +1 -1
- package/runtime/sdk-runner/src/tool-proxy.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +0 -40
|
@@ -22,12 +22,7 @@ import {
|
|
|
22
22
|
} from '../agents/map.js';
|
|
23
23
|
import { setKeyMap } from '../agents/keys.js';
|
|
24
24
|
import { STATIONS, type StationName } from '@metro-labs/core/station-names';
|
|
25
|
-
|
|
26
|
-
interface StationTarget {
|
|
27
|
-
file: string;
|
|
28
|
-
fileEnv: string;
|
|
29
|
-
trainImport: string | null;
|
|
30
|
-
}
|
|
25
|
+
import { stationByName } from './registry.js';
|
|
31
26
|
|
|
32
27
|
export interface LoadedAccount {
|
|
33
28
|
station: StationName;
|
|
@@ -48,59 +43,15 @@ export interface LoadedAgent {
|
|
|
48
43
|
|
|
49
44
|
const METRO_DIR = join(homedir(), '.metro');
|
|
50
45
|
|
|
51
|
-
const
|
|
52
|
-
xmtp: {
|
|
53
|
-
file: 'xmtp-accounts.json',
|
|
54
|
-
fileEnv: 'XMTP_ACCOUNTS_FILE',
|
|
55
|
-
trainImport: '@metro-labs/xmtp/train',
|
|
56
|
-
},
|
|
57
|
-
'telegram-bot': {
|
|
58
|
-
file: 'telegram-bot-accounts.json',
|
|
59
|
-
fileEnv: 'TELEGRAM_BOT_ACCOUNTS_FILE',
|
|
60
|
-
trainImport: '@metro-labs/telegram-bot/train',
|
|
61
|
-
},
|
|
62
|
-
'telegram': {
|
|
63
|
-
file: 'telegram-accounts.json',
|
|
64
|
-
fileEnv: 'TELEGRAM_ACCOUNTS_FILE',
|
|
65
|
-
trainImport: '@metro-labs/telegram/train',
|
|
66
|
-
},
|
|
67
|
-
'discord-bot': {
|
|
68
|
-
file: 'discord-bot-accounts.json',
|
|
69
|
-
fileEnv: 'DISCORD_BOT_ACCOUNTS_FILE',
|
|
70
|
-
trainImport: '@metro-labs/discord-bot/train',
|
|
71
|
-
},
|
|
72
|
-
whatsapp: {
|
|
73
|
-
file: 'whatsapp-accounts.json',
|
|
74
|
-
fileEnv: 'WHATSAPP_ACCOUNTS_FILE',
|
|
75
|
-
trainImport: '@metro-labs/whatsapp/train',
|
|
76
|
-
},
|
|
77
|
-
threema: {
|
|
78
|
-
file: 'threema-accounts.json',
|
|
79
|
-
fileEnv: 'THREEMA_ACCOUNTS_FILE',
|
|
80
|
-
trainImport: '@metro-labs/threema/train',
|
|
81
|
-
},
|
|
82
|
-
outlook: {
|
|
83
|
-
file: 'outlook-accounts.json',
|
|
84
|
-
fileEnv: 'OUTLOOK_ACCOUNTS_FILE',
|
|
85
|
-
trainImport: '@metro-labs/outlook/train',
|
|
86
|
-
},
|
|
87
|
-
gmail: {
|
|
88
|
-
file: 'gmail-accounts.json',
|
|
89
|
-
fileEnv: 'GMAIL_ACCOUNTS_FILE',
|
|
90
|
-
trainImport: '@metro-labs/gmail/train',
|
|
91
|
-
},
|
|
92
|
-
webhook: {
|
|
93
|
-
file: 'webhook-accounts.json',
|
|
94
|
-
fileEnv: 'WEBHOOK_ACCOUNTS_FILE',
|
|
95
|
-
trainImport: null,
|
|
96
|
-
},
|
|
97
|
-
};
|
|
46
|
+
const STATION_SET = new Set<string>(STATIONS);
|
|
98
47
|
|
|
99
48
|
export function accountFilePath(station: StationName): string {
|
|
100
|
-
|
|
101
|
-
return process.env[target.fileEnv] ?? join(METRO_DIR, target.file);
|
|
49
|
+
return process.env[`${station.toUpperCase().replace(/-/g, '_')}_ACCOUNTS_FILE`] ?? join(METRO_DIR, `${station}-accounts.json`);
|
|
102
50
|
}
|
|
103
51
|
|
|
52
|
+
const trainImport = (station: StationName): string | null =>
|
|
53
|
+
stationByName(station)?.hasTrain === false ? null : `@metro-labs/${station}/train`;
|
|
54
|
+
|
|
104
55
|
export type StationSource = () => Promise<LoadedAgent[]>;
|
|
105
56
|
|
|
106
57
|
export const MOVABLE_STATIONS = new Set<StationName>(
|
|
@@ -154,7 +105,7 @@ function writeStations(list: LoadedAgent[]): WrittenStations {
|
|
|
154
105
|
for (const agent of list) {
|
|
155
106
|
names[agent.id] = agent.name;
|
|
156
107
|
for (const a of agent.accounts) {
|
|
157
|
-
if (!(a.station
|
|
108
|
+
if (!STATION_SET.has(a.station)) {
|
|
158
109
|
log.error(
|
|
159
110
|
{ station: a.station, id: a.id },
|
|
160
111
|
'unknown station — this metro is older or newer than the row; skipping it',
|
|
@@ -185,9 +136,9 @@ function writeStations(list: LoadedAgent[]): WrittenStations {
|
|
|
185
136
|
writeSecure(path, content);
|
|
186
137
|
changed.push(station);
|
|
187
138
|
}
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
190
|
-
writeIfChanged(trainStubPath(station), `import '${
|
|
139
|
+
const train = trainImport(station);
|
|
140
|
+
if (train !== null)
|
|
141
|
+
writeIfChanged(trainStubPath(station), `import '${train}';\n`);
|
|
191
142
|
active.set(station, accts.length);
|
|
192
143
|
}
|
|
193
144
|
return { active, changed };
|
|
@@ -197,9 +148,9 @@ const BLANKED = '[]\n';
|
|
|
197
148
|
|
|
198
149
|
function pruneStations(present: Map<StationName, number>): StationName[] {
|
|
199
150
|
const removed: StationName[] = [];
|
|
200
|
-
for (const station of
|
|
151
|
+
for (const station of STATIONS) {
|
|
201
152
|
if (present.has(station)) continue;
|
|
202
|
-
const hasTrain =
|
|
153
|
+
const hasTrain = trainImport(station) !== null;
|
|
203
154
|
const stub = trainStubPath(station);
|
|
204
155
|
const stubGone = !hasTrain || !existsSync(stub);
|
|
205
156
|
const file = currentText(accountFilePath(station));
|
|
@@ -44,10 +44,6 @@ export class TrainSupervisor {
|
|
|
44
44
|
|
|
45
45
|
constructor(private dir: string = trainsDir()) {}
|
|
46
46
|
|
|
47
|
-
running(): string[] {
|
|
48
|
-
return [...this.trains.keys()];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
47
|
onTrainEvent(handler: (event: TrainEvent, train: string) => void): void {
|
|
52
48
|
this.onEvent = handler;
|
|
53
49
|
}
|
|
@@ -89,13 +85,7 @@ export class TrainSupervisor {
|
|
|
89
85
|
clearTimeout(timer);
|
|
90
86
|
this.reloadTimers.delete(name);
|
|
91
87
|
}
|
|
92
|
-
|
|
93
|
-
if (t.restartTimer) {
|
|
94
|
-
clearTimeout(t.restartTimer);
|
|
95
|
-
t.restartTimer = null;
|
|
96
|
-
}
|
|
97
|
-
failAllPending(t.pending, `train '${name}' stopped`);
|
|
98
|
-
const wait = killGracefully(t.proc);
|
|
88
|
+
const wait = this.halt(t, `train '${name}' stopped`);
|
|
99
89
|
if (wait) await wait;
|
|
100
90
|
this.trains.delete(name);
|
|
101
91
|
log.info({ name }, 'train: stopped, last account detached');
|
|
@@ -133,13 +123,7 @@ export class TrainSupervisor {
|
|
|
133
123
|
this.reloadTimers.clear();
|
|
134
124
|
const tasks: Promise<unknown>[] = [];
|
|
135
125
|
for (const t of this.trains.values()) {
|
|
136
|
-
|
|
137
|
-
if (t.restartTimer) {
|
|
138
|
-
clearTimeout(t.restartTimer);
|
|
139
|
-
t.restartTimer = null;
|
|
140
|
-
}
|
|
141
|
-
failAllPending(t.pending, 'train shutting down');
|
|
142
|
-
const wait = killGracefully(t.proc);
|
|
126
|
+
const wait = this.halt(t, 'train shutting down');
|
|
143
127
|
if (wait) tasks.push(wait);
|
|
144
128
|
}
|
|
145
129
|
await Promise.all(tasks);
|
|
@@ -148,13 +132,7 @@ export class TrainSupervisor {
|
|
|
148
132
|
async restart(name: string): Promise<void> {
|
|
149
133
|
const t = this.trains.get(name);
|
|
150
134
|
if (!t) throw new Error(`no train named '${name}'`);
|
|
151
|
-
|
|
152
|
-
clearTimeout(t.restartTimer);
|
|
153
|
-
t.restartTimer = null;
|
|
154
|
-
}
|
|
155
|
-
failAllPending(t.pending, `train '${name}' restarting`);
|
|
156
|
-
t.stopped = true;
|
|
157
|
-
const wait = killGracefully(t.proc);
|
|
135
|
+
const wait = this.halt(t, `train '${name}' restarting`);
|
|
158
136
|
if (wait) await wait;
|
|
159
137
|
t.stopped = false;
|
|
160
138
|
t.failCount = 0;
|
|
@@ -162,6 +140,16 @@ export class TrainSupervisor {
|
|
|
162
140
|
this.spawn(t);
|
|
163
141
|
}
|
|
164
142
|
|
|
143
|
+
private halt(t: TrainState, reason: string): Promise<unknown> | null {
|
|
144
|
+
t.stopped = true;
|
|
145
|
+
if (t.restartTimer) {
|
|
146
|
+
clearTimeout(t.restartTimer);
|
|
147
|
+
t.restartTimer = null;
|
|
148
|
+
}
|
|
149
|
+
failAllPending(t.pending, reason);
|
|
150
|
+
return killGracefully(t.proc);
|
|
151
|
+
}
|
|
152
|
+
|
|
165
153
|
async call(
|
|
166
154
|
name: string,
|
|
167
155
|
action: string,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readJson } from '@metro-labs/core/secure-fs';
|
|
2
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
2
3
|
import { accountFilePath } from './materialize.js';
|
|
3
4
|
|
|
4
5
|
export interface ThreemaCallback {
|
|
@@ -11,11 +12,10 @@ const str = (v: unknown): string | undefined =>
|
|
|
11
12
|
typeof v === 'string' && v !== '' ? v : undefined;
|
|
12
13
|
|
|
13
14
|
function toCallback(row: unknown): ThreemaCallback | null {
|
|
14
|
-
if (
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const callbackToken = str(rec.callbackToken);
|
|
15
|
+
if (!isRecord(row)) return null;
|
|
16
|
+
const id = str(row.id);
|
|
17
|
+
const callbackId = str(row.callbackId);
|
|
18
|
+
const callbackToken = str(row.callbackToken);
|
|
19
19
|
if (id === undefined || callbackId === undefined || callbackToken === undefined) return null;
|
|
20
20
|
return { id, callbackId, callbackToken };
|
|
21
21
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { Endpoint } from '@metro-labs/core/endpoints';
|
|
2
2
|
import { timingSafeEqual } from 'node:crypto';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
3
|
import { readJson } from '@metro-labs/core/secure-fs';
|
|
4
|
+
import { accountFilePath } from './materialize.js';
|
|
6
5
|
|
|
7
6
|
interface AccountRecord {
|
|
8
7
|
id?: unknown;
|
|
@@ -12,10 +11,6 @@ interface AccountRecord {
|
|
|
12
11
|
createdAt?: unknown;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
const accountsFile = (): string =>
|
|
16
|
-
process.env.WEBHOOK_ACCOUNTS_FILE ??
|
|
17
|
-
join(homedir(), '.metro', 'webhook-accounts.json');
|
|
18
|
-
|
|
19
14
|
const str = (v: unknown): string | undefined =>
|
|
20
15
|
typeof v === 'string' && v !== '' ? v : undefined;
|
|
21
16
|
|
|
@@ -32,7 +27,7 @@ function toEndpoint(raw: AccountRecord): Endpoint | null {
|
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
export function listEndpoints(): Endpoint[] {
|
|
35
|
-
const raw = readJson<AccountRecord[]>(
|
|
30
|
+
const raw = readJson<AccountRecord[]>(accountFilePath('webhook'), [], {
|
|
36
31
|
warn: 'webhook-accounts.json: malformed, ignoring',
|
|
37
32
|
});
|
|
38
33
|
if (!Array.isArray(raw)) return [];
|
|
@@ -114,12 +114,8 @@ export class Call {
|
|
|
114
114
|
else if (raw.kind === 'leave' && fromCaller) this.hangUp('the caller hung up');
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
hangUp(reason: string): void {
|
|
118
|
-
this.end(reason);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
117
|
leave(reason: string): Promise<void> {
|
|
122
|
-
this.
|
|
118
|
+
this.hangUp(reason);
|
|
123
119
|
return this.left;
|
|
124
120
|
}
|
|
125
121
|
|
|
@@ -149,7 +145,7 @@ export class Call {
|
|
|
149
145
|
return this.deps.train('callSignal', { line: this.start.line, signal });
|
|
150
146
|
}
|
|
151
147
|
|
|
152
|
-
|
|
148
|
+
hangUp(reason: string): void {
|
|
153
149
|
if (this.over) return;
|
|
154
150
|
this.over = true;
|
|
155
151
|
if (this.timer !== null) clearTimeout(this.timer);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { errMsg, log } from '@metro-labs/core/log';
|
|
3
3
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
4
|
+
import { stringOf } from '@metro-labs/http/api-http';
|
|
4
5
|
import type { TrainEvent } from '@metro-labs/core/trains/protocol';
|
|
5
6
|
import { agentIdForLine, allowlistForLine, lineReceives, mayApprove, senderPermitted } from '../agents/map.js';
|
|
6
7
|
import { Call, trainCall, type CallStart } from './call.js';
|
|
@@ -22,10 +23,9 @@ const INVITES_MAX = 256;
|
|
|
22
23
|
const makeCall: MakeCall = (start, cfg, onOver) => new Call(start, cfg, onOver);
|
|
23
24
|
|
|
24
25
|
const record = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
|
|
25
|
-
const str = (value: unknown): string => (typeof value === 'string' ? value : '');
|
|
26
26
|
|
|
27
|
-
const contentTypeOf = (env: TrainEvent): string =>
|
|
28
|
-
const sourceIdOf = (env: TrainEvent): string =>
|
|
27
|
+
const contentTypeOf = (env: TrainEvent): string => stringOf(record(env.payload).contentType);
|
|
28
|
+
const sourceIdOf = (env: TrainEvent): string => stringOf(env.message_id) || stringOf(env.id) || randomUUID();
|
|
29
29
|
|
|
30
30
|
export const isCallEvent = (env: TrainEvent): boolean => CALL_TYPES.has(contentTypeOf(env));
|
|
31
31
|
|
|
@@ -46,16 +46,16 @@ function decline(line: string, callId: string): void {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function ringing(env: TrainEvent): boolean {
|
|
49
|
-
const sent = Date.parse(
|
|
49
|
+
const sent = Date.parse(stringOf(env.ts));
|
|
50
50
|
const age = Date.now() - sent;
|
|
51
51
|
return Number.isFinite(age) && age >= -5_000 && age < RING_MS;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function invite(env: TrainEvent, body: Record<string, unknown>, create: MakeCall): void {
|
|
55
|
-
const line =
|
|
56
|
-
const from =
|
|
57
|
-
const callId =
|
|
58
|
-
const callerPeer =
|
|
55
|
+
const line = stringOf(env.line);
|
|
56
|
+
const from = stringOf(env.from);
|
|
57
|
+
const callId = stringOf(body.callId);
|
|
58
|
+
const callerPeer = stringOf(body.from);
|
|
59
59
|
const key = JSON.stringify([line, from, callId]);
|
|
60
60
|
if (callId === '' || callerPeer === '' || invites.has(key)) return;
|
|
61
61
|
if (!ringing(env)) {
|
|
@@ -85,16 +85,16 @@ function invite(env: TrainEvent, body: Record<string, unknown>, create: MakeCall
|
|
|
85
85
|
|
|
86
86
|
function authenticated(env: TrainEvent): boolean {
|
|
87
87
|
if (env.sender_verified === false || (env.station !== undefined && env.station !== 'xmtp')) return false;
|
|
88
|
-
const line =
|
|
88
|
+
const line = stringOf(env.line);
|
|
89
89
|
const match = /^metro:\/\/xmtp\/([^/]+)\/[^/]+$/.exec(line);
|
|
90
|
-
return match !== null &&
|
|
90
|
+
return match !== null && stringOf(env.from).startsWith(`metro://xmtp/${String(match[1])}/user/`) && stringOf(env.from).split('/').length === 6;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export function onCallEvent(env: TrainEvent, train = 'xmtp', create: MakeCall = makeCall): void {
|
|
94
94
|
if (train !== 'xmtp' || !authenticated(env)) return;
|
|
95
95
|
const body = record(record(env.payload).call);
|
|
96
96
|
if (contentTypeOf(env) === 'callInvite') invite(env, body, create);
|
|
97
|
-
else if (contentTypeOf(env) === 'callSignal' && current !== null && sameCaller(current,
|
|
97
|
+
else if (contentTypeOf(env) === 'callSignal' && current !== null && sameCaller(current, stringOf(env.line), stringOf(env.from)))
|
|
98
98
|
current.signalled(body);
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -117,7 +117,7 @@ function leftoversOf(result: unknown): Leftover[] {
|
|
|
117
117
|
const rows = isRecord(result) && Array.isArray(result.calls) ? result.calls : [];
|
|
118
118
|
return rows
|
|
119
119
|
.map((row) => record(row))
|
|
120
|
-
.map((row) => ({ line:
|
|
120
|
+
.map((row) => ({ line: stringOf(row.line), callId: stringOf(row.callId), peer: stringOf(row.peer) }))
|
|
121
121
|
.filter((row) => row.line !== '' && row.callId !== '' && row.peer !== '');
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
outgoingFiles,
|
|
13
13
|
type OutgoingFile,
|
|
14
14
|
} from './send-files.js';
|
|
15
|
-
import { makeStation, respond, type
|
|
15
|
+
import { makeStation, respond, type StationHandler } from '@metro-labs/core/stations/station-runtime';
|
|
16
16
|
import { readProfile, setProfile } from './profile.js';
|
|
17
17
|
import { discordMembers } from './members.js';
|
|
18
18
|
import { listChannels } from './channels.js';
|
|
@@ -28,30 +28,14 @@ async function sendMessage(
|
|
|
28
28
|
body: Record<string, unknown>,
|
|
29
29
|
files: OutgoingFile[],
|
|
30
30
|
): Promise<{ id: string; delivered: string[] }> {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
accountId,
|
|
34
|
-
'POST',
|
|
35
|
-
`/channels/${channel}/messages`,
|
|
36
|
-
body,
|
|
37
|
-
);
|
|
38
|
-
return { id: plain.id, delivered: [] };
|
|
39
|
-
}
|
|
31
|
+
const path = `/channels/${channel}/messages`;
|
|
32
|
+
if (files.length === 0) return { id: (await rest<{ id: string }>(accountId, 'POST', path, body)).id, delivered: [] };
|
|
40
33
|
const form = new FormData();
|
|
41
34
|
form.append('payload_json', JSON.stringify(body));
|
|
42
35
|
const delivered = await appendFiles(form, files);
|
|
43
|
-
|
|
44
|
-
accountId,
|
|
45
|
-
'POST',
|
|
46
|
-
`/channels/${channel}/messages`,
|
|
47
|
-
form,
|
|
48
|
-
true,
|
|
49
|
-
);
|
|
50
|
-
return { id: res.id, delivered };
|
|
36
|
+
return { id: (await rest<{ id: string }>(accountId, 'POST', path, form, true)).id, delivered };
|
|
51
37
|
}
|
|
52
38
|
|
|
53
|
-
export type { CallMsg };
|
|
54
|
-
|
|
55
39
|
async function send(id: string, args: Record<string, unknown>): Promise<void> {
|
|
56
40
|
const { line, text, replyTo, account } = args as {
|
|
57
41
|
line: string;
|
|
@@ -198,9 +182,7 @@ async function listMembers(
|
|
|
198
182
|
}
|
|
199
183
|
|
|
200
184
|
const HANDLERS: Record<string, StationHandler> = {
|
|
201
|
-
accounts:
|
|
202
|
-
listAccounts(id);
|
|
203
|
-
},
|
|
185
|
+
accounts: listAccounts,
|
|
204
186
|
groupCreate: groupCreateHandler,
|
|
205
187
|
groupAddMembers: groupAddHandler,
|
|
206
188
|
groupRemoveMembers: groupRemoveHandler,
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
saveBufferToCache,
|
|
3
|
-
assertContentLength,
|
|
4
|
-
} from '@metro-labs/core/stations/attachments';
|
|
5
|
-
import type { SavedAttachment } from '@metro-labs/core/stations/attachments';
|
|
6
|
-
|
|
7
|
-
export type { SavedAttachment };
|
|
1
|
+
import { assertContentLength, saveBufferToCache, type SavedAttachment } from '@metro-labs/core/stations/attachments';
|
|
8
2
|
|
|
9
3
|
export interface DiscordAttachmentRef {
|
|
10
4
|
url: string;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type ChannelEntry,
|
|
5
5
|
type ChannelSnapshot,
|
|
6
6
|
} from '@metro-labs/core/stations/channel-directory';
|
|
7
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
7
8
|
import { respond } from '@metro-labs/core/stations/station-runtime';
|
|
8
9
|
import { TrainError } from '@metro-labs/core/train-error';
|
|
9
10
|
import { ChannelType, PermissionFlagsBits } from 'discord.js';
|
|
@@ -42,8 +43,7 @@ function visible(channel: DiscordChannelMetadata): boolean {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
function inaccessible(error: unknown): boolean {
|
|
45
|
-
return
|
|
46
|
-
(error.status === 403 || error.status === 404);
|
|
46
|
+
return isRecord(error) && (error.status === 403 || error.status === 404);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
class Snapshot {
|
|
@@ -22,21 +22,18 @@ function membersOf(args: Record<string, unknown>): string[] {
|
|
|
22
22
|
return raw.filter((v) => typeof v === 'string' && v.length > 0) as string[];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
async function
|
|
25
|
+
async function threadMembers(
|
|
26
26
|
rest: RestFn,
|
|
27
27
|
accountId: string,
|
|
28
28
|
threadId: string,
|
|
29
|
-
|
|
29
|
+
args: Record<string, unknown>,
|
|
30
|
+
method: 'PUT' | 'DELETE',
|
|
30
31
|
): Promise<MemberOutcome[]> {
|
|
31
32
|
const outcomes: MemberOutcome[] = [];
|
|
32
|
-
for (const userId of
|
|
33
|
+
for (const userId of membersOf(args)) {
|
|
33
34
|
try {
|
|
34
|
-
await rest(
|
|
35
|
-
|
|
36
|
-
'PUT',
|
|
37
|
-
`/channels/${threadId}/thread-members/${userId}`,
|
|
38
|
-
);
|
|
39
|
-
outcomes.push({ id: userId, status: 'added' });
|
|
35
|
+
await rest(accountId, method, `/channels/${threadId}/thread-members/${userId}`);
|
|
36
|
+
outcomes.push({ id: userId, status: method === 'PUT' ? 'added' : 'removed' });
|
|
40
37
|
} catch (e) {
|
|
41
38
|
outcomes.push({ id: userId, status: 'failed', reason: errMsg(e) });
|
|
42
39
|
}
|
|
@@ -60,12 +57,7 @@ export async function groupCreate(
|
|
|
60
57
|
`/channels/${parentChannelId}/threads`,
|
|
61
58
|
{ name, type, auto_archive_duration: AUTO_ARCHIVE },
|
|
62
59
|
);
|
|
63
|
-
const members = await
|
|
64
|
-
rest,
|
|
65
|
-
accountId,
|
|
66
|
-
thread.id,
|
|
67
|
-
membersOf(args),
|
|
68
|
-
);
|
|
60
|
+
const members = await threadMembers(rest, accountId, thread.id, args, 'PUT');
|
|
69
61
|
return {
|
|
70
62
|
capability: { supported: true },
|
|
71
63
|
line: lineOf(accountId, thread.id),
|
|
@@ -82,12 +74,7 @@ export async function groupAddMembers(
|
|
|
82
74
|
line: string,
|
|
83
75
|
args: Record<string, unknown>,
|
|
84
76
|
): Promise<GroupResult> {
|
|
85
|
-
const members = await
|
|
86
|
-
rest,
|
|
87
|
-
accountId,
|
|
88
|
-
threadId,
|
|
89
|
-
membersOf(args),
|
|
90
|
-
);
|
|
77
|
+
const members = await threadMembers(rest, accountId, threadId, args, 'PUT');
|
|
91
78
|
return { capability: { supported: true }, line, id: threadId, members };
|
|
92
79
|
}
|
|
93
80
|
|
|
@@ -98,18 +85,6 @@ export async function groupRemoveMembers(
|
|
|
98
85
|
line: string,
|
|
99
86
|
args: Record<string, unknown>,
|
|
100
87
|
): Promise<GroupResult> {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
await rest(
|
|
105
|
-
accountId,
|
|
106
|
-
'DELETE',
|
|
107
|
-
`/channels/${threadId}/thread-members/${userId}`,
|
|
108
|
-
);
|
|
109
|
-
outcomes.push({ id: userId, status: 'removed' });
|
|
110
|
-
} catch (e) {
|
|
111
|
-
outcomes.push({ id: userId, status: 'failed', reason: errMsg(e) });
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return { capability: { supported: true }, line, id: threadId, members: outcomes };
|
|
88
|
+
const members = await threadMembers(rest, accountId, threadId, args, 'DELETE');
|
|
89
|
+
return { capability: { supported: true }, line, id: threadId, members };
|
|
115
90
|
}
|
|
@@ -70,7 +70,7 @@ async function bootAccount(cfg: AccountConfig): Promise<void> {
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
client.on(Events.MessageReactionAdd, (r, u) => {
|
|
73
|
-
|
|
73
|
+
(async () => {
|
|
74
74
|
try {
|
|
75
75
|
if (r.partial) await r.fetch();
|
|
76
76
|
if (u.partial) await u.fetch();
|
|
@@ -85,18 +85,12 @@ async function bootAccount(cfg: AccountConfig): Promise<void> {
|
|
|
85
85
|
});
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
client.on(Events.MessageUpdate, (_old,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
onEdit(accountId, _new.partial ? await _new.fetch() : _new);
|
|
92
|
-
} catch (err) {
|
|
93
|
-
process.stderr.write(
|
|
94
|
-
`discord-bot[${accountId}] message update fetch failed: ${errMsg(err)}\n`,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
88
|
+
client.on(Events.MessageUpdate, (_old, updated) => {
|
|
89
|
+
(async () => {
|
|
90
|
+
onEdit(accountId, updated.partial ? await updated.fetch() : updated);
|
|
97
91
|
})().catch((err: unknown) => {
|
|
98
92
|
process.stderr.write(
|
|
99
|
-
`discord-bot[${accountId}] message update
|
|
93
|
+
`discord-bot[${accountId}] message update fetch failed: ${errMsg(err)}\n`,
|
|
100
94
|
);
|
|
101
95
|
});
|
|
102
96
|
});
|
|
@@ -97,38 +97,6 @@ export function mapRecipients(users: DiscordUser[]): MetroMember[] {
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
async function fetchRoles(
|
|
101
|
-
accountId: string,
|
|
102
|
-
guildId: string,
|
|
103
|
-
): Promise<Map<string, RoleInfo>> {
|
|
104
|
-
try {
|
|
105
|
-
const roles = await rest<DiscordRole[]>(
|
|
106
|
-
accountId,
|
|
107
|
-
'GET',
|
|
108
|
-
`/guilds/${guildId}/roles`,
|
|
109
|
-
);
|
|
110
|
-
return indexRoles(roles);
|
|
111
|
-
} catch {
|
|
112
|
-
return new Map();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function fetchOwnerId(
|
|
117
|
-
accountId: string,
|
|
118
|
-
guildId: string,
|
|
119
|
-
): Promise<string | undefined> {
|
|
120
|
-
try {
|
|
121
|
-
const g = await rest<{ owner_id?: string }>(
|
|
122
|
-
accountId,
|
|
123
|
-
'GET',
|
|
124
|
-
`/guilds/${guildId}`,
|
|
125
|
-
);
|
|
126
|
-
return g.owner_id;
|
|
127
|
-
} catch {
|
|
128
|
-
return undefined;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
100
|
interface Collected {
|
|
133
101
|
raw: DiscordGuildMember[];
|
|
134
102
|
exhausted: boolean;
|
|
@@ -168,8 +136,12 @@ export async function listGuildMembers(
|
|
|
168
136
|
limit?: number,
|
|
169
137
|
): Promise<MemberList> {
|
|
170
138
|
const cap = Math.min(Math.max(1, limit ?? PAGE), 10_000);
|
|
171
|
-
const roles = await
|
|
172
|
-
|
|
139
|
+
const roles = await rest<DiscordRole[]>(accountId, 'GET', `/guilds/${guildId}/roles`)
|
|
140
|
+
.then(indexRoles)
|
|
141
|
+
.catch(() => new Map<string, RoleInfo>());
|
|
142
|
+
const ownerId = await rest<{ owner_id?: string }>(accountId, 'GET', `/guilds/${guildId}`)
|
|
143
|
+
.then((g) => g.owner_id)
|
|
144
|
+
.catch(() => undefined);
|
|
173
145
|
try {
|
|
174
146
|
const { raw, exhausted } = await paginate(accountId, guildId, cap);
|
|
175
147
|
const members = mapGuildMembers(raw, roles, ownerId);
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type ProfileChange,
|
|
10
10
|
} from '@metro-labs/core/stations/profile';
|
|
11
11
|
import type { SenderProfile } from '@metro-labs/core/stations/sender-profile';
|
|
12
|
-
import { accountFor, accounts } from './accounts.js';
|
|
12
|
+
import { accountFor, accounts, type Account } from './accounts.js';
|
|
13
13
|
import { respond } from '@metro-labs/core/stations/station-runtime';
|
|
14
14
|
|
|
15
15
|
export async function applyProfile(
|
|
@@ -32,29 +32,26 @@ export async function applyProfile(
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function accountOf(args: Record<string, unknown>): Account {
|
|
36
|
+
const accountId = accountFor({ account: args.account as string | undefined });
|
|
37
|
+
const acct = accounts.get(accountId);
|
|
38
|
+
if (!acct) throw new Error(`unknown account '${accountId}'`);
|
|
39
|
+
return acct;
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
export async function setProfile(
|
|
36
43
|
id: string,
|
|
37
44
|
args: Record<string, unknown>,
|
|
38
45
|
): Promise<void> {
|
|
39
|
-
const
|
|
40
|
-
const acct = accounts.get(accountId);
|
|
41
|
-
if (!acct) {
|
|
42
|
-
respond(id, { error: `unknown account '${accountId}'` });
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
46
|
+
const acct = accountOf(args);
|
|
45
47
|
const change = parseProfileChange(args);
|
|
46
48
|
await applyProfile(acct.client, change);
|
|
47
|
-
const result: ProfileApplied = { account:
|
|
49
|
+
const result: ProfileApplied = { account: acct.cfg.id, applied: fieldsOf(change) };
|
|
48
50
|
respond(id, { result });
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export async function readProfile(id: string, args: Record<string, unknown>): Promise<void> {
|
|
52
|
-
const
|
|
53
|
-
const acct = accounts.get(accountId);
|
|
54
|
-
if (!acct) {
|
|
55
|
-
respond(id, { error: `unknown account '${accountId}'` });
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
54
|
+
const acct = accountOf(args);
|
|
58
55
|
const user = typeof args.user === 'string' ? args.user.trim() : '';
|
|
59
56
|
if (!/^\d+$/.test(user)) throw new TrainError('discord_user_required', 'profile needs the Discord id of the person', { retryable: false });
|
|
60
57
|
const found = await acct.client.users.fetch(user);
|
|
@@ -32,9 +32,7 @@ export async function appendFiles(
|
|
|
32
32
|
files: OutgoingFile[],
|
|
33
33
|
): Promise<string[]> {
|
|
34
34
|
const delivered: string[] = [];
|
|
35
|
-
for (
|
|
36
|
-
const file = files[i];
|
|
37
|
-
if (!file) continue;
|
|
35
|
+
for (const [i, file] of files.entries()) {
|
|
38
36
|
await appendFile(form, `files[${i}]`, file.path, file.name);
|
|
39
37
|
delivered.push(file.kind);
|
|
40
38
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
1
2
|
import { API } from './api-base.js';
|
|
2
3
|
|
|
3
4
|
export class DiscordTokenError extends Error {}
|
|
@@ -25,9 +26,7 @@ function botFetch(token: string, path: string): Promise<Response> {
|
|
|
25
26
|
async function readJson(res: Response): Promise<Record<string, unknown>> {
|
|
26
27
|
try {
|
|
27
28
|
const body: unknown = await res.json();
|
|
28
|
-
return
|
|
29
|
-
? (body as Record<string, unknown>)
|
|
30
|
-
: {};
|
|
29
|
+
return isRecord(body) ? body : {};
|
|
31
30
|
} catch {
|
|
32
31
|
throw new DiscordTokenError('Discord returned a response Metro could not read');
|
|
33
32
|
}
|