@stage-labs/metro 0.1.0-beta.201 → 0.1.0-beta.203
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/marketplace/plugin/orchestrator.md +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/core/src/stations/types.ts +3 -1
- 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 +8 -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/call-tools.ts +19 -1
- 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/mcp/tool-schemas.ts +14 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/typing.ts +46 -0
- 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 +19 -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/telegram-bot/src/station.ts +2 -0
- 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 +14 -6
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +5 -0
- 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/whatsapp/src/station.ts +2 -0
- 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
|
@@ -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 {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from '@metro-labs/core/stations/station-runtime';
|
|
9
9
|
import { accountFor, accounts, targetOf } from './accounts.js';
|
|
10
10
|
import type { UserClient } from './client.js';
|
|
11
|
-
import {
|
|
11
|
+
import { messagingAliases } from '@metro-labs/core/stations/messaging-normalize';
|
|
12
12
|
import {
|
|
13
13
|
sendAttachments,
|
|
14
14
|
type CanonicalAttachment,
|
|
@@ -251,6 +251,6 @@ export function makeHandleCall(
|
|
|
251
251
|
set_profile: makeSetProfile(clientFor),
|
|
252
252
|
profile: makeReadProfile(clientFor),
|
|
253
253
|
},
|
|
254
|
-
normalize:
|
|
254
|
+
normalize: messagingAliases(),
|
|
255
255
|
});
|
|
256
256
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Chat, Message, Peer, User } from '@mtcute/bun';
|
|
2
2
|
import { lineOf } from './accounts.js';
|
|
3
|
-
import { mintId } from '
|
|
3
|
+
import { mintId } from '@metro-labs/core/stations/station-runtime';
|
|
4
4
|
import { isDownloadable, pendingDescriptorOf } from './attachments.js';
|
|
5
5
|
|
|
6
6
|
const isUser = (peer: Peer): peer is User => peer.type === 'user';
|
|
@@ -72,7 +72,6 @@ export function envelope(
|
|
|
72
72
|
? [pendingDescriptorOf(media)]
|
|
73
73
|
: undefined;
|
|
74
74
|
return {
|
|
75
|
-
kind: 'inbound',
|
|
76
75
|
id: mintId(),
|
|
77
76
|
ts: m.date.toISOString(),
|
|
78
77
|
station: 'telegram',
|
|
@@ -108,7 +107,6 @@ export interface ReactionInput {
|
|
|
108
107
|
|
|
109
108
|
export function reactionEnvelope(r: ReactionInput): Record<string, unknown> {
|
|
110
109
|
return {
|
|
111
|
-
kind: 'react',
|
|
112
110
|
id: mintId(),
|
|
113
111
|
ts: r.date.toISOString(),
|
|
114
112
|
station: 'telegram',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Message } from '@mtcute/bun';
|
|
2
2
|
import { errMsg, log } from '@metro-labs/core/log';
|
|
3
|
-
import { emit } from '
|
|
3
|
+
import { emit } from '@metro-labs/core/stations/station-runtime';
|
|
4
4
|
import { reportAttachment } from '@metro-labs/core/stations/train-events';
|
|
5
5
|
import { envelope, isOwnEcho } from './format.js';
|
|
6
6
|
import { downloadMedia, isDownloadable } from './attachments.js';
|
|
@@ -6,12 +6,11 @@ import type { UserClient } from './client.js';
|
|
|
6
6
|
export interface CanonicalAttachment {
|
|
7
7
|
kind?: string;
|
|
8
8
|
path?: string;
|
|
9
|
-
url?: string;
|
|
10
9
|
mime?: string;
|
|
11
10
|
name?: string;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const srcOf = (att: CanonicalAttachment): string => att.path ??
|
|
13
|
+
const srcOf = (att: CanonicalAttachment): string => att.path ?? '';
|
|
15
14
|
|
|
16
15
|
function isImage(att: CanonicalAttachment): boolean {
|
|
17
16
|
if (att.kind === 'image') return true;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { tl } from '@mtcute/bun';
|
|
2
2
|
import { getBarePeerId, getMarkedPeerId } from '@mtcute/bun';
|
|
3
3
|
import { errMsg } from '@metro-labs/core/log';
|
|
4
|
-
import { emit } from '
|
|
4
|
+
import { emit } from '@metro-labs/core/stations/station-runtime';
|
|
5
5
|
import { reactionEnvelope } from './format.js';
|
|
6
6
|
import type { UserClient } from './client.js';
|
|
7
7
|
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
1
|
import {
|
|
4
2
|
makeAccountStore,
|
|
5
3
|
resolveAccountId,
|
|
6
4
|
} from '@metro-labs/core/stations/account-store';
|
|
7
5
|
import { Line } from '@metro-labs/core/lines';
|
|
8
6
|
|
|
9
|
-
const ACCOUNTS_FILE =
|
|
10
|
-
process.env.TELEGRAM_BOT_ACCOUNTS_FILE ??
|
|
11
|
-
join(homedir(), '.metro', 'telegram-bot-accounts.json');
|
|
12
|
-
|
|
13
7
|
export interface AccountConfig {
|
|
14
8
|
id: string;
|
|
15
9
|
token: string;
|
|
@@ -17,20 +11,15 @@ export interface AccountConfig {
|
|
|
17
11
|
|
|
18
12
|
export const { loadAccounts } = makeAccountStore<AccountConfig>({
|
|
19
13
|
prefix: 'telegram-bot',
|
|
20
|
-
file: ACCOUNTS_FILE,
|
|
21
14
|
validate(raw, die) {
|
|
22
|
-
const seenId = new Set<string>();
|
|
23
15
|
const seenTok = new Set<string>();
|
|
24
16
|
for (const a of raw) {
|
|
25
|
-
if (!a.id) die('account missing id');
|
|
26
17
|
if (!a.token || typeof a.token !== 'string')
|
|
27
18
|
die(`account '${a.id}' missing token`);
|
|
28
|
-
if (seenId.has(a.id)) die(`duplicate account id '${a.id}'`);
|
|
29
19
|
if (seenTok.has(a.token))
|
|
30
20
|
die(
|
|
31
21
|
`account '${a.id}' reuses a token used by another account (409 on getUpdates)`,
|
|
32
22
|
);
|
|
33
|
-
seenId.add(a.id);
|
|
34
23
|
seenTok.add(a.token);
|
|
35
24
|
}
|
|
36
25
|
},
|
|
@@ -49,37 +38,15 @@ export async function tg<T>(
|
|
|
49
38
|
accountId: string,
|
|
50
39
|
method: string,
|
|
51
40
|
body: unknown,
|
|
52
|
-
timeoutMs = 30_000,
|
|
53
|
-
): Promise<T> {
|
|
54
|
-
const acct = accounts.get(accountId);
|
|
55
|
-
if (!acct) throw new Error(`unknown account '${accountId}'`);
|
|
56
|
-
const res = await fetch(`${acct.api}/${method}`, {
|
|
57
|
-
method: 'POST',
|
|
58
|
-
headers: { 'Content-Type': 'application/json' },
|
|
59
|
-
body: JSON.stringify(body),
|
|
60
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
61
|
-
});
|
|
62
|
-
const json = (await res.json()) as {
|
|
63
|
-
ok: boolean;
|
|
64
|
-
description?: string;
|
|
65
|
-
result?: T;
|
|
66
|
-
};
|
|
67
|
-
if (!json.ok)
|
|
68
|
-
throw new Error(`telegram-bot ${method}: ${json.description ?? 'unknown'}`);
|
|
69
|
-
return json.result as T;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export async function tgForm<T>(
|
|
73
|
-
accountId: string,
|
|
74
|
-
method: string,
|
|
75
|
-
form: FormData,
|
|
76
|
-
timeoutMs = 60_000,
|
|
41
|
+
timeoutMs = body instanceof FormData ? 60_000 : 30_000,
|
|
77
42
|
): Promise<T> {
|
|
78
43
|
const acct = accounts.get(accountId);
|
|
79
44
|
if (!acct) throw new Error(`unknown account '${accountId}'`);
|
|
45
|
+
const form = body instanceof FormData;
|
|
80
46
|
const res = await fetch(`${acct.api}/${method}`, {
|
|
81
47
|
method: 'POST',
|
|
82
|
-
|
|
48
|
+
...(form ? {} : { headers: { 'Content-Type': 'application/json' } }),
|
|
49
|
+
body: form ? body : JSON.stringify(body),
|
|
83
50
|
signal: AbortSignal.timeout(timeoutMs),
|
|
84
51
|
});
|
|
85
52
|
const json = (await res.json()) as {
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import { accounts, tg, targetOf } from './accounts.js';
|
|
2
|
-
import { respond } from './wire.js';
|
|
3
2
|
import { errMsg } from '@metro-labs/core/log';
|
|
4
|
-
import {
|
|
3
|
+
import { messagingAliases } from '@metro-labs/core/stations/messaging-normalize';
|
|
5
4
|
import {
|
|
6
5
|
adminMemberList,
|
|
7
6
|
inaccessibleMemberList,
|
|
8
7
|
type TgChatMember,
|
|
9
8
|
} from './members.js';
|
|
10
|
-
import {
|
|
11
|
-
makeStation,
|
|
12
|
-
type CallMsg,
|
|
13
|
-
type StationHandler,
|
|
14
|
-
} from '@metro-labs/core/stations/station-runtime';
|
|
9
|
+
import { makeStation, respond, type CallMsg, type StationHandler } from '@metro-labs/core/stations/station-runtime';
|
|
15
10
|
import { mediaKindOf } from './attachments.js';
|
|
16
11
|
import {
|
|
17
12
|
emitOutbound,
|
|
@@ -65,13 +60,10 @@ interface SendArgs {
|
|
|
65
60
|
line: string;
|
|
66
61
|
text: string;
|
|
67
62
|
replyTo?: string;
|
|
68
|
-
parseMode?: string;
|
|
69
|
-
buttons?: { text: string; url: string }[][];
|
|
70
63
|
account?: string;
|
|
71
64
|
attachments?: {
|
|
72
65
|
kind?: string;
|
|
73
66
|
path?: string;
|
|
74
|
-
url?: string;
|
|
75
67
|
mime?: string;
|
|
76
68
|
name?: string;
|
|
77
69
|
}[];
|
|
@@ -80,11 +72,8 @@ interface SendArgs {
|
|
|
80
72
|
|
|
81
73
|
type WireAttachment = NonNullable<SendArgs['attachments']>[number];
|
|
82
74
|
|
|
83
|
-
const attachmentSrc = (att: WireAttachment): string | undefined =>
|
|
84
|
-
att.path ?? att.url;
|
|
85
|
-
|
|
86
75
|
const attachmentKind = (att: WireAttachment): string =>
|
|
87
|
-
mediaKindOf(att.kind, att.mime,
|
|
76
|
+
mediaKindOf(att.kind, att.mime, att.path ?? att.name);
|
|
88
77
|
|
|
89
78
|
async function sendOneAttachment(
|
|
90
79
|
a: SendArgs,
|
|
@@ -96,10 +85,9 @@ async function sendOneAttachment(
|
|
|
96
85
|
if (!mf) throw new Error(`telegram-bot cannot send a '${kind}' attachment`);
|
|
97
86
|
const sent = await sendMedia(mf.method, mf.field, {
|
|
98
87
|
line: a.line,
|
|
99
|
-
path:
|
|
88
|
+
path: att.path,
|
|
100
89
|
caption,
|
|
101
90
|
replyTo: a.replyTo,
|
|
102
|
-
parseMode: a.parseMode,
|
|
103
91
|
account: a.account,
|
|
104
92
|
name: att.name,
|
|
105
93
|
});
|
|
@@ -144,13 +132,11 @@ async function send(id: string, args: Record<string, unknown>): Promise<void> {
|
|
|
144
132
|
await sendAttachments(id, a);
|
|
145
133
|
return;
|
|
146
134
|
}
|
|
147
|
-
const { line, text, replyTo,
|
|
135
|
+
const { line, text, replyTo, account } = a;
|
|
148
136
|
const { accountId, chatId, topicId } = targetOf(line, account);
|
|
149
137
|
const body: Record<string, unknown> = { chat_id: chatId, text };
|
|
150
138
|
if (topicId !== undefined) body.message_thread_id = topicId;
|
|
151
139
|
if (replyTo) body.reply_parameters = { message_id: Number(replyTo) };
|
|
152
|
-
if (parseMode) body.parse_mode = parseMode;
|
|
153
|
-
if (buttons) body.reply_markup = { inline_keyboard: buttons };
|
|
154
140
|
const sent = await tg<{ message_id: number }>(accountId, 'sendMessage', body);
|
|
155
141
|
finishSend(id, accountId, line, String(sent.message_id), text, replyTo);
|
|
156
142
|
}
|
|
@@ -171,12 +157,22 @@ async function react(id: string, args: Record<string, unknown>): Promise<void> {
|
|
|
171
157
|
respond(id, { result: { ok: true, account: accountId } });
|
|
172
158
|
}
|
|
173
159
|
|
|
160
|
+
async function typing(id: string, args: Record<string, unknown>): Promise<void> {
|
|
161
|
+
const { line, on, account } = args as { line: string; on?: boolean; account?: string };
|
|
162
|
+
const { accountId, chatId, topicId } = targetOf(line, account);
|
|
163
|
+
if (on !== false) {
|
|
164
|
+
const body: Record<string, unknown> = { chat_id: chatId, action: 'typing' };
|
|
165
|
+
if (topicId !== undefined) body.message_thread_id = topicId;
|
|
166
|
+
await tg(accountId, 'sendChatAction', body);
|
|
167
|
+
}
|
|
168
|
+
respond(id, { result: { ok: true, account: accountId } });
|
|
169
|
+
}
|
|
170
|
+
|
|
174
171
|
async function edit(id: string, args: Record<string, unknown>): Promise<void> {
|
|
175
|
-
const { line, messageId, text,
|
|
172
|
+
const { line, messageId, text, account } = args as {
|
|
176
173
|
line: string;
|
|
177
174
|
messageId: string;
|
|
178
175
|
text: string;
|
|
179
|
-
parseMode?: string;
|
|
180
176
|
account?: string;
|
|
181
177
|
};
|
|
182
178
|
const { accountId, chatId } = targetOf(line, account);
|
|
@@ -184,7 +180,6 @@ async function edit(id: string, args: Record<string, unknown>): Promise<void> {
|
|
|
184
180
|
chat_id: chatId,
|
|
185
181
|
message_id: Number(messageId),
|
|
186
182
|
text,
|
|
187
|
-
parse_mode: parseMode,
|
|
188
183
|
});
|
|
189
184
|
respond(id, { result: { ok: true, account: accountId } });
|
|
190
185
|
}
|
|
@@ -233,6 +228,7 @@ const HANDLERS: Record<string, StationHandler> = {
|
|
|
233
228
|
accounts: (id) => listAccounts(id),
|
|
234
229
|
send,
|
|
235
230
|
react,
|
|
231
|
+
typing,
|
|
236
232
|
edit,
|
|
237
233
|
delete: remove,
|
|
238
234
|
listMembers,
|
|
@@ -242,5 +238,5 @@ const HANDLERS: Record<string, StationHandler> = {
|
|
|
242
238
|
|
|
243
239
|
export const handleCall = makeStation({
|
|
244
240
|
handlers: HANDLERS,
|
|
245
|
-
normalize:
|
|
241
|
+
normalize: messagingAliases(),
|
|
246
242
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { accounts, lineOf } from './accounts.js';
|
|
2
2
|
import { reportAttachment } from '@metro-labs/core/stations/train-events';
|
|
3
|
-
import { mintId } from '
|
|
3
|
+
import { mintId } from '@metro-labs/core/stations/station-runtime';
|
|
4
4
|
import { mediaRefOf, saveTelegramMedia } from './attachments.js';
|
|
5
5
|
import type { TgMsg, TgReaction, TgReactionCount, TgUser } from './types.js';
|
|
6
6
|
|
|
@@ -80,7 +80,6 @@ export const fullName = (u: TgUser | undefined): string | undefined => {
|
|
|
80
80
|
export function envelope(accountId: string, m: TgMsg): Record<string, unknown> {
|
|
81
81
|
const { line } = lineForMsg(accountId, m);
|
|
82
82
|
return {
|
|
83
|
-
kind: 'inbound',
|
|
84
83
|
id: mintId(),
|
|
85
84
|
ts: new Date(m.date * 1000).toISOString(),
|
|
86
85
|
station: 'telegram-bot',
|
|
@@ -111,7 +110,6 @@ export function reactionEnvelope(
|
|
|
111
110
|
const added = newEmojis.filter((e) => !oldEmojis.includes(e));
|
|
112
111
|
if (!added.length) return null;
|
|
113
112
|
return {
|
|
114
|
-
kind: 'react',
|
|
115
113
|
id: mintId(),
|
|
116
114
|
ts: new Date(r.date * 1000).toISOString(),
|
|
117
115
|
station: 'telegram-bot',
|
|
@@ -141,7 +139,6 @@ export function reactionCountEnvelope(
|
|
|
141
139
|
const emoji = top.type.emoji ?? '';
|
|
142
140
|
if (!emoji) return null;
|
|
143
141
|
return {
|
|
144
|
-
kind: 'react',
|
|
145
142
|
id: mintId(),
|
|
146
143
|
ts: new Date(rc.date * 1000).toISOString(),
|
|
147
144
|
station: 'telegram-bot',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tg, targetOf } from './accounts.js';
|
|
2
2
|
import { selfUri } from '@metro-labs/core/stations/train-events';
|
|
3
|
-
import { emit, mintId, respond } from '
|
|
3
|
+
import { emit, mintId, respond } from '@metro-labs/core/stations/station-runtime';
|
|
4
4
|
import { appendFile } from '@metro-labs/core/stations/attachments';
|
|
5
5
|
|
|
6
6
|
export function emitOutbound(
|
|
@@ -11,18 +11,16 @@ export function emitOutbound(
|
|
|
11
11
|
replyTo?: string,
|
|
12
12
|
): void {
|
|
13
13
|
emit({
|
|
14
|
-
kind: 'outbound',
|
|
15
14
|
id: mintId(),
|
|
16
15
|
ts: new Date().toISOString(),
|
|
17
16
|
station: 'telegram-bot',
|
|
18
17
|
line,
|
|
19
|
-
from: selfUri(
|
|
18
|
+
from: selfUri(),
|
|
20
19
|
to: line,
|
|
21
20
|
message_id: messageId,
|
|
22
21
|
text,
|
|
23
22
|
reply_to: replyTo,
|
|
24
23
|
...(replyTo ? { event: { type: 'reply', replyTo } } : {}),
|
|
25
|
-
account: accountId,
|
|
26
24
|
payload: { account: accountId },
|
|
27
25
|
});
|
|
28
26
|
}
|
|
@@ -47,7 +45,6 @@ interface MediaArgs {
|
|
|
47
45
|
path: string;
|
|
48
46
|
caption?: string;
|
|
49
47
|
replyTo?: string;
|
|
50
|
-
parseMode?: string;
|
|
51
48
|
account?: string;
|
|
52
49
|
name?: string;
|
|
53
50
|
}
|
|
@@ -62,7 +59,6 @@ export async function sendMedia(
|
|
|
62
59
|
path,
|
|
63
60
|
caption,
|
|
64
61
|
replyTo,
|
|
65
|
-
parseMode,
|
|
66
62
|
account,
|
|
67
63
|
name: fileName,
|
|
68
64
|
} = args as unknown as MediaArgs;
|
|
@@ -71,7 +67,6 @@ export async function sendMedia(
|
|
|
71
67
|
form.append('chat_id', String(chatId));
|
|
72
68
|
if (topicId !== undefined) form.append('message_thread_id', String(topicId));
|
|
73
69
|
if (caption) form.append('caption', caption);
|
|
74
|
-
if (parseMode) form.append('parse_mode', parseMode);
|
|
75
70
|
if (replyTo)
|
|
76
71
|
form.append(
|
|
77
72
|
'reply_parameters',
|
|
@@ -79,7 +74,7 @@ export async function sendMedia(
|
|
|
79
74
|
);
|
|
80
75
|
const name = fileName ?? path.split('/').pop() ?? fieldName;
|
|
81
76
|
await appendFile(form, fieldName, path, name);
|
|
82
|
-
const r = await
|
|
77
|
+
const r = await tg<{ message_id: number }>(accountId, method, form);
|
|
83
78
|
return { accountId, message_id: r.message_id };
|
|
84
79
|
}
|
|
85
80
|
|
|
@@ -9,15 +9,15 @@ import {
|
|
|
9
9
|
} from '@metro-labs/core/stations/profile';
|
|
10
10
|
import { makeProfileCache, nonEmpty, type SenderProfile } from '@metro-labs/core/stations/sender-profile';
|
|
11
11
|
import { TrainError } from '@metro-labs/core/train-error';
|
|
12
|
-
import { accountFor, tg
|
|
13
|
-
import { respond } from '
|
|
12
|
+
import { accountFor, tg } from './accounts.js';
|
|
13
|
+
import { respond } from '@metro-labs/core/stations/station-runtime';
|
|
14
14
|
|
|
15
15
|
async function setPhoto(accountId: string, avatar: ProfileAvatar): Promise<void> {
|
|
16
16
|
assertImage(avatar);
|
|
17
17
|
const form = new FormData();
|
|
18
18
|
form.append('photo', JSON.stringify({ type: 'static', photo: 'attach://avatar' }));
|
|
19
19
|
await appendFile(form, 'avatar', avatar.path, avatar.name);
|
|
20
|
-
await
|
|
20
|
+
await tg(accountId, 'setMyProfilePhoto', form);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export async function applyProfile(accountId: string, change: ProfileChange): Promise<void> {
|
|
@@ -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,
|
|
@@ -12,10 +10,6 @@ import { isGatewayId, normalizeThreemaId, parsePrivateKey } from './ids.js';
|
|
|
12
10
|
import { GroupStore, parseGroupKey } from './groups.js';
|
|
13
11
|
import type { GroupRef } from './messages.js';
|
|
14
12
|
|
|
15
|
-
const ACCOUNTS_FILE =
|
|
16
|
-
process.env.THREEMA_ACCOUNTS_FILE ??
|
|
17
|
-
join(homedir(), '.metro', 'threema-accounts.json');
|
|
18
|
-
|
|
19
13
|
export interface AccountConfig {
|
|
20
14
|
id: string;
|
|
21
15
|
gatewayId: string;
|
|
@@ -26,7 +20,6 @@ export interface AccountConfig {
|
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
function checkAccount(a: AccountConfig, die: Die): void {
|
|
29
|
-
if (!a.id) die('account missing id');
|
|
30
23
|
if (typeof a.gatewayId !== 'string' || !isGatewayId(a.gatewayId))
|
|
31
24
|
die(`account '${a.id}' has no Gateway ID`);
|
|
32
25
|
if (typeof a.secret !== 'string' || a.secret === '')
|
|
@@ -37,16 +30,12 @@ function checkAccount(a: AccountConfig, die: Die): void {
|
|
|
37
30
|
|
|
38
31
|
export const { loadAccounts } = makeAccountStore<AccountConfig>({
|
|
39
32
|
prefix: 'threema',
|
|
40
|
-
file: ACCOUNTS_FILE,
|
|
41
33
|
validate(raw, die) {
|
|
42
|
-
const seenId = new Set<string>();
|
|
43
34
|
const seenGateway = new Set<string>();
|
|
44
35
|
for (const a of raw) {
|
|
45
36
|
checkAccount(a, die);
|
|
46
|
-
if (seenId.has(a.id)) die(`duplicate account id '${a.id}'`);
|
|
47
37
|
if (seenGateway.has(a.gatewayId))
|
|
48
38
|
die(`account '${a.id}' reuses the Gateway ID of another account`);
|
|
49
|
-
seenId.add(a.id);
|
|
50
39
|
seenGateway.add(a.gatewayId);
|
|
51
40
|
}
|
|
52
41
|
},
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
respond,
|
|
5
5
|
type CallMsg,
|
|
6
6
|
} from '@metro-labs/core/stations/station-runtime';
|
|
7
|
-
import
|
|
7
|
+
import { messagingAliases } from '@metro-labs/core/stations/messaging-normalize';
|
|
8
8
|
import { accountFor, accounts, publicKeyFor, targetOf, type Account } from './accounts.js';
|
|
9
9
|
import { hexToBytes, macMatches, open } from './crypto.js';
|
|
10
10
|
import { groupLineOf, type InboundMeta } from './format.js';
|
|
@@ -118,17 +118,7 @@ async function callback(id: string, args: Args): Promise<void> {
|
|
|
118
118
|
respond(id, { result: { ok: true, kind: deliver(acct, meta, decode(plain)) } });
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
export function normalizeThreema(action: string, env: Args): Normalized {
|
|
122
|
-
if (action === 'reply')
|
|
123
|
-
return {
|
|
124
|
-
action: 'send',
|
|
125
|
-
args: { line: env.line, text: env.text, replyTo: env.replyTo, account: env.account },
|
|
126
|
-
};
|
|
127
|
-
if (action === 'unreact') return { action: 'react', args: { ...env, action: 'removed' } };
|
|
128
|
-
return { action, args: env };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
121
|
export const handleCall = makeStation({
|
|
132
122
|
handlers: { accounts: listAccounts, send, react, callback, listMembers },
|
|
133
|
-
normalize:
|
|
123
|
+
normalize: messagingAliases({ action: 'removed' }),
|
|
134
124
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import {
|
|
3
|
-
import { attachmentFailedEvent, attachmentSavedEvent, emitInbound } from '@metro-labs/core/stations/train-events';
|
|
2
|
+
import { emitInbound, reportAttachment, type SavedFile } from '@metro-labs/core/stations/train-events';
|
|
4
3
|
import { assertAttachmentSize, isImageMime, kindOf, saveBufferToCache } from '@metro-labs/core/stations/attachments';
|
|
5
4
|
import { TrainError } from '@metro-labs/core/train-error';
|
|
6
5
|
import type { Account } from './accounts.js';
|
|
@@ -30,12 +29,11 @@ export const encodeFileFor = (group: GroupRef | null, file: FileData): Uint8Arra
|
|
|
30
29
|
|
|
31
30
|
export const fileLabel = (file: OutgoingFile): string => kindOf(file.mime, file.path);
|
|
32
31
|
|
|
33
|
-
async function fetchFile(acct: Account, m: InboundMeta, file: FileData): Promise<
|
|
32
|
+
async function fetchFile(acct: Account, m: InboundMeta, file: FileData): Promise<SavedFile> {
|
|
34
33
|
const sealed = await downloadBlob(acct.cfg, file.blobId);
|
|
35
34
|
const plain = openBlob(sealed, hexToBytes(file.key, 'blob key'));
|
|
36
35
|
if (plain === null) throw new TrainError('threema_bad_blob', 'the file did not decrypt with the key in the message', { retryable: false });
|
|
37
|
-
|
|
38
|
-
return { path: saved.path, bytes: saved.bytes };
|
|
36
|
+
return saveBufferToCache(plain, m.messageId, 0, { mime: file.mime, name: file.name });
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
export function deliverFile(acct: Account, m: InboundMeta, file: FileData, room: Room, sentByUs: (id: string) => boolean): void {
|
|
@@ -44,15 +42,6 @@ export function deliverFile(acct: Account, m: InboundMeta, file: FileData, room:
|
|
|
44
42
|
const forId = String(env.id);
|
|
45
43
|
const payload = { ...(env.payload as Record<string, unknown>), attachments: [{ kind: kindOf(file.mime, file.name), name: file.name, mime: file.mime, ...(file.size === null ? {} : { size: file.size }) }] };
|
|
46
44
|
emitInbound(acct.cfg.id, { ...env, payload });
|
|
47
|
-
const at = { station: 'threema', account: acct.cfg.id, line, forId, index: 0 };
|
|
48
45
|
const kind = kindOf(file.mime, file.name);
|
|
49
|
-
fetchFile(acct, m, file)
|
|
50
|
-
.then(({ path, bytes }) => {
|
|
51
|
-
emit(attachmentSavedEvent({ ...at, saved: { path, mime: file.mime, name: file.name }, extra: { kind, size: bytes } }));
|
|
52
|
-
})
|
|
53
|
-
.catch((err: unknown) => {
|
|
54
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
55
|
-
process.stderr.write(`threema[${acct.cfg.id}]: could not fetch ${file.name} from ${m.from}: ${reason}\n`);
|
|
56
|
-
emit(attachmentFailedEvent({ ...at, reason, extra: { kind } }));
|
|
57
|
-
});
|
|
46
|
+
reportAttachment(fetchFile(acct, m, file), { station: 'threema', account: acct.cfg.id, line, forId, index: 0 }, { saved: { kind }, failed: { kind } });
|
|
58
47
|
}
|
|
@@ -53,7 +53,6 @@ function base(
|
|
|
53
53
|
): Record<string, unknown> {
|
|
54
54
|
const group = room.group;
|
|
55
55
|
return {
|
|
56
|
-
kind: 'inbound',
|
|
57
56
|
id: mintId(),
|
|
58
57
|
ts: tsOf(m.date),
|
|
59
58
|
station: 'threema',
|
|
@@ -127,18 +126,16 @@ export function emitOutbound(
|
|
|
127
126
|
replyTo?: string,
|
|
128
127
|
): void {
|
|
129
128
|
emit({
|
|
130
|
-
kind: 'outbound',
|
|
131
129
|
id: mintId(),
|
|
132
130
|
ts: new Date().toISOString(),
|
|
133
131
|
station: 'threema',
|
|
134
132
|
line,
|
|
135
|
-
from: selfUri(
|
|
133
|
+
from: selfUri(),
|
|
136
134
|
to: line,
|
|
137
135
|
message_id: messageId,
|
|
138
136
|
text,
|
|
139
137
|
reply_to: replyTo,
|
|
140
138
|
...(replyTo ? { event: { type: 'reply', replyTo } } : {}),
|
|
141
|
-
account: accountId,
|
|
142
139
|
payload: { account: accountId },
|
|
143
140
|
});
|
|
144
141
|
}
|
|
@@ -152,18 +149,16 @@ export function emitOutboundReaction(
|
|
|
152
149
|
removed: boolean,
|
|
153
150
|
): void {
|
|
154
151
|
emit({
|
|
155
|
-
kind: 'outbound',
|
|
156
152
|
id: mintId(),
|
|
157
153
|
ts: new Date().toISOString(),
|
|
158
154
|
station: 'threema',
|
|
159
155
|
line,
|
|
160
|
-
from: selfUri(
|
|
156
|
+
from: selfUri(),
|
|
161
157
|
to: line,
|
|
162
158
|
message_id: messageId,
|
|
163
159
|
text: `[react ${emoji}${removed ? ' (removed)' : ''}]`,
|
|
164
160
|
emoji,
|
|
165
161
|
event: { type: 'react', emoji, targetId, ...(removed ? { removed: true } : {}) },
|
|
166
|
-
account: accountId,
|
|
167
162
|
payload: { account: accountId },
|
|
168
163
|
});
|
|
169
164
|
}
|