@overpod/mcp-telegram 1.16.0 → 1.18.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/README.md +14 -294
- package/dist/telegram-client.d.ts +18 -0
- package/dist/telegram-client.js +177 -38
- package/dist/tools/chats.js +24 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -204,302 +204,13 @@ const telegramMcp = new MCPClient({
|
|
|
204
204
|
});
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
-
##
|
|
207
|
+
## Tools
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
| Tool | Description |
|
|
212
|
-
|------|-------------|
|
|
213
|
-
| `telegram-status` | Check connection status and current account info |
|
|
214
|
-
| `telegram-login` | Authenticate via QR code |
|
|
209
|
+
All tools are auto-discoverable via MCP — your AI client will see the full list with parameters and descriptions when connected.
|
|
215
210
|
|
|
216
|
-
|
|
211
|
+
**Categories:** authentication, messaging (send, edit, delete, forward, schedule, polls), reading (chats, messages, search, unread), forum topics, group management (create, edit, invite, kick, ban, admin, leave), contacts & moderation, user profiles, reactions, media.
|
|
217
212
|
|
|
218
|
-
|
|
219
|
-
|------|-------------|
|
|
220
|
-
| `telegram-send-message` | Send a text message to a chat |
|
|
221
|
-
| `telegram-send-file` | Send a file (photo, document, video, etc.) to a chat |
|
|
222
|
-
| `telegram-send-reaction` | Send or remove an emoji reaction on a message |
|
|
223
|
-
| `telegram-send-scheduled` | Schedule a message for future delivery |
|
|
224
|
-
| `telegram-create-poll` | Create a poll (multiple choice or quiz mode) |
|
|
225
|
-
| `telegram-edit-message` | Edit a previously sent message |
|
|
226
|
-
| `telegram-delete-message` | Delete messages in a chat |
|
|
227
|
-
| `telegram-forward-message` | Forward messages between chats |
|
|
228
|
-
|
|
229
|
-
### Reading
|
|
230
|
-
|
|
231
|
-
| Tool | Description |
|
|
232
|
-
|------|-------------|
|
|
233
|
-
| `telegram-list-chats` | List recent dialogs with unread counts, bot/contact markers |
|
|
234
|
-
| `telegram-read-messages` | Read recent messages from a chat |
|
|
235
|
-
| `telegram-search-chats` | Search for chats, users, or channels by name |
|
|
236
|
-
| `telegram-search-messages` | Search messages in a chat by text |
|
|
237
|
-
| `telegram-search-global` | Search messages across all public chats and channels |
|
|
238
|
-
| `telegram-get-unread` | Get chats with unread messages; forums show per-topic unread breakdown |
|
|
239
|
-
| `telegram-get-contact-requests` | Get incoming messages from non-contacts with preview |
|
|
240
|
-
|
|
241
|
-
### Forum Topics
|
|
242
|
-
|
|
243
|
-
| Tool | Description |
|
|
244
|
-
|------|-------------|
|
|
245
|
-
| `telegram-list-topics` | List forum topics in a group with unread counts and status |
|
|
246
|
-
| `telegram-read-topic-messages` | Read messages from a specific forum topic |
|
|
247
|
-
|
|
248
|
-
### Chat Management
|
|
249
|
-
|
|
250
|
-
| Tool | Description |
|
|
251
|
-
|------|-------------|
|
|
252
|
-
| `telegram-mark-as-read` | Mark a chat as read |
|
|
253
|
-
| `telegram-get-chat-info` | Get detailed info about a chat (name, type, members, bot/contact status) |
|
|
254
|
-
| `telegram-get-chat-members` | List members of a group or channel |
|
|
255
|
-
| `telegram-join-chat` | Join a group or channel by username or invite link |
|
|
256
|
-
| `telegram-pin-message` | Pin a message in a chat |
|
|
257
|
-
| `telegram-unpin-message` | Unpin a message in a chat |
|
|
258
|
-
|
|
259
|
-
### Contacts & Moderation
|
|
260
|
-
|
|
261
|
-
| Tool | Description |
|
|
262
|
-
|------|-------------|
|
|
263
|
-
| `telegram-add-contact` | Add a user to your contacts (accept contact request) |
|
|
264
|
-
| `telegram-block-user` | Block a user from sending you messages |
|
|
265
|
-
| `telegram-report-spam` | Report a chat as spam to Telegram |
|
|
266
|
-
|
|
267
|
-
### User Info
|
|
268
|
-
|
|
269
|
-
| Tool | Description |
|
|
270
|
-
|------|-------------|
|
|
271
|
-
| `telegram-get-contacts` | Get your contacts list with phone numbers |
|
|
272
|
-
| `telegram-get-profile` | Get detailed profile info for a user (bio, photo, last seen) |
|
|
273
|
-
| `telegram-get-profile-photo` | Get a user's or chat's profile photo (inline or file) |
|
|
274
|
-
| `telegram-get-reactions` | Get reactions on a specific message with user details |
|
|
275
|
-
|
|
276
|
-
### Media
|
|
277
|
-
|
|
278
|
-
| Tool | Description |
|
|
279
|
-
|------|-------------|
|
|
280
|
-
| `telegram-download-media` | Download media from a message to a local file |
|
|
281
|
-
|
|
282
|
-
## Tool Parameters
|
|
283
|
-
|
|
284
|
-
### Common patterns
|
|
285
|
-
|
|
286
|
-
Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-1001234567890"`) or a username (e.g., `"@username"`).
|
|
287
|
-
|
|
288
|
-
### telegram-send-message
|
|
289
|
-
|
|
290
|
-
| Parameter | Type | Required | Description |
|
|
291
|
-
|-----------|------|----------|-------------|
|
|
292
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
293
|
-
| `text` | string | yes | Message text |
|
|
294
|
-
| `replyTo` | number | no | Message ID to reply to |
|
|
295
|
-
| `parseMode` | `"md"` / `"html"` | no | Message formatting mode |
|
|
296
|
-
| `topicId` | number | no | Forum topic ID to send into (for groups with Topics) |
|
|
297
|
-
|
|
298
|
-
### telegram-list-topics
|
|
299
|
-
|
|
300
|
-
| Parameter | Type | Required | Description |
|
|
301
|
-
|-----------|------|----------|-------------|
|
|
302
|
-
| `chatId` | string | yes | Chat ID or @username (group with Topics enabled) |
|
|
303
|
-
| `limit` | number | no | Max topics to return (default: 100) |
|
|
304
|
-
|
|
305
|
-
### telegram-read-topic-messages
|
|
306
|
-
|
|
307
|
-
| Parameter | Type | Required | Description |
|
|
308
|
-
|-----------|------|----------|-------------|
|
|
309
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
310
|
-
| `topicId` | number | yes | Topic ID (from `telegram-list-topics`) |
|
|
311
|
-
| `limit` | number | no | Number of messages (default: 20) |
|
|
312
|
-
| `offsetId` | number | no | Message ID for pagination |
|
|
313
|
-
|
|
314
|
-
### telegram-list-chats
|
|
315
|
-
|
|
316
|
-
| Parameter | Type | Required | Description |
|
|
317
|
-
|-----------|------|----------|-------------|
|
|
318
|
-
| `limit` | number | no | Number of chats to return (default: 20) |
|
|
319
|
-
| `offsetDate` | number | no | Unix timestamp for pagination |
|
|
320
|
-
| `filterType` | `"private"` / `"group"` / `"channel"` / `"contact_requests"` | no | Filter by chat type |
|
|
321
|
-
|
|
322
|
-
### telegram-read-messages
|
|
323
|
-
|
|
324
|
-
| Parameter | Type | Required | Description |
|
|
325
|
-
|-----------|------|----------|-------------|
|
|
326
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
327
|
-
| `limit` | number | no | Number of messages (default: 10) |
|
|
328
|
-
| `offsetId` | number | no | Message ID for pagination |
|
|
329
|
-
|
|
330
|
-
### telegram-send-file
|
|
331
|
-
|
|
332
|
-
| Parameter | Type | Required | Description |
|
|
333
|
-
|-----------|------|----------|-------------|
|
|
334
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
335
|
-
| `filePath` | string | yes | Absolute path to the file |
|
|
336
|
-
| `caption` | string | no | File caption |
|
|
337
|
-
|
|
338
|
-
### telegram-download-media
|
|
339
|
-
|
|
340
|
-
| Parameter | Type | Required | Description |
|
|
341
|
-
|-----------|------|----------|-------------|
|
|
342
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
343
|
-
| `messageId` | number | yes | Message ID containing media |
|
|
344
|
-
| `downloadPath` | string | yes | Absolute path to save the file |
|
|
345
|
-
|
|
346
|
-
### telegram-forward-message
|
|
347
|
-
|
|
348
|
-
| Parameter | Type | Required | Description |
|
|
349
|
-
|-----------|------|----------|-------------|
|
|
350
|
-
| `fromChatId` | string | yes | Source chat ID or @username |
|
|
351
|
-
| `toChatId` | string | yes | Destination chat ID or @username |
|
|
352
|
-
| `messageIds` | number[] | yes | Array of message IDs to forward |
|
|
353
|
-
|
|
354
|
-
### telegram-edit-message
|
|
355
|
-
|
|
356
|
-
| Parameter | Type | Required | Description |
|
|
357
|
-
|-----------|------|----------|-------------|
|
|
358
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
359
|
-
| `messageId` | number | yes | ID of the message to edit |
|
|
360
|
-
| `text` | string | yes | New message text |
|
|
361
|
-
|
|
362
|
-
### telegram-delete-message
|
|
363
|
-
|
|
364
|
-
| Parameter | Type | Required | Description |
|
|
365
|
-
|-----------|------|----------|-------------|
|
|
366
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
367
|
-
| `messageIds` | number[] | yes | Array of message IDs to delete |
|
|
368
|
-
|
|
369
|
-
### telegram-pin-message
|
|
370
|
-
|
|
371
|
-
| Parameter | Type | Required | Description |
|
|
372
|
-
|-----------|------|----------|-------------|
|
|
373
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
374
|
-
| `messageId` | number | yes | Message ID to pin |
|
|
375
|
-
| `silent` | boolean | no | Pin without notification (default: false) |
|
|
376
|
-
|
|
377
|
-
### telegram-join-chat
|
|
378
|
-
|
|
379
|
-
| Parameter | Type | Required | Description |
|
|
380
|
-
|-----------|------|----------|-------------|
|
|
381
|
-
| `target` | string | yes | Username (@group), link (t.me/group), or invite link (t.me/+xxx) |
|
|
382
|
-
|
|
383
|
-
### telegram-send-reaction
|
|
384
|
-
|
|
385
|
-
| Parameter | Type | Required | Description |
|
|
386
|
-
|-----------|------|----------|-------------|
|
|
387
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
388
|
-
| `messageId` | number | yes | Message ID to react to |
|
|
389
|
-
| `emoji` | string \| string[] | no | Single emoji `"👍"` or array `["👍","🔥"]`. Omit to remove all reactions |
|
|
390
|
-
| `addToExisting` | boolean | no | Add to existing reactions instead of replacing (default: false) |
|
|
391
|
-
|
|
392
|
-
### telegram-send-scheduled
|
|
393
|
-
|
|
394
|
-
| Parameter | Type | Required | Description |
|
|
395
|
-
|-----------|------|----------|-------------|
|
|
396
|
-
| `chatId` | string | yes | Chat ID or @username (use `"me"` or `"self"` for Saved Messages) |
|
|
397
|
-
| `text` | string | yes | Message text |
|
|
398
|
-
| `scheduleDate` | number | yes | Unix timestamp when to send (must be in the future) |
|
|
399
|
-
| `replyTo` | number | no | Message ID to reply to |
|
|
400
|
-
| `parseMode` | `"md"` / `"html"` | no | Message formatting mode |
|
|
401
|
-
|
|
402
|
-
### telegram-create-poll
|
|
403
|
-
|
|
404
|
-
| Parameter | Type | Required | Description |
|
|
405
|
-
|-----------|------|----------|-------------|
|
|
406
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
407
|
-
| `question` | string | yes | Poll question |
|
|
408
|
-
| `answers` | string[] | yes | Answer options (2-10 items) |
|
|
409
|
-
| `multipleChoice` | boolean | no | Allow multiple answers (default: false) |
|
|
410
|
-
| `quiz` | boolean | no | Quiz mode with one correct answer (default: false) |
|
|
411
|
-
| `correctAnswer` | number | no | Index of correct answer, 0-based (required for quiz mode) |
|
|
412
|
-
|
|
413
|
-
### telegram-search-messages
|
|
414
|
-
|
|
415
|
-
| Parameter | Type | Required | Description |
|
|
416
|
-
|-----------|------|----------|-------------|
|
|
417
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
418
|
-
| `query` | string | yes | Search text |
|
|
419
|
-
| `limit` | number | no | Max results (default: 20) |
|
|
420
|
-
|
|
421
|
-
### telegram-search-global
|
|
422
|
-
|
|
423
|
-
| Parameter | Type | Required | Description |
|
|
424
|
-
|-----------|------|----------|-------------|
|
|
425
|
-
| `query` | string | yes | Search text |
|
|
426
|
-
| `limit` | number | no | Max results (default: 20) |
|
|
427
|
-
| `minDate` | number | no | Unix timestamp: only messages after this date |
|
|
428
|
-
| `maxDate` | number | no | Unix timestamp: only messages before this date |
|
|
429
|
-
|
|
430
|
-
### telegram-search-chats
|
|
431
|
-
|
|
432
|
-
| Parameter | Type | Required | Description |
|
|
433
|
-
|-----------|------|----------|-------------|
|
|
434
|
-
| `query` | string | yes | Search query (name or username) |
|
|
435
|
-
| `limit` | number | no | Max results (default: 10) |
|
|
436
|
-
|
|
437
|
-
### telegram-get-chat-members
|
|
438
|
-
|
|
439
|
-
| Parameter | Type | Required | Description |
|
|
440
|
-
|-----------|------|----------|-------------|
|
|
441
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
442
|
-
| `limit` | number | no | Number of members (default: 50) |
|
|
443
|
-
|
|
444
|
-
### telegram-get-contacts
|
|
445
|
-
|
|
446
|
-
| Parameter | Type | Required | Description |
|
|
447
|
-
|-----------|------|----------|-------------|
|
|
448
|
-
| `limit` | number | no | Number of contacts (default: 50) |
|
|
449
|
-
|
|
450
|
-
### telegram-get-profile
|
|
451
|
-
|
|
452
|
-
| Parameter | Type | Required | Description |
|
|
453
|
-
|-----------|------|----------|-------------|
|
|
454
|
-
| `userId` | string | yes | User ID or @username |
|
|
455
|
-
|
|
456
|
-
### telegram-get-profile-photo
|
|
457
|
-
|
|
458
|
-
| Parameter | Type | Required | Description |
|
|
459
|
-
|-----------|------|----------|-------------|
|
|
460
|
-
| `entityId` | string | yes | User/Chat/Channel ID or username |
|
|
461
|
-
| `savePath` | string | no | Absolute path to save file. If omitted, returns inline base64 image |
|
|
462
|
-
| `size` | `"small"` / `"big"` | no | Photo size: small (160x160) or big (640x640). Default: big |
|
|
463
|
-
|
|
464
|
-
### telegram-get-reactions
|
|
465
|
-
|
|
466
|
-
| Parameter | Type | Required | Description |
|
|
467
|
-
|-----------|------|----------|-------------|
|
|
468
|
-
| `chatId` | string | yes | Chat ID or @username |
|
|
469
|
-
| `messageId` | number | yes | Message ID to get reactions for |
|
|
470
|
-
|
|
471
|
-
### telegram-get-unread
|
|
472
|
-
|
|
473
|
-
| Parameter | Type | Required | Description |
|
|
474
|
-
|-----------|------|----------|-------------|
|
|
475
|
-
| `limit` | number | no | Number of unread chats (default: 20) |
|
|
476
|
-
|
|
477
|
-
### telegram-get-contact-requests
|
|
478
|
-
|
|
479
|
-
| Parameter | Type | Required | Description |
|
|
480
|
-
|-----------|------|----------|-------------|
|
|
481
|
-
| `limit` | number | no | Number of contact requests (default: 20) |
|
|
482
|
-
|
|
483
|
-
### telegram-add-contact
|
|
484
|
-
|
|
485
|
-
| Parameter | Type | Required | Description |
|
|
486
|
-
|-----------|------|----------|-------------|
|
|
487
|
-
| `userId` | string | yes | User ID or @username to add |
|
|
488
|
-
| `firstName` | string | yes | First name for the contact |
|
|
489
|
-
| `lastName` | string | no | Last name for the contact |
|
|
490
|
-
| `phone` | string | no | Phone number for the contact |
|
|
491
|
-
|
|
492
|
-
### telegram-block-user
|
|
493
|
-
|
|
494
|
-
| Parameter | Type | Required | Description |
|
|
495
|
-
|-----------|------|----------|-------------|
|
|
496
|
-
| `userId` | string | yes | User ID or @username to block |
|
|
497
|
-
|
|
498
|
-
### telegram-report-spam
|
|
499
|
-
|
|
500
|
-
| Parameter | Type | Required | Description |
|
|
501
|
-
|-----------|------|----------|-------------|
|
|
502
|
-
| `chatId` | string | yes | Chat ID or @username to report |
|
|
213
|
+
> **Tip**: Ask your AI assistant *"What Telegram tools are available?"* to get the current list with parameters.
|
|
503
214
|
|
|
504
215
|
## Development
|
|
505
216
|
|
|
@@ -517,9 +228,18 @@ npm run format # Format code with Biome
|
|
|
517
228
|
|
|
518
229
|
```
|
|
519
230
|
src/
|
|
520
|
-
index.ts -- MCP server entry point
|
|
231
|
+
index.ts -- MCP server entry point
|
|
521
232
|
telegram-client.ts -- TelegramService class (GramJS wrapper)
|
|
522
233
|
qr-login-cli.ts -- CLI utility for QR code login
|
|
234
|
+
tools/ -- Modular tool definitions
|
|
235
|
+
auth.ts -- Connection & login
|
|
236
|
+
messages.ts -- Send, read, search, edit, delete, forward
|
|
237
|
+
chats.ts -- Chat listing, group management, admin
|
|
238
|
+
contacts.ts -- Contacts, profiles, moderation
|
|
239
|
+
media.ts -- Files, photos
|
|
240
|
+
reactions.ts -- Reactions
|
|
241
|
+
extras.ts -- Pin, schedule, polls, topics
|
|
242
|
+
shared.ts -- Shared utilities
|
|
523
243
|
```
|
|
524
244
|
|
|
525
245
|
## Tech Stack
|
|
@@ -85,6 +85,16 @@ export declare class TelegramService {
|
|
|
85
85
|
forwardMessage(fromChatId: string, toChatId: string, messageIds: number[]): Promise<void>;
|
|
86
86
|
editMessage(chatId: string, messageId: number, newText: string): Promise<void>;
|
|
87
87
|
deleteMessages(chatId: string, messageIds: number[]): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Resolve a chat by ID, username, or display name.
|
|
90
|
+
* Falls back to searching user's dialogs if getEntity() fails.
|
|
91
|
+
*/
|
|
92
|
+
resolveChat(chatId: string): Promise<any>;
|
|
93
|
+
/**
|
|
94
|
+
* Resolve chatId to a peer string that GramJS methods accept.
|
|
95
|
+
* Handles display names by searching dialogs.
|
|
96
|
+
*/
|
|
97
|
+
private resolvePeer;
|
|
88
98
|
getChatInfo(chatId: string): Promise<{
|
|
89
99
|
id: string;
|
|
90
100
|
name: string;
|
|
@@ -172,7 +182,15 @@ export declare class TelegramService {
|
|
|
172
182
|
id: string;
|
|
173
183
|
name: string;
|
|
174
184
|
username?: string;
|
|
185
|
+
role: string;
|
|
175
186
|
}>>;
|
|
187
|
+
getMyRole(chatId: string): Promise<{
|
|
188
|
+
role: string;
|
|
189
|
+
chatId: string;
|
|
190
|
+
chatName: string;
|
|
191
|
+
}>;
|
|
192
|
+
private getParticipantUserId;
|
|
193
|
+
private getParticipantRole;
|
|
176
194
|
getProfile(userId: string): Promise<{
|
|
177
195
|
id: string;
|
|
178
196
|
name: string;
|
package/dist/telegram-client.js
CHANGED
|
@@ -299,9 +299,10 @@ export class TelegramService {
|
|
|
299
299
|
async sendMessage(chatId, text, replyTo, parseMode, topicId) {
|
|
300
300
|
if (!this.client || !this.connected)
|
|
301
301
|
throw new Error("Not connected");
|
|
302
|
+
const resolved = await this.resolvePeer(chatId);
|
|
302
303
|
if (topicId) {
|
|
303
304
|
// Forum topics require raw API call with InputReplyToMessage
|
|
304
|
-
const peer = await this.client.getInputEntity(
|
|
305
|
+
const peer = await this.client.getInputEntity(resolved);
|
|
305
306
|
await this.client.invoke(new Api.messages.SendMessage({
|
|
306
307
|
peer,
|
|
307
308
|
message: text,
|
|
@@ -313,7 +314,7 @@ export class TelegramService {
|
|
|
313
314
|
}));
|
|
314
315
|
}
|
|
315
316
|
else {
|
|
316
|
-
await this.client.sendMessage(
|
|
317
|
+
await this.client.sendMessage(resolved, {
|
|
317
318
|
message: text,
|
|
318
319
|
...(replyTo ? { replyTo } : {}),
|
|
319
320
|
...(parseMode ? { parseMode: parseMode === "html" ? "html" : "md" } : {}),
|
|
@@ -323,12 +324,14 @@ export class TelegramService {
|
|
|
323
324
|
async sendFile(chatId, filePath, caption) {
|
|
324
325
|
if (!this.client || !this.connected)
|
|
325
326
|
throw new Error("Not connected");
|
|
326
|
-
await this.
|
|
327
|
+
const resolved = await this.resolvePeer(chatId);
|
|
328
|
+
await this.client.sendFile(resolved, { file: filePath, caption });
|
|
327
329
|
}
|
|
328
330
|
async downloadMedia(chatId, messageId, downloadPath) {
|
|
329
331
|
if (!this.client || !this.connected)
|
|
330
332
|
throw new Error("Not connected");
|
|
331
|
-
const
|
|
333
|
+
const resolved = await this.resolvePeer(chatId);
|
|
334
|
+
const messages = await this.client.getMessages(resolved, { ids: [messageId] });
|
|
332
335
|
const message = messages[0];
|
|
333
336
|
if (!message)
|
|
334
337
|
throw new Error(`Message ${messageId} not found`);
|
|
@@ -343,7 +346,8 @@ export class TelegramService {
|
|
|
343
346
|
async downloadMediaAsBuffer(chatId, messageId) {
|
|
344
347
|
if (!this.client || !this.connected)
|
|
345
348
|
throw new Error("Not connected");
|
|
346
|
-
const
|
|
349
|
+
const resolved = await this.resolvePeer(chatId);
|
|
350
|
+
const messages = await this.client.getMessages(resolved, { ids: [messageId] });
|
|
347
351
|
const message = messages[0];
|
|
348
352
|
if (!message)
|
|
349
353
|
throw new Error(`Message ${messageId} not found`);
|
|
@@ -378,12 +382,14 @@ export class TelegramService {
|
|
|
378
382
|
async pinMessage(chatId, messageId, silent = false) {
|
|
379
383
|
if (!this.client || !this.connected)
|
|
380
384
|
throw new Error("Not connected");
|
|
381
|
-
await this.
|
|
385
|
+
const resolved = await this.resolvePeer(chatId);
|
|
386
|
+
await this.client.pinMessage(resolved, messageId, { notify: !silent });
|
|
382
387
|
}
|
|
383
388
|
async unpinMessage(chatId, messageId) {
|
|
384
389
|
if (!this.client || !this.connected)
|
|
385
390
|
throw new Error("Not connected");
|
|
386
|
-
await this.
|
|
391
|
+
const resolved = await this.resolvePeer(chatId);
|
|
392
|
+
await this.client.unpinMessage(resolved, messageId);
|
|
387
393
|
}
|
|
388
394
|
async getDialogs(limit = 20, offsetDate, filterType) {
|
|
389
395
|
if (!this.client || !this.connected)
|
|
@@ -505,22 +511,70 @@ export class TelegramService {
|
|
|
505
511
|
async forwardMessage(fromChatId, toChatId, messageIds) {
|
|
506
512
|
if (!this.client || !this.connected)
|
|
507
513
|
throw new Error("Not connected");
|
|
508
|
-
await this.
|
|
514
|
+
const resolvedFrom = await this.resolvePeer(fromChatId);
|
|
515
|
+
const resolvedTo = await this.resolvePeer(toChatId);
|
|
516
|
+
await this.client.forwardMessages(resolvedTo, { messages: messageIds, fromPeer: resolvedFrom });
|
|
509
517
|
}
|
|
510
518
|
async editMessage(chatId, messageId, newText) {
|
|
511
519
|
if (!this.client || !this.connected)
|
|
512
520
|
throw new Error("Not connected");
|
|
513
|
-
await this.
|
|
521
|
+
const resolved = await this.resolvePeer(chatId);
|
|
522
|
+
await this.client.editMessage(resolved, { message: messageId, text: newText });
|
|
514
523
|
}
|
|
515
524
|
async deleteMessages(chatId, messageIds) {
|
|
516
525
|
if (!this.client || !this.connected)
|
|
517
526
|
throw new Error("Not connected");
|
|
518
|
-
await this.
|
|
527
|
+
const resolved = await this.resolvePeer(chatId);
|
|
528
|
+
await this.client.deleteMessages(resolved, messageIds, { revoke: true });
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Resolve a chat by ID, username, or display name.
|
|
532
|
+
* Falls back to searching user's dialogs if getEntity() fails.
|
|
533
|
+
*/
|
|
534
|
+
// biome-ignore lint: GramJS has no proper entity union type
|
|
535
|
+
async resolveChat(chatId) {
|
|
536
|
+
if (!this.client)
|
|
537
|
+
throw new Error("Not connected");
|
|
538
|
+
// First try direct resolve (numeric ID, username, phone)
|
|
539
|
+
try {
|
|
540
|
+
return await this.client.getEntity(chatId);
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
// Fall through to dialog search
|
|
544
|
+
}
|
|
545
|
+
// Search dialogs by display name
|
|
546
|
+
const dialogs = await this.client.getDialogs({ limit: 100 });
|
|
547
|
+
const query = chatId.toLowerCase();
|
|
548
|
+
// Exact match first
|
|
549
|
+
const exact = dialogs.find((d) => d.title?.toLowerCase() === query);
|
|
550
|
+
if (exact?.entity)
|
|
551
|
+
return exact.entity;
|
|
552
|
+
// Partial match
|
|
553
|
+
const partial = dialogs.filter((d) => d.title?.toLowerCase().includes(query));
|
|
554
|
+
if (partial.length === 1 && partial[0].entity)
|
|
555
|
+
return partial[0].entity;
|
|
556
|
+
if (partial.length > 1) {
|
|
557
|
+
const matches = partial.map((d) => ` ${d.title} (${d.entity?.id?.toString() ?? "?"})`).join("\n");
|
|
558
|
+
throw new Error(`Multiple chats match "${chatId}". Use the numeric ID instead:\n${matches}`);
|
|
559
|
+
}
|
|
560
|
+
throw new Error(`Cannot find chat "${chatId}". Use a numeric ID, @username, or run telegram-search-chats to find it.`);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Resolve chatId to a peer string that GramJS methods accept.
|
|
564
|
+
* Handles display names by searching dialogs.
|
|
565
|
+
*/
|
|
566
|
+
// biome-ignore lint: GramJS has no proper entity union type
|
|
567
|
+
async resolvePeer(chatId) {
|
|
568
|
+
// Numeric IDs and @usernames work directly
|
|
569
|
+
if (/^-?\d+$/.test(chatId) || chatId.startsWith("@"))
|
|
570
|
+
return chatId;
|
|
571
|
+
// Everything else — resolve via dialogs
|
|
572
|
+
return this.resolveChat(chatId);
|
|
519
573
|
}
|
|
520
574
|
async getChatInfo(chatId) {
|
|
521
575
|
if (!this.client || !this.connected)
|
|
522
576
|
throw new Error("Not connected");
|
|
523
|
-
const entity = await this.
|
|
577
|
+
const entity = await this.resolveChat(chatId);
|
|
524
578
|
if (entity instanceof Api.User) {
|
|
525
579
|
const parts = [entity.firstName, entity.lastName].filter(Boolean);
|
|
526
580
|
return {
|
|
@@ -628,12 +682,13 @@ export class TelegramService {
|
|
|
628
682
|
async getMessages(chatId, limit = 10, offsetId, minDate, maxDate) {
|
|
629
683
|
if (!this.client || !this.connected)
|
|
630
684
|
throw new Error("Not connected");
|
|
685
|
+
const resolved = await this.resolvePeer(chatId);
|
|
631
686
|
const opts = {
|
|
632
687
|
limit,
|
|
633
688
|
...(offsetId ? { offsetId } : {}),
|
|
634
689
|
...(maxDate ? { offsetDate: maxDate } : {}),
|
|
635
690
|
};
|
|
636
|
-
const messages = await this.client.getMessages(
|
|
691
|
+
const messages = await this.client.getMessages(resolved, opts);
|
|
637
692
|
let filtered = messages;
|
|
638
693
|
if (minDate) {
|
|
639
694
|
filtered = filtered.filter((m) => (m.date ?? 0) >= minDate);
|
|
@@ -781,7 +836,8 @@ export class TelegramService {
|
|
|
781
836
|
async searchMessages(chatId, query, limit = 20, minDate, maxDate) {
|
|
782
837
|
if (!this.client || !this.connected)
|
|
783
838
|
throw new Error("Not connected");
|
|
784
|
-
const
|
|
839
|
+
const resolved = await this.resolvePeer(chatId);
|
|
840
|
+
const messages = await this.client.getMessages(resolved, {
|
|
785
841
|
search: query,
|
|
786
842
|
limit,
|
|
787
843
|
...(maxDate ? { offsetDate: maxDate } : {}),
|
|
@@ -823,19 +879,99 @@ export class TelegramService {
|
|
|
823
879
|
async getChatMembers(chatId, limit = 50) {
|
|
824
880
|
if (!this.client || !this.connected)
|
|
825
881
|
throw new Error("Not connected");
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
882
|
+
const entity = await this.resolveChat(chatId);
|
|
883
|
+
if (entity instanceof Api.Channel) {
|
|
884
|
+
const result = await this.client.invoke(new Api.channels.GetParticipants({
|
|
885
|
+
channel: entity,
|
|
886
|
+
filter: new Api.ChannelParticipantsRecent(),
|
|
887
|
+
offset: 0,
|
|
888
|
+
limit,
|
|
889
|
+
hash: bigInt.zero,
|
|
890
|
+
}));
|
|
891
|
+
if (!(result instanceof Api.channels.ChannelParticipants))
|
|
892
|
+
return [];
|
|
893
|
+
const userMap = new Map();
|
|
894
|
+
for (const u of result.users) {
|
|
895
|
+
if (u instanceof Api.User)
|
|
896
|
+
userMap.set(u.id.toString(), u);
|
|
897
|
+
}
|
|
898
|
+
return result.participants.map((p) => {
|
|
899
|
+
const userId = this.getParticipantUserId(p);
|
|
900
|
+
const user = userMap.get(userId);
|
|
901
|
+
const parts = user ? [user.firstName, user.lastName].filter(Boolean) : [];
|
|
902
|
+
return {
|
|
903
|
+
id: userId,
|
|
833
904
|
name: parts.join(" ") || "Unknown",
|
|
834
|
-
username:
|
|
835
|
-
|
|
905
|
+
username: user?.username ?? undefined,
|
|
906
|
+
role: this.getParticipantRole(p),
|
|
907
|
+
};
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
// Basic group — use getParticipants (no role info available)
|
|
911
|
+
const participants = await this.client.getParticipants(entity, { limit });
|
|
912
|
+
return participants
|
|
913
|
+
.filter((p) => p instanceof Api.User)
|
|
914
|
+
.map((p) => {
|
|
915
|
+
const parts = [p.firstName, p.lastName].filter(Boolean);
|
|
916
|
+
return {
|
|
917
|
+
id: p.id.toString(),
|
|
918
|
+
name: parts.join(" ") || "Unknown",
|
|
919
|
+
username: p.username ?? undefined,
|
|
920
|
+
role: "member",
|
|
921
|
+
};
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
async getMyRole(chatId) {
|
|
925
|
+
if (!this.client || !this.connected)
|
|
926
|
+
throw new Error("Not connected");
|
|
927
|
+
const entity = await this.resolveChat(chatId);
|
|
928
|
+
const me = await this.getMe();
|
|
929
|
+
if (entity instanceof Api.Channel) {
|
|
930
|
+
const result = await this.client.invoke(new Api.channels.GetParticipant({ channel: entity, participant: new Api.InputUserSelf() }));
|
|
931
|
+
return {
|
|
932
|
+
role: this.getParticipantRole(result.participant),
|
|
933
|
+
chatId: entity.id.toString(),
|
|
934
|
+
chatName: entity.title ?? "Unknown",
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
if (entity instanceof Api.Chat) {
|
|
938
|
+
// Basic group — check if creator
|
|
939
|
+
if (entity.creator) {
|
|
940
|
+
return { role: "creator", chatId: entity.id.toString(), chatName: entity.title ?? "Unknown" };
|
|
941
|
+
}
|
|
942
|
+
if (entity.adminRights) {
|
|
943
|
+
return { role: "admin", chatId: entity.id.toString(), chatName: entity.title ?? "Unknown" };
|
|
836
944
|
}
|
|
945
|
+
return { role: "member", chatId: entity.id.toString(), chatName: entity.title ?? "Unknown" };
|
|
946
|
+
}
|
|
947
|
+
if (entity instanceof Api.User) {
|
|
948
|
+
return { role: "user", chatId: entity.id.toString(), chatName: me.username ?? "self" };
|
|
837
949
|
}
|
|
838
|
-
return
|
|
950
|
+
return { role: "unknown", chatId: chatId, chatName: "Unknown" };
|
|
951
|
+
}
|
|
952
|
+
getParticipantUserId(p) {
|
|
953
|
+
if (p instanceof Api.ChannelParticipantCreator)
|
|
954
|
+
return p.userId.toString();
|
|
955
|
+
if (p instanceof Api.ChannelParticipantAdmin)
|
|
956
|
+
return p.userId.toString();
|
|
957
|
+
if (p instanceof Api.ChannelParticipantSelf)
|
|
958
|
+
return p.userId.toString();
|
|
959
|
+
if (p instanceof Api.ChannelParticipantBanned)
|
|
960
|
+
return p.peer?.userId?.toString() ?? "0";
|
|
961
|
+
if (p instanceof Api.ChannelParticipant)
|
|
962
|
+
return p.userId.toString();
|
|
963
|
+
return "0";
|
|
964
|
+
}
|
|
965
|
+
getParticipantRole(p) {
|
|
966
|
+
if (p instanceof Api.ChannelParticipantCreator)
|
|
967
|
+
return "creator";
|
|
968
|
+
if (p instanceof Api.ChannelParticipantAdmin)
|
|
969
|
+
return "admin";
|
|
970
|
+
if (p instanceof Api.ChannelParticipantBanned)
|
|
971
|
+
return "banned";
|
|
972
|
+
if (p instanceof Api.ChannelParticipantLeft)
|
|
973
|
+
return "left";
|
|
974
|
+
return "member";
|
|
839
975
|
}
|
|
840
976
|
async getProfile(userId) {
|
|
841
977
|
if (!this.client || !this.connected)
|
|
@@ -951,13 +1087,14 @@ export class TelegramService {
|
|
|
951
1087
|
async sendReaction(chatId, messageId, emoji, addToExisting = false) {
|
|
952
1088
|
if (!this.client || !this.connected)
|
|
953
1089
|
throw new Error("Not connected");
|
|
954
|
-
const
|
|
1090
|
+
const resolved = await this.resolvePeer(chatId);
|
|
1091
|
+
const peer = await this.client.getInputEntity(resolved);
|
|
955
1092
|
const reactionList = [];
|
|
956
1093
|
if (emoji) {
|
|
957
1094
|
const emojis = Array.isArray(emoji) ? emoji : [emoji];
|
|
958
1095
|
if (addToExisting) {
|
|
959
1096
|
// Fetch current reactions to preserve them
|
|
960
|
-
const msgs = await this.client.getMessages(
|
|
1097
|
+
const msgs = await this.client.getMessages(resolved, { ids: [messageId] });
|
|
961
1098
|
const msg = msgs[0];
|
|
962
1099
|
if (msg?.reactions?.results) {
|
|
963
1100
|
for (const r of msg.reactions.results) {
|
|
@@ -990,9 +1127,10 @@ export class TelegramService {
|
|
|
990
1127
|
async getMessageReactions(chatId, messageId) {
|
|
991
1128
|
if (!this.client || !this.connected)
|
|
992
1129
|
throw new Error("Not connected");
|
|
993
|
-
const
|
|
1130
|
+
const resolved = await this.resolvePeer(chatId);
|
|
1131
|
+
const peer = await this.client.getInputEntity(resolved);
|
|
994
1132
|
// First get the message to know which reactions exist
|
|
995
|
-
const msgs = await this.client.getMessages(
|
|
1133
|
+
const msgs = await this.client.getMessages(resolved, { ids: [messageId] });
|
|
996
1134
|
const msg = msgs[0];
|
|
997
1135
|
if (!msg?.reactions?.results?.length) {
|
|
998
1136
|
return { reactions: [], total: 0 };
|
|
@@ -1044,7 +1182,8 @@ export class TelegramService {
|
|
|
1044
1182
|
async sendScheduledMessage(chatId, text, scheduleDate, replyTo, parseMode) {
|
|
1045
1183
|
if (!this.client || !this.connected)
|
|
1046
1184
|
throw new Error("Not connected");
|
|
1047
|
-
await this.
|
|
1185
|
+
const resolved = await this.resolvePeer(chatId);
|
|
1186
|
+
await this.client.sendMessage(resolved, {
|
|
1048
1187
|
message: text,
|
|
1049
1188
|
schedule: scheduleDate,
|
|
1050
1189
|
...(replyTo ? { replyTo } : {}),
|
|
@@ -1090,7 +1229,7 @@ export class TelegramService {
|
|
|
1090
1229
|
async getForumTopics(chatId, limit = 100) {
|
|
1091
1230
|
if (!this.client || !this.connected)
|
|
1092
1231
|
throw new Error("Not connected");
|
|
1093
|
-
const entity = await this.
|
|
1232
|
+
const entity = await this.resolveChat(chatId);
|
|
1094
1233
|
if (!(entity instanceof Api.Channel))
|
|
1095
1234
|
throw new Error("Forum topics are only available in supergroups");
|
|
1096
1235
|
const result = await this.client.invoke(new Api.channels.GetForumTopics({
|
|
@@ -1149,7 +1288,7 @@ export class TelegramService {
|
|
|
1149
1288
|
if (!this.client || !this.connected)
|
|
1150
1289
|
throw new Error("Not connected");
|
|
1151
1290
|
try {
|
|
1152
|
-
const entity = await this.
|
|
1291
|
+
const entity = await this.resolveChat(chatId);
|
|
1153
1292
|
if (entity instanceof Api.Channel) {
|
|
1154
1293
|
return Boolean(entity.forum);
|
|
1155
1294
|
}
|
|
@@ -1265,7 +1404,7 @@ export class TelegramService {
|
|
|
1265
1404
|
async inviteToGroup(chatId, users) {
|
|
1266
1405
|
if (!this.client)
|
|
1267
1406
|
throw new Error("Not connected");
|
|
1268
|
-
const entity = await this.
|
|
1407
|
+
const entity = await this.resolveChat(chatId);
|
|
1269
1408
|
const invited = [];
|
|
1270
1409
|
const failed = [];
|
|
1271
1410
|
for (const u of users) {
|
|
@@ -1293,7 +1432,7 @@ export class TelegramService {
|
|
|
1293
1432
|
async kickUser(chatId, userId) {
|
|
1294
1433
|
if (!this.client)
|
|
1295
1434
|
throw new Error("Not connected");
|
|
1296
|
-
const entity = await this.
|
|
1435
|
+
const entity = await this.resolveChat(chatId);
|
|
1297
1436
|
const user = await this.client.getEntity(userId);
|
|
1298
1437
|
if (!(user instanceof Api.User))
|
|
1299
1438
|
throw new Error("Target is not a user");
|
|
@@ -1318,7 +1457,7 @@ export class TelegramService {
|
|
|
1318
1457
|
async banUser(chatId, userId) {
|
|
1319
1458
|
if (!this.client)
|
|
1320
1459
|
throw new Error("Not connected");
|
|
1321
|
-
const entity = await this.
|
|
1460
|
+
const entity = await this.resolveChat(chatId);
|
|
1322
1461
|
const user = await this.client.getEntity(userId);
|
|
1323
1462
|
if (!(user instanceof Api.User))
|
|
1324
1463
|
throw new Error("Target is not a user");
|
|
@@ -1334,7 +1473,7 @@ export class TelegramService {
|
|
|
1334
1473
|
async unbanUser(chatId, userId) {
|
|
1335
1474
|
if (!this.client)
|
|
1336
1475
|
throw new Error("Not connected");
|
|
1337
|
-
const entity = await this.
|
|
1476
|
+
const entity = await this.resolveChat(chatId);
|
|
1338
1477
|
const user = await this.client.getEntity(userId);
|
|
1339
1478
|
if (!(user instanceof Api.User))
|
|
1340
1479
|
throw new Error("Target is not a user");
|
|
@@ -1350,7 +1489,7 @@ export class TelegramService {
|
|
|
1350
1489
|
async editGroup(chatId, options) {
|
|
1351
1490
|
if (!this.client)
|
|
1352
1491
|
throw new Error("Not connected");
|
|
1353
|
-
const entity = await this.
|
|
1492
|
+
const entity = await this.resolveChat(chatId);
|
|
1354
1493
|
if (options.title) {
|
|
1355
1494
|
if (entity instanceof Api.Channel) {
|
|
1356
1495
|
await this.client.invoke(new Api.channels.EditTitle({ channel: entity, title: options.title }));
|
|
@@ -1380,7 +1519,7 @@ export class TelegramService {
|
|
|
1380
1519
|
async leaveGroup(chatId) {
|
|
1381
1520
|
if (!this.client)
|
|
1382
1521
|
throw new Error("Not connected");
|
|
1383
|
-
const entity = await this.
|
|
1522
|
+
const entity = await this.resolveChat(chatId);
|
|
1384
1523
|
if (entity instanceof Api.Channel) {
|
|
1385
1524
|
await this.client.invoke(new Api.channels.LeaveChannel({ channel: entity }));
|
|
1386
1525
|
}
|
|
@@ -1397,7 +1536,7 @@ export class TelegramService {
|
|
|
1397
1536
|
async setAdmin(chatId, userId, options) {
|
|
1398
1537
|
if (!this.client)
|
|
1399
1538
|
throw new Error("Not connected");
|
|
1400
|
-
const entity = await this.
|
|
1539
|
+
const entity = await this.resolveChat(chatId);
|
|
1401
1540
|
if (!(entity instanceof Api.Channel))
|
|
1402
1541
|
throw new Error("Set admin is only supported for supergroups and channels");
|
|
1403
1542
|
const user = await this.client.getEntity(userId);
|
|
@@ -1423,7 +1562,7 @@ export class TelegramService {
|
|
|
1423
1562
|
async removeAdmin(chatId, userId) {
|
|
1424
1563
|
if (!this.client)
|
|
1425
1564
|
throw new Error("Not connected");
|
|
1426
|
-
const entity = await this.
|
|
1565
|
+
const entity = await this.resolveChat(chatId);
|
|
1427
1566
|
if (!(entity instanceof Api.Channel))
|
|
1428
1567
|
throw new Error("Remove admin is only supported for supergroups and channels");
|
|
1429
1568
|
const user = await this.client.getEntity(userId);
|
package/dist/tools/chats.js
CHANGED
|
@@ -93,13 +93,36 @@ export function registerChatTools(server, telegram) {
|
|
|
93
93
|
return fail(new Error(err));
|
|
94
94
|
try {
|
|
95
95
|
const members = await telegram.getChatMembers(chatId, limit);
|
|
96
|
-
const text = members
|
|
96
|
+
const text = members
|
|
97
|
+
.map((m) => {
|
|
98
|
+
const role = m.role !== "member" ? ` [${m.role}]` : "";
|
|
99
|
+
return `${m.name}${m.username ? ` (@${m.username})` : ""} (${m.id})${role}`;
|
|
100
|
+
})
|
|
101
|
+
.join("\n");
|
|
97
102
|
return ok(sanitize(text) || "No members found");
|
|
98
103
|
}
|
|
99
104
|
catch (e) {
|
|
100
105
|
return fail(e);
|
|
101
106
|
}
|
|
102
107
|
});
|
|
108
|
+
server.registerTool("telegram-get-my-role", {
|
|
109
|
+
description: "Get the current user's role in a chat (creator, admin, or member)",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
112
|
+
},
|
|
113
|
+
annotations: READ_ONLY,
|
|
114
|
+
}, async ({ chatId }) => {
|
|
115
|
+
const err = await requireConnection(telegram);
|
|
116
|
+
if (err)
|
|
117
|
+
return fail(new Error(err));
|
|
118
|
+
try {
|
|
119
|
+
const result = await telegram.getMyRole(chatId);
|
|
120
|
+
return ok(`Role: ${result.role}\nChat: ${result.chatName} (${result.chatId})`);
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
return fail(e);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
103
126
|
server.registerTool("telegram-create-group", {
|
|
104
127
|
description: "Create a new Telegram group or supergroup",
|
|
105
128
|
inputSchema: {
|
package/package.json
CHANGED