@overpod/mcp-telegram 1.1.1 → 1.3.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/dist/cli.d.ts +2 -0
- package/dist/cli.js +0 -0
- package/dist/index.d.ts +2 -0
- package/dist/qr-login-cli.d.ts +2 -0
- package/dist/telegram-client.d.ts +122 -0
- package/dist/telegram-client.js +100 -4
- package/package.json +5 -1
package/dist/cli.d.ts
ADDED
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export declare class TelegramService {
|
|
2
|
+
private client;
|
|
3
|
+
private apiId;
|
|
4
|
+
private apiHash;
|
|
5
|
+
private sessionString;
|
|
6
|
+
private connected;
|
|
7
|
+
lastError: string;
|
|
8
|
+
constructor(apiId: number, apiHash: string);
|
|
9
|
+
loadSession(): Promise<boolean>;
|
|
10
|
+
/** Set session string in memory (for programmatic / hosted use) */
|
|
11
|
+
setSessionString(session: string): void;
|
|
12
|
+
/** Get the current session string (for external persistence) */
|
|
13
|
+
getSessionString(): string;
|
|
14
|
+
private saveSession;
|
|
15
|
+
connect(): Promise<boolean>;
|
|
16
|
+
clearSession(): Promise<void>;
|
|
17
|
+
/** Ensure connection is active, auto-reconnect if session exists */
|
|
18
|
+
ensureConnected(): Promise<boolean>;
|
|
19
|
+
disconnect(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Log out from Telegram completely — terminates the session on Telegram servers.
|
|
22
|
+
* After this, the session string becomes invalid and won't appear in "Active Sessions".
|
|
23
|
+
*/
|
|
24
|
+
logOut(): Promise<boolean>;
|
|
25
|
+
isConnected(): boolean;
|
|
26
|
+
startQrLogin(onQrDataUrl: (dataUrl: string) => void, onQrUrl?: (url: string) => void): Promise<{
|
|
27
|
+
success: boolean;
|
|
28
|
+
message: string;
|
|
29
|
+
}>;
|
|
30
|
+
getMe(): Promise<{
|
|
31
|
+
id: string;
|
|
32
|
+
username?: string;
|
|
33
|
+
firstName?: string;
|
|
34
|
+
}>;
|
|
35
|
+
sendMessage(chatId: string, text: string, replyTo?: number, parseMode?: "md" | "html"): Promise<void>;
|
|
36
|
+
sendFile(chatId: string, filePath: string, caption?: string): Promise<void>;
|
|
37
|
+
downloadMedia(chatId: string, messageId: number, downloadPath: string): Promise<string>;
|
|
38
|
+
downloadMediaAsBuffer(chatId: string, messageId: number): Promise<{
|
|
39
|
+
buffer: Buffer;
|
|
40
|
+
mimeType: string;
|
|
41
|
+
}>;
|
|
42
|
+
/** Detect MIME type from buffer magic bytes, falling back to media metadata */
|
|
43
|
+
private detectMimeType;
|
|
44
|
+
pinMessage(chatId: string, messageId: number, silent?: boolean): Promise<void>;
|
|
45
|
+
unpinMessage(chatId: string, messageId: number): Promise<void>;
|
|
46
|
+
getDialogs(limit?: number, offsetDate?: number, filterType?: "private" | "group" | "channel"): Promise<Array<{
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
type: string;
|
|
50
|
+
unreadCount: number;
|
|
51
|
+
}>>;
|
|
52
|
+
getUnreadDialogs(limit?: number): Promise<Array<{
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
type: string;
|
|
56
|
+
unreadCount: number;
|
|
57
|
+
}>>;
|
|
58
|
+
markAsRead(chatId: string): Promise<void>;
|
|
59
|
+
forwardMessage(fromChatId: string, toChatId: string, messageIds: number[]): Promise<void>;
|
|
60
|
+
editMessage(chatId: string, messageId: number, newText: string): Promise<void>;
|
|
61
|
+
deleteMessages(chatId: string, messageIds: number[]): Promise<void>;
|
|
62
|
+
getChatInfo(chatId: string): Promise<{
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
type: string;
|
|
66
|
+
username?: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
membersCount?: number;
|
|
69
|
+
}>;
|
|
70
|
+
/** Extract media info from a message */
|
|
71
|
+
private extractMediaInfo;
|
|
72
|
+
/** Resolve sender ID to a display name */
|
|
73
|
+
private resolveSenderName;
|
|
74
|
+
getMessages(chatId: string, limit?: number, offsetId?: number, minDate?: number, maxDate?: number): Promise<Array<{
|
|
75
|
+
id: number;
|
|
76
|
+
text: string;
|
|
77
|
+
sender: string;
|
|
78
|
+
date: string;
|
|
79
|
+
media?: {
|
|
80
|
+
type: string;
|
|
81
|
+
fileName?: string;
|
|
82
|
+
size?: number;
|
|
83
|
+
};
|
|
84
|
+
}>>;
|
|
85
|
+
searchChats(query: string, limit?: number): Promise<Array<{
|
|
86
|
+
id: string;
|
|
87
|
+
name: string;
|
|
88
|
+
type: string;
|
|
89
|
+
username?: string;
|
|
90
|
+
}>>;
|
|
91
|
+
searchMessages(chatId: string, query: string, limit?: number, minDate?: number, maxDate?: number): Promise<Array<{
|
|
92
|
+
id: number;
|
|
93
|
+
text: string;
|
|
94
|
+
sender: string;
|
|
95
|
+
date: string;
|
|
96
|
+
media?: {
|
|
97
|
+
type: string;
|
|
98
|
+
fileName?: string;
|
|
99
|
+
size?: number;
|
|
100
|
+
};
|
|
101
|
+
}>>;
|
|
102
|
+
getContacts(limit?: number): Promise<Array<{
|
|
103
|
+
id: string;
|
|
104
|
+
name: string;
|
|
105
|
+
username?: string;
|
|
106
|
+
phone?: string;
|
|
107
|
+
}>>;
|
|
108
|
+
getChatMembers(chatId: string, limit?: number): Promise<Array<{
|
|
109
|
+
id: string;
|
|
110
|
+
name: string;
|
|
111
|
+
username?: string;
|
|
112
|
+
}>>;
|
|
113
|
+
getProfile(userId: string): Promise<{
|
|
114
|
+
id: string;
|
|
115
|
+
name: string;
|
|
116
|
+
username?: string;
|
|
117
|
+
phone?: string;
|
|
118
|
+
bio?: string;
|
|
119
|
+
photo: boolean;
|
|
120
|
+
lastSeen?: string;
|
|
121
|
+
}>;
|
|
122
|
+
}
|
package/dist/telegram-client.js
CHANGED
|
@@ -27,9 +27,22 @@ export class TelegramService {
|
|
|
27
27
|
}
|
|
28
28
|
return false;
|
|
29
29
|
}
|
|
30
|
+
/** Set session string in memory (for programmatic / hosted use) */
|
|
31
|
+
setSessionString(session) {
|
|
32
|
+
this.sessionString = session;
|
|
33
|
+
}
|
|
34
|
+
/** Get the current session string (for external persistence) */
|
|
35
|
+
getSessionString() {
|
|
36
|
+
return this.sessionString;
|
|
37
|
+
}
|
|
30
38
|
async saveSession(session) {
|
|
31
39
|
this.sessionString = session;
|
|
32
|
-
|
|
40
|
+
try {
|
|
41
|
+
await writeFile(SESSION_FILE, session, "utf-8");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// File write may fail in containerized environments — session string is still in memory
|
|
45
|
+
}
|
|
33
46
|
}
|
|
34
47
|
async connect() {
|
|
35
48
|
if (this.connected && this.client)
|
|
@@ -96,9 +109,29 @@ export class TelegramService {
|
|
|
96
109
|
}
|
|
97
110
|
async disconnect() {
|
|
98
111
|
if (this.client && this.connected) {
|
|
99
|
-
await this.client.
|
|
112
|
+
await this.client.destroy();
|
|
113
|
+
this.connected = false;
|
|
114
|
+
this.client = null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Log out from Telegram completely — terminates the session on Telegram servers.
|
|
119
|
+
* After this, the session string becomes invalid and won't appear in "Active Sessions".
|
|
120
|
+
*/
|
|
121
|
+
async logOut() {
|
|
122
|
+
if (!this.client || !this.connected)
|
|
123
|
+
return false;
|
|
124
|
+
try {
|
|
125
|
+
await this.client.invoke(new Api.auth.LogOut());
|
|
100
126
|
this.connected = false;
|
|
127
|
+
this.sessionString = "";
|
|
101
128
|
this.client = null;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error("[telegram] logOut error:", error);
|
|
133
|
+
await this.disconnect();
|
|
134
|
+
return false;
|
|
102
135
|
}
|
|
103
136
|
}
|
|
104
137
|
isConnected() {
|
|
@@ -224,6 +257,41 @@ export class TelegramService {
|
|
|
224
257
|
await writeFile(downloadPath, buffer);
|
|
225
258
|
return downloadPath;
|
|
226
259
|
}
|
|
260
|
+
async downloadMediaAsBuffer(chatId, messageId) {
|
|
261
|
+
if (!this.client || !this.connected)
|
|
262
|
+
throw new Error("Not connected");
|
|
263
|
+
const messages = await this.client.getMessages(chatId, { ids: [messageId] });
|
|
264
|
+
const message = messages[0];
|
|
265
|
+
if (!message)
|
|
266
|
+
throw new Error(`Message ${messageId} not found`);
|
|
267
|
+
if (!message.media)
|
|
268
|
+
throw new Error(`Message ${messageId} has no media`);
|
|
269
|
+
const buffer = await this.client.downloadMedia(message);
|
|
270
|
+
if (!buffer)
|
|
271
|
+
throw new Error("Failed to download media");
|
|
272
|
+
const mimeType = this.detectMimeType(buffer, message.media);
|
|
273
|
+
return { buffer, mimeType };
|
|
274
|
+
}
|
|
275
|
+
/** Detect MIME type from buffer magic bytes, falling back to media metadata */
|
|
276
|
+
detectMimeType(buffer, media) {
|
|
277
|
+
// Check magic bytes first
|
|
278
|
+
if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF)
|
|
279
|
+
return "image/jpeg";
|
|
280
|
+
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47)
|
|
281
|
+
return "image/png";
|
|
282
|
+
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46)
|
|
283
|
+
return "image/gif";
|
|
284
|
+
if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46)
|
|
285
|
+
return "image/webp";
|
|
286
|
+
// Fall back to document mimeType
|
|
287
|
+
const m = media;
|
|
288
|
+
const doc = m.document;
|
|
289
|
+
if (doc?.mimeType)
|
|
290
|
+
return doc.mimeType;
|
|
291
|
+
if (m.photo)
|
|
292
|
+
return "image/jpeg";
|
|
293
|
+
return "application/octet-stream";
|
|
294
|
+
}
|
|
227
295
|
async pinMessage(chatId, messageId, silent = false) {
|
|
228
296
|
if (!this.client || !this.connected)
|
|
229
297
|
throw new Error("Not connected");
|
|
@@ -295,20 +363,48 @@ export class TelegramService {
|
|
|
295
363
|
};
|
|
296
364
|
}
|
|
297
365
|
if (entity instanceof Api.Channel) {
|
|
366
|
+
let membersCount = entity.participantsCount ?? undefined;
|
|
367
|
+
let description;
|
|
368
|
+
try {
|
|
369
|
+
const full = await this.client.invoke(new Api.channels.GetFullChannel({ channel: entity }));
|
|
370
|
+
if (full.fullChat instanceof Api.ChannelFull) {
|
|
371
|
+
membersCount = membersCount ?? full.fullChat.participantsCount ?? undefined;
|
|
372
|
+
description = full.fullChat.about || undefined;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// May fail for some channels — fall back to basic info
|
|
377
|
+
}
|
|
298
378
|
return {
|
|
299
379
|
id: entity.id.toString(),
|
|
300
380
|
name: entity.title,
|
|
301
381
|
type: entity.megagroup ? "group" : "channel",
|
|
302
382
|
username: entity.username ?? undefined,
|
|
303
|
-
|
|
383
|
+
description,
|
|
384
|
+
membersCount,
|
|
304
385
|
};
|
|
305
386
|
}
|
|
306
387
|
if (entity instanceof Api.Chat) {
|
|
388
|
+
let membersCount = entity.participantsCount ?? undefined;
|
|
389
|
+
let description;
|
|
390
|
+
try {
|
|
391
|
+
const full = await this.client.invoke(new Api.messages.GetFullChat({ chatId: entity.id }));
|
|
392
|
+
if (full.fullChat instanceof Api.ChatFull) {
|
|
393
|
+
if (!membersCount && full.fullChat.participants instanceof Api.ChatParticipants) {
|
|
394
|
+
membersCount = full.fullChat.participants.participants.length;
|
|
395
|
+
}
|
|
396
|
+
description = full.fullChat.about || undefined;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
// Fall back to basic info
|
|
401
|
+
}
|
|
307
402
|
return {
|
|
308
403
|
id: entity.id.toString(),
|
|
309
404
|
name: entity.title,
|
|
310
405
|
type: "group",
|
|
311
|
-
|
|
406
|
+
description,
|
|
407
|
+
membersCount,
|
|
312
408
|
};
|
|
313
409
|
}
|
|
314
410
|
return { id: chatId, name: "Unknown", type: "unknown" };
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overpod/mcp-telegram",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "MCP server for Telegram userbot — 20 tools for messages, media, contacts & more. Built on GramJS/MTProto.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./service": "./dist/telegram-client.js"
|
|
10
|
+
},
|
|
7
11
|
"bin": {
|
|
8
12
|
"mcp-telegram": "dist/cli.js"
|
|
9
13
|
},
|