@overpod/mcp-telegram 1.8.1 → 1.10.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 +61 -2
- package/dist/telegram-client.d.ts +36 -0
- package/dist/telegram-client.js +152 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -183,7 +183,7 @@ server.tool("telegram-search-chats", "Search for Telegram chats/users/channels b
|
|
|
183
183
|
try {
|
|
184
184
|
const results = await telegram.searchChats(query, limit);
|
|
185
185
|
const text = results
|
|
186
|
-
.map((c) => `${c.type === "group" ? "G" : c.type === "channel" ? "C" : "P"} ${c.name}${c.username ? ` (@${c.username})` : ""} (${c.id})`)
|
|
186
|
+
.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
187
|
.join("\n");
|
|
188
188
|
return { content: [{ type: "text", text: text || "No results" }] };
|
|
189
189
|
}
|
|
@@ -191,6 +191,26 @@ server.tool("telegram-search-chats", "Search for Telegram chats/users/channels b
|
|
|
191
191
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
192
192
|
}
|
|
193
193
|
});
|
|
194
|
+
server.tool("telegram-search-global", "Search messages globally across all public Telegram chats and channels", {
|
|
195
|
+
query: z.string().describe("Search text"),
|
|
196
|
+
limit: z.number().default(20).describe("Max results"),
|
|
197
|
+
minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
|
|
198
|
+
maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
|
|
199
|
+
}, async ({ query, limit, minDate, maxDate }) => {
|
|
200
|
+
const err = await requireConnection();
|
|
201
|
+
if (err)
|
|
202
|
+
return { content: [{ type: "text", text: err }] };
|
|
203
|
+
try {
|
|
204
|
+
const messages = await telegram.searchGlobal(query, limit, minDate, maxDate);
|
|
205
|
+
const text = messages
|
|
206
|
+
.map((m) => `[${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}` : ""}]` : ""}`)
|
|
207
|
+
.join("\n\n");
|
|
208
|
+
return { content: [{ type: "text", text: text || "No messages found" }] };
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
212
|
+
}
|
|
213
|
+
});
|
|
194
214
|
server.tool("telegram-search-messages", "Search messages in a Telegram chat by text", {
|
|
195
215
|
chatId: z.string().describe("Chat ID or username"),
|
|
196
216
|
query: z.string().describe("Search text"),
|
|
@@ -424,7 +444,7 @@ server.tool("telegram-get-chat-members", "Get members of a Telegram group or cha
|
|
|
424
444
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
425
445
|
}
|
|
426
446
|
});
|
|
427
|
-
server.tool("telegram-get-profile", "Get detailed profile info of a Telegram user", {
|
|
447
|
+
server.tool("telegram-get-profile", "Get detailed profile info of a Telegram user including bio, birthday, business info and more", {
|
|
428
448
|
userId: z.string().describe("User ID or username"),
|
|
429
449
|
}, async ({ userId }) => {
|
|
430
450
|
const err = await requireConnection();
|
|
@@ -439,7 +459,13 @@ server.tool("telegram-get-profile", "Get detailed profile info of a Telegram use
|
|
|
439
459
|
...(profile.phone ? [`Phone: +${profile.phone}`] : []),
|
|
440
460
|
...(profile.bio ? [`Bio: ${profile.bio}`] : []),
|
|
441
461
|
`Photo: ${profile.photo ? "yes" : "no"}`,
|
|
462
|
+
...(profile.premium ? ["Premium: yes"] : []),
|
|
442
463
|
...(profile.lastSeen ? [`Last seen: ${profile.lastSeen}`] : []),
|
|
464
|
+
...(profile.birthday ? [`Birthday: ${profile.birthday}`] : []),
|
|
465
|
+
...(profile.commonChatsCount ? [`Common chats: ${profile.commonChatsCount}`] : []),
|
|
466
|
+
...(profile.personalChannelId ? [`Personal channel ID: ${profile.personalChannelId}`] : []),
|
|
467
|
+
...(profile.businessLocation ? [`Business location: ${profile.businessLocation}`] : []),
|
|
468
|
+
...(profile.businessWorkHours ? [`Business hours timezone: ${profile.businessWorkHours}`] : []),
|
|
443
469
|
];
|
|
444
470
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
445
471
|
}
|
|
@@ -447,6 +473,39 @@ server.tool("telegram-get-profile", "Get detailed profile info of a Telegram use
|
|
|
447
473
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
448
474
|
}
|
|
449
475
|
});
|
|
476
|
+
server.tool("telegram-get-profile-photo", "Download profile photo of a Telegram user, group, or channel. Returns inline image or saves to file", {
|
|
477
|
+
entityId: z.string().describe("User/Chat/Channel ID or username"),
|
|
478
|
+
savePath: z.string().optional().describe("Absolute path to save file. If omitted, returns inline base64 image"),
|
|
479
|
+
size: z
|
|
480
|
+
.enum(["small", "big"])
|
|
481
|
+
.optional()
|
|
482
|
+
.describe("Photo size: 'small' (160x160) or 'big' (640x640). Default: big"),
|
|
483
|
+
}, async ({ entityId, savePath, size }) => {
|
|
484
|
+
const err = await requireConnection();
|
|
485
|
+
if (err)
|
|
486
|
+
return { content: [{ type: "text", text: err }] };
|
|
487
|
+
try {
|
|
488
|
+
const result = await telegram.downloadProfilePhoto(entityId, {
|
|
489
|
+
isBig: size !== "small",
|
|
490
|
+
savePath,
|
|
491
|
+
});
|
|
492
|
+
if (!result) {
|
|
493
|
+
return { content: [{ type: "text", text: "No profile photo found" }] };
|
|
494
|
+
}
|
|
495
|
+
if ("filePath" in result) {
|
|
496
|
+
return { content: [{ type: "text", text: `Downloaded to: ${result.filePath}` }] };
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
content: [
|
|
500
|
+
{ type: "image", data: result.buffer.toString("base64"), mimeType: result.mimeType },
|
|
501
|
+
{ type: "text", text: `Profile photo (${(result.buffer.length / 1024).toFixed(0)} KB, ${result.mimeType})` },
|
|
502
|
+
],
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
catch (e) {
|
|
506
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
507
|
+
}
|
|
508
|
+
});
|
|
450
509
|
server.tool("telegram-join-chat", "Join a Telegram group or channel by username or invite link", {
|
|
451
510
|
target: z.string().describe("Username (@group), link (t.me/group), or invite link (t.me/+xxx)"),
|
|
452
511
|
}, async ({ target }) => {
|
|
@@ -116,6 +116,25 @@ export declare class TelegramService {
|
|
|
116
116
|
name: string;
|
|
117
117
|
type: string;
|
|
118
118
|
username?: string;
|
|
119
|
+
membersCount?: number;
|
|
120
|
+
description?: string;
|
|
121
|
+
}>>;
|
|
122
|
+
searchGlobal(query: string, limit?: number, minDate?: number, maxDate?: number): Promise<Array<{
|
|
123
|
+
id: number;
|
|
124
|
+
text: string;
|
|
125
|
+
sender: string;
|
|
126
|
+
date: string;
|
|
127
|
+
chat: {
|
|
128
|
+
id: string;
|
|
129
|
+
name: string;
|
|
130
|
+
type: string;
|
|
131
|
+
username?: string;
|
|
132
|
+
};
|
|
133
|
+
media?: {
|
|
134
|
+
type: string;
|
|
135
|
+
fileName?: string;
|
|
136
|
+
size?: number;
|
|
137
|
+
};
|
|
119
138
|
}>>;
|
|
120
139
|
searchMessages(chatId: string, query: string, limit?: number, minDate?: number, maxDate?: number): Promise<Array<{
|
|
121
140
|
id: number;
|
|
@@ -147,7 +166,24 @@ export declare class TelegramService {
|
|
|
147
166
|
bio?: string;
|
|
148
167
|
photo: boolean;
|
|
149
168
|
lastSeen?: string;
|
|
169
|
+
premium?: boolean;
|
|
170
|
+
birthday?: string;
|
|
171
|
+
commonChatsCount?: number;
|
|
172
|
+
personalChannelId?: string;
|
|
173
|
+
businessWorkHours?: string;
|
|
174
|
+
businessLocation?: string;
|
|
150
175
|
}>;
|
|
176
|
+
downloadProfilePhoto(entityId: string, options?: {
|
|
177
|
+
isBig?: boolean;
|
|
178
|
+
savePath?: string;
|
|
179
|
+
}): Promise<{
|
|
180
|
+
buffer: Buffer;
|
|
181
|
+
mimeType: string;
|
|
182
|
+
} | {
|
|
183
|
+
filePath: string;
|
|
184
|
+
} | null>;
|
|
185
|
+
/** Detect MIME type from buffer magic bytes */
|
|
186
|
+
private detectMimeFromBuffer;
|
|
151
187
|
sendReaction(chatId: string, messageId: number, emoji?: string): Promise<void>;
|
|
152
188
|
sendScheduledMessage(chatId: string, text: string, scheduleDate: number, replyTo?: number, parseMode?: "md" | "html"): Promise<void>;
|
|
153
189
|
createPoll(chatId: string, question: string, answers: string[], options?: {
|
package/dist/telegram-client.js
CHANGED
|
@@ -642,7 +642,12 @@ export class TelegramService {
|
|
|
642
642
|
}
|
|
643
643
|
for (const chat of result.chats) {
|
|
644
644
|
if (chat instanceof Api.Chat) {
|
|
645
|
-
chats.push({
|
|
645
|
+
chats.push({
|
|
646
|
+
id: chat.id.toString(),
|
|
647
|
+
name: chat.title,
|
|
648
|
+
type: "group",
|
|
649
|
+
membersCount: chat.participantsCount ?? undefined,
|
|
650
|
+
});
|
|
646
651
|
}
|
|
647
652
|
else if (chat instanceof Api.Channel) {
|
|
648
653
|
chats.push({
|
|
@@ -650,11 +655,104 @@ export class TelegramService {
|
|
|
650
655
|
name: chat.title,
|
|
651
656
|
type: chat.megagroup ? "group" : "channel",
|
|
652
657
|
username: chat.username ?? undefined,
|
|
658
|
+
membersCount: chat.participantsCount ?? undefined,
|
|
653
659
|
});
|
|
654
660
|
}
|
|
655
661
|
}
|
|
662
|
+
// Enrich channels/groups with description and accurate members count
|
|
663
|
+
for (const chat of chats) {
|
|
664
|
+
if (chat.type === "private")
|
|
665
|
+
continue;
|
|
666
|
+
try {
|
|
667
|
+
const entity = await this.client.getEntity(chat.id);
|
|
668
|
+
if (entity instanceof Api.Channel) {
|
|
669
|
+
const full = await this.client.invoke(new Api.channels.GetFullChannel({ channel: entity }));
|
|
670
|
+
if (full.fullChat instanceof Api.ChannelFull) {
|
|
671
|
+
chat.description = full.fullChat.about || undefined;
|
|
672
|
+
chat.membersCount = full.fullChat.participantsCount ?? chat.membersCount;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else if (entity instanceof Api.Chat) {
|
|
676
|
+
const full = await this.client.invoke(new Api.messages.GetFullChat({ chatId: entity.id }));
|
|
677
|
+
if (full.fullChat instanceof Api.ChatFull) {
|
|
678
|
+
chat.description = full.fullChat.about || undefined;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
catch {
|
|
683
|
+
// Skip enrichment on error (private channels, etc.)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
656
686
|
return chats;
|
|
657
687
|
}
|
|
688
|
+
async searchGlobal(query, limit = 20, minDate, maxDate) {
|
|
689
|
+
if (!this.client || !this.connected)
|
|
690
|
+
throw new Error("Not connected");
|
|
691
|
+
const result = await this.client.invoke(new Api.messages.SearchGlobal({
|
|
692
|
+
q: query,
|
|
693
|
+
filter: new Api.InputMessagesFilterEmpty(),
|
|
694
|
+
minDate: minDate || 0,
|
|
695
|
+
maxDate: maxDate || 0,
|
|
696
|
+
offsetRate: 0,
|
|
697
|
+
offsetPeer: new Api.InputPeerEmpty(),
|
|
698
|
+
offsetId: 0,
|
|
699
|
+
limit,
|
|
700
|
+
}));
|
|
701
|
+
const chatsMap = new Map();
|
|
702
|
+
if ("chats" in result) {
|
|
703
|
+
for (const chat of result.chats) {
|
|
704
|
+
if (chat instanceof Api.Channel) {
|
|
705
|
+
chatsMap.set(chat.id.toString(), {
|
|
706
|
+
id: chat.id.toString(),
|
|
707
|
+
name: chat.title,
|
|
708
|
+
type: chat.megagroup ? "group" : "channel",
|
|
709
|
+
username: chat.username ?? undefined,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
else if (chat instanceof Api.Chat) {
|
|
713
|
+
chatsMap.set(chat.id.toString(), {
|
|
714
|
+
id: chat.id.toString(),
|
|
715
|
+
name: chat.title,
|
|
716
|
+
type: "group",
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if ("users" in result) {
|
|
722
|
+
for (const user of result.users) {
|
|
723
|
+
if (user instanceof Api.User) {
|
|
724
|
+
const parts = [user.firstName, user.lastName].filter(Boolean);
|
|
725
|
+
chatsMap.set(user.id.toString(), {
|
|
726
|
+
id: user.id.toString(),
|
|
727
|
+
name: parts.join(" ") || "Unknown",
|
|
728
|
+
type: "private",
|
|
729
|
+
username: user.username ?? undefined,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
const rawMessages = "messages" in result ? result.messages : [];
|
|
735
|
+
const messages = rawMessages.filter((m) => m instanceof Api.Message);
|
|
736
|
+
const results = await Promise.all(messages.map(async (m) => {
|
|
737
|
+
const peerId = m.peerId;
|
|
738
|
+
let chatId = "";
|
|
739
|
+
if (peerId instanceof Api.PeerChannel)
|
|
740
|
+
chatId = peerId.channelId.toString();
|
|
741
|
+
else if (peerId instanceof Api.PeerChat)
|
|
742
|
+
chatId = peerId.chatId.toString();
|
|
743
|
+
else if (peerId instanceof Api.PeerUser)
|
|
744
|
+
chatId = peerId.userId.toString();
|
|
745
|
+
return {
|
|
746
|
+
id: m.id,
|
|
747
|
+
text: m.message ?? "",
|
|
748
|
+
sender: await this.resolveSenderName(m.senderId),
|
|
749
|
+
date: new Date((m.date ?? 0) * 1000).toISOString(),
|
|
750
|
+
chat: chatsMap.get(chatId) || { id: chatId, name: "Unknown", type: "unknown" },
|
|
751
|
+
media: this.extractMediaInfo(m.media),
|
|
752
|
+
};
|
|
753
|
+
}));
|
|
754
|
+
return results;
|
|
755
|
+
}
|
|
658
756
|
async searchMessages(chatId, query, limit = 20, minDate, maxDate) {
|
|
659
757
|
if (!this.client || !this.connected)
|
|
660
758
|
throw new Error("Not connected");
|
|
@@ -721,7 +819,8 @@ export class TelegramService {
|
|
|
721
819
|
throw new Error("Entity is not a user");
|
|
722
820
|
const inputEntity = await this.client.getInputEntity(userId);
|
|
723
821
|
const fullResult = await this.client.invoke(new Api.users.GetFullUser({ id: inputEntity }));
|
|
724
|
-
const
|
|
822
|
+
const full = fullResult.fullUser;
|
|
823
|
+
const bio = full.about ?? undefined;
|
|
725
824
|
const parts = [entity.firstName, entity.lastName].filter(Boolean);
|
|
726
825
|
let lastSeen;
|
|
727
826
|
if (entity.status instanceof Api.UserStatusOnline) {
|
|
@@ -739,6 +838,23 @@ export class TelegramService {
|
|
|
739
838
|
else if (entity.status instanceof Api.UserStatusLastMonth) {
|
|
740
839
|
lastSeen = "last month";
|
|
741
840
|
}
|
|
841
|
+
let birthday;
|
|
842
|
+
if (full.birthday) {
|
|
843
|
+
const b = full.birthday;
|
|
844
|
+
birthday = b.year
|
|
845
|
+
? `${b.year}-${String(b.month).padStart(2, "0")}-${String(b.day).padStart(2, "0")}`
|
|
846
|
+
: `${String(b.month).padStart(2, "0")}-${String(b.day).padStart(2, "0")}`;
|
|
847
|
+
}
|
|
848
|
+
let businessWorkHours;
|
|
849
|
+
if (full.businessWorkHours) {
|
|
850
|
+
const wh = full.businessWorkHours;
|
|
851
|
+
businessWorkHours = wh.timezoneId ?? "configured";
|
|
852
|
+
}
|
|
853
|
+
let businessLocation;
|
|
854
|
+
if (full.businessLocation) {
|
|
855
|
+
const loc = full.businessLocation;
|
|
856
|
+
businessLocation = loc.address ?? "configured";
|
|
857
|
+
}
|
|
742
858
|
return {
|
|
743
859
|
id: entity.id.toString(),
|
|
744
860
|
name: parts.join(" ") || "Unknown",
|
|
@@ -747,8 +863,42 @@ export class TelegramService {
|
|
|
747
863
|
bio,
|
|
748
864
|
photo: !!entity.photo,
|
|
749
865
|
lastSeen,
|
|
866
|
+
premium: entity.premium || undefined,
|
|
867
|
+
birthday,
|
|
868
|
+
commonChatsCount: full.commonChatsCount || undefined,
|
|
869
|
+
personalChannelId: full.personalChannelId ? full.personalChannelId.toString() : undefined,
|
|
870
|
+
businessWorkHours,
|
|
871
|
+
businessLocation,
|
|
750
872
|
};
|
|
751
873
|
}
|
|
874
|
+
async downloadProfilePhoto(entityId, options) {
|
|
875
|
+
if (!this.client || !this.connected)
|
|
876
|
+
throw new Error("Not connected");
|
|
877
|
+
const entity = await this.client.getEntity(entityId);
|
|
878
|
+
const buffer = (await this.client.downloadProfilePhoto(entity, {
|
|
879
|
+
isBig: options?.isBig !== false,
|
|
880
|
+
}));
|
|
881
|
+
if (!buffer || buffer.length === 0)
|
|
882
|
+
return null;
|
|
883
|
+
const mimeType = this.detectMimeFromBuffer(buffer);
|
|
884
|
+
if (options?.savePath) {
|
|
885
|
+
await writeFile(options.savePath, buffer);
|
|
886
|
+
return { filePath: options.savePath };
|
|
887
|
+
}
|
|
888
|
+
return { buffer, mimeType };
|
|
889
|
+
}
|
|
890
|
+
/** Detect MIME type from buffer magic bytes */
|
|
891
|
+
detectMimeFromBuffer(buffer) {
|
|
892
|
+
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff)
|
|
893
|
+
return "image/jpeg";
|
|
894
|
+
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47)
|
|
895
|
+
return "image/png";
|
|
896
|
+
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46)
|
|
897
|
+
return "image/gif";
|
|
898
|
+
if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46)
|
|
899
|
+
return "image/webp";
|
|
900
|
+
return "image/jpeg"; // Telegram profile photos are almost always JPEG
|
|
901
|
+
}
|
|
752
902
|
async sendReaction(chatId, messageId, emoji) {
|
|
753
903
|
if (!this.client || !this.connected)
|
|
754
904
|
throw new Error("Not connected");
|
package/package.json
CHANGED