@overpod/mcp-telegram 1.7.0 → 1.8.1
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 +59 -3
- package/dist/index.js +62 -4
- package/dist/telegram-client.d.ts +30 -1
- package/dist/telegram-client.js +118 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ An MCP (Model Context Protocol) server that connects AI assistants like Claude t
|
|
|
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
|
+
- **Forum Topics** -- list topics, read per-topic messages, send to specific topics, per-topic unread counts
|
|
24
25
|
- **QR code login** -- authenticate by scanning a QR code in the Telegram app
|
|
25
26
|
- **Session persistence** -- login once, stay connected across restarts
|
|
26
27
|
- **Human-readable output** -- sender names are resolved, not just numeric IDs
|
|
@@ -46,7 +47,9 @@ An MCP (Model Context Protocol) server that connects AI assistants like Claude t
|
|
|
46
47
|
TELEGRAM_API_ID=YOUR_ID TELEGRAM_API_HASH=YOUR_HASH npx @overpod/mcp-telegram login
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
A QR code will appear in the terminal. Open Telegram on your phone, go to **Settings > Devices > Link Desktop Device**, and scan the code. The session is saved to `~/.telegram
|
|
50
|
+
A QR code will appear in the terminal. Open Telegram on your phone, go to **Settings > Devices > Link Desktop Device**, and scan the code. The session is saved to `~/.mcp-telegram/session` and reused automatically.
|
|
51
|
+
|
|
52
|
+
> **Custom session path:** set `TELEGRAM_SESSION_PATH=/path/to/session` to store the session file elsewhere.
|
|
50
53
|
|
|
51
54
|
### 3. Add to Claude
|
|
52
55
|
|
|
@@ -59,6 +62,34 @@ claude mcp add telegram -s user \
|
|
|
59
62
|
|
|
60
63
|
That's it! Ask Claude to run `telegram-status` to verify.
|
|
61
64
|
|
|
65
|
+
### Multiple Accounts
|
|
66
|
+
|
|
67
|
+
Use `TELEGRAM_SESSION_PATH` to run separate Telegram accounts side by side:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Login each account with a unique session path
|
|
71
|
+
TELEGRAM_API_ID=ID1 TELEGRAM_API_HASH=HASH1 TELEGRAM_SESSION_PATH=~/.mcp-telegram/session-work npx @overpod/mcp-telegram login
|
|
72
|
+
TELEGRAM_API_ID=ID2 TELEGRAM_API_HASH=HASH2 TELEGRAM_SESSION_PATH=~/.mcp-telegram/session-personal npx @overpod/mcp-telegram login
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then add each as a separate MCP server:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
claude mcp add telegram-work -s user \
|
|
79
|
+
-e TELEGRAM_API_ID=ID1 \
|
|
80
|
+
-e TELEGRAM_API_HASH=HASH1 \
|
|
81
|
+
-e TELEGRAM_SESSION_PATH=~/.mcp-telegram/session-work \
|
|
82
|
+
-- npx @overpod/mcp-telegram
|
|
83
|
+
|
|
84
|
+
claude mcp add telegram-personal -s user \
|
|
85
|
+
-e TELEGRAM_API_ID=ID2 \
|
|
86
|
+
-e TELEGRAM_API_HASH=HASH2 \
|
|
87
|
+
-e TELEGRAM_SESSION_PATH=~/.mcp-telegram/session-personal \
|
|
88
|
+
-- npx @overpod/mcp-telegram
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Each account gets its own session file — no conflicts.
|
|
92
|
+
|
|
62
93
|
## Installation Options
|
|
63
94
|
|
|
64
95
|
### npx (recommended, zero install)
|
|
@@ -177,9 +208,16 @@ const telegramMcp = new MCPClient({
|
|
|
177
208
|
| `telegram-read-messages` | Read recent messages from a chat |
|
|
178
209
|
| `telegram-search-chats` | Search for chats, users, or channels by name |
|
|
179
210
|
| `telegram-search-messages` | Search messages in a chat by text |
|
|
180
|
-
| `telegram-get-unread` | Get chats with unread messages
|
|
211
|
+
| `telegram-get-unread` | Get chats with unread messages; forums show per-topic unread breakdown |
|
|
181
212
|
| `telegram-get-contact-requests` | Get incoming messages from non-contacts with preview |
|
|
182
213
|
|
|
214
|
+
### Forum Topics
|
|
215
|
+
|
|
216
|
+
| Tool | Description |
|
|
217
|
+
|------|-------------|
|
|
218
|
+
| `telegram-list-topics` | List forum topics in a group with unread counts and status |
|
|
219
|
+
| `telegram-read-topic-messages` | Read messages from a specific forum topic |
|
|
220
|
+
|
|
183
221
|
### Chat Management
|
|
184
222
|
|
|
185
223
|
| Tool | Description |
|
|
@@ -226,6 +264,23 @@ Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-10012345
|
|
|
226
264
|
| `text` | string | yes | Message text |
|
|
227
265
|
| `replyTo` | number | no | Message ID to reply to |
|
|
228
266
|
| `parseMode` | `"md"` / `"html"` | no | Message formatting mode |
|
|
267
|
+
| `topicId` | number | no | Forum topic ID to send into (for groups with Topics) |
|
|
268
|
+
|
|
269
|
+
### telegram-list-topics
|
|
270
|
+
|
|
271
|
+
| Parameter | Type | Required | Description |
|
|
272
|
+
|-----------|------|----------|-------------|
|
|
273
|
+
| `chatId` | string | yes | Chat ID or @username (group with Topics enabled) |
|
|
274
|
+
| `limit` | number | no | Max topics to return (default: 100) |
|
|
275
|
+
|
|
276
|
+
### telegram-read-topic-messages
|
|
277
|
+
|
|
278
|
+
| Parameter | Type | Required | Description |
|
|
279
|
+
|-----------|------|----------|-------------|
|
|
280
|
+
| `chatId` | string | yes | Chat ID or @username |
|
|
281
|
+
| `topicId` | number | yes | Topic ID (from `telegram-list-topics`) |
|
|
282
|
+
| `limit` | number | no | Number of messages (default: 20) |
|
|
283
|
+
| `offsetId` | number | no | Message ID for pagination |
|
|
229
284
|
|
|
230
285
|
### telegram-list-chats
|
|
231
286
|
|
|
@@ -426,7 +481,8 @@ src/
|
|
|
426
481
|
## Security
|
|
427
482
|
|
|
428
483
|
- API credentials are stored in `.env` (gitignored)
|
|
429
|
-
- Session is stored in
|
|
484
|
+
- Session is stored in `~/.mcp-telegram/session` with `0600` permissions (owner-only access)
|
|
485
|
+
- Session directory is created with `0700` permissions
|
|
430
486
|
- Phone number is **not required** -- QR-only authentication
|
|
431
487
|
- This is a **userbot** (personal account), not a bot -- respect the [Telegram Terms of Service](https://core.telegram.org/api/terms)
|
|
432
488
|
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// Redirect console.log to stderr BEFORE any imports.
|
|
3
|
+
// GramJS Logger uses console.log (stdout) which corrupts MCP JSON-RPC stream.
|
|
4
|
+
const _origLog = console.log;
|
|
5
|
+
console.log = (...args) => {
|
|
6
|
+
console.error(...args);
|
|
7
|
+
};
|
|
2
8
|
import "dotenv/config";
|
|
3
9
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -104,13 +110,15 @@ server.tool("telegram-send-message", "Send a message to a Telegram chat", {
|
|
|
104
110
|
text: z.string().describe("Message text"),
|
|
105
111
|
replyTo: z.number().optional().describe("Message ID to reply to"),
|
|
106
112
|
parseMode: z.enum(["md", "html"]).optional().describe("Message format: md (Markdown) or html"),
|
|
107
|
-
|
|
113
|
+
topicId: z.number().optional().describe("Forum topic ID to send message into (for groups with Topics enabled)"),
|
|
114
|
+
}, async ({ chatId, text, replyTo, parseMode, topicId }) => {
|
|
108
115
|
const err = await requireConnection();
|
|
109
116
|
if (err)
|
|
110
117
|
return { content: [{ type: "text", text: err }] };
|
|
111
118
|
try {
|
|
112
|
-
await telegram.sendMessage(chatId, text, replyTo, parseMode);
|
|
113
|
-
|
|
119
|
+
await telegram.sendMessage(chatId, text, replyTo, parseMode, topicId);
|
|
120
|
+
const dest = topicId ? `topic ${topicId} in ${chatId}` : chatId;
|
|
121
|
+
return { content: [{ type: "text", text: `Message sent to ${dest}` }] };
|
|
114
122
|
}
|
|
115
123
|
catch (e) {
|
|
116
124
|
return { content: [{ type: "text", text: `Send error: ${e.message}` }] };
|
|
@@ -217,7 +225,13 @@ server.tool("telegram-get-unread", "Get unread Telegram chats", {
|
|
|
217
225
|
const prefix = d.type === "group" ? "G" : d.type === "channel" ? "C" : "P";
|
|
218
226
|
const botTag = d.isBot ? " [bot]" : "";
|
|
219
227
|
const contactTag = d.type === "private" && d.isContact === false ? " [not in contacts]" : "";
|
|
220
|
-
|
|
228
|
+
const forumTag = d.forum ? " [forum]" : "";
|
|
229
|
+
let line = `${prefix} ${d.name} (${d.id})${botTag}${contactTag}${forumTag} [${d.unreadCount} unread]`;
|
|
230
|
+
if (d.topics && d.topics.length > 0) {
|
|
231
|
+
const topicLines = d.topics.map((t) => ` # ${t.title} [${t.unreadCount} unread]`);
|
|
232
|
+
line += `\n${topicLines.join("\n")}`;
|
|
233
|
+
}
|
|
234
|
+
return line;
|
|
221
235
|
})
|
|
222
236
|
.join("\n");
|
|
223
237
|
return { content: [{ type: "text", text: text || "No unread chats" }] };
|
|
@@ -303,6 +317,7 @@ server.tool("telegram-get-chat-info", "Get detailed info about a Telegram chat",
|
|
|
303
317
|
`Name: ${info.name}`,
|
|
304
318
|
`ID: ${info.id}`,
|
|
305
319
|
`Type: ${info.type}`,
|
|
320
|
+
...(info.forum ? ["Forum: yes"] : []),
|
|
306
321
|
...(info.username ? [`Username: @${info.username}`] : []),
|
|
307
322
|
...(info.description ? [`Description: ${info.description}`] : []),
|
|
308
323
|
...(info.membersCount != null ? [`Members: ${info.membersCount}`] : []),
|
|
@@ -594,6 +609,49 @@ server.tool("telegram-report-spam", "Report a chat as spam to Telegram", {
|
|
|
594
609
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
595
610
|
}
|
|
596
611
|
});
|
|
612
|
+
server.tool("telegram-list-topics", "List forum topics in a Telegram group with Topics enabled. Shows topic names, unread counts, and status", {
|
|
613
|
+
chatId: z.string().describe("Chat ID or username of a group with Topics enabled"),
|
|
614
|
+
limit: z.number().default(100).describe("Max topics to return"),
|
|
615
|
+
}, async ({ chatId, limit }) => {
|
|
616
|
+
const err = await requireConnection();
|
|
617
|
+
if (err)
|
|
618
|
+
return { content: [{ type: "text", text: err }] };
|
|
619
|
+
try {
|
|
620
|
+
const topics = await telegram.getForumTopics(chatId, limit);
|
|
621
|
+
const text = topics
|
|
622
|
+
.map((t) => {
|
|
623
|
+
const flags = [t.pinned ? "pinned" : "", t.closed ? "closed" : ""].filter(Boolean).join(", ");
|
|
624
|
+
const flagStr = flags ? ` [${flags}]` : "";
|
|
625
|
+
const unread = t.unreadCount > 0 ? ` [${t.unreadCount} unread]` : "";
|
|
626
|
+
return `# ${t.title} (id: ${t.id})${flagStr}${unread}`;
|
|
627
|
+
})
|
|
628
|
+
.join("\n");
|
|
629
|
+
return { content: [{ type: "text", text: text || "No topics found" }] };
|
|
630
|
+
}
|
|
631
|
+
catch (e) {
|
|
632
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
server.tool("telegram-read-topic-messages", "Read messages from a specific forum topic in a Telegram group", {
|
|
636
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
637
|
+
topicId: z.number().describe("Topic ID (get from telegram-list-topics)"),
|
|
638
|
+
limit: z.number().default(20).describe("Number of messages to return"),
|
|
639
|
+
offsetId: z.number().optional().describe("Message ID to start from (for pagination)"),
|
|
640
|
+
}, async ({ chatId, topicId, limit, offsetId }) => {
|
|
641
|
+
const err = await requireConnection();
|
|
642
|
+
if (err)
|
|
643
|
+
return { content: [{ type: "text", text: err }] };
|
|
644
|
+
try {
|
|
645
|
+
const messages = await telegram.getTopicMessages(chatId, topicId, limit, offsetId);
|
|
646
|
+
const text = messages
|
|
647
|
+
.map((m) => `[${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}`)
|
|
648
|
+
.join("\n\n");
|
|
649
|
+
return { content: [{ type: "text", text: text || "No messages in this topic" }] };
|
|
650
|
+
}
|
|
651
|
+
catch (e) {
|
|
652
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
653
|
+
}
|
|
654
|
+
});
|
|
597
655
|
// --- Start ---
|
|
598
656
|
async function main() {
|
|
599
657
|
// Try to auto-connect with saved session
|
|
@@ -36,7 +36,7 @@ export declare class TelegramService {
|
|
|
36
36
|
username?: string;
|
|
37
37
|
firstName?: string;
|
|
38
38
|
}>;
|
|
39
|
-
sendMessage(chatId: string, text: string, replyTo?: number, parseMode?: "md" | "html"): Promise<void>;
|
|
39
|
+
sendMessage(chatId: string, text: string, replyTo?: number, parseMode?: "md" | "html", topicId?: number): Promise<void>;
|
|
40
40
|
sendFile(chatId: string, filePath: string, caption?: string): Promise<void>;
|
|
41
41
|
downloadMedia(chatId: string, messageId: number, downloadPath: string): Promise<string>;
|
|
42
42
|
downloadMediaAsBuffer(chatId: string, messageId: number): Promise<{
|
|
@@ -62,6 +62,12 @@ export declare class TelegramService {
|
|
|
62
62
|
unreadCount: number;
|
|
63
63
|
isBot?: boolean;
|
|
64
64
|
isContact?: boolean;
|
|
65
|
+
forum?: boolean;
|
|
66
|
+
topics?: Array<{
|
|
67
|
+
id: number;
|
|
68
|
+
title: string;
|
|
69
|
+
unreadCount: number;
|
|
70
|
+
}>;
|
|
65
71
|
}>>;
|
|
66
72
|
getContactRequests(limit?: number): Promise<Array<{
|
|
67
73
|
id: string;
|
|
@@ -88,6 +94,7 @@ export declare class TelegramService {
|
|
|
88
94
|
membersCount?: number;
|
|
89
95
|
isBot?: boolean;
|
|
90
96
|
isContact?: boolean;
|
|
97
|
+
forum?: boolean;
|
|
91
98
|
}>;
|
|
92
99
|
/** Extract media info from a message */
|
|
93
100
|
private extractMediaInfo;
|
|
@@ -148,6 +155,28 @@ export declare class TelegramService {
|
|
|
148
155
|
quiz?: boolean;
|
|
149
156
|
correctAnswer?: number;
|
|
150
157
|
}): Promise<number>;
|
|
158
|
+
getForumTopics(chatId: string, limit?: number): Promise<Array<{
|
|
159
|
+
id: number;
|
|
160
|
+
title: string;
|
|
161
|
+
unreadCount: number;
|
|
162
|
+
unreadMentions: number;
|
|
163
|
+
iconColor: number;
|
|
164
|
+
closed: boolean;
|
|
165
|
+
pinned: boolean;
|
|
166
|
+
}>>;
|
|
167
|
+
getTopicMessages(chatId: string, topicId: number, limit?: number, offsetId?: number): Promise<Array<{
|
|
168
|
+
id: number;
|
|
169
|
+
text: string;
|
|
170
|
+
sender: string;
|
|
171
|
+
date: string;
|
|
172
|
+
media?: {
|
|
173
|
+
type: string;
|
|
174
|
+
fileName?: string;
|
|
175
|
+
size?: number;
|
|
176
|
+
};
|
|
177
|
+
}>>;
|
|
178
|
+
/** Check if a chat entity is a forum (has topics enabled) */
|
|
179
|
+
isForum(chatId: string): Promise<boolean>;
|
|
151
180
|
joinChat(target: string): Promise<{
|
|
152
181
|
id: string;
|
|
153
182
|
title: string;
|
package/dist/telegram-client.js
CHANGED
|
@@ -273,14 +273,29 @@ export class TelegramService {
|
|
|
273
273
|
firstName: user.firstName ?? undefined,
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
|
-
async sendMessage(chatId, text, replyTo, parseMode) {
|
|
276
|
+
async sendMessage(chatId, text, replyTo, parseMode, topicId) {
|
|
277
277
|
if (!this.client || !this.connected)
|
|
278
278
|
throw new Error("Not connected");
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
279
|
+
if (topicId) {
|
|
280
|
+
// Forum topics require raw API call with InputReplyToMessage
|
|
281
|
+
const peer = await this.client.getInputEntity(chatId);
|
|
282
|
+
await this.client.invoke(new Api.messages.SendMessage({
|
|
283
|
+
peer,
|
|
284
|
+
message: text,
|
|
285
|
+
randomId: bigInt(Math.floor(Math.random() * 1e15)),
|
|
286
|
+
replyTo: new Api.InputReplyToMessage({
|
|
287
|
+
replyToMsgId: replyTo ?? topicId,
|
|
288
|
+
topMsgId: topicId,
|
|
289
|
+
}),
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
await this.client.sendMessage(chatId, {
|
|
294
|
+
message: text,
|
|
295
|
+
...(replyTo ? { replyTo } : {}),
|
|
296
|
+
...(parseMode ? { parseMode: parseMode === "html" ? "html" : "md" } : {}),
|
|
297
|
+
});
|
|
298
|
+
}
|
|
284
299
|
}
|
|
285
300
|
async sendFile(chatId, filePath, caption) {
|
|
286
301
|
if (!this.client || !this.connected)
|
|
@@ -374,12 +389,11 @@ export class TelegramService {
|
|
|
374
389
|
if (!this.client || !this.connected)
|
|
375
390
|
throw new Error("Not connected");
|
|
376
391
|
const dialogs = await this.client.getDialogs({ limit: limit * 3 });
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
.slice(0, limit)
|
|
380
|
-
.map((d) => {
|
|
392
|
+
const unread = dialogs.filter((d) => d.unreadCount > 0).slice(0, limit);
|
|
393
|
+
const results = await Promise.all(unread.map(async (d) => {
|
|
381
394
|
const isUser = d.entity instanceof Api.User;
|
|
382
|
-
|
|
395
|
+
const isForum = d.entity instanceof Api.Channel && Boolean(d.entity.forum);
|
|
396
|
+
const base = {
|
|
383
397
|
id: d.id?.toString() ?? "",
|
|
384
398
|
name: d.title ?? d.name ?? "Unknown",
|
|
385
399
|
type: d.isGroup ? "group" : d.isChannel ? "channel" : "private",
|
|
@@ -388,7 +402,29 @@ export class TelegramService {
|
|
|
388
402
|
? { isBot: Boolean(d.entity.bot), isContact: Boolean(d.entity.contact) }
|
|
389
403
|
: {}),
|
|
390
404
|
};
|
|
391
|
-
|
|
405
|
+
if (isForum) {
|
|
406
|
+
try {
|
|
407
|
+
const forumTopics = await this.getForumTopics(d.id?.toString() ?? "");
|
|
408
|
+
const unreadTopics = forumTopics
|
|
409
|
+
.filter((t) => t.unreadCount > 0)
|
|
410
|
+
.map((t) => ({ id: t.id, title: t.title, unreadCount: t.unreadCount }));
|
|
411
|
+
const realUnread = unreadTopics.reduce((sum, t) => sum + t.unreadCount, 0);
|
|
412
|
+
if (realUnread === 0)
|
|
413
|
+
return null;
|
|
414
|
+
return {
|
|
415
|
+
...base,
|
|
416
|
+
unreadCount: realUnread,
|
|
417
|
+
forum: true,
|
|
418
|
+
topics: unreadTopics.length > 0 ? unreadTopics : undefined,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return { ...base, forum: true };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return base;
|
|
426
|
+
}));
|
|
427
|
+
return results.filter((r) => r !== null);
|
|
392
428
|
}
|
|
393
429
|
async getContactRequests(limit = 20) {
|
|
394
430
|
if (!this.client || !this.connected)
|
|
@@ -493,6 +529,7 @@ export class TelegramService {
|
|
|
493
529
|
username: entity.username ?? undefined,
|
|
494
530
|
description,
|
|
495
531
|
membersCount,
|
|
532
|
+
forum: Boolean(entity.forum) || undefined,
|
|
496
533
|
};
|
|
497
534
|
}
|
|
498
535
|
if (entity instanceof Api.Chat) {
|
|
@@ -769,6 +806,75 @@ export class TelegramService {
|
|
|
769
806
|
}
|
|
770
807
|
return 0;
|
|
771
808
|
}
|
|
809
|
+
async getForumTopics(chatId, limit = 100) {
|
|
810
|
+
if (!this.client || !this.connected)
|
|
811
|
+
throw new Error("Not connected");
|
|
812
|
+
const entity = await this.client.getEntity(chatId);
|
|
813
|
+
if (!(entity instanceof Api.Channel))
|
|
814
|
+
throw new Error("Forum topics are only available in supergroups");
|
|
815
|
+
const result = await this.client.invoke(new Api.channels.GetForumTopics({
|
|
816
|
+
channel: entity,
|
|
817
|
+
limit,
|
|
818
|
+
offsetTopic: 0,
|
|
819
|
+
offsetDate: 0,
|
|
820
|
+
offsetId: 0,
|
|
821
|
+
}));
|
|
822
|
+
const topics = [];
|
|
823
|
+
for (const topic of result.topics) {
|
|
824
|
+
if (topic instanceof Api.ForumTopic) {
|
|
825
|
+
topics.push({
|
|
826
|
+
id: topic.id,
|
|
827
|
+
title: topic.title,
|
|
828
|
+
unreadCount: topic.unreadCount,
|
|
829
|
+
unreadMentions: topic.unreadMentionsCount,
|
|
830
|
+
iconColor: topic.iconColor,
|
|
831
|
+
closed: Boolean(topic.closed),
|
|
832
|
+
pinned: Boolean(topic.pinned),
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return topics;
|
|
837
|
+
}
|
|
838
|
+
async getTopicMessages(chatId, topicId, limit = 20, offsetId) {
|
|
839
|
+
if (!this.client || !this.connected)
|
|
840
|
+
throw new Error("Not connected");
|
|
841
|
+
const peer = await this.client.getInputEntity(chatId);
|
|
842
|
+
const result = await this.client.invoke(new Api.messages.GetReplies({
|
|
843
|
+
peer,
|
|
844
|
+
msgId: topicId,
|
|
845
|
+
limit,
|
|
846
|
+
...(offsetId ? { offsetId } : {}),
|
|
847
|
+
offsetDate: 0,
|
|
848
|
+
addOffset: 0,
|
|
849
|
+
maxId: 0,
|
|
850
|
+
minId: 0,
|
|
851
|
+
hash: bigInt(0),
|
|
852
|
+
}));
|
|
853
|
+
const messages = "messages" in result ? result.messages : [];
|
|
854
|
+
const results = await Promise.all(messages
|
|
855
|
+
.filter((m) => m instanceof Api.Message)
|
|
856
|
+
.map(async (m) => ({
|
|
857
|
+
id: m.id,
|
|
858
|
+
text: m.message ?? "",
|
|
859
|
+
sender: await this.resolveSenderName(m.senderId),
|
|
860
|
+
date: new Date((m.date ?? 0) * 1000).toISOString(),
|
|
861
|
+
media: this.extractMediaInfo(m.media),
|
|
862
|
+
})));
|
|
863
|
+
return results;
|
|
864
|
+
}
|
|
865
|
+
/** Check if a chat entity is a forum (has topics enabled) */
|
|
866
|
+
async isForum(chatId) {
|
|
867
|
+
if (!this.client || !this.connected)
|
|
868
|
+
throw new Error("Not connected");
|
|
869
|
+
try {
|
|
870
|
+
const entity = await this.client.getEntity(chatId);
|
|
871
|
+
if (entity instanceof Api.Channel) {
|
|
872
|
+
return Boolean(entity.forum);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
catch { }
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
772
878
|
async joinChat(target) {
|
|
773
879
|
if (!this.client)
|
|
774
880
|
throw new Error("Not connected");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overpod/mcp-telegram",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
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",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"zod": "^4.3.6"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@biomejs/biome": "^2.4.
|
|
59
|
+
"@biomejs/biome": "^2.4.8",
|
|
60
60
|
"@types/node": "^25.5.0",
|
|
61
61
|
"@types/qrcode": "^1.5.6",
|
|
62
62
|
"tsx": "^4.21.0",
|