@stage-labs/metro 0.1.0-beta.21 → 0.1.0-beta.22
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/package.json +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/package.json +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/accounts.ts +10 -10
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/actions.ts +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/attachments.ts +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/format.ts +7 -7
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group-actions.ts +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/index.ts +10 -10
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/station.ts +2 -2
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/voice-transcribe.ts +6 -6
- package/runtime/node_modules/@metro-labs/mcp/package.json +2 -2
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +14 -8
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +17 -10
- package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +2 -2
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +6 -6
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +6 -6
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +6 -6
- package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +2 -2
- package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +4 -4
- package/runtime/node_modules/@metro-labs/telegram/package.json +5 -2
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +47 -86
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +210 -251
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +66 -107
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/client.ts +4 -4
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +110 -141
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/group.ts +1 -1
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/history.ts +1 -1
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/inbound.ts +6 -6
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +35 -92
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/login.ts +20 -20
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +65 -151
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +60 -32
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/reactions.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +8 -1
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +6 -47
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -3
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram-bot}/package.json +3 -6
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +172 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +48 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
- package/runtime/runtime.json +1 -1
- package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +0 -81
- package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +0 -254
- package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +0 -87
- package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +0 -141
- package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +0 -46
- package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +0 -80
- package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +0 -75
- package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +0 -24
- package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +0 -7
- package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +0 -1
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/api-base.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/members.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/send-files.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/verify.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/voice-speak.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/voice.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/wire.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/normalize.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{telegram → telegram-bot}/src/verify.ts +0 -0
|
@@ -3,102 +3,43 @@ import { join } from 'node:path';
|
|
|
3
3
|
import {
|
|
4
4
|
makeAccountStore,
|
|
5
5
|
resolveAccountId,
|
|
6
|
+
type Die,
|
|
6
7
|
} from '@metro-labs/mcp/stations/account-store';
|
|
7
|
-
import {
|
|
8
|
+
import type { UserAccount } from './types.js';
|
|
8
9
|
|
|
9
10
|
const ACCOUNTS_FILE =
|
|
10
11
|
process.env.TELEGRAM_ACCOUNTS_FILE ??
|
|
11
12
|
join(homedir(), '.metro', 'telegram-accounts.json');
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const isSignedInt = (s: string): boolean => /^-?\d+$/.test(s);
|
|
15
|
+
const isTopic = (s: string): boolean => /^\d+$/.test(s);
|
|
16
|
+
|
|
17
|
+
function validateAccount(a: UserAccount, seen: Set<string>, die: Die): void {
|
|
18
|
+
if (!a.id) die('account missing id');
|
|
19
|
+
if (!a.session || typeof a.session !== 'string')
|
|
20
|
+
die(`account '${a.id}' missing session`);
|
|
21
|
+
if (!Number.isInteger(a.apiId) || (a.apiId ?? 0) <= 0)
|
|
22
|
+
die(`account '${a.id}' missing apiId`);
|
|
23
|
+
if (!a.apiHash || typeof a.apiHash !== 'string')
|
|
24
|
+
die(`account '${a.id}' missing apiHash`);
|
|
25
|
+
if (seen.has(a.id)) die(`duplicate account id '${a.id}'`);
|
|
26
|
+
seen.add(a.id);
|
|
17
27
|
}
|
|
18
28
|
|
|
19
|
-
export const { loadAccounts } = makeAccountStore<
|
|
29
|
+
export const { loadAccounts } = makeAccountStore<UserAccount>({
|
|
20
30
|
prefix: 'telegram',
|
|
21
31
|
file: ACCOUNTS_FILE,
|
|
22
32
|
allowlistEnv: ['TELEGRAM_ONLY_ACCOUNTS', 'TELEGRAM_ACCOUNTS'],
|
|
23
33
|
validate(raw, die) {
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
for (const a of raw) {
|
|
27
|
-
if (!a.id) die('account missing id');
|
|
28
|
-
if (!a.token || typeof a.token !== 'string')
|
|
29
|
-
die(`account '${a.id}' missing token`);
|
|
30
|
-
if (seenId.has(a.id)) die(`duplicate account id '${a.id}'`);
|
|
31
|
-
if (seenTok.has(a.token))
|
|
32
|
-
die(
|
|
33
|
-
`account '${a.id}' reuses a token used by another account (409 on getUpdates)`,
|
|
34
|
-
);
|
|
35
|
-
seenId.add(a.id);
|
|
36
|
-
seenTok.add(a.token);
|
|
37
|
-
}
|
|
34
|
+
const seen = new Set<string>();
|
|
35
|
+
for (const a of raw) validateAccount(a, seen, die);
|
|
38
36
|
},
|
|
39
37
|
});
|
|
40
38
|
|
|
41
|
-
export
|
|
42
|
-
cfg: AccountConfig;
|
|
43
|
-
api: string;
|
|
44
|
-
fileApi: string;
|
|
45
|
-
offset: number;
|
|
46
|
-
}
|
|
47
|
-
export const accounts = new Map<string, Account>();
|
|
48
|
-
|
|
49
|
-
export async function tg<T>(
|
|
50
|
-
accountId: string,
|
|
51
|
-
method: string,
|
|
52
|
-
body: unknown,
|
|
53
|
-
timeoutMs = 30_000,
|
|
54
|
-
): Promise<T> {
|
|
55
|
-
const acct = accounts.get(accountId);
|
|
56
|
-
if (!acct) throw new Error(`unknown account '${accountId}'`);
|
|
57
|
-
const res = await fetch(`${acct.api}/${method}`, {
|
|
58
|
-
method: 'POST',
|
|
59
|
-
headers: { 'Content-Type': 'application/json' },
|
|
60
|
-
body: JSON.stringify(body),
|
|
61
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
62
|
-
});
|
|
63
|
-
const json = (await res.json()) as {
|
|
64
|
-
ok: boolean;
|
|
65
|
-
description?: string;
|
|
66
|
-
result?: T;
|
|
67
|
-
};
|
|
68
|
-
if (!json.ok)
|
|
69
|
-
throw new Error(`telegram ${method}: ${json.description ?? 'unknown'}`);
|
|
70
|
-
return json.result as T;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function tgForm<T>(
|
|
74
|
-
accountId: string,
|
|
75
|
-
method: string,
|
|
76
|
-
form: FormData,
|
|
77
|
-
timeoutMs = 60_000,
|
|
78
|
-
): Promise<T> {
|
|
79
|
-
const acct = accounts.get(accountId);
|
|
80
|
-
if (!acct) throw new Error(`unknown account '${accountId}'`);
|
|
81
|
-
const res = await fetch(`${acct.api}/${method}`, {
|
|
82
|
-
method: 'POST',
|
|
83
|
-
body: form,
|
|
84
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
85
|
-
});
|
|
86
|
-
const json = (await res.json()) as {
|
|
87
|
-
ok: boolean;
|
|
88
|
-
description?: string;
|
|
89
|
-
result?: T;
|
|
90
|
-
};
|
|
91
|
-
if (!json.ok)
|
|
92
|
-
throw new Error(`telegram ${method}: ${json.description ?? 'unknown'}`);
|
|
93
|
-
return json.result as T;
|
|
94
|
-
}
|
|
39
|
+
export const accounts = new Map<string, UserAccount>();
|
|
95
40
|
|
|
96
41
|
export function accountFor(args: { account?: string; line?: string }): string {
|
|
97
|
-
return resolveAccountId(
|
|
98
|
-
accounts,
|
|
99
|
-
args,
|
|
100
|
-
(line) => Line.parseTelegram(line)?.accountId,
|
|
101
|
-
);
|
|
42
|
+
return resolveAccountId(accounts, args, (line) => targetOf(line)?.accountId);
|
|
102
43
|
}
|
|
103
44
|
|
|
104
45
|
export function lineOf(
|
|
@@ -110,11 +51,31 @@ export function lineOf(
|
|
|
110
51
|
return `metro://telegram/${accountId}/${tail}`;
|
|
111
52
|
}
|
|
112
53
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
54
|
+
interface Target {
|
|
55
|
+
accountId: string;
|
|
56
|
+
chatId: number;
|
|
57
|
+
topicId?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function splitScoped(path: string[]): { accountId: string; rest: string[] } {
|
|
61
|
+
const first = path[0];
|
|
62
|
+
if (path.length >= 2 && first !== undefined && !isSignedInt(first))
|
|
63
|
+
return { accountId: first, rest: path.slice(1) };
|
|
64
|
+
return { accountId: 'default', rest: path };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function targetOf(line: string): Target | undefined {
|
|
68
|
+
const prefix = 'metro://telegram/';
|
|
69
|
+
if (!line.startsWith(prefix)) return undefined;
|
|
70
|
+
const path = line.slice(prefix.length).split('/').filter(Boolean);
|
|
71
|
+
const { accountId, rest } = splitScoped(path);
|
|
72
|
+
const [chatId, topicId] = rest;
|
|
73
|
+
if (rest.length < 1 || rest.length > 2 || chatId === undefined) return undefined;
|
|
74
|
+
if (!isSignedInt(chatId)) return undefined;
|
|
75
|
+
if (topicId !== undefined && !isTopic(topicId)) return undefined;
|
|
76
|
+
return {
|
|
77
|
+
accountId,
|
|
78
|
+
chatId: Number(chatId),
|
|
79
|
+
...(topicId !== undefined ? { topicId: Number(topicId) } : {}),
|
|
80
|
+
};
|
|
120
81
|
}
|
|
@@ -1,295 +1,254 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { respond } from './wire.js';
|
|
1
|
+
import { TrainError } from '@metro-labs/mcp/train-error';
|
|
3
2
|
import { errMsg } from '@metro-labs/mcp/log';
|
|
4
|
-
import { normalizeTelegram } from '@metro-labs/mcp/stations/messaging-normalize';
|
|
5
|
-
import {
|
|
6
|
-
adminMemberList,
|
|
7
|
-
inaccessibleMemberList,
|
|
8
|
-
type TgChatMember,
|
|
9
|
-
} from './members.js';
|
|
10
3
|
import {
|
|
11
4
|
makeStation,
|
|
5
|
+
respond,
|
|
12
6
|
type CallMsg,
|
|
13
7
|
type StationHandler,
|
|
14
8
|
} from '@metro-labs/mcp/stations/station-runtime';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
9
|
+
import { accountFor, accounts, targetOf } from './accounts.js';
|
|
10
|
+
import type { UserClient } from './client.js';
|
|
11
|
+
import { normalizeTelegramUser } from './normalize.js';
|
|
17
12
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
media,
|
|
21
|
-
MEDIA_METHOD_FIELD,
|
|
22
|
-
sendDice,
|
|
23
|
-
sendLocation,
|
|
24
|
-
sendMedia,
|
|
13
|
+
sendAttachments,
|
|
14
|
+
type CanonicalAttachment,
|
|
25
15
|
} from './media-actions.js';
|
|
16
|
+
import { clampLimit, shapeHistory } from './history.js';
|
|
17
|
+
import { fetchMembers, isRestricted, restrictedMemberList } from './members.js';
|
|
18
|
+
import {
|
|
19
|
+
groupAddMembers,
|
|
20
|
+
groupCreate,
|
|
21
|
+
groupInviteLink,
|
|
22
|
+
groupRemoveMembers,
|
|
23
|
+
} from './group.js';
|
|
24
|
+
|
|
25
|
+
type Args = Record<string, unknown>;
|
|
26
|
+
|
|
27
|
+
interface Resolved {
|
|
28
|
+
accountId: string;
|
|
29
|
+
client: UserClient;
|
|
30
|
+
chatId: number;
|
|
31
|
+
topicId?: number;
|
|
32
|
+
}
|
|
26
33
|
|
|
34
|
+
type ClientFor = (accountId: string) => UserClient;
|
|
27
35
|
|
|
36
|
+
function str(v: unknown): string | undefined {
|
|
37
|
+
return typeof v === 'string' ? v : undefined;
|
|
38
|
+
}
|
|
28
39
|
|
|
40
|
+
function lineOfArgs(args: Args): string {
|
|
41
|
+
const line = str(args.line);
|
|
42
|
+
if (!line) throw new TrainError('bad_request', 'missing line');
|
|
43
|
+
return line;
|
|
44
|
+
}
|
|
29
45
|
|
|
30
|
-
|
|
46
|
+
function resolve(args: Args, clientFor: ClientFor): Resolved {
|
|
47
|
+
const line = lineOfArgs(args);
|
|
48
|
+
const target = targetOf(line);
|
|
49
|
+
if (!target) throw new TrainError('bad_request', `bad line '${line}'`);
|
|
50
|
+
const accountId = accountFor({ account: str(args.account), line });
|
|
51
|
+
return {
|
|
52
|
+
accountId,
|
|
53
|
+
client: clientFor(accountId),
|
|
54
|
+
chatId: target.chatId,
|
|
55
|
+
...(target.topicId !== undefined ? { topicId: target.topicId } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
31
58
|
|
|
32
|
-
|
|
33
|
-
async function getMe(
|
|
34
|
-
accountId: string,
|
|
35
|
-
): Promise<{ id: number; username: string | null } | null> {
|
|
36
|
-
const cached = meCache.get(accountId);
|
|
37
|
-
if (cached) return cached;
|
|
59
|
+
async function guard<T>(run: () => Promise<T>): Promise<T> {
|
|
38
60
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
const v = { id: me.id, username: me.username ?? null };
|
|
46
|
-
meCache.set(accountId, v);
|
|
47
|
-
return v;
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
61
|
+
return await run();
|
|
62
|
+
} catch (e) {
|
|
63
|
+
const msg = errMsg(e);
|
|
64
|
+
if (/FLOOD_WAIT|SLOWMODE_WAIT/i.test(msg))
|
|
65
|
+
throw new TrainError('rate_limited', msg);
|
|
66
|
+
throw new TrainError('telegram_call', msg);
|
|
50
67
|
}
|
|
51
68
|
}
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const me = await getMe(a.cfg.id);
|
|
57
|
-
const username = me?.username ?? null;
|
|
58
|
-
return {
|
|
59
|
-
id: a.cfg.id,
|
|
60
|
-
handle: username === null ? null : `@${username}`,
|
|
61
|
-
url: username === null ? null : `https://t.me/${username}`,
|
|
62
|
-
owner: a.cfg.owner ?? null,
|
|
63
|
-
botId: me?.id ?? null,
|
|
64
|
-
username,
|
|
65
|
-
};
|
|
66
|
-
}),
|
|
67
|
-
);
|
|
68
|
-
respond(id, { result: { accounts: list } });
|
|
70
|
+
function attachmentsOf(args: Args): CanonicalAttachment[] {
|
|
71
|
+
const raw = args.attachments;
|
|
72
|
+
return Array.isArray(raw) ? (raw as CanonicalAttachment[]) : [];
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
function makeSend(clientFor: ClientFor): StationHandler {
|
|
76
|
+
return async (id, args) => {
|
|
77
|
+
const { accountId, client, chatId, topicId } = resolve(args, clientFor);
|
|
78
|
+
const text = str(args.text) ?? '';
|
|
79
|
+
const replyTo = str(args.replyTo);
|
|
80
|
+
const explicitReplyTo = replyTo ? Number(replyTo) : undefined;
|
|
81
|
+
const replyParam = explicitReplyTo ?? topicId;
|
|
82
|
+
const attachments = attachmentsOf(args);
|
|
83
|
+
if (attachments.length) {
|
|
84
|
+
const sent = await guard(() =>
|
|
85
|
+
sendAttachments({ client, chatId, replyTo: replyParam }, attachments, text),
|
|
86
|
+
);
|
|
87
|
+
respond(id, {
|
|
88
|
+
result: {
|
|
89
|
+
messageId: String(sent.message.id),
|
|
90
|
+
account: accountId,
|
|
91
|
+
attachments: sent.delivered,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const peer = await guard(() => client.tg.resolvePeer(chatId));
|
|
97
|
+
const sent = await guard(() =>
|
|
98
|
+
client.tg.sendText(
|
|
99
|
+
peer,
|
|
100
|
+
text,
|
|
101
|
+
replyParam !== undefined ? { replyTo: replyParam } : undefined,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
respond(id, {
|
|
105
|
+
result: { messageId: String(sent.id), account: accountId },
|
|
106
|
+
});
|
|
107
|
+
};
|
|
85
108
|
}
|
|
86
109
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
att: WireAttachment,
|
|
99
|
-
caption: string | undefined,
|
|
100
|
-
): Promise<{ accountId: string; message_id: number; kind: string }> {
|
|
101
|
-
const kind = attachmentKind(att);
|
|
102
|
-
const mf = MEDIA_METHOD_FIELD[kind];
|
|
103
|
-
if (!mf) throw new Error(`telegram cannot send a '${kind}' attachment`);
|
|
104
|
-
const sent = await sendMedia(mf.method, mf.field, {
|
|
105
|
-
line: a.line,
|
|
106
|
-
path: attachmentSrc(att),
|
|
107
|
-
caption,
|
|
108
|
-
replyTo: a.replyTo,
|
|
109
|
-
parseMode: a.parseMode,
|
|
110
|
-
account: a.account,
|
|
111
|
-
name: att.name,
|
|
112
|
-
});
|
|
113
|
-
return { ...sent, kind };
|
|
110
|
+
function makeReact(clientFor: ClientFor): StationHandler {
|
|
111
|
+
return async (id, args) => {
|
|
112
|
+
const { accountId, client, chatId } = resolve(args, clientFor);
|
|
113
|
+
const message = Number(str(args.messageId));
|
|
114
|
+
const raw = str(args.emoji);
|
|
115
|
+
const emoji = raw === undefined || raw === '' ? null : raw;
|
|
116
|
+
await guard(() =>
|
|
117
|
+
client.tg.sendReaction({ chatId, message, emoji }),
|
|
118
|
+
);
|
|
119
|
+
respond(id, { result: { ok: true, account: accountId } });
|
|
120
|
+
};
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
function makeEdit(clientFor: ClientFor): StationHandler {
|
|
124
|
+
return async (id, args) => {
|
|
125
|
+
const { accountId, client, chatId } = resolve(args, clientFor);
|
|
126
|
+
const message = Number(str(args.messageId));
|
|
127
|
+
const text = str(args.text) ?? '';
|
|
128
|
+
await guard(() => client.tg.editMessage({ chatId, message, text }));
|
|
129
|
+
respond(id, { result: { ok: true, account: accountId } });
|
|
130
|
+
};
|
|
131
|
+
}
|
|
118
132
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (!att) continue;
|
|
126
|
-
const caption = i === 0 ? text : undefined;
|
|
127
|
-
const sent = await sendOneAttachment(a, att, caption);
|
|
128
|
-
last = sent;
|
|
129
|
-
delivered.push(sent.kind);
|
|
130
|
-
emitOutbound(
|
|
131
|
-
sent.accountId,
|
|
132
|
-
line,
|
|
133
|
-
String(sent.message_id),
|
|
134
|
-
captionLabel(caption, sent.kind),
|
|
135
|
-
replyTo,
|
|
133
|
+
function makeDelete(clientFor: ClientFor): StationHandler {
|
|
134
|
+
return async (id, args) => {
|
|
135
|
+
const { accountId, client, chatId } = resolve(args, clientFor);
|
|
136
|
+
const message = Number(str(args.messageId));
|
|
137
|
+
await guard(() =>
|
|
138
|
+
client.tg.deleteMessagesById(chatId, [message], { revoke: true }),
|
|
136
139
|
);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
respond(id, {
|
|
140
|
-
result: {
|
|
141
|
-
messageId: String(last.message_id),
|
|
142
|
-
account: last.accountId,
|
|
143
|
-
attachments: delivered,
|
|
144
|
-
},
|
|
145
|
-
});
|
|
140
|
+
respond(id, { result: { ok: true, account: accountId } });
|
|
141
|
+
};
|
|
146
142
|
}
|
|
147
143
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
144
|
+
function makeRead(clientFor: ClientFor): StationHandler {
|
|
145
|
+
return async (id, args) => {
|
|
146
|
+
const { accountId, client, chatId } = resolve(args, clientFor);
|
|
147
|
+
const limit = clampLimit(args.limit);
|
|
148
|
+
const before = str(args.before);
|
|
149
|
+
const beforeId = before ? Number(before) : undefined;
|
|
150
|
+
const offset =
|
|
151
|
+
beforeId !== undefined && Number.isFinite(beforeId)
|
|
152
|
+
? { offset: { id: beforeId, date: 0 } }
|
|
153
|
+
: {};
|
|
154
|
+
const page = await guard(() =>
|
|
155
|
+
client.tg.getHistory(chatId, { limit, ...offset }),
|
|
156
|
+
);
|
|
157
|
+
respond(id, { result: shapeHistory(accountId, chatId, page) });
|
|
158
|
+
};
|
|
163
159
|
}
|
|
164
160
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
161
|
+
function makeListMembers(clientFor: ClientFor): StationHandler {
|
|
162
|
+
return async (id, args) => {
|
|
163
|
+
const { client, chatId } = resolve(args, clientFor);
|
|
164
|
+
const limit = typeof args.limit === 'number' ? args.limit : undefined;
|
|
165
|
+
try {
|
|
166
|
+
respond(id, { result: await fetchMembers(client, chatId, limit) });
|
|
167
|
+
} catch (e) {
|
|
168
|
+
const msg = errMsg(e);
|
|
169
|
+
if (isRestricted(msg)) {
|
|
170
|
+
respond(id, { result: restrictedMemberList(msg) });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
throw e;
|
|
174
|
+
}
|
|
171
175
|
};
|
|
172
|
-
const { accountId, chatId } = targetOf(line, account);
|
|
173
|
-
await tg(accountId, 'setMessageReaction', {
|
|
174
|
-
chat_id: chatId,
|
|
175
|
-
message_id: Number(messageId),
|
|
176
|
-
reaction: emoji ? [{ type: 'emoji', emoji }] : [],
|
|
177
|
-
});
|
|
178
|
-
respond(id, { result: { ok: true, account: accountId } });
|
|
179
176
|
}
|
|
180
177
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
178
|
+
function makeGroupCreate(clientFor: ClientFor): StationHandler {
|
|
179
|
+
return async (id, args) => {
|
|
180
|
+
const accountId = accountFor({ account: str(args.account) });
|
|
181
|
+
const client = clientFor(accountId);
|
|
182
|
+
respond(id, {
|
|
183
|
+
result: await guard(() => groupCreate(client, accountId, args)),
|
|
184
|
+
});
|
|
188
185
|
};
|
|
189
|
-
const { accountId, chatId } = targetOf(line, account);
|
|
190
|
-
await tg(accountId, 'editMessageText', {
|
|
191
|
-
chat_id: chatId,
|
|
192
|
-
message_id: Number(messageId),
|
|
193
|
-
text,
|
|
194
|
-
parse_mode: parseMode,
|
|
195
|
-
});
|
|
196
|
-
respond(id, { result: { ok: true, account: accountId } });
|
|
197
186
|
}
|
|
198
187
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
188
|
+
function makeGroupAdd(clientFor: ClientFor): StationHandler {
|
|
189
|
+
return async (id, args) => {
|
|
190
|
+
const { client, chatId } = resolve(args, clientFor);
|
|
191
|
+
const line = lineOfArgs(args);
|
|
192
|
+
respond(id, {
|
|
193
|
+
result: await guard(() => groupAddMembers(client, chatId, line, args)),
|
|
194
|
+
});
|
|
204
195
|
};
|
|
205
|
-
const { accountId, chatId } = targetOf(line, account);
|
|
206
|
-
await tg(accountId, 'deleteMessage', {
|
|
207
|
-
chat_id: chatId,
|
|
208
|
-
message_id: Number(messageId),
|
|
209
|
-
});
|
|
210
|
-
respond(id, { result: { ok: true, account: accountId } });
|
|
211
196
|
}
|
|
212
197
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
const accountId = accountFor({ account });
|
|
222
|
-
const acct = accounts.get(accountId);
|
|
223
|
-
if (!acct) throw new Error(`unknown account '${accountId}'`);
|
|
224
|
-
const meta = await tg<{ file_path: string }>(accountId, 'getFile', {
|
|
225
|
-
file_id: fileId,
|
|
226
|
-
});
|
|
227
|
-
const res = await fetch(`${acct.fileApi}/${meta.file_path}`, {
|
|
228
|
-
signal: AbortSignal.timeout(60_000),
|
|
229
|
-
});
|
|
230
|
-
if (!res.ok) throw new Error(`telegram download ${res.status}`);
|
|
231
|
-
assertContentLength(res.headers.get('content-length'));
|
|
232
|
-
const data = await res.arrayBuffer();
|
|
233
|
-
const filename = meta.file_path.split('/').pop() ?? `${fileId}.bin`;
|
|
234
|
-
const path = `${outDir}/${Date.now()}-${filename}`;
|
|
235
|
-
await Bun.write(path, data);
|
|
236
|
-
respond(id, {
|
|
237
|
-
result: { path, fileSize: data.byteLength, account: accountId },
|
|
238
|
-
});
|
|
198
|
+
function makeGroupRemove(clientFor: ClientFor): StationHandler {
|
|
199
|
+
return async (id, args) => {
|
|
200
|
+
const { client, chatId } = resolve(args, clientFor);
|
|
201
|
+
const line = lineOfArgs(args);
|
|
202
|
+
respond(id, {
|
|
203
|
+
result: await guard(() => groupRemoveMembers(client, chatId, line, args)),
|
|
204
|
+
});
|
|
205
|
+
};
|
|
239
206
|
}
|
|
240
207
|
|
|
241
|
-
|
|
242
|
-
id
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
let admins: TgChatMember[];
|
|
248
|
-
try {
|
|
249
|
-
admins = await tg<TgChatMember[]>(accountId, 'getChatAdministrators', {
|
|
250
|
-
chat_id: chatId,
|
|
251
|
-
});
|
|
252
|
-
} catch (e) {
|
|
253
|
-
respond(id, { result: inaccessibleMemberList(errMsg(e)) });
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
let total: number | null = null;
|
|
257
|
-
try {
|
|
258
|
-
total = await tg<number>(accountId, 'getChatMemberCount', {
|
|
259
|
-
chat_id: chatId,
|
|
208
|
+
function makeGroupInvite(clientFor: ClientFor): StationHandler {
|
|
209
|
+
return async (id, args) => {
|
|
210
|
+
const { client, chatId } = resolve(args, clientFor);
|
|
211
|
+
const line = lineOfArgs(args);
|
|
212
|
+
respond(id, {
|
|
213
|
+
result: await guard(() => groupInviteLink(client, chatId, line)),
|
|
260
214
|
});
|
|
261
|
-
}
|
|
262
|
-
total = null;
|
|
263
|
-
}
|
|
264
|
-
respond(id, { result: adminMemberList(admins, total) });
|
|
215
|
+
};
|
|
265
216
|
}
|
|
266
217
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
};
|
|
218
|
+
function makeAccounts(clientFor: ClientFor): StationHandler {
|
|
219
|
+
return (id) => {
|
|
220
|
+
const list = [...accounts.values()].map((a) => {
|
|
221
|
+
const me = clientFor(a.id).self();
|
|
222
|
+
const username = me?.username ?? null;
|
|
223
|
+
return {
|
|
224
|
+
id: a.id,
|
|
225
|
+
handle: username === null ? me?.phone ?? null : `@${username}`,
|
|
226
|
+
url: username === null ? null : `https://t.me/${username}`,
|
|
227
|
+
owner: a.owner ?? null,
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
respond(id, { result: { accounts: list } });
|
|
231
|
+
return Promise.resolve();
|
|
232
|
+
};
|
|
233
|
+
}
|
|
284
234
|
|
|
285
|
-
export
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
235
|
+
export function makeHandleCall(
|
|
236
|
+
clientFor: ClientFor,
|
|
237
|
+
): (msg: CallMsg) => Promise<void> {
|
|
238
|
+
return makeStation({
|
|
239
|
+
handlers: {
|
|
240
|
+
accounts: makeAccounts(clientFor),
|
|
241
|
+
send: makeSend(clientFor),
|
|
242
|
+
react: makeReact(clientFor),
|
|
243
|
+
edit: makeEdit(clientFor),
|
|
244
|
+
delete: makeDelete(clientFor),
|
|
245
|
+
read: makeRead(clientFor),
|
|
246
|
+
listMembers: makeListMembers(clientFor),
|
|
247
|
+
groupCreate: makeGroupCreate(clientFor),
|
|
248
|
+
groupAddMembers: makeGroupAdd(clientFor),
|
|
249
|
+
groupRemoveMembers: makeGroupRemove(clientFor),
|
|
250
|
+
groupInviteLink: makeGroupInvite(clientFor),
|
|
251
|
+
},
|
|
252
|
+
normalize: normalizeTelegramUser,
|
|
253
|
+
});
|
|
254
|
+
}
|