@zhin.js/adapter-telegram 1.1.17 → 1.1.19
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 +65 -0
- package/README.md +19 -2
- package/adapters/telegram/index.js +3 -17
- package/adapters/telegram/index.ts +2 -17
- package/agents/telegram/skills/telegram-messages/tools/send_poll/index.js +2 -1
- package/agents/telegram/skills/telegram-messages/tools/send_poll/index.ts +2 -4
- package/commands/telegram/endpoint/definition.js +0 -2
- package/commands/telegram/endpoint/definition.ts +0 -2
- package/lib/adapter.d.ts +15 -0
- package/lib/adapter.js +33 -0
- package/lib/client.d.ts +3 -7
- package/lib/client.js +1 -2
- package/lib/console-spec.d.ts +12 -0
- package/lib/console-spec.js +15 -0
- package/lib/console.d.ts +2 -0
- package/lib/console.js +13 -0
- package/lib/endpoint.d.ts +7 -84
- package/lib/endpoint.js +126 -289
- package/lib/index.d.ts +3 -1
- package/lib/index.js +1 -1
- package/lib/telegram-client.d.ts +79 -0
- package/lib/telegram-client.js +159 -0
- package/package.json +26 -17
- package/pages/index/index.js +15 -0
- package/pages/index/index.ts +16 -0
- package/plugin.js +3 -4
- package/src/adapter.ts +27 -0
- package/src/client.ts +2 -10
- package/src/console-spec.ts +20 -0
- package/src/console.ts +17 -0
- package/src/endpoint.ts +107 -359
- package/src/index.ts +4 -1
- package/src/telegram-client.ts +250 -0
- package/lib/telegram-runtime-state.d.ts +0 -1
- package/lib/telegram-runtime-state.js +0 -6
- package/src/telegram-runtime-state.ts +0 -7
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type ResolvedTelegramConfig, type TelegramChatMember } from './protocol.js';
|
|
2
|
+
export type TelegramFetch = (url: string, init?: {
|
|
3
|
+
readonly method?: string;
|
|
4
|
+
readonly headers?: Record<string, string>;
|
|
5
|
+
readonly body?: string | FormData;
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
}) => Promise<{
|
|
8
|
+
readonly ok: boolean;
|
|
9
|
+
readonly status: number;
|
|
10
|
+
text(): Promise<string>;
|
|
11
|
+
json(): Promise<unknown>;
|
|
12
|
+
arrayBuffer?(): Promise<ArrayBuffer>;
|
|
13
|
+
readonly headers?: {
|
|
14
|
+
get(name: string): string | null;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
17
|
+
export interface TelegramClientApi {
|
|
18
|
+
callApi<T = unknown>(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<T>;
|
|
19
|
+
callApiForm<T = unknown>(method: string, form: FormData, signal?: AbortSignal): Promise<T>;
|
|
20
|
+
pinMessage(chatId: number, messageId: number): Promise<boolean>;
|
|
21
|
+
unpinMessage(chatId: number, messageId?: number): Promise<boolean>;
|
|
22
|
+
setChatDescription(chatId: number, description: string): Promise<boolean>;
|
|
23
|
+
setMessageReaction(chatId: number, messageId: number, reaction: string): Promise<boolean>;
|
|
24
|
+
getChatMemberCount(chatId: number): Promise<number>;
|
|
25
|
+
getChatAdmins(chatId: number): Promise<TelegramChatMember[]>;
|
|
26
|
+
sendStickerMessage(chatId: number, sticker: string): Promise<{
|
|
27
|
+
message_id: number;
|
|
28
|
+
}>;
|
|
29
|
+
setChatPermissionsAll(chatId: number, permissions: Record<string, boolean | undefined>): Promise<boolean>;
|
|
30
|
+
createInviteLink(chatId: number): Promise<string>;
|
|
31
|
+
sendPoll(request: TelegramPollRequest): Promise<{
|
|
32
|
+
message_id: number;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface TelegramPollRequest {
|
|
36
|
+
readonly chatId: number;
|
|
37
|
+
readonly question: string;
|
|
38
|
+
readonly options: string[];
|
|
39
|
+
readonly isAnonymous?: boolean;
|
|
40
|
+
readonly allowsMultipleAnswers?: boolean;
|
|
41
|
+
}
|
|
42
|
+
interface TelegramDeadlineOperation<T> {
|
|
43
|
+
readonly method: string;
|
|
44
|
+
readonly run: (signal: AbortSignal) => Promise<T>;
|
|
45
|
+
readonly signal?: AbortSignal;
|
|
46
|
+
readonly timeoutMs?: number;
|
|
47
|
+
}
|
|
48
|
+
/** Owns Telegram Bot API transport, cancellation, deadlines and native API operations. */
|
|
49
|
+
export declare class TelegramClient implements TelegramClientApi {
|
|
50
|
+
#private;
|
|
51
|
+
protected readonly fetchTransport: TelegramFetch;
|
|
52
|
+
protected requestSignal?: AbortSignal;
|
|
53
|
+
constructor(options: Readonly<{
|
|
54
|
+
config: ResolvedTelegramConfig;
|
|
55
|
+
fetch?: TelegramFetch;
|
|
56
|
+
requestTimeoutMs?: number;
|
|
57
|
+
}>);
|
|
58
|
+
protected restartRequests(): void;
|
|
59
|
+
protected cancelRequests(): void;
|
|
60
|
+
callApi<T = unknown>(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<T>;
|
|
61
|
+
/** multipart/form-data 变体(attach:// 媒体上传;Content-Type 边界由 FormData 自带)。 */
|
|
62
|
+
callApiForm<T = unknown>(method: string, form: FormData, signal?: AbortSignal): Promise<T>;
|
|
63
|
+
protected withRequestDeadline<T>({ method, run, signal, timeoutMs, }: TelegramDeadlineOperation<T>): Promise<T>;
|
|
64
|
+
pinMessage(chatId: number, messageId: number): Promise<boolean>;
|
|
65
|
+
unpinMessage(chatId: number, messageId?: number): Promise<boolean>;
|
|
66
|
+
setChatDescription(chatId: number, description: string): Promise<boolean>;
|
|
67
|
+
setMessageReaction(chatId: number, messageId: number, reaction: string): Promise<boolean>;
|
|
68
|
+
getChatMemberCount(chatId: number): Promise<number>;
|
|
69
|
+
getChatAdmins(chatId: number): Promise<TelegramChatMember[]>;
|
|
70
|
+
sendStickerMessage(chatId: number, sticker: string): Promise<{
|
|
71
|
+
message_id: number;
|
|
72
|
+
}>;
|
|
73
|
+
setChatPermissionsAll(chatId: number, permissions: Record<string, boolean | undefined>): Promise<boolean>;
|
|
74
|
+
createInviteLink(chatId: number): Promise<string>;
|
|
75
|
+
sendPoll({ chatId, question, options, isAnonymous, allowsMultipleAnswers }: TelegramPollRequest): Promise<{
|
|
76
|
+
message_id: number;
|
|
77
|
+
}>;
|
|
78
|
+
}
|
|
79
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { botApiUrl } from './protocol.js';
|
|
2
|
+
import { TelegramApiError } from './api-error.js';
|
|
3
|
+
const DEFAULT_API_TIMEOUT_MS = 30_000;
|
|
4
|
+
/** Owns Telegram Bot API transport, cancellation, deadlines and native API operations. */
|
|
5
|
+
export class TelegramClient {
|
|
6
|
+
#options;
|
|
7
|
+
fetchTransport;
|
|
8
|
+
requestSignal;
|
|
9
|
+
#apiAbort = new AbortController();
|
|
10
|
+
constructor(options) {
|
|
11
|
+
if (options.requestTimeoutMs !== undefined && (!Number.isFinite(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)) {
|
|
12
|
+
throw new TypeError('Telegram requestTimeoutMs must be a positive finite number');
|
|
13
|
+
}
|
|
14
|
+
this.#options = options;
|
|
15
|
+
this.fetchTransport = options.fetch ?? globalThis.fetch;
|
|
16
|
+
}
|
|
17
|
+
restartRequests() { if (this.#apiAbort.signal.aborted)
|
|
18
|
+
this.#apiAbort = new AbortController(); }
|
|
19
|
+
cancelRequests() { this.#apiAbort.abort(); }
|
|
20
|
+
async callApi(method, params = {}, signal) {
|
|
21
|
+
return this.#requestApi({ method, init: {
|
|
22
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params),
|
|
23
|
+
}, signal, timeoutMs: method === 'getUpdates'
|
|
24
|
+
? Math.max(this.#options.requestTimeoutMs ?? DEFAULT_API_TIMEOUT_MS, (Number(params.timeout) || 0) * 1_000 + 15_000)
|
|
25
|
+
: undefined });
|
|
26
|
+
}
|
|
27
|
+
/** multipart/form-data 变体(attach:// 媒体上传;Content-Type 边界由 FormData 自带)。 */
|
|
28
|
+
async callApiForm(method, form, signal) {
|
|
29
|
+
return this.#requestApi({ method, init: { method: 'POST', body: form }, signal });
|
|
30
|
+
}
|
|
31
|
+
async #requestApi({ method, init, signal, timeoutMs }) {
|
|
32
|
+
return this.withRequestDeadline({ method, run: async (requestSignal) => {
|
|
33
|
+
const response = await this.fetchTransport(botApiUrl(this.#options.config, method), { ...init, signal: requestSignal });
|
|
34
|
+
return this.#parseApiResponse(method, response);
|
|
35
|
+
}, signal, timeoutMs });
|
|
36
|
+
}
|
|
37
|
+
async withRequestDeadline({ method, run, signal, timeoutMs = this.#options.requestTimeoutMs ?? DEFAULT_API_TIMEOUT_MS, }) {
|
|
38
|
+
const abort = new AbortController();
|
|
39
|
+
let issued = false;
|
|
40
|
+
const sources = [...new Set([signal, this.requestSignal, this.#apiAbort.signal].filter((item) => !!item))];
|
|
41
|
+
let rejectAbort;
|
|
42
|
+
const cancelled = new Promise((_resolve, reject) => { rejectAbort = reject; });
|
|
43
|
+
const onCancel = () => {
|
|
44
|
+
const error = new TelegramApiError('cancelled', `Telegram API ${method} cancelled`, {
|
|
45
|
+
disposition: issued ? 'unknown' : 'not_sent',
|
|
46
|
+
});
|
|
47
|
+
abort.abort(error);
|
|
48
|
+
rejectAbort(error);
|
|
49
|
+
};
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
const error = new TelegramApiError('timeout', `Telegram API ${method} timed out`);
|
|
52
|
+
abort.abort(error);
|
|
53
|
+
rejectAbort(error);
|
|
54
|
+
}, timeoutMs);
|
|
55
|
+
for (const source of sources)
|
|
56
|
+
source.addEventListener('abort', onCancel, { once: true });
|
|
57
|
+
try {
|
|
58
|
+
if (sources.some((source) => source.aborted))
|
|
59
|
+
onCancel();
|
|
60
|
+
const request = Promise.resolve().then(async () => {
|
|
61
|
+
abort.signal.throwIfAborted();
|
|
62
|
+
issued = true;
|
|
63
|
+
return run(abort.signal);
|
|
64
|
+
});
|
|
65
|
+
return await Promise.race([request, cancelled]);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (abort.signal.aborted)
|
|
69
|
+
throw abort.signal.reason;
|
|
70
|
+
if (error instanceof TelegramApiError)
|
|
71
|
+
throw error;
|
|
72
|
+
// Network exceptions can contain credential-bearing URLs. Keep those out of errors/logs.
|
|
73
|
+
const detail = error instanceof Error
|
|
74
|
+
? error.message.replaceAll(this.#options.config.token, '[redacted]') : 'transport failure';
|
|
75
|
+
throw new TelegramApiError('network', `Telegram API ${method} network request failed: ${detail}`);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
for (const source of sources)
|
|
80
|
+
source.removeEventListener('abort', onCancel);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async #parseApiResponse(method, response) {
|
|
84
|
+
const text = await response.text();
|
|
85
|
+
let body;
|
|
86
|
+
try {
|
|
87
|
+
body = JSON.parse(text);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
throw new TelegramApiError('invalid_response', `Telegram API ${method} invalid JSON (${response.status})`, { status: response.status });
|
|
91
|
+
}
|
|
92
|
+
if (!body || typeof body !== 'object' || typeof body.ok !== 'boolean') {
|
|
93
|
+
throw new TelegramApiError('invalid_response', `Telegram API ${method} invalid response envelope`, { status: response.status });
|
|
94
|
+
}
|
|
95
|
+
if (!body.ok || response.status < 200 || response.status >= 300) {
|
|
96
|
+
const status = !body.ok ? body.error_code ?? response.status : response.status;
|
|
97
|
+
const kind = status === 401 || status === 403 ? 'authentication'
|
|
98
|
+
: status === 429 ? 'rate_limit' : response.status >= 400 ? 'http' : 'api';
|
|
99
|
+
const description = !body.ok ? body.description?.replaceAll(this.#options.config.token, '[redacted]') : undefined;
|
|
100
|
+
throw new TelegramApiError(kind, `Telegram API ${method} failed (${status}): ${description ?? 'request rejected'}`, { status, disposition: !body.ok ? 'rejected' : 'unknown', ...(!body.ok && body.parameters?.retry_after != null
|
|
101
|
+
? { retryAfterSeconds: body.parameters.retry_after } : {}) });
|
|
102
|
+
}
|
|
103
|
+
if (!('result' in body))
|
|
104
|
+
throw new TelegramApiError('invalid_response', `Telegram API ${method} missing result`);
|
|
105
|
+
return body.result;
|
|
106
|
+
}
|
|
107
|
+
// ── Agent tool surface ──────────────────────────────────────────────
|
|
108
|
+
async pinMessage(chatId, messageId) {
|
|
109
|
+
await this.callApi('pinChatMessage', { chat_id: chatId, message_id: messageId });
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
async unpinMessage(chatId, messageId) {
|
|
113
|
+
if (messageId != null) {
|
|
114
|
+
await this.callApi('unpinChatMessage', { chat_id: chatId, message_id: messageId });
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
await this.callApi('unpinAllChatMessages', { chat_id: chatId });
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
async setChatDescription(chatId, description) {
|
|
122
|
+
await this.callApi('setChatDescription', { chat_id: chatId, description });
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
async setMessageReaction(chatId, messageId, reaction) {
|
|
126
|
+
await this.callApi('setMessageReaction', {
|
|
127
|
+
chat_id: chatId,
|
|
128
|
+
message_id: messageId,
|
|
129
|
+
reaction: [{ type: 'emoji', emoji: reaction }],
|
|
130
|
+
});
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
async getChatMemberCount(chatId) {
|
|
134
|
+
return this.callApi('getChatMemberCount', { chat_id: chatId });
|
|
135
|
+
}
|
|
136
|
+
async getChatAdmins(chatId) {
|
|
137
|
+
return this.callApi('getChatAdministrators', { chat_id: chatId });
|
|
138
|
+
}
|
|
139
|
+
async sendStickerMessage(chatId, sticker) {
|
|
140
|
+
return this.callApi('sendSticker', { chat_id: chatId, sticker });
|
|
141
|
+
}
|
|
142
|
+
async setChatPermissionsAll(chatId, permissions) {
|
|
143
|
+
await this.callApi('setChatPermissions', { chat_id: chatId, permissions });
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
async createInviteLink(chatId) {
|
|
147
|
+
const link = await this.callApi('createChatInviteLink', { chat_id: chatId });
|
|
148
|
+
return link.invite_link;
|
|
149
|
+
}
|
|
150
|
+
async sendPoll({ chatId, question, options, isAnonymous = true, allowsMultipleAnswers = false }) {
|
|
151
|
+
return this.callApi('sendPoll', {
|
|
152
|
+
chat_id: chatId,
|
|
153
|
+
question,
|
|
154
|
+
options,
|
|
155
|
+
is_anonymous: isAnonymous,
|
|
156
|
+
allows_multiple_answers: allowsMultipleAnswers,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
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.19",
|
|
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",
|
|
@@ -31,23 +31,26 @@
|
|
|
31
31
|
"directory": "plugins/adapters/telegram"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@zhin.js/adapter": "1.1.
|
|
35
|
-
"@zhin.js/agent-feature": "1.1.
|
|
36
|
-
"@zhin.js/
|
|
37
|
-
"@zhin.js/
|
|
38
|
-
"@zhin.js/
|
|
39
|
-
"@zhin.js/
|
|
34
|
+
"@zhin.js/adapter": "1.1.26",
|
|
35
|
+
"@zhin.js/agent-feature": "1.1.11",
|
|
36
|
+
"@zhin.js/client": "1.1.22",
|
|
37
|
+
"@zhin.js/console-contract": "1.1.3",
|
|
38
|
+
"@zhin.js/core": "1.1.54",
|
|
39
|
+
"@zhin.js/feature-kit": "1.1.10",
|
|
40
|
+
"@zhin.js/host-http": "1.1.12",
|
|
41
|
+
"@zhin.js/im-contract": "1.1.9",
|
|
40
42
|
"@zhin.js/logger": "1.1.2",
|
|
41
|
-
"@zhin.js/
|
|
42
|
-
"@zhin.js/
|
|
43
|
+
"@zhin.js/page": "1.1.11",
|
|
44
|
+
"@zhin.js/skill": "1.1.11",
|
|
45
|
+
"@zhin.js/tool": "1.1.13"
|
|
43
46
|
},
|
|
44
47
|
"peerDependencies": {
|
|
45
48
|
"zod": "^4.0.0",
|
|
46
|
-
"@zhin.js/adapter": "^1.1.
|
|
47
|
-
"@zhin.js/command": "^1.1.
|
|
48
|
-
"@zhin.js/core": "^1.1.
|
|
49
|
-
"@zhin.js/permission": "^1.1.
|
|
50
|
-
"zhin.js": "^1.1.
|
|
49
|
+
"@zhin.js/adapter": "^1.1.26",
|
|
50
|
+
"@zhin.js/command": "^1.1.12",
|
|
51
|
+
"@zhin.js/core": "^1.1.54",
|
|
52
|
+
"@zhin.js/permission": "^1.1.10",
|
|
53
|
+
"zhin.js": "^1.1.19"
|
|
51
54
|
},
|
|
52
55
|
"peerDependenciesMeta": {
|
|
53
56
|
"@zhin.js/command": {
|
|
@@ -65,12 +68,14 @@
|
|
|
65
68
|
"typescript": "^6.0.3",
|
|
66
69
|
"vitest": "^4.1.10",
|
|
67
70
|
"zod": "^4.4.3",
|
|
68
|
-
"@zhin.js/host-http": "1.1.
|
|
69
|
-
"zhin.js": "1.1.
|
|
71
|
+
"@zhin.js/host-http": "1.1.12",
|
|
72
|
+
"@zhin.js/pagemanager": "1.1.18",
|
|
73
|
+
"zhin.js": "1.1.19"
|
|
70
74
|
},
|
|
71
75
|
"files": [
|
|
72
76
|
"adapters",
|
|
73
77
|
"commands",
|
|
78
|
+
"pages",
|
|
74
79
|
"plugin.js",
|
|
75
80
|
"schema.json",
|
|
76
81
|
"src",
|
|
@@ -97,7 +102,7 @@
|
|
|
97
102
|
"features": [
|
|
98
103
|
{
|
|
99
104
|
"package": "@zhin.js/adapter",
|
|
100
|
-
"api": "^
|
|
105
|
+
"api": "^2.0.0"
|
|
101
106
|
},
|
|
102
107
|
{
|
|
103
108
|
"package": "@zhin.js/command",
|
|
@@ -114,6 +119,10 @@
|
|
|
114
119
|
{
|
|
115
120
|
"package": "@zhin.js/agent-feature",
|
|
116
121
|
"api": "^1.0.0"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"package": "@zhin.js/page",
|
|
125
|
+
"api": "^1.0.0"
|
|
117
126
|
}
|
|
118
127
|
],
|
|
119
128
|
"plugins": []
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { definePage } from '@zhin.js/console-contract';
|
|
3
|
+
import { createAdapterConsolePage } from '@zhin.js/client';
|
|
4
|
+
export const meta = definePage({
|
|
5
|
+
title: 'Telegram',
|
|
6
|
+
titleI18n: { en: 'Telegram' },
|
|
7
|
+
icon: 'Send',
|
|
8
|
+
order: 74,
|
|
9
|
+
requiredPermissions: ['telegram:read'],
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Convention page: `pages/index/index.ts` → `/telegram`(插件路径)。
|
|
13
|
+
* 连接状态、连接测试、endpoint 增删;登录待办来自 LoginAssist(ICQQ 等)。
|
|
14
|
+
*/
|
|
15
|
+
export default createAdapterConsolePage('telegram');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { definePage } from '@zhin.js/console-contract';
|
|
2
|
+
import { createAdapterConsolePage } from '@zhin.js/client';
|
|
3
|
+
|
|
4
|
+
export const meta = definePage({
|
|
5
|
+
title: 'Telegram',
|
|
6
|
+
titleI18n: { en: 'Telegram' },
|
|
7
|
+
icon: 'Send',
|
|
8
|
+
order: 74,
|
|
9
|
+
requiredPermissions: ['telegram:read'],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convention page: `pages/index/index.ts` → `/telegram`(插件路径)。
|
|
14
|
+
* 连接状态、连接测试、endpoint 增删;登录待办来自 LoginAssist(ICQQ 等)。
|
|
15
|
+
*/
|
|
16
|
+
export default createAdapterConsolePage('telegram');
|
package/plugin.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
-
import {
|
|
3
|
-
import { definePlugin } from 'zhin.js';
|
|
2
|
+
import { definePlugin, pluginConsoleRpcToken } from 'zhin.js';
|
|
4
3
|
import { permissionHostToken } from '@zhin.js/permission';
|
|
5
4
|
import { checkTelegramPlatformPermit } from "./lib/platform-permit.js";
|
|
6
|
-
import {
|
|
5
|
+
import { createTelegramConsoleRpc } from "./lib/console.js";
|
|
7
6
|
export default definePlugin({
|
|
8
7
|
name: 'telegram',
|
|
9
8
|
metadata: {
|
|
10
9
|
displayName: 'Telegram Bot API Adapter',
|
|
11
10
|
},
|
|
12
11
|
setup(context) {
|
|
13
|
-
context.resources.provide(
|
|
12
|
+
context.resources.provide(pluginConsoleRpcToken, createTelegramConsoleRpc(() => context.config.get()));
|
|
14
13
|
if (context.resources.has(permissionHostToken)) {
|
|
15
14
|
const host = context.resources.use(permissionHostToken);
|
|
16
15
|
return host.registerPlatform('telegram', checkTelegramPlatformPermit);
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Adapter, type AdapterContext, type EndpointCreationContext } from 'zhin.js/adapter';
|
|
2
|
+
import { httpHostToken } from '@zhin.js/host-http';
|
|
3
|
+
import { TelegramEndpoint, type TelegramEndpointOptions } from './endpoint.js';
|
|
4
|
+
import { TelegramClient } from './telegram-client.js';
|
|
5
|
+
import { resolveTelegramConfig, type TelegramEndpointConfig } from './protocol.js';
|
|
6
|
+
import type { TelegramClientEventMap } from './client.js';
|
|
7
|
+
|
|
8
|
+
export class TelegramAdapter extends Adapter<TelegramEndpoint, TelegramEndpointConfig, TelegramClientEventMap> {
|
|
9
|
+
constructor(private readonly context: AdapterContext<TelegramEndpointConfig>,
|
|
10
|
+
private readonly transport: Pick<TelegramEndpointOptions, 'fetch' | 'requestTimeoutMs'> = {}) { super(); }
|
|
11
|
+
override get configurationIdentityFields(): readonly string[] { return ['id']; }
|
|
12
|
+
override get configurationIdentityProbeFields(): readonly string[] { return ['token', 'apiBaseUrl']; }
|
|
13
|
+
override get configurationUpdateFields(): readonly string[] { return ['token', 'polling', 'webhook',
|
|
14
|
+
'webhook.domain', 'webhook.path', 'webhook.secretToken', 'allowedUpdates', 'apiBaseUrl']; }
|
|
15
|
+
protected override validateConfiguration(config: TelegramEndpointConfig): void { resolveTelegramConfig(config); }
|
|
16
|
+
protected override async identifyConfiguration(input: TelegramEndpointConfig, signal: AbortSignal): Promise<string> {
|
|
17
|
+
const client = new TelegramClient({ config: resolveTelegramConfig(input), ...this.transport });
|
|
18
|
+
const me = await client.callApi<{ id?: number }>('getMe', {}, signal);
|
|
19
|
+
if (typeof me?.id !== 'number' || !Number.isFinite(me.id)) throw new Error('Telegram identity response is incomplete');
|
|
20
|
+
return JSON.stringify(['telegram', String(me.id)]);
|
|
21
|
+
}
|
|
22
|
+
protected createEndpoint(input: TelegramEndpointConfig, execution: EndpointCreationContext): TelegramEndpoint {
|
|
23
|
+
const config = resolveTelegramConfig(input);
|
|
24
|
+
return new TelegramEndpoint({ id: execution.canonicalId, config, ...this.transport,
|
|
25
|
+
...(config.mode === 'webhook' ? { http: this.context.use(httpHostToken) } : {}) });
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { TelegramClient } from './endpoint.js';
|
|
1
|
+
import type { TelegramAdapter } from './adapter.js';
|
|
3
2
|
import type { TelegramUpdate } from './protocol.js';
|
|
4
3
|
|
|
5
4
|
export type TelegramClientEventMap = Record<string, TelegramUpdate>;
|
|
6
5
|
|
|
7
6
|
declare module '@zhin.js/feature-kit' {
|
|
8
|
-
interface
|
|
9
|
-
readonly telegram: {
|
|
10
|
-
readonly client: TelegramClient;
|
|
11
|
-
readonly events: TelegramClientEventMap;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
7
|
+
interface AdapterRegistry { readonly telegram: TelegramAdapter; }
|
|
14
8
|
}
|
|
15
|
-
|
|
16
|
-
export const telegramClient = defineEndpointClient<TelegramClient, TelegramClientEventMap>('telegram');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Console 页面元数据(浏览器与服务端共享;不要在这里 import 服务端代码)。
|
|
3
|
+
*/
|
|
4
|
+
import type { AdapterConsoleField } from 'zhin.js/adapter';
|
|
5
|
+
|
|
6
|
+
export const telegramConsoleSpec = {
|
|
7
|
+
adapterKey: 'telegram',
|
|
8
|
+
adapterDisplayName: 'Telegram',
|
|
9
|
+
fields: [
|
|
10
|
+
{ key: 'token', required: true, env: true, description: 'Telegram bot token' },
|
|
11
|
+
{ key: 'apiBaseUrl', description: 'Telegram Bot API URL' },
|
|
12
|
+
{ key: 'polling', type: 'boolean', description: 'Use long polling; false selects webhook mode' },
|
|
13
|
+
{ key: 'webhook.domain', description: 'Public webhook origin' },
|
|
14
|
+
{ key: 'webhook.path', description: 'Local webhook route' },
|
|
15
|
+
{ key: 'webhook.secretToken', env: true, description: 'Webhook secret token' },
|
|
16
|
+
] as readonly AdapterConsoleField[],
|
|
17
|
+
docsUrl: 'https://zhin.js.org/adapters/telegram',
|
|
18
|
+
readPermission: 'telegram:read',
|
|
19
|
+
writePermission: 'telegram:write',
|
|
20
|
+
} as const;
|
package/src/console.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Console RPC:运行状态 + endpoint 增删(原 `telegram endpoint` 聊天命令)。
|
|
3
|
+
*
|
|
4
|
+
* 页面绑定 `pages/index`,读取 / 写入分别要求 `<adapter>:read` / `<adapter>:write`。
|
|
5
|
+
*/
|
|
6
|
+
import { createAdapterConsoleRpc } from 'zhin.js/adapter';
|
|
7
|
+
import type { PluginConsoleRpc } from 'zhin.js';
|
|
8
|
+
import { telegramConsoleSpec } from './console-spec.js';
|
|
9
|
+
|
|
10
|
+
export function createTelegramConsoleRpc(
|
|
11
|
+
getConfig: () => unknown,
|
|
12
|
+
): PluginConsoleRpc {
|
|
13
|
+
return createAdapterConsoleRpc({
|
|
14
|
+
...telegramConsoleSpec,
|
|
15
|
+
getConfig,
|
|
16
|
+
});
|
|
17
|
+
}
|