@overpod/mcp-telegram 1.28.1 → 1.33.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 +169 -0
- package/README.md +21 -8
- package/dist/telegram-client.d.ts +316 -4
- package/dist/telegram-client.js +1250 -5
- package/dist/telegram-helpers.d.ts +111 -1
- package/dist/telegram-helpers.js +270 -0
- package/dist/tools/account.js +221 -6
- package/dist/tools/business.d.ts +3 -0
- package/dist/tools/business.js +333 -0
- package/dist/tools/fact-check.d.ts +3 -0
- package/dist/tools/fact-check.js +72 -0
- package/dist/tools/folders.d.ts +3 -0
- package/dist/tools/folders.js +239 -0
- package/dist/tools/index.js +10 -0
- package/dist/tools/messages.js +183 -4
- package/dist/tools/reactions.js +62 -0
- package/dist/tools/send-media.d.ts +3 -0
- package/dist/tools/send-media.js +259 -0
- package/dist/tools/shared.d.ts +28 -0
- package/dist/tools/shared.js +59 -0
- package/dist/tools/stories.js +303 -1
- package/dist/tools/transcribe.d.ts +3 -0
- package/dist/tools/transcribe.js +75 -0
- package/package.json +1 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, ok, READ_ONLY, requireConnection, WRITE } from "./shared.js";
|
|
3
|
+
const AUDIENCE = z.enum(["all_new", "contacts_only", "non_contacts", "existing_only"]).default("all_new");
|
|
4
|
+
export function registerBusinessTools(server, telegram) {
|
|
5
|
+
server.registerTool("telegram-get-business-chat-links", {
|
|
6
|
+
description: "List Telegram Business chat links configured for the account. Each entry includes the t.me/... link, the prefilled message, optional title (admin-facing label), views count, and entityCount. Requires Telegram Business — returns empty list when none configured.",
|
|
7
|
+
inputSchema: {},
|
|
8
|
+
annotations: READ_ONLY,
|
|
9
|
+
}, async () => {
|
|
10
|
+
const err = await requireConnection(telegram);
|
|
11
|
+
if (err)
|
|
12
|
+
return fail(new Error(err));
|
|
13
|
+
try {
|
|
14
|
+
const result = await telegram.getBusinessChatLinks();
|
|
15
|
+
return ok(JSON.stringify(result));
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
return fail(e);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
server.registerTool("telegram-create-business-chat-link", {
|
|
22
|
+
description: "Create a Telegram Business chat link (t.me/m/... deep-link that opens a chat with you pre-filled with a message). Returns JSON with link, slug, message, title, and views. Requires Telegram Business subscription.",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
message: z.string().min(1).max(4096).describe("Pre-filled message text shown to users who click the link"),
|
|
25
|
+
title: z.string().max(32).optional().describe("Admin-facing label (not visible to visitors, max 32 chars)"),
|
|
26
|
+
parseMode: z.enum(["md", "html"]).optional().describe("Format message as Markdown or HTML"),
|
|
27
|
+
},
|
|
28
|
+
annotations: WRITE,
|
|
29
|
+
}, async ({ message, title, parseMode }) => {
|
|
30
|
+
const err = await requireConnection(telegram);
|
|
31
|
+
if (err)
|
|
32
|
+
return fail(new Error(err));
|
|
33
|
+
try {
|
|
34
|
+
const result = await telegram.createBusinessChatLink({ message, title, parseMode });
|
|
35
|
+
return ok(JSON.stringify(result));
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
return fail(e);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
server.registerTool("telegram-edit-business-chat-link", {
|
|
42
|
+
description: "Edit an existing Telegram Business chat link by its slug (the trailing segment after t.me/m/). Returns JSON with updated fields. Requires Telegram Business subscription.",
|
|
43
|
+
inputSchema: {
|
|
44
|
+
slug: z.string().min(1).describe("Link slug — the last path segment of t.me/m/<slug>"),
|
|
45
|
+
message: z.string().min(1).max(4096).describe("New pre-filled message text"),
|
|
46
|
+
title: z.string().max(32).optional().describe("New admin-facing label (max 32 chars)"),
|
|
47
|
+
parseMode: z.enum(["md", "html"]).optional().describe("Format message as Markdown or HTML"),
|
|
48
|
+
},
|
|
49
|
+
annotations: WRITE,
|
|
50
|
+
}, async ({ slug, message, title, parseMode }) => {
|
|
51
|
+
const err = await requireConnection(telegram);
|
|
52
|
+
if (err)
|
|
53
|
+
return fail(new Error(err));
|
|
54
|
+
try {
|
|
55
|
+
const result = await telegram.editBusinessChatLink({ slug, message, title, parseMode });
|
|
56
|
+
return ok(JSON.stringify(result));
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
return fail(e);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
server.registerTool("telegram-delete-business-chat-link", {
|
|
63
|
+
description: "Delete a Telegram Business chat link by its slug. Requires Telegram Business subscription.",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
slug: z.string().min(1).describe("Link slug to delete (from t.me/m/<slug>)"),
|
|
66
|
+
},
|
|
67
|
+
annotations: WRITE,
|
|
68
|
+
}, async ({ slug }) => {
|
|
69
|
+
const err = await requireConnection(telegram);
|
|
70
|
+
if (err)
|
|
71
|
+
return fail(new Error(err));
|
|
72
|
+
try {
|
|
73
|
+
await telegram.deleteBusinessChatLink(slug);
|
|
74
|
+
return ok(`Business chat link '${slug}' deleted`);
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
return fail(e);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
server.registerTool("telegram-resolve-business-chat-link", {
|
|
81
|
+
description: "Resolve a Telegram Business chat link by slug to see whose chat it opens and the pre-filled message. Returns JSON with peerId, peerType, message, and entityCount.",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
slug: z.string().min(1).describe("Link slug to resolve (from t.me/m/<slug>)"),
|
|
84
|
+
},
|
|
85
|
+
annotations: READ_ONLY,
|
|
86
|
+
}, async ({ slug }) => {
|
|
87
|
+
const err = await requireConnection(telegram);
|
|
88
|
+
if (err)
|
|
89
|
+
return fail(new Error(err));
|
|
90
|
+
try {
|
|
91
|
+
const result = await telegram.resolveBusinessChatLink(slug);
|
|
92
|
+
return ok(JSON.stringify(result));
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
return fail(e);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
server.registerTool("telegram-set-business-hours", {
|
|
99
|
+
description: "Set Telegram Business work hours — days and time ranges when your business is open. Requires Telegram Business subscription. Pass clear=true to disable the work hours display entirely.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
timezone: z
|
|
102
|
+
.string()
|
|
103
|
+
.optional()
|
|
104
|
+
.describe("IANA timezone ID (e.g. 'Europe/Moscow', 'America/New_York'). Required when setting schedule."),
|
|
105
|
+
openNow: z
|
|
106
|
+
.boolean()
|
|
107
|
+
.optional()
|
|
108
|
+
.describe("Manually override current open/closed status. Omit to derive from schedule."),
|
|
109
|
+
schedule: z
|
|
110
|
+
.array(z.object({
|
|
111
|
+
day: z.enum(["mon", "tue", "wed", "thu", "fri", "sat", "sun"]).describe("Day of week"),
|
|
112
|
+
openFrom: z
|
|
113
|
+
.string()
|
|
114
|
+
.regex(/^\d{2}:\d{2}$/)
|
|
115
|
+
.describe("Opening time in 24h HH:MM format"),
|
|
116
|
+
openTo: z
|
|
117
|
+
.string()
|
|
118
|
+
.regex(/^\d{2}:\d{2}$/)
|
|
119
|
+
.describe("Closing time in 24h HH:MM. Use '24:00' for end of day."),
|
|
120
|
+
}))
|
|
121
|
+
.optional()
|
|
122
|
+
.describe("Weekly schedule. Multiple ranges per day are allowed."),
|
|
123
|
+
clear: z.boolean().optional().describe("Pass true to remove business hours entirely"),
|
|
124
|
+
},
|
|
125
|
+
annotations: WRITE,
|
|
126
|
+
}, async ({ timezone, openNow, schedule, clear }) => {
|
|
127
|
+
const err = await requireConnection(telegram);
|
|
128
|
+
if (err)
|
|
129
|
+
return fail(new Error(err));
|
|
130
|
+
if (!clear && (!timezone || !schedule?.length)) {
|
|
131
|
+
return fail(new Error("timezone and schedule are required when not clearing"));
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
await telegram.setBusinessWorkHours({ timezone, openNow, schedule, clear });
|
|
135
|
+
if (clear)
|
|
136
|
+
return ok("Business hours cleared");
|
|
137
|
+
return ok(`Business hours set: ${schedule?.length} range(s) in ${timezone}`);
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
return fail(e);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
server.registerTool("telegram-set-business-location", {
|
|
144
|
+
description: "Set Telegram Business physical location (address + optional geo coordinates). Requires Telegram Business subscription. Pass clear=true to remove.",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
address: z.string().min(1).max(512).optional().describe("Street address text"),
|
|
147
|
+
latitude: z.number().min(-90).max(90).optional().describe("Geo latitude (-90 to 90)"),
|
|
148
|
+
longitude: z.number().min(-180).max(180).optional().describe("Geo longitude (-180 to 180)"),
|
|
149
|
+
clear: z.boolean().optional().describe("Pass true to remove business location"),
|
|
150
|
+
},
|
|
151
|
+
annotations: WRITE,
|
|
152
|
+
}, async ({ address, latitude, longitude, clear }) => {
|
|
153
|
+
const err = await requireConnection(telegram);
|
|
154
|
+
if (err)
|
|
155
|
+
return fail(new Error(err));
|
|
156
|
+
if (!clear && !address) {
|
|
157
|
+
return fail(new Error("address is required when not clearing"));
|
|
158
|
+
}
|
|
159
|
+
if ((latitude === undefined) !== (longitude === undefined)) {
|
|
160
|
+
return fail(new Error("latitude and longitude must both be set or both omitted"));
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
await telegram.setBusinessLocation({ address, latitude, longitude, clear });
|
|
164
|
+
if (clear)
|
|
165
|
+
return ok("Business location cleared");
|
|
166
|
+
const geo = latitude !== undefined ? ` @ ${latitude},${longitude}` : "";
|
|
167
|
+
return ok(`Business location set: ${address}${geo}`);
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
return fail(e);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
server.registerTool("telegram-set-business-greeting", {
|
|
174
|
+
description: "Set Telegram Business greeting message — auto-reply sent to new conversations using a quick reply shortcut as template. Requires Telegram Business subscription. Pass clear=true to disable.",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
shortcutId: z
|
|
177
|
+
.number()
|
|
178
|
+
.int()
|
|
179
|
+
.positive()
|
|
180
|
+
.optional()
|
|
181
|
+
.describe("Quick reply shortcut ID (from telegram-get-quick-replies) used as the greeting template"),
|
|
182
|
+
audience: AUDIENCE.describe("Who receives the greeting: all_new (new contacts+non-contacts), contacts_only, non_contacts, existing_only"),
|
|
183
|
+
includeUsers: z.array(z.string()).optional().describe("Additional usernames/IDs to always include"),
|
|
184
|
+
excludeUsers: z
|
|
185
|
+
.array(z.string())
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("Usernames/IDs to exclude — overrides audience. Cannot be combined with includeUsers."),
|
|
188
|
+
noActivityDays: z
|
|
189
|
+
.number()
|
|
190
|
+
.int()
|
|
191
|
+
.min(1)
|
|
192
|
+
.max(365)
|
|
193
|
+
.default(7)
|
|
194
|
+
.describe("Send greeting if user has been inactive for N days"),
|
|
195
|
+
clear: z.boolean().optional().describe("Pass true to disable greeting message"),
|
|
196
|
+
},
|
|
197
|
+
annotations: WRITE,
|
|
198
|
+
}, async ({ shortcutId, audience, includeUsers, excludeUsers, noActivityDays, clear }) => {
|
|
199
|
+
const err = await requireConnection(telegram);
|
|
200
|
+
if (err)
|
|
201
|
+
return fail(new Error(err));
|
|
202
|
+
if (!clear && shortcutId === undefined) {
|
|
203
|
+
return fail(new Error("shortcutId is required when not clearing"));
|
|
204
|
+
}
|
|
205
|
+
if (includeUsers?.length && excludeUsers?.length) {
|
|
206
|
+
return fail(new Error("includeUsers and excludeUsers cannot both be set"));
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
await telegram.setBusinessGreeting({
|
|
210
|
+
shortcutId,
|
|
211
|
+
audience,
|
|
212
|
+
includeUsers,
|
|
213
|
+
excludeUsers,
|
|
214
|
+
noActivityDays,
|
|
215
|
+
clear,
|
|
216
|
+
});
|
|
217
|
+
if (clear)
|
|
218
|
+
return ok("Business greeting cleared");
|
|
219
|
+
return ok(`Business greeting set: shortcut=${shortcutId} audience=${audience} noActivityDays=${noActivityDays}`);
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
return fail(e);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
server.registerTool("telegram-set-business-away", {
|
|
226
|
+
description: "Set Telegram Business away message — auto-reply when you are offline or outside work hours. Uses a quick reply shortcut as template. Requires Telegram Business subscription. Pass clear=true to disable.",
|
|
227
|
+
inputSchema: {
|
|
228
|
+
shortcutId: z
|
|
229
|
+
.number()
|
|
230
|
+
.int()
|
|
231
|
+
.positive()
|
|
232
|
+
.optional()
|
|
233
|
+
.describe("Quick reply shortcut ID used as the away message template"),
|
|
234
|
+
schedule: z
|
|
235
|
+
.enum(["always", "outside_hours", "custom"])
|
|
236
|
+
.default("outside_hours")
|
|
237
|
+
.describe("When to send: always (any time offline), outside_hours (based on business hours), custom (time range)"),
|
|
238
|
+
customFrom: z.number().int().positive().optional().describe("For schedule=custom: Unix timestamp range start"),
|
|
239
|
+
customTo: z.number().int().positive().optional().describe("For schedule=custom: Unix timestamp range end"),
|
|
240
|
+
offlineOnly: z
|
|
241
|
+
.boolean()
|
|
242
|
+
.default(true)
|
|
243
|
+
.describe("Send only when you appear offline (true) or regardless of online status (false)"),
|
|
244
|
+
audience: AUDIENCE.describe("Who receives the away message"),
|
|
245
|
+
includeUsers: z.array(z.string()).optional(),
|
|
246
|
+
excludeUsers: z.array(z.string()).optional(),
|
|
247
|
+
clear: z.boolean().optional().describe("Pass true to disable away message"),
|
|
248
|
+
},
|
|
249
|
+
annotations: WRITE,
|
|
250
|
+
}, async ({ shortcutId, schedule, customFrom, customTo, offlineOnly, audience, includeUsers, excludeUsers, clear, }) => {
|
|
251
|
+
const err = await requireConnection(telegram);
|
|
252
|
+
if (err)
|
|
253
|
+
return fail(new Error(err));
|
|
254
|
+
if (!clear && shortcutId === undefined) {
|
|
255
|
+
return fail(new Error("shortcutId is required when not clearing"));
|
|
256
|
+
}
|
|
257
|
+
if (schedule === "custom" && (customFrom === undefined || customTo === undefined)) {
|
|
258
|
+
return fail(new Error("customFrom and customTo are required when schedule=custom"));
|
|
259
|
+
}
|
|
260
|
+
if (includeUsers?.length && excludeUsers?.length) {
|
|
261
|
+
return fail(new Error("includeUsers and excludeUsers cannot both be set"));
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
await telegram.setBusinessAway({
|
|
265
|
+
shortcutId,
|
|
266
|
+
schedule,
|
|
267
|
+
customFrom,
|
|
268
|
+
customTo,
|
|
269
|
+
offlineOnly,
|
|
270
|
+
audience,
|
|
271
|
+
includeUsers,
|
|
272
|
+
excludeUsers,
|
|
273
|
+
clear,
|
|
274
|
+
});
|
|
275
|
+
if (clear)
|
|
276
|
+
return ok("Business away message cleared");
|
|
277
|
+
return ok(`Business away message set: shortcut=${shortcutId} schedule=${schedule}`);
|
|
278
|
+
}
|
|
279
|
+
catch (e) {
|
|
280
|
+
return fail(e);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
server.registerTool("telegram-set-business-intro", {
|
|
284
|
+
description: "Set Telegram Business intro card — title and description shown to new users opening your chat, with an optional sticker. Requires Telegram Business subscription. Pass clear=true to remove.",
|
|
285
|
+
inputSchema: {
|
|
286
|
+
title: z.string().min(1).max(32).optional().describe("Intro title (max 32 chars)"),
|
|
287
|
+
description: z.string().min(1).max(70).optional().describe("Intro description (max 70 chars)"),
|
|
288
|
+
stickerId: z
|
|
289
|
+
.string()
|
|
290
|
+
.optional()
|
|
291
|
+
.describe("Sticker document ID (stringified long) — optional illustrative sticker. Requires stickerAccessHash and stickerFileReference."),
|
|
292
|
+
stickerAccessHash: z
|
|
293
|
+
.string()
|
|
294
|
+
.optional()
|
|
295
|
+
.describe("Access hash of the sticker document (required with stickerId)"),
|
|
296
|
+
stickerFileReference: z
|
|
297
|
+
.string()
|
|
298
|
+
.regex(/^[\da-fA-F]{2,}$/)
|
|
299
|
+
.refine((v) => v.length % 2 === 0, "must be even-length hex")
|
|
300
|
+
.optional()
|
|
301
|
+
.describe("Hex-encoded file_reference bytes (required with stickerId)"),
|
|
302
|
+
clear: z.boolean().optional().describe("Pass true to remove the intro card"),
|
|
303
|
+
},
|
|
304
|
+
annotations: WRITE,
|
|
305
|
+
}, async ({ title, description, stickerId, stickerAccessHash, stickerFileReference, clear }) => {
|
|
306
|
+
const err = await requireConnection(telegram);
|
|
307
|
+
if (err)
|
|
308
|
+
return fail(new Error(err));
|
|
309
|
+
if (!clear && (!title || !description)) {
|
|
310
|
+
return fail(new Error("title and description are required when not clearing"));
|
|
311
|
+
}
|
|
312
|
+
const stickerFields = [stickerId, stickerAccessHash, stickerFileReference].filter(Boolean);
|
|
313
|
+
if (stickerFields.length > 0 && stickerFields.length < 3) {
|
|
314
|
+
return fail(new Error("stickerId, stickerAccessHash, and stickerFileReference must all be set together"));
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
await telegram.setBusinessIntro({
|
|
318
|
+
title,
|
|
319
|
+
description,
|
|
320
|
+
stickerId,
|
|
321
|
+
stickerAccessHash,
|
|
322
|
+
stickerFileReference,
|
|
323
|
+
clear,
|
|
324
|
+
});
|
|
325
|
+
if (clear)
|
|
326
|
+
return ok("Business intro cleared");
|
|
327
|
+
return ok(`Business intro set: "${title}"`);
|
|
328
|
+
}
|
|
329
|
+
catch (e) {
|
|
330
|
+
return fail(e);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DESTRUCTIVE, fail, ok, READ_ONLY, requireConnection, sanitizeInputText, WRITE } from "./shared.js";
|
|
3
|
+
export function registerFactCheckTools(server, telegram) {
|
|
4
|
+
server.registerTool("telegram-get-fact-check", {
|
|
5
|
+
description: "Get fact-check annotations on channel messages. Fact-checks are added by independent fact-checkers in supported countries. Most messages will show no fact-check.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
chatId: z.string().describe("Chat ID or username (channel)"),
|
|
8
|
+
messageIds: z
|
|
9
|
+
.array(z.number().int().positive())
|
|
10
|
+
.min(1)
|
|
11
|
+
.max(100)
|
|
12
|
+
.describe("Message IDs to get fact-checks for (1-100)"),
|
|
13
|
+
},
|
|
14
|
+
annotations: READ_ONLY,
|
|
15
|
+
}, async ({ chatId, messageIds }) => {
|
|
16
|
+
const err = await requireConnection(telegram);
|
|
17
|
+
if (err)
|
|
18
|
+
return fail(new Error(err));
|
|
19
|
+
try {
|
|
20
|
+
const result = await telegram.getFactCheck(chatId, messageIds);
|
|
21
|
+
return ok(JSON.stringify(result));
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
return fail(e);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
server.registerTool("telegram-edit-fact-check", {
|
|
28
|
+
description: "Add or update a fact-check annotation. Requires fact-checker privileges (limited to independent verifiers in supported countries).",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
chatId: z.string().describe("Chat ID or username (channel)"),
|
|
31
|
+
messageId: z.number().int().positive().describe("Message ID to annotate"),
|
|
32
|
+
text: z
|
|
33
|
+
.string()
|
|
34
|
+
.transform(sanitizeInputText)
|
|
35
|
+
.pipe(z.string().min(1).max(1024))
|
|
36
|
+
.describe("Fact-check annotation text (1-1024 chars)"),
|
|
37
|
+
parseMode: z.enum(["md", "html"]).optional().describe("Text format (currently ignored — plain text only)"),
|
|
38
|
+
},
|
|
39
|
+
annotations: WRITE,
|
|
40
|
+
}, async ({ chatId, messageId, text, parseMode }) => {
|
|
41
|
+
const err = await requireConnection(telegram);
|
|
42
|
+
if (err)
|
|
43
|
+
return fail(new Error(err));
|
|
44
|
+
try {
|
|
45
|
+
await telegram.editFactCheck(chatId, messageId, text, { parseMode });
|
|
46
|
+
const preview = `${text.slice(0, 80)}${text.length > 80 ? "..." : ""}`;
|
|
47
|
+
return ok(`Fact-check set on message #${messageId} in ${chatId}: "${preview}"`);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
return fail(e);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
server.registerTool("telegram-delete-fact-check", {
|
|
54
|
+
description: "Remove a fact-check annotation. Requires fact-checker privileges.",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
chatId: z.string().describe("Chat ID or username (channel)"),
|
|
57
|
+
messageId: z.number().int().positive().describe("Message ID whose fact-check to remove"),
|
|
58
|
+
},
|
|
59
|
+
annotations: DESTRUCTIVE,
|
|
60
|
+
}, async ({ chatId, messageId }) => {
|
|
61
|
+
const err = await requireConnection(telegram);
|
|
62
|
+
if (err)
|
|
63
|
+
return fail(new Error(err));
|
|
64
|
+
try {
|
|
65
|
+
await telegram.deleteFactCheck(chatId, messageId);
|
|
66
|
+
return ok(`Removed fact-check from message #${messageId} in ${chatId}`);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
return fail(e);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, ok, READ_ONLY, requireConnection, WRITE } from "./shared.js";
|
|
3
|
+
export function registerFolderTools(server, telegram) {
|
|
4
|
+
server.registerTool("telegram-create-folder", {
|
|
5
|
+
description: "Create a new Telegram chat folder (filter). Returns the new folder ID. Pass type flags to auto-include entire categories, or list specific chats in includePeers. Emoticon must be a single emoji character.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
title: z.string().min(1).max(12).describe("Folder name (max 12 chars)"),
|
|
8
|
+
emoticon: z.string().max(2).optional().describe("Single emoji icon for the folder"),
|
|
9
|
+
contacts: z.boolean().optional().describe("Include all contacts"),
|
|
10
|
+
nonContacts: z.boolean().optional().describe("Include all non-contacts"),
|
|
11
|
+
groups: z.boolean().optional().describe("Include all groups"),
|
|
12
|
+
broadcasts: z.boolean().optional().describe("Include all channels"),
|
|
13
|
+
bots: z.boolean().optional().describe("Include all bots"),
|
|
14
|
+
excludeMuted: z.boolean().optional().describe("Exclude muted chats"),
|
|
15
|
+
excludeRead: z.boolean().optional().describe("Exclude read chats"),
|
|
16
|
+
excludeArchived: z.boolean().optional().describe("Exclude archived chats"),
|
|
17
|
+
includePeers: z
|
|
18
|
+
.array(z.string())
|
|
19
|
+
.max(100)
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Chat IDs/usernames to explicitly include (max 100)"),
|
|
22
|
+
excludePeers: z
|
|
23
|
+
.array(z.string())
|
|
24
|
+
.max(100)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Chat IDs/usernames to explicitly exclude (max 100)"),
|
|
27
|
+
pinnedPeers: z.array(z.string()).max(5).optional().describe("Chats to pin at top of this folder (max 5)"),
|
|
28
|
+
},
|
|
29
|
+
annotations: WRITE,
|
|
30
|
+
}, async ({ title, emoticon, contacts, nonContacts, groups, broadcasts, bots, excludeMuted, excludeRead, excludeArchived, includePeers, excludePeers, pinnedPeers, }) => {
|
|
31
|
+
const err = await requireConnection(telegram);
|
|
32
|
+
if (err)
|
|
33
|
+
return fail(new Error(err));
|
|
34
|
+
try {
|
|
35
|
+
const id = await telegram.createFolder({
|
|
36
|
+
title,
|
|
37
|
+
emoticon,
|
|
38
|
+
contacts,
|
|
39
|
+
nonContacts,
|
|
40
|
+
groups,
|
|
41
|
+
broadcasts,
|
|
42
|
+
bots,
|
|
43
|
+
excludeMuted,
|
|
44
|
+
excludeRead,
|
|
45
|
+
excludeArchived,
|
|
46
|
+
includePeers,
|
|
47
|
+
excludePeers,
|
|
48
|
+
pinnedPeers,
|
|
49
|
+
});
|
|
50
|
+
return ok(`Folder created: "${title}" [id=${id}]`);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
return fail(e);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
server.registerTool("telegram-edit-folder", {
|
|
57
|
+
description: "Edit an existing Telegram chat folder by its ID (from telegram-get-chat-folders). Only pass fields you want to change — omitted fields keep their current values.",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
id: z.number().int().min(2).describe("Folder ID (≥ 2; 0 = All Chats, 1 = Archive are system folders)"),
|
|
60
|
+
title: z.string().min(1).max(12).optional().describe("New folder name (max 12 chars)"),
|
|
61
|
+
emoticon: z.string().max(2).optional().describe("New emoji icon"),
|
|
62
|
+
contacts: z.boolean().optional(),
|
|
63
|
+
nonContacts: z.boolean().optional(),
|
|
64
|
+
groups: z.boolean().optional(),
|
|
65
|
+
broadcasts: z.boolean().optional(),
|
|
66
|
+
bots: z.boolean().optional(),
|
|
67
|
+
excludeMuted: z.boolean().optional(),
|
|
68
|
+
excludeRead: z.boolean().optional(),
|
|
69
|
+
excludeArchived: z.boolean().optional(),
|
|
70
|
+
includePeers: z.array(z.string()).max(100).optional().describe("Replace includePeers list entirely"),
|
|
71
|
+
excludePeers: z.array(z.string()).max(100).optional().describe("Replace excludePeers list entirely"),
|
|
72
|
+
pinnedPeers: z.array(z.string()).max(5).optional().describe("Replace pinnedPeers list entirely"),
|
|
73
|
+
},
|
|
74
|
+
annotations: WRITE,
|
|
75
|
+
}, async ({ id, title, emoticon, contacts, nonContacts, groups, broadcasts, bots, excludeMuted, excludeRead, excludeArchived, includePeers, excludePeers, pinnedPeers, }) => {
|
|
76
|
+
const err = await requireConnection(telegram);
|
|
77
|
+
if (err)
|
|
78
|
+
return fail(new Error(err));
|
|
79
|
+
try {
|
|
80
|
+
await telegram.editFolder(id, {
|
|
81
|
+
title,
|
|
82
|
+
emoticon,
|
|
83
|
+
contacts,
|
|
84
|
+
nonContacts,
|
|
85
|
+
groups,
|
|
86
|
+
broadcasts,
|
|
87
|
+
bots,
|
|
88
|
+
excludeMuted,
|
|
89
|
+
excludeRead,
|
|
90
|
+
excludeArchived,
|
|
91
|
+
includePeers,
|
|
92
|
+
excludePeers,
|
|
93
|
+
pinnedPeers,
|
|
94
|
+
});
|
|
95
|
+
return ok(`Folder ${id} updated`);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
return fail(e);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
server.registerTool("telegram-delete-folder", {
|
|
102
|
+
description: "Delete a Telegram chat folder by its ID. Chats inside the folder are not deleted — they remain in All Chats. System folders (0 = All Chats, 1 = Archive) cannot be deleted.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
id: z.number().int().min(2).describe("Folder ID to delete (≥ 2)"),
|
|
105
|
+
},
|
|
106
|
+
annotations: WRITE,
|
|
107
|
+
}, async ({ id }) => {
|
|
108
|
+
const err = await requireConnection(telegram);
|
|
109
|
+
if (err)
|
|
110
|
+
return fail(new Error(err));
|
|
111
|
+
try {
|
|
112
|
+
await telegram.deleteFolder(id);
|
|
113
|
+
return ok(`Folder ${id} deleted`);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
return fail(e);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
server.registerTool("telegram-reorder-folders", {
|
|
120
|
+
description: "Reorder Telegram chat folders by specifying a new order of folder IDs. All existing custom folder IDs must be included.",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
order: z
|
|
123
|
+
.array(z.number().int().min(2))
|
|
124
|
+
.min(1)
|
|
125
|
+
.describe("Ordered list of folder IDs (≥ 2). Obtain IDs from telegram-get-chat-folders"),
|
|
126
|
+
},
|
|
127
|
+
annotations: WRITE,
|
|
128
|
+
}, async ({ order }) => {
|
|
129
|
+
const err = await requireConnection(telegram);
|
|
130
|
+
if (err)
|
|
131
|
+
return fail(new Error(err));
|
|
132
|
+
try {
|
|
133
|
+
await telegram.reorderFolders(order);
|
|
134
|
+
return ok(`Folders reordered: [${order.join(", ")}]`);
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
return fail(e);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
server.registerTool("telegram-get-suggested-folders", {
|
|
141
|
+
description: "Get Telegram's suggested chat folders based on your chat list (e.g. 'Unread', 'Personal', 'Work'). Returns folder templates you can create with telegram-create-folder.",
|
|
142
|
+
inputSchema: {},
|
|
143
|
+
annotations: READ_ONLY,
|
|
144
|
+
}, async () => {
|
|
145
|
+
const err = await requireConnection(telegram);
|
|
146
|
+
if (err)
|
|
147
|
+
return fail(new Error(err));
|
|
148
|
+
try {
|
|
149
|
+
const suggestions = await telegram.getSuggestedFolders();
|
|
150
|
+
if (!suggestions.length)
|
|
151
|
+
return ok("No folder suggestions available");
|
|
152
|
+
return ok(suggestions.map((s) => `${s.emoticon ? `${s.emoticon} ` : ""}${s.title}`).join("\n"));
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
return fail(e);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
server.registerTool("telegram-toggle-folder-tags", {
|
|
159
|
+
description: "Enable or disable folder tags (colored labels that appear on messages in chat lists when the message belongs to a tagged folder). Requires Telegram Premium.",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
enabled: z.boolean().describe("true to enable folder tags, false to disable"),
|
|
162
|
+
},
|
|
163
|
+
annotations: WRITE,
|
|
164
|
+
}, async ({ enabled }) => {
|
|
165
|
+
const err = await requireConnection(telegram);
|
|
166
|
+
if (err)
|
|
167
|
+
return fail(new Error(err));
|
|
168
|
+
try {
|
|
169
|
+
await telegram.toggleDialogFilterTags(enabled);
|
|
170
|
+
return ok(`Folder tags ${enabled ? "enabled" : "disabled"}`);
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
return fail(e);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
server.registerTool("telegram-get-global-privacy-settings", {
|
|
177
|
+
description: "Get your account-level global privacy settings: whether new non-contacts are auto-archived/muted, whether archived chats are kept unmuted, whether read receipts are hidden, and whether non-contacts must have Premium to message you.",
|
|
178
|
+
inputSchema: {},
|
|
179
|
+
annotations: READ_ONLY,
|
|
180
|
+
}, async () => {
|
|
181
|
+
const err = await requireConnection(telegram);
|
|
182
|
+
if (err)
|
|
183
|
+
return fail(new Error(err));
|
|
184
|
+
try {
|
|
185
|
+
const s = await telegram.getGlobalPrivacySettings();
|
|
186
|
+
return ok(JSON.stringify(s, null, 2));
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
return fail(e);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
server.registerTool("telegram-set-global-privacy-settings", {
|
|
193
|
+
description: "Update account-level global privacy settings. Only pass the fields you want to change — omitted fields keep their current values. hideReadMarks and newNoncontactPeersRequirePremium require Telegram Premium.",
|
|
194
|
+
inputSchema: {
|
|
195
|
+
archiveAndMuteNewNoncontactPeers: z
|
|
196
|
+
.boolean()
|
|
197
|
+
.optional()
|
|
198
|
+
.describe("Auto-archive and mute messages from unknown users"),
|
|
199
|
+
keepArchivedUnmuted: z.boolean().optional().describe("Keep archived chats unmuted when archiving"),
|
|
200
|
+
keepArchivedFolders: z.boolean().optional().describe("Keep archived chats in their folders"),
|
|
201
|
+
hideReadMarks: z
|
|
202
|
+
.boolean()
|
|
203
|
+
.optional()
|
|
204
|
+
.describe("Hide read receipts — others cannot see when you read their messages (Premium)"),
|
|
205
|
+
newNoncontactPeersRequirePremium: z
|
|
206
|
+
.boolean()
|
|
207
|
+
.optional()
|
|
208
|
+
.describe("Only allow users with Telegram Premium to message you if they are not in your contacts (Premium)"),
|
|
209
|
+
},
|
|
210
|
+
annotations: WRITE,
|
|
211
|
+
}, async ({ archiveAndMuteNewNoncontactPeers, keepArchivedUnmuted, keepArchivedFolders, hideReadMarks, newNoncontactPeersRequirePremium, }) => {
|
|
212
|
+
const err = await requireConnection(telegram);
|
|
213
|
+
if (err)
|
|
214
|
+
return fail(new Error(err));
|
|
215
|
+
try {
|
|
216
|
+
await telegram.setGlobalPrivacySettings({
|
|
217
|
+
archiveAndMuteNewNoncontactPeers,
|
|
218
|
+
keepArchivedUnmuted,
|
|
219
|
+
keepArchivedFolders,
|
|
220
|
+
hideReadMarks,
|
|
221
|
+
newNoncontactPeersRequirePremium,
|
|
222
|
+
});
|
|
223
|
+
const changed = Object.entries({
|
|
224
|
+
archiveAndMuteNewNoncontactPeers,
|
|
225
|
+
keepArchivedUnmuted,
|
|
226
|
+
keepArchivedFolders,
|
|
227
|
+
hideReadMarks,
|
|
228
|
+
newNoncontactPeersRequirePremium,
|
|
229
|
+
})
|
|
230
|
+
.filter(([, v]) => v !== undefined)
|
|
231
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
232
|
+
.join(", ");
|
|
233
|
+
return ok(`Global privacy updated: ${changed || "no fields changed"}`);
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
return fail(e);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|