@overpod/mcp-telegram 1.10.1 → 1.11.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 +30 -2
- package/dist/index.js +66 -21
- package/dist/telegram-client.d.ts +38 -1
- package/dist/telegram-client.js +113 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@overpod/mcp-telegram)
|
|
4
4
|
[](https://www.npmjs.com/package/@overpod/mcp-telegram)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://www.typescriptlang.org/)
|
|
7
7
|
[](https://modelcontextprotocol.io/)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://glama.ai/mcp/servers/overpod/mcp-telegram)
|
|
@@ -208,6 +208,7 @@ const telegramMcp = new MCPClient({
|
|
|
208
208
|
| `telegram-read-messages` | Read recent messages from a chat |
|
|
209
209
|
| `telegram-search-chats` | Search for chats, users, or channels by name |
|
|
210
210
|
| `telegram-search-messages` | Search messages in a chat by text |
|
|
211
|
+
| `telegram-search-global` | Search messages across all public chats and channels |
|
|
211
212
|
| `telegram-get-unread` | Get chats with unread messages; forums show per-topic unread breakdown |
|
|
212
213
|
| `telegram-get-contact-requests` | Get incoming messages from non-contacts with preview |
|
|
213
214
|
|
|
@@ -243,6 +244,8 @@ const telegramMcp = new MCPClient({
|
|
|
243
244
|
|------|-------------|
|
|
244
245
|
| `telegram-get-contacts` | Get your contacts list with phone numbers |
|
|
245
246
|
| `telegram-get-profile` | Get detailed profile info for a user (bio, photo, last seen) |
|
|
247
|
+
| `telegram-get-profile-photo` | Get a user's or chat's profile photo (inline or file) |
|
|
248
|
+
| `telegram-get-reactions` | Get reactions on a specific message with user details |
|
|
246
249
|
|
|
247
250
|
### Media
|
|
248
251
|
|
|
@@ -357,7 +360,8 @@ Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-10012345
|
|
|
357
360
|
|-----------|------|----------|-------------|
|
|
358
361
|
| `chatId` | string | yes | Chat ID or @username |
|
|
359
362
|
| `messageId` | number | yes | Message ID to react to |
|
|
360
|
-
| `emoji` | string | no |
|
|
363
|
+
| `emoji` | string \| string[] | no | Single emoji `"👍"` or array `["👍","🔥"]`. Omit to remove all reactions |
|
|
364
|
+
| `addToExisting` | boolean | no | Add to existing reactions instead of replacing (default: false) |
|
|
361
365
|
|
|
362
366
|
### telegram-send-scheduled
|
|
363
367
|
|
|
@@ -388,6 +392,15 @@ Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-10012345
|
|
|
388
392
|
| `query` | string | yes | Search text |
|
|
389
393
|
| `limit` | number | no | Max results (default: 20) |
|
|
390
394
|
|
|
395
|
+
### telegram-search-global
|
|
396
|
+
|
|
397
|
+
| Parameter | Type | Required | Description |
|
|
398
|
+
|-----------|------|----------|-------------|
|
|
399
|
+
| `query` | string | yes | Search text |
|
|
400
|
+
| `limit` | number | no | Max results (default: 20) |
|
|
401
|
+
| `minDate` | number | no | Unix timestamp: only messages after this date |
|
|
402
|
+
| `maxDate` | number | no | Unix timestamp: only messages before this date |
|
|
403
|
+
|
|
391
404
|
### telegram-search-chats
|
|
392
405
|
|
|
393
406
|
| Parameter | Type | Required | Description |
|
|
@@ -414,6 +427,21 @@ Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-10012345
|
|
|
414
427
|
|-----------|------|----------|-------------|
|
|
415
428
|
| `userId` | string | yes | User ID or @username |
|
|
416
429
|
|
|
430
|
+
### telegram-get-profile-photo
|
|
431
|
+
|
|
432
|
+
| Parameter | Type | Required | Description |
|
|
433
|
+
|-----------|------|----------|-------------|
|
|
434
|
+
| `entityId` | string | yes | User/Chat/Channel ID or username |
|
|
435
|
+
| `savePath` | string | no | Absolute path to save file. If omitted, returns inline base64 image |
|
|
436
|
+
| `size` | `"small"` / `"big"` | no | Photo size: small (160x160) or big (640x640). Default: big |
|
|
437
|
+
|
|
438
|
+
### telegram-get-reactions
|
|
439
|
+
|
|
440
|
+
| Parameter | Type | Required | Description |
|
|
441
|
+
|-----------|------|----------|-------------|
|
|
442
|
+
| `chatId` | string | yes | Chat ID or @username |
|
|
443
|
+
| `messageId` | number | yes | Message ID to get reactions for |
|
|
444
|
+
|
|
417
445
|
### telegram-get-unread
|
|
418
446
|
|
|
419
447
|
| Parameter | Type | Required | Description |
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,17 @@ if (!API_ID || !API_HASH) {
|
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
20
|
const telegram = new TelegramService(API_ID, API_HASH);
|
|
21
|
+
/** Remove unpaired UTF-16 surrogates that break JSON serialization */
|
|
22
|
+
function sanitize(text) {
|
|
23
|
+
return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
24
|
+
}
|
|
25
|
+
/** Format reactions array into compact text like: [👍×5 ❤️×3(me) 🔥×1] */
|
|
26
|
+
function formatReactions(reactions) {
|
|
27
|
+
if (!reactions?.length)
|
|
28
|
+
return "";
|
|
29
|
+
const parts = reactions.map((r) => `${r.emoji}×${r.count}${r.me ? "(me)" : ""}`);
|
|
30
|
+
return ` [${parts.join(" ")}]`;
|
|
31
|
+
}
|
|
21
32
|
const server = new McpServer({
|
|
22
33
|
name: "mcp-telegram",
|
|
23
34
|
version: "1.0.0",
|
|
@@ -146,7 +157,7 @@ server.tool("telegram-list-chats", "List Telegram chats", {
|
|
|
146
157
|
return `${prefix} ${d.name} (${d.id})${botTag}${contactTag}${unread}`;
|
|
147
158
|
})
|
|
148
159
|
.join("\n");
|
|
149
|
-
return { content: [{ type: "text", text: text || "No chats" }] };
|
|
160
|
+
return { content: [{ type: "text", text: sanitize(text) || "No chats" }] };
|
|
150
161
|
}
|
|
151
162
|
catch (e) {
|
|
152
163
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -165,9 +176,9 @@ server.tool("telegram-read-messages", "Read recent messages from a Telegram chat
|
|
|
165
176
|
try {
|
|
166
177
|
const messages = await telegram.getMessages(chatId, limit, offsetId, minDate, maxDate);
|
|
167
178
|
const text = messages
|
|
168
|
-
.map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}`)
|
|
179
|
+
.map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
|
|
169
180
|
.join("\n\n");
|
|
170
|
-
return { content: [{ type: "text", text: text || "No messages" }] };
|
|
181
|
+
return { content: [{ type: "text", text: sanitize(text) || "No messages" }] };
|
|
171
182
|
}
|
|
172
183
|
catch (e) {
|
|
173
184
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -185,7 +196,7 @@ server.tool("telegram-search-chats", "Search for Telegram chats/users/channels b
|
|
|
185
196
|
const text = results
|
|
186
197
|
.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)}` : ""}`)
|
|
187
198
|
.join("\n");
|
|
188
|
-
return { content: [{ type: "text", text: text || "No results" }] };
|
|
199
|
+
return { content: [{ type: "text", text: sanitize(text) || "No results" }] };
|
|
189
200
|
}
|
|
190
201
|
catch (e) {
|
|
191
202
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -203,9 +214,9 @@ server.tool("telegram-search-global", "Search messages globally across all publi
|
|
|
203
214
|
try {
|
|
204
215
|
const messages = await telegram.searchGlobal(query, limit, minDate, maxDate);
|
|
205
216
|
const text = messages
|
|
206
|
-
.map((m) => `[#${m.id}] [${m.date}] [${m.chat.type === "channel" ? "C" : m.chat.type === "group" ? "G" : "P"} ${m.chat.name}${m.chat.username ? ` @${m.chat.username}` : ""}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}`)
|
|
217
|
+
.map((m) => `[#${m.id}] [${m.date}] [${m.chat.type === "channel" ? "C" : m.chat.type === "group" ? "G" : "P"} ${m.chat.name}${m.chat.username ? ` @${m.chat.username}` : ""}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
|
|
207
218
|
.join("\n\n");
|
|
208
|
-
return { content: [{ type: "text", text: text || "No messages found" }] };
|
|
219
|
+
return { content: [{ type: "text", text: sanitize(text) || "No messages found" }] };
|
|
209
220
|
}
|
|
210
221
|
catch (e) {
|
|
211
222
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -224,9 +235,9 @@ server.tool("telegram-search-messages", "Search messages in a Telegram chat by t
|
|
|
224
235
|
try {
|
|
225
236
|
const messages = await telegram.searchMessages(chatId, query, limit, minDate, maxDate);
|
|
226
237
|
const text = messages
|
|
227
|
-
.map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}`)
|
|
238
|
+
.map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
|
|
228
239
|
.join("\n\n");
|
|
229
|
-
return { content: [{ type: "text", text: text || "No messages found" }] };
|
|
240
|
+
return { content: [{ type: "text", text: sanitize(text) || "No messages found" }] };
|
|
230
241
|
}
|
|
231
242
|
catch (e) {
|
|
232
243
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -254,7 +265,7 @@ server.tool("telegram-get-unread", "Get unread Telegram chats", {
|
|
|
254
265
|
return line;
|
|
255
266
|
})
|
|
256
267
|
.join("\n");
|
|
257
|
-
return { content: [{ type: "text", text: text || "No unread chats" }] };
|
|
268
|
+
return { content: [{ type: "text", text: sanitize(text) || "No unread chats" }] };
|
|
258
269
|
}
|
|
259
270
|
catch (e) {
|
|
260
271
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -422,7 +433,7 @@ server.tool("telegram-get-contacts", "Get Telegram contacts list", {
|
|
|
422
433
|
const text = contacts
|
|
423
434
|
.map((c) => `P ${c.name}${c.username ? ` (@${c.username})` : ""} (${c.id})${c.phone ? ` +${c.phone}` : ""}`)
|
|
424
435
|
.join("\n");
|
|
425
|
-
return { content: [{ type: "text", text: text || "No contacts" }] };
|
|
436
|
+
return { content: [{ type: "text", text: sanitize(text) || "No contacts" }] };
|
|
426
437
|
}
|
|
427
438
|
catch (e) {
|
|
428
439
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -438,7 +449,7 @@ server.tool("telegram-get-chat-members", "Get members of a Telegram group or cha
|
|
|
438
449
|
try {
|
|
439
450
|
const members = await telegram.getChatMembers(chatId, limit);
|
|
440
451
|
const text = members.map((m) => `${m.name}${m.username ? ` (@${m.username})` : ""} (${m.id})`).join("\n");
|
|
441
|
-
return { content: [{ type: "text", text: text || "No members found" }] };
|
|
452
|
+
return { content: [{ type: "text", text: sanitize(text) || "No members found" }] };
|
|
442
453
|
}
|
|
443
454
|
catch (e) {
|
|
444
455
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -529,23 +540,57 @@ server.tool("telegram-join-chat", "Join a Telegram group or channel by username
|
|
|
529
540
|
};
|
|
530
541
|
}
|
|
531
542
|
});
|
|
532
|
-
server.tool("telegram-send-reaction", "Send
|
|
543
|
+
server.tool("telegram-send-reaction", "Send emoji reaction(s) to a message. Supports multiple reactions and adding to existing ones. Omit emoji to remove all reactions", {
|
|
533
544
|
chatId: z.string().describe("Chat ID or username"),
|
|
534
545
|
messageId: z.number().describe("Message ID to react to"),
|
|
535
|
-
emoji: z
|
|
536
|
-
|
|
546
|
+
emoji: z
|
|
547
|
+
.union([z.string(), z.array(z.string())])
|
|
548
|
+
.optional()
|
|
549
|
+
.describe("Reaction emoji(s): single '👍' or array ['👍','🔥']. Omit to remove all reactions"),
|
|
550
|
+
addToExisting: z
|
|
551
|
+
.boolean()
|
|
552
|
+
.default(false)
|
|
553
|
+
.describe("If true, add reaction(s) to existing ones instead of replacing"),
|
|
554
|
+
}, async ({ chatId, messageId, emoji, addToExisting }) => {
|
|
537
555
|
const err = await requireConnection();
|
|
538
556
|
if (err)
|
|
539
557
|
return { content: [{ type: "text", text: err }] };
|
|
540
558
|
try {
|
|
541
|
-
await telegram.sendReaction(chatId, messageId, emoji);
|
|
542
|
-
const
|
|
543
|
-
|
|
559
|
+
const updated = await telegram.sendReaction(chatId, messageId, emoji, addToExisting);
|
|
560
|
+
const emojiStr = Array.isArray(emoji) ? emoji.join("") : emoji;
|
|
561
|
+
const action = emoji ? `Reacted ${emojiStr} to` : "Removed reactions from";
|
|
562
|
+
const reactionsInfo = updated
|
|
563
|
+
? ` | Reactions: ${updated.map((r) => `${r.emoji}×${r.count}${r.me ? "(me)" : ""}`).join(" ")}`
|
|
564
|
+
: "";
|
|
565
|
+
return { content: [{ type: "text", text: `${action} message ${messageId} in ${chatId}${reactionsInfo}` }] };
|
|
544
566
|
}
|
|
545
567
|
catch (e) {
|
|
546
568
|
return { content: [{ type: "text", text: `Reaction error: ${e.message}` }] };
|
|
547
569
|
}
|
|
548
570
|
});
|
|
571
|
+
server.tool("telegram-get-reactions", "Get detailed reaction info for a message: which reactions, counts, and who reacted (when visible)", {
|
|
572
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
573
|
+
messageId: z.number().describe("Message ID to get reactions for"),
|
|
574
|
+
}, async ({ chatId, messageId }) => {
|
|
575
|
+
const err = await requireConnection();
|
|
576
|
+
if (err)
|
|
577
|
+
return { content: [{ type: "text", text: err }] };
|
|
578
|
+
try {
|
|
579
|
+
const result = await telegram.getMessageReactions(chatId, messageId);
|
|
580
|
+
if (result.reactions.length === 0) {
|
|
581
|
+
return { content: [{ type: "text", text: `No reactions on message ${messageId}` }] };
|
|
582
|
+
}
|
|
583
|
+
const lines = result.reactions.map((r) => {
|
|
584
|
+
const usersStr = r.users.length > 0 ? `: ${r.users.map((u) => u.name).join(", ")}` : "";
|
|
585
|
+
return `${r.emoji} × ${r.count}${usersStr}`;
|
|
586
|
+
});
|
|
587
|
+
lines.push(`\nTotal: ${result.total} reactions`);
|
|
588
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
589
|
+
}
|
|
590
|
+
catch (e) {
|
|
591
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
592
|
+
}
|
|
593
|
+
});
|
|
549
594
|
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", {
|
|
550
595
|
chatId: z.string().describe("Chat ID or username (use 'me' or 'self' for Saved Messages)"),
|
|
551
596
|
text: z.string().describe("Message text"),
|
|
@@ -615,7 +660,7 @@ server.tool("telegram-get-contact-requests", "Get incoming messages from non-con
|
|
|
615
660
|
return `${tag} ${r.name}${username} (${r.id})${unread}${preview}`;
|
|
616
661
|
})
|
|
617
662
|
.join("\n");
|
|
618
|
-
return { content: [{ type: "text", text: text }] };
|
|
663
|
+
return { content: [{ type: "text", text: sanitize(text) }] };
|
|
619
664
|
}
|
|
620
665
|
catch (e) {
|
|
621
666
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -685,7 +730,7 @@ server.tool("telegram-list-topics", "List forum topics in a Telegram group with
|
|
|
685
730
|
return `# ${t.title} (id: ${t.id})${flagStr}${unread}`;
|
|
686
731
|
})
|
|
687
732
|
.join("\n");
|
|
688
|
-
return { content: [{ type: "text", text: text || "No topics found" }] };
|
|
733
|
+
return { content: [{ type: "text", text: sanitize(text) || "No topics found" }] };
|
|
689
734
|
}
|
|
690
735
|
catch (e) {
|
|
691
736
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -703,9 +748,9 @@ server.tool("telegram-read-topic-messages", "Read messages from a specific forum
|
|
|
703
748
|
try {
|
|
704
749
|
const messages = await telegram.getTopicMessages(chatId, topicId, limit, offsetId);
|
|
705
750
|
const text = messages
|
|
706
|
-
.map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}`)
|
|
751
|
+
.map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
|
|
707
752
|
.join("\n\n");
|
|
708
|
-
return { content: [{ type: "text", text: text || "No messages in this topic" }] };
|
|
753
|
+
return { content: [{ type: "text", text: sanitize(text) || "No messages in this topic" }] };
|
|
709
754
|
}
|
|
710
755
|
catch (e) {
|
|
711
756
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
@@ -110,6 +110,11 @@ export declare class TelegramService {
|
|
|
110
110
|
fileName?: string;
|
|
111
111
|
size?: number;
|
|
112
112
|
};
|
|
113
|
+
reactions?: {
|
|
114
|
+
emoji: string;
|
|
115
|
+
count: number;
|
|
116
|
+
me: boolean;
|
|
117
|
+
}[];
|
|
113
118
|
}>>;
|
|
114
119
|
searchChats(query: string, limit?: number): Promise<Array<{
|
|
115
120
|
id: string;
|
|
@@ -135,6 +140,11 @@ export declare class TelegramService {
|
|
|
135
140
|
fileName?: string;
|
|
136
141
|
size?: number;
|
|
137
142
|
};
|
|
143
|
+
reactions?: {
|
|
144
|
+
emoji: string;
|
|
145
|
+
count: number;
|
|
146
|
+
me: boolean;
|
|
147
|
+
}[];
|
|
138
148
|
}>>;
|
|
139
149
|
searchMessages(chatId: string, query: string, limit?: number, minDate?: number, maxDate?: number): Promise<Array<{
|
|
140
150
|
id: number;
|
|
@@ -146,6 +156,11 @@ export declare class TelegramService {
|
|
|
146
156
|
fileName?: string;
|
|
147
157
|
size?: number;
|
|
148
158
|
};
|
|
159
|
+
reactions?: {
|
|
160
|
+
emoji: string;
|
|
161
|
+
count: number;
|
|
162
|
+
me: boolean;
|
|
163
|
+
}[];
|
|
149
164
|
}>>;
|
|
150
165
|
getContacts(limit?: number): Promise<Array<{
|
|
151
166
|
id: string;
|
|
@@ -184,7 +199,24 @@ export declare class TelegramService {
|
|
|
184
199
|
} | null>;
|
|
185
200
|
/** Detect MIME type from buffer magic bytes */
|
|
186
201
|
private detectMimeFromBuffer;
|
|
187
|
-
|
|
202
|
+
/** Extract reactions from a message into a simple format */
|
|
203
|
+
private extractReactions;
|
|
204
|
+
sendReaction(chatId: string, messageId: number, emoji?: string | string[], addToExisting?: boolean): Promise<{
|
|
205
|
+
emoji: string;
|
|
206
|
+
count: number;
|
|
207
|
+
me: boolean;
|
|
208
|
+
}[] | undefined>;
|
|
209
|
+
getMessageReactions(chatId: string, messageId: number): Promise<{
|
|
210
|
+
reactions: {
|
|
211
|
+
emoji: string;
|
|
212
|
+
count: number;
|
|
213
|
+
users: {
|
|
214
|
+
id: string;
|
|
215
|
+
name: string;
|
|
216
|
+
}[];
|
|
217
|
+
}[];
|
|
218
|
+
total: number;
|
|
219
|
+
}>;
|
|
188
220
|
sendScheduledMessage(chatId: string, text: string, scheduleDate: number, replyTo?: number, parseMode?: "md" | "html"): Promise<void>;
|
|
189
221
|
createPoll(chatId: string, question: string, answers: string[], options?: {
|
|
190
222
|
multipleChoice?: boolean;
|
|
@@ -210,6 +242,11 @@ export declare class TelegramService {
|
|
|
210
242
|
fileName?: string;
|
|
211
243
|
size?: number;
|
|
212
244
|
};
|
|
245
|
+
reactions?: {
|
|
246
|
+
emoji: string;
|
|
247
|
+
count: number;
|
|
248
|
+
me: boolean;
|
|
249
|
+
}[];
|
|
213
250
|
}>>;
|
|
214
251
|
/** Check if a chat entity is a forum (has topics enabled) */
|
|
215
252
|
isForum(chatId: string): Promise<boolean>;
|
package/dist/telegram-client.js
CHANGED
|
@@ -621,6 +621,7 @@ export class TelegramService {
|
|
|
621
621
|
sender: await this.resolveSenderName(m.senderId),
|
|
622
622
|
date: new Date((m.date ?? 0) * 1000).toISOString(),
|
|
623
623
|
media: this.extractMediaInfo(m.media),
|
|
624
|
+
reactions: this.extractReactions(m.reactions),
|
|
624
625
|
})));
|
|
625
626
|
return results;
|
|
626
627
|
}
|
|
@@ -749,6 +750,7 @@ export class TelegramService {
|
|
|
749
750
|
date: new Date((m.date ?? 0) * 1000).toISOString(),
|
|
750
751
|
chat: chatsMap.get(chatId) || { id: chatId, name: "Unknown", type: "unknown" },
|
|
751
752
|
media: this.extractMediaInfo(m.media),
|
|
753
|
+
reactions: this.extractReactions(m.reactions),
|
|
752
754
|
};
|
|
753
755
|
}));
|
|
754
756
|
return results;
|
|
@@ -771,6 +773,7 @@ export class TelegramService {
|
|
|
771
773
|
sender: await this.resolveSenderName(m.senderId),
|
|
772
774
|
date: new Date((m.date ?? 0) * 1000).toISOString(),
|
|
773
775
|
media: this.extractMediaInfo(m.media),
|
|
776
|
+
reactions: this.extractReactions(m.reactions),
|
|
774
777
|
})));
|
|
775
778
|
return results;
|
|
776
779
|
}
|
|
@@ -899,16 +902,121 @@ export class TelegramService {
|
|
|
899
902
|
return "image/webp";
|
|
900
903
|
return "image/jpeg"; // Telegram profile photos are almost always JPEG
|
|
901
904
|
}
|
|
902
|
-
|
|
905
|
+
/** Extract reactions from a message into a simple format */
|
|
906
|
+
extractReactions(reactions) {
|
|
907
|
+
if (!reactions?.results?.length)
|
|
908
|
+
return undefined;
|
|
909
|
+
const items = [];
|
|
910
|
+
for (const r of reactions.results) {
|
|
911
|
+
let emoji;
|
|
912
|
+
if (r.reaction instanceof Api.ReactionEmoji) {
|
|
913
|
+
emoji = r.reaction.emoticon;
|
|
914
|
+
}
|
|
915
|
+
else if (r.reaction instanceof Api.ReactionCustomEmoji) {
|
|
916
|
+
emoji = `custom:${r.reaction.documentId}`;
|
|
917
|
+
}
|
|
918
|
+
else if (r.reaction instanceof Api.ReactionPaid) {
|
|
919
|
+
emoji = "⭐";
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
items.push({ emoji, count: r.count, me: r.chosenOrder != null });
|
|
925
|
+
}
|
|
926
|
+
return items.length > 0 ? items : undefined;
|
|
927
|
+
}
|
|
928
|
+
async sendReaction(chatId, messageId, emoji, addToExisting = false) {
|
|
903
929
|
if (!this.client || !this.connected)
|
|
904
930
|
throw new Error("Not connected");
|
|
905
931
|
const peer = await this.client.getInputEntity(chatId);
|
|
906
|
-
const
|
|
907
|
-
|
|
932
|
+
const reactionList = [];
|
|
933
|
+
if (emoji) {
|
|
934
|
+
const emojis = Array.isArray(emoji) ? emoji : [emoji];
|
|
935
|
+
if (addToExisting) {
|
|
936
|
+
// Fetch current reactions to preserve them
|
|
937
|
+
const msgs = await this.client.getMessages(chatId, { ids: [messageId] });
|
|
938
|
+
const msg = msgs[0];
|
|
939
|
+
if (msg?.reactions?.results) {
|
|
940
|
+
for (const r of msg.reactions.results) {
|
|
941
|
+
if (r.chosenOrder != null) {
|
|
942
|
+
reactionList.push(r.reaction);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
for (const e of emojis) {
|
|
948
|
+
reactionList.push(new Api.ReactionEmoji({ emoticon: e }));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
// empty array = remove all reactions
|
|
952
|
+
const result = await this.client.invoke(new Api.messages.SendReaction({
|
|
908
953
|
peer,
|
|
909
954
|
msgId: messageId,
|
|
910
|
-
reaction,
|
|
955
|
+
reaction: reactionList,
|
|
911
956
|
}));
|
|
957
|
+
// Extract updated reactions from the response
|
|
958
|
+
if ("updates" in result) {
|
|
959
|
+
for (const upd of result.updates) {
|
|
960
|
+
if (upd instanceof Api.UpdateMessageReactions) {
|
|
961
|
+
return this.extractReactions(upd.reactions);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return undefined;
|
|
966
|
+
}
|
|
967
|
+
async getMessageReactions(chatId, messageId) {
|
|
968
|
+
if (!this.client || !this.connected)
|
|
969
|
+
throw new Error("Not connected");
|
|
970
|
+
const peer = await this.client.getInputEntity(chatId);
|
|
971
|
+
// First get the message to know which reactions exist
|
|
972
|
+
const msgs = await this.client.getMessages(chatId, { ids: [messageId] });
|
|
973
|
+
const msg = msgs[0];
|
|
974
|
+
if (!msg?.reactions?.results?.length) {
|
|
975
|
+
return { reactions: [], total: 0 };
|
|
976
|
+
}
|
|
977
|
+
const reactionsOut = [];
|
|
978
|
+
for (const rc of msg.reactions.results) {
|
|
979
|
+
let emoji;
|
|
980
|
+
if (rc.reaction instanceof Api.ReactionEmoji) {
|
|
981
|
+
emoji = rc.reaction.emoticon;
|
|
982
|
+
}
|
|
983
|
+
else if (rc.reaction instanceof Api.ReactionCustomEmoji) {
|
|
984
|
+
emoji = `custom:${rc.reaction.documentId}`;
|
|
985
|
+
}
|
|
986
|
+
else if (rc.reaction instanceof Api.ReactionPaid) {
|
|
987
|
+
emoji = "⭐";
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
const users = [];
|
|
993
|
+
// Try to get the list of users who reacted (may fail if canSeeList is false)
|
|
994
|
+
if (msg.reactions.canSeeList) {
|
|
995
|
+
try {
|
|
996
|
+
const list = await this.client.invoke(new Api.messages.GetMessageReactionsList({
|
|
997
|
+
peer,
|
|
998
|
+
id: messageId,
|
|
999
|
+
reaction: rc.reaction,
|
|
1000
|
+
limit: 50,
|
|
1001
|
+
}));
|
|
1002
|
+
if (list instanceof Api.messages.MessageReactionsList) {
|
|
1003
|
+
for (const r of list.reactions) {
|
|
1004
|
+
const userId = r.peerId instanceof Api.PeerUser ? r.peerId.userId.toString() : "";
|
|
1005
|
+
if (userId) {
|
|
1006
|
+
const name = await this.resolveSenderName(bigInt(Number.parseInt(userId)));
|
|
1007
|
+
users.push({ id: userId, name });
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
catch {
|
|
1013
|
+
// canSeeList may be false or request may fail for channels
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
reactionsOut.push({ emoji, count: rc.count, users });
|
|
1017
|
+
}
|
|
1018
|
+
const total = reactionsOut.reduce((sum, r) => sum + r.count, 0);
|
|
1019
|
+
return { reactions: reactionsOut, total };
|
|
912
1020
|
}
|
|
913
1021
|
async sendScheduledMessage(chatId, text, scheduleDate, replyTo, parseMode) {
|
|
914
1022
|
if (!this.client || !this.connected)
|
|
@@ -1009,6 +1117,7 @@ export class TelegramService {
|
|
|
1009
1117
|
sender: await this.resolveSenderName(m.senderId),
|
|
1010
1118
|
date: new Date((m.date ?? 0) * 1000).toISOString(),
|
|
1011
1119
|
media: this.extractMediaInfo(m.media),
|
|
1120
|
+
reactions: this.extractReactions(m.reactions),
|
|
1012
1121
|
})));
|
|
1013
1122
|
return results;
|
|
1014
1123
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overpod/mcp-telegram",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.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",
|
|
@@ -60,6 +60,6 @@
|
|
|
60
60
|
"@types/node": "^25.5.0",
|
|
61
61
|
"@types/qrcode": "^1.5.6",
|
|
62
62
|
"tsx": "^4.21.0",
|
|
63
|
-
"typescript": "^
|
|
63
|
+
"typescript": "^6.0.2"
|
|
64
64
|
}
|
|
65
65
|
}
|