@zhin.js/adapter-telegram 1.1.4 → 1.1.5
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/CHANGELOG.md +27 -0
- package/README.md +12 -0
- package/lib/api-error.d.ts +13 -0
- package/lib/api-error.js +16 -0
- package/lib/endpoint.d.ts +11 -6
- package/lib/endpoint.js +204 -73
- package/lib/polling.d.ts +2 -1
- package/lib/polling.js +7 -2
- package/lib/protocol.js +30 -4
- package/lib/webhook.d.ts +1 -1
- package/lib/webhook.js +11 -6
- package/package.json +14 -14
- package/src/api-error.ts +33 -0
- package/src/endpoint.ts +216 -77
- package/src/polling.ts +8 -3
- package/src/protocol.ts +19 -5
- package/src/webhook.ts +11 -8
package/lib/protocol.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Telegram Bot API protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
*/
|
|
5
|
-
import { isMediaRef } from '@zhin.js/im-contract';
|
|
5
|
+
import { EndpointDeliveryError, isMediaRef } from '@zhin.js/im-contract';
|
|
6
6
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
7
7
|
import { escapeTelegramHtml, markdownToTelegramHtml } from './markdown-to-html.js';
|
|
8
8
|
const logger = getLogger('telegram');
|
|
@@ -219,7 +219,7 @@ function buildOutboundActions(target, payload, uploads) {
|
|
|
219
219
|
: payload && typeof payload === 'object' && 'type' in payload
|
|
220
220
|
? [payload]
|
|
221
221
|
: [];
|
|
222
|
-
const hasMarkdown = items.some((item) => typeof item !== 'string' && item.type
|
|
222
|
+
const hasMarkdown = items.some((item) => typeof item !== 'string' && ['markdown', 'share'].includes(item.type));
|
|
223
223
|
const appendPlain = (value) => hasMarkdown
|
|
224
224
|
? escapeTelegramHtml(String(value ?? ''))
|
|
225
225
|
: String(value ?? '');
|
|
@@ -295,6 +295,24 @@ function buildOutboundActions(target, payload, uploads) {
|
|
|
295
295
|
case 'markdown':
|
|
296
296
|
textContent += markdownToTelegramHtml(String(data.content ?? data.text ?? ''));
|
|
297
297
|
break;
|
|
298
|
+
case 'share': {
|
|
299
|
+
let url;
|
|
300
|
+
try {
|
|
301
|
+
url = new URL(String(data.url ?? ''));
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
throw new EndpointDeliveryError('invalid_payload', 'Telegram share requires an HTTP(S) URL', 'not_sent');
|
|
305
|
+
}
|
|
306
|
+
if (!['http:', 'https:'].includes(url.protocol) || typeof data.title !== 'string' || !data.title.trim())
|
|
307
|
+
throw new EndpointDeliveryError('invalid_payload', 'Telegram share requires an HTTP(S) URL and title', 'not_sent');
|
|
308
|
+
if (['image', 'audio', 'artist', 'duration', 'config'].some(key => data[key] !== undefined))
|
|
309
|
+
throw new EndpointDeliveryError('unsupported_operation', 'Telegram share media and app metadata are not implemented', 'not_sent');
|
|
310
|
+
textContent += `<a href="${escapeTelegramHtml(url.href)}">${escapeTelegramHtml(data.title)}</a>`;
|
|
311
|
+
for (const value of [data.description, data.content])
|
|
312
|
+
if (typeof value === 'string' && value)
|
|
313
|
+
textContent += `\n${escapeTelegramHtml(value)}`;
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
298
316
|
case 'at':
|
|
299
317
|
if (data.id)
|
|
300
318
|
textContent += `@${appendPlain(data.name || data.id)}`;
|
|
@@ -307,15 +325,23 @@ function buildOutboundActions(target, payload, uploads) {
|
|
|
307
325
|
}
|
|
308
326
|
case 'keyboard': {
|
|
309
327
|
const rows = Array.isArray(data.rows) ? data.rows : [];
|
|
328
|
+
if (!rows.length || rows.some(row => !Array.isArray(row) || !row.length))
|
|
329
|
+
throw new EndpointDeliveryError('invalid_payload', 'Telegram keyboard requires nonempty rows', 'not_sent');
|
|
310
330
|
keyboard = rows.map((row) => {
|
|
311
331
|
const buttons = Array.isArray(row) ? row : [];
|
|
312
332
|
return buttons.map((btn) => {
|
|
313
333
|
const record = btn && typeof btn === 'object'
|
|
314
334
|
? btn
|
|
315
335
|
: {};
|
|
336
|
+
const text = String(record.label ?? record.text ?? '');
|
|
337
|
+
const callback = String(record.payload ?? record.callback_data ?? '');
|
|
338
|
+
if (record.disabled || record.mode === 'command')
|
|
339
|
+
throw new EndpointDeliveryError('unsupported_operation', 'Telegram disabled/command buttons are not implemented', 'not_sent');
|
|
340
|
+
if (!text.trim() || Buffer.byteLength(callback, 'utf8') < 1 || Buffer.byteLength(callback, 'utf8') > 64)
|
|
341
|
+
throw new EndpointDeliveryError('invalid_payload', 'Telegram callback payload must be 1-64 UTF-8 bytes and label nonempty', 'not_sent');
|
|
316
342
|
return {
|
|
317
|
-
text
|
|
318
|
-
callback_data:
|
|
343
|
+
text,
|
|
344
|
+
callback_data: callback,
|
|
319
345
|
};
|
|
320
346
|
});
|
|
321
347
|
});
|
package/lib/webhook.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare function safeTokenEqual(a: string, b: string): boolean;
|
|
|
6
6
|
export interface TelegramWebhookHandler {
|
|
7
7
|
readonly config: ResolvedTelegramConfig;
|
|
8
8
|
readonly isOpen: boolean;
|
|
9
|
-
handleUpdate(update: TelegramUpdate): void
|
|
9
|
+
handleUpdate(update: TelegramUpdate): void | Promise<void>;
|
|
10
10
|
}
|
|
11
11
|
export declare function registerTelegramWebhookRoutes(http: HttpHost, handler: TelegramWebhookHandler): HttpRouteRegistration[];
|
|
12
12
|
export declare function handleTelegramWebhookRequest(request: IncomingMessage, response: ServerResponse, handler: TelegramWebhookHandler): Promise<void>;
|
package/lib/webhook.js
CHANGED
|
@@ -37,19 +37,24 @@ export async function handleTelegramWebhookRequest(request, response, handler) {
|
|
|
37
37
|
update = JSON.parse(rawBody);
|
|
38
38
|
}
|
|
39
39
|
catch {
|
|
40
|
-
response.writeHead(
|
|
41
|
-
response.end(JSON.stringify({ ok:
|
|
40
|
+
response.writeHead(400, { 'Content-Type': 'application/json' });
|
|
41
|
+
response.end(JSON.stringify({ ok: false, description: 'Invalid JSON' }));
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
if (
|
|
45
|
-
|
|
44
|
+
if (!update || !Number.isSafeInteger(update.update_id) || update.update_id < 0) {
|
|
45
|
+
response.writeHead(400, { 'Content-Type': 'application/json' });
|
|
46
|
+
response.end(JSON.stringify({ ok: false, description: 'Invalid update_id' }));
|
|
47
|
+
return;
|
|
46
48
|
}
|
|
49
|
+
if (!handler.isOpen)
|
|
50
|
+
throw new Error('Telegram endpoint is not accepting updates');
|
|
51
|
+
await handler.handleUpdate(update);
|
|
47
52
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
48
53
|
response.end(JSON.stringify({ ok: true }));
|
|
49
54
|
}
|
|
50
55
|
catch (error) {
|
|
51
56
|
logger.error('Telegram webhook error:', error);
|
|
52
|
-
response.writeHead(
|
|
53
|
-
response.end(JSON.stringify({ ok:
|
|
57
|
+
response.writeHead(503, { 'Content-Type': 'application/json' });
|
|
58
|
+
response.end(JSON.stringify({ ok: false, description: 'Update was not admitted' }));
|
|
54
59
|
}
|
|
55
60
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-telegram",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Zhin.js Telegram Bot API adapter for Plugin Runtime (long-poll getUpdates)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -32,23 +32,23 @@
|
|
|
32
32
|
"directory": "plugins/adapters/telegram"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@zhin.js/adapter": "1.1.
|
|
36
|
-
"@zhin.js/agent-feature": "1.1.
|
|
37
|
-
"@zhin.js/core": "1.1.
|
|
35
|
+
"@zhin.js/adapter": "1.1.16",
|
|
36
|
+
"@zhin.js/agent-feature": "1.1.3",
|
|
37
|
+
"@zhin.js/core": "1.1.40",
|
|
38
38
|
"@zhin.js/feature-kit": "1.1.2",
|
|
39
|
-
"@zhin.js/host-http": "1.1.
|
|
40
|
-
"@zhin.js/im-contract": "1.1.
|
|
39
|
+
"@zhin.js/host-http": "1.1.3",
|
|
40
|
+
"@zhin.js/im-contract": "1.1.3",
|
|
41
41
|
"@zhin.js/logger": "1.1.1",
|
|
42
|
-
"@zhin.js/skill": "1.1.
|
|
43
|
-
"@zhin.js/tool": "1.1.
|
|
42
|
+
"@zhin.js/skill": "1.1.3",
|
|
43
|
+
"@zhin.js/tool": "1.1.3"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"zod": "^4.0.0",
|
|
47
|
-
"@zhin.js/adapter": "^1.1.
|
|
48
|
-
"@zhin.js/command": "^1.1.
|
|
49
|
-
"@zhin.js/core": "^1.1.
|
|
47
|
+
"@zhin.js/adapter": "^1.1.16",
|
|
48
|
+
"@zhin.js/command": "^1.1.4",
|
|
49
|
+
"@zhin.js/core": "^1.1.40",
|
|
50
50
|
"@zhin.js/permission": "^1.1.3",
|
|
51
|
-
"zhin.js": "^1.1.
|
|
51
|
+
"zhin.js": "^1.1.5"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@zhin.js/command": {
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"typescript": "^6.0.3",
|
|
67
67
|
"vitest": "^4.1.10",
|
|
68
68
|
"zod": "^4.4.3",
|
|
69
|
-
"@zhin.js/host-http": "1.1.
|
|
70
|
-
"zhin.js": "1.1.
|
|
69
|
+
"@zhin.js/host-http": "1.1.3",
|
|
70
|
+
"zhin.js": "1.1.5"
|
|
71
71
|
},
|
|
72
72
|
"files": [
|
|
73
73
|
"adapters",
|
package/src/api-error.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
2
|
+
|
|
3
|
+
/** Machine-readable failures. Sending is never automatically retried. */
|
|
4
|
+
export type TelegramApiErrorKind =
|
|
5
|
+
| 'timeout' | 'cancelled' | 'network' | 'http' | 'authentication'
|
|
6
|
+
| 'rate_limit' | 'api' | 'invalid_response';
|
|
7
|
+
|
|
8
|
+
export class TelegramApiError extends EndpointDeliveryError {
|
|
9
|
+
readonly kind: TelegramApiErrorKind;
|
|
10
|
+
readonly status?: number;
|
|
11
|
+
readonly retryAfterSeconds?: number;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
kind: TelegramApiErrorKind,
|
|
15
|
+
message: string,
|
|
16
|
+
details: {
|
|
17
|
+
readonly status?: number;
|
|
18
|
+
readonly retryAfterSeconds?: number;
|
|
19
|
+
readonly disposition?: EndpointDeliveryError['disposition'];
|
|
20
|
+
} = {},
|
|
21
|
+
) {
|
|
22
|
+
const disposition = details.disposition ?? (
|
|
23
|
+
kind === 'authentication' || kind === 'rate_limit' || kind === 'api' ? 'rejected' : 'unknown'
|
|
24
|
+
);
|
|
25
|
+
super(disposition === 'rejected' ? 'platform_rejected'
|
|
26
|
+
: kind === 'timeout' ? 'endpoint_timeout'
|
|
27
|
+
: kind === 'cancelled' ? 'endpoint_cancelled' : 'delivery_unconfirmed', message, disposition);
|
|
28
|
+
this.name = 'TelegramApiError';
|
|
29
|
+
this.kind = kind;
|
|
30
|
+
this.status = details.status;
|
|
31
|
+
this.retryAfterSeconds = details.retryAfterSeconds;
|
|
32
|
+
}
|
|
33
|
+
}
|