@zhin.js/adapter-telegram 7.0.2 → 7.0.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 +95 -0
- package/README.md +3 -3
- package/adapters/telegram.js +2 -2
- package/adapters/telegram.ts +2 -2
- package/lib/endpoint.d.ts +1 -1
- package/lib/endpoint.js +42 -45
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/platform-permit.d.ts +2 -3
- package/lib/platform-permit.js +3 -13
- package/lib/protocol.d.ts +10 -6
- package/lib/protocol.js +19 -6
- package/lib/telegram-agent-deps.d.ts +2 -2
- package/lib/telegram-agent-deps.js +7 -7
- package/package.json +17 -16
- package/plugin.js +6 -4
- package/schema.json +41 -5
- package/src/endpoint.ts +45 -47
- package/src/index.ts +0 -2
- package/src/platform-permit.ts +4 -11
- package/src/protocol.ts +26 -11
- package/src/telegram-agent-deps.ts +8 -8
- /package/commands/endpoint/add/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/add/{[name:string].ts → [id].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [id].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 7.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
|
|
8
|
+
|
|
9
|
+
All adapter platform-permit checkers now accept PermissionSubject
|
|
10
|
+
(duck-typed from Message/CommandSession) instead of Message directly.
|
|
11
|
+
registerPlatformPermitChecker is replaced by host.registerPlatform in
|
|
12
|
+
the adapter plugin setup. activity-feedback service updated for new
|
|
13
|
+
PermissionHost integration.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [6f9c366]
|
|
16
|
+
- Updated dependencies [373a56b]
|
|
17
|
+
- Updated dependencies [0de46a8]
|
|
18
|
+
- Updated dependencies [c106ecc]
|
|
19
|
+
- Updated dependencies [ca92e03]
|
|
20
|
+
- Updated dependencies [b0f37ae]
|
|
21
|
+
- Updated dependencies [ba08a2f]
|
|
22
|
+
- Updated dependencies [b08f7fe]
|
|
23
|
+
- Updated dependencies [daffd4c]
|
|
24
|
+
- Updated dependencies [36c7400]
|
|
25
|
+
- Updated dependencies [a9fa72e]
|
|
26
|
+
- Updated dependencies [c8de3ef]
|
|
27
|
+
- Updated dependencies [60f0fc8]
|
|
28
|
+
- Updated dependencies [574c990]
|
|
29
|
+
- Updated dependencies [d096f16]
|
|
30
|
+
- Updated dependencies [3f29623]
|
|
31
|
+
- Updated dependencies [2916852]
|
|
32
|
+
- Updated dependencies [d047869]
|
|
33
|
+
- Updated dependencies [162fa34]
|
|
34
|
+
- Updated dependencies [e62561e]
|
|
35
|
+
- Updated dependencies [3eeeb46]
|
|
36
|
+
- Updated dependencies [85d0f82]
|
|
37
|
+
- Updated dependencies [61bfc1c]
|
|
38
|
+
- Updated dependencies [04bad47]
|
|
39
|
+
- Updated dependencies [e40b048]
|
|
40
|
+
- Updated dependencies [5a5b1bb]
|
|
41
|
+
- Updated dependencies [f1708c3]
|
|
42
|
+
- Updated dependencies [d254a81]
|
|
43
|
+
- Updated dependencies [7123c47]
|
|
44
|
+
- Updated dependencies [05befc1]
|
|
45
|
+
- Updated dependencies [0663b6a]
|
|
46
|
+
- Updated dependencies [f28f9b3]
|
|
47
|
+
- Updated dependencies [9f340f7]
|
|
48
|
+
- Updated dependencies [e53444f]
|
|
49
|
+
- Updated dependencies [5eedd26]
|
|
50
|
+
- Updated dependencies [92b0dd7]
|
|
51
|
+
- Updated dependencies [f919b6f]
|
|
52
|
+
- Updated dependencies [098e411]
|
|
53
|
+
- Updated dependencies [e1b7c01]
|
|
54
|
+
- Updated dependencies [9b94f87]
|
|
55
|
+
- Updated dependencies [a7df753]
|
|
56
|
+
- @zhin.js/agent@1.1.5
|
|
57
|
+
- @zhin.js/host-http@1.0.8
|
|
58
|
+
- @zhin.js/permission@1.0.1
|
|
59
|
+
- @zhin.js/im-contract@1.0.3
|
|
60
|
+
- @zhin.js/adapter@1.1.7
|
|
61
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
62
|
+
- @zhin.js/command@1.0.9
|
|
63
|
+
- @zhin.js/core@1.5.4
|
|
64
|
+
- zhin.js@6.0.4
|
|
65
|
+
|
|
66
|
+
## 7.0.4
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- f8c7a54: fix: im
|
|
71
|
+
- Updated dependencies [f8c7a54]
|
|
72
|
+
- @zhin.js/logger@1.0.76
|
|
73
|
+
- @zhin.js/host-http@1.0.7
|
|
74
|
+
- @zhin.js/adapter@1.1.6
|
|
75
|
+
- @zhin.js/agent@1.1.4
|
|
76
|
+
- @zhin.js/command@1.0.8
|
|
77
|
+
- @zhin.js/core@1.5.3
|
|
78
|
+
- @zhin.js/im-contract@1.0.2
|
|
79
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
80
|
+
- zhin.js@6.0.3
|
|
81
|
+
|
|
82
|
+
## 7.0.3
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- Updated dependencies [afc0e66]
|
|
87
|
+
- Updated dependencies [2e41ad5]
|
|
88
|
+
- Updated dependencies [9f57124]
|
|
89
|
+
- @zhin.js/core@1.5.2
|
|
90
|
+
- @zhin.js/adapter@1.1.5
|
|
91
|
+
- @zhin.js/im-contract@1.0.1
|
|
92
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
93
|
+
- @zhin.js/command@1.0.7
|
|
94
|
+
- @zhin.js/host-http@1.0.6
|
|
95
|
+
- @zhin.js/agent@1.1.3
|
|
96
|
+
- zhin.js@6.0.2
|
|
97
|
+
|
|
3
98
|
## 7.0.2
|
|
4
99
|
|
|
5
100
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Zhin.js Telegram Bot API 适配器(Plugin Runtime),默认通过 **长轮
|
|
|
7
7
|
- 长轮询 `getUpdates` 入站(默认;无需公网 IP / host-http)
|
|
8
8
|
- 解析 text / image / video / audio / voice / document / sticker / location / callback_query
|
|
9
9
|
- 支持私聊与群组
|
|
10
|
-
- 出站 `send({
|
|
10
|
+
- 出站 `send({ conversation, payload })` → Bot API(text / media / keyboard)
|
|
11
11
|
- 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
|
|
12
12
|
- Webhook 模式延期(需 `httpHostToken`);配置 `polling: false` 会明确报错
|
|
13
13
|
|
|
@@ -25,8 +25,8 @@ pnpm add @zhin.js/adapter-telegram
|
|
|
25
25
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
26
26
|
- **无需** `@zhin.js/host-http`(polling 路径)
|
|
27
27
|
|
|
28
|
-
入站:`gateway.receive({
|
|
29
|
-
出站:`send({
|
|
28
|
+
入站:`gateway.receive({ conversation, message, content, sender, metadata })`(`conversation` 为 ConversationRef,由 `telegramInboundConversation` 归一化)
|
|
29
|
+
出站:`send({ conversation, payload })` → Telegram Bot API
|
|
30
30
|
|
|
31
31
|
### 平台权限(platform permit)
|
|
32
32
|
|
package/adapters/telegram.js
CHANGED
|
@@ -21,8 +21,8 @@ export default defineAdapter({
|
|
|
21
21
|
create(context) {
|
|
22
22
|
const config = resolveTelegramConfig(context.config);
|
|
23
23
|
// 注册到插件运行时状态(telegram.endpoint list 的"运行中"数据源)
|
|
24
|
-
context.use(telegramRuntimeStateToken).endpoints.set(config.
|
|
25
|
-
|
|
24
|
+
context.use(telegramRuntimeStateToken).endpoints.set(config.id, {
|
|
25
|
+
id: config.id,
|
|
26
26
|
mode: config.mode,
|
|
27
27
|
});
|
|
28
28
|
return new TelegramEndpoint({
|
package/adapters/telegram.ts
CHANGED
|
@@ -26,8 +26,8 @@ export default defineAdapter<TelegramAdapterConfig>({
|
|
|
26
26
|
create(context) {
|
|
27
27
|
const config = resolveTelegramConfig(context.config);
|
|
28
28
|
// 注册到插件运行时状态(telegram.endpoint list 的"运行中"数据源)
|
|
29
|
-
context.use(telegramRuntimeStateToken).endpoints.set(config.
|
|
30
|
-
|
|
29
|
+
context.use(telegramRuntimeStateToken).endpoints.set(config.id, {
|
|
30
|
+
id: config.id,
|
|
31
31
|
mode: config.mode,
|
|
32
32
|
});
|
|
33
33
|
return new TelegramEndpoint({
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export declare class TelegramEndpoint implements EndpointInstance {
|
|
|
40
40
|
open(): void;
|
|
41
41
|
close(): void;
|
|
42
42
|
stop(): Promise<void>;
|
|
43
|
-
send({
|
|
43
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
44
44
|
recallMessage(messageId: string): Promise<void>;
|
|
45
45
|
/** Test / internal: admit a message when open. */
|
|
46
46
|
admit(msg: TelegramMessage): void;
|
package/lib/endpoint.js
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
* TelegramEndpoint — lifecycle, outbound, admit, Bot API helpers for agent tools.
|
|
3
3
|
*/
|
|
4
4
|
import { readFile } from 'node:fs/promises';
|
|
5
|
-
import {
|
|
6
|
-
import { formatCompact,
|
|
5
|
+
import { formatLegacyMessageRef, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
|
|
6
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
7
7
|
import { runTelegramPollLoop } from './polling.js';
|
|
8
8
|
import { normalizeTelegramChatMember } from './platform-permit.js';
|
|
9
|
-
import { botApiUrl, buildWebhookUrl, formatCallbackContent, formatCallbackSegments, formatInboundContent, formatInboundSegments, formatOutboundPlan,
|
|
9
|
+
import { botApiUrl, buildWebhookUrl, formatCallbackContent, formatCallbackSegments, formatInboundContent, formatInboundSegments, formatOutboundPlan, resolveTelegramChannelType, senderDisplayName, telegramInboundConversation, } from './protocol.js';
|
|
10
10
|
import { registerTelegramAgentEndpoint } from './telegram-agent-deps.js';
|
|
11
11
|
import { registerTelegramWebhookRoutes } from './webhook.js';
|
|
12
|
-
const logger = getLogger('telegram');
|
|
13
12
|
const CHAT_MEMBER_CACHE_TTL_MS = 60_000;
|
|
14
13
|
const CHAT_MEMBER_CACHE_MAX = 2_000;
|
|
15
14
|
/**
|
|
@@ -18,6 +17,7 @@ const CHAT_MEMBER_CACHE_MAX = 2_000;
|
|
|
18
17
|
* 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
|
|
19
18
|
*/
|
|
20
19
|
export class TelegramEndpoint {
|
|
20
|
+
#logger;
|
|
21
21
|
#options;
|
|
22
22
|
#fetch;
|
|
23
23
|
#pollAbort;
|
|
@@ -34,6 +34,7 @@ export class TelegramEndpoint {
|
|
|
34
34
|
recall: (messageId) => this.recallMessage(messageId),
|
|
35
35
|
});
|
|
36
36
|
constructor(options) {
|
|
37
|
+
this.#logger = getAdapterLogger('telegram', options.config.id);
|
|
37
38
|
this.#options = options;
|
|
38
39
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
39
40
|
}
|
|
@@ -58,7 +59,7 @@ export class TelegramEndpoint {
|
|
|
58
59
|
return;
|
|
59
60
|
this.#started = true;
|
|
60
61
|
try {
|
|
61
|
-
this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.
|
|
62
|
+
this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.id, this);
|
|
62
63
|
const me = await this.callApi('getMe');
|
|
63
64
|
this.#botUserId = me.id;
|
|
64
65
|
this.#botUsername = me.username;
|
|
@@ -70,9 +71,9 @@ export class TelegramEndpoint {
|
|
|
70
71
|
const webhook = this.#options.config.webhook;
|
|
71
72
|
if (!webhook.secretToken) {
|
|
72
73
|
// 未配 secretToken 时 webhook 无鉴权:任何人知道 path 即可注入假 update。
|
|
73
|
-
logger.warn(formatCompact({
|
|
74
|
+
this.#logger.warn(formatCompact({
|
|
74
75
|
op: 'webhook_no_secret',
|
|
75
|
-
endpoint: this.#options.config.
|
|
76
|
+
endpoint: this.#options.config.id,
|
|
76
77
|
path: webhook.path,
|
|
77
78
|
hint: 'set webhook.secretToken to authenticate Telegram callbacks',
|
|
78
79
|
}));
|
|
@@ -83,9 +84,9 @@ export class TelegramEndpoint {
|
|
|
83
84
|
allowed_updates: this.#options.config.allowedUpdates,
|
|
84
85
|
...(webhook.secretToken ? { secret_token: webhook.secretToken } : {}),
|
|
85
86
|
});
|
|
86
|
-
logger.info(formatCompact({
|
|
87
|
+
this.#logger.info(formatCompact({
|
|
87
88
|
op: 'connect',
|
|
88
|
-
endpoint: this.#options.config.
|
|
89
|
+
endpoint: this.#options.config.id,
|
|
89
90
|
mode: 'webhook',
|
|
90
91
|
path: webhook.path,
|
|
91
92
|
username: me.username,
|
|
@@ -95,16 +96,16 @@ export class TelegramEndpoint {
|
|
|
95
96
|
await this.callApi('deleteWebhook', { drop_pending_updates: false });
|
|
96
97
|
this.#pollAbort = new AbortController();
|
|
97
98
|
this.#pollPromise = runTelegramPollLoop(this, this.#pollAbort.signal);
|
|
98
|
-
logger.info(formatCompact({
|
|
99
|
+
this.#logger.info(formatCompact({
|
|
99
100
|
op: 'connect',
|
|
100
|
-
endpoint: this.#options.config.
|
|
101
|
+
endpoint: this.#options.config.id,
|
|
101
102
|
mode: 'polling',
|
|
102
103
|
username: me.username,
|
|
103
104
|
}));
|
|
104
105
|
}
|
|
105
106
|
catch (error) {
|
|
106
107
|
await this.stop();
|
|
107
|
-
logger.error('Failed to connect Telegram bot:', error);
|
|
108
|
+
this.#logger.error('Failed to connect Telegram bot:', error);
|
|
108
109
|
throw error;
|
|
109
110
|
}
|
|
110
111
|
}
|
|
@@ -129,11 +130,10 @@ export class TelegramEndpoint {
|
|
|
129
130
|
this.#unregisterAgent = undefined;
|
|
130
131
|
this.#chatMemberCache.clear();
|
|
131
132
|
this.#started = false;
|
|
132
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
133
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
133
134
|
}
|
|
134
|
-
async send({
|
|
135
|
-
const
|
|
136
|
-
const plan = formatOutboundPlan(chatId, payload);
|
|
135
|
+
async send({ conversation, payload }) {
|
|
136
|
+
const plan = formatOutboundPlan(conversation.id, payload);
|
|
137
137
|
let lastId = '';
|
|
138
138
|
for (const action of plan.actions) {
|
|
139
139
|
const form = await this.#buildUploadForm(action.params, plan.uploads);
|
|
@@ -145,7 +145,7 @@ export class TelegramEndpoint {
|
|
|
145
145
|
}
|
|
146
146
|
if (!lastId)
|
|
147
147
|
return `telegram-${Date.now()}`;
|
|
148
|
-
return
|
|
148
|
+
return formatLegacyMessageRef({ conversation, id: lastId });
|
|
149
149
|
}
|
|
150
150
|
async recallMessage(messageId) {
|
|
151
151
|
if (!messageId || messageId.startsWith('telegram-'))
|
|
@@ -188,37 +188,38 @@ export class TelegramEndpoint {
|
|
|
188
188
|
admit(msg) {
|
|
189
189
|
if (!this.#open)
|
|
190
190
|
return;
|
|
191
|
-
const
|
|
192
|
-
void this.#admitWithSenderRole(msg,
|
|
193
|
-
logger.warn(formatCompact({
|
|
191
|
+
const conversation = telegramInboundConversation(String(this.#options.id), msg.chat);
|
|
192
|
+
void this.#admitWithSenderRole(msg, conversation).catch((err) => {
|
|
193
|
+
this.#logger.warn(formatCompact({
|
|
194
194
|
op: 'telegram_gateway_receive_failed',
|
|
195
|
-
target:
|
|
195
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
196
196
|
error: err instanceof Error ? err.message : String(err),
|
|
197
197
|
}));
|
|
198
198
|
});
|
|
199
199
|
}
|
|
200
|
-
async #admitWithSenderRole(msg,
|
|
200
|
+
async #admitWithSenderRole(msg, conversation) {
|
|
201
201
|
const permit = await this.#resolveGroupSenderPermit(msg);
|
|
202
202
|
// 新 Runtime Message.content 为纯文本:@ 本机只能经 metadata 传递
|
|
203
203
|
const mentioned = this.#isBotMentioned(msg);
|
|
204
204
|
await this.#options.gateway.receive({
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
conversation,
|
|
206
|
+
message: { conversation, id: String(msg.message_id) },
|
|
207
207
|
content: formatInboundContent(msg),
|
|
208
208
|
segments: formatInboundSegments(msg),
|
|
209
|
-
sender:
|
|
210
|
-
|
|
209
|
+
sender: {
|
|
210
|
+
id: String(msg.from?.id ?? ''),
|
|
211
|
+
name: senderDisplayName(msg.from) || undefined,
|
|
212
|
+
...(permit?.role ? { roles: [permit.role] } : {}),
|
|
213
|
+
},
|
|
214
|
+
endpointId: this.#options.config.id,
|
|
215
|
+
...(mentioned ? { mentioned: true } : {}),
|
|
211
216
|
metadata: Object.freeze({
|
|
212
|
-
endpoint: this.#options.config.name,
|
|
213
|
-
// Preserve Telegram's native value below, but publish the canonical
|
|
214
|
-
// scene kind so every Runtime consumer sees the same conversation type.
|
|
215
217
|
channelType: resolveTelegramChannelType(msg.chat.type),
|
|
216
218
|
chatType: msg.chat.type,
|
|
217
219
|
userId: msg.from?.id,
|
|
218
220
|
date: msg.date,
|
|
219
221
|
...(permit?.role ? { senderRole: permit.role } : {}),
|
|
220
222
|
...(permit?.permissions.length ? { senderPermissions: [...permit.permissions] } : {}),
|
|
221
|
-
...(mentioned ? { mentioned: true } : {}),
|
|
222
223
|
}),
|
|
223
224
|
});
|
|
224
225
|
}
|
|
@@ -287,25 +288,28 @@ export class TelegramEndpoint {
|
|
|
287
288
|
admitCallback(query) {
|
|
288
289
|
if (!this.#open)
|
|
289
290
|
return;
|
|
291
|
+
const endpointKey = String(this.#options.id);
|
|
290
292
|
const msg = query.message;
|
|
291
|
-
|
|
293
|
+
// 无挂载消息的 callback(如 inline 模式)退化为 sender 私聊会话
|
|
294
|
+
const conversation = msg
|
|
295
|
+
? telegramInboundConversation(endpointKey, msg.chat)
|
|
296
|
+
: telegramInboundConversation(endpointKey, { id: query.from.id, type: 'private' });
|
|
292
297
|
void this.#options.gateway.receive({
|
|
293
|
-
|
|
294
|
-
|
|
298
|
+
conversation,
|
|
299
|
+
message: { conversation, id: query.id },
|
|
295
300
|
content: formatCallbackContent(query),
|
|
296
301
|
segments: formatCallbackSegments(query),
|
|
297
|
-
sender: senderDisplayName(query.from),
|
|
298
|
-
|
|
302
|
+
sender: { id: String(query.from?.id ?? ''), name: senderDisplayName(query.from) || undefined },
|
|
303
|
+
endpointId: this.#options.config.id,
|
|
299
304
|
metadata: Object.freeze({
|
|
300
|
-
endpoint: this.#options.config.name,
|
|
301
305
|
eventType: 'callback_query',
|
|
302
306
|
payload: query.data,
|
|
303
307
|
sourceMessageId: msg ? String(msg.message_id) : undefined,
|
|
304
308
|
}),
|
|
305
309
|
}).catch((err) => {
|
|
306
|
-
logger.warn(formatCompact({
|
|
310
|
+
this.#logger.warn(formatCompact({
|
|
307
311
|
op: 'telegram_gateway_receive_failed',
|
|
308
|
-
target:
|
|
312
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
309
313
|
error: err instanceof Error ? err.message : String(err),
|
|
310
314
|
}));
|
|
311
315
|
});
|
|
@@ -413,10 +417,3 @@ export class TelegramEndpoint {
|
|
|
413
417
|
});
|
|
414
418
|
}
|
|
415
419
|
}
|
|
416
|
-
function resolveTelegramChannelType(chatType) {
|
|
417
|
-
if (chatType === 'private')
|
|
418
|
-
return 'private';
|
|
419
|
-
if (chatType === 'channel')
|
|
420
|
-
return 'channel';
|
|
421
|
-
return 'group';
|
|
422
|
-
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { TelegramEndpoint, type TelegramEndpointOptions, type TelegramFetch, } from './endpoint.js';
|
|
2
|
-
export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath,
|
|
2
|
+
export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveTelegramConfig, senderDisplayName, type ResolvedTelegramConfig, type TelegramAdapterConfig, type TelegramCallbackQuery, type TelegramChat, type TelegramChatMember, type TelegramMessage, type TelegramOutboundAction, type TelegramOutboundPlan, type TelegramOutboundUpload, type TelegramUpdate, type TelegramUser, type TelegramWireSegment, } from './protocol.js';
|
|
3
3
|
export { getTelegramAgentDeps, registerTelegramAgentEndpoint, setTelegramAgentDeps, type TelegramAgentDeps, type TelegramAgentEndpoint, } from './telegram-agent-deps.js';
|
|
4
|
-
export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit,
|
|
4
|
+
export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit, telegramGroupPermitResolver, } from './platform-permit.js';
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { TelegramEndpoint, } from './endpoint.js';
|
|
2
|
-
export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath,
|
|
2
|
+
export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveTelegramConfig, senderDisplayName, } from './protocol.js';
|
|
3
3
|
export { getTelegramAgentDeps, registerTelegramAgentEndpoint, setTelegramAgentDeps, } from './telegram-agent-deps.js';
|
|
4
|
-
export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit,
|
|
4
|
+
export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit, telegramGroupPermitResolver, } from './platform-permit.js';
|
package/lib/platform-permit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram platform permit — ChatMember 状态 + 细粒度权限
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
export declare function platformPermit(perm: string): string;
|
|
6
6
|
export declare function telegramGroupPermitResolver(logicalPerm: string): string;
|
|
7
7
|
export declare function normalizeTelegramChatMember(member: {
|
|
@@ -14,5 +14,4 @@ export declare function normalizeTelegramChatMember(member: {
|
|
|
14
14
|
role?: string;
|
|
15
15
|
permissions: string[];
|
|
16
16
|
};
|
|
17
|
-
export declare function checkTelegramPlatformPermit(perm: string,
|
|
18
|
-
export declare function registerTelegramPlatformPermitChecker(): () => void;
|
|
17
|
+
export declare function checkTelegramPlatformPermit(perm: string, subject: PermissionSubject): boolean;
|
package/lib/platform-permit.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Telegram platform permit — ChatMember 状态 + 细粒度权限
|
|
3
|
-
*/
|
|
4
|
-
import { registerPlatformPermitChecker } from '@zhin.js/core';
|
|
5
1
|
const ADAPTER = 'telegram';
|
|
6
2
|
export function platformPermit(perm) {
|
|
7
3
|
return `platform(${ADAPTER},${perm})`;
|
|
@@ -34,12 +30,9 @@ export function normalizeTelegramChatMember(member) {
|
|
|
34
30
|
}
|
|
35
31
|
return { role: 'member', permissions };
|
|
36
32
|
}
|
|
37
|
-
function
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
export function checkTelegramPlatformPermit(perm, message) {
|
|
42
|
-
const { role, permissions } = senderPermits(message);
|
|
33
|
+
export function checkTelegramPlatformPermit(perm, subject) {
|
|
34
|
+
const role = subject.sender?.role?.[0];
|
|
35
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
43
36
|
const has = (t) => permissions.includes(t);
|
|
44
37
|
switch (perm) {
|
|
45
38
|
case 'chat_creator':
|
|
@@ -56,6 +49,3 @@ export function checkTelegramPlatformPermit(perm, message) {
|
|
|
56
49
|
return false;
|
|
57
50
|
}
|
|
58
51
|
}
|
|
59
|
-
export function registerTelegramPlatformPermitChecker() {
|
|
60
|
-
return registerPlatformPermitChecker(ADAPTER, checkTelegramPlatformPermit);
|
|
61
|
-
}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { IncomingMessage } from 'node:http';
|
|
6
6
|
import type { Segment } from '@zhin.js/core/runtime';
|
|
7
|
+
import type { ConversationKind, ConversationRef } from '@zhin.js/im-contract';
|
|
7
8
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
8
9
|
export interface TelegramAdapterConfig {
|
|
9
|
-
readonly
|
|
10
|
+
readonly id?: string;
|
|
10
11
|
readonly token?: string;
|
|
11
12
|
/** Default true. `false` selects webhook mode (requires httpHostToken). */
|
|
12
13
|
readonly polling?: boolean;
|
|
@@ -28,7 +29,7 @@ export interface TelegramAdapterConfig {
|
|
|
28
29
|
}
|
|
29
30
|
export interface ResolvedTelegramConfig {
|
|
30
31
|
readonly context: 'telegram';
|
|
31
|
-
readonly
|
|
32
|
+
readonly id: string;
|
|
32
33
|
readonly token: string;
|
|
33
34
|
readonly mode: 'polling' | 'webhook';
|
|
34
35
|
readonly allowedUpdates: readonly string[];
|
|
@@ -236,10 +237,13 @@ export declare function readTextBody(request: IncomingMessage, options?: {
|
|
|
236
237
|
readonly limit?: number;
|
|
237
238
|
}): Promise<string>;
|
|
238
239
|
export declare function botApiUrl(config: Pick<ResolvedTelegramConfig, 'apiBaseUrl' | 'token'>, method: string): string;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
/** Telegram chat.type → canonical 会话 kind(supergroup 即 group,无容器层级故无 parent)。 */
|
|
241
|
+
export declare function resolveTelegramChannelType(chatType: TelegramChat['type']): ConversationKind;
|
|
242
|
+
/**
|
|
243
|
+
* 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
|
|
244
|
+
* kind 直取 chat.type(private/group/supergroup/channel),无 parent。
|
|
245
|
+
*/
|
|
246
|
+
export declare function telegramInboundConversation(endpointKey: string, chat: Pick<TelegramChat, 'id' | 'type'>): ConversationRef;
|
|
243
247
|
export declare function senderDisplayName(user?: TelegramUser): string;
|
|
244
248
|
/** Build inbound text for MessageGateway.receive. */
|
|
245
249
|
export declare function formatInboundContent(msg: TelegramMessage): string;
|
package/lib/protocol.js
CHANGED
|
@@ -14,8 +14,8 @@ export function resolveTelegramConfig(config = {}) {
|
|
|
14
14
|
if (!token) {
|
|
15
15
|
throw new TypeError('Telegram adapter requires token (plugins.<key>.token or endpoints with context: telegram)');
|
|
16
16
|
}
|
|
17
|
-
const
|
|
18
|
-
|| (typeof entry?.
|
|
17
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
18
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
19
19
|
|| process.env.TELEGRAM_BOT_NAME
|
|
20
20
|
|| 'telegram-bot';
|
|
21
21
|
const polling = config.polling ?? entry?.polling;
|
|
@@ -39,7 +39,7 @@ export function resolveTelegramConfig(config = {}) {
|
|
|
39
39
|
: undefined;
|
|
40
40
|
return {
|
|
41
41
|
context: 'telegram',
|
|
42
|
-
|
|
42
|
+
id,
|
|
43
43
|
token,
|
|
44
44
|
mode,
|
|
45
45
|
allowedUpdates: [...allowedUpdates],
|
|
@@ -76,10 +76,23 @@ export async function readTextBody(request, options = {}) {
|
|
|
76
76
|
export function botApiUrl(config, method) {
|
|
77
77
|
return `${config.apiBaseUrl}/bot${config.token}/${method}`;
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
/** Telegram chat.type → canonical 会话 kind(supergroup 即 group,无容器层级故无 parent)。 */
|
|
80
|
+
export function resolveTelegramChannelType(chatType) {
|
|
81
|
+
if (chatType === 'private')
|
|
82
|
+
return 'private';
|
|
83
|
+
if (chatType === 'channel')
|
|
84
|
+
return 'channel';
|
|
85
|
+
return 'group';
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
|
|
89
|
+
* kind 直取 chat.type(private/group/supergroup/channel),无 parent。
|
|
90
|
+
*/
|
|
91
|
+
export function telegramInboundConversation(endpointKey, chat) {
|
|
80
92
|
return {
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
94
|
+
kind: resolveTelegramChannelType(chat.type),
|
|
95
|
+
id: String(chat.id),
|
|
83
96
|
};
|
|
84
97
|
}
|
|
85
98
|
export function senderDisplayName(user) {
|
|
@@ -20,9 +20,9 @@ export interface TelegramAgentEndpoint {
|
|
|
20
20
|
}>;
|
|
21
21
|
}
|
|
22
22
|
export interface TelegramAgentDeps {
|
|
23
|
-
getEndpoint: (
|
|
23
|
+
getEndpoint: (endpointKey: string) => TelegramAgentEndpoint;
|
|
24
24
|
}
|
|
25
|
-
export declare function registerTelegramAgentEndpoint(
|
|
25
|
+
export declare function registerTelegramAgentEndpoint(endpointKey: string, endpoint: TelegramAgentEndpoint): () => void;
|
|
26
26
|
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
27
27
|
export declare function setTelegramAgentDeps(deps: TelegramAgentDeps | null): void;
|
|
28
28
|
export declare function getTelegramAgentDeps(): TelegramAgentDeps;
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const endpoints = new Map();
|
|
6
6
|
let override = null;
|
|
7
|
-
export function registerTelegramAgentEndpoint(
|
|
8
|
-
endpoints.set(
|
|
7
|
+
export function registerTelegramAgentEndpoint(endpointKey, endpoint) {
|
|
8
|
+
endpoints.set(endpointKey, endpoint);
|
|
9
9
|
return () => {
|
|
10
|
-
if (endpoints.get(
|
|
11
|
-
endpoints.delete(
|
|
10
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
11
|
+
endpoints.delete(endpointKey);
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
}
|
|
@@ -20,10 +20,10 @@ export function getTelegramAgentDeps() {
|
|
|
20
20
|
if (override)
|
|
21
21
|
return override;
|
|
22
22
|
return {
|
|
23
|
-
getEndpoint(
|
|
24
|
-
const registered = endpoints.get(
|
|
23
|
+
getEndpoint(endpointKey) {
|
|
24
|
+
const registered = endpoints.get(endpointKey);
|
|
25
25
|
if (!registered)
|
|
26
|
-
throw new Error(`Endpoint ${
|
|
26
|
+
throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
27
27
|
return registered;
|
|
28
28
|
},
|
|
29
29
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-telegram",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.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,21 +32,22 @@
|
|
|
32
32
|
"directory": "plugins/adapters/telegram"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@zhin.js/adapter": "1.1.
|
|
36
|
-
"@zhin.js/command": "1.0.
|
|
37
|
-
"@zhin.js/core": "1.5.
|
|
38
|
-
"@zhin.js/host-http": "1.0.
|
|
39
|
-
"@zhin.js/im-contract": "1.0.
|
|
40
|
-
"@zhin.js/logger": "1.0.
|
|
41
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
35
|
+
"@zhin.js/adapter": "1.1.7",
|
|
36
|
+
"@zhin.js/command": "1.0.9",
|
|
37
|
+
"@zhin.js/core": "1.5.4",
|
|
38
|
+
"@zhin.js/host-http": "1.0.8",
|
|
39
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
40
|
+
"@zhin.js/logger": "1.0.76",
|
|
41
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"zod": "^4.0.0",
|
|
45
|
-
"@zhin.js/adapter": "1.1.
|
|
46
|
-
"@zhin.js/agent": "1.1.
|
|
47
|
-
"@zhin.js/core": "1.5.
|
|
48
|
-
"@zhin.js/
|
|
49
|
-
"zhin.js": "
|
|
45
|
+
"@zhin.js/adapter": "1.1.7",
|
|
46
|
+
"@zhin.js/agent": "1.1.5",
|
|
47
|
+
"@zhin.js/core": "1.5.4",
|
|
48
|
+
"@zhin.js/permission": "1.0.1",
|
|
49
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
50
|
+
"zhin.js": "6.0.4"
|
|
50
51
|
},
|
|
51
52
|
"peerDependenciesMeta": {
|
|
52
53
|
"zhin.js": {
|
|
@@ -64,9 +65,9 @@
|
|
|
64
65
|
"typescript": "^6.0.3",
|
|
65
66
|
"vitest": "^4.1.10",
|
|
66
67
|
"zod": "^4.4.3",
|
|
67
|
-
"@zhin.js/agent": "1.1.
|
|
68
|
-
"@zhin.js/host-http": "1.0.
|
|
69
|
-
"zhin.js": "6.0.
|
|
68
|
+
"@zhin.js/agent": "1.1.5",
|
|
69
|
+
"@zhin.js/host-http": "1.0.8",
|
|
70
|
+
"zhin.js": "6.0.4"
|
|
70
71
|
},
|
|
71
72
|
"files": [
|
|
72
73
|
"adapters",
|
package/plugin.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { createEndpointRuntimeState } from '@zhin.js/adapter';
|
|
3
3
|
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
4
|
-
import {
|
|
4
|
+
import { permissionHostToken } from '@zhin.js/permission';
|
|
5
|
+
import { checkTelegramPlatformPermit } from "./lib/platform-permit.js";
|
|
5
6
|
import { telegramRuntimeStateToken } from "./lib/telegram-runtime-state.js";
|
|
6
7
|
export default definePlugin({
|
|
7
8
|
name: 'telegram',
|
|
@@ -9,9 +10,10 @@ export default definePlugin({
|
|
|
9
10
|
displayName: 'Telegram Bot API Adapter',
|
|
10
11
|
},
|
|
11
12
|
setup(context) {
|
|
12
|
-
// 运行中 endpoint 注册表(telegram.endpoint list 的"运行中"数据源)
|
|
13
13
|
context.resources.provide(telegramRuntimeStateToken, createEndpointRuntimeState());
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
if (context.resources.has(permissionHostToken)) {
|
|
15
|
+
const host = context.resources.use(permissionHostToken);
|
|
16
|
+
return host.registerPlatform('telegram', checkTelegramPlatformPermit);
|
|
17
|
+
}
|
|
16
18
|
},
|
|
17
19
|
});
|
package/schema.json
CHANGED
|
@@ -40,24 +40,60 @@
|
|
|
40
40
|
"type": "string",
|
|
41
41
|
"default": "https://api.telegram.org"
|
|
42
42
|
},
|
|
43
|
+
"master": {
|
|
44
|
+
"type": [
|
|
45
|
+
"string",
|
|
46
|
+
"number"
|
|
47
|
+
],
|
|
48
|
+
"description": "框架 master(Telegram user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
49
|
+
},
|
|
50
|
+
"trusted": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": {
|
|
53
|
+
"type": [
|
|
54
|
+
"string",
|
|
55
|
+
"number"
|
|
56
|
+
],
|
|
57
|
+
"description": "Trusted Telegram user id"
|
|
58
|
+
},
|
|
59
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
60
|
+
},
|
|
43
61
|
"endpoints": {
|
|
44
62
|
"type": "array",
|
|
45
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
63
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
46
64
|
"items": {
|
|
47
65
|
"type": "object",
|
|
48
66
|
"additionalProperties": true,
|
|
49
67
|
"properties": {
|
|
50
|
-
"
|
|
51
|
-
"type":
|
|
52
|
-
|
|
68
|
+
"master": {
|
|
69
|
+
"type": [
|
|
70
|
+
"string",
|
|
71
|
+
"number"
|
|
72
|
+
],
|
|
73
|
+
"description": "本 endpoint 的框架 master(Telegram user id);覆盖顶层 master"
|
|
74
|
+
},
|
|
75
|
+
"trusted": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"items": {
|
|
78
|
+
"type": [
|
|
79
|
+
"string",
|
|
80
|
+
"number"
|
|
81
|
+
],
|
|
82
|
+
"description": "Trusted Telegram user id"
|
|
83
|
+
},
|
|
84
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
53
85
|
},
|
|
54
86
|
"token": {
|
|
55
87
|
"type": "string",
|
|
56
88
|
"description": "Telegram bot token"
|
|
89
|
+
},
|
|
90
|
+
"id": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"description": "Telegram bot name"
|
|
57
93
|
}
|
|
58
94
|
},
|
|
59
95
|
"required": [
|
|
60
|
-
"
|
|
96
|
+
"id",
|
|
61
97
|
"token"
|
|
62
98
|
]
|
|
63
99
|
}
|
package/src/endpoint.ts
CHANGED
|
@@ -6,11 +6,12 @@ import type { EndpointControl, EndpointInstance, EndpointSendRequest } from '@zh
|
|
|
6
6
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
7
7
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
formatLegacyMessageRef,
|
|
10
10
|
nativeConversationId,
|
|
11
11
|
parseLegacyMessageReference,
|
|
12
|
+
type ConversationRef,
|
|
12
13
|
} from '@zhin.js/im-contract';
|
|
13
|
-
import { formatCompact,
|
|
14
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
14
15
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
15
16
|
import { runTelegramPollLoop } from './polling.js';
|
|
16
17
|
import { normalizeTelegramChatMember } from './platform-permit.js';
|
|
@@ -22,8 +23,9 @@ import {
|
|
|
22
23
|
formatInboundContent,
|
|
23
24
|
formatInboundSegments,
|
|
24
25
|
formatOutboundPlan,
|
|
25
|
-
|
|
26
|
+
resolveTelegramChannelType,
|
|
26
27
|
senderDisplayName,
|
|
28
|
+
telegramInboundConversation,
|
|
27
29
|
type ResolvedTelegramConfig,
|
|
28
30
|
type TelegramCallbackQuery,
|
|
29
31
|
type TelegramChatMember,
|
|
@@ -34,8 +36,6 @@ import {
|
|
|
34
36
|
import { registerTelegramAgentEndpoint } from './telegram-agent-deps.js';
|
|
35
37
|
import { registerTelegramWebhookRoutes } from './webhook.js';
|
|
36
38
|
|
|
37
|
-
const logger = getLogger('telegram');
|
|
38
|
-
|
|
39
39
|
const CHAT_MEMBER_CACHE_TTL_MS = 60_000;
|
|
40
40
|
const CHAT_MEMBER_CACHE_MAX = 2_000;
|
|
41
41
|
|
|
@@ -85,6 +85,8 @@ interface TelegramApiErr {
|
|
|
85
85
|
* 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
|
|
86
86
|
*/
|
|
87
87
|
export class TelegramEndpoint implements EndpointInstance {
|
|
88
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
89
|
+
|
|
88
90
|
readonly #options: TelegramEndpointOptions;
|
|
89
91
|
readonly #fetch: TelegramFetch;
|
|
90
92
|
#pollAbort?: AbortController;
|
|
@@ -102,6 +104,7 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
102
104
|
});
|
|
103
105
|
|
|
104
106
|
constructor(options: TelegramEndpointOptions) {
|
|
107
|
+
this.#logger = getAdapterLogger('telegram', options.config.id);
|
|
105
108
|
this.#options = options;
|
|
106
109
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
107
110
|
}
|
|
@@ -131,7 +134,7 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
131
134
|
if (this.#started) return;
|
|
132
135
|
this.#started = true;
|
|
133
136
|
try {
|
|
134
|
-
this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.
|
|
137
|
+
this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.id, this);
|
|
135
138
|
const me = await this.callApi<{ id?: number; username?: string; first_name?: string }>('getMe');
|
|
136
139
|
this.#botUserId = me.id;
|
|
137
140
|
this.#botUsername = me.username;
|
|
@@ -144,9 +147,9 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
144
147
|
const webhook = this.#options.config.webhook!;
|
|
145
148
|
if (!webhook.secretToken) {
|
|
146
149
|
// 未配 secretToken 时 webhook 无鉴权:任何人知道 path 即可注入假 update。
|
|
147
|
-
logger.warn(formatCompact({
|
|
150
|
+
this.#logger.warn(formatCompact({
|
|
148
151
|
op: 'webhook_no_secret',
|
|
149
|
-
endpoint: this.#options.config.
|
|
152
|
+
endpoint: this.#options.config.id,
|
|
150
153
|
path: webhook.path,
|
|
151
154
|
hint: 'set webhook.secretToken to authenticate Telegram callbacks',
|
|
152
155
|
}));
|
|
@@ -157,9 +160,9 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
157
160
|
allowed_updates: this.#options.config.allowedUpdates,
|
|
158
161
|
...(webhook.secretToken ? { secret_token: webhook.secretToken } : {}),
|
|
159
162
|
});
|
|
160
|
-
logger.info(formatCompact({
|
|
163
|
+
this.#logger.info(formatCompact({
|
|
161
164
|
op: 'connect',
|
|
162
|
-
endpoint: this.#options.config.
|
|
165
|
+
endpoint: this.#options.config.id,
|
|
163
166
|
mode: 'webhook',
|
|
164
167
|
path: webhook.path,
|
|
165
168
|
username: me.username,
|
|
@@ -170,15 +173,15 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
170
173
|
await this.callApi('deleteWebhook', { drop_pending_updates: false });
|
|
171
174
|
this.#pollAbort = new AbortController();
|
|
172
175
|
this.#pollPromise = runTelegramPollLoop(this, this.#pollAbort.signal);
|
|
173
|
-
logger.info(formatCompact({
|
|
176
|
+
this.#logger.info(formatCompact({
|
|
174
177
|
op: 'connect',
|
|
175
|
-
endpoint: this.#options.config.
|
|
178
|
+
endpoint: this.#options.config.id,
|
|
176
179
|
mode: 'polling',
|
|
177
180
|
username: me.username,
|
|
178
181
|
}));
|
|
179
182
|
} catch (error) {
|
|
180
183
|
await this.stop();
|
|
181
|
-
logger.error('Failed to connect Telegram bot:', error);
|
|
184
|
+
this.#logger.error('Failed to connect Telegram bot:', error);
|
|
182
185
|
throw error;
|
|
183
186
|
}
|
|
184
187
|
}
|
|
@@ -204,12 +207,11 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
204
207
|
this.#unregisterAgent = undefined;
|
|
205
208
|
this.#chatMemberCache.clear();
|
|
206
209
|
this.#started = false;
|
|
207
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
210
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
208
211
|
}
|
|
209
212
|
|
|
210
|
-
async send({
|
|
211
|
-
const
|
|
212
|
-
const plan = formatOutboundPlan(chatId, payload);
|
|
213
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
214
|
+
const plan = formatOutboundPlan(conversation.id, payload);
|
|
213
215
|
let lastId = '';
|
|
214
216
|
for (const action of plan.actions) {
|
|
215
217
|
const form = await this.#buildUploadForm(action.params, plan.uploads);
|
|
@@ -219,7 +221,7 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
219
221
|
if (result.message_id != null) lastId = String(result.message_id);
|
|
220
222
|
}
|
|
221
223
|
if (!lastId) return `telegram-${Date.now()}`;
|
|
222
|
-
return
|
|
224
|
+
return formatLegacyMessageRef({ conversation, id: lastId });
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
async recallMessage(messageId: string): Promise<void> {
|
|
@@ -266,38 +268,39 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
266
268
|
/** Test / internal: admit a message when open. */
|
|
267
269
|
admit(msg: TelegramMessage): void {
|
|
268
270
|
if (!this.#open) return;
|
|
269
|
-
const
|
|
270
|
-
void this.#admitWithSenderRole(msg,
|
|
271
|
-
logger.warn(formatCompact({
|
|
271
|
+
const conversation = telegramInboundConversation(String(this.#options.id), msg.chat);
|
|
272
|
+
void this.#admitWithSenderRole(msg, conversation).catch((err) => {
|
|
273
|
+
this.#logger.warn(formatCompact({
|
|
272
274
|
op: 'telegram_gateway_receive_failed',
|
|
273
|
-
target:
|
|
275
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
274
276
|
error: err instanceof Error ? err.message : String(err),
|
|
275
277
|
}));
|
|
276
278
|
});
|
|
277
279
|
}
|
|
278
280
|
|
|
279
|
-
async #admitWithSenderRole(msg: TelegramMessage,
|
|
281
|
+
async #admitWithSenderRole(msg: TelegramMessage, conversation: ConversationRef): Promise<void> {
|
|
280
282
|
const permit = await this.#resolveGroupSenderPermit(msg);
|
|
281
283
|
// 新 Runtime Message.content 为纯文本:@ 本机只能经 metadata 传递
|
|
282
284
|
const mentioned = this.#isBotMentioned(msg);
|
|
283
285
|
await this.#options.gateway.receive({
|
|
284
|
-
|
|
285
|
-
|
|
286
|
+
conversation,
|
|
287
|
+
message: { conversation, id: String(msg.message_id) },
|
|
286
288
|
content: formatInboundContent(msg),
|
|
287
289
|
segments: formatInboundSegments(msg),
|
|
288
|
-
sender:
|
|
289
|
-
|
|
290
|
+
sender: {
|
|
291
|
+
id: String(msg.from?.id ?? ''),
|
|
292
|
+
name: senderDisplayName(msg.from) || undefined,
|
|
293
|
+
...(permit?.role ? { roles: [permit.role] } : {}),
|
|
294
|
+
},
|
|
295
|
+
endpointId: this.#options.config.id,
|
|
296
|
+
...(mentioned ? { mentioned: true } : {}),
|
|
290
297
|
metadata: Object.freeze({
|
|
291
|
-
endpoint: this.#options.config.name,
|
|
292
|
-
// Preserve Telegram's native value below, but publish the canonical
|
|
293
|
-
// scene kind so every Runtime consumer sees the same conversation type.
|
|
294
298
|
channelType: resolveTelegramChannelType(msg.chat.type),
|
|
295
299
|
chatType: msg.chat.type,
|
|
296
300
|
userId: msg.from?.id,
|
|
297
301
|
date: msg.date,
|
|
298
302
|
...(permit?.role ? { senderRole: permit.role } : {}),
|
|
299
303
|
...(permit?.permissions.length ? { senderPermissions: [...permit.permissions] } : {}),
|
|
300
|
-
...(mentioned ? { mentioned: true } : {}),
|
|
301
304
|
}),
|
|
302
305
|
});
|
|
303
306
|
}
|
|
@@ -360,25 +363,28 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
360
363
|
/** Test / internal: admit a callback query when open. */
|
|
361
364
|
admitCallback(query: TelegramCallbackQuery): void {
|
|
362
365
|
if (!this.#open) return;
|
|
366
|
+
const endpointKey = String(this.#options.id);
|
|
363
367
|
const msg = query.message;
|
|
364
|
-
|
|
368
|
+
// 无挂载消息的 callback(如 inline 模式)退化为 sender 私聊会话
|
|
369
|
+
const conversation = msg
|
|
370
|
+
? telegramInboundConversation(endpointKey, msg.chat)
|
|
371
|
+
: telegramInboundConversation(endpointKey, { id: query.from.id, type: 'private' });
|
|
365
372
|
void this.#options.gateway.receive({
|
|
366
|
-
|
|
367
|
-
|
|
373
|
+
conversation,
|
|
374
|
+
message: { conversation, id: query.id },
|
|
368
375
|
content: formatCallbackContent(query),
|
|
369
376
|
segments: formatCallbackSegments(query),
|
|
370
|
-
sender: senderDisplayName(query.from),
|
|
371
|
-
|
|
377
|
+
sender: { id: String(query.from?.id ?? ''), name: senderDisplayName(query.from) || undefined },
|
|
378
|
+
endpointId: this.#options.config.id,
|
|
372
379
|
metadata: Object.freeze({
|
|
373
|
-
endpoint: this.#options.config.name,
|
|
374
380
|
eventType: 'callback_query',
|
|
375
381
|
payload: query.data,
|
|
376
382
|
sourceMessageId: msg ? String(msg.message_id) : undefined,
|
|
377
383
|
}),
|
|
378
384
|
}).catch((err) => {
|
|
379
|
-
logger.warn(formatCompact({
|
|
385
|
+
this.#logger.warn(formatCompact({
|
|
380
386
|
op: 'telegram_gateway_receive_failed',
|
|
381
|
-
target:
|
|
387
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
382
388
|
error: err instanceof Error ? err.message : String(err),
|
|
383
389
|
}));
|
|
384
390
|
});
|
|
@@ -521,11 +527,3 @@ export class TelegramEndpoint implements EndpointInstance {
|
|
|
521
527
|
});
|
|
522
528
|
}
|
|
523
529
|
}
|
|
524
|
-
|
|
525
|
-
function resolveTelegramChannelType(
|
|
526
|
-
chatType: TelegramMessage['chat']['type'],
|
|
527
|
-
): 'private' | 'group' | 'channel' {
|
|
528
|
-
if (chatType === 'private') return 'private';
|
|
529
|
-
if (chatType === 'channel') return 'channel';
|
|
530
|
-
return 'group';
|
|
531
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,6 @@ export {
|
|
|
11
11
|
formatOutboundActions,
|
|
12
12
|
formatOutboundPlan,
|
|
13
13
|
normalizeWebhookPath,
|
|
14
|
-
resolveChannel,
|
|
15
14
|
resolveTelegramConfig,
|
|
16
15
|
senderDisplayName,
|
|
17
16
|
type ResolvedTelegramConfig,
|
|
@@ -40,6 +39,5 @@ export {
|
|
|
40
39
|
checkTelegramPlatformPermit,
|
|
41
40
|
normalizeTelegramChatMember,
|
|
42
41
|
platformPermit,
|
|
43
|
-
registerTelegramPlatformPermitChecker,
|
|
44
42
|
telegramGroupPermitResolver,
|
|
45
43
|
} from './platform-permit.js';
|
package/src/platform-permit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram platform permit — ChatMember 状态 + 细粒度权限
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
|
|
6
6
|
const ADAPTER = 'telegram';
|
|
7
7
|
|
|
@@ -42,13 +42,9 @@ export function normalizeTelegramChatMember(member: {
|
|
|
42
42
|
return { role: 'member', permissions };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function checkTelegramPlatformPermit(perm: string, message: Message<any>): boolean {
|
|
51
|
-
const { role, permissions } = senderPermits(message);
|
|
45
|
+
export function checkTelegramPlatformPermit(perm: string, subject: PermissionSubject): boolean {
|
|
46
|
+
const role = subject.sender?.role?.[0];
|
|
47
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
52
48
|
const has = (t: string) => permissions.includes(t);
|
|
53
49
|
|
|
54
50
|
switch (perm) {
|
|
@@ -67,6 +63,3 @@ export function checkTelegramPlatformPermit(perm: string, message: Message<any>)
|
|
|
67
63
|
}
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
export function registerTelegramPlatformPermitChecker(): () => void {
|
|
71
|
-
return registerPlatformPermitChecker(ADAPTER, checkTelegramPlatformPermit);
|
|
72
|
-
}
|
package/src/protocol.ts
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
import type { IncomingMessage } from 'node:http';
|
|
7
7
|
import { isMediaRef } from '@zhin.js/core';
|
|
8
8
|
import type { Segment } from '@zhin.js/core/runtime';
|
|
9
|
+
import type { ConversationKind, ConversationRef } from '@zhin.js/im-contract';
|
|
9
10
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
10
11
|
|
|
11
12
|
const logger = getLogger('telegram');
|
|
12
13
|
|
|
13
14
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
14
15
|
export interface TelegramAdapterConfig {
|
|
15
|
-
readonly
|
|
16
|
+
readonly id?: string;
|
|
16
17
|
readonly token?: string;
|
|
17
18
|
/** Default true. `false` selects webhook mode (requires httpHostToken). */
|
|
18
19
|
readonly polling?: boolean;
|
|
@@ -35,7 +36,7 @@ export interface TelegramAdapterConfig {
|
|
|
35
36
|
|
|
36
37
|
export interface ResolvedTelegramConfig {
|
|
37
38
|
readonly context: 'telegram';
|
|
38
|
-
readonly
|
|
39
|
+
readonly id: string;
|
|
39
40
|
readonly token: string;
|
|
40
41
|
readonly mode: 'polling' | 'webhook';
|
|
41
42
|
readonly allowedUpdates: readonly string[];
|
|
@@ -249,8 +250,8 @@ export function resolveTelegramConfig(config: TelegramAdapterConfig = {}): Resol
|
|
|
249
250
|
'Telegram adapter requires token (plugins.<key>.token or endpoints with context: telegram)',
|
|
250
251
|
);
|
|
251
252
|
}
|
|
252
|
-
const
|
|
253
|
-
|| (typeof entry?.
|
|
253
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
254
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
254
255
|
|| process.env.TELEGRAM_BOT_NAME
|
|
255
256
|
|| 'telegram-bot';
|
|
256
257
|
const polling = config.polling ?? entry?.polling;
|
|
@@ -276,7 +277,7 @@ export function resolveTelegramConfig(config: TelegramAdapterConfig = {}): Resol
|
|
|
276
277
|
: undefined;
|
|
277
278
|
return {
|
|
278
279
|
context: 'telegram',
|
|
279
|
-
|
|
280
|
+
id,
|
|
280
281
|
token,
|
|
281
282
|
mode,
|
|
282
283
|
allowedUpdates: [...allowedUpdates],
|
|
@@ -321,13 +322,27 @@ export function botApiUrl(config: Pick<ResolvedTelegramConfig, 'apiBaseUrl' | 't
|
|
|
321
322
|
return `${config.apiBaseUrl}/bot${config.token}/${method}`;
|
|
322
323
|
}
|
|
323
324
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
325
|
+
/** Telegram chat.type → canonical 会话 kind(supergroup 即 group,无容器层级故无 parent)。 */
|
|
326
|
+
export function resolveTelegramChannelType(
|
|
327
|
+
chatType: TelegramChat['type'],
|
|
328
|
+
): ConversationKind {
|
|
329
|
+
if (chatType === 'private') return 'private';
|
|
330
|
+
if (chatType === 'channel') return 'channel';
|
|
331
|
+
return 'group';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
|
|
336
|
+
* kind 直取 chat.type(private/group/supergroup/channel),无 parent。
|
|
337
|
+
*/
|
|
338
|
+
export function telegramInboundConversation(
|
|
339
|
+
endpointKey: string,
|
|
340
|
+
chat: Pick<TelegramChat, 'id' | 'type'>,
|
|
341
|
+
): ConversationRef {
|
|
328
342
|
return {
|
|
329
|
-
|
|
330
|
-
|
|
343
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
344
|
+
kind: resolveTelegramChannelType(chat.type),
|
|
345
|
+
id: String(chat.id),
|
|
331
346
|
};
|
|
332
347
|
}
|
|
333
348
|
|
|
@@ -28,20 +28,20 @@ export interface TelegramAgentEndpoint {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface TelegramAgentDeps {
|
|
31
|
-
getEndpoint: (
|
|
31
|
+
getEndpoint: (endpointKey: string) => TelegramAgentEndpoint;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const endpoints = new Map<string, TelegramAgentEndpoint>();
|
|
35
35
|
let override: TelegramAgentDeps | null = null;
|
|
36
36
|
|
|
37
37
|
export function registerTelegramAgentEndpoint(
|
|
38
|
-
|
|
38
|
+
endpointKey: string,
|
|
39
39
|
endpoint: TelegramAgentEndpoint,
|
|
40
40
|
): () => void {
|
|
41
|
-
endpoints.set(
|
|
41
|
+
endpoints.set(endpointKey, endpoint);
|
|
42
42
|
return () => {
|
|
43
|
-
if (endpoints.get(
|
|
44
|
-
endpoints.delete(
|
|
43
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
44
|
+
endpoints.delete(endpointKey);
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
}
|
|
@@ -54,9 +54,9 @@ export function setTelegramAgentDeps(deps: TelegramAgentDeps | null): void {
|
|
|
54
54
|
export function getTelegramAgentDeps(): TelegramAgentDeps {
|
|
55
55
|
if (override) return override;
|
|
56
56
|
return {
|
|
57
|
-
getEndpoint(
|
|
58
|
-
const registered = endpoints.get(
|
|
59
|
-
if (!registered) throw new Error(`Endpoint ${
|
|
57
|
+
getEndpoint(endpointKey: string): TelegramAgentEndpoint {
|
|
58
|
+
const registered = endpoints.get(endpointKey);
|
|
59
|
+
if (!registered) throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
60
60
|
return registered;
|
|
61
61
|
},
|
|
62
62
|
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|