@stage-labs/metro 0.1.0-beta.21 → 0.1.0-beta.23
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/cli.js +18 -12
- package/dist/control.js +68 -12
- package/dist/prompt.js +51 -0
- 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/daemon/boot.ts +2 -4
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +49 -27
- 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
|
@@ -1,128 +1,87 @@
|
|
|
1
|
+
import type { Message } from '@mtcute/bun';
|
|
2
|
+
import { FileLocation } from '@mtcute/bun';
|
|
1
3
|
import {
|
|
2
4
|
saveBufferToCache,
|
|
3
|
-
|
|
5
|
+
type SavedAttachment,
|
|
4
6
|
} from '@metro-labs/mcp/stations/attachments';
|
|
5
|
-
import type {
|
|
6
|
-
import { tg, accounts } from './accounts.js';
|
|
7
|
-
import type { TgMsg } from './types.js';
|
|
7
|
+
import type { UserClient } from './client.js';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
type Media = NonNullable<Message['media']>;
|
|
10
|
+
export type DownloadableMedia = Media & FileLocation;
|
|
10
11
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
return mime === 'audio/ogg' || ext === 'ogg' || ext === 'oga';
|
|
12
|
+
export function isDownloadable(media: Media): media is DownloadableMedia {
|
|
13
|
+
return media instanceof FileLocation;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
ref: string | undefined,
|
|
20
|
-
): boolean {
|
|
21
|
-
const isOgg = isOggRef(mime, ref);
|
|
22
|
-
if (kind === 'voice') return true;
|
|
23
|
-
if (kind === 'audio') return isOgg;
|
|
24
|
-
return !kind && isOgg;
|
|
16
|
+
interface MediaMeta {
|
|
17
|
+
mime?: string;
|
|
18
|
+
name?: string;
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const m = mime ?? '';
|
|
33
|
-
if (kind === 'image' || m.startsWith('image/')) return 'image';
|
|
34
|
-
if (isVoiceRef(kind, mime, ref)) return 'voice';
|
|
35
|
-
if (kind === 'video' || m.startsWith('video/')) return 'video';
|
|
36
|
-
if (kind === 'audio' || m.startsWith('audio/')) return 'audio';
|
|
37
|
-
return 'document';
|
|
21
|
+
function fallbackMime(media: DownloadableMedia): string | undefined {
|
|
22
|
+
const type = (media as { type?: unknown }).type;
|
|
23
|
+
if (type === 'photo') return 'image/jpeg';
|
|
24
|
+
if (type === 'sticker') return 'image/webp';
|
|
25
|
+
return undefined;
|
|
38
26
|
}
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
function metaOf(media: DownloadableMedia): MediaMeta {
|
|
29
|
+
const withMime = media as { mimeType?: unknown };
|
|
30
|
+
const withName = media as { fileName?: unknown };
|
|
31
|
+
const mime =
|
|
32
|
+
(typeof withMime.mimeType === 'string' ? withMime.mimeType : undefined) ??
|
|
33
|
+
fallbackMime(media);
|
|
34
|
+
const name =
|
|
35
|
+
typeof withName.fileName === 'string' ? withName.fileName : undefined;
|
|
36
|
+
return {
|
|
37
|
+
...(mime ? { mime } : {}),
|
|
38
|
+
...(name ? { name } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PendingDescriptor {
|
|
43
|
+
kind: string;
|
|
42
44
|
name?: string;
|
|
43
|
-
mime?: string;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
? {
|
|
70
|
-
fileId: m.animation.file_id,
|
|
71
|
-
name: m.animation.file_name,
|
|
72
|
-
mime: m.animation.mime_type,
|
|
73
|
-
}
|
|
74
|
-
: null,
|
|
75
|
-
(m) =>
|
|
76
|
-
m.audio?.file_id
|
|
77
|
-
? {
|
|
78
|
-
fileId: m.audio.file_id,
|
|
79
|
-
name: m.audio.file_name,
|
|
80
|
-
mime: m.audio.mime_type,
|
|
81
|
-
}
|
|
82
|
-
: null,
|
|
83
|
-
(m) =>
|
|
84
|
-
m.voice?.file_id
|
|
85
|
-
? { fileId: m.voice.file_id, mime: m.voice.mime_type ?? 'audio/ogg' }
|
|
86
|
-
: null,
|
|
87
|
-
(m) =>
|
|
88
|
-
m.sticker?.file_id
|
|
89
|
-
? { fileId: m.sticker.file_id, mime: 'image/webp' }
|
|
90
|
-
: null,
|
|
91
|
-
];
|
|
47
|
+
const KIND_BY_TYPE: Record<string, string> = {
|
|
48
|
+
photo: 'image',
|
|
49
|
+
sticker: 'image',
|
|
50
|
+
voice: 'audio',
|
|
51
|
+
audio: 'audio',
|
|
52
|
+
video: 'video',
|
|
53
|
+
video_note: 'video',
|
|
54
|
+
animation: 'video',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function mediaType(media: DownloadableMedia): string {
|
|
58
|
+
const type = (media as { type?: unknown }).type;
|
|
59
|
+
return typeof type === 'string' ? type : '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function kindOf(media: DownloadableMedia, mime: string | undefined): string {
|
|
63
|
+
const byType = KIND_BY_TYPE[mediaType(media)];
|
|
64
|
+
if (byType) return byType;
|
|
65
|
+
if (mime?.startsWith('image/')) return 'image';
|
|
66
|
+
if (mime?.startsWith('audio/')) return 'audio';
|
|
67
|
+
if (mime?.startsWith('video/')) return 'video';
|
|
68
|
+
return 'file';
|
|
69
|
+
}
|
|
92
70
|
|
|
93
|
-
export function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
71
|
+
export function pendingDescriptorOf(media: DownloadableMedia): PendingDescriptor {
|
|
72
|
+
const meta = metaOf(media);
|
|
73
|
+
return {
|
|
74
|
+
kind: kindOf(media, meta.mime),
|
|
75
|
+
...(meta.name ? { name: meta.name } : {}),
|
|
76
|
+
};
|
|
99
77
|
}
|
|
100
78
|
|
|
101
|
-
export async function
|
|
102
|
-
|
|
103
|
-
|
|
79
|
+
export async function downloadMedia(
|
|
80
|
+
client: UserClient,
|
|
81
|
+
media: DownloadableMedia,
|
|
104
82
|
messageId: string,
|
|
105
83
|
index = 0,
|
|
106
84
|
): Promise<SavedAttachment> {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
});
|
|
110
|
-
if (!file.file_path)
|
|
111
|
-
throw new Error(`telegram getFile returned no file_path for ${ref.fileId}`);
|
|
112
|
-
const acct = accounts.get(accountId);
|
|
113
|
-
if (!acct) throw new Error(`unknown account '${accountId}'`);
|
|
114
|
-
const res = await fetch(`${acct.fileApi}/${file.file_path}`, {
|
|
115
|
-
signal: AbortSignal.timeout(60_000),
|
|
116
|
-
});
|
|
117
|
-
if (!res.ok)
|
|
118
|
-
throw new Error(
|
|
119
|
-
`telegram file download ${res.status} for ${file.file_path}`,
|
|
120
|
-
);
|
|
121
|
-
assertContentLength(res.headers.get('content-length'));
|
|
122
|
-
const data = new Uint8Array(await res.arrayBuffer());
|
|
123
|
-
const saved = await saveBufferToCache(data, messageId, index, {
|
|
124
|
-
mime: ref.mime,
|
|
125
|
-
name: ref.name ?? file.file_path,
|
|
126
|
-
});
|
|
127
|
-
return { ...saved, name: ref.name };
|
|
85
|
+
const buf = await client.tg.downloadAsBuffer(media);
|
|
86
|
+
return saveBufferToCache(buf, messageId, index, metaOf(media));
|
|
128
87
|
}
|
|
@@ -24,7 +24,7 @@ export interface UserClient {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function storagePath(accountId: string): string {
|
|
27
|
-
const dir = join(STATE_DIR, 'telegram
|
|
27
|
+
const dir = join(STATE_DIR, 'telegram');
|
|
28
28
|
mkdirSync(dir, { recursive: true });
|
|
29
29
|
return join(dir, `${accountId}.session`);
|
|
30
30
|
}
|
|
@@ -34,7 +34,7 @@ export function createClient(account: UserAccount): UserClient {
|
|
|
34
34
|
const { apiHash } = account;
|
|
35
35
|
if (!Number.isInteger(apiId) || apiId <= 0 || !apiHash)
|
|
36
36
|
throw new TrainError(
|
|
37
|
-
'
|
|
37
|
+
'telegram_auth',
|
|
38
38
|
`account '${account.id}' missing apiId/apiHash`,
|
|
39
39
|
);
|
|
40
40
|
const tg = new TelegramClient({
|
|
@@ -51,7 +51,7 @@ export function createClient(account: UserAccount): UserClient {
|
|
|
51
51
|
await tg.connect();
|
|
52
52
|
} catch (e) {
|
|
53
53
|
throw new TrainError(
|
|
54
|
-
'
|
|
54
|
+
'telegram_auth',
|
|
55
55
|
`account '${account.id}' connect failed: ${errMsg(e)}`,
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -60,7 +60,7 @@ export function createClient(account: UserAccount): UserClient {
|
|
|
60
60
|
me = { username: user.username ?? null, phone: user.phoneNumber ?? null };
|
|
61
61
|
} catch (e) {
|
|
62
62
|
process.stderr.write(
|
|
63
|
-
`telegram
|
|
63
|
+
`telegram[${account.id}] identity unavailable: ${errMsg(e)}\n`,
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
};
|
|
@@ -1,172 +1,141 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
1
|
+
import type { Chat, Message, Peer, User } from '@mtcute/bun';
|
|
2
|
+
import type { SavedAttachment } from '@metro-labs/mcp/stations/attachments';
|
|
3
|
+
import { lineOf } from './accounts.js';
|
|
4
|
+
import { mintId } from './wire.js';
|
|
5
|
+
import { isDownloadable, pendingDescriptorOf } from './attachments.js';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const isUser = (peer: Peer): peer is User => peer.type === 'user';
|
|
8
|
+
const isChat = (peer: Peer): peer is Chat => peer.type === 'chat';
|
|
8
9
|
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
export function senderName(peer: Peer): string | undefined {
|
|
11
|
+
if (isUser(peer)) return peer.username ? `@${peer.username}` : peer.firstName;
|
|
12
|
+
return peer.title;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function displayName(peer: Peer): string | undefined {
|
|
16
|
+
if (isUser(peer)) return peer.firstName;
|
|
17
|
+
return peer.title;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function chatName(peer: Peer): string | undefined {
|
|
21
|
+
if (isChat(peer)) return peer.title;
|
|
22
|
+
if (isUser(peer)) return peer.username ? `@${peer.username}` : peer.firstName;
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function topicOf(m: Message): number | undefined {
|
|
27
|
+
if (!m.isTopicMessage) return undefined;
|
|
28
|
+
const threadId = m.replyToMessage?.threadId;
|
|
29
|
+
return threadId ?? undefined;
|
|
15
30
|
}
|
|
16
31
|
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
return `[
|
|
32
|
+
function mediaTag(m: Message): string | undefined {
|
|
33
|
+
if (m.media === null) return undefined;
|
|
34
|
+
return `[${m.media.type}]`;
|
|
20
35
|
}
|
|
21
36
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
(m) => (m.video ? `[video: ${m.video.file_name ?? 'video'}]` : null),
|
|
27
|
-
(m) => (m.animation ? `[gif: ${m.animation.file_name ?? 'gif'}]` : null),
|
|
28
|
-
(m) => (m.sticker ? stickerTag(m.sticker) : null),
|
|
29
|
-
(m) =>
|
|
30
|
-
m.document && !m.animation
|
|
31
|
-
? `[file: ${m.document.file_name ?? 'doc'}]`
|
|
32
|
-
: null,
|
|
33
|
-
(m) =>
|
|
34
|
-
m.location
|
|
35
|
-
? `[location: ${m.location.latitude}, ${m.location.longitude}]`
|
|
36
|
-
: null,
|
|
37
|
-
(m) => (m.dice ? `[dice ${m.dice.emoji} = ${m.dice.value}]` : null),
|
|
38
|
-
];
|
|
37
|
+
function projectText(m: Message): string {
|
|
38
|
+
const tag = mediaTag(m);
|
|
39
|
+
return [m.text, tag].filter(Boolean).join(' ');
|
|
40
|
+
}
|
|
39
41
|
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
);
|
|
44
|
-
return [m.text ?? m.caption, ...tags].filter(Boolean).join(' ');
|
|
42
|
+
export function isOwnEcho(m: Message): boolean {
|
|
43
|
+
if (m.isOutgoing) return true;
|
|
44
|
+
const { sender } = m;
|
|
45
|
+
return isUser(sender) && sender.isSelf;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
export function envelope(
|
|
48
|
-
|
|
48
|
+
export function envelope(
|
|
49
|
+
accountId: string,
|
|
50
|
+
m: Message,
|
|
51
|
+
): Record<string, unknown> {
|
|
52
|
+
const topicId = topicOf(m);
|
|
53
|
+
const line = lineOf(accountId, m.chat.id, topicId);
|
|
54
|
+
const isPrivate = isUser(m.chat);
|
|
55
|
+
const { media } = m;
|
|
56
|
+
const attachments =
|
|
57
|
+
media !== null && isDownloadable(media)
|
|
58
|
+
? [pendingDescriptorOf(media)]
|
|
59
|
+
: undefined;
|
|
49
60
|
return {
|
|
50
61
|
kind: 'inbound',
|
|
51
62
|
id: mintId(),
|
|
52
|
-
ts:
|
|
63
|
+
ts: m.date.toISOString(),
|
|
53
64
|
station: 'telegram',
|
|
54
65
|
line,
|
|
55
|
-
line_name: m.chat
|
|
56
|
-
from: `metro://telegram/${accountId}/user/${m.
|
|
57
|
-
from_name: m.
|
|
58
|
-
from_display_name: m.
|
|
59
|
-
message_id: String(m.
|
|
66
|
+
line_name: chatName(m.chat),
|
|
67
|
+
from: `metro://telegram/${accountId}/user/${m.sender.id}`,
|
|
68
|
+
from_name: senderName(m.sender),
|
|
69
|
+
from_display_name: displayName(m.sender),
|
|
70
|
+
message_id: String(m.id),
|
|
60
71
|
text: projectText(m),
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
is_private: isPrivate,
|
|
73
|
+
has_media: media !== null,
|
|
74
|
+
payload: {
|
|
75
|
+
account: accountId,
|
|
76
|
+
message_id: String(m.id),
|
|
77
|
+
...(attachments ? { attachments } : {}),
|
|
78
|
+
},
|
|
63
79
|
};
|
|
64
80
|
}
|
|
65
81
|
|
|
66
|
-
export function
|
|
82
|
+
export function attachmentSavedEnvelope(
|
|
67
83
|
accountId: string,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.map((x) => x.emoji ?? '');
|
|
74
|
-
const oldEmojis = r.old_reaction
|
|
75
|
-
.filter((x) => x.type === 'emoji')
|
|
76
|
-
.map((x) => x.emoji ?? '');
|
|
77
|
-
const added = newEmojis.filter((e) => !oldEmojis.includes(e));
|
|
78
|
-
if (!added.length) return null;
|
|
84
|
+
line: string,
|
|
85
|
+
sourceEnvId: string,
|
|
86
|
+
saved: SavedAttachment,
|
|
87
|
+
index = 0,
|
|
88
|
+
): Record<string, unknown> {
|
|
79
89
|
return {
|
|
80
|
-
kind: '
|
|
90
|
+
kind: 'inbound',
|
|
81
91
|
id: mintId(),
|
|
82
|
-
ts: new Date(
|
|
92
|
+
ts: new Date().toISOString(),
|
|
83
93
|
station: 'telegram',
|
|
84
|
-
line
|
|
85
|
-
from: `metro://telegram/${accountId}/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
line,
|
|
95
|
+
from: `metro://telegram/${accountId}/self`,
|
|
96
|
+
text: `📎 saved: ${saved.path}`,
|
|
97
|
+
payload: {
|
|
98
|
+
account: accountId,
|
|
99
|
+
contentType: 'attachmentSaved',
|
|
100
|
+
attachmentFor: sourceEnvId,
|
|
101
|
+
index,
|
|
102
|
+
attachmentPath: saved.path,
|
|
103
|
+
localPath: saved.path,
|
|
104
|
+
mime: saved.mime,
|
|
105
|
+
name: saved.name,
|
|
106
|
+
},
|
|
93
107
|
};
|
|
94
108
|
}
|
|
95
109
|
|
|
96
|
-
export
|
|
97
|
-
accountId: string
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
export interface ReactionInput {
|
|
111
|
+
accountId: string;
|
|
112
|
+
chatId: number;
|
|
113
|
+
messageId: number;
|
|
114
|
+
emoji: string;
|
|
115
|
+
senderId: number;
|
|
116
|
+
date: Date;
|
|
117
|
+
isPrivate: boolean;
|
|
118
|
+
senderName?: string;
|
|
119
|
+
removed?: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function reactionEnvelope(r: ReactionInput): Record<string, unknown> {
|
|
109
123
|
return {
|
|
110
124
|
kind: 'react',
|
|
111
125
|
id: mintId(),
|
|
112
|
-
ts:
|
|
126
|
+
ts: r.date.toISOString(),
|
|
113
127
|
station: 'telegram',
|
|
114
|
-
line: lineOf(accountId,
|
|
115
|
-
from: `metro://telegram/${accountId}/user
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
accountId: string,
|
|
127
|
-
e: Record<string, unknown>,
|
|
128
|
-
): void {
|
|
129
|
-
const owner = accounts.get(accountId)?.cfg.owner;
|
|
130
|
-
const payload = {
|
|
131
|
-
...(e.payload as Record<string, unknown> | undefined),
|
|
132
|
-
account: accountId,
|
|
128
|
+
line: lineOf(r.accountId, r.chatId),
|
|
129
|
+
from: `metro://telegram/${r.accountId}/user/${r.senderId}`,
|
|
130
|
+
from_name: r.senderName,
|
|
131
|
+
message_id: String(r.messageId),
|
|
132
|
+
emoji: r.emoji,
|
|
133
|
+
event: { type: 'react', emoji: r.emoji, targetId: String(r.messageId) },
|
|
134
|
+
is_private: r.isPrivate,
|
|
135
|
+
payload: {
|
|
136
|
+
account: r.accountId,
|
|
137
|
+
message_id: String(r.messageId),
|
|
138
|
+
removed: r.removed === true,
|
|
139
|
+
},
|
|
133
140
|
};
|
|
134
|
-
emit({ ...e, ...(owner ? { to: owner } : {}), account: accountId, payload });
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function saveMediaAndEmit(
|
|
138
|
-
emit: (e: unknown) => void,
|
|
139
|
-
accountId: string,
|
|
140
|
-
m: TgMsg,
|
|
141
|
-
sourceEnvId: string,
|
|
142
|
-
): void {
|
|
143
|
-
const ref = mediaRefOf(m);
|
|
144
|
-
if (!ref) return;
|
|
145
|
-
const line = lineForMsg(accountId, m).line;
|
|
146
|
-
void saveTelegramMedia(accountId, ref, String(m.message_id), 0)
|
|
147
|
-
.then((saved) => {
|
|
148
|
-
emitInbound(emit, accountId, {
|
|
149
|
-
kind: 'inbound',
|
|
150
|
-
id: mintId(),
|
|
151
|
-
ts: new Date().toISOString(),
|
|
152
|
-
station: 'telegram',
|
|
153
|
-
line,
|
|
154
|
-
from: SELF_URI || `metro://telegram/${accountId}/self`,
|
|
155
|
-
text: `📎 saved: ${saved.path}`,
|
|
156
|
-
payload: {
|
|
157
|
-
contentType: 'attachmentSaved',
|
|
158
|
-
attachmentFor: sourceEnvId,
|
|
159
|
-
index: 0,
|
|
160
|
-
attachmentPath: saved.path,
|
|
161
|
-
localPath: saved.path,
|
|
162
|
-
mime: saved.mime,
|
|
163
|
-
name: saved.name,
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
})
|
|
167
|
-
.catch((err: unknown) =>
|
|
168
|
-
process.stderr.write(
|
|
169
|
-
`telegram media save failed: ${errMsg(err)}\n`,
|
|
170
|
-
),
|
|
171
|
-
);
|
|
172
141
|
}
|
|
@@ -140,7 +140,7 @@ async function createPerMember(
|
|
|
140
140
|
}
|
|
141
141
|
if (chatId === null)
|
|
142
142
|
throw new TrainError(
|
|
143
|
-
'
|
|
143
|
+
'telegram_call',
|
|
144
144
|
'could not create group: no requested member could be added',
|
|
145
145
|
);
|
|
146
146
|
for (const m of pending) outcomes.push(await addOne(client, chatId, m));
|
|
@@ -12,7 +12,7 @@ export function clampLimit(limit: unknown): number {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function senderUri(accountId: string, m: Message): string {
|
|
15
|
-
return `metro://telegram
|
|
15
|
+
return `metro://telegram/${accountId}/user/${m.sender.id}`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function textOf(m: Message): string {
|
|
@@ -26,14 +26,14 @@ async function saveMediaAndEmit(
|
|
|
26
26
|
);
|
|
27
27
|
} catch (e) {
|
|
28
28
|
process.stderr.write(
|
|
29
|
-
`telegram
|
|
29
|
+
`telegram[${accountId}] media save failed: ${errMsg(e)}\n`,
|
|
30
30
|
);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function emitMessage(client: UserClient, m: Message): void {
|
|
35
35
|
log.debug(
|
|
36
|
-
{ train: 'telegram
|
|
36
|
+
{ train: 'telegram', account: client.account.id, messageId: m.id },
|
|
37
37
|
'inbound message received',
|
|
38
38
|
);
|
|
39
39
|
if (isOwnEcho(m)) return;
|
|
@@ -42,7 +42,7 @@ function emitMessage(client: UserClient, m: Message): void {
|
|
|
42
42
|
if (m.media !== null)
|
|
43
43
|
saveMediaAndEmit(client, m, env).catch((err: unknown) => {
|
|
44
44
|
process.stderr.write(
|
|
45
|
-
`telegram
|
|
45
|
+
`telegram[${client.account.id}] media save failed: ${errMsg(err)}\n`,
|
|
46
46
|
);
|
|
47
47
|
});
|
|
48
48
|
}
|
|
@@ -54,7 +54,7 @@ function subscribe(client: UserClient): void {
|
|
|
54
54
|
emitMessage(client, m);
|
|
55
55
|
} catch (e) {
|
|
56
56
|
process.stderr.write(
|
|
57
|
-
`telegram
|
|
57
|
+
`telegram[${accountId}] normalize failed: ${errMsg(e)}\n`,
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
};
|
|
@@ -69,10 +69,10 @@ export async function startInbound(client: UserClient): Promise<void> {
|
|
|
69
69
|
subscribe(client);
|
|
70
70
|
await client.connect();
|
|
71
71
|
await client.startUpdates();
|
|
72
|
-
process.stderr.write(`telegram
|
|
72
|
+
process.stderr.write(`telegram[${accountId}] inbound connected\n`);
|
|
73
73
|
} catch (e) {
|
|
74
74
|
process.stderr.write(
|
|
75
|
-
`telegram
|
|
75
|
+
`telegram[${accountId}] connect failed: ${errMsg(e)}\n`,
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
}
|