@stage-labs/metro 0.1.0-beta.2 → 0.1.0-beta.21
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/api.js +103 -0
- package/dist/cli.js +134 -565
- package/dist/control.js +102 -0
- package/dist/prompt.js +10 -0
- package/dist/runtime.js +94 -0
- package/dist/store.js +70 -0
- package/dist/update.js +33 -0
- package/dist/version.js +97 -0
- package/package.json +34 -22
- package/runtime/node_modules/@metro-labs/discord/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord/src/accounts.ts +119 -0
- package/runtime/node_modules/@metro-labs/discord/src/actions.ts +388 -0
- package/runtime/node_modules/@metro-labs/discord/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord/src/format.ts +210 -0
- package/runtime/node_modules/@metro-labs/discord/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord/src/index.ts +127 -0
- package/runtime/node_modules/@metro-labs/discord/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord/src/station.ts +19 -0
- package/runtime/node_modules/@metro-labs/discord/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord/src/voice-speak.ts +195 -0
- package/runtime/node_modules/@metro-labs/discord/src/voice-transcribe.ts +292 -0
- package/runtime/node_modules/@metro-labs/discord/src/voice.ts +197 -0
- package/runtime/node_modules/@metro-labs/discord/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/mcp/package.json +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +367 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +335 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +124 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +278 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +322 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +111 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +20 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +202 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +338 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +31 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +100 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +333 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +117 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +164 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +208 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +357 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +79 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +162 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +120 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +58 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +134 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +214 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +224 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +125 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +18 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +105 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +27 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +41 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +218 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +323 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +169 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +295 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +91 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +215 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +71 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +37 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +218 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +147 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +329 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +19 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +364 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +300 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +31 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +105 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +131 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +45 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +18 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +145 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +203 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +192 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +73 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +211 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +212 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +397 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +240 -0
- package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +80 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +182 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +120 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +295 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +172 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +103 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +48 -0
- package/runtime/node_modules/@metro-labs/telegram/src/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +3 -0
- package/runtime/node_modules/@metro-labs/telegram-user/package.json +28 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +81 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +254 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +141 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/inbound.ts +78 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +46 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +29 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +67 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +318 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +132 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +141 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +113 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +333 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +78 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +39 -0
- package/runtime/runtime.json +3 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -78
- package/dist/channels/discord.js +0 -148
- package/dist/channels/telegram.js +0 -137
- package/dist/lib/address.js +0 -21
- package/dist/lib/codex-rc.js +0 -274
- package/dist/lib/dotenv.js +0 -31
- package/dist/log.js +0 -12
- package/dist/paths.js +0 -45
- package/dist/tail.js +0 -161
- package/skills/metro/SKILL.md +0 -122
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { TrainError } from '@metro-labs/mcp/train-error';
|
|
2
|
+
import { errMsg } from '@metro-labs/mcp/log';
|
|
3
|
+
import { readCalls } from '@metro-labs/mcp/trains/protocol';
|
|
4
|
+
import {
|
|
5
|
+
accounts,
|
|
6
|
+
loadAccounts,
|
|
7
|
+
accountFor,
|
|
8
|
+
lineOf,
|
|
9
|
+
targetOf,
|
|
10
|
+
} from './accounts.js';
|
|
11
|
+
import { createClient, type UserClient } from './client.js';
|
|
12
|
+
import { startInbound } from './inbound.js';
|
|
13
|
+
import { makeHandleCall } from './actions.js';
|
|
14
|
+
|
|
15
|
+
const clients = new Map<string, UserClient>();
|
|
16
|
+
|
|
17
|
+
function clientFor(accountId: string): UserClient {
|
|
18
|
+
let client = clients.get(accountId);
|
|
19
|
+
if (!client) {
|
|
20
|
+
const account = accounts.get(accountId);
|
|
21
|
+
if (!account)
|
|
22
|
+
throw new TrainError('not_implemented', `unknown account '${accountId}'`);
|
|
23
|
+
client = createClient(account);
|
|
24
|
+
clients.set(accountId, client);
|
|
25
|
+
}
|
|
26
|
+
return client;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const handleCall = makeHandleCall(clientFor);
|
|
30
|
+
|
|
31
|
+
readCalls('telegram-user', handleCall);
|
|
32
|
+
|
|
33
|
+
function boot(): void {
|
|
34
|
+
for (const cfg of loadAccounts()) accounts.set(cfg.id, cfg);
|
|
35
|
+
for (const id of accounts.keys())
|
|
36
|
+
startInbound(clientFor(id)).catch((err: unknown) => {
|
|
37
|
+
process.stderr.write(`telegram-user[${id}] inbound failed: ${errMsg(err)}\n`);
|
|
38
|
+
});
|
|
39
|
+
process.stderr.write(
|
|
40
|
+
`telegram-user train ready (inbound+outbound) — ${accounts.size} account(s): ${[...accounts.keys()].join(', ')}\n`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
boot();
|
|
45
|
+
|
|
46
|
+
export { clients, accounts, clientFor, accountFor, lineOf, targetOf };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { TelegramClient } from '@mtcute/bun';
|
|
2
|
+
|
|
3
|
+
export class TelegramUserLoginError extends Error {}
|
|
4
|
+
|
|
5
|
+
export interface TelegramUserCredentials {
|
|
6
|
+
apiId: number;
|
|
7
|
+
apiHash: string;
|
|
8
|
+
phone: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TelegramUserSignedIn {
|
|
12
|
+
config: { session: string; apiId: number; apiHash: string };
|
|
13
|
+
identity: { userId: string; displayName: string };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type TelegramUserStep = 'code' | 'password';
|
|
17
|
+
|
|
18
|
+
const PHONE_RE = /^[0-9]{6,15}$/;
|
|
19
|
+
const API_HASH_RE = /^[a-f0-9]{32}$/i;
|
|
20
|
+
|
|
21
|
+
export function rpcErrorText(err: unknown): string {
|
|
22
|
+
if (typeof err !== 'object' || err === null) return '';
|
|
23
|
+
const text = (err as { text?: unknown }).text;
|
|
24
|
+
return typeof text === 'string' ? text : '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function needsPassword(err: unknown): boolean {
|
|
28
|
+
return rpcErrorText(err) === 'SESSION_PASSWORD_NEEDED';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function readableAuthError(err: unknown): string {
|
|
32
|
+
const text = rpcErrorText(err);
|
|
33
|
+
if (text.startsWith('PHONE_CODE_INVALID')) return 'that login code is not right';
|
|
34
|
+
if (text.startsWith('PHONE_CODE_EXPIRED'))
|
|
35
|
+
return 'that login code has expired, start again';
|
|
36
|
+
if (text.startsWith('PHONE_NUMBER_INVALID'))
|
|
37
|
+
return 'Telegram does not recognise that phone number';
|
|
38
|
+
if (text.startsWith('PHONE_NUMBER_BANNED'))
|
|
39
|
+
return 'Telegram has banned that phone number';
|
|
40
|
+
if (text.startsWith('PASSWORD_HASH_INVALID'))
|
|
41
|
+
return 'that two-step verification password is not right';
|
|
42
|
+
if (text.startsWith('FLOOD_WAIT'))
|
|
43
|
+
return 'Telegram is rate-limiting this number, try again later';
|
|
44
|
+
return text === '' ? 'Telegram refused the sign-in' : `Telegram said ${text}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function validateCredentials(raw: {
|
|
48
|
+
apiId: unknown;
|
|
49
|
+
apiHash: unknown;
|
|
50
|
+
phone: unknown;
|
|
51
|
+
}): TelegramUserCredentials {
|
|
52
|
+
const apiId = Number(raw.apiId);
|
|
53
|
+
if (!Number.isInteger(apiId) || apiId <= 0)
|
|
54
|
+
throw new TelegramUserLoginError(
|
|
55
|
+
'api id must be the integer from my.telegram.org',
|
|
56
|
+
);
|
|
57
|
+
const apiHash = typeof raw.apiHash === 'string' ? raw.apiHash.trim() : '';
|
|
58
|
+
if (!API_HASH_RE.test(apiHash))
|
|
59
|
+
throw new TelegramUserLoginError(
|
|
60
|
+
'api hash must be the 32-character hash from my.telegram.org',
|
|
61
|
+
);
|
|
62
|
+
const phone =
|
|
63
|
+
typeof raw.phone === 'string' ? raw.phone.replace(/[^0-9]/g, '') : '';
|
|
64
|
+
if (!PHONE_RE.test(phone))
|
|
65
|
+
throw new TelegramUserLoginError(
|
|
66
|
+
'phone must be an international number, digits only',
|
|
67
|
+
);
|
|
68
|
+
return { apiId, apiHash, phone };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface SentCodeLike {
|
|
72
|
+
phoneCodeHash: string;
|
|
73
|
+
length: number;
|
|
74
|
+
type: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const isSentCode = (v: unknown): v is SentCodeLike =>
|
|
78
|
+
typeof v === 'object' &&
|
|
79
|
+
v !== null &&
|
|
80
|
+
typeof (v as { phoneCodeHash?: unknown }).phoneCodeHash === 'string';
|
|
81
|
+
|
|
82
|
+
interface UserLike {
|
|
83
|
+
id: number;
|
|
84
|
+
displayName: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class TelegramUserLogin {
|
|
88
|
+
private tg: TelegramClient;
|
|
89
|
+
private phoneCodeHash = '';
|
|
90
|
+
private closed = false;
|
|
91
|
+
|
|
92
|
+
constructor(private creds: TelegramUserCredentials) {
|
|
93
|
+
this.tg = new TelegramClient({
|
|
94
|
+
apiId: creds.apiId,
|
|
95
|
+
apiHash: creds.apiHash,
|
|
96
|
+
storage: ':memory:',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async requestCode(): Promise<{ step: TelegramUserStep; length: number }> {
|
|
101
|
+
await this.tg.connect();
|
|
102
|
+
const sent = await this.tg
|
|
103
|
+
.sendCode({ phone: this.creds.phone })
|
|
104
|
+
.catch((err: unknown) => {
|
|
105
|
+
throw new TelegramUserLoginError(readableAuthError(err));
|
|
106
|
+
});
|
|
107
|
+
if (!isSentCode(sent))
|
|
108
|
+
throw new TelegramUserLoginError(
|
|
109
|
+
'Telegram signed this number in without a code, which Metro cannot use',
|
|
110
|
+
);
|
|
111
|
+
this.phoneCodeHash = sent.phoneCodeHash;
|
|
112
|
+
return { step: 'code', length: sent.length };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async submitCode(raw: string): Promise<TelegramUserSignedIn | 'password'> {
|
|
116
|
+
const phoneCode = raw.replace(/[^0-9]/g, '');
|
|
117
|
+
if (phoneCode === '')
|
|
118
|
+
throw new TelegramUserLoginError('enter the login code Telegram sent you');
|
|
119
|
+
try {
|
|
120
|
+
const user = await this.tg.signIn({
|
|
121
|
+
phone: this.creds.phone,
|
|
122
|
+
phoneCodeHash: this.phoneCodeHash,
|
|
123
|
+
phoneCode,
|
|
124
|
+
});
|
|
125
|
+
return await this.exportFor(user);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (needsPassword(err)) return 'password';
|
|
128
|
+
throw new TelegramUserLoginError(readableAuthError(err));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async submitPassword(password: string): Promise<TelegramUserSignedIn> {
|
|
133
|
+
if (password === '')
|
|
134
|
+
throw new TelegramUserLoginError(
|
|
135
|
+
'enter your Telegram two-step verification password',
|
|
136
|
+
);
|
|
137
|
+
try {
|
|
138
|
+
const user = await this.tg.checkPassword(password);
|
|
139
|
+
return await this.exportFor(user);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
throw new TelegramUserLoginError(readableAuthError(err));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async exportFor(user: UserLike): Promise<TelegramUserSignedIn> {
|
|
146
|
+
const session = await this.tg.exportSession();
|
|
147
|
+
await this.close();
|
|
148
|
+
return {
|
|
149
|
+
config: {
|
|
150
|
+
session,
|
|
151
|
+
apiId: this.creds.apiId,
|
|
152
|
+
apiHash: this.creds.apiHash,
|
|
153
|
+
},
|
|
154
|
+
identity: {
|
|
155
|
+
userId: String(user.id),
|
|
156
|
+
displayName: user.displayName,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async close(): Promise<void> {
|
|
162
|
+
if (this.closed) return;
|
|
163
|
+
this.closed = true;
|
|
164
|
+
await this.tg.destroy().catch(() => undefined);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { InputMediaLike, Message } from '@mtcute/bun';
|
|
2
|
+
import { InputMedia } from '@mtcute/bun';
|
|
3
|
+
import { isImageMime, isImageExt } from '@metro-labs/mcp/stations/attachments';
|
|
4
|
+
import type { UserClient } from './client.js';
|
|
5
|
+
|
|
6
|
+
export interface CanonicalAttachment {
|
|
7
|
+
kind?: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
mime?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const srcOf = (att: CanonicalAttachment): string => att.path ?? att.url ?? '';
|
|
15
|
+
|
|
16
|
+
function isImage(att: CanonicalAttachment): boolean {
|
|
17
|
+
if (att.kind === 'image') return true;
|
|
18
|
+
if (att.mime !== undefined) return isImageMime(att.mime);
|
|
19
|
+
const src = srcOf(att);
|
|
20
|
+
return src !== '' && isImageExt(src);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fileRef(att: CanonicalAttachment): string {
|
|
24
|
+
const src = srcOf(att);
|
|
25
|
+
if (src === '' || /^https?:\/\//i.test(src)) return src;
|
|
26
|
+
return src.startsWith('file:') ? src : `file:${src}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface OutgoingMedia {
|
|
30
|
+
media: InputMediaLike;
|
|
31
|
+
kind: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function buildInputMedia(
|
|
35
|
+
att: CanonicalAttachment,
|
|
36
|
+
caption: string | undefined,
|
|
37
|
+
): OutgoingMedia {
|
|
38
|
+
const file = fileRef(att);
|
|
39
|
+
const params = {
|
|
40
|
+
...(caption ? { caption } : {}),
|
|
41
|
+
...(att.name ? { fileName: att.name } : {}),
|
|
42
|
+
};
|
|
43
|
+
return isImage(att)
|
|
44
|
+
? { media: InputMedia.photo(file, params), kind: 'image' }
|
|
45
|
+
: { media: InputMedia.document(file, params), kind: 'file' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface SendMediaTarget {
|
|
49
|
+
client: UserClient;
|
|
50
|
+
chatId: number;
|
|
51
|
+
replyTo?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SentMedia {
|
|
55
|
+
message: Message;
|
|
56
|
+
delivered: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function sendAttachments(
|
|
60
|
+
target: SendMediaTarget,
|
|
61
|
+
attachments: CanonicalAttachment[],
|
|
62
|
+
text: string,
|
|
63
|
+
): Promise<SentMedia> {
|
|
64
|
+
const { client, chatId, replyTo } = target;
|
|
65
|
+
const peer = await client.tg.resolvePeer(chatId);
|
|
66
|
+
let last: Message | undefined;
|
|
67
|
+
const delivered: string[] = [];
|
|
68
|
+
for (let i = 0; i < attachments.length; i++) {
|
|
69
|
+
const att = attachments[i];
|
|
70
|
+
if (!att || srcOf(att) === '') continue;
|
|
71
|
+
const caption = i === 0 ? text : undefined;
|
|
72
|
+
const out = buildInputMedia(att, caption);
|
|
73
|
+
last = await client.tg.sendMedia(peer, out.media, {
|
|
74
|
+
...(replyTo !== undefined ? { replyTo } : {}),
|
|
75
|
+
});
|
|
76
|
+
delivered.push(out.kind);
|
|
77
|
+
}
|
|
78
|
+
if (!last) throw new Error('no attachments were sent');
|
|
79
|
+
return { message: last, delivered };
|
|
80
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { MemberList, MetroMember } from '@metro-labs/mcp/stations/types';
|
|
2
|
+
import type { UserClient } from './client.js';
|
|
3
|
+
|
|
4
|
+
export interface RawUserMember {
|
|
5
|
+
user: {
|
|
6
|
+
id: number;
|
|
7
|
+
username?: string | null;
|
|
8
|
+
displayName?: string;
|
|
9
|
+
isBot?: boolean;
|
|
10
|
+
};
|
|
11
|
+
status: string;
|
|
12
|
+
title?: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const PAGE = 200;
|
|
16
|
+
const HARD_CAP = 2000;
|
|
17
|
+
|
|
18
|
+
export function mapUserMember(m: RawUserMember): MetroMember {
|
|
19
|
+
const u = m.user;
|
|
20
|
+
const member: MetroMember = { id: String(u.id) };
|
|
21
|
+
if (u.username) member.name = u.username;
|
|
22
|
+
if (u.displayName) member.display_name = u.displayName;
|
|
23
|
+
if (m.title) member.roles = [m.title];
|
|
24
|
+
member.is_admin = m.status === 'creator' || m.status === 'admin';
|
|
25
|
+
member.is_bot = Boolean(u.isBot);
|
|
26
|
+
return member;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function restrictedMemberList(reason: string): MemberList {
|
|
30
|
+
return { members: [], capability: { supported: false, complete: false, reason } };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const RESTRICTED_RE =
|
|
34
|
+
/ADMIN|PRIVATE|CHAT_ADMIN_REQUIRED|not (a )?participant|permission|not enough rights/i;
|
|
35
|
+
|
|
36
|
+
export function isRestricted(msg: string): boolean {
|
|
37
|
+
return RESTRICTED_RE.test(msg);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type Chunk = RawUserMember[] & { total?: number };
|
|
41
|
+
|
|
42
|
+
export async function fetchMembers(
|
|
43
|
+
client: UserClient,
|
|
44
|
+
chatId: number,
|
|
45
|
+
limit?: number,
|
|
46
|
+
): Promise<MemberList> {
|
|
47
|
+
const target = Math.min(Math.max(1, limit ?? PAGE), HARD_CAP);
|
|
48
|
+
const collected: RawUserMember[] = [];
|
|
49
|
+
let total = 0;
|
|
50
|
+
for (let offset = 0; collected.length < target; ) {
|
|
51
|
+
const want = Math.min(PAGE, target - collected.length);
|
|
52
|
+
const getChatMembers = (
|
|
53
|
+
client.tg as unknown as {
|
|
54
|
+
getChatMembers: (
|
|
55
|
+
id: number,
|
|
56
|
+
p: { offset: number; limit: number },
|
|
57
|
+
) => Promise<Chunk>;
|
|
58
|
+
}
|
|
59
|
+
).getChatMembers;
|
|
60
|
+
const chunk = await getChatMembers.call(client.tg, chatId, {
|
|
61
|
+
offset,
|
|
62
|
+
limit: want,
|
|
63
|
+
});
|
|
64
|
+
total = chunk.total ?? collected.length + chunk.length;
|
|
65
|
+
if (!chunk.length) break;
|
|
66
|
+
collected.push(...chunk);
|
|
67
|
+
offset += chunk.length;
|
|
68
|
+
if (collected.length >= total || chunk.length < want) break;
|
|
69
|
+
}
|
|
70
|
+
const members = collected.map(mapUserMember);
|
|
71
|
+
return {
|
|
72
|
+
members,
|
|
73
|
+
capability: { supported: true, complete: members.length >= total, total },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Normalized } from '@metro-labs/mcp/stations/messaging-normalize';
|
|
2
|
+
|
|
3
|
+
type Args = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export function normalizeTelegramUser(action: string, env: Args): Normalized {
|
|
6
|
+
if (action === 'reply') {
|
|
7
|
+
return {
|
|
8
|
+
action: 'send',
|
|
9
|
+
args: {
|
|
10
|
+
line: env.line,
|
|
11
|
+
text: env.text,
|
|
12
|
+
replyTo: env.replyTo,
|
|
13
|
+
account: env.account,
|
|
14
|
+
attachments: env.attachments,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (action === 'unreact') {
|
|
19
|
+
return { action: 'react', args: { ...env, emoji: '' } };
|
|
20
|
+
}
|
|
21
|
+
return { action, args: env };
|
|
22
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { tl } from '@mtcute/bun';
|
|
2
|
+
import { getBarePeerId, getMarkedPeerId } from '@mtcute/bun';
|
|
3
|
+
import { errMsg } from '@metro-labs/mcp/log';
|
|
4
|
+
import { emit } from './wire.js';
|
|
5
|
+
import { reactionEnvelope } from './format.js';
|
|
6
|
+
import type { UserClient } from './client.js';
|
|
7
|
+
|
|
8
|
+
interface SeenReaction {
|
|
9
|
+
reactorId: number;
|
|
10
|
+
emoji: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type CountMap = Map<string, number>;
|
|
14
|
+
|
|
15
|
+
interface SeenState {
|
|
16
|
+
recent: SeenReaction[];
|
|
17
|
+
counts: CountMap;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SEEN_BY_KEY_MAX = 5000;
|
|
21
|
+
const seenByKey = new Map<string, SeenState>();
|
|
22
|
+
|
|
23
|
+
function emojiOf(reaction: tl.TypeReaction): string | undefined {
|
|
24
|
+
if (reaction._ === 'reactionEmoji') return reaction.emoticon;
|
|
25
|
+
if (reaction._ === 'reactionCustomEmoji')
|
|
26
|
+
return `custom:${String(reaction.documentId)}`;
|
|
27
|
+
if (reaction._ === 'reactionPaid') return '⭐';
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function recentOf(reactions: tl.TypeMessageReactions): SeenReaction[] {
|
|
32
|
+
const recent = reactions.recentReactions ?? [];
|
|
33
|
+
const out: SeenReaction[] = [];
|
|
34
|
+
for (const r of recent) {
|
|
35
|
+
if (r.my === true) continue;
|
|
36
|
+
const emoji = emojiOf(r.reaction);
|
|
37
|
+
if (emoji === undefined) continue;
|
|
38
|
+
out.push({ reactorId: getBarePeerId(r.peerId), emoji });
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function countsOf(reactions: tl.TypeMessageReactions): CountMap {
|
|
44
|
+
const out: CountMap = new Map();
|
|
45
|
+
for (const r of reactions.results) {
|
|
46
|
+
const emoji = emojiOf(r.reaction);
|
|
47
|
+
if (emoji === undefined) continue;
|
|
48
|
+
const count = r.chosenOrder === undefined ? r.count : r.count - 1;
|
|
49
|
+
if (count > 0) out.set(emoji, count);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function has(list: SeenReaction[], item: SeenReaction): boolean {
|
|
55
|
+
return list.some(
|
|
56
|
+
(r) => r.reactorId === item.reactorId && r.emoji === item.emoji,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface DiffResult {
|
|
61
|
+
added: SeenReaction[];
|
|
62
|
+
removed: SeenReaction[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function diffRecent(prev: SeenReaction[], next: SeenReaction[]): DiffResult {
|
|
66
|
+
return {
|
|
67
|
+
added: next.filter((n) => !has(prev, n)),
|
|
68
|
+
removed: prev.filter((p) => !has(next, p)),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function diffCounts(
|
|
73
|
+
prev: CountMap,
|
|
74
|
+
next: CountMap,
|
|
75
|
+
reactorId: number,
|
|
76
|
+
): DiffResult {
|
|
77
|
+
const added: SeenReaction[] = [];
|
|
78
|
+
const removed: SeenReaction[] = [];
|
|
79
|
+
const emojis = new Set([...prev.keys(), ...next.keys()]);
|
|
80
|
+
for (const emoji of emojis) {
|
|
81
|
+
const before = prev.get(emoji) ?? 0;
|
|
82
|
+
const after = next.get(emoji) ?? 0;
|
|
83
|
+
if (after > before) added.push({ reactorId, emoji });
|
|
84
|
+
else if (after < before) removed.push({ reactorId, emoji });
|
|
85
|
+
}
|
|
86
|
+
return { added, removed };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface ReactionContext {
|
|
90
|
+
accountId: string;
|
|
91
|
+
chatId: number;
|
|
92
|
+
messageId: number;
|
|
93
|
+
isPrivate: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function emitDiff(ctx: ReactionContext, change: DiffResult): void {
|
|
97
|
+
const base = {
|
|
98
|
+
accountId: ctx.accountId,
|
|
99
|
+
chatId: ctx.chatId,
|
|
100
|
+
messageId: ctx.messageId,
|
|
101
|
+
date: new Date(),
|
|
102
|
+
isPrivate: ctx.isPrivate,
|
|
103
|
+
};
|
|
104
|
+
for (const a of change.added)
|
|
105
|
+
emit(reactionEnvelope({ ...base, emoji: a.emoji, senderId: a.reactorId }));
|
|
106
|
+
for (const r of change.removed)
|
|
107
|
+
emit(
|
|
108
|
+
reactionEnvelope({
|
|
109
|
+
...base,
|
|
110
|
+
emoji: r.emoji,
|
|
111
|
+
senderId: r.reactorId,
|
|
112
|
+
removed: true,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function computeChange(
|
|
118
|
+
update: tl.RawUpdateMessageReactions,
|
|
119
|
+
prev: SeenState,
|
|
120
|
+
next: SeenState,
|
|
121
|
+
isPrivate: boolean,
|
|
122
|
+
): DiffResult {
|
|
123
|
+
if (next.recent.length > 0 || prev.recent.length > 0)
|
|
124
|
+
return diffRecent(prev.recent, next.recent);
|
|
125
|
+
if (!isPrivate) return { added: [], removed: [] };
|
|
126
|
+
return diffCounts(prev.counts, next.counts, getBarePeerId(update.peer));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function handleUpdate(
|
|
130
|
+
client: UserClient,
|
|
131
|
+
update: tl.RawUpdateMessageReactions,
|
|
132
|
+
): void {
|
|
133
|
+
const chatId = getMarkedPeerId(update.peer);
|
|
134
|
+
const isPrivate = update.peer._ === 'peerUser';
|
|
135
|
+
const key = `${chatId}:${update.msgId}`;
|
|
136
|
+
const next: SeenState = {
|
|
137
|
+
recent: recentOf(update.reactions),
|
|
138
|
+
counts: countsOf(update.reactions),
|
|
139
|
+
};
|
|
140
|
+
const prev = seenByKey.get(key) ?? { recent: [], counts: new Map() };
|
|
141
|
+
const change = computeChange(update, prev, next, isPrivate);
|
|
142
|
+
seenByKey.set(key, next);
|
|
143
|
+
if (seenByKey.size > SEEN_BY_KEY_MAX) {
|
|
144
|
+
const oldest = seenByKey.keys().next().value;
|
|
145
|
+
if (oldest !== undefined) seenByKey.delete(oldest);
|
|
146
|
+
}
|
|
147
|
+
if (change.added.length === 0 && change.removed.length === 0) return;
|
|
148
|
+
emitDiff(
|
|
149
|
+
{ accountId: client.account.id, chatId, messageId: update.msgId, isPrivate },
|
|
150
|
+
change,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function subscribeReactions(client: UserClient): void {
|
|
155
|
+
const accountId = client.account.id;
|
|
156
|
+
client.tg.onRawUpdate.add((info) => {
|
|
157
|
+
const { update } = info;
|
|
158
|
+
if (update._ !== 'updateMessageReactions') return;
|
|
159
|
+
try {
|
|
160
|
+
handleUpdate(client, update);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
process.stderr.write(
|
|
163
|
+
`telegram-user[${accountId}] reaction normalize failed: ${errMsg(e)}\n`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GroupOp, Station, Verb } from '@metro-labs/mcp/stations/types';
|
|
2
|
+
|
|
3
|
+
export const telegramUserStation: Station = {
|
|
4
|
+
name: 'telegram-user',
|
|
5
|
+
hasAccounts: true,
|
|
6
|
+
hasTrain: true,
|
|
7
|
+
messageVerbs: new Set<Verb>([
|
|
8
|
+
'send',
|
|
9
|
+
'reply',
|
|
10
|
+
'react',
|
|
11
|
+
'unreact',
|
|
12
|
+
'edit',
|
|
13
|
+
'delete',
|
|
14
|
+
'read',
|
|
15
|
+
]),
|
|
16
|
+
groupOps: new Set<GroupOp>([
|
|
17
|
+
'create_group',
|
|
18
|
+
'add_members',
|
|
19
|
+
'remove_members',
|
|
20
|
+
'invite_link',
|
|
21
|
+
]),
|
|
22
|
+
attachmentMode: 'canonical',
|
|
23
|
+
tools: [],
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { emit, mintId } from '@metro-labs/mcp/stations/station-runtime';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@metro-labs/webhook",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/station.ts"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"test": "tsc --noEmit && bun test test/"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@metro-labs/mcp": "workspace:*"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@stage-labs/config": "0.1.0-beta.2",
|
|
18
|
+
"eslint": "^10.3.0",
|
|
19
|
+
"knip": "^6.15.0",
|
|
20
|
+
"madge": "^8.0.0",
|
|
21
|
+
"typescript": "^5"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { Line, asLine } from '@metro-labs/mcp/lines';
|
|
3
|
+
import { mintId, type MetroEvent } from '@metro-labs/mcp/events';
|
|
4
|
+
import type { Endpoint } from '@metro-labs/mcp/endpoints';
|
|
5
|
+
import type { Station, Verb } from '@metro-labs/mcp/stations/types';
|
|
6
|
+
|
|
7
|
+
const sessionOwner = (sessionId: string): Line =>
|
|
8
|
+
asLine(`metro://session/${sessionId}`);
|
|
9
|
+
|
|
10
|
+
export const webhookStation: Station = {
|
|
11
|
+
name: 'webhook',
|
|
12
|
+
hasAccounts: true,
|
|
13
|
+
hasTrain: false,
|
|
14
|
+
messageVerbs: new Set<Verb>(),
|
|
15
|
+
attachmentMode: 'none',
|
|
16
|
+
tools: [],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function webhookEntry(
|
|
20
|
+
endpoint: Endpoint,
|
|
21
|
+
headers: Record<string, string>,
|
|
22
|
+
body: unknown,
|
|
23
|
+
method: string,
|
|
24
|
+
url: string,
|
|
25
|
+
): MetroEvent {
|
|
26
|
+
const line = Line.webhook(endpoint.id);
|
|
27
|
+
return {
|
|
28
|
+
id: mintId(),
|
|
29
|
+
ts: new Date().toISOString(),
|
|
30
|
+
station: 'webhook',
|
|
31
|
+
line,
|
|
32
|
+
lineName: endpoint.label,
|
|
33
|
+
from: line,
|
|
34
|
+
to: endpoint.session ? sessionOwner(endpoint.session) : line,
|
|
35
|
+
messageId:
|
|
36
|
+
headers['x-github-delivery'] ?? headers['x-request-id'] ?? randomUUID(),
|
|
37
|
+
text: `${headers['x-github-event'] ?? headers['x-intercom-topic'] ?? 'event'} ${method} ${url}`,
|
|
38
|
+
payload: { headers, body },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@metro-labs/whatsapp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/station.ts",
|
|
8
|
+
"./train": "./src/index.ts",
|
|
9
|
+
"./login": "./src/login.ts",
|
|
10
|
+
"./tokens": "./src/token-store.ts"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"test": "tsc --noEmit && bun test test/"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@metro-labs/mcp": "workspace:*",
|
|
18
|
+
"baileys": "7.0.0-rc14"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@stage-labs/config": "0.1.0-beta.2",
|
|
22
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
23
|
+
"eslint": "^10.3.0",
|
|
24
|
+
"knip": "^6.15.0",
|
|
25
|
+
"madge": "^8.0.0",
|
|
26
|
+
"qrcode-terminal": "^0.12.0",
|
|
27
|
+
"typescript": "^5"
|
|
28
|
+
}
|
|
29
|
+
}
|