@overpod/mcp-telegram 1.10.1 → 1.11.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/dist/index.js +51 -10
- package/dist/telegram-client.d.ts +38 -1
- package/dist/telegram-client.js +113 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,6 +18,13 @@ if (!API_ID || !API_HASH) {
|
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
20
|
const telegram = new TelegramService(API_ID, API_HASH);
|
|
21
|
+
/** Format reactions array into compact text like: [👍×5 ❤️×3(me) 🔥×1] */
|
|
22
|
+
function formatReactions(reactions) {
|
|
23
|
+
if (!reactions?.length)
|
|
24
|
+
return "";
|
|
25
|
+
const parts = reactions.map((r) => `${r.emoji}×${r.count}${r.me ? "(me)" : ""}`);
|
|
26
|
+
return ` [${parts.join(" ")}]`;
|
|
27
|
+
}
|
|
21
28
|
const server = new McpServer({
|
|
22
29
|
name: "mcp-telegram",
|
|
23
30
|
version: "1.0.0",
|
|
@@ -165,7 +172,7 @@ server.tool("telegram-read-messages", "Read recent messages from a Telegram chat
|
|
|
165
172
|
try {
|
|
166
173
|
const messages = await telegram.getMessages(chatId, limit, offsetId, minDate, maxDate);
|
|
167
174
|
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}` : ""}]` : ""}`)
|
|
175
|
+
.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
176
|
.join("\n\n");
|
|
170
177
|
return { content: [{ type: "text", text: text || "No messages" }] };
|
|
171
178
|
}
|
|
@@ -203,7 +210,7 @@ server.tool("telegram-search-global", "Search messages globally across all publi
|
|
|
203
210
|
try {
|
|
204
211
|
const messages = await telegram.searchGlobal(query, limit, minDate, maxDate);
|
|
205
212
|
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}` : ""}]` : ""}`)
|
|
213
|
+
.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
214
|
.join("\n\n");
|
|
208
215
|
return { content: [{ type: "text", text: text || "No messages found" }] };
|
|
209
216
|
}
|
|
@@ -224,7 +231,7 @@ server.tool("telegram-search-messages", "Search messages in a Telegram chat by t
|
|
|
224
231
|
try {
|
|
225
232
|
const messages = await telegram.searchMessages(chatId, query, limit, minDate, maxDate);
|
|
226
233
|
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}` : ""}]` : ""}`)
|
|
234
|
+
.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
235
|
.join("\n\n");
|
|
229
236
|
return { content: [{ type: "text", text: text || "No messages found" }] };
|
|
230
237
|
}
|
|
@@ -529,23 +536,57 @@ server.tool("telegram-join-chat", "Join a Telegram group or channel by username
|
|
|
529
536
|
};
|
|
530
537
|
}
|
|
531
538
|
});
|
|
532
|
-
server.tool("telegram-send-reaction", "Send
|
|
539
|
+
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
540
|
chatId: z.string().describe("Chat ID or username"),
|
|
534
541
|
messageId: z.number().describe("Message ID to react to"),
|
|
535
|
-
emoji: z
|
|
536
|
-
|
|
542
|
+
emoji: z
|
|
543
|
+
.union([z.string(), z.array(z.string())])
|
|
544
|
+
.optional()
|
|
545
|
+
.describe("Reaction emoji(s): single '👍' or array ['👍','🔥']. Omit to remove all reactions"),
|
|
546
|
+
addToExisting: z
|
|
547
|
+
.boolean()
|
|
548
|
+
.default(false)
|
|
549
|
+
.describe("If true, add reaction(s) to existing ones instead of replacing"),
|
|
550
|
+
}, async ({ chatId, messageId, emoji, addToExisting }) => {
|
|
537
551
|
const err = await requireConnection();
|
|
538
552
|
if (err)
|
|
539
553
|
return { content: [{ type: "text", text: err }] };
|
|
540
554
|
try {
|
|
541
|
-
await telegram.sendReaction(chatId, messageId, emoji);
|
|
542
|
-
const
|
|
543
|
-
|
|
555
|
+
const updated = await telegram.sendReaction(chatId, messageId, emoji, addToExisting);
|
|
556
|
+
const emojiStr = Array.isArray(emoji) ? emoji.join("") : emoji;
|
|
557
|
+
const action = emoji ? `Reacted ${emojiStr} to` : "Removed reactions from";
|
|
558
|
+
const reactionsInfo = updated
|
|
559
|
+
? ` | Reactions: ${updated.map((r) => `${r.emoji}×${r.count}${r.me ? "(me)" : ""}`).join(" ")}`
|
|
560
|
+
: "";
|
|
561
|
+
return { content: [{ type: "text", text: `${action} message ${messageId} in ${chatId}${reactionsInfo}` }] };
|
|
544
562
|
}
|
|
545
563
|
catch (e) {
|
|
546
564
|
return { content: [{ type: "text", text: `Reaction error: ${e.message}` }] };
|
|
547
565
|
}
|
|
548
566
|
});
|
|
567
|
+
server.tool("telegram-get-reactions", "Get detailed reaction info for a message: which reactions, counts, and who reacted (when visible)", {
|
|
568
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
569
|
+
messageId: z.number().describe("Message ID to get reactions for"),
|
|
570
|
+
}, async ({ chatId, messageId }) => {
|
|
571
|
+
const err = await requireConnection();
|
|
572
|
+
if (err)
|
|
573
|
+
return { content: [{ type: "text", text: err }] };
|
|
574
|
+
try {
|
|
575
|
+
const result = await telegram.getMessageReactions(chatId, messageId);
|
|
576
|
+
if (result.reactions.length === 0) {
|
|
577
|
+
return { content: [{ type: "text", text: `No reactions on message ${messageId}` }] };
|
|
578
|
+
}
|
|
579
|
+
const lines = result.reactions.map((r) => {
|
|
580
|
+
const usersStr = r.users.length > 0 ? `: ${r.users.map((u) => u.name).join(", ")}` : "";
|
|
581
|
+
return `${r.emoji} × ${r.count}${usersStr}`;
|
|
582
|
+
});
|
|
583
|
+
lines.push(`\nTotal: ${result.total} reactions`);
|
|
584
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
585
|
+
}
|
|
586
|
+
catch (e) {
|
|
587
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
588
|
+
}
|
|
589
|
+
});
|
|
549
590
|
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
591
|
chatId: z.string().describe("Chat ID or username (use 'me' or 'self' for Saved Messages)"),
|
|
551
592
|
text: z.string().describe("Message text"),
|
|
@@ -703,7 +744,7 @@ server.tool("telegram-read-topic-messages", "Read messages from a specific forum
|
|
|
703
744
|
try {
|
|
704
745
|
const messages = await telegram.getTopicMessages(chatId, topicId, limit, offsetId);
|
|
705
746
|
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}` : ""}]` : ""}`)
|
|
747
|
+
.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
748
|
.join("\n\n");
|
|
708
749
|
return { content: [{ type: "text", text: text || "No messages in this topic" }] };
|
|
709
750
|
}
|
|
@@ -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