@zhin.js/adapter-telegram 5.0.2 → 6.0.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 +62 -0
- package/README.md +63 -145
- package/adapters/telegram.ts +27 -0
- package/agent/tools/create_invite.ts +2 -2
- package/agent/tools/list_admins.ts +2 -2
- package/agent/tools/member_count.ts +2 -2
- package/agent/tools/pin_message.ts +2 -2
- package/agent/tools/react.ts +2 -2
- package/agent/tools/send_poll.ts +2 -2
- package/agent/tools/send_sticker.ts +2 -2
- package/agent/tools/set_description.ts +2 -2
- package/agent/tools/set_permissions.ts +2 -2
- package/agent/tools/unpin_message.ts +2 -2
- package/lib/endpoint.d.ts +65 -0
- package/lib/endpoint.js +332 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/platform-permit.d.ts +18 -0
- package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
- package/lib/polling.d.ts +9 -0
- package/lib/polling.js +52 -0
- package/lib/protocol.d.ts +250 -0
- package/lib/protocol.js +324 -0
- package/lib/telegram-agent-deps.d.ts +28 -0
- package/lib/telegram-agent-deps.js +30 -0
- package/lib/webhook.d.ts +13 -0
- package/lib/webhook.js +45 -0
- package/package.json +43 -40
- package/plugin.ts +13 -0
- package/schema.json +74 -0
- package/src/endpoint.ts +339 -1103
- package/src/index.ts +40 -186
- package/src/platform-permit.ts +1 -1
- package/src/polling.ts +71 -0
- package/src/protocol.ts +573 -0
- package/src/telegram-agent-deps.ts +52 -11
- package/src/webhook.ts +67 -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 -30
- package/dist/index.js +0 -32
- package/lib/agent/tools/create_invite.js +0 -20
- package/lib/agent/tools/create_invite.js.map +0 -1
- package/lib/agent/tools/list_admins.js +0 -26
- package/lib/agent/tools/list_admins.js.map +0 -1
- package/lib/agent/tools/member_count.js +0 -18
- package/lib/agent/tools/member_count.js.map +0 -1
- package/lib/agent/tools/pin_message.js +0 -21
- package/lib/agent/tools/pin_message.js.map +0 -1
- package/lib/agent/tools/react.js +0 -20
- package/lib/agent/tools/react.js.map +0 -1
- package/lib/agent/tools/send_poll.js +0 -32
- package/lib/agent/tools/send_poll.js.map +0 -1
- package/lib/agent/tools/send_sticker.js +0 -19
- package/lib/agent/tools/send_sticker.js.map +0 -1
- package/lib/agent/tools/set_description.js +0 -19
- package/lib/agent/tools/set_description.js.map +0 -1
- package/lib/agent/tools/set_permissions.js +0 -34
- package/lib/agent/tools/set_permissions.js.map +0 -1
- package/lib/agent/tools/unpin_message.js +0 -21
- package/lib/agent/tools/unpin_message.js.map +0 -1
- package/lib/src/adapter.js +0 -58
- package/lib/src/adapter.js.map +0 -1
- package/lib/src/endpoint.js +0 -1046
- package/lib/src/endpoint.js.map +0 -1
- package/lib/src/index.js +0 -215
- package/lib/src/index.js.map +0 -1
- package/lib/src/platform-permit.js.map +0 -1
- package/lib/src/segment-mapper.js +0 -2
- package/lib/src/segment-mapper.js.map +0 -1
- package/lib/src/telegram-agent-deps.js +0 -10
- package/lib/src/telegram-agent-deps.js.map +0 -1
- package/lib/src/types.js +0 -2
- package/lib/src/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -66
- package/src/segment-mapper.ts +0 -1
- package/src/types.ts +0 -32
package/src/index.ts
CHANGED
|
@@ -1,188 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
export {
|
|
2
|
+
TelegramEndpoint,
|
|
3
|
+
type TelegramEndpointOptions,
|
|
4
|
+
type TelegramFetch,
|
|
5
|
+
} from './endpoint.js';
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
botApiUrl,
|
|
9
|
+
formatCallbackContent,
|
|
10
|
+
formatInboundContent,
|
|
11
|
+
formatOutboundActions,
|
|
12
|
+
normalizeWebhookPath,
|
|
13
|
+
resolveChannel,
|
|
14
|
+
resolveTelegramConfig,
|
|
15
|
+
senderDisplayName,
|
|
16
|
+
type ResolvedTelegramConfig,
|
|
17
|
+
type TelegramAdapterConfig,
|
|
18
|
+
type TelegramCallbackQuery,
|
|
19
|
+
type TelegramChat,
|
|
20
|
+
type TelegramChatMember,
|
|
21
|
+
type TelegramMessage,
|
|
22
|
+
type TelegramOutboundAction,
|
|
23
|
+
type TelegramUpdate,
|
|
24
|
+
type TelegramUser,
|
|
25
|
+
type TelegramWireSegment,
|
|
26
|
+
} from './protocol.js';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
getTelegramAgentDeps,
|
|
30
|
+
registerTelegramAgentEndpoint,
|
|
31
|
+
setTelegramAgentDeps,
|
|
32
|
+
type TelegramAgentDeps,
|
|
33
|
+
type TelegramAgentEndpoint,
|
|
34
|
+
} from './telegram-agent-deps.js';
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
checkTelegramPlatformPermit,
|
|
38
|
+
normalizeTelegramChatMember,
|
|
39
|
+
platformPermit,
|
|
9
40
|
registerTelegramPlatformPermitChecker,
|
|
10
41
|
telegramGroupPermitResolver,
|
|
11
|
-
} from
|
|
12
|
-
import { setTelegramAgentDeps } from "./telegram-agent-deps.js";
|
|
13
|
-
|
|
14
|
-
declare module "zhin.js" {
|
|
15
|
-
namespace Plugin {
|
|
16
|
-
interface Contexts {
|
|
17
|
-
web: PageManager;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
declare module "zhin.js" {
|
|
23
|
-
interface Adapters {
|
|
24
|
-
telegram: TelegramAdapter;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export * from "./types.js";
|
|
29
|
-
export { TelegramEndpoint } from "./endpoint.js";
|
|
30
|
-
export { TelegramAdapter } from "./adapter.js";
|
|
31
|
-
|
|
32
|
-
const plugin = usePlugin();
|
|
33
|
-
const { provide, useContext } = plugin;
|
|
34
|
-
|
|
35
|
-
provide({
|
|
36
|
-
name: "telegram",
|
|
37
|
-
description: "Telegram Endpoint Adapter",
|
|
38
|
-
mounted: async (p: Plugin) => {
|
|
39
|
-
const adapter = new TelegramAdapter(p);
|
|
40
|
-
await adapter.start();
|
|
41
|
-
return adapter;
|
|
42
|
-
},
|
|
43
|
-
dispose: async (adapter: TelegramAdapter) => {
|
|
44
|
-
await adapter.stop();
|
|
45
|
-
},
|
|
46
|
-
} as Context<"telegram">);
|
|
47
|
-
|
|
48
|
-
useContext('tool', 'telegram', (toolService: ToolFeature, telegram: TelegramAdapter) => {
|
|
49
|
-
const disposers: (() => void)[] = [];
|
|
50
|
-
disposers.push(registerTelegramPlatformPermitChecker());
|
|
51
|
-
setTelegramAgentDeps({
|
|
52
|
-
getEndpoint: (endpointId) => {
|
|
53
|
-
const endpoint = telegram.endpoints.get(endpointId);
|
|
54
|
-
if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
55
|
-
return endpoint;
|
|
56
|
-
},
|
|
57
|
-
getAdapter: () => telegram,
|
|
58
|
-
});
|
|
59
|
-
const sceneTools = createSceneManagementTools(
|
|
60
|
-
telegram as unknown as ISceneManagement,
|
|
61
|
-
'telegram',
|
|
62
|
-
{ permitResolver: telegramGroupPermitResolver, registerChecker: false },
|
|
63
|
-
);
|
|
64
|
-
disposers.push(...sceneTools.map(t => toolService.addTool(t, plugin.name)));
|
|
65
|
-
|
|
66
|
-
return () => disposers.forEach(d => d());
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// ── Web 控制台 ─────────────────────────────────────────────────────────
|
|
70
|
-
useContext("web", (pageManager) => {
|
|
71
|
-
pageManager.addEntry({
|
|
72
|
-
id: "telegram",
|
|
73
|
-
development: path.resolve(import.meta.dirname, "../client/index.tsx"),
|
|
74
|
-
production: path.resolve(import.meta.dirname, "../dist/index.js"),
|
|
75
|
-
meta: { name: "Telegram" },
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
useContext("router", "telegram", (router: any, telegram: TelegramAdapter) => {
|
|
80
|
-
router.get("/api/telegram/endpoints", async (ctx: any) => {
|
|
81
|
-
try {
|
|
82
|
-
const endpoints = Array.from(telegram.endpoints.values());
|
|
83
|
-
const result = endpoints.map((endpoint: any) => {
|
|
84
|
-
try {
|
|
85
|
-
return {
|
|
86
|
-
name: endpoint.$config.name,
|
|
87
|
-
connected: endpoint.$connected || false,
|
|
88
|
-
mode: endpoint.$config.polling !== false ? "polling" : "webhook",
|
|
89
|
-
status: endpoint.$connected ? "online" : "offline",
|
|
90
|
-
botInfo: endpoint.botInfo ? { username: endpoint.botInfo.username, firstName: endpoint.botInfo.first_name } : null,
|
|
91
|
-
};
|
|
92
|
-
} catch {
|
|
93
|
-
return { name: endpoint.$config.name, connected: false, mode: "unknown", status: "error", botInfo: null };
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
ctx.body = { success: true, data: result };
|
|
97
|
-
} catch {
|
|
98
|
-
ctx.status = 500;
|
|
99
|
-
ctx.body = { success: false, error: "获取 Endpoint 数据失败" };
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Endpoint 连接/断开
|
|
104
|
-
router.post("/api/telegram/endpoints/:name/connect", async (ctx: any) => {
|
|
105
|
-
try {
|
|
106
|
-
const endpoint = telegram.endpoints.get(ctx.params.name);
|
|
107
|
-
if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
|
|
108
|
-
if (endpoint.$connected) { ctx.body = { success: true, message: "已经在线" }; return; }
|
|
109
|
-
await endpoint.$connect();
|
|
110
|
-
ctx.body = { success: true, message: "连接成功" };
|
|
111
|
-
} catch (e: unknown) {
|
|
112
|
-
ctx.status = 500;
|
|
113
|
-
ctx.body = { success: false, error: e instanceof Error ? e.message : "连接失败" };
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
router.post("/api/telegram/endpoints/:name/disconnect", async (ctx: any) => {
|
|
118
|
-
try {
|
|
119
|
-
const endpoint = telegram.endpoints.get(ctx.params.name);
|
|
120
|
-
if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
|
|
121
|
-
if (!endpoint.$connected) { ctx.body = { success: true, message: "已经离线" }; return; }
|
|
122
|
-
await endpoint.$disconnect();
|
|
123
|
-
ctx.body = { success: true, message: "已断开" };
|
|
124
|
-
} catch (e: unknown) {
|
|
125
|
-
ctx.status = 500;
|
|
126
|
-
ctx.body = { success: false, error: e instanceof Error ? e.message : "断开失败" };
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// 快捷操作:创建邀请链接
|
|
131
|
-
router.post("/api/telegram/endpoints/:name/invite", async (ctx: any) => {
|
|
132
|
-
try {
|
|
133
|
-
const endpoint: any = telegram.endpoints.get(ctx.params.name);
|
|
134
|
-
if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
|
|
135
|
-
if (!endpoint.$connected) { ctx.status = 400; ctx.body = { success: false, error: "Endpoint 未连接" }; return; }
|
|
136
|
-
const { chat_id } = ctx.request.body || {};
|
|
137
|
-
if (!chat_id) { ctx.status = 400; ctx.body = { success: false, error: "缺少 chat_id" }; return; }
|
|
138
|
-
const link = await endpoint.createInviteLink(Number(chat_id));
|
|
139
|
-
ctx.body = { success: true, data: { invite_link: link } };
|
|
140
|
-
} catch (e: unknown) {
|
|
141
|
-
ctx.status = 500;
|
|
142
|
-
ctx.body = { success: false, error: e instanceof Error ? e.message : "创建邀请链接失败" };
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// 快捷操作:发起投票
|
|
147
|
-
router.post("/api/telegram/endpoints/:name/poll", async (ctx: any) => {
|
|
148
|
-
try {
|
|
149
|
-
const endpoint: any = telegram.endpoints.get(ctx.params.name);
|
|
150
|
-
if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
|
|
151
|
-
if (!endpoint.$connected) { ctx.status = 400; ctx.body = { success: false, error: "Endpoint 未连接" }; return; }
|
|
152
|
-
const { chat_id, question, options, is_anonymous, allows_multiple } = ctx.request.body || {};
|
|
153
|
-
if (!chat_id || !question || !options?.length) {
|
|
154
|
-
ctx.status = 400; ctx.body = { success: false, error: "缺少 chat_id, question 或 options" }; return;
|
|
155
|
-
}
|
|
156
|
-
if (options.length < 2) { ctx.status = 400; ctx.body = { success: false, error: "至少需要 2 个选项" }; return; }
|
|
157
|
-
const result = await endpoint.sendPoll(Number(chat_id), question, options, is_anonymous ?? true, allows_multiple ?? false);
|
|
158
|
-
ctx.body = { success: true, data: { message_id: result.message_id } };
|
|
159
|
-
} catch (e: unknown) {
|
|
160
|
-
ctx.status = 500;
|
|
161
|
-
ctx.body = { success: false, error: e instanceof Error ? e.message : "发起投票失败" };
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// 快捷操作:获取群管理员
|
|
166
|
-
router.get("/api/telegram/endpoints/:name/admins", async (ctx: any) => {
|
|
167
|
-
try {
|
|
168
|
-
const endpoint: any = telegram.endpoints.get(ctx.params.name);
|
|
169
|
-
if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
|
|
170
|
-
if (!endpoint.$connected) { ctx.status = 400; ctx.body = { success: false, error: "Endpoint 未连接" }; return; }
|
|
171
|
-
const chat_id = ctx.query.chat_id;
|
|
172
|
-
if (!chat_id) { ctx.status = 400; ctx.body = { success: false, error: "缺少 chat_id" }; return; }
|
|
173
|
-
const admins = await endpoint.getChatAdmins(Number(chat_id));
|
|
174
|
-
ctx.body = {
|
|
175
|
-
success: true,
|
|
176
|
-
data: admins.map((a: any) => ({
|
|
177
|
-
user_id: a.user.id,
|
|
178
|
-
username: a.user.username,
|
|
179
|
-
first_name: a.user.first_name,
|
|
180
|
-
status: a.status,
|
|
181
|
-
})),
|
|
182
|
-
};
|
|
183
|
-
} catch (e: unknown) {
|
|
184
|
-
ctx.status = 500;
|
|
185
|
-
ctx.body = { success: false, error: e instanceof Error ? e.message : "获取管理员失败" };
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
});
|
|
42
|
+
} from './platform-permit.js';
|
package/src/platform-permit.ts
CHANGED
package/src/polling.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram long-polling loop for getUpdates.
|
|
3
|
+
*/
|
|
4
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
5
|
+
import type { TelegramUpdate } from './protocol.js';
|
|
6
|
+
|
|
7
|
+
const logger = getLogger('telegram');
|
|
8
|
+
const RETRY_DELAY_MS = 2_000;
|
|
9
|
+
const BACKOFF_DELAY_MS = 10_000;
|
|
10
|
+
const MAX_CONSECUTIVE_FAILURES = 5;
|
|
11
|
+
const DEFAULT_POLL_TIMEOUT_SEC = 30;
|
|
12
|
+
|
|
13
|
+
export interface TelegramPollingHost {
|
|
14
|
+
readonly allowedUpdates: readonly string[];
|
|
15
|
+
callApi<T>(
|
|
16
|
+
method: string,
|
|
17
|
+
params?: Record<string, unknown>,
|
|
18
|
+
signal?: AbortSignal,
|
|
19
|
+
): Promise<T>;
|
|
20
|
+
getUpdateOffset(): number;
|
|
21
|
+
setUpdateOffset(offset: number): void;
|
|
22
|
+
handleUpdate(update: TelegramUpdate): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function runTelegramPollLoop(
|
|
26
|
+
host: TelegramPollingHost,
|
|
27
|
+
abortSignal: AbortSignal,
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
let consecutiveFailures = 0;
|
|
30
|
+
while (!abortSignal.aborted) {
|
|
31
|
+
try {
|
|
32
|
+
const updates = await host.callApi<TelegramUpdate[]>('getUpdates', {
|
|
33
|
+
offset: host.getUpdateOffset() || undefined,
|
|
34
|
+
timeout: DEFAULT_POLL_TIMEOUT_SEC,
|
|
35
|
+
allowed_updates: host.allowedUpdates,
|
|
36
|
+
}, abortSignal);
|
|
37
|
+
consecutiveFailures = 0;
|
|
38
|
+
for (const update of updates) {
|
|
39
|
+
host.setUpdateOffset(update.update_id + 1);
|
|
40
|
+
host.handleUpdate(update);
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (abortSignal.aborted) return;
|
|
44
|
+
consecutiveFailures += 1;
|
|
45
|
+
logger.error(formatCompact({
|
|
46
|
+
op: 'poll',
|
|
47
|
+
ok: false,
|
|
48
|
+
error: err instanceof Error ? err.message : String(err),
|
|
49
|
+
}));
|
|
50
|
+
await sleep(
|
|
51
|
+
consecutiveFailures >= MAX_CONSECUTIVE_FAILURES ? BACKOFF_DELAY_MS : RETRY_DELAY_MS,
|
|
52
|
+
abortSignal,
|
|
53
|
+
);
|
|
54
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) consecutiveFailures = 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
if (signal?.aborted) {
|
|
62
|
+
resolve();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const timer = setTimeout(resolve, ms);
|
|
66
|
+
signal?.addEventListener('abort', () => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
resolve();
|
|
69
|
+
}, { once: true });
|
|
70
|
+
});
|
|
71
|
+
}
|