@adminforth/chat-surface-adapter-telegram 1.1.1 → 1.2.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/dist/index.d.ts +32 -0
- package/dist/index.js +115 -49
- package/index.ts +171 -54
- package/package.json +1 -2
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,16 @@ export { getFinalMessageStreamPreview, renderFinalMessageImages, renderHtmlBlock
|
|
|
5
5
|
type TelegramUpdate = {
|
|
6
6
|
message?: {
|
|
7
7
|
text?: string;
|
|
8
|
+
caption?: string;
|
|
9
|
+
voice?: {
|
|
10
|
+
file_id?: string;
|
|
11
|
+
mime_type?: string;
|
|
12
|
+
};
|
|
13
|
+
audio?: {
|
|
14
|
+
file_id?: string;
|
|
15
|
+
file_name?: string;
|
|
16
|
+
mime_type?: string;
|
|
17
|
+
};
|
|
8
18
|
chat?: {
|
|
9
19
|
id?: number | string;
|
|
10
20
|
};
|
|
@@ -37,6 +47,24 @@ export declare class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
|
|
|
37
47
|
externalUserId: string;
|
|
38
48
|
userTimeZone: string;
|
|
39
49
|
metadata: {
|
|
50
|
+
isStartCommand: boolean;
|
|
51
|
+
startPayload: string | null;
|
|
52
|
+
telegramUpdate: TelegramUpdate;
|
|
53
|
+
};
|
|
54
|
+
audio?: undefined;
|
|
55
|
+
} | {
|
|
56
|
+
surface: string;
|
|
57
|
+
prompt: string;
|
|
58
|
+
audio: {
|
|
59
|
+
buffer: Buffer<ArrayBuffer>;
|
|
60
|
+
filename: string;
|
|
61
|
+
mimeType: string;
|
|
62
|
+
};
|
|
63
|
+
externalConversationId: string;
|
|
64
|
+
externalUserId: string;
|
|
65
|
+
userTimeZone: string;
|
|
66
|
+
metadata: {
|
|
67
|
+
isStartCommand: boolean;
|
|
40
68
|
startPayload: string | null;
|
|
41
69
|
telegramUpdate: TelegramUpdate;
|
|
42
70
|
};
|
|
@@ -46,6 +74,10 @@ export declare class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
|
|
|
46
74
|
private sendFinalMessage;
|
|
47
75
|
private sendPhoto;
|
|
48
76
|
private sendChatAction;
|
|
77
|
+
private downloadTelegramFile;
|
|
78
|
+
private sendAudioFile;
|
|
79
|
+
private telegramMultipart;
|
|
80
|
+
private telegramJson;
|
|
49
81
|
private sendMessageDraft;
|
|
50
82
|
}
|
|
51
83
|
export default TelegramChatSurfaceAdapter;
|
package/dist/index.js
CHANGED
|
@@ -45,12 +45,14 @@ function splitTelegramMessage(text) {
|
|
|
45
45
|
}
|
|
46
46
|
return chunks;
|
|
47
47
|
}
|
|
48
|
-
function
|
|
48
|
+
function parseTelegramStartCommand(text) {
|
|
49
49
|
const [command, ...payloadParts] = text.trim().split(TELEGRAM_COMMAND_PARTS_RE);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const isStartCommand = command === TELEGRAM_START_COMMAND_PREFIX ||
|
|
51
|
+
command.startsWith(`${TELEGRAM_START_COMMAND_PREFIX}@`);
|
|
52
|
+
return {
|
|
53
|
+
isStartCommand,
|
|
54
|
+
payload: isStartCommand ? payloadParts.join(" ") || null : null,
|
|
55
|
+
};
|
|
54
56
|
}
|
|
55
57
|
export class TelegramChatSurfaceAdapter {
|
|
56
58
|
constructor(options) {
|
|
@@ -71,27 +73,58 @@ export class TelegramChatSurfaceAdapter {
|
|
|
71
73
|
}
|
|
72
74
|
parseIncomingMessage(ctx) {
|
|
73
75
|
return __awaiter(this, void 0, void 0, function* () {
|
|
74
|
-
var _a, _b, _c, _d, _e;
|
|
76
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
75
77
|
if (this.options.webhookSecret
|
|
76
78
|
&& getHeaderValue(ctx.headers, TELEGRAM_SECRET_HEADER) !== this.options.webhookSecret) {
|
|
77
79
|
return null;
|
|
78
80
|
}
|
|
79
81
|
const update = ctx.body;
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
82
|
+
const message = update.message;
|
|
83
|
+
const text = message === null || message === void 0 ? void 0 : message.text;
|
|
84
|
+
const chatId = (_a = message === null || message === void 0 ? void 0 : message.chat) === null || _a === void 0 ? void 0 : _a.id;
|
|
85
|
+
const userId = (_b = message === null || message === void 0 ? void 0 : message.from) === null || _b === void 0 ? void 0 : _b.id;
|
|
86
|
+
if (chatId === undefined || userId === undefined) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const startCommand = text
|
|
90
|
+
? parseTelegramStartCommand(text)
|
|
91
|
+
: { isStartCommand: false, payload: null };
|
|
92
|
+
if (text) {
|
|
93
|
+
return {
|
|
94
|
+
surface: this.name,
|
|
95
|
+
prompt: text,
|
|
96
|
+
externalConversationId: String(chatId),
|
|
97
|
+
externalUserId: String(userId),
|
|
98
|
+
userTimeZone: "UTC",
|
|
99
|
+
metadata: {
|
|
100
|
+
isStartCommand: startCommand.isStartCommand,
|
|
101
|
+
startPayload: startCommand.payload,
|
|
102
|
+
telegramUpdate: update,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const voiceFileId = (_c = message === null || message === void 0 ? void 0 : message.voice) === null || _c === void 0 ? void 0 : _c.file_id;
|
|
107
|
+
const audioFileId = (_d = message === null || message === void 0 ? void 0 : message.audio) === null || _d === void 0 ? void 0 : _d.file_id;
|
|
108
|
+
const fileId = voiceFileId !== null && voiceFileId !== void 0 ? voiceFileId : audioFileId;
|
|
109
|
+
if (!fileId) {
|
|
84
110
|
return null;
|
|
85
111
|
}
|
|
86
|
-
const
|
|
112
|
+
const audio = yield this.downloadTelegramFile({
|
|
113
|
+
fileId,
|
|
114
|
+
filename: (_f = (_e = message === null || message === void 0 ? void 0 : message.audio) === null || _e === void 0 ? void 0 : _e.file_name) !== null && _f !== void 0 ? _f : (voiceFileId ? "telegram-voice.ogg" : "telegram-audio"),
|
|
115
|
+
mimeType: (_k = (_h = (_g = message === null || message === void 0 ? void 0 : message.voice) === null || _g === void 0 ? void 0 : _g.mime_type) !== null && _h !== void 0 ? _h : (_j = message === null || message === void 0 ? void 0 : message.audio) === null || _j === void 0 ? void 0 : _j.mime_type) !== null && _k !== void 0 ? _k : "application/octet-stream",
|
|
116
|
+
abortSignal: ctx.abortSignal,
|
|
117
|
+
});
|
|
87
118
|
return {
|
|
88
119
|
surface: this.name,
|
|
89
|
-
prompt:
|
|
120
|
+
prompt: (_l = message === null || message === void 0 ? void 0 : message.caption) !== null && _l !== void 0 ? _l : "",
|
|
121
|
+
audio,
|
|
90
122
|
externalConversationId: String(chatId),
|
|
91
123
|
externalUserId: String(userId),
|
|
92
124
|
userTimeZone: "UTC",
|
|
93
125
|
metadata: {
|
|
94
|
-
|
|
126
|
+
isStartCommand: startCommand.isStartCommand,
|
|
127
|
+
startPayload: startCommand.payload,
|
|
95
128
|
telegramUpdate: update,
|
|
96
129
|
},
|
|
97
130
|
};
|
|
@@ -182,6 +215,10 @@ export class TelegramChatSurfaceAdapter {
|
|
|
182
215
|
yield this.sendFinalMessage(chatId, text || event.text);
|
|
183
216
|
return;
|
|
184
217
|
}
|
|
218
|
+
if (event.type === "audio") {
|
|
219
|
+
yield this.sendAudioFile(chatId, event.audio, event.filename, event.mimeType);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
185
222
|
if (event.type === "error") {
|
|
186
223
|
done = true;
|
|
187
224
|
stopTyping();
|
|
@@ -202,20 +239,11 @@ export class TelegramChatSurfaceAdapter {
|
|
|
202
239
|
return;
|
|
203
240
|
}
|
|
204
241
|
for (const chunk of splitTelegramMessage(text)) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
},
|
|
210
|
-
body: JSON.stringify({
|
|
211
|
-
chat_id: chatId,
|
|
212
|
-
text: chunk,
|
|
213
|
-
parse_mode: "Markdown",
|
|
214
|
-
}),
|
|
242
|
+
yield this.telegramJson("sendMessage", {
|
|
243
|
+
chat_id: chatId,
|
|
244
|
+
text: chunk,
|
|
245
|
+
parse_mode: "Markdown",
|
|
215
246
|
});
|
|
216
|
-
if (!response.ok) {
|
|
217
|
-
throw new Error(`Telegram sendMessage failed: ${response.status} ${yield response.text()}`);
|
|
218
|
-
}
|
|
219
247
|
}
|
|
220
248
|
});
|
|
221
249
|
}
|
|
@@ -236,49 +264,87 @@ export class TelegramChatSurfaceAdapter {
|
|
|
236
264
|
formData.append("photo", new Blob([photoBytes], {
|
|
237
265
|
type: "image/png",
|
|
238
266
|
}), filename);
|
|
239
|
-
|
|
267
|
+
yield this.telegramMultipart("sendPhoto", formData);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
sendChatAction(chatId, action) {
|
|
271
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
272
|
+
yield this.telegramJson("sendChatAction", {
|
|
273
|
+
chat_id: chatId,
|
|
274
|
+
action,
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
downloadTelegramFile(input) {
|
|
279
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
280
|
+
var _a, _b;
|
|
281
|
+
const fileResponse = yield fetch(`${TELEGRAM_API_BASE_URL}/bot${this.options.botToken}/getFile?file_id=${encodeURIComponent(input.fileId)}`, { signal: input.abortSignal });
|
|
282
|
+
if (!fileResponse.ok) {
|
|
283
|
+
throw new Error(`Telegram getFile failed: ${fileResponse.status} ${yield fileResponse.text()}`);
|
|
284
|
+
}
|
|
285
|
+
const fileData = yield fileResponse.json();
|
|
286
|
+
const filePath = (_a = fileData.result) === null || _a === void 0 ? void 0 : _a.file_path;
|
|
287
|
+
if (!fileData.ok || !filePath) {
|
|
288
|
+
throw new Error(`Telegram getFile failed: ${(_b = fileData.description) !== null && _b !== void 0 ? _b : "file_path is missing"}`);
|
|
289
|
+
}
|
|
290
|
+
const downloadResponse = yield fetch(`${TELEGRAM_API_BASE_URL}/file/bot${this.options.botToken}/${filePath}`, { signal: input.abortSignal });
|
|
291
|
+
if (!downloadResponse.ok) {
|
|
292
|
+
throw new Error(`Telegram file download failed: ${downloadResponse.status} ${yield downloadResponse.text()}`);
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
buffer: Buffer.from(yield downloadResponse.arrayBuffer()),
|
|
296
|
+
filename: input.filename,
|
|
297
|
+
mimeType: input.mimeType,
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
sendAudioFile(chatId, audio, filename, mimeType) {
|
|
302
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
303
|
+
const sendAsVoice = ["audio/ogg", "audio/opus"].includes(mimeType) || filename.endsWith(".ogg") || filename.endsWith(".opus");
|
|
304
|
+
const method = sendAsVoice ? "sendVoice" : "sendAudio";
|
|
305
|
+
const fieldName = sendAsVoice ? "voice" : "audio";
|
|
306
|
+
const audioBytes = new Uint8Array(audio);
|
|
307
|
+
const formData = new FormData();
|
|
308
|
+
formData.append("chat_id", chatId);
|
|
309
|
+
formData.append(fieldName, new Blob([audioBytes], {
|
|
310
|
+
type: mimeType,
|
|
311
|
+
}), filename);
|
|
312
|
+
yield this.telegramMultipart(method, formData);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
telegramMultipart(method, formData) {
|
|
316
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
317
|
+
const response = yield fetch(`${TELEGRAM_API_BASE_URL}/bot${this.options.botToken}/${method}`, {
|
|
240
318
|
method: "POST",
|
|
241
319
|
body: formData,
|
|
242
320
|
});
|
|
243
321
|
if (!response.ok) {
|
|
244
|
-
throw new Error(`Telegram
|
|
322
|
+
throw new Error(`Telegram ${method} failed: ${response.status} ${yield response.text()}`);
|
|
245
323
|
}
|
|
246
324
|
});
|
|
247
325
|
}
|
|
248
|
-
|
|
326
|
+
telegramJson(method, body) {
|
|
249
327
|
return __awaiter(this, void 0, void 0, function* () {
|
|
250
|
-
const response = yield fetch(`${TELEGRAM_API_BASE_URL}/bot${this.options.botToken}
|
|
328
|
+
const response = yield fetch(`${TELEGRAM_API_BASE_URL}/bot${this.options.botToken}/${method}`, {
|
|
251
329
|
method: "POST",
|
|
252
330
|
headers: {
|
|
253
331
|
"Content-Type": "application/json",
|
|
254
332
|
},
|
|
255
|
-
body: JSON.stringify(
|
|
256
|
-
chat_id: chatId,
|
|
257
|
-
action,
|
|
258
|
-
}),
|
|
333
|
+
body: JSON.stringify(body),
|
|
259
334
|
});
|
|
260
335
|
if (!response.ok) {
|
|
261
|
-
throw new Error(`Telegram
|
|
336
|
+
throw new Error(`Telegram ${method} failed: ${response.status} ${yield response.text()}`);
|
|
262
337
|
}
|
|
263
338
|
});
|
|
264
339
|
}
|
|
265
340
|
sendMessageDraft(input) {
|
|
266
341
|
return __awaiter(this, void 0, void 0, function* () {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
body: JSON.stringify({
|
|
273
|
-
chat_id: Number(input.chatId),
|
|
274
|
-
draft_id: input.draftId,
|
|
275
|
-
text: input.text,
|
|
276
|
-
parse_mode: input.parseMode,
|
|
277
|
-
}),
|
|
342
|
+
yield this.telegramJson("sendMessageDraft", {
|
|
343
|
+
chat_id: Number(input.chatId),
|
|
344
|
+
draft_id: input.draftId,
|
|
345
|
+
text: input.text,
|
|
346
|
+
parse_mode: input.parseMode,
|
|
278
347
|
});
|
|
279
|
-
if (!response.ok) {
|
|
280
|
-
throw new Error(`Telegram sendMessageDraft failed: ${response.status} ${yield response.text()}`);
|
|
281
|
-
}
|
|
282
348
|
});
|
|
283
349
|
}
|
|
284
350
|
}
|
package/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ChatSurfaceAdapter,
|
|
3
|
+
type ChatSurfaceEvent,
|
|
3
4
|
type ChatSurfaceEventSink,
|
|
4
5
|
type ChatSurfaceIncomingMessage,
|
|
5
6
|
type ChatSurfaceRequestContext,
|
|
@@ -24,6 +25,16 @@ export {
|
|
|
24
25
|
type TelegramUpdate = {
|
|
25
26
|
message?: {
|
|
26
27
|
text?: string;
|
|
28
|
+
caption?: string;
|
|
29
|
+
voice?: {
|
|
30
|
+
file_id?: string;
|
|
31
|
+
mime_type?: string;
|
|
32
|
+
};
|
|
33
|
+
audio?: {
|
|
34
|
+
file_id?: string;
|
|
35
|
+
file_name?: string;
|
|
36
|
+
mime_type?: string;
|
|
37
|
+
};
|
|
27
38
|
chat?: {
|
|
28
39
|
id?: number | string;
|
|
29
40
|
};
|
|
@@ -37,12 +48,29 @@ type TelegramUpdate = {
|
|
|
37
48
|
};
|
|
38
49
|
};
|
|
39
50
|
|
|
51
|
+
type TelegramGetFileResponse = {
|
|
52
|
+
ok: boolean;
|
|
53
|
+
result?: {
|
|
54
|
+
file_path?: string;
|
|
55
|
+
};
|
|
56
|
+
description?: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
40
59
|
type ChatSurfaceConnectAction = {
|
|
41
60
|
type: "url";
|
|
42
61
|
label: string;
|
|
43
62
|
url: string;
|
|
44
63
|
};
|
|
45
64
|
|
|
65
|
+
type TelegramChatSurfaceEvent =
|
|
66
|
+
| ChatSurfaceEvent
|
|
67
|
+
| {
|
|
68
|
+
type: "audio";
|
|
69
|
+
audio: Buffer;
|
|
70
|
+
filename: string;
|
|
71
|
+
mimeType: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
46
74
|
const TELEGRAM_API_BASE_URL = "https://api.telegram.org";
|
|
47
75
|
const TELEGRAM_SECRET_HEADER = "x-telegram-bot-api-secret-token";
|
|
48
76
|
const TELEGRAM_MESSAGE_MAX_LENGTH = 4096;
|
|
@@ -93,14 +121,16 @@ function splitTelegramMessage(text: string) {
|
|
|
93
121
|
return chunks;
|
|
94
122
|
}
|
|
95
123
|
|
|
96
|
-
function
|
|
124
|
+
function parseTelegramStartCommand(text: string) {
|
|
97
125
|
const [command, ...payloadParts] = text.trim().split(TELEGRAM_COMMAND_PARTS_RE);
|
|
126
|
+
const isStartCommand =
|
|
127
|
+
command === TELEGRAM_START_COMMAND_PREFIX ||
|
|
128
|
+
command.startsWith(`${TELEGRAM_START_COMMAND_PREFIX}@`);
|
|
98
129
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return payloadParts.join(" ") || null;
|
|
130
|
+
return {
|
|
131
|
+
isStartCommand,
|
|
132
|
+
payload: isStartCommand ? payloadParts.join(" ") || null : null,
|
|
133
|
+
};
|
|
104
134
|
}
|
|
105
135
|
|
|
106
136
|
export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
|
|
@@ -132,24 +162,59 @@ export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
|
|
|
132
162
|
}
|
|
133
163
|
|
|
134
164
|
const update = ctx.body as TelegramUpdate;
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
165
|
+
const message = update.message;
|
|
166
|
+
const text = message?.text;
|
|
167
|
+
const chatId = message?.chat?.id;
|
|
168
|
+
const userId = message?.from?.id;
|
|
138
169
|
|
|
139
|
-
if (
|
|
170
|
+
if (chatId === undefined || userId === undefined) {
|
|
140
171
|
return null;
|
|
141
172
|
}
|
|
142
173
|
|
|
143
|
-
const
|
|
174
|
+
const startCommand = text
|
|
175
|
+
? parseTelegramStartCommand(text)
|
|
176
|
+
: { isStartCommand: false, payload: null };
|
|
177
|
+
|
|
178
|
+
if (text) {
|
|
179
|
+
return {
|
|
180
|
+
surface: this.name,
|
|
181
|
+
prompt: text,
|
|
182
|
+
externalConversationId: String(chatId),
|
|
183
|
+
externalUserId: String(userId),
|
|
184
|
+
userTimeZone: "UTC",
|
|
185
|
+
metadata: {
|
|
186
|
+
isStartCommand: startCommand.isStartCommand,
|
|
187
|
+
startPayload: startCommand.payload,
|
|
188
|
+
telegramUpdate: update,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const voiceFileId = message?.voice?.file_id;
|
|
194
|
+
const audioFileId = message?.audio?.file_id;
|
|
195
|
+
const fileId = voiceFileId ?? audioFileId;
|
|
196
|
+
|
|
197
|
+
if (!fileId) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const audio = await this.downloadTelegramFile({
|
|
202
|
+
fileId,
|
|
203
|
+
filename: message?.audio?.file_name ?? (voiceFileId ? "telegram-voice.ogg" : "telegram-audio"),
|
|
204
|
+
mimeType: message?.voice?.mime_type ?? message?.audio?.mime_type ?? "application/octet-stream",
|
|
205
|
+
abortSignal: ctx.abortSignal,
|
|
206
|
+
});
|
|
144
207
|
|
|
145
208
|
return {
|
|
146
209
|
surface: this.name,
|
|
147
|
-
prompt:
|
|
210
|
+
prompt: message?.caption ?? "",
|
|
211
|
+
audio,
|
|
148
212
|
externalConversationId: String(chatId),
|
|
149
213
|
externalUserId: String(userId),
|
|
150
214
|
userTimeZone: "UTC",
|
|
151
215
|
metadata: {
|
|
152
|
-
|
|
216
|
+
isStartCommand: startCommand.isStartCommand,
|
|
217
|
+
startPayload: startCommand.payload,
|
|
153
218
|
telegramUpdate: update,
|
|
154
219
|
},
|
|
155
220
|
};
|
|
@@ -245,7 +310,7 @@ export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
|
|
|
245
310
|
startTyping();
|
|
246
311
|
|
|
247
312
|
return {
|
|
248
|
-
emit: async (event) => {
|
|
313
|
+
emit: async (event: TelegramChatSurfaceEvent) => {
|
|
249
314
|
if (closed) {
|
|
250
315
|
return;
|
|
251
316
|
}
|
|
@@ -265,9 +330,16 @@ export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
|
|
|
265
330
|
stopTyping();
|
|
266
331
|
clearDraftTimer();
|
|
267
332
|
|
|
268
|
-
await this.sendFinalMessage(
|
|
333
|
+
await this.sendFinalMessage(chatId, text || event.text);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (event.type === "audio") {
|
|
338
|
+
await this.sendAudioFile(
|
|
269
339
|
chatId,
|
|
270
|
-
|
|
340
|
+
event.audio,
|
|
341
|
+
event.filename,
|
|
342
|
+
event.mimeType,
|
|
271
343
|
);
|
|
272
344
|
return;
|
|
273
345
|
}
|
|
@@ -298,21 +370,11 @@ export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
|
|
|
298
370
|
}
|
|
299
371
|
|
|
300
372
|
for (const chunk of splitTelegramMessage(text)) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
},
|
|
306
|
-
body: JSON.stringify({
|
|
307
|
-
chat_id: chatId,
|
|
308
|
-
text: chunk,
|
|
309
|
-
parse_mode: "Markdown",
|
|
310
|
-
}),
|
|
373
|
+
await this.telegramJson("sendMessage", {
|
|
374
|
+
chat_id: chatId,
|
|
375
|
+
text: chunk,
|
|
376
|
+
parse_mode: "Markdown",
|
|
311
377
|
});
|
|
312
|
-
|
|
313
|
-
if (!response.ok) {
|
|
314
|
-
throw new Error(`Telegram sendMessage failed: ${response.status} ${await response.text()}`);
|
|
315
|
-
}
|
|
316
378
|
}
|
|
317
379
|
}
|
|
318
380
|
|
|
@@ -334,30 +396,95 @@ export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
|
|
|
334
396
|
type: "image/png",
|
|
335
397
|
}), filename);
|
|
336
398
|
|
|
337
|
-
|
|
399
|
+
await this.telegramMultipart("sendPhoto", formData);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private async sendChatAction(chatId: string, action: "typing" | "upload_voice" | "upload_audio") {
|
|
403
|
+
await this.telegramJson("sendChatAction", {
|
|
404
|
+
chat_id: chatId,
|
|
405
|
+
action,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private async downloadTelegramFile(input: {
|
|
410
|
+
fileId: string;
|
|
411
|
+
filename: string;
|
|
412
|
+
mimeType: string;
|
|
413
|
+
abortSignal: AbortSignal;
|
|
414
|
+
}) {
|
|
415
|
+
const fileResponse = await fetch(
|
|
416
|
+
`${TELEGRAM_API_BASE_URL}/bot${this.options.botToken}/getFile?file_id=${encodeURIComponent(input.fileId)}`,
|
|
417
|
+
{ signal: input.abortSignal },
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
if (!fileResponse.ok) {
|
|
421
|
+
throw new Error(`Telegram getFile failed: ${fileResponse.status} ${await fileResponse.text()}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const fileData = await fileResponse.json() as TelegramGetFileResponse;
|
|
425
|
+
const filePath = fileData.result?.file_path;
|
|
426
|
+
|
|
427
|
+
if (!fileData.ok || !filePath) {
|
|
428
|
+
throw new Error(`Telegram getFile failed: ${fileData.description ?? "file_path is missing"}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const downloadResponse = await fetch(
|
|
432
|
+
`${TELEGRAM_API_BASE_URL}/file/bot${this.options.botToken}/${filePath}`,
|
|
433
|
+
{ signal: input.abortSignal },
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
if (!downloadResponse.ok) {
|
|
437
|
+
throw new Error(`Telegram file download failed: ${downloadResponse.status} ${await downloadResponse.text()}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
buffer: Buffer.from(await downloadResponse.arrayBuffer()),
|
|
442
|
+
filename: input.filename,
|
|
443
|
+
mimeType: input.mimeType,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private async sendAudioFile(
|
|
448
|
+
chatId: string,
|
|
449
|
+
audio: Buffer,
|
|
450
|
+
filename: string,
|
|
451
|
+
mimeType: string,
|
|
452
|
+
) {
|
|
453
|
+
const sendAsVoice = ["audio/ogg", "audio/opus"].includes(mimeType) || filename.endsWith(".ogg") || filename.endsWith(".opus");
|
|
454
|
+
const method = sendAsVoice ? "sendVoice" : "sendAudio";
|
|
455
|
+
const fieldName = sendAsVoice ? "voice" : "audio";
|
|
456
|
+
const audioBytes = new Uint8Array(audio);
|
|
457
|
+
const formData = new FormData();
|
|
458
|
+
formData.append("chat_id", chatId);
|
|
459
|
+
formData.append(fieldName, new Blob([audioBytes], {
|
|
460
|
+
type: mimeType,
|
|
461
|
+
}), filename);
|
|
462
|
+
|
|
463
|
+
await this.telegramMultipart(method, formData);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private async telegramMultipart(method: string, formData: FormData) {
|
|
467
|
+
const response = await fetch(`${TELEGRAM_API_BASE_URL}/bot${this.options.botToken}/${method}`, {
|
|
338
468
|
method: "POST",
|
|
339
469
|
body: formData,
|
|
340
470
|
});
|
|
341
471
|
|
|
342
472
|
if (!response.ok) {
|
|
343
|
-
throw new Error(`Telegram
|
|
473
|
+
throw new Error(`Telegram ${method} failed: ${response.status} ${await response.text()}`);
|
|
344
474
|
}
|
|
345
475
|
}
|
|
346
476
|
|
|
347
|
-
private async
|
|
348
|
-
const response = await fetch(`${TELEGRAM_API_BASE_URL}/bot${this.options.botToken}
|
|
477
|
+
private async telegramJson(method: string, body: unknown) {
|
|
478
|
+
const response = await fetch(`${TELEGRAM_API_BASE_URL}/bot${this.options.botToken}/${method}`, {
|
|
349
479
|
method: "POST",
|
|
350
480
|
headers: {
|
|
351
481
|
"Content-Type": "application/json",
|
|
352
482
|
},
|
|
353
|
-
body: JSON.stringify(
|
|
354
|
-
chat_id: chatId,
|
|
355
|
-
action,
|
|
356
|
-
}),
|
|
483
|
+
body: JSON.stringify(body),
|
|
357
484
|
});
|
|
358
485
|
|
|
359
486
|
if (!response.ok) {
|
|
360
|
-
throw new Error(`Telegram
|
|
487
|
+
throw new Error(`Telegram ${method} failed: ${response.status} ${await response.text()}`);
|
|
361
488
|
}
|
|
362
489
|
}
|
|
363
490
|
|
|
@@ -367,22 +494,12 @@ export class TelegramChatSurfaceAdapter implements ChatSurfaceAdapter {
|
|
|
367
494
|
text: string;
|
|
368
495
|
parseMode?: "Markdown" | "MarkdownV2" | "HTML";
|
|
369
496
|
}) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
body: JSON.stringify({
|
|
376
|
-
chat_id: Number(input.chatId),
|
|
377
|
-
draft_id: input.draftId,
|
|
378
|
-
text: input.text,
|
|
379
|
-
parse_mode: input.parseMode,
|
|
380
|
-
}),
|
|
497
|
+
await this.telegramJson("sendMessageDraft", {
|
|
498
|
+
chat_id: Number(input.chatId),
|
|
499
|
+
draft_id: input.draftId,
|
|
500
|
+
text: input.text,
|
|
501
|
+
parse_mode: input.parseMode,
|
|
381
502
|
});
|
|
382
|
-
|
|
383
|
-
if (!response.ok) {
|
|
384
|
-
throw new Error(`Telegram sendMessageDraft failed: ${response.status} ${await response.text()}`);
|
|
385
|
-
}
|
|
386
503
|
}
|
|
387
504
|
}
|
|
388
505
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/chat-surface-adapter-telegram",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"playwright": "^1.57.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@adminforth/agent": ">=1.0.2",
|
|
37
36
|
"adminforth": ">=2.60.0"
|
|
38
37
|
},
|
|
39
38
|
"release": {
|