@overpod/mcp-telegram 1.4.0 → 1.6.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 +36 -4
- package/dist/index.js +157 -6
- package/dist/telegram-client.d.ts +26 -1
- package/dist/telegram-client.js +134 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://glama.ai/mcp/servers/overpod/mcp-telegram)
|
|
10
10
|
|
|
11
|
-
> **Hosted version available!** Don't want to self-host? Use [mcp-telegram.com](https://mcp-telegram.com) -- connect Telegram to Claude.ai in 30 seconds with QR code. No API keys needed.
|
|
11
|
+
> **Hosted version available!** Don't want to self-host? Use [mcp-telegram.com](https://mcp-telegram.com) -- connect Telegram to Claude.ai or ChatGPT in 30 seconds with QR code. No API keys needed.
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<img src="assets/demo.gif" alt="MCP Telegram demo — connect and summarize chats in Claude" width="700">
|
|
@@ -20,11 +20,11 @@ An MCP (Model Context Protocol) server that connects AI assistants like Claude t
|
|
|
20
20
|
|
|
21
21
|
- **MTProto protocol** -- direct Telegram API access, not the limited Bot API
|
|
22
22
|
- **Userbot** -- operates as your personal account, not a bot
|
|
23
|
-
- **
|
|
23
|
+
- **Full-featured** -- messaging, reactions, polls, scheduled messages, media, contacts, and more
|
|
24
24
|
- **QR code login** -- authenticate by scanning a QR code in the Telegram app
|
|
25
25
|
- **Session persistence** -- login once, stay connected across restarts
|
|
26
26
|
- **Human-readable output** -- sender names are resolved, not just numeric IDs
|
|
27
|
-
- **Works with any MCP client** -- Claude Code, Claude Desktop, Cursor, VS Code, Mastra, etc.
|
|
27
|
+
- **Works with any MCP client** -- Claude Code, Claude Desktop, ChatGPT, Cursor, VS Code, Mastra, etc.
|
|
28
28
|
|
|
29
29
|
## Prerequisites
|
|
30
30
|
|
|
@@ -162,6 +162,9 @@ const telegramMcp = new MCPClient({
|
|
|
162
162
|
|------|-------------|
|
|
163
163
|
| `telegram-send-message` | Send a text message to a chat |
|
|
164
164
|
| `telegram-send-file` | Send a file (photo, document, video, etc.) to a chat |
|
|
165
|
+
| `telegram-send-reaction` | Send or remove an emoji reaction on a message |
|
|
166
|
+
| `telegram-send-scheduled` | Schedule a message for future delivery |
|
|
167
|
+
| `telegram-create-poll` | Create a poll (multiple choice or quiz mode) |
|
|
165
168
|
| `telegram-edit-message` | Edit a previously sent message |
|
|
166
169
|
| `telegram-delete-message` | Delete messages in a chat |
|
|
167
170
|
| `telegram-forward-message` | Forward messages between chats |
|
|
@@ -284,6 +287,35 @@ Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-10012345
|
|
|
284
287
|
|-----------|------|----------|-------------|
|
|
285
288
|
| `target` | string | yes | Username (@group), link (t.me/group), or invite link (t.me/+xxx) |
|
|
286
289
|
|
|
290
|
+
### telegram-send-reaction
|
|
291
|
+
|
|
292
|
+
| Parameter | Type | Required | Description |
|
|
293
|
+
|-----------|------|----------|-------------|
|
|
294
|
+
| `chatId` | string | yes | Chat ID or @username |
|
|
295
|
+
| `messageId` | number | yes | Message ID to react to |
|
|
296
|
+
| `emoji` | string | no | Reaction emoji (e.g. 👍❤️🔥😂🎉). Omit to remove reaction |
|
|
297
|
+
|
|
298
|
+
### telegram-send-scheduled
|
|
299
|
+
|
|
300
|
+
| Parameter | Type | Required | Description |
|
|
301
|
+
|-----------|------|----------|-------------|
|
|
302
|
+
| `chatId` | string | yes | Chat ID or @username (use `"me"` or `"self"` for Saved Messages) |
|
|
303
|
+
| `text` | string | yes | Message text |
|
|
304
|
+
| `scheduleDate` | number | yes | Unix timestamp when to send (must be in the future) |
|
|
305
|
+
| `replyTo` | number | no | Message ID to reply to |
|
|
306
|
+
| `parseMode` | `"md"` / `"html"` | no | Message formatting mode |
|
|
307
|
+
|
|
308
|
+
### telegram-create-poll
|
|
309
|
+
|
|
310
|
+
| Parameter | Type | Required | Description |
|
|
311
|
+
|-----------|------|----------|-------------|
|
|
312
|
+
| `chatId` | string | yes | Chat ID or @username |
|
|
313
|
+
| `question` | string | yes | Poll question |
|
|
314
|
+
| `answers` | string[] | yes | Answer options (2-10 items) |
|
|
315
|
+
| `multipleChoice` | boolean | no | Allow multiple answers (default: false) |
|
|
316
|
+
| `quiz` | boolean | no | Quiz mode with one correct answer (default: false) |
|
|
317
|
+
| `correctAnswer` | number | no | Index of correct answer, 0-based (required for quiz mode) |
|
|
318
|
+
|
|
287
319
|
### telegram-search-messages
|
|
288
320
|
|
|
289
321
|
| Parameter | Type | Required | Description |
|
|
@@ -340,7 +372,7 @@ npm run format # Format code with Biome
|
|
|
340
372
|
|
|
341
373
|
```
|
|
342
374
|
src/
|
|
343
|
-
index.ts -- MCP server entry point,
|
|
375
|
+
index.ts -- MCP server entry point, tool definitions
|
|
344
376
|
telegram-client.ts -- TelegramService class (GramJS wrapper)
|
|
345
377
|
qr-login-cli.ts -- CLI utility for QR code login
|
|
346
378
|
```
|
package/dist/index.js
CHANGED
|
@@ -119,7 +119,10 @@ server.tool("telegram-send-message", "Send a message to a Telegram chat", {
|
|
|
119
119
|
server.tool("telegram-list-chats", "List Telegram chats", {
|
|
120
120
|
limit: z.number().default(20).describe("Number of chats to return"),
|
|
121
121
|
offsetDate: z.number().optional().describe("Unix timestamp offset for pagination"),
|
|
122
|
-
filterType: z
|
|
122
|
+
filterType: z
|
|
123
|
+
.enum(["private", "group", "channel", "contact_requests"])
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Filter by chat type. 'contact_requests' shows only private chats from non-contacts"),
|
|
123
126
|
}, async ({ limit, offsetDate, filterType }) => {
|
|
124
127
|
const err = await requireConnection();
|
|
125
128
|
if (err)
|
|
@@ -127,7 +130,13 @@ server.tool("telegram-list-chats", "List Telegram chats", {
|
|
|
127
130
|
try {
|
|
128
131
|
const dialogs = await telegram.getDialogs(limit, offsetDate, filterType);
|
|
129
132
|
const text = dialogs
|
|
130
|
-
.map((d) =>
|
|
133
|
+
.map((d) => {
|
|
134
|
+
const prefix = d.type === "group" ? "G" : d.type === "channel" ? "C" : "P";
|
|
135
|
+
const botTag = d.isBot ? " [bot]" : "";
|
|
136
|
+
const contactTag = d.type === "private" && d.isContact === false ? " [not in contacts]" : "";
|
|
137
|
+
const unread = d.unreadCount > 0 ? ` [${d.unreadCount} unread]` : "";
|
|
138
|
+
return `${prefix} ${d.name} (${d.id})${botTag}${contactTag}${unread}`;
|
|
139
|
+
})
|
|
131
140
|
.join("\n");
|
|
132
141
|
return { content: [{ type: "text", text: text || "No chats" }] };
|
|
133
142
|
}
|
|
@@ -204,7 +213,12 @@ server.tool("telegram-get-unread", "Get unread Telegram chats", {
|
|
|
204
213
|
try {
|
|
205
214
|
const dialogs = await telegram.getUnreadDialogs(limit);
|
|
206
215
|
const text = dialogs
|
|
207
|
-
.map((d) =>
|
|
216
|
+
.map((d) => {
|
|
217
|
+
const prefix = d.type === "group" ? "G" : d.type === "channel" ? "C" : "P";
|
|
218
|
+
const botTag = d.isBot ? " [bot]" : "";
|
|
219
|
+
const contactTag = d.type === "private" && d.isContact === false ? " [not in contacts]" : "";
|
|
220
|
+
return `${prefix} ${d.name} (${d.id})${botTag}${contactTag} [${d.unreadCount} unread]`;
|
|
221
|
+
})
|
|
208
222
|
.join("\n");
|
|
209
223
|
return { content: [{ type: "text", text: text || "No unread chats" }] };
|
|
210
224
|
}
|
|
@@ -419,9 +433,7 @@ server.tool("telegram-get-profile", "Get detailed profile info of a Telegram use
|
|
|
419
433
|
}
|
|
420
434
|
});
|
|
421
435
|
server.tool("telegram-join-chat", "Join a Telegram group or channel by username or invite link", {
|
|
422
|
-
target: z
|
|
423
|
-
.string()
|
|
424
|
-
.describe("Username (@group), link (t.me/group), or invite link (t.me/+xxx)"),
|
|
436
|
+
target: z.string().describe("Username (@group), link (t.me/group), or invite link (t.me/+xxx)"),
|
|
425
437
|
}, async ({ target }) => {
|
|
426
438
|
const err = await requireConnection();
|
|
427
439
|
if (err)
|
|
@@ -443,6 +455,145 @@ server.tool("telegram-join-chat", "Join a Telegram group or channel by username
|
|
|
443
455
|
};
|
|
444
456
|
}
|
|
445
457
|
});
|
|
458
|
+
server.tool("telegram-send-reaction", "Send an emoji reaction to a message. Pass emoji to react, omit to remove reaction", {
|
|
459
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
460
|
+
messageId: z.number().describe("Message ID to react to"),
|
|
461
|
+
emoji: z.string().optional().describe("Reaction emoji (e.g. 👍❤️🔥😂🎉). Omit to remove reaction"),
|
|
462
|
+
}, async ({ chatId, messageId, emoji }) => {
|
|
463
|
+
const err = await requireConnection();
|
|
464
|
+
if (err)
|
|
465
|
+
return { content: [{ type: "text", text: err }] };
|
|
466
|
+
try {
|
|
467
|
+
await telegram.sendReaction(chatId, messageId, emoji);
|
|
468
|
+
const action = emoji ? `Reacted ${emoji} to` : "Removed reaction from";
|
|
469
|
+
return { content: [{ type: "text", text: `${action} message ${messageId} in ${chatId}` }] };
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
return { content: [{ type: "text", text: `Reaction error: ${e.message}` }] };
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
server.tool("telegram-send-scheduled", "Send a scheduled message to a Telegram chat. The message will be delivered at the specified time by Telegram servers", {
|
|
476
|
+
chatId: z.string().describe("Chat ID or username (use 'me' or 'self' for Saved Messages)"),
|
|
477
|
+
text: z.string().describe("Message text"),
|
|
478
|
+
scheduleDate: z.number().describe("Unix timestamp when to send the message (must be in the future)"),
|
|
479
|
+
replyTo: z.number().optional().describe("Message ID to reply to"),
|
|
480
|
+
parseMode: z.enum(["md", "html"]).optional().describe("Message format: md (Markdown) or html"),
|
|
481
|
+
}, async ({ chatId, text, scheduleDate, replyTo, parseMode }) => {
|
|
482
|
+
const err = await requireConnection();
|
|
483
|
+
if (err)
|
|
484
|
+
return { content: [{ type: "text", text: err }] };
|
|
485
|
+
// Resolve 'me'/'self' to Saved Messages
|
|
486
|
+
let target = chatId;
|
|
487
|
+
if (target === "me" || target === "self") {
|
|
488
|
+
try {
|
|
489
|
+
const me = await telegram.getMe();
|
|
490
|
+
target = me.id;
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
return { content: [{ type: "text", text: "Failed to resolve Saved Messages" }] };
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
await telegram.sendScheduledMessage(target, text, scheduleDate, replyTo, parseMode);
|
|
498
|
+
const date = new Date(scheduleDate * 1000).toISOString();
|
|
499
|
+
return { content: [{ type: "text", text: `Message scheduled for ${date} in ${chatId}` }] };
|
|
500
|
+
}
|
|
501
|
+
catch (e) {
|
|
502
|
+
return { content: [{ type: "text", text: `Schedule error: ${e.message}` }] };
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
server.tool("telegram-create-poll", "Create a poll in a Telegram chat", {
|
|
506
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
507
|
+
question: z.string().describe("Poll question"),
|
|
508
|
+
answers: z.array(z.string()).min(2).max(10).describe("Answer options (2-10)"),
|
|
509
|
+
multipleChoice: z.boolean().default(false).describe("Allow multiple answers"),
|
|
510
|
+
quiz: z.boolean().default(false).describe("Quiz mode (one correct answer)"),
|
|
511
|
+
correctAnswer: z.number().optional().describe("Index of correct answer (0-based, required for quiz mode)"),
|
|
512
|
+
}, async ({ chatId, question, answers, multipleChoice, quiz, correctAnswer }) => {
|
|
513
|
+
const err = await requireConnection();
|
|
514
|
+
if (err)
|
|
515
|
+
return { content: [{ type: "text", text: err }] };
|
|
516
|
+
try {
|
|
517
|
+
const msgId = await telegram.createPoll(chatId, question, answers, { multipleChoice, quiz, correctAnswer });
|
|
518
|
+
return { content: [{ type: "text", text: `Poll created in ${chatId}${msgId ? ` (message #${msgId})` : ""}` }] };
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
return { content: [{ type: "text", text: `Poll error: ${e.message}` }] };
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
server.tool("telegram-get-contact-requests", "Get incoming messages from non-contacts (contact requests). Shows who messaged you without being in your contacts, with message preview", {
|
|
525
|
+
limit: z.number().default(20).describe("Number of contact requests to return"),
|
|
526
|
+
}, async ({ limit }) => {
|
|
527
|
+
const err = await requireConnection();
|
|
528
|
+
if (err)
|
|
529
|
+
return { content: [{ type: "text", text: err }] };
|
|
530
|
+
try {
|
|
531
|
+
const requests = await telegram.getContactRequests(limit);
|
|
532
|
+
if (requests.length === 0) {
|
|
533
|
+
return { content: [{ type: "text", text: "No contact requests" }] };
|
|
534
|
+
}
|
|
535
|
+
const text = requests
|
|
536
|
+
.map((r) => {
|
|
537
|
+
const tag = r.isBot ? "[bot]" : "[user]";
|
|
538
|
+
const username = r.username ? ` @${r.username}` : "";
|
|
539
|
+
const unread = r.unreadCount > 0 ? ` [${r.unreadCount} unread]` : "";
|
|
540
|
+
const preview = r.lastMessage ? `\n > ${r.lastMessage.slice(0, 100)}` : "";
|
|
541
|
+
return `${tag} ${r.name}${username} (${r.id})${unread}${preview}`;
|
|
542
|
+
})
|
|
543
|
+
.join("\n");
|
|
544
|
+
return { content: [{ type: "text", text: text }] };
|
|
545
|
+
}
|
|
546
|
+
catch (e) {
|
|
547
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
server.tool("telegram-add-contact", "Add a user to your Telegram contacts. Use this to accept contact requests from non-contacts", {
|
|
551
|
+
userId: z.string().describe("User ID or username to add"),
|
|
552
|
+
firstName: z.string().describe("First name for the contact"),
|
|
553
|
+
lastName: z.string().optional().describe("Last name for the contact"),
|
|
554
|
+
phone: z.string().optional().describe("Phone number for the contact"),
|
|
555
|
+
}, async ({ userId, firstName, lastName, phone }) => {
|
|
556
|
+
const err = await requireConnection();
|
|
557
|
+
if (err)
|
|
558
|
+
return { content: [{ type: "text", text: err }] };
|
|
559
|
+
try {
|
|
560
|
+
await telegram.addContact(userId, firstName, lastName, phone);
|
|
561
|
+
return {
|
|
562
|
+
content: [{ type: "text", text: `Contact added: ${firstName}${lastName ? ` ${lastName}` : ""} (${userId})` }],
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
catch (e) {
|
|
566
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
server.tool("telegram-block-user", "Block a Telegram user. Blocked users cannot send you messages", {
|
|
570
|
+
userId: z.string().describe("User ID or username to block"),
|
|
571
|
+
}, async ({ userId }) => {
|
|
572
|
+
const err = await requireConnection();
|
|
573
|
+
if (err)
|
|
574
|
+
return { content: [{ type: "text", text: err }] };
|
|
575
|
+
try {
|
|
576
|
+
await telegram.blockUser(userId);
|
|
577
|
+
return { content: [{ type: "text", text: `User blocked: ${userId}` }] };
|
|
578
|
+
}
|
|
579
|
+
catch (e) {
|
|
580
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
server.tool("telegram-report-spam", "Report a chat as spam to Telegram", {
|
|
584
|
+
chatId: z.string().describe("Chat ID or username to report"),
|
|
585
|
+
}, async ({ chatId }) => {
|
|
586
|
+
const err = await requireConnection();
|
|
587
|
+
if (err)
|
|
588
|
+
return { content: [{ type: "text", text: err }] };
|
|
589
|
+
try {
|
|
590
|
+
await telegram.reportSpam(chatId);
|
|
591
|
+
return { content: [{ type: "text", text: `Reported as spam: ${chatId}` }] };
|
|
592
|
+
}
|
|
593
|
+
catch (e) {
|
|
594
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
595
|
+
}
|
|
596
|
+
});
|
|
446
597
|
// --- Start ---
|
|
447
598
|
async function main() {
|
|
448
599
|
// Try to auto-connect with saved session
|
|
@@ -43,18 +43,34 @@ export declare class TelegramService {
|
|
|
43
43
|
private detectMimeType;
|
|
44
44
|
pinMessage(chatId: string, messageId: number, silent?: boolean): Promise<void>;
|
|
45
45
|
unpinMessage(chatId: string, messageId: number): Promise<void>;
|
|
46
|
-
getDialogs(limit?: number, offsetDate?: number, filterType?: "private" | "group" | "channel"): Promise<Array<{
|
|
46
|
+
getDialogs(limit?: number, offsetDate?: number, filterType?: "private" | "group" | "channel" | "contact_requests"): Promise<Array<{
|
|
47
47
|
id: string;
|
|
48
48
|
name: string;
|
|
49
49
|
type: string;
|
|
50
50
|
unreadCount: number;
|
|
51
|
+
isBot?: boolean;
|
|
52
|
+
isContact?: boolean;
|
|
51
53
|
}>>;
|
|
52
54
|
getUnreadDialogs(limit?: number): Promise<Array<{
|
|
53
55
|
id: string;
|
|
54
56
|
name: string;
|
|
55
57
|
type: string;
|
|
56
58
|
unreadCount: number;
|
|
59
|
+
isBot?: boolean;
|
|
60
|
+
isContact?: boolean;
|
|
57
61
|
}>>;
|
|
62
|
+
getContactRequests(limit?: number): Promise<Array<{
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
username?: string;
|
|
66
|
+
isBot: boolean;
|
|
67
|
+
unreadCount: number;
|
|
68
|
+
lastMessage?: string;
|
|
69
|
+
lastMessageDate?: number;
|
|
70
|
+
}>>;
|
|
71
|
+
addContact(userId: string, firstName: string, lastName?: string, phone?: string): Promise<void>;
|
|
72
|
+
blockUser(userId: string): Promise<void>;
|
|
73
|
+
reportSpam(chatId: string): Promise<void>;
|
|
58
74
|
markAsRead(chatId: string): Promise<void>;
|
|
59
75
|
forwardMessage(fromChatId: string, toChatId: string, messageIds: number[]): Promise<void>;
|
|
60
76
|
editMessage(chatId: string, messageId: number, newText: string): Promise<void>;
|
|
@@ -66,6 +82,8 @@ export declare class TelegramService {
|
|
|
66
82
|
username?: string;
|
|
67
83
|
description?: string;
|
|
68
84
|
membersCount?: number;
|
|
85
|
+
isBot?: boolean;
|
|
86
|
+
isContact?: boolean;
|
|
69
87
|
}>;
|
|
70
88
|
/** Extract media info from a message */
|
|
71
89
|
private extractMediaInfo;
|
|
@@ -119,6 +137,13 @@ export declare class TelegramService {
|
|
|
119
137
|
photo: boolean;
|
|
120
138
|
lastSeen?: string;
|
|
121
139
|
}>;
|
|
140
|
+
sendReaction(chatId: string, messageId: number, emoji?: string): Promise<void>;
|
|
141
|
+
sendScheduledMessage(chatId: string, text: string, scheduleDate: number, replyTo?: number, parseMode?: "md" | "html"): Promise<void>;
|
|
142
|
+
createPoll(chatId: string, question: string, answers: string[], options?: {
|
|
143
|
+
multipleChoice?: boolean;
|
|
144
|
+
quiz?: boolean;
|
|
145
|
+
correctAnswer?: number;
|
|
146
|
+
}): Promise<number>;
|
|
122
147
|
joinChat(target: string): Promise<{
|
|
123
148
|
id: string;
|
|
124
149
|
title: string;
|
package/dist/telegram-client.js
CHANGED
|
@@ -309,12 +309,22 @@ export class TelegramService {
|
|
|
309
309
|
throw new Error("Not connected");
|
|
310
310
|
const fetchLimit = filterType ? limit * 3 : limit;
|
|
311
311
|
const dialogs = await this.client.getDialogs({ limit: fetchLimit, ...(offsetDate ? { offsetDate } : {}) });
|
|
312
|
-
const mapped = dialogs.map((d) =>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
312
|
+
const mapped = dialogs.map((d) => {
|
|
313
|
+
const type = d.isGroup ? "group" : d.isChannel ? "channel" : "private";
|
|
314
|
+
const isUser = d.entity instanceof Api.User;
|
|
315
|
+
return {
|
|
316
|
+
id: d.id?.toString() ?? "",
|
|
317
|
+
name: d.title ?? d.name ?? "Unknown",
|
|
318
|
+
type,
|
|
319
|
+
unreadCount: d.unreadCount,
|
|
320
|
+
...(isUser
|
|
321
|
+
? { isBot: Boolean(d.entity.bot), isContact: Boolean(d.entity.contact) }
|
|
322
|
+
: {}),
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
if (filterType === "contact_requests") {
|
|
326
|
+
return mapped.filter((d) => d.type === "private" && d.isContact === false).slice(0, limit);
|
|
327
|
+
}
|
|
318
328
|
return filterType ? mapped.filter((d) => d.type === filterType).slice(0, limit) : mapped;
|
|
319
329
|
}
|
|
320
330
|
async getUnreadDialogs(limit = 20) {
|
|
@@ -324,13 +334,67 @@ export class TelegramService {
|
|
|
324
334
|
return dialogs
|
|
325
335
|
.filter((d) => d.unreadCount > 0)
|
|
326
336
|
.slice(0, limit)
|
|
327
|
-
.map((d) =>
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
337
|
+
.map((d) => {
|
|
338
|
+
const isUser = d.entity instanceof Api.User;
|
|
339
|
+
return {
|
|
340
|
+
id: d.id?.toString() ?? "",
|
|
341
|
+
name: d.title ?? d.name ?? "Unknown",
|
|
342
|
+
type: d.isGroup ? "group" : d.isChannel ? "channel" : "private",
|
|
343
|
+
unreadCount: d.unreadCount,
|
|
344
|
+
...(isUser
|
|
345
|
+
? { isBot: Boolean(d.entity.bot), isContact: Boolean(d.entity.contact) }
|
|
346
|
+
: {}),
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
async getContactRequests(limit = 20) {
|
|
351
|
+
if (!this.client || !this.connected)
|
|
352
|
+
throw new Error("Not connected");
|
|
353
|
+
const dialogs = await this.client.getDialogs({ limit: limit * 5 });
|
|
354
|
+
return dialogs
|
|
355
|
+
.filter((d) => {
|
|
356
|
+
if (d.isGroup || d.isChannel)
|
|
357
|
+
return false;
|
|
358
|
+
return d.entity instanceof Api.User && !d.entity.contact;
|
|
359
|
+
})
|
|
360
|
+
.slice(0, limit)
|
|
361
|
+
.map((d) => {
|
|
362
|
+
const user = d.entity;
|
|
363
|
+
const msg = d.message;
|
|
364
|
+
return {
|
|
365
|
+
id: d.id?.toString() ?? "",
|
|
366
|
+
name: [user.firstName, user.lastName].filter(Boolean).join(" ") || "Unknown",
|
|
367
|
+
username: user.username ?? undefined,
|
|
368
|
+
isBot: Boolean(user.bot),
|
|
369
|
+
unreadCount: d.unreadCount,
|
|
370
|
+
lastMessage: msg?.message ?? undefined,
|
|
371
|
+
lastMessageDate: msg?.date ?? undefined,
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
async addContact(userId, firstName, lastName, phone) {
|
|
376
|
+
if (!this.client || !this.connected)
|
|
377
|
+
throw new Error("Not connected");
|
|
378
|
+
const entity = await this.client.getInputEntity(userId);
|
|
379
|
+
await this.client.invoke(new Api.contacts.AddContact({
|
|
380
|
+
id: entity,
|
|
381
|
+
firstName,
|
|
382
|
+
lastName: lastName ?? "",
|
|
383
|
+
phone: phone ?? "",
|
|
332
384
|
}));
|
|
333
385
|
}
|
|
386
|
+
async blockUser(userId) {
|
|
387
|
+
if (!this.client || !this.connected)
|
|
388
|
+
throw new Error("Not connected");
|
|
389
|
+
const entity = await this.client.getInputEntity(userId);
|
|
390
|
+
await this.client.invoke(new Api.contacts.Block({ id: entity }));
|
|
391
|
+
}
|
|
392
|
+
async reportSpam(chatId) {
|
|
393
|
+
if (!this.client || !this.connected)
|
|
394
|
+
throw new Error("Not connected");
|
|
395
|
+
const peer = await this.client.getInputEntity(chatId);
|
|
396
|
+
await this.client.invoke(new Api.messages.ReportSpam({ peer }));
|
|
397
|
+
}
|
|
334
398
|
async markAsRead(chatId) {
|
|
335
399
|
if (!this.client || !this.connected)
|
|
336
400
|
throw new Error("Not connected");
|
|
@@ -362,6 +426,8 @@ export class TelegramService {
|
|
|
362
426
|
name: parts.join(" ") || "Unknown",
|
|
363
427
|
type: "private",
|
|
364
428
|
username: entity.username ?? undefined,
|
|
429
|
+
isBot: Boolean(entity.bot),
|
|
430
|
+
isContact: Boolean(entity.contact),
|
|
365
431
|
};
|
|
366
432
|
}
|
|
367
433
|
if (entity instanceof Api.Channel) {
|
|
@@ -603,6 +669,63 @@ export class TelegramService {
|
|
|
603
669
|
lastSeen,
|
|
604
670
|
};
|
|
605
671
|
}
|
|
672
|
+
async sendReaction(chatId, messageId, emoji) {
|
|
673
|
+
if (!this.client || !this.connected)
|
|
674
|
+
throw new Error("Not connected");
|
|
675
|
+
const peer = await this.client.getInputEntity(chatId);
|
|
676
|
+
const reaction = emoji ? [new Api.ReactionEmoji({ emoticon: emoji })] : []; // empty = remove reaction
|
|
677
|
+
await this.client.invoke(new Api.messages.SendReaction({
|
|
678
|
+
peer,
|
|
679
|
+
msgId: messageId,
|
|
680
|
+
reaction,
|
|
681
|
+
}));
|
|
682
|
+
}
|
|
683
|
+
async sendScheduledMessage(chatId, text, scheduleDate, replyTo, parseMode) {
|
|
684
|
+
if (!this.client || !this.connected)
|
|
685
|
+
throw new Error("Not connected");
|
|
686
|
+
await this.client.sendMessage(chatId, {
|
|
687
|
+
message: text,
|
|
688
|
+
schedule: scheduleDate,
|
|
689
|
+
...(replyTo ? { replyTo } : {}),
|
|
690
|
+
...(parseMode ? { parseMode: parseMode === "html" ? "html" : "md" } : {}),
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
async createPoll(chatId, question, answers, options) {
|
|
694
|
+
if (!this.client || !this.connected)
|
|
695
|
+
throw new Error("Not connected");
|
|
696
|
+
const peer = await this.client.getInputEntity(chatId);
|
|
697
|
+
const pollAnswers = answers.map((text, i) => new Api.PollAnswer({
|
|
698
|
+
text: new Api.TextWithEntities({ text, entities: [] }),
|
|
699
|
+
option: Buffer.from([i]),
|
|
700
|
+
}));
|
|
701
|
+
const poll = new Api.Poll({
|
|
702
|
+
id: bigInt(Date.now()),
|
|
703
|
+
question: new Api.TextWithEntities({ text: question, entities: [] }),
|
|
704
|
+
answers: pollAnswers,
|
|
705
|
+
multipleChoice: options?.multipleChoice ?? false,
|
|
706
|
+
quiz: options?.quiz ?? false,
|
|
707
|
+
});
|
|
708
|
+
const result = await this.client.invoke(new Api.messages.SendMedia({
|
|
709
|
+
peer,
|
|
710
|
+
media: new Api.InputMediaPoll({
|
|
711
|
+
poll,
|
|
712
|
+
...(options?.quiz && options.correctAnswer != null
|
|
713
|
+
? { correctAnswers: [Buffer.from([options.correctAnswer])] }
|
|
714
|
+
: {}),
|
|
715
|
+
}),
|
|
716
|
+
message: "",
|
|
717
|
+
randomId: bigInt(Math.floor(Math.random() * 1e15)),
|
|
718
|
+
}));
|
|
719
|
+
// Extract message ID from result
|
|
720
|
+
if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
|
|
721
|
+
for (const update of result.updates) {
|
|
722
|
+
if (update instanceof Api.UpdateMessageID) {
|
|
723
|
+
return update.id;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return 0;
|
|
728
|
+
}
|
|
606
729
|
async joinChat(target) {
|
|
607
730
|
if (!this.client)
|
|
608
731
|
throw new Error("Not connected");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overpod/mcp-telegram",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for Telegram userbot —
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "MCP server for Telegram userbot — messages, media, reactions, polls & more. Built on GramJS/MTProto.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"exports": {
|