@krischoichoi/channel-telegram 0.6.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 +129 -0
- package/lib/adapter.d.ts +94 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +417 -0
- package/lib/adapter.js.map +1 -0
- package/lib/api-error.d.ts +74 -0
- package/lib/api-error.d.ts.map +1 -0
- package/lib/api-error.js +103 -0
- package/lib/api-error.js.map +1 -0
- package/lib/config.d.ts +115 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +67 -0
- package/lib/config.js.map +1 -0
- package/lib/definition.d.ts +59 -0
- package/lib/definition.d.ts.map +1 -0
- package/lib/definition.js +158 -0
- package/lib/definition.js.map +1 -0
- package/lib/inbound.d.ts +61 -0
- package/lib/inbound.d.ts.map +1 -0
- package/lib/inbound.js +125 -0
- package/lib/inbound.js.map +1 -0
- package/lib/index.d.ts +52 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +114 -0
- package/lib/index.js.map +1 -0
- package/lib/manifest.d.ts +16 -0
- package/lib/manifest.d.ts.map +1 -0
- package/lib/manifest.js +26 -0
- package/lib/manifest.js.map +1 -0
- package/lib/mapper.d.ts +51 -0
- package/lib/mapper.d.ts.map +1 -0
- package/lib/mapper.js +314 -0
- package/lib/mapper.js.map +1 -0
- package/lib/media-hydrator.d.ts +46 -0
- package/lib/media-hydrator.d.ts.map +1 -0
- package/lib/media-hydrator.js +85 -0
- package/lib/media-hydrator.js.map +1 -0
- package/lib/outbound.d.ts +42 -0
- package/lib/outbound.d.ts.map +1 -0
- package/lib/outbound.js +215 -0
- package/lib/outbound.js.map +1 -0
- package/lib/render/html.d.ts +13 -0
- package/lib/render/html.d.ts.map +1 -0
- package/lib/render/html.js +110 -0
- package/lib/render/html.js.map +1 -0
- package/lib/render/index.d.ts +64 -0
- package/lib/render/index.d.ts.map +1 -0
- package/lib/render/index.js +76 -0
- package/lib/render/index.js.map +1 -0
- package/lib/render/markdown.d.ts +38 -0
- package/lib/render/markdown.d.ts.map +1 -0
- package/lib/render/markdown.js +187 -0
- package/lib/render/markdown.js.map +1 -0
- package/lib/render/plain.d.ts +43 -0
- package/lib/render/plain.d.ts.map +1 -0
- package/lib/render/plain.js +97 -0
- package/lib/render/plain.js.map +1 -0
- package/lib/render/segment.d.ts +39 -0
- package/lib/render/segment.d.ts.map +1 -0
- package/lib/render/segment.js +117 -0
- package/lib/render/segment.js.map +1 -0
- package/lib/rich-message.d.ts +75 -0
- package/lib/rich-message.d.ts.map +1 -0
- package/lib/rich-message.js +11 -0
- package/lib/rich-message.js.map +1 -0
- package/lib/rich-streaming-reply.d.ts +69 -0
- package/lib/rich-streaming-reply.d.ts.map +1 -0
- package/lib/rich-streaming-reply.js +207 -0
- package/lib/rich-streaming-reply.js.map +1 -0
- package/lib/streaming-reply.d.ts +98 -0
- package/lib/streaming-reply.d.ts.map +1 -0
- package/lib/streaming-reply.js +274 -0
- package/lib/streaming-reply.js.map +1 -0
- package/lib/transport.d.ts +38 -0
- package/lib/transport.d.ts.map +1 -0
- package/lib/transport.js +91 -0
- package/lib/transport.js.map +1 -0
- package/lib/upstream.d.ts +174 -0
- package/lib/upstream.d.ts.map +1 -0
- package/lib/upstream.js +452 -0
- package/lib/upstream.js.map +1 -0
- package/package.json +52 -0
package/lib/mapper.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { textParts } from '@krischoichoi/channel-core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/** An untrusted Telegram update did not satisfy the canonical identity schema. */
|
|
4
|
+
export class TelegramInboundValidationError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'TelegramInboundValidationError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const telegramInteger = z.number().int().refine(Number.isSafeInteger, {
|
|
11
|
+
message: 'expected a safe integer',
|
|
12
|
+
});
|
|
13
|
+
const telegramChatSchema = z.object({
|
|
14
|
+
id: telegramInteger,
|
|
15
|
+
type: z.enum(['private', 'group', 'supergroup']),
|
|
16
|
+
first_name: z.string().optional(),
|
|
17
|
+
username: z.string().optional(),
|
|
18
|
+
title: z.string().optional(),
|
|
19
|
+
}).passthrough();
|
|
20
|
+
const telegramUserSchema = z.object({
|
|
21
|
+
id: telegramInteger,
|
|
22
|
+
is_bot: z.boolean().optional(),
|
|
23
|
+
first_name: z.string().optional(),
|
|
24
|
+
username: z.string().optional(),
|
|
25
|
+
}).passthrough();
|
|
26
|
+
const telegramMessageEntitySchema = z.object({
|
|
27
|
+
type: z.string(),
|
|
28
|
+
offset: telegramInteger.nonnegative(),
|
|
29
|
+
length: telegramInteger.nonnegative(),
|
|
30
|
+
user: telegramUserSchema.optional(),
|
|
31
|
+
}).passthrough();
|
|
32
|
+
const telegramPhotoSchema = z.object({
|
|
33
|
+
file_id: z.string(),
|
|
34
|
+
file_unique_id: z.string().optional(),
|
|
35
|
+
width: telegramInteger.optional(),
|
|
36
|
+
height: telegramInteger.optional(),
|
|
37
|
+
file_size: telegramInteger.nonnegative().optional(),
|
|
38
|
+
}).passthrough();
|
|
39
|
+
const telegramDocumentSchema = z.object({
|
|
40
|
+
file_id: z.string(),
|
|
41
|
+
file_name: z.string().optional(),
|
|
42
|
+
mime_type: z.string().optional(),
|
|
43
|
+
file_size: telegramInteger.nonnegative().optional(),
|
|
44
|
+
}).passthrough();
|
|
45
|
+
const telegramAudioSchema = z.object({
|
|
46
|
+
file_id: z.string(),
|
|
47
|
+
duration: telegramInteger.nonnegative().optional(),
|
|
48
|
+
mime_type: z.string().optional(),
|
|
49
|
+
file_name: z.string().optional(),
|
|
50
|
+
}).passthrough();
|
|
51
|
+
const telegramVoiceSchema = z.object({
|
|
52
|
+
file_id: z.string(),
|
|
53
|
+
duration: telegramInteger.nonnegative().optional(),
|
|
54
|
+
mime_type: z.string().optional(),
|
|
55
|
+
}).passthrough();
|
|
56
|
+
const telegramVideoSchema = z.object({
|
|
57
|
+
file_id: z.string(),
|
|
58
|
+
duration: telegramInteger.nonnegative().optional(),
|
|
59
|
+
mime_type: z.string().optional(),
|
|
60
|
+
file_name: z.string().optional(),
|
|
61
|
+
}).passthrough();
|
|
62
|
+
const telegramMessageSchema = z.object({
|
|
63
|
+
message_id: telegramInteger,
|
|
64
|
+
date: telegramInteger.optional(),
|
|
65
|
+
chat: telegramChatSchema,
|
|
66
|
+
from: telegramUserSchema,
|
|
67
|
+
text: z.string().optional(),
|
|
68
|
+
caption: z.string().optional(),
|
|
69
|
+
entities: z.array(telegramMessageEntitySchema).optional(),
|
|
70
|
+
caption_entities: z.array(telegramMessageEntitySchema).optional(),
|
|
71
|
+
message_thread_id: telegramInteger.optional(),
|
|
72
|
+
reply_to_message: z.object({ message_id: telegramInteger }).passthrough().optional(),
|
|
73
|
+
photo: z.array(telegramPhotoSchema).optional(),
|
|
74
|
+
document: telegramDocumentSchema.optional(),
|
|
75
|
+
audio: telegramAudioSchema.optional(),
|
|
76
|
+
voice: telegramVoiceSchema.optional(),
|
|
77
|
+
video: telegramVideoSchema.optional(),
|
|
78
|
+
}).passthrough();
|
|
79
|
+
const telegramMessageUpdateSchema = z.object({
|
|
80
|
+
update_id: telegramInteger.optional(),
|
|
81
|
+
message: telegramMessageSchema,
|
|
82
|
+
}).passthrough();
|
|
83
|
+
const telegramDedupSchema = z.object({
|
|
84
|
+
update_id: telegramInteger.optional(),
|
|
85
|
+
message: z.object({ message_id: telegramInteger.optional() }).passthrough().optional(),
|
|
86
|
+
callback_query: z.object({ id: z.string().optional() }).passthrough().optional(),
|
|
87
|
+
}).passthrough();
|
|
88
|
+
/** Message fields that carry metadata rather than content kind. */
|
|
89
|
+
const TELEGRAM_META_KEYS = new Set([
|
|
90
|
+
'message_id', 'date', 'chat', 'from', 'reply_to_message',
|
|
91
|
+
'edit_date', 'media_group_id', 'message_thread_id', 'entities', 'caption',
|
|
92
|
+
'caption_entities', 'author_signature', 'is_automatic_forward',
|
|
93
|
+
]);
|
|
94
|
+
/** Stable hash for ids when the platform omits them. */
|
|
95
|
+
export function simpleHash(input) {
|
|
96
|
+
let hash = 0;
|
|
97
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
98
|
+
hash = (hash * 31 + input.charCodeAt(i)) | 0;
|
|
99
|
+
}
|
|
100
|
+
return Math.abs(hash).toString(36);
|
|
101
|
+
}
|
|
102
|
+
/** Map one raw Telegram update into the stable channel event shape. */
|
|
103
|
+
export function mapInbound(raw, meta) {
|
|
104
|
+
const parsed = telegramMessageUpdateSchema.safeParse(raw);
|
|
105
|
+
if (!parsed.success) {
|
|
106
|
+
throw new TelegramInboundValidationError('telegram mapInbound: invalid message update');
|
|
107
|
+
}
|
|
108
|
+
const { message } = parsed.data;
|
|
109
|
+
const { chat, from } = message;
|
|
110
|
+
const sender = String(from.id);
|
|
111
|
+
// The chat id is the conversation id for both dm and group messages.
|
|
112
|
+
const conversationId = String(chat.id);
|
|
113
|
+
const messageId = String(message.message_id);
|
|
114
|
+
const conversationType = chat.type === 'group' || chat.type === 'supergroup' ? 'group' : 'dm';
|
|
115
|
+
const mentionedBot = conversationType === 'group'
|
|
116
|
+
? messageMentionedBot(message, meta.bot)
|
|
117
|
+
: undefined;
|
|
118
|
+
return {
|
|
119
|
+
type: 'message.received',
|
|
120
|
+
channel: meta.channel,
|
|
121
|
+
accountId: meta.accountId,
|
|
122
|
+
conversation: {
|
|
123
|
+
id: conversationId,
|
|
124
|
+
// 'private' → dm; 'group'/'supergroup' → group keyed by the chat id.
|
|
125
|
+
// The schema rejects unsupported chat types rather than treating them as
|
|
126
|
+
// a private conversation.
|
|
127
|
+
type: chat.type === 'group' || chat.type === 'supergroup' ? 'group' : 'dm',
|
|
128
|
+
...(message.message_thread_id !== undefined
|
|
129
|
+
? { threadId: String(message.message_thread_id) }
|
|
130
|
+
: {}),
|
|
131
|
+
},
|
|
132
|
+
sender: { id: sender, name: from.first_name },
|
|
133
|
+
message: {
|
|
134
|
+
id: messageId,
|
|
135
|
+
content: partsFor(message),
|
|
136
|
+
...(message.reply_to_message?.message_id !== undefined
|
|
137
|
+
? { replyTo: String(message.reply_to_message.message_id) }
|
|
138
|
+
: {}),
|
|
139
|
+
// Telegram timestamps are Unix seconds; the contract uses ms.
|
|
140
|
+
createdAt: message.date !== undefined ? message.date * 1000 : Date.now(),
|
|
141
|
+
...(mentionedBot !== undefined ? { activation: { mentionedBot } } : {}),
|
|
142
|
+
},
|
|
143
|
+
raw,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Telegram entity offsets use UTF-16 code units, matching JavaScript slice. */
|
|
147
|
+
function messageMentionedBot(message, bot) {
|
|
148
|
+
if (!bot)
|
|
149
|
+
return undefined;
|
|
150
|
+
const sources = [
|
|
151
|
+
{ text: message.text, entities: message.entities },
|
|
152
|
+
{ text: message.caption, entities: message.caption_entities },
|
|
153
|
+
];
|
|
154
|
+
for (const source of sources) {
|
|
155
|
+
if (!source.text || !source.entities)
|
|
156
|
+
continue;
|
|
157
|
+
for (const entity of source.entities) {
|
|
158
|
+
if (entity.type === 'text_mention' && entity.user?.id === bot.id)
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const username = bot.username?.replace(/^@/, '').toLowerCase();
|
|
163
|
+
if (!username)
|
|
164
|
+
return false;
|
|
165
|
+
for (const source of sources) {
|
|
166
|
+
if (!source.text || !source.entities)
|
|
167
|
+
continue;
|
|
168
|
+
for (const entity of source.entities) {
|
|
169
|
+
const value = source.text.slice(entity.offset, entity.offset + entity.length);
|
|
170
|
+
if (entity.type === 'mention' && value.slice(1).toLowerCase() === username)
|
|
171
|
+
return true;
|
|
172
|
+
if (entity.type === 'bot_command') {
|
|
173
|
+
const addressedTo = value.slice(value.lastIndexOf('@') + 1).toLowerCase();
|
|
174
|
+
if (value.includes('@') && addressedTo === username)
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
function partsFor(message) {
|
|
182
|
+
if (typeof message.text === 'string') {
|
|
183
|
+
return textParts(message.text);
|
|
184
|
+
}
|
|
185
|
+
if (Array.isArray(message.photo) && message.photo.length > 0) {
|
|
186
|
+
// Telegram sends several sizes; the last entry is the largest. file_id is
|
|
187
|
+
// a platform-opaque handle, so it maps to `resourceRef` — never `url`,
|
|
188
|
+
// which is reserved for real http(s) URLs. The inbound processor resolves
|
|
189
|
+
// this reference through getFile before emitting the channel event.
|
|
190
|
+
const last = message.photo[message.photo.length - 1];
|
|
191
|
+
const resourceRef = last?.file_id;
|
|
192
|
+
return withCaption(message.caption, [{ type: 'image', resourceRef }]);
|
|
193
|
+
}
|
|
194
|
+
if (message.document) {
|
|
195
|
+
return withCaption(message.caption, [{
|
|
196
|
+
type: 'file',
|
|
197
|
+
resourceRef: message.document.file_id,
|
|
198
|
+
name: message.document.file_name,
|
|
199
|
+
mimeType: message.document.mime_type,
|
|
200
|
+
}]);
|
|
201
|
+
}
|
|
202
|
+
if (message.audio) {
|
|
203
|
+
return withCaption(message.caption, [{
|
|
204
|
+
type: 'audio',
|
|
205
|
+
resourceRef: message.audio.file_id,
|
|
206
|
+
durationMs: message.audio.duration !== undefined ? message.audio.duration * 1000 : undefined,
|
|
207
|
+
mimeType: message.audio.mime_type,
|
|
208
|
+
}]);
|
|
209
|
+
}
|
|
210
|
+
if (message.voice) {
|
|
211
|
+
return [{
|
|
212
|
+
type: 'audio',
|
|
213
|
+
resourceRef: message.voice.file_id,
|
|
214
|
+
durationMs: message.voice.duration !== undefined ? message.voice.duration * 1000 : undefined,
|
|
215
|
+
mimeType: message.voice.mime_type,
|
|
216
|
+
}];
|
|
217
|
+
}
|
|
218
|
+
if (message.video) {
|
|
219
|
+
return withCaption(message.caption, [{
|
|
220
|
+
type: 'video',
|
|
221
|
+
resourceRef: message.video.file_id,
|
|
222
|
+
durationMs: message.video.duration !== undefined ? message.video.duration * 1000 : undefined,
|
|
223
|
+
mimeType: message.video.mime_type,
|
|
224
|
+
}]);
|
|
225
|
+
}
|
|
226
|
+
return [{ type: 'unsupported', reason: `unsupported telegram message type '${messageKind(message)}'` }];
|
|
227
|
+
}
|
|
228
|
+
function withCaption(caption, parts) {
|
|
229
|
+
return typeof caption === 'string' && caption.length > 0
|
|
230
|
+
? [...textParts(caption), ...parts]
|
|
231
|
+
: parts;
|
|
232
|
+
}
|
|
233
|
+
/** Best-effort content-kind name for the unsupported-part reason. */
|
|
234
|
+
function messageKind(message) {
|
|
235
|
+
for (const key of Object.keys(message)) {
|
|
236
|
+
if (!TELEGRAM_META_KEYS.has(key))
|
|
237
|
+
return key;
|
|
238
|
+
}
|
|
239
|
+
return 'unknown';
|
|
240
|
+
}
|
|
241
|
+
/** Dedup identity for raw updates (update_id, then message_id). */
|
|
242
|
+
export function dedupKey(raw) {
|
|
243
|
+
const parsed = telegramDedupSchema.safeParse(raw);
|
|
244
|
+
if (parsed.success) {
|
|
245
|
+
const update = parsed.data;
|
|
246
|
+
if (update.update_id !== undefined)
|
|
247
|
+
return `update-${update.update_id}`;
|
|
248
|
+
if (update.message?.message_id !== undefined)
|
|
249
|
+
return `message-${update.message.message_id}`;
|
|
250
|
+
if (update.callback_query?.id !== undefined)
|
|
251
|
+
return `callback-${update.callback_query.id}`;
|
|
252
|
+
}
|
|
253
|
+
return `telegram-${simpleHash(JSON.stringify(raw))}`;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Zod schema for a `callback_query` update. `callback_query.data` is UNTRUSTED
|
|
257
|
+
* client payload: it is validated as a plain string here and only ever echoed
|
|
258
|
+
* back as `InteractionReceived.action` (plan §5 / red line 5). The adapter never
|
|
259
|
+
* parses it into Harness question semantics — that interpretation belongs to
|
|
260
|
+
* `channel-harness`.
|
|
261
|
+
*/
|
|
262
|
+
const callbackQueryUpdateSchema = z.object({
|
|
263
|
+
update_id: telegramInteger.optional(),
|
|
264
|
+
callback_query: z.object({
|
|
265
|
+
id: z.string(),
|
|
266
|
+
from: telegramUserSchema,
|
|
267
|
+
message: z.object({
|
|
268
|
+
message_id: telegramInteger,
|
|
269
|
+
message_thread_id: telegramInteger.optional(),
|
|
270
|
+
chat: telegramChatSchema,
|
|
271
|
+
}).passthrough(),
|
|
272
|
+
data: z.string().optional(),
|
|
273
|
+
chat_instance: z.string().optional(),
|
|
274
|
+
}).passthrough(),
|
|
275
|
+
}).loose();
|
|
276
|
+
/** Whether a raw update carries a `callback_query` (message-bearing). */
|
|
277
|
+
export function isCallbackQueryUpdate(raw) {
|
|
278
|
+
return callbackQueryUpdateSchema.safeParse(raw).success;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Map a validated `callback_query` update to `InteractionReceived`. Always run
|
|
282
|
+
* `isCallbackQueryUpdate` first; this function re-parses defensively and throws
|
|
283
|
+
* a clear error for a malformed tolerance so the adapter fails closed rather
|
|
284
|
+
* than emitting a fabricated interaction.
|
|
285
|
+
*/
|
|
286
|
+
export function mapCallbackQuery(raw, meta) {
|
|
287
|
+
const parsed = callbackQueryUpdateSchema.safeParse(raw);
|
|
288
|
+
if (!parsed.success) {
|
|
289
|
+
throw new TelegramInboundValidationError('telegram mapCallbackQuery: invalid callback_query payload');
|
|
290
|
+
}
|
|
291
|
+
const cq = parsed.data.callback_query;
|
|
292
|
+
const { chat, message_thread_id: threadId } = cq.message;
|
|
293
|
+
const { from } = cq;
|
|
294
|
+
const sender = String(from.id);
|
|
295
|
+
const conversationId = String(chat.id);
|
|
296
|
+
return {
|
|
297
|
+
type: 'interaction.received',
|
|
298
|
+
channel: meta.channel,
|
|
299
|
+
accountId: meta.accountId,
|
|
300
|
+
conversation: {
|
|
301
|
+
id: conversationId,
|
|
302
|
+
// 'private' → dm; 'group'/'supergroup' → group keyed by the chat id.
|
|
303
|
+
type: chat.type === 'group' || chat.type === 'supergroup' ? 'group' : 'dm',
|
|
304
|
+
...(threadId !== undefined ? { threadId: String(threadId) } : {}),
|
|
305
|
+
},
|
|
306
|
+
sender: { id: sender, name: from.first_name },
|
|
307
|
+
interactionId: cq.id,
|
|
308
|
+
// The untrusted callback data is surfaced verbatim as the action; the
|
|
309
|
+
// adapter never interprets it (red line 5).
|
|
310
|
+
action: typeof cq.data === 'string' ? cq.data : '',
|
|
311
|
+
raw,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapper.js","sourceRoot":"","sources":["../src/mapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram inbound media hydration.
|
|
3
|
+
*
|
|
4
|
+
* The pure mapper maps Telegram file_id values to the contract's
|
|
5
|
+
* `resourceRef` carrier (an opaque platform handle — never `url`). This module
|
|
6
|
+
* resolves those handles through the platform upstream (`getFile` +
|
|
7
|
+
* `/file/bot<token>/<file_path>`) and places the trusted bytes on
|
|
8
|
+
* `localData`, exactly like the official adapters' hydrators do for their
|
|
9
|
+
* platform URLs.
|
|
10
|
+
*
|
|
11
|
+
* Every transportable binary kind — `image`, `file`, `audio` and `video`
|
|
12
|
+
* (voice notes and audio messages both map to AudioPart, video messages to
|
|
13
|
+
* VideoPart) — is hydrated: Transport is transport, so a `resourceRef` is
|
|
14
|
+
* resolved into real bytes before emit for whichever binary part carries one.
|
|
15
|
+
* The download seam is generic for any supported file_id (photo / document /
|
|
16
|
+
* voice / audio / video).
|
|
17
|
+
*
|
|
18
|
+
* Failure handling follows the shared contract: a download failure never
|
|
19
|
+
* blocks text delivery. The part keeps its `resourceRef` and records a stable
|
|
20
|
+
* de-identified `ingressFailure` code via `toIngressFailureCode` from
|
|
21
|
+
* `@krischoichoi/channel-core` (shared `BodyTooLargeError` maps to 'too-large').
|
|
22
|
+
*/
|
|
23
|
+
import { type ChannelLogger, type MessagePart } from '@krischoichoi/channel-core';
|
|
24
|
+
/** Platform file resolution seam (implemented by the upstream driver). */
|
|
25
|
+
export interface TelegramFileResolver {
|
|
26
|
+
downloadFile(fileId: string, signal?: AbortSignal): Promise<{
|
|
27
|
+
data: Uint8Array;
|
|
28
|
+
mimeType?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export interface TelegramMediaHydratorOptions {
|
|
33
|
+
/** Hard byte cap for one download. */
|
|
34
|
+
maxBytes: number;
|
|
35
|
+
/** External cancellation signal (from the adapter context). */
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
/** Optional logger for safe per-media failure diagnostics. */
|
|
38
|
+
logger?: Pick<ChannelLogger, 'warn'>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Hydrate Telegram `resourceRef` bytes on `parts` in place. Returns the
|
|
42
|
+
* mutated array. Never throws — every download failure is recorded as
|
|
43
|
+
* `ingressFailure` on the part and the event still carries the text parts.
|
|
44
|
+
*/
|
|
45
|
+
export declare function hydrateTelegramParts(parts: MessagePart[], resolver: TelegramFileResolver, options: TelegramMediaHydratorOptions): Promise<MessagePart[]>;
|
|
46
|
+
//# sourceMappingURL=media-hydrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-hydrator.d.ts","sourceRoot":"","sources":["../src/media-hydrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,4BAA4B,CAAC;AAEpC,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,YAAY,CACV,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,4BAA4B;IAC3C,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,WAAW,EAAE,EACpB,QAAQ,EAAE,oBAAoB,EAC9B,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,WAAW,EAAE,CAAC,CA6CxB"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram inbound media hydration.
|
|
3
|
+
*
|
|
4
|
+
* The pure mapper maps Telegram file_id values to the contract's
|
|
5
|
+
* `resourceRef` carrier (an opaque platform handle — never `url`). This module
|
|
6
|
+
* resolves those handles through the platform upstream (`getFile` +
|
|
7
|
+
* `/file/bot<token>/<file_path>`) and places the trusted bytes on
|
|
8
|
+
* `localData`, exactly like the official adapters' hydrators do for their
|
|
9
|
+
* platform URLs.
|
|
10
|
+
*
|
|
11
|
+
* Every transportable binary kind — `image`, `file`, `audio` and `video`
|
|
12
|
+
* (voice notes and audio messages both map to AudioPart, video messages to
|
|
13
|
+
* VideoPart) — is hydrated: Transport is transport, so a `resourceRef` is
|
|
14
|
+
* resolved into real bytes before emit for whichever binary part carries one.
|
|
15
|
+
* The download seam is generic for any supported file_id (photo / document /
|
|
16
|
+
* voice / audio / video).
|
|
17
|
+
*
|
|
18
|
+
* Failure handling follows the shared contract: a download failure never
|
|
19
|
+
* blocks text delivery. The part keeps its `resourceRef` and records a stable
|
|
20
|
+
* de-identified `ingressFailure` code via `toIngressFailureCode` from
|
|
21
|
+
* `@krischoichoi/channel-core` (shared `BodyTooLargeError` maps to 'too-large').
|
|
22
|
+
*/
|
|
23
|
+
import { BodyTooLargeError, isHydratableBinaryPart, toIngressFailureCode, } from '@krischoichoi/channel-core';
|
|
24
|
+
/**
|
|
25
|
+
* Hydrate Telegram `resourceRef` bytes on `parts` in place. Returns the
|
|
26
|
+
* mutated array. Never throws — every download failure is recorded as
|
|
27
|
+
* `ingressFailure` on the part and the event still carries the text parts.
|
|
28
|
+
*/
|
|
29
|
+
export async function hydrateTelegramParts(parts, resolver, options) {
|
|
30
|
+
const { maxBytes, signal, logger } = options;
|
|
31
|
+
await Promise.allSettled(parts.map(async (part) => {
|
|
32
|
+
// All four transportable binary kinds hydrate; anything else is ignored.
|
|
33
|
+
if (!isHydratableBinaryPart(part))
|
|
34
|
+
return;
|
|
35
|
+
const fileId = part.resourceRef;
|
|
36
|
+
if (typeof fileId !== 'string' || fileId.length === 0)
|
|
37
|
+
return;
|
|
38
|
+
// Trusted bytes already in hand take precedence — never re-download.
|
|
39
|
+
if (part.localData !== undefined || part.dataUri !== undefined)
|
|
40
|
+
return;
|
|
41
|
+
try {
|
|
42
|
+
const result = await resolver.downloadFile(fileId, signal);
|
|
43
|
+
if (result.data.byteLength > maxBytes) {
|
|
44
|
+
throw new BodyTooLargeError(maxBytes, `Telegram file ${result.data.byteLength} bytes exceeds the ${maxBytes} byte cap`);
|
|
45
|
+
}
|
|
46
|
+
part.localData = result.data;
|
|
47
|
+
// Actual downloaded bytes are authoritative over any previously-known
|
|
48
|
+
// size.
|
|
49
|
+
part.size = result.data.byteLength;
|
|
50
|
+
// Telegram's original message metadata is more authoritative than
|
|
51
|
+
// metadata reconstructed from the download response/path. Only fill
|
|
52
|
+
// missing hints so a generated file_path cannot replace a user name
|
|
53
|
+
// (photos carry no user-facing name, so image is left untouched).
|
|
54
|
+
if (part.mimeType === undefined && result.mimeType)
|
|
55
|
+
part.mimeType = result.mimeType;
|
|
56
|
+
if (part.type !== 'image' && part.name === undefined && result.name)
|
|
57
|
+
part.name = result.name;
|
|
58
|
+
// A successful hydration clears any previous failure marker.
|
|
59
|
+
delete part.ingressFailure;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// Keep the resourceRef, record the stable de-identified failure code.
|
|
63
|
+
part.ingressFailure = toIngressFailureCode(error);
|
|
64
|
+
logger?.warn('[channel-telegram] media hydration failed', {
|
|
65
|
+
type: part.type,
|
|
66
|
+
failure: part.ingressFailure,
|
|
67
|
+
error: safeErrorDetails(error),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
return parts;
|
|
72
|
+
}
|
|
73
|
+
/** Keep diagnostics useful without allowing bearer paths or raw payloads into logs. */
|
|
74
|
+
function safeErrorDetails(error) {
|
|
75
|
+
if (!(error instanceof Error))
|
|
76
|
+
return { message: 'unknown error' };
|
|
77
|
+
const value = error;
|
|
78
|
+
return {
|
|
79
|
+
name: value.name,
|
|
80
|
+
...(typeof value.code === 'string' ? { code: value.code } : {}),
|
|
81
|
+
message: value.message.replace(/\/file\/bot[^/?#]+/g, '/file/bot<redacted>')
|
|
82
|
+
.replace(/\/bot[^/?#]+/g, '/bot<redacted>'),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=media-hydrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-hydrator.js","sourceRoot":"","sources":["../src/media-hydrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound sending: channel message → Telegram Bot API payload → upstream.
|
|
3
|
+
*
|
|
4
|
+
* Text messages go through the configured formatting renderer (Rich Markdown /
|
|
5
|
+
* HTML / MarkdownV2 / plain) and `sendMessage`; messages carrying sendable media
|
|
6
|
+
* bytes or references go through `sendMedia` with the message text rendered as a
|
|
7
|
+
* 1024-caption. Interactive `OutboundMessage.actions` map to a Telegram
|
|
8
|
+
* `InlineKeyboardMarkup` (plan §14). A binary part with no supported carrier
|
|
9
|
+
* fails closed so an attachment is never silently discarded as plain text.
|
|
10
|
+
*
|
|
11
|
+
* Formatting fallback is exact-once: only a `format`-kind error downgrades to
|
|
12
|
+
* plain; 401/403 / 429 / network / 5xx propagate (plan §20.9).
|
|
13
|
+
*/
|
|
14
|
+
import type { ChannelLogger, ChannelTarget, OutboundActionRow, OutboundMessage, SendResult } from '@krischoichoi/channel-core';
|
|
15
|
+
import type { TelegramFormattingConfig } from './config.js';
|
|
16
|
+
import type { TelegramUpstream } from './upstream.js';
|
|
17
|
+
import type { TelegramInlineKeyboardMarkup } from './rich-message.js';
|
|
18
|
+
export interface TelegramOutboundOptions {
|
|
19
|
+
/** Formatting policy controlling how text/captions are rendered. */
|
|
20
|
+
formatting?: Partial<TelegramFormattingConfig>;
|
|
21
|
+
}
|
|
22
|
+
export declare class OutboundSender {
|
|
23
|
+
private readonly upstream;
|
|
24
|
+
private readonly logger;
|
|
25
|
+
private readonly formatting;
|
|
26
|
+
constructor(upstream: TelegramUpstream, logger: ChannelLogger, options?: TelegramOutboundOptions);
|
|
27
|
+
send(target: ChannelTarget, message: OutboundMessage): Promise<SendResult>;
|
|
28
|
+
/** Send text through the configured renderer with exact-once fallback. */
|
|
29
|
+
private sendTextWithFormatting;
|
|
30
|
+
/** Send media with the text rendered as a ≤1024 caption (plain fallback). */
|
|
31
|
+
private sendMediaWithFormatting;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Map `OutboundMessage.actions` to a Telegram `InlineKeyboardMarkup`.
|
|
35
|
+
*
|
|
36
|
+
* Each row becomes one inline-keyboard row; each action becomes a button with
|
|
37
|
+
* `text = label`, `callback_data = id` and the mapped style. An action id that
|
|
38
|
+
* exceeds Telegram's 64-byte `callback_data` cap fails closed (we cannot
|
|
39
|
+
* round-trip a truncated id), rather than silently dropping the button.
|
|
40
|
+
*/
|
|
41
|
+
export declare function actionsToReplyMarkup(actions: OutboundActionRow[] | undefined): TelegramInlineKeyboardMarkup | undefined;
|
|
42
|
+
//# sourceMappingURL=outbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../src/outbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EAEb,iBAAiB,EACjB,eAAe,EACf,UAAU,EACX,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAA2D,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC/G,OAAO,KAAK,EAEV,4BAA4B,EAG7B,MAAM,mBAAmB,CAAC;AAQ3B,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,CAAC;CAChD;AAED,qBAAa,cAAc;IAIvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;gBAGnC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,aAAa,EACtC,OAAO,GAAE,uBAA4B;IAKjC,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;IAwChF,0EAA0E;YAC5D,sBAAsB;IAsCpC,6EAA6E;YAC/D,uBAAuB;CA2CtC;AA+BD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,SAAS,GAAG,4BAA4B,GAAG,SAAS,CAsBvH"}
|