@llblab/pi-telegram 0.2.9 → 0.3.0
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/README.md +40 -26
- package/docs/architecture.md +62 -35
- package/index.ts +388 -1936
- package/lib/api.ts +647 -76
- package/lib/attachments.ts +128 -16
- package/lib/commands.ts +721 -0
- package/lib/config.ts +157 -0
- package/lib/media.ts +211 -36
- package/lib/menu.ts +920 -338
- package/lib/model.ts +647 -0
- package/lib/pi.ts +80 -0
- package/lib/polling.ts +264 -18
- package/lib/preview.ts +451 -29
- package/lib/queue.ts +1134 -110
- package/lib/registration.ts +127 -28
- package/lib/rendering.ts +575 -281
- package/lib/replies.ts +198 -8
- package/lib/runtime.ts +475 -0
- package/lib/setup.ts +129 -1
- package/lib/status.ts +428 -13
- package/lib/turns.ts +207 -17
- package/lib/updates.ts +392 -99
- package/package.json +18 -3
- package/AGENTS.md +0 -91
- package/BACKLOG.md +0 -5
- package/CHANGELOG.md +0 -23
- package/lib/model-switch.ts +0 -62
- package/tests/api.test.ts +0 -89
- package/tests/attachments.test.ts +0 -132
- package/tests/config.test.ts +0 -80
- package/tests/media.test.ts +0 -77
- package/tests/menu.test.ts +0 -676
- package/tests/polling.test.ts +0 -129
- package/tests/preview.test.ts +0 -441
- package/tests/queue.test.ts +0 -3245
- package/tests/registration.test.ts +0 -268
- package/tests/rendering.test.ts +0 -475
- package/tests/replies.test.ts +0 -142
- package/tests/turns.test.ts +0 -132
- package/tests/updates.test.ts +0 -357
package/lib/config.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram bridge config and pairing helpers
|
|
3
|
+
* Owns persisted bot/session pairing state, local config storage, authorization policy, and first-user pairing side effects
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
const AGENT_DIR = join(homedir(), ".pi", "agent");
|
|
11
|
+
const CONFIG_PATH = join(AGENT_DIR, "telegram.json");
|
|
12
|
+
|
|
13
|
+
export interface TelegramConfig {
|
|
14
|
+
botToken?: string;
|
|
15
|
+
botUsername?: string;
|
|
16
|
+
botId?: number;
|
|
17
|
+
allowedUserId?: number;
|
|
18
|
+
lastUpdateId?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TelegramConfigStore {
|
|
22
|
+
get: () => TelegramConfig;
|
|
23
|
+
set: (config: TelegramConfig) => void;
|
|
24
|
+
update: (mutate: (config: TelegramConfig) => void) => void;
|
|
25
|
+
getBotToken: () => string | undefined;
|
|
26
|
+
hasBotToken: () => boolean;
|
|
27
|
+
getAllowedUserId: () => number | undefined;
|
|
28
|
+
setAllowedUserId: (userId: number) => void;
|
|
29
|
+
load: () => Promise<void>;
|
|
30
|
+
persist: (config?: TelegramConfig) => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TelegramConfigStoreOptions {
|
|
34
|
+
initialConfig?: TelegramConfig;
|
|
35
|
+
agentDir?: string;
|
|
36
|
+
configPath?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function readTelegramConfig(
|
|
40
|
+
configPath: string,
|
|
41
|
+
): Promise<TelegramConfig> {
|
|
42
|
+
try {
|
|
43
|
+
const content = await readFile(configPath, "utf8");
|
|
44
|
+
return JSON.parse(content) as TelegramConfig;
|
|
45
|
+
} catch {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function writeTelegramConfig(
|
|
51
|
+
agentDir: string,
|
|
52
|
+
configPath: string,
|
|
53
|
+
config: TelegramConfig,
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
await mkdir(agentDir, { recursive: true });
|
|
56
|
+
await writeFile(configPath, JSON.stringify(config, null, "\t") + "\n", {
|
|
57
|
+
encoding: "utf8",
|
|
58
|
+
mode: 0o600,
|
|
59
|
+
});
|
|
60
|
+
await chmod(configPath, 0o600);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createTelegramConfigStore(
|
|
64
|
+
options: TelegramConfigStoreOptions = {},
|
|
65
|
+
): TelegramConfigStore {
|
|
66
|
+
let config: TelegramConfig = options.initialConfig ?? {};
|
|
67
|
+
const agentDir = options.agentDir ?? AGENT_DIR;
|
|
68
|
+
const configPath = options.configPath ?? CONFIG_PATH;
|
|
69
|
+
return {
|
|
70
|
+
get: () => config,
|
|
71
|
+
set: (nextConfig) => {
|
|
72
|
+
config = nextConfig;
|
|
73
|
+
},
|
|
74
|
+
update: (mutate) => {
|
|
75
|
+
mutate(config);
|
|
76
|
+
},
|
|
77
|
+
getBotToken: () => config.botToken,
|
|
78
|
+
hasBotToken: () => !!config.botToken,
|
|
79
|
+
getAllowedUserId: () => config.allowedUserId,
|
|
80
|
+
setAllowedUserId: (userId) => {
|
|
81
|
+
config.allowedUserId = userId;
|
|
82
|
+
},
|
|
83
|
+
load: async () => {
|
|
84
|
+
config = await readTelegramConfig(configPath);
|
|
85
|
+
},
|
|
86
|
+
persist: async (nextConfig = config) => {
|
|
87
|
+
await writeTelegramConfig(agentDir, configPath, nextConfig);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type TelegramAuthorizationState =
|
|
93
|
+
| { kind: "pair"; userId: number }
|
|
94
|
+
| { kind: "allow" }
|
|
95
|
+
| { kind: "deny" };
|
|
96
|
+
|
|
97
|
+
export interface TelegramUserPairingDeps<TContext> {
|
|
98
|
+
allowedUserId?: number;
|
|
99
|
+
ctx: TContext;
|
|
100
|
+
setAllowedUserId: (userId: number) => void;
|
|
101
|
+
persistConfig: () => Promise<void>;
|
|
102
|
+
updateStatus: (ctx: TContext) => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface TelegramUserPairingRuntimeDeps<TContext> {
|
|
106
|
+
getAllowedUserId: () => number | undefined;
|
|
107
|
+
setAllowedUserId: (userId: number) => void;
|
|
108
|
+
persistConfig: () => Promise<void>;
|
|
109
|
+
updateStatus: (ctx: TContext) => void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface TelegramUserPairingRuntime<TContext> {
|
|
113
|
+
pairIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getTelegramAuthorizationState(
|
|
117
|
+
userId: number,
|
|
118
|
+
allowedUserId?: number,
|
|
119
|
+
): TelegramAuthorizationState {
|
|
120
|
+
if (allowedUserId === undefined) {
|
|
121
|
+
return { kind: "pair", userId };
|
|
122
|
+
}
|
|
123
|
+
if (userId === allowedUserId) {
|
|
124
|
+
return { kind: "allow" };
|
|
125
|
+
}
|
|
126
|
+
return { kind: "deny" };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function pairTelegramUserIfNeeded<TContext>(
|
|
130
|
+
userId: number,
|
|
131
|
+
deps: TelegramUserPairingDeps<TContext>,
|
|
132
|
+
): Promise<boolean> {
|
|
133
|
+
const authorization = getTelegramAuthorizationState(
|
|
134
|
+
userId,
|
|
135
|
+
deps.allowedUserId,
|
|
136
|
+
);
|
|
137
|
+
if (authorization.kind !== "pair") return false;
|
|
138
|
+
deps.setAllowedUserId(authorization.userId);
|
|
139
|
+
await deps.persistConfig();
|
|
140
|
+
deps.updateStatus(deps.ctx);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function createTelegramUserPairingRuntime<TContext>(
|
|
145
|
+
deps: TelegramUserPairingRuntimeDeps<TContext>,
|
|
146
|
+
): TelegramUserPairingRuntime<TContext> {
|
|
147
|
+
return {
|
|
148
|
+
pairIfNeeded: (userId, ctx) =>
|
|
149
|
+
pairTelegramUserIfNeeded(userId, {
|
|
150
|
+
allowedUserId: deps.getAllowedUserId(),
|
|
151
|
+
ctx,
|
|
152
|
+
setAllowedUserId: deps.setAllowedUserId,
|
|
153
|
+
persistConfig: deps.persistConfig,
|
|
154
|
+
updateStatus: deps.updateStatus,
|
|
155
|
+
}),
|
|
156
|
+
};
|
|
157
|
+
}
|
package/lib/media.ts
CHANGED
|
@@ -1,57 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram media and text extraction helpers
|
|
3
|
-
* Normalizes inbound Telegram messages into reusable file, text, id, and
|
|
3
|
+
* Normalizes inbound Telegram messages into reusable file, text, id, history, and media-group metadata
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const TELEGRAM_MEDIA_GROUP_DEBOUNCE_MS = 1200;
|
|
7
|
+
|
|
8
|
+
export interface TelegramPhotoSize {
|
|
7
9
|
file_id: string;
|
|
8
10
|
file_size?: number;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
export interface
|
|
13
|
+
export interface TelegramDocument {
|
|
12
14
|
file_id: string;
|
|
13
15
|
file_name?: string;
|
|
14
16
|
mime_type?: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
export
|
|
19
|
+
export type TelegramVideo = TelegramDocument;
|
|
20
|
+
export type TelegramAudio = TelegramDocument;
|
|
21
|
+
export type TelegramAnimation = TelegramDocument;
|
|
22
|
+
|
|
23
|
+
export interface TelegramVoice {
|
|
18
24
|
file_id: string;
|
|
19
|
-
file_name?: string;
|
|
20
25
|
mime_type?: string;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
export interface
|
|
28
|
+
export interface TelegramSticker {
|
|
24
29
|
file_id: string;
|
|
25
|
-
file_name?: string;
|
|
26
|
-
mime_type?: string;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
export interface
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
export interface TelegramMediaMessage {
|
|
33
|
+
message_id: number;
|
|
34
|
+
text?: string;
|
|
35
|
+
caption?: string;
|
|
36
|
+
media_group_id?: string;
|
|
37
|
+
photo?: TelegramPhotoSize[];
|
|
38
|
+
document?: TelegramDocument;
|
|
39
|
+
video?: TelegramVideo;
|
|
40
|
+
audio?: TelegramAudio;
|
|
41
|
+
voice?: TelegramVoice;
|
|
42
|
+
animation?: TelegramAnimation;
|
|
43
|
+
sticker?: TelegramSticker;
|
|
32
44
|
}
|
|
33
45
|
|
|
34
|
-
export interface
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
export interface TelegramMediaGroupMessage {
|
|
47
|
+
message_id: number;
|
|
48
|
+
chat: { id: number };
|
|
49
|
+
media_group_id?: string;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
export interface
|
|
41
|
-
|
|
52
|
+
export interface TelegramMediaGroupState<TMessage> {
|
|
53
|
+
messages: TMessage[];
|
|
54
|
+
flushTimer?: ReturnType<typeof setTimeout>;
|
|
42
55
|
}
|
|
43
56
|
|
|
44
|
-
export interface
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
export interface TelegramMediaGroupController<
|
|
58
|
+
TMessage extends TelegramMediaGroupMessage,
|
|
59
|
+
> {
|
|
60
|
+
queueMessage: (options: {
|
|
61
|
+
message: TMessage;
|
|
62
|
+
dispatchMessages: (messages: TMessage[]) => void;
|
|
63
|
+
}) => boolean;
|
|
64
|
+
removeMessages: (messageIds: number[]) => number;
|
|
65
|
+
clear: () => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface TelegramMediaGroupDispatchRuntimeDeps<
|
|
69
|
+
TMessage extends TelegramMediaGroupMessage,
|
|
70
|
+
TContext,
|
|
71
|
+
> {
|
|
72
|
+
mediaGroups: TelegramMediaGroupController<TMessage>;
|
|
73
|
+
dispatchMessages: (messages: TMessage[], ctx: TContext) => Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface TelegramMediaGroupDispatchRuntime<
|
|
77
|
+
TMessage extends TelegramMediaGroupMessage,
|
|
78
|
+
TContext,
|
|
79
|
+
> {
|
|
80
|
+
handleMessage: (message: TMessage, ctx: TContext) => Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface TelegramMediaGroupControllerOptions {
|
|
84
|
+
debounceMs?: number;
|
|
85
|
+
setTimer?: (
|
|
86
|
+
callback: () => void,
|
|
87
|
+
ms: number,
|
|
88
|
+
) => ReturnType<typeof setTimeout>;
|
|
89
|
+
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
55
90
|
}
|
|
56
91
|
|
|
57
92
|
export interface TelegramFileInfo {
|
|
@@ -61,8 +96,22 @@ export interface TelegramFileInfo {
|
|
|
61
96
|
isImage: boolean;
|
|
62
97
|
}
|
|
63
98
|
|
|
64
|
-
export interface
|
|
99
|
+
export interface DownloadedTelegramFile {
|
|
100
|
+
path: string;
|
|
101
|
+
fileName?: string;
|
|
102
|
+
isImage?: boolean;
|
|
103
|
+
mimeType?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface DownloadedTelegramMessageFile {
|
|
65
107
|
path: string;
|
|
108
|
+
fileName: string;
|
|
109
|
+
isImage: boolean;
|
|
110
|
+
mimeType?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface DownloadTelegramMessageFilesDeps {
|
|
114
|
+
downloadFile: (fileId: string, fileName: string) => Promise<string>;
|
|
66
115
|
}
|
|
67
116
|
|
|
68
117
|
export function guessExtensionFromMime(
|
|
@@ -94,37 +143,147 @@ export function guessMediaType(path: string): string | undefined {
|
|
|
94
143
|
return undefined;
|
|
95
144
|
}
|
|
96
145
|
|
|
97
|
-
|
|
146
|
+
function isImageMimeType(mimeType: string | undefined): boolean {
|
|
98
147
|
return mimeType?.toLowerCase().startsWith("image/") ?? false;
|
|
99
148
|
}
|
|
100
149
|
|
|
101
150
|
export function extractTelegramMessageText(
|
|
102
|
-
message:
|
|
151
|
+
message: TelegramMediaMessage,
|
|
103
152
|
): string {
|
|
104
153
|
return (message.text || message.caption || "").trim();
|
|
105
154
|
}
|
|
106
155
|
|
|
107
156
|
export function extractTelegramMessagesText(
|
|
108
|
-
messages:
|
|
157
|
+
messages: TelegramMediaMessage[],
|
|
109
158
|
): string {
|
|
110
159
|
return messages.map(extractTelegramMessageText).filter(Boolean).join("\n\n");
|
|
111
160
|
}
|
|
112
161
|
|
|
113
162
|
export function extractFirstTelegramMessageText(
|
|
114
|
-
messages:
|
|
163
|
+
messages: TelegramMediaMessage[],
|
|
115
164
|
): string {
|
|
116
165
|
return messages.map(extractTelegramMessageText).find(Boolean) ?? "";
|
|
117
166
|
}
|
|
118
167
|
|
|
119
168
|
export function collectTelegramMessageIds(
|
|
120
|
-
messages:
|
|
169
|
+
messages: TelegramMediaMessage[],
|
|
121
170
|
): number[] {
|
|
122
171
|
return [...new Set(messages.map((message) => message.message_id))];
|
|
123
172
|
}
|
|
124
173
|
|
|
174
|
+
export function getTelegramMediaGroupKey(
|
|
175
|
+
message: TelegramMediaGroupMessage,
|
|
176
|
+
): string | undefined {
|
|
177
|
+
if (!message.media_group_id) return undefined;
|
|
178
|
+
return `${message.chat.id}:${message.media_group_id}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function removePendingTelegramMediaGroupMessages<
|
|
182
|
+
TMessage extends TelegramMediaGroupMessage,
|
|
183
|
+
>(
|
|
184
|
+
groups: Map<string, TelegramMediaGroupState<TMessage>>,
|
|
185
|
+
messageIds: number[],
|
|
186
|
+
clearTimer: (timer: ReturnType<typeof setTimeout>) => void,
|
|
187
|
+
): number {
|
|
188
|
+
if (messageIds.length === 0 || groups.size === 0) return 0;
|
|
189
|
+
const deletedMessageIds = new Set(messageIds);
|
|
190
|
+
let removedGroups = 0;
|
|
191
|
+
for (const [key, state] of groups.entries()) {
|
|
192
|
+
if (
|
|
193
|
+
!state.messages.some((message) =>
|
|
194
|
+
deletedMessageIds.has(message.message_id),
|
|
195
|
+
)
|
|
196
|
+
) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (state.flushTimer) clearTimer(state.flushTimer);
|
|
200
|
+
groups.delete(key);
|
|
201
|
+
removedGroups += 1;
|
|
202
|
+
}
|
|
203
|
+
return removedGroups;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function queueTelegramMediaGroupMessage<
|
|
207
|
+
TMessage extends TelegramMediaGroupMessage,
|
|
208
|
+
>(options: {
|
|
209
|
+
message: TMessage;
|
|
210
|
+
groups: Map<string, TelegramMediaGroupState<TMessage>>;
|
|
211
|
+
debounceMs: number;
|
|
212
|
+
setTimer: (callback: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
|
213
|
+
clearTimer: (timer: ReturnType<typeof setTimeout>) => void;
|
|
214
|
+
dispatchMessages: (messages: TMessage[]) => void;
|
|
215
|
+
}): boolean {
|
|
216
|
+
const key = getTelegramMediaGroupKey(options.message);
|
|
217
|
+
if (!key) return false;
|
|
218
|
+
const existing = options.groups.get(key) ?? { messages: [] };
|
|
219
|
+
existing.messages.push(options.message);
|
|
220
|
+
if (existing.flushTimer) options.clearTimer(existing.flushTimer);
|
|
221
|
+
existing.flushTimer = options.setTimer(() => {
|
|
222
|
+
const state = options.groups.get(key);
|
|
223
|
+
options.groups.delete(key);
|
|
224
|
+
if (!state) return;
|
|
225
|
+
options.dispatchMessages(state.messages);
|
|
226
|
+
}, options.debounceMs);
|
|
227
|
+
options.groups.set(key, existing);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function createTelegramMediaGroupController<
|
|
232
|
+
TMessage extends TelegramMediaGroupMessage,
|
|
233
|
+
>(
|
|
234
|
+
options: TelegramMediaGroupControllerOptions = {},
|
|
235
|
+
): TelegramMediaGroupController<TMessage> {
|
|
236
|
+
const groups = new Map<string, TelegramMediaGroupState<TMessage>>();
|
|
237
|
+
const debounceMs = options.debounceMs ?? TELEGRAM_MEDIA_GROUP_DEBOUNCE_MS;
|
|
238
|
+
const setTimer =
|
|
239
|
+
options.setTimer ??
|
|
240
|
+
((callback: () => void, ms: number): ReturnType<typeof setTimeout> =>
|
|
241
|
+
setTimeout(callback, ms));
|
|
242
|
+
const clearTimer = options.clearTimer ?? clearTimeout;
|
|
243
|
+
return {
|
|
244
|
+
queueMessage: ({ message, dispatchMessages }) =>
|
|
245
|
+
queueTelegramMediaGroupMessage({
|
|
246
|
+
message,
|
|
247
|
+
groups,
|
|
248
|
+
debounceMs,
|
|
249
|
+
setTimer,
|
|
250
|
+
clearTimer,
|
|
251
|
+
dispatchMessages,
|
|
252
|
+
}),
|
|
253
|
+
removeMessages: (messageIds) =>
|
|
254
|
+
removePendingTelegramMediaGroupMessages(groups, messageIds, clearTimer),
|
|
255
|
+
clear: () => {
|
|
256
|
+
for (const state of groups.values()) {
|
|
257
|
+
if (state.flushTimer) clearTimer(state.flushTimer);
|
|
258
|
+
}
|
|
259
|
+
groups.clear();
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function createTelegramMediaGroupDispatchRuntime<
|
|
265
|
+
TMessage extends TelegramMediaGroupMessage,
|
|
266
|
+
TContext,
|
|
267
|
+
>(
|
|
268
|
+
deps: TelegramMediaGroupDispatchRuntimeDeps<TMessage, TContext>,
|
|
269
|
+
): TelegramMediaGroupDispatchRuntime<TMessage, TContext> {
|
|
270
|
+
return {
|
|
271
|
+
handleMessage: async (message, ctx) => {
|
|
272
|
+
const queuedMediaGroup = deps.mediaGroups.queueMessage({
|
|
273
|
+
message,
|
|
274
|
+
dispatchMessages: (messages) => {
|
|
275
|
+
void deps.dispatchMessages(messages, ctx);
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
if (queuedMediaGroup) return;
|
|
279
|
+
await deps.dispatchMessages([message], ctx);
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
125
284
|
export function formatTelegramHistoryText(
|
|
126
285
|
rawText: string,
|
|
127
|
-
files:
|
|
286
|
+
files: DownloadedTelegramFile[],
|
|
128
287
|
): string {
|
|
129
288
|
let summary = rawText.length > 0 ? rawText : "(no text)";
|
|
130
289
|
if (files.length > 0) {
|
|
@@ -136,8 +295,24 @@ export function formatTelegramHistoryText(
|
|
|
136
295
|
return summary;
|
|
137
296
|
}
|
|
138
297
|
|
|
298
|
+
export async function downloadTelegramMessageFiles(
|
|
299
|
+
messages: TelegramMediaMessage[],
|
|
300
|
+
deps: DownloadTelegramMessageFilesDeps,
|
|
301
|
+
): Promise<DownloadedTelegramMessageFile[]> {
|
|
302
|
+
const downloaded: DownloadedTelegramMessageFile[] = [];
|
|
303
|
+
for (const file of collectTelegramFileInfos(messages)) {
|
|
304
|
+
downloaded.push({
|
|
305
|
+
path: await deps.downloadFile(file.file_id, file.fileName),
|
|
306
|
+
fileName: file.fileName,
|
|
307
|
+
isImage: file.isImage,
|
|
308
|
+
mimeType: file.mimeType,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return downloaded;
|
|
312
|
+
}
|
|
313
|
+
|
|
139
314
|
export function collectTelegramFileInfos(
|
|
140
|
-
messages:
|
|
315
|
+
messages: TelegramMediaMessage[],
|
|
141
316
|
): TelegramFileInfo[] {
|
|
142
317
|
const files: TelegramFileInfo[] = [];
|
|
143
318
|
for (const message of messages) {
|