@zhin.js/adapter-telegram 1.0.69 → 1.1.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/CHANGELOG.md +816 -17
- package/README.md +106 -77
- package/adapters/telegram.js +34 -0
- package/adapters/telegram.ts +39 -0
- package/agent/PERMITS.md +24 -0
- package/agent/tools/create_invite.ts +18 -0
- package/agent/tools/list_admins.ts +24 -0
- package/agent/tools/member_count.ts +16 -0
- package/agent/tools/pin_message.ts +19 -0
- package/agent/tools/react.ts +18 -0
- package/agent/tools/send_poll.ts +32 -0
- package/agent/tools/send_sticker.ts +17 -0
- package/agent/tools/set_description.ts +17 -0
- package/agent/tools/set_permissions.ts +31 -0
- package/agent/tools/unpin_message.ts +19 -0
- package/commands/endpoint/add/[id].js +3 -0
- package/commands/endpoint/add/[id].ts +3 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +12 -0
- package/lib/client.js +2 -0
- package/lib/endpoint.d.ts +112 -0
- package/lib/endpoint.js +535 -0
- package/lib/index.d.ts +4 -18
- package/lib/index.js +4 -455
- package/lib/markdown-to-html.d.ts +9 -0
- package/lib/markdown-to-html.js +75 -0
- package/lib/platform-permit.d.ts +17 -0
- package/lib/platform-permit.js +51 -0
- package/lib/polling.d.ts +9 -0
- package/lib/polling.js +57 -0
- package/lib/protocol.d.ts +299 -0
- package/lib/protocol.js +474 -0
- package/lib/telegram-endpoint-commands.d.ts +1 -0
- package/lib/telegram-endpoint-commands.js +16 -0
- package/lib/telegram-runtime-state.d.ts +1 -0
- package/lib/telegram-runtime-state.js +6 -0
- package/lib/webhook.d.ts +12 -0
- package/lib/webhook.js +55 -0
- package/package.json +59 -28
- package/plugin.js +19 -0
- package/schema.json +110 -0
- package/src/client.ts +16 -0
- package/src/endpoint.ts +679 -0
- package/src/index.ts +39 -426
- package/src/markdown-to-html.ts +86 -0
- package/src/platform-permit.ts +65 -0
- package/src/polling.ts +76 -0
- package/src/protocol.ts +767 -0
- package/src/telegram-endpoint-commands.ts +17 -0
- package/src/telegram-runtime-state.ts +7 -0
- package/src/webhook.ts +75 -0
- package/client/Dashboard.tsx +0 -295
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -17
- package/dist/index.js +0 -32
- package/lib/adapter.d.ts +0 -18
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -55
- package/lib/adapter.js.map +0 -1
- package/lib/bot.d.ts +0 -140
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -866
- package/lib/bot.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/types.d.ts +0 -29
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -64
- package/src/bot.ts +0 -983
- package/src/types.ts +0 -32
- /package/{skills/telegram/SKILL.md → agent/skills/telegram.md} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,426 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
},
|
|
41
|
-
} as Context<"telegram">);
|
|
42
|
-
|
|
43
|
-
useContext('tool', 'telegram', (toolService: ToolFeature, telegram: TelegramAdapter) => {
|
|
44
|
-
const groupTools = createGroupManagementTools(
|
|
45
|
-
telegram as unknown as IGroupManagement,
|
|
46
|
-
'telegram',
|
|
47
|
-
);
|
|
48
|
-
const disposers: (() => void)[] = groupTools.map(t => toolService.addTool(t, plugin.name));
|
|
49
|
-
|
|
50
|
-
disposers.push(toolService.addTool({
|
|
51
|
-
name: 'telegram_pin_message',
|
|
52
|
-
description: '置顶 Telegram 群组消息',
|
|
53
|
-
parameters: {
|
|
54
|
-
type: 'object',
|
|
55
|
-
properties: {
|
|
56
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
57
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
58
|
-
message_id: { type: 'string', description: '消息 ID' },
|
|
59
|
-
},
|
|
60
|
-
required: ['bot', 'chat_id', 'message_id'],
|
|
61
|
-
},
|
|
62
|
-
platforms: ['telegram'],
|
|
63
|
-
tags: ['telegram'],
|
|
64
|
-
execute: async (args: Record<string, any>) => {
|
|
65
|
-
const bot = telegram.bots.get(args.bot);
|
|
66
|
-
if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
|
|
67
|
-
const success = await bot.pinMessage(Number(args.chat_id), Number(args.message_id));
|
|
68
|
-
return { success, message: success ? '消息已置顶' : '操作失败' };
|
|
69
|
-
},
|
|
70
|
-
}, plugin.name));
|
|
71
|
-
|
|
72
|
-
disposers.push(toolService.addTool({
|
|
73
|
-
name: 'telegram_unpin_message',
|
|
74
|
-
description: '取消置顶 Telegram 群组消息',
|
|
75
|
-
parameters: {
|
|
76
|
-
type: 'object',
|
|
77
|
-
properties: {
|
|
78
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
79
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
80
|
-
message_id: { type: 'string', description: '消息 ID(可选,不提供则取消所有置顶)' },
|
|
81
|
-
},
|
|
82
|
-
required: ['bot', 'chat_id'],
|
|
83
|
-
},
|
|
84
|
-
platforms: ['telegram'],
|
|
85
|
-
tags: ['telegram'],
|
|
86
|
-
execute: async (args: Record<string, any>) => {
|
|
87
|
-
const bot = telegram.bots.get(args.bot);
|
|
88
|
-
if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
|
|
89
|
-
const success = await bot.unpinMessage(Number(args.chat_id), args.message_id ? Number(args.message_id) : undefined);
|
|
90
|
-
return { success, message: success ? '已取消置顶' : '操作失败' };
|
|
91
|
-
},
|
|
92
|
-
}, plugin.name));
|
|
93
|
-
|
|
94
|
-
disposers.push(toolService.addTool({
|
|
95
|
-
name: 'telegram_list_admins',
|
|
96
|
-
description: '获取 Telegram 群组管理员列表',
|
|
97
|
-
parameters: {
|
|
98
|
-
type: 'object',
|
|
99
|
-
properties: {
|
|
100
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
101
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
102
|
-
},
|
|
103
|
-
required: ['bot', 'chat_id'],
|
|
104
|
-
},
|
|
105
|
-
platforms: ['telegram'],
|
|
106
|
-
tags: ['telegram'],
|
|
107
|
-
execute: async (args: Record<string, any>) => {
|
|
108
|
-
const bot = telegram.bots.get(args.bot);
|
|
109
|
-
if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
|
|
110
|
-
const admins = await bot.getChatAdmins(Number(args.chat_id));
|
|
111
|
-
return {
|
|
112
|
-
admins: admins.map((a: any) => ({
|
|
113
|
-
user_id: a.user.id,
|
|
114
|
-
username: a.user.username,
|
|
115
|
-
first_name: a.user.first_name,
|
|
116
|
-
status: a.status,
|
|
117
|
-
})),
|
|
118
|
-
count: admins.length,
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
}, plugin.name));
|
|
122
|
-
|
|
123
|
-
disposers.push(toolService.addTool({
|
|
124
|
-
name: 'telegram_member_count',
|
|
125
|
-
description: '获取 Telegram 群组成员数量',
|
|
126
|
-
parameters: {
|
|
127
|
-
type: 'object',
|
|
128
|
-
properties: {
|
|
129
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
130
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
131
|
-
},
|
|
132
|
-
required: ['bot', 'chat_id'],
|
|
133
|
-
},
|
|
134
|
-
platforms: ['telegram'],
|
|
135
|
-
tags: ['telegram'],
|
|
136
|
-
execute: async (args: Record<string, any>) => {
|
|
137
|
-
const bot = telegram.bots.get(args.bot);
|
|
138
|
-
if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
|
|
139
|
-
const count = await bot.getChatMemberCount(Number(args.chat_id));
|
|
140
|
-
return { count, message: `群组共有 ${count} 名成员` };
|
|
141
|
-
},
|
|
142
|
-
}, plugin.name));
|
|
143
|
-
|
|
144
|
-
disposers.push(toolService.addTool({
|
|
145
|
-
name: 'telegram_create_invite',
|
|
146
|
-
description: '创建 Telegram 群组邀请链接',
|
|
147
|
-
parameters: {
|
|
148
|
-
type: 'object',
|
|
149
|
-
properties: {
|
|
150
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
151
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
152
|
-
},
|
|
153
|
-
required: ['bot', 'chat_id'],
|
|
154
|
-
},
|
|
155
|
-
platforms: ['telegram'],
|
|
156
|
-
tags: ['telegram'],
|
|
157
|
-
execute: async (args: Record<string, any>) => {
|
|
158
|
-
const bot = telegram.bots.get(args.bot);
|
|
159
|
-
if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
|
|
160
|
-
const link = await bot.createInviteLink(Number(args.chat_id));
|
|
161
|
-
return { invite_link: link, message: `邀请链接: ${link}` };
|
|
162
|
-
},
|
|
163
|
-
}, plugin.name));
|
|
164
|
-
|
|
165
|
-
disposers.push(toolService.addTool({
|
|
166
|
-
name: 'telegram_send_poll',
|
|
167
|
-
description: '在 Telegram 群组中发起投票',
|
|
168
|
-
parameters: {
|
|
169
|
-
type: 'object',
|
|
170
|
-
properties: {
|
|
171
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
172
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
173
|
-
question: { type: 'string', description: '投票问题' },
|
|
174
|
-
options: { type: 'string', description: '选项 JSON 数组,如 ["A","B","C"]' },
|
|
175
|
-
is_anonymous: { type: 'boolean', description: '是否匿名投票,默认 true' },
|
|
176
|
-
allows_multiple: { type: 'boolean', description: '是否允许多选,默认 false' },
|
|
177
|
-
},
|
|
178
|
-
required: ['bot', 'chat_id', 'question', 'options'],
|
|
179
|
-
},
|
|
180
|
-
platforms: ['telegram'],
|
|
181
|
-
tags: ['telegram'],
|
|
182
|
-
execute: async (args: Record<string, any>) => {
|
|
183
|
-
const bot = telegram.bots.get(args.bot);
|
|
184
|
-
if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
|
|
185
|
-
let optList: string[];
|
|
186
|
-
try {
|
|
187
|
-
optList = JSON.parse(args.options);
|
|
188
|
-
} catch {
|
|
189
|
-
return { success: false, message: 'options 格式错误,应为 JSON 数组' };
|
|
190
|
-
}
|
|
191
|
-
if (!Array.isArray(optList) || optList.length < 2) {
|
|
192
|
-
return { success: false, message: '至少需要 2 个选项' };
|
|
193
|
-
}
|
|
194
|
-
const result = await bot.sendPoll(
|
|
195
|
-
Number(args.chat_id), args.question, optList,
|
|
196
|
-
args.is_anonymous ?? true, args.allows_multiple ?? false,
|
|
197
|
-
);
|
|
198
|
-
return { success: true, message_id: result.message_id, message: '投票已发送' };
|
|
199
|
-
},
|
|
200
|
-
}, plugin.name));
|
|
201
|
-
|
|
202
|
-
disposers.push(toolService.addTool({
|
|
203
|
-
name: 'telegram_react',
|
|
204
|
-
description: '对 Telegram 消息添加表情反应',
|
|
205
|
-
parameters: {
|
|
206
|
-
type: 'object',
|
|
207
|
-
properties: {
|
|
208
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
209
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
210
|
-
message_id: { type: 'string', description: '消息 ID' },
|
|
211
|
-
reaction: { type: 'string', description: '反应表情(如 👍、❤️、🔥)' },
|
|
212
|
-
},
|
|
213
|
-
required: ['bot', 'chat_id', 'message_id', 'reaction'],
|
|
214
|
-
},
|
|
215
|
-
platforms: ['telegram'],
|
|
216
|
-
tags: ['telegram'],
|
|
217
|
-
execute: async (args: Record<string, any>) => {
|
|
218
|
-
const bot = telegram.bots.get(args.bot);
|
|
219
|
-
if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
|
|
220
|
-
const success = await bot.setMessageReaction(Number(args.chat_id), Number(args.message_id), args.reaction);
|
|
221
|
-
return { success, message: success ? `已添加反应 ${args.reaction}` : '操作失败' };
|
|
222
|
-
},
|
|
223
|
-
}, plugin.name));
|
|
224
|
-
|
|
225
|
-
disposers.push(toolService.addTool({
|
|
226
|
-
name: 'telegram_send_sticker',
|
|
227
|
-
description: '发送 Telegram 贴纸',
|
|
228
|
-
parameters: {
|
|
229
|
-
type: 'object',
|
|
230
|
-
properties: {
|
|
231
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
232
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
233
|
-
sticker: { type: 'string', description: '贴纸 file_id 或 URL' },
|
|
234
|
-
},
|
|
235
|
-
required: ['bot', 'chat_id', 'sticker'],
|
|
236
|
-
},
|
|
237
|
-
platforms: ['telegram'],
|
|
238
|
-
tags: ['telegram'],
|
|
239
|
-
execute: async (args: Record<string, any>) => {
|
|
240
|
-
const bot = telegram.bots.get(args.bot);
|
|
241
|
-
if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
|
|
242
|
-
const result = await bot.sendStickerMessage(Number(args.chat_id), args.sticker);
|
|
243
|
-
return { success: true, message_id: result.message_id, message: '贴纸已发送' };
|
|
244
|
-
},
|
|
245
|
-
}, plugin.name));
|
|
246
|
-
|
|
247
|
-
disposers.push(toolService.addTool({
|
|
248
|
-
name: 'telegram_set_permissions',
|
|
249
|
-
description: '设置 Telegram 群组的默认成员权限',
|
|
250
|
-
parameters: {
|
|
251
|
-
type: 'object',
|
|
252
|
-
properties: {
|
|
253
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
254
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
255
|
-
can_send_messages: { type: 'boolean', description: '是否可以发消息' },
|
|
256
|
-
can_send_photos: { type: 'boolean', description: '是否可以发图片' },
|
|
257
|
-
can_send_videos: { type: 'boolean', description: '是否可以发视频' },
|
|
258
|
-
can_send_polls: { type: 'boolean', description: '是否可以发投票' },
|
|
259
|
-
can_send_other_messages: { type: 'boolean', description: '是否可以发贴纸/GIF等' },
|
|
260
|
-
can_add_web_page_previews: { type: 'boolean', description: '是否可以添加网页预览' },
|
|
261
|
-
can_change_info: { type: 'boolean', description: '是否可以改群信息' },
|
|
262
|
-
can_invite_users: { type: 'boolean', description: '是否可以邀请用户' },
|
|
263
|
-
can_pin_messages: { type: 'boolean', description: '是否可以置顶消息' },
|
|
264
|
-
},
|
|
265
|
-
required: ['bot', 'chat_id'],
|
|
266
|
-
},
|
|
267
|
-
platforms: ['telegram'],
|
|
268
|
-
tags: ['telegram'],
|
|
269
|
-
execute: async (args: Record<string, any>) => {
|
|
270
|
-
const { bot: botId, chat_id, ...perms } = args;
|
|
271
|
-
const bot = telegram.bots.get(botId);
|
|
272
|
-
if (!bot) throw new Error(`Bot ${botId} 不存在`);
|
|
273
|
-
const permissions: any = {};
|
|
274
|
-
for (const [k, v] of Object.entries(perms)) {
|
|
275
|
-
if (typeof v === 'boolean') permissions[k] = v;
|
|
276
|
-
}
|
|
277
|
-
const success = await bot.setChatPermissionsAll(Number(chat_id), permissions);
|
|
278
|
-
return { success, message: success ? '群权限已更新' : '操作失败' };
|
|
279
|
-
},
|
|
280
|
-
}, plugin.name));
|
|
281
|
-
|
|
282
|
-
disposers.push(toolService.addTool({
|
|
283
|
-
name: 'telegram_set_description',
|
|
284
|
-
description: '设置 Telegram 群组描述',
|
|
285
|
-
parameters: {
|
|
286
|
-
type: 'object',
|
|
287
|
-
properties: {
|
|
288
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
289
|
-
chat_id: { type: 'string', description: '聊天 ID' },
|
|
290
|
-
description: { type: 'string', description: '群描述文字' },
|
|
291
|
-
},
|
|
292
|
-
required: ['bot', 'chat_id', 'description'],
|
|
293
|
-
},
|
|
294
|
-
platforms: ['telegram'],
|
|
295
|
-
tags: ['telegram'],
|
|
296
|
-
execute: async (args: Record<string, any>) => {
|
|
297
|
-
const bot = telegram.bots.get(args.bot);
|
|
298
|
-
if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
|
|
299
|
-
const success = await bot.setChatDescription(Number(args.chat_id), args.description);
|
|
300
|
-
return { success, message: success ? '群描述已更新' : '操作失败' };
|
|
301
|
-
},
|
|
302
|
-
}, plugin.name));
|
|
303
|
-
|
|
304
|
-
return () => disposers.forEach(d => d());
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// ── Web 控制台 ─────────────────────────────────────────────────────────
|
|
308
|
-
useContext("web", (pageManager) => {
|
|
309
|
-
pageManager.addEntry({
|
|
310
|
-
id: "telegram",
|
|
311
|
-
development: path.resolve(import.meta.dirname, "../client/index.tsx"),
|
|
312
|
-
production: path.resolve(import.meta.dirname, "../dist/index.js"),
|
|
313
|
-
meta: { name: "Telegram" },
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
useContext("router", "telegram", (router: any, telegram: TelegramAdapter) => {
|
|
318
|
-
router.get("/api/telegram/bots", async (ctx: any) => {
|
|
319
|
-
try {
|
|
320
|
-
const bots = Array.from(telegram.bots.values());
|
|
321
|
-
const result = bots.map((bot: any) => {
|
|
322
|
-
try {
|
|
323
|
-
return {
|
|
324
|
-
name: bot.$config.name,
|
|
325
|
-
connected: bot.$connected || false,
|
|
326
|
-
mode: bot.$config.polling !== false ? "polling" : "webhook",
|
|
327
|
-
status: bot.$connected ? "online" : "offline",
|
|
328
|
-
botInfo: bot.botInfo ? { username: bot.botInfo.username, firstName: bot.botInfo.first_name } : null,
|
|
329
|
-
};
|
|
330
|
-
} catch {
|
|
331
|
-
return { name: bot.$config.name, connected: false, mode: "unknown", status: "error", botInfo: null };
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
ctx.body = { success: true, data: result };
|
|
335
|
-
} catch {
|
|
336
|
-
ctx.status = 500;
|
|
337
|
-
ctx.body = { success: false, error: "获取机器人数据失败" };
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// Bot 连接/断开
|
|
342
|
-
router.post("/api/telegram/bots/:name/connect", async (ctx: any) => {
|
|
343
|
-
try {
|
|
344
|
-
const bot = telegram.bots.get(ctx.params.name);
|
|
345
|
-
if (!bot) { ctx.status = 404; ctx.body = { success: false, error: "Bot 不存在" }; return; }
|
|
346
|
-
if (bot.$connected) { ctx.body = { success: true, message: "已经在线" }; return; }
|
|
347
|
-
await bot.$connect();
|
|
348
|
-
ctx.body = { success: true, message: "连接成功" };
|
|
349
|
-
} catch (e: any) {
|
|
350
|
-
ctx.status = 500;
|
|
351
|
-
ctx.body = { success: false, error: e?.message || "连接失败" };
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
router.post("/api/telegram/bots/:name/disconnect", async (ctx: any) => {
|
|
356
|
-
try {
|
|
357
|
-
const bot = telegram.bots.get(ctx.params.name);
|
|
358
|
-
if (!bot) { ctx.status = 404; ctx.body = { success: false, error: "Bot 不存在" }; return; }
|
|
359
|
-
if (!bot.$connected) { ctx.body = { success: true, message: "已经离线" }; return; }
|
|
360
|
-
await bot.$disconnect();
|
|
361
|
-
ctx.body = { success: true, message: "已断开" };
|
|
362
|
-
} catch (e: any) {
|
|
363
|
-
ctx.status = 500;
|
|
364
|
-
ctx.body = { success: false, error: e?.message || "断开失败" };
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
// 快捷操作:创建邀请链接
|
|
369
|
-
router.post("/api/telegram/bots/:name/invite", async (ctx: any) => {
|
|
370
|
-
try {
|
|
371
|
-
const bot: any = telegram.bots.get(ctx.params.name);
|
|
372
|
-
if (!bot) { ctx.status = 404; ctx.body = { success: false, error: "Bot 不存在" }; return; }
|
|
373
|
-
if (!bot.$connected) { ctx.status = 400; ctx.body = { success: false, error: "Bot 未连接" }; return; }
|
|
374
|
-
const { chat_id } = ctx.request.body || {};
|
|
375
|
-
if (!chat_id) { ctx.status = 400; ctx.body = { success: false, error: "缺少 chat_id" }; return; }
|
|
376
|
-
const link = await bot.createInviteLink(Number(chat_id));
|
|
377
|
-
ctx.body = { success: true, data: { invite_link: link } };
|
|
378
|
-
} catch (e: any) {
|
|
379
|
-
ctx.status = 500;
|
|
380
|
-
ctx.body = { success: false, error: e?.message || "创建邀请链接失败" };
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// 快捷操作:发起投票
|
|
385
|
-
router.post("/api/telegram/bots/:name/poll", async (ctx: any) => {
|
|
386
|
-
try {
|
|
387
|
-
const bot: any = telegram.bots.get(ctx.params.name);
|
|
388
|
-
if (!bot) { ctx.status = 404; ctx.body = { success: false, error: "Bot 不存在" }; return; }
|
|
389
|
-
if (!bot.$connected) { ctx.status = 400; ctx.body = { success: false, error: "Bot 未连接" }; return; }
|
|
390
|
-
const { chat_id, question, options, is_anonymous, allows_multiple } = ctx.request.body || {};
|
|
391
|
-
if (!chat_id || !question || !options?.length) {
|
|
392
|
-
ctx.status = 400; ctx.body = { success: false, error: "缺少 chat_id, question 或 options" }; return;
|
|
393
|
-
}
|
|
394
|
-
if (options.length < 2) { ctx.status = 400; ctx.body = { success: false, error: "至少需要 2 个选项" }; return; }
|
|
395
|
-
const result = await bot.sendPoll(Number(chat_id), question, options, is_anonymous ?? true, allows_multiple ?? false);
|
|
396
|
-
ctx.body = { success: true, data: { message_id: result.message_id } };
|
|
397
|
-
} catch (e: any) {
|
|
398
|
-
ctx.status = 500;
|
|
399
|
-
ctx.body = { success: false, error: e?.message || "发起投票失败" };
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// 快捷操作:获取群管理员
|
|
404
|
-
router.get("/api/telegram/bots/:name/admins", async (ctx: any) => {
|
|
405
|
-
try {
|
|
406
|
-
const bot: any = telegram.bots.get(ctx.params.name);
|
|
407
|
-
if (!bot) { ctx.status = 404; ctx.body = { success: false, error: "Bot 不存在" }; return; }
|
|
408
|
-
if (!bot.$connected) { ctx.status = 400; ctx.body = { success: false, error: "Bot 未连接" }; return; }
|
|
409
|
-
const chat_id = ctx.query.chat_id;
|
|
410
|
-
if (!chat_id) { ctx.status = 400; ctx.body = { success: false, error: "缺少 chat_id" }; return; }
|
|
411
|
-
const admins = await bot.getChatAdmins(Number(chat_id));
|
|
412
|
-
ctx.body = {
|
|
413
|
-
success: true,
|
|
414
|
-
data: admins.map((a: any) => ({
|
|
415
|
-
user_id: a.user.id,
|
|
416
|
-
username: a.user.username,
|
|
417
|
-
first_name: a.user.first_name,
|
|
418
|
-
status: a.status,
|
|
419
|
-
})),
|
|
420
|
-
};
|
|
421
|
-
} catch (e: any) {
|
|
422
|
-
ctx.status = 500;
|
|
423
|
-
ctx.body = { success: false, error: e?.message || "获取管理员失败" };
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
});
|
|
1
|
+
export {
|
|
2
|
+
TelegramClient,
|
|
3
|
+
TelegramEndpoint,
|
|
4
|
+
type TelegramClientApi,
|
|
5
|
+
type TelegramEndpointOptions,
|
|
6
|
+
type TelegramFetch,
|
|
7
|
+
} from './endpoint.js';
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
botApiUrl,
|
|
11
|
+
formatCallbackContent,
|
|
12
|
+
formatInboundContent,
|
|
13
|
+
formatOutboundActions,
|
|
14
|
+
formatOutboundPlan,
|
|
15
|
+
normalizeWebhookPath,
|
|
16
|
+
resolveTelegramConfig,
|
|
17
|
+
senderDisplayName,
|
|
18
|
+
type ResolvedTelegramConfig,
|
|
19
|
+
type TelegramAdapterConfig,
|
|
20
|
+
type TelegramCallbackQuery,
|
|
21
|
+
type TelegramChat,
|
|
22
|
+
type TelegramChatMember,
|
|
23
|
+
type TelegramMessage,
|
|
24
|
+
type TelegramOutboundAction,
|
|
25
|
+
type TelegramOutboundPlan,
|
|
26
|
+
type TelegramOutboundUpload,
|
|
27
|
+
type TelegramUpdate,
|
|
28
|
+
type TelegramUser,
|
|
29
|
+
type TelegramWireSegment,
|
|
30
|
+
} from './protocol.js';
|
|
31
|
+
|
|
32
|
+
export { telegramClient, type TelegramClientEventMap } from './client.js';
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
checkTelegramPlatformPermit,
|
|
36
|
+
normalizeTelegramChatMember,
|
|
37
|
+
platformPermit,
|
|
38
|
+
telegramGroupPermitResolver,
|
|
39
|
+
} from './platform-permit.js';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Markdown → Telegram Bot API HTML.
|
|
3
|
+
*
|
|
4
|
+
* Telegram's MarkdownV2 is not CommonMark-compatible and rejects unescaped
|
|
5
|
+
* punctuation. HTML gives the adapter a safer dialect seam: user text is
|
|
6
|
+
* escaped first and only the subset supported by Bot API is emitted as tags.
|
|
7
|
+
*/
|
|
8
|
+
export function markdownToTelegramHtml(markdown: string): string {
|
|
9
|
+
const protectedFragments: string[] = [];
|
|
10
|
+
const protect = (html: string): string => {
|
|
11
|
+
const token = `\uE000${protectedFragments.length}\uE001`;
|
|
12
|
+
protectedFragments.push(html);
|
|
13
|
+
return token;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let value = protectTelegramFencedCode(markdown, protect);
|
|
17
|
+
value = value.replace(/`([^`\n]+)`/g, (_match, code) => (
|
|
18
|
+
protect(`<code>${escapeTelegramHtml(String(code))}</code>`)
|
|
19
|
+
));
|
|
20
|
+
value = escapeTelegramHtml(value);
|
|
21
|
+
|
|
22
|
+
value = value
|
|
23
|
+
.replace(/!\[([^\[\]]*)\]\(([^()\s]+)(?:\s+"[^&]*")?\)/g, '$1')
|
|
24
|
+
.replace(/\[([^\[\]]+)\]\(([^()\s]+)(?:\s+"[^&]*")?\)/g,
|
|
25
|
+
(_match, label: string, href: string) => isSafeTelegramHref(href)
|
|
26
|
+
? `<a href="${href}">${label}</a>`
|
|
27
|
+
: label)
|
|
28
|
+
.replace(/^#{1,6}\s+(.+)$/gm, '<b>$1</b>')
|
|
29
|
+
.replace(/^>\s?(.*)$/gm, '<blockquote>$1</blockquote>')
|
|
30
|
+
.replace(/^\s*[-+*]\s+(.+)$/gm, '• $1')
|
|
31
|
+
.replace(/\*\*([^*\n]+)\*\*/g, '<b>$1</b>')
|
|
32
|
+
.replace(/__([^_\n]+)__/g, '<b>$1</b>')
|
|
33
|
+
.replace(/~~([^~\n]+)~~/g, '<s>$1</s>')
|
|
34
|
+
.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1<i>$2</i>')
|
|
35
|
+
.replace(/(^|[^_])_([^_\n]+)_/g, '$1<i>$2</i>');
|
|
36
|
+
|
|
37
|
+
for (let index = 0; index < protectedFragments.length; index += 1) {
|
|
38
|
+
value = value.replace(`\uE000${index}\uE001`, protectedFragments[index]!);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function protectTelegramFencedCode(
|
|
44
|
+
markdown: string,
|
|
45
|
+
protect: (html: string) => string,
|
|
46
|
+
): string {
|
|
47
|
+
const chunks: string[] = [];
|
|
48
|
+
let cursor = 0;
|
|
49
|
+
while (cursor < markdown.length) {
|
|
50
|
+
const opening = markdown.indexOf('```', cursor);
|
|
51
|
+
if (opening < 0) {
|
|
52
|
+
chunks.push(markdown.slice(cursor));
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
chunks.push(markdown.slice(cursor, opening));
|
|
56
|
+
const contentStart = opening + 3;
|
|
57
|
+
const closing = markdown.indexOf('```', contentStart);
|
|
58
|
+
if (closing < 0) {
|
|
59
|
+
chunks.push(markdown.slice(opening));
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
const newline = markdown.indexOf('\n', contentStart);
|
|
63
|
+
const hasHeader = newline >= 0 && newline < closing;
|
|
64
|
+
const language = markdown.slice(contentStart, hasHeader ? newline : closing).trim();
|
|
65
|
+
let code = hasHeader ? markdown.slice(newline + 1, closing) : '';
|
|
66
|
+
if (code.endsWith('\n')) code = code.slice(0, -1);
|
|
67
|
+
const escapedCode = escapeTelegramHtml(code);
|
|
68
|
+
chunks.push(/^[A-Za-z0-9_+-]+$/.test(language)
|
|
69
|
+
? protect(`<pre><code class="language-${language}">${escapedCode}</code></pre>`)
|
|
70
|
+
: protect(`<pre>${escapedCode}</pre>`));
|
|
71
|
+
cursor = closing + 3;
|
|
72
|
+
}
|
|
73
|
+
return chunks.join('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isSafeTelegramHref(value: string): boolean {
|
|
77
|
+
return /^(?:https?:\/\/|tg:\/\/|mailto:)[^\s<>]+$/iu.test(value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function escapeTelegramHtml(value: string): string {
|
|
81
|
+
return value
|
|
82
|
+
.replace(/&/g, '&')
|
|
83
|
+
.replace(/</g, '<')
|
|
84
|
+
.replace(/>/g, '>')
|
|
85
|
+
.replace(/"/g, '"');
|
|
86
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram platform permit — ChatMember 状态 + 细粒度权限
|
|
3
|
+
*/
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
|
+
|
|
6
|
+
const ADAPTER = 'telegram';
|
|
7
|
+
|
|
8
|
+
export function platformPermit(perm: string): string {
|
|
9
|
+
return `platform(${ADAPTER},${perm})`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const FACTORY_PERM_MAP: Record<string, string> = {
|
|
13
|
+
scene_admin: 'chat_administrator',
|
|
14
|
+
scene_owner: 'chat_creator',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function telegramGroupPermitResolver(logicalPerm: string): string {
|
|
18
|
+
return platformPermit(FACTORY_PERM_MAP[logicalPerm] ?? logicalPerm);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function normalizeTelegramChatMember(member: {
|
|
22
|
+
status?: string;
|
|
23
|
+
can_restrict_members?: boolean;
|
|
24
|
+
can_pin_messages?: boolean;
|
|
25
|
+
can_delete_messages?: boolean;
|
|
26
|
+
can_manage_chat?: boolean;
|
|
27
|
+
}): { role?: string; permissions: string[] } {
|
|
28
|
+
const permissions: string[] = [];
|
|
29
|
+
const status = member.status;
|
|
30
|
+
if (status === 'creator') {
|
|
31
|
+
permissions.push('chat_creator', 'chat_administrator', 'restrict_members', 'pin_messages');
|
|
32
|
+
return { role: 'creator', permissions };
|
|
33
|
+
}
|
|
34
|
+
if (status === 'administrator') {
|
|
35
|
+
permissions.push('chat_administrator');
|
|
36
|
+
if (member.can_restrict_members) permissions.push('restrict_members');
|
|
37
|
+
if (member.can_pin_messages) permissions.push('pin_messages');
|
|
38
|
+
if (member.can_delete_messages) permissions.push('delete_messages');
|
|
39
|
+
if (member.can_manage_chat) permissions.push('manage_chat');
|
|
40
|
+
return { role: 'administrator', permissions };
|
|
41
|
+
}
|
|
42
|
+
return { role: 'member', permissions };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function checkTelegramPlatformPermit(perm: string, subject: PermissionSubject): boolean {
|
|
46
|
+
const role = subject.sender?.role?.[0];
|
|
47
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
48
|
+
const has = (t: string) => permissions.includes(t);
|
|
49
|
+
|
|
50
|
+
switch (perm) {
|
|
51
|
+
case 'chat_creator':
|
|
52
|
+
return role === 'creator' || has('chat_creator');
|
|
53
|
+
case 'chat_administrator':
|
|
54
|
+
return role === 'creator' || role === 'administrator' || has('chat_administrator');
|
|
55
|
+
case 'restrict_members':
|
|
56
|
+
return has('restrict_members') || role === 'creator';
|
|
57
|
+
case 'pin_messages':
|
|
58
|
+
return has('pin_messages') || role === 'creator';
|
|
59
|
+
case 'manage_chat':
|
|
60
|
+
return has('manage_chat') || role === 'creator';
|
|
61
|
+
default:
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|