@overpod/mcp-telegram 1.12.0 → 1.14.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/README.md +26 -0
- package/dist/index.js +2 -838
- package/dist/telegram-client.js +23 -1
- package/dist/tools/auth.d.ts +3 -0
- package/dist/tools/auth.js +72 -0
- package/dist/tools/chats.d.ts +3 -0
- package/dist/tools/chats.js +121 -0
- package/dist/tools/contacts.d.ts +3 -0
- package/dist/tools/contacts.js +135 -0
- package/dist/tools/extras.d.ts +3 -0
- package/dist/tools/extras.js +155 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +16 -0
- package/dist/tools/media.d.ts +3 -0
- package/dist/tools/media.js +84 -0
- package/dist/tools/messages.d.ts +3 -0
- package/dist/tools/messages.js +208 -0
- package/dist/tools/reactions.d.ts +3 -0
- package/dist/tools/reactions.js +63 -0
- package/dist/tools/shared.d.ts +40 -0
- package/dist/tools/shared.js +30 -0
- package/package.json +1 -1
package/dist/telegram-client.js
CHANGED
|
@@ -17,6 +17,24 @@ const MIN_SESSION_LENGTH = 100;
|
|
|
17
17
|
function resolveSessionPath(sessionPath) {
|
|
18
18
|
return sessionPath ?? process.env.TELEGRAM_SESSION_PATH ?? DEFAULT_SESSION_FILE;
|
|
19
19
|
}
|
|
20
|
+
function resolveProxy() {
|
|
21
|
+
const ip = process.env.TELEGRAM_PROXY_IP;
|
|
22
|
+
const port = process.env.TELEGRAM_PROXY_PORT;
|
|
23
|
+
if (!ip || !port)
|
|
24
|
+
return undefined;
|
|
25
|
+
const secret = process.env.TELEGRAM_PROXY_SECRET;
|
|
26
|
+
if (secret) {
|
|
27
|
+
return { ip, port: Number(port), secret, MTProxy: true };
|
|
28
|
+
}
|
|
29
|
+
const socksType = Number(process.env.TELEGRAM_PROXY_SOCKS_TYPE || "5");
|
|
30
|
+
return {
|
|
31
|
+
ip,
|
|
32
|
+
port: Number(port),
|
|
33
|
+
socksType: socksType,
|
|
34
|
+
...(process.env.TELEGRAM_PROXY_USERNAME && { username: process.env.TELEGRAM_PROXY_USERNAME }),
|
|
35
|
+
...(process.env.TELEGRAM_PROXY_PASSWORD && { password: process.env.TELEGRAM_PROXY_PASSWORD }),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
20
38
|
function ensureSessionDir(filePath) {
|
|
21
39
|
const dir = dirname(filePath);
|
|
22
40
|
if (!existsSync(dir)) {
|
|
@@ -96,8 +114,10 @@ export class TelegramService {
|
|
|
96
114
|
return false;
|
|
97
115
|
}
|
|
98
116
|
const session = new StringSession(this.sessionString);
|
|
117
|
+
const proxy = resolveProxy();
|
|
99
118
|
this.client = new TelegramClient(session, this.apiId, this.apiHash, {
|
|
100
119
|
connectionRetries: 5,
|
|
120
|
+
...(proxy && { proxy }),
|
|
101
121
|
});
|
|
102
122
|
try {
|
|
103
123
|
await this.client.connect();
|
|
@@ -183,8 +203,10 @@ export class TelegramService {
|
|
|
183
203
|
}
|
|
184
204
|
async startQrLogin(onQrDataUrl, onQrUrl) {
|
|
185
205
|
const session = new StringSession("");
|
|
206
|
+
const proxy = resolveProxy();
|
|
186
207
|
const client = new TelegramClient(session, this.apiId, this.apiHash, {
|
|
187
208
|
connectionRetries: 5,
|
|
209
|
+
...(proxy && { proxy }),
|
|
188
210
|
});
|
|
189
211
|
try {
|
|
190
212
|
await client.connect();
|
|
@@ -1003,7 +1025,7 @@ export class TelegramService {
|
|
|
1003
1025
|
for (const r of list.reactions) {
|
|
1004
1026
|
const userId = r.peerId instanceof Api.PeerUser ? r.peerId.userId.toString() : "";
|
|
1005
1027
|
if (userId) {
|
|
1006
|
-
const name = await this.resolveSenderName(bigInt(Number.parseInt(userId)));
|
|
1028
|
+
const name = await this.resolveSenderName(bigInt(Number.parseInt(userId, 10)));
|
|
1007
1029
|
users.push({ id: userId, name });
|
|
1008
1030
|
}
|
|
1009
1031
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { fail, ok, READ_ONLY, WRITE } from "./shared.js";
|
|
2
|
+
export function registerAuthTools(server, telegram) {
|
|
3
|
+
server.registerTool("telegram-status", { description: "Check Telegram connection status", annotations: READ_ONLY }, async () => {
|
|
4
|
+
if (await telegram.ensureConnected()) {
|
|
5
|
+
try {
|
|
6
|
+
const me = await telegram.getMe();
|
|
7
|
+
return ok(`Connected as ${me.firstName ?? ""} (@${me.username ?? "unknown"}, id: ${me.id})`);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return ok("Connected, but failed to get user info");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const reason = telegram.lastError ? ` Reason: ${telegram.lastError}` : "";
|
|
14
|
+
return ok(`Not connected.${reason} Use telegram-login to authenticate via QR code.`);
|
|
15
|
+
});
|
|
16
|
+
server.registerTool("telegram-login", {
|
|
17
|
+
description: "Login to Telegram via QR code. Returns QR image. IMPORTANT: pass the entire result to user without modifications.",
|
|
18
|
+
annotations: WRITE,
|
|
19
|
+
}, async () => {
|
|
20
|
+
let qrDataUrl = "";
|
|
21
|
+
let qrRawUrl = "";
|
|
22
|
+
const loginPromise = telegram.startQrLogin((dataUrl) => {
|
|
23
|
+
qrDataUrl = dataUrl;
|
|
24
|
+
}, (url) => {
|
|
25
|
+
qrRawUrl = url;
|
|
26
|
+
});
|
|
27
|
+
// Wait for first QR to be generated
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
while (!qrDataUrl && Date.now() - startTime < 15000) {
|
|
30
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
31
|
+
}
|
|
32
|
+
if (!qrDataUrl) {
|
|
33
|
+
return fail(new Error("Failed to generate QR code"));
|
|
34
|
+
}
|
|
35
|
+
// Login continues in background
|
|
36
|
+
loginPromise.then((result) => {
|
|
37
|
+
if (result.success) {
|
|
38
|
+
console.error("[mcp-telegram] Login successful");
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.error(`[mcp-telegram] Login failed: ${result.message}`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
// Return as MCP image content + text with fallback options
|
|
45
|
+
const base64 = qrDataUrl.replace(/^data:image\/png;base64,/, "");
|
|
46
|
+
const qrApiUrl = qrRawUrl
|
|
47
|
+
? `https://api.qrserver.com/v1/create-qr-code/?size=256x256&data=${encodeURIComponent(qrRawUrl)}`
|
|
48
|
+
: "";
|
|
49
|
+
const instructions = [
|
|
50
|
+
"Scan this QR code in Telegram: **Settings → Devices → Link Desktop Device**.",
|
|
51
|
+
"",
|
|
52
|
+
qrApiUrl ? `If the QR image is not visible, open this link in your browser:\n${qrApiUrl}` : "",
|
|
53
|
+
"",
|
|
54
|
+
"After scanning, run **telegram-status** to verify the connection.",
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join("\n");
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "image",
|
|
62
|
+
data: base64,
|
|
63
|
+
mimeType: "image/png",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: instructions,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, ok, READ_ONLY, requireConnection, sanitize, WRITE } from "./shared.js";
|
|
3
|
+
export function registerChatTools(server, telegram) {
|
|
4
|
+
server.registerTool("telegram-list-chats", {
|
|
5
|
+
description: "List Telegram chats with unread counts, type indicators, and contact status",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
limit: z.number().default(20).describe("Number of chats to return"),
|
|
8
|
+
offsetDate: z.number().optional().describe("Unix timestamp offset for pagination"),
|
|
9
|
+
filterType: z
|
|
10
|
+
.enum(["private", "group", "channel", "contact_requests"])
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Filter by chat type. 'contact_requests' shows only private chats from non-contacts"),
|
|
13
|
+
},
|
|
14
|
+
annotations: READ_ONLY,
|
|
15
|
+
}, async ({ limit, offsetDate, filterType }) => {
|
|
16
|
+
const err = await requireConnection(telegram);
|
|
17
|
+
if (err)
|
|
18
|
+
return fail(new Error(err));
|
|
19
|
+
try {
|
|
20
|
+
const dialogs = await telegram.getDialogs(limit, offsetDate, filterType);
|
|
21
|
+
const text = dialogs
|
|
22
|
+
.map((d) => {
|
|
23
|
+
const prefix = d.type === "group" ? "G" : d.type === "channel" ? "C" : "P";
|
|
24
|
+
const botTag = d.isBot ? " [bot]" : "";
|
|
25
|
+
const contactTag = d.type === "private" && d.isContact === false ? " [not in contacts]" : "";
|
|
26
|
+
const unread = d.unreadCount > 0 ? ` [${d.unreadCount} unread]` : "";
|
|
27
|
+
return `${prefix} ${d.name} (${d.id})${botTag}${contactTag}${unread}`;
|
|
28
|
+
})
|
|
29
|
+
.join("\n");
|
|
30
|
+
return ok(sanitize(text) || "No chats");
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
return fail(e);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
server.registerTool("telegram-search-chats", {
|
|
37
|
+
description: "Search for Telegram chats, users, or channels by name or username. Returns description and member count",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
query: z.string().describe("Search query (name or username)"),
|
|
40
|
+
limit: z.number().default(10).describe("Max results"),
|
|
41
|
+
},
|
|
42
|
+
annotations: READ_ONLY,
|
|
43
|
+
}, async ({ query, limit }) => {
|
|
44
|
+
const err = await requireConnection(telegram);
|
|
45
|
+
if (err)
|
|
46
|
+
return fail(new Error(err));
|
|
47
|
+
try {
|
|
48
|
+
const results = await telegram.searchChats(query, limit);
|
|
49
|
+
const text = results
|
|
50
|
+
.map((c) => `${c.type === "group" ? "G" : c.type === "channel" ? "C" : "P"} ${c.name}${c.username ? ` (@${c.username})` : ""} (${c.id})${c.membersCount ? ` [${c.membersCount} members]` : ""}${c.description ? ` — ${c.description.split("\n")[0].slice(0, 100)}` : ""}`)
|
|
51
|
+
.join("\n");
|
|
52
|
+
return ok(sanitize(text) || "No results");
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
return fail(e);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
server.registerTool("telegram-get-chat-info", {
|
|
59
|
+
description: "Get detailed info about a Telegram chat including name, type, members, description, and forum status",
|
|
60
|
+
inputSchema: { chatId: z.string().describe("Chat ID or username") },
|
|
61
|
+
annotations: READ_ONLY,
|
|
62
|
+
}, async ({ chatId }) => {
|
|
63
|
+
const err = await requireConnection(telegram);
|
|
64
|
+
if (err)
|
|
65
|
+
return fail(new Error(err));
|
|
66
|
+
try {
|
|
67
|
+
const info = await telegram.getChatInfo(chatId);
|
|
68
|
+
const lines = [
|
|
69
|
+
`Name: ${info.name}`,
|
|
70
|
+
`ID: ${info.id}`,
|
|
71
|
+
`Type: ${info.type}`,
|
|
72
|
+
...(info.forum ? ["Forum: yes"] : []),
|
|
73
|
+
...(info.username ? [`Username: @${info.username}`] : []),
|
|
74
|
+
...(info.description ? [`Description: ${info.description}`] : []),
|
|
75
|
+
...(info.membersCount != null ? [`Members: ${info.membersCount}`] : []),
|
|
76
|
+
];
|
|
77
|
+
return ok(lines.join("\n"));
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
return fail(e);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
server.registerTool("telegram-get-chat-members", {
|
|
84
|
+
description: "Get members of a Telegram group or channel",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
87
|
+
limit: z.number().default(50).describe("Number of members to return"),
|
|
88
|
+
},
|
|
89
|
+
annotations: READ_ONLY,
|
|
90
|
+
}, async ({ chatId, limit }) => {
|
|
91
|
+
const err = await requireConnection(telegram);
|
|
92
|
+
if (err)
|
|
93
|
+
return fail(new Error(err));
|
|
94
|
+
try {
|
|
95
|
+
const members = await telegram.getChatMembers(chatId, limit);
|
|
96
|
+
const text = members.map((m) => `${m.name}${m.username ? ` (@${m.username})` : ""} (${m.id})`).join("\n");
|
|
97
|
+
return ok(sanitize(text) || "No members found");
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
return fail(e);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
server.registerTool("telegram-join-chat", {
|
|
104
|
+
description: "Join a Telegram group or channel by username or invite link",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
target: z.string().describe("Username (@group), link (t.me/group), or invite link (t.me/+xxx)"),
|
|
107
|
+
},
|
|
108
|
+
annotations: WRITE,
|
|
109
|
+
}, async ({ target }) => {
|
|
110
|
+
const err = await requireConnection(telegram);
|
|
111
|
+
if (err)
|
|
112
|
+
return fail(new Error(err));
|
|
113
|
+
try {
|
|
114
|
+
const result = await telegram.joinChat(target);
|
|
115
|
+
return ok(`Joined ${result.type}: ${result.title} (ID: ${result.id})`);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
return fail(e);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, ok, READ_ONLY, requireConnection, sanitize, WRITE } from "./shared.js";
|
|
3
|
+
export function registerContactTools(server, telegram) {
|
|
4
|
+
server.registerTool("telegram-get-contacts", {
|
|
5
|
+
description: "Get your Telegram contacts list with phone numbers",
|
|
6
|
+
inputSchema: { limit: z.number().default(50).describe("Number of contacts to return") },
|
|
7
|
+
annotations: READ_ONLY,
|
|
8
|
+
}, async ({ limit }) => {
|
|
9
|
+
const err = await requireConnection(telegram);
|
|
10
|
+
if (err)
|
|
11
|
+
return fail(new Error(err));
|
|
12
|
+
try {
|
|
13
|
+
const contacts = await telegram.getContacts(limit);
|
|
14
|
+
const text = contacts
|
|
15
|
+
.map((c) => `P ${c.name}${c.username ? ` (@${c.username})` : ""} (${c.id})${c.phone ? ` +${c.phone}` : ""}`)
|
|
16
|
+
.join("\n");
|
|
17
|
+
return ok(sanitize(text) || "No contacts");
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
return fail(e);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
server.registerTool("telegram-get-profile", {
|
|
24
|
+
description: "Get detailed profile info of a Telegram user including bio, birthday, premium status, business info and more",
|
|
25
|
+
inputSchema: { userId: z.string().describe("User ID or username") },
|
|
26
|
+
annotations: READ_ONLY,
|
|
27
|
+
}, async ({ userId }) => {
|
|
28
|
+
const err = await requireConnection(telegram);
|
|
29
|
+
if (err)
|
|
30
|
+
return fail(new Error(err));
|
|
31
|
+
try {
|
|
32
|
+
const profile = await telegram.getProfile(userId);
|
|
33
|
+
const lines = [
|
|
34
|
+
`Name: ${profile.name}`,
|
|
35
|
+
`ID: ${profile.id}`,
|
|
36
|
+
...(profile.username ? [`Username: @${profile.username}`] : []),
|
|
37
|
+
...(profile.phone ? [`Phone: +${profile.phone}`] : []),
|
|
38
|
+
...(profile.bio ? [`Bio: ${profile.bio}`] : []),
|
|
39
|
+
`Photo: ${profile.photo ? "yes" : "no"}`,
|
|
40
|
+
...(profile.premium ? ["Premium: yes"] : []),
|
|
41
|
+
...(profile.lastSeen ? [`Last seen: ${profile.lastSeen}`] : []),
|
|
42
|
+
...(profile.birthday ? [`Birthday: ${profile.birthday}`] : []),
|
|
43
|
+
...(profile.commonChatsCount ? [`Common chats: ${profile.commonChatsCount}`] : []),
|
|
44
|
+
...(profile.personalChannelId ? [`Personal channel ID: ${profile.personalChannelId}`] : []),
|
|
45
|
+
...(profile.businessLocation ? [`Business location: ${profile.businessLocation}`] : []),
|
|
46
|
+
...(profile.businessWorkHours ? [`Business hours timezone: ${profile.businessWorkHours}`] : []),
|
|
47
|
+
];
|
|
48
|
+
return ok(lines.join("\n"));
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
return fail(e);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
server.registerTool("telegram-get-contact-requests", {
|
|
55
|
+
description: "Get incoming messages from non-contacts (contact requests). Shows who messaged you without being in your contacts, with message preview",
|
|
56
|
+
inputSchema: { limit: z.number().default(20).describe("Number of contact requests to return") },
|
|
57
|
+
annotations: READ_ONLY,
|
|
58
|
+
}, async ({ limit }) => {
|
|
59
|
+
const err = await requireConnection(telegram);
|
|
60
|
+
if (err)
|
|
61
|
+
return fail(new Error(err));
|
|
62
|
+
try {
|
|
63
|
+
const requests = await telegram.getContactRequests(limit);
|
|
64
|
+
if (requests.length === 0) {
|
|
65
|
+
return ok("No contact requests");
|
|
66
|
+
}
|
|
67
|
+
const text = requests
|
|
68
|
+
.map((r) => {
|
|
69
|
+
const tag = r.isBot ? "[bot]" : "[user]";
|
|
70
|
+
const username = r.username ? ` @${r.username}` : "";
|
|
71
|
+
const unread = r.unreadCount > 0 ? ` [${r.unreadCount} unread]` : "";
|
|
72
|
+
const preview = r.lastMessage ? `\n > ${r.lastMessage.slice(0, 100)}` : "";
|
|
73
|
+
return `${tag} ${r.name}${username} (${r.id})${unread}${preview}`;
|
|
74
|
+
})
|
|
75
|
+
.join("\n");
|
|
76
|
+
return ok(sanitize(text));
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
return fail(e);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
server.registerTool("telegram-add-contact", {
|
|
83
|
+
description: "Add a user to your Telegram contacts. Use this to accept contact requests from non-contacts",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
userId: z.string().describe("User ID or username to add"),
|
|
86
|
+
firstName: z.string().describe("First name for the contact"),
|
|
87
|
+
lastName: z.string().optional().describe("Last name for the contact"),
|
|
88
|
+
phone: z.string().optional().describe("Phone number for the contact"),
|
|
89
|
+
},
|
|
90
|
+
annotations: WRITE,
|
|
91
|
+
}, async ({ userId, firstName, lastName, phone }) => {
|
|
92
|
+
const err = await requireConnection(telegram);
|
|
93
|
+
if (err)
|
|
94
|
+
return fail(new Error(err));
|
|
95
|
+
try {
|
|
96
|
+
await telegram.addContact(userId, firstName, lastName, phone);
|
|
97
|
+
return ok(`Contact added: ${firstName}${lastName ? ` ${lastName}` : ""} (${userId})`);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
return fail(e);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
server.registerTool("telegram-block-user", {
|
|
104
|
+
description: "Block a Telegram user. Blocked users cannot send you messages",
|
|
105
|
+
inputSchema: { userId: z.string().describe("User ID or username to block") },
|
|
106
|
+
annotations: WRITE,
|
|
107
|
+
}, async ({ userId }) => {
|
|
108
|
+
const err = await requireConnection(telegram);
|
|
109
|
+
if (err)
|
|
110
|
+
return fail(new Error(err));
|
|
111
|
+
try {
|
|
112
|
+
await telegram.blockUser(userId);
|
|
113
|
+
return ok(`User blocked: ${userId}`);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
return fail(e);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
server.registerTool("telegram-report-spam", {
|
|
120
|
+
description: "Report a chat as spam to Telegram",
|
|
121
|
+
inputSchema: { chatId: z.string().describe("Chat ID or username to report") },
|
|
122
|
+
annotations: WRITE,
|
|
123
|
+
}, async ({ chatId }) => {
|
|
124
|
+
const err = await requireConnection(telegram);
|
|
125
|
+
if (err)
|
|
126
|
+
return fail(new Error(err));
|
|
127
|
+
try {
|
|
128
|
+
await telegram.reportSpam(chatId);
|
|
129
|
+
return ok(`Reported as spam: ${chatId}`);
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
return fail(e);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, formatReactions, ok, READ_ONLY, requireConnection, sanitize, WRITE } from "./shared.js";
|
|
3
|
+
export function registerExtraTools(server, telegram) {
|
|
4
|
+
server.registerTool("telegram-pin-message", {
|
|
5
|
+
description: "Pin a message in a Telegram chat",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
8
|
+
messageId: z.number().describe("Message ID to pin"),
|
|
9
|
+
silent: z.boolean().default(false).describe("Pin without notification"),
|
|
10
|
+
},
|
|
11
|
+
annotations: WRITE,
|
|
12
|
+
}, async ({ chatId, messageId, silent }) => {
|
|
13
|
+
const err = await requireConnection(telegram);
|
|
14
|
+
if (err)
|
|
15
|
+
return fail(new Error(err));
|
|
16
|
+
try {
|
|
17
|
+
await telegram.pinMessage(chatId, messageId, silent);
|
|
18
|
+
return ok(`Message ${messageId} pinned in ${chatId}`);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return fail(e);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
server.registerTool("telegram-unpin-message", {
|
|
25
|
+
description: "Unpin a message in a Telegram chat",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
28
|
+
messageId: z.number().describe("Message ID to unpin"),
|
|
29
|
+
},
|
|
30
|
+
annotations: WRITE,
|
|
31
|
+
}, async ({ chatId, messageId }) => {
|
|
32
|
+
const err = await requireConnection(telegram);
|
|
33
|
+
if (err)
|
|
34
|
+
return fail(new Error(err));
|
|
35
|
+
try {
|
|
36
|
+
await telegram.unpinMessage(chatId, messageId);
|
|
37
|
+
return ok(`Message ${messageId} unpinned in ${chatId}`);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
return fail(e);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
server.registerTool("telegram-send-scheduled", {
|
|
44
|
+
description: "Send a scheduled message to a Telegram chat. The message will be delivered at the specified time by Telegram servers",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
chatId: z.string().describe("Chat ID or username (use 'me' or 'self' for Saved Messages)"),
|
|
47
|
+
text: z.string().describe("Message text"),
|
|
48
|
+
scheduleDate: z.number().describe("Unix timestamp when to send the message (must be in the future)"),
|
|
49
|
+
replyTo: z.number().optional().describe("Message ID to reply to"),
|
|
50
|
+
parseMode: z.enum(["md", "html"]).optional().describe("Message format: md (Markdown) or html"),
|
|
51
|
+
},
|
|
52
|
+
annotations: WRITE,
|
|
53
|
+
}, async ({ chatId, text, scheduleDate, replyTo, parseMode }) => {
|
|
54
|
+
const err = await requireConnection(telegram);
|
|
55
|
+
if (err)
|
|
56
|
+
return fail(new Error(err));
|
|
57
|
+
// Resolve 'me'/'self' to Saved Messages
|
|
58
|
+
let target = chatId;
|
|
59
|
+
if (target === "me" || target === "self") {
|
|
60
|
+
try {
|
|
61
|
+
const me = await telegram.getMe();
|
|
62
|
+
target = me.id;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return fail(new Error("Failed to resolve Saved Messages"));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
await telegram.sendScheduledMessage(target, text, scheduleDate, replyTo, parseMode);
|
|
70
|
+
const date = new Date(scheduleDate * 1000).toISOString();
|
|
71
|
+
return ok(`Message scheduled for ${date} in ${chatId}`);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
return fail(e);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
server.registerTool("telegram-create-poll", {
|
|
78
|
+
description: "Create a poll in a Telegram chat (multiple choice or quiz mode)",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
81
|
+
question: z.string().describe("Poll question"),
|
|
82
|
+
answers: z.array(z.string()).min(2).max(10).describe("Answer options (2-10)"),
|
|
83
|
+
multipleChoice: z.boolean().default(false).describe("Allow multiple answers"),
|
|
84
|
+
quiz: z.boolean().default(false).describe("Quiz mode (one correct answer)"),
|
|
85
|
+
correctAnswer: z.number().optional().describe("Index of correct answer (0-based, required for quiz mode)"),
|
|
86
|
+
},
|
|
87
|
+
annotations: WRITE,
|
|
88
|
+
}, async ({ chatId, question, answers, multipleChoice, quiz, correctAnswer }) => {
|
|
89
|
+
const err = await requireConnection(telegram);
|
|
90
|
+
if (err)
|
|
91
|
+
return fail(new Error(err));
|
|
92
|
+
try {
|
|
93
|
+
const msgId = await telegram.createPoll(chatId, question, answers, {
|
|
94
|
+
multipleChoice,
|
|
95
|
+
quiz,
|
|
96
|
+
correctAnswer,
|
|
97
|
+
});
|
|
98
|
+
return ok(`Poll created in ${chatId}${msgId ? ` (message #${msgId})` : ""}`);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return fail(e);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
server.registerTool("telegram-list-topics", {
|
|
105
|
+
description: "List forum topics in a Telegram group with Topics enabled. Shows topic names, unread counts, and status",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
chatId: z.string().describe("Chat ID or username of a group with Topics enabled"),
|
|
108
|
+
limit: z.number().default(100).describe("Max topics to return"),
|
|
109
|
+
},
|
|
110
|
+
annotations: READ_ONLY,
|
|
111
|
+
}, async ({ chatId, limit }) => {
|
|
112
|
+
const err = await requireConnection(telegram);
|
|
113
|
+
if (err)
|
|
114
|
+
return fail(new Error(err));
|
|
115
|
+
try {
|
|
116
|
+
const topics = await telegram.getForumTopics(chatId, limit);
|
|
117
|
+
const text = topics
|
|
118
|
+
.map((t) => {
|
|
119
|
+
const flags = [t.pinned ? "pinned" : "", t.closed ? "closed" : ""].filter(Boolean).join(", ");
|
|
120
|
+
const flagStr = flags ? ` [${flags}]` : "";
|
|
121
|
+
const unread = t.unreadCount > 0 ? ` [${t.unreadCount} unread]` : "";
|
|
122
|
+
return `# ${t.title} (id: ${t.id})${flagStr}${unread}`;
|
|
123
|
+
})
|
|
124
|
+
.join("\n");
|
|
125
|
+
return ok(sanitize(text) || "No topics found");
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
return fail(e);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
server.registerTool("telegram-read-topic-messages", {
|
|
132
|
+
description: "Read messages from a specific forum topic in a Telegram group",
|
|
133
|
+
inputSchema: {
|
|
134
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
135
|
+
topicId: z.number().describe("Topic ID (get from telegram-list-topics)"),
|
|
136
|
+
limit: z.number().default(20).describe("Number of messages to return"),
|
|
137
|
+
offsetId: z.number().optional().describe("Message ID to start from (for pagination)"),
|
|
138
|
+
},
|
|
139
|
+
annotations: READ_ONLY,
|
|
140
|
+
}, async ({ chatId, topicId, limit, offsetId }) => {
|
|
141
|
+
const err = await requireConnection(telegram);
|
|
142
|
+
if (err)
|
|
143
|
+
return fail(new Error(err));
|
|
144
|
+
try {
|
|
145
|
+
const messages = await telegram.getTopicMessages(chatId, topicId, limit, offsetId);
|
|
146
|
+
const text = messages
|
|
147
|
+
.map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
|
|
148
|
+
.join("\n\n");
|
|
149
|
+
return ok(sanitize(text) || "No messages in this topic");
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
return fail(e);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registerAuthTools } from "./auth.js";
|
|
2
|
+
import { registerChatTools } from "./chats.js";
|
|
3
|
+
import { registerContactTools } from "./contacts.js";
|
|
4
|
+
import { registerExtraTools } from "./extras.js";
|
|
5
|
+
import { registerMediaTools } from "./media.js";
|
|
6
|
+
import { registerMessageTools } from "./messages.js";
|
|
7
|
+
import { registerReactionTools } from "./reactions.js";
|
|
8
|
+
export function registerTools(server, telegram) {
|
|
9
|
+
registerAuthTools(server, telegram);
|
|
10
|
+
registerMessageTools(server, telegram);
|
|
11
|
+
registerChatTools(server, telegram);
|
|
12
|
+
registerMediaTools(server, telegram);
|
|
13
|
+
registerContactTools(server, telegram);
|
|
14
|
+
registerReactionTools(server, telegram);
|
|
15
|
+
registerExtraTools(server, telegram);
|
|
16
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, ok, READ_ONLY, requireConnection, WRITE } from "./shared.js";
|
|
3
|
+
export function registerMediaTools(server, telegram) {
|
|
4
|
+
server.registerTool("telegram-send-file", {
|
|
5
|
+
description: "Send a file (photo, document, video, etc.) to a Telegram chat",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
8
|
+
filePath: z.string().describe("Absolute path to file"),
|
|
9
|
+
caption: z.string().optional().describe("File caption"),
|
|
10
|
+
},
|
|
11
|
+
annotations: WRITE,
|
|
12
|
+
}, async ({ chatId, filePath, caption }) => {
|
|
13
|
+
const err = await requireConnection(telegram);
|
|
14
|
+
if (err)
|
|
15
|
+
return fail(new Error(err));
|
|
16
|
+
try {
|
|
17
|
+
await telegram.sendFile(chatId, filePath, caption);
|
|
18
|
+
return ok(`File sent to ${chatId}`);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return fail(e);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
server.registerTool("telegram-download-media", {
|
|
25
|
+
description: "Download media from a Telegram message to a local file",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
28
|
+
messageId: z.number().describe("Message ID containing media"),
|
|
29
|
+
downloadPath: z.string().describe("Absolute path to save file"),
|
|
30
|
+
},
|
|
31
|
+
annotations: READ_ONLY,
|
|
32
|
+
}, async ({ chatId, messageId, downloadPath }) => {
|
|
33
|
+
const err = await requireConnection(telegram);
|
|
34
|
+
if (err)
|
|
35
|
+
return fail(new Error(err));
|
|
36
|
+
try {
|
|
37
|
+
const path = await telegram.downloadMedia(chatId, messageId, downloadPath);
|
|
38
|
+
return ok(`Media downloaded to ${path}`);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
return fail(e);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
server.registerTool("telegram-get-profile-photo", {
|
|
45
|
+
description: "Download profile photo of a Telegram user, group, or channel. Returns inline image or saves to file",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
entityId: z.string().describe("User/Chat/Channel ID or username"),
|
|
48
|
+
savePath: z.string().optional().describe("Absolute path to save file. If omitted, returns inline base64 image"),
|
|
49
|
+
size: z
|
|
50
|
+
.enum(["small", "big"])
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Photo size: 'small' (160x160) or 'big' (640x640). Default: big"),
|
|
53
|
+
},
|
|
54
|
+
annotations: READ_ONLY,
|
|
55
|
+
}, async ({ entityId, savePath, size }) => {
|
|
56
|
+
const err = await requireConnection(telegram);
|
|
57
|
+
if (err)
|
|
58
|
+
return fail(new Error(err));
|
|
59
|
+
try {
|
|
60
|
+
const result = await telegram.downloadProfilePhoto(entityId, {
|
|
61
|
+
isBig: size !== "small",
|
|
62
|
+
savePath,
|
|
63
|
+
});
|
|
64
|
+
if (!result) {
|
|
65
|
+
return ok("No profile photo found");
|
|
66
|
+
}
|
|
67
|
+
if ("filePath" in result) {
|
|
68
|
+
return ok(`Downloaded to: ${result.filePath}`);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{ type: "image", data: result.buffer.toString("base64"), mimeType: result.mimeType },
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: `Profile photo (${(result.buffer.length / 1024).toFixed(0)} KB, ${result.mimeType})`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return fail(e);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|