@overpod/mcp-telegram 1.15.0 → 1.17.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 +27 -0
- package/dist/telegram-client.js +256 -21
- package/dist/tools/chats.js +160 -0
- 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;
|
|
@@ -267,4 +277,21 @@ export declare class TelegramService {
|
|
|
267
277
|
type: string;
|
|
268
278
|
inviteLink?: string;
|
|
269
279
|
}>;
|
|
280
|
+
inviteToGroup(chatId: string, users: string[]): Promise<{
|
|
281
|
+
invited: string[];
|
|
282
|
+
failed: string[];
|
|
283
|
+
}>;
|
|
284
|
+
kickUser(chatId: string, userId: string): Promise<void>;
|
|
285
|
+
banUser(chatId: string, userId: string): Promise<void>;
|
|
286
|
+
unbanUser(chatId: string, userId: string): Promise<void>;
|
|
287
|
+
editGroup(chatId: string, options: {
|
|
288
|
+
title?: string;
|
|
289
|
+
description?: string;
|
|
290
|
+
photoPath?: string;
|
|
291
|
+
}): Promise<void>;
|
|
292
|
+
leaveGroup(chatId: string): Promise<void>;
|
|
293
|
+
setAdmin(chatId: string, userId: string, options?: {
|
|
294
|
+
title?: string;
|
|
295
|
+
}): Promise<void>;
|
|
296
|
+
removeAdmin(chatId: string, userId: string): Promise<void>;
|
|
270
297
|
}
|
package/dist/telegram-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { chmod, readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import bigInt from "big-integer";
|
|
7
7
|
import QRCode from "qrcode";
|
|
8
8
|
import { TelegramClient } from "telegram";
|
|
9
|
+
import { CustomFile } from "telegram/client/uploads.js";
|
|
9
10
|
import { StringSession } from "telegram/sessions/index.js";
|
|
10
11
|
import { Api } from "telegram/tl/index.js";
|
|
11
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -298,9 +299,10 @@ export class TelegramService {
|
|
|
298
299
|
async sendMessage(chatId, text, replyTo, parseMode, topicId) {
|
|
299
300
|
if (!this.client || !this.connected)
|
|
300
301
|
throw new Error("Not connected");
|
|
302
|
+
const resolved = await this.resolvePeer(chatId);
|
|
301
303
|
if (topicId) {
|
|
302
304
|
// Forum topics require raw API call with InputReplyToMessage
|
|
303
|
-
const peer = await this.client.getInputEntity(
|
|
305
|
+
const peer = await this.client.getInputEntity(resolved);
|
|
304
306
|
await this.client.invoke(new Api.messages.SendMessage({
|
|
305
307
|
peer,
|
|
306
308
|
message: text,
|
|
@@ -312,7 +314,7 @@ export class TelegramService {
|
|
|
312
314
|
}));
|
|
313
315
|
}
|
|
314
316
|
else {
|
|
315
|
-
await this.client.sendMessage(
|
|
317
|
+
await this.client.sendMessage(resolved, {
|
|
316
318
|
message: text,
|
|
317
319
|
...(replyTo ? { replyTo } : {}),
|
|
318
320
|
...(parseMode ? { parseMode: parseMode === "html" ? "html" : "md" } : {}),
|
|
@@ -322,12 +324,14 @@ export class TelegramService {
|
|
|
322
324
|
async sendFile(chatId, filePath, caption) {
|
|
323
325
|
if (!this.client || !this.connected)
|
|
324
326
|
throw new Error("Not connected");
|
|
325
|
-
await this.
|
|
327
|
+
const resolved = await this.resolvePeer(chatId);
|
|
328
|
+
await this.client.sendFile(resolved, { file: filePath, caption });
|
|
326
329
|
}
|
|
327
330
|
async downloadMedia(chatId, messageId, downloadPath) {
|
|
328
331
|
if (!this.client || !this.connected)
|
|
329
332
|
throw new Error("Not connected");
|
|
330
|
-
const
|
|
333
|
+
const resolved = await this.resolvePeer(chatId);
|
|
334
|
+
const messages = await this.client.getMessages(resolved, { ids: [messageId] });
|
|
331
335
|
const message = messages[0];
|
|
332
336
|
if (!message)
|
|
333
337
|
throw new Error(`Message ${messageId} not found`);
|
|
@@ -342,7 +346,8 @@ export class TelegramService {
|
|
|
342
346
|
async downloadMediaAsBuffer(chatId, messageId) {
|
|
343
347
|
if (!this.client || !this.connected)
|
|
344
348
|
throw new Error("Not connected");
|
|
345
|
-
const
|
|
349
|
+
const resolved = await this.resolvePeer(chatId);
|
|
350
|
+
const messages = await this.client.getMessages(resolved, { ids: [messageId] });
|
|
346
351
|
const message = messages[0];
|
|
347
352
|
if (!message)
|
|
348
353
|
throw new Error(`Message ${messageId} not found`);
|
|
@@ -377,12 +382,14 @@ export class TelegramService {
|
|
|
377
382
|
async pinMessage(chatId, messageId, silent = false) {
|
|
378
383
|
if (!this.client || !this.connected)
|
|
379
384
|
throw new Error("Not connected");
|
|
380
|
-
await this.
|
|
385
|
+
const resolved = await this.resolvePeer(chatId);
|
|
386
|
+
await this.client.pinMessage(resolved, messageId, { notify: !silent });
|
|
381
387
|
}
|
|
382
388
|
async unpinMessage(chatId, messageId) {
|
|
383
389
|
if (!this.client || !this.connected)
|
|
384
390
|
throw new Error("Not connected");
|
|
385
|
-
await this.
|
|
391
|
+
const resolved = await this.resolvePeer(chatId);
|
|
392
|
+
await this.client.unpinMessage(resolved, messageId);
|
|
386
393
|
}
|
|
387
394
|
async getDialogs(limit = 20, offsetDate, filterType) {
|
|
388
395
|
if (!this.client || !this.connected)
|
|
@@ -504,22 +511,70 @@ export class TelegramService {
|
|
|
504
511
|
async forwardMessage(fromChatId, toChatId, messageIds) {
|
|
505
512
|
if (!this.client || !this.connected)
|
|
506
513
|
throw new Error("Not connected");
|
|
507
|
-
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 });
|
|
508
517
|
}
|
|
509
518
|
async editMessage(chatId, messageId, newText) {
|
|
510
519
|
if (!this.client || !this.connected)
|
|
511
520
|
throw new Error("Not connected");
|
|
512
|
-
await this.
|
|
521
|
+
const resolved = await this.resolvePeer(chatId);
|
|
522
|
+
await this.client.editMessage(resolved, { message: messageId, text: newText });
|
|
513
523
|
}
|
|
514
524
|
async deleteMessages(chatId, messageIds) {
|
|
515
525
|
if (!this.client || !this.connected)
|
|
516
526
|
throw new Error("Not connected");
|
|
517
|
-
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);
|
|
518
573
|
}
|
|
519
574
|
async getChatInfo(chatId) {
|
|
520
575
|
if (!this.client || !this.connected)
|
|
521
576
|
throw new Error("Not connected");
|
|
522
|
-
const entity = await this.
|
|
577
|
+
const entity = await this.resolveChat(chatId);
|
|
523
578
|
if (entity instanceof Api.User) {
|
|
524
579
|
const parts = [entity.firstName, entity.lastName].filter(Boolean);
|
|
525
580
|
return {
|
|
@@ -627,12 +682,13 @@ export class TelegramService {
|
|
|
627
682
|
async getMessages(chatId, limit = 10, offsetId, minDate, maxDate) {
|
|
628
683
|
if (!this.client || !this.connected)
|
|
629
684
|
throw new Error("Not connected");
|
|
685
|
+
const resolved = await this.resolvePeer(chatId);
|
|
630
686
|
const opts = {
|
|
631
687
|
limit,
|
|
632
688
|
...(offsetId ? { offsetId } : {}),
|
|
633
689
|
...(maxDate ? { offsetDate: maxDate } : {}),
|
|
634
690
|
};
|
|
635
|
-
const messages = await this.client.getMessages(
|
|
691
|
+
const messages = await this.client.getMessages(resolved, opts);
|
|
636
692
|
let filtered = messages;
|
|
637
693
|
if (minDate) {
|
|
638
694
|
filtered = filtered.filter((m) => (m.date ?? 0) >= minDate);
|
|
@@ -780,7 +836,8 @@ export class TelegramService {
|
|
|
780
836
|
async searchMessages(chatId, query, limit = 20, minDate, maxDate) {
|
|
781
837
|
if (!this.client || !this.connected)
|
|
782
838
|
throw new Error("Not connected");
|
|
783
|
-
const
|
|
839
|
+
const resolved = await this.resolvePeer(chatId);
|
|
840
|
+
const messages = await this.client.getMessages(resolved, {
|
|
784
841
|
search: query,
|
|
785
842
|
limit,
|
|
786
843
|
...(maxDate ? { offsetDate: maxDate } : {}),
|
|
@@ -950,13 +1007,14 @@ export class TelegramService {
|
|
|
950
1007
|
async sendReaction(chatId, messageId, emoji, addToExisting = false) {
|
|
951
1008
|
if (!this.client || !this.connected)
|
|
952
1009
|
throw new Error("Not connected");
|
|
953
|
-
const
|
|
1010
|
+
const resolved = await this.resolvePeer(chatId);
|
|
1011
|
+
const peer = await this.client.getInputEntity(resolved);
|
|
954
1012
|
const reactionList = [];
|
|
955
1013
|
if (emoji) {
|
|
956
1014
|
const emojis = Array.isArray(emoji) ? emoji : [emoji];
|
|
957
1015
|
if (addToExisting) {
|
|
958
1016
|
// Fetch current reactions to preserve them
|
|
959
|
-
const msgs = await this.client.getMessages(
|
|
1017
|
+
const msgs = await this.client.getMessages(resolved, { ids: [messageId] });
|
|
960
1018
|
const msg = msgs[0];
|
|
961
1019
|
if (msg?.reactions?.results) {
|
|
962
1020
|
for (const r of msg.reactions.results) {
|
|
@@ -989,9 +1047,10 @@ export class TelegramService {
|
|
|
989
1047
|
async getMessageReactions(chatId, messageId) {
|
|
990
1048
|
if (!this.client || !this.connected)
|
|
991
1049
|
throw new Error("Not connected");
|
|
992
|
-
const
|
|
1050
|
+
const resolved = await this.resolvePeer(chatId);
|
|
1051
|
+
const peer = await this.client.getInputEntity(resolved);
|
|
993
1052
|
// First get the message to know which reactions exist
|
|
994
|
-
const msgs = await this.client.getMessages(
|
|
1053
|
+
const msgs = await this.client.getMessages(resolved, { ids: [messageId] });
|
|
995
1054
|
const msg = msgs[0];
|
|
996
1055
|
if (!msg?.reactions?.results?.length) {
|
|
997
1056
|
return { reactions: [], total: 0 };
|
|
@@ -1043,7 +1102,8 @@ export class TelegramService {
|
|
|
1043
1102
|
async sendScheduledMessage(chatId, text, scheduleDate, replyTo, parseMode) {
|
|
1044
1103
|
if (!this.client || !this.connected)
|
|
1045
1104
|
throw new Error("Not connected");
|
|
1046
|
-
await this.
|
|
1105
|
+
const resolved = await this.resolvePeer(chatId);
|
|
1106
|
+
await this.client.sendMessage(resolved, {
|
|
1047
1107
|
message: text,
|
|
1048
1108
|
schedule: scheduleDate,
|
|
1049
1109
|
...(replyTo ? { replyTo } : {}),
|
|
@@ -1089,7 +1149,7 @@ export class TelegramService {
|
|
|
1089
1149
|
async getForumTopics(chatId, limit = 100) {
|
|
1090
1150
|
if (!this.client || !this.connected)
|
|
1091
1151
|
throw new Error("Not connected");
|
|
1092
|
-
const entity = await this.
|
|
1152
|
+
const entity = await this.resolveChat(chatId);
|
|
1093
1153
|
if (!(entity instanceof Api.Channel))
|
|
1094
1154
|
throw new Error("Forum topics are only available in supergroups");
|
|
1095
1155
|
const result = await this.client.invoke(new Api.channels.GetForumTopics({
|
|
@@ -1148,7 +1208,7 @@ export class TelegramService {
|
|
|
1148
1208
|
if (!this.client || !this.connected)
|
|
1149
1209
|
throw new Error("Not connected");
|
|
1150
1210
|
try {
|
|
1151
|
-
const entity = await this.
|
|
1211
|
+
const entity = await this.resolveChat(chatId);
|
|
1152
1212
|
if (entity instanceof Api.Channel) {
|
|
1153
1213
|
return Boolean(entity.forum);
|
|
1154
1214
|
}
|
|
@@ -1261,4 +1321,179 @@ export class TelegramService {
|
|
|
1261
1321
|
throw new Error("Failed to create group");
|
|
1262
1322
|
return { id: chat.id.toString(), title, type: "group" };
|
|
1263
1323
|
}
|
|
1324
|
+
async inviteToGroup(chatId, users) {
|
|
1325
|
+
if (!this.client)
|
|
1326
|
+
throw new Error("Not connected");
|
|
1327
|
+
const entity = await this.resolveChat(chatId);
|
|
1328
|
+
const invited = [];
|
|
1329
|
+
const failed = [];
|
|
1330
|
+
for (const u of users) {
|
|
1331
|
+
try {
|
|
1332
|
+
const user = await this.client.getEntity(u);
|
|
1333
|
+
if (!(user instanceof Api.User)) {
|
|
1334
|
+
failed.push(u);
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
1337
|
+
const inputUser = new Api.InputUser({ userId: user.id, accessHash: user.accessHash ?? bigInt.zero });
|
|
1338
|
+
if (entity instanceof Api.Channel) {
|
|
1339
|
+
await this.client.invoke(new Api.channels.InviteToChannel({ channel: entity, users: [inputUser] }));
|
|
1340
|
+
}
|
|
1341
|
+
else if (entity instanceof Api.Chat) {
|
|
1342
|
+
await this.client.invoke(new Api.messages.AddChatUser({ chatId: entity.id, userId: inputUser, fwdLimit: 50 }));
|
|
1343
|
+
}
|
|
1344
|
+
invited.push(u);
|
|
1345
|
+
}
|
|
1346
|
+
catch {
|
|
1347
|
+
failed.push(u);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return { invited, failed };
|
|
1351
|
+
}
|
|
1352
|
+
async kickUser(chatId, userId) {
|
|
1353
|
+
if (!this.client)
|
|
1354
|
+
throw new Error("Not connected");
|
|
1355
|
+
const entity = await this.resolveChat(chatId);
|
|
1356
|
+
const user = await this.client.getEntity(userId);
|
|
1357
|
+
if (!(user instanceof Api.User))
|
|
1358
|
+
throw new Error("Target is not a user");
|
|
1359
|
+
const inputUser = new Api.InputUser({ userId: user.id, accessHash: user.accessHash ?? bigInt.zero });
|
|
1360
|
+
if (entity instanceof Api.Channel) {
|
|
1361
|
+
// Kick = ban + unban (removes without permanent ban)
|
|
1362
|
+
await this.client.invoke(new Api.channels.EditBanned({
|
|
1363
|
+
channel: entity,
|
|
1364
|
+
participant: inputUser,
|
|
1365
|
+
bannedRights: new Api.ChatBannedRights({ untilDate: 0, viewMessages: true }),
|
|
1366
|
+
}));
|
|
1367
|
+
await this.client.invoke(new Api.channels.EditBanned({
|
|
1368
|
+
channel: entity,
|
|
1369
|
+
participant: inputUser,
|
|
1370
|
+
bannedRights: new Api.ChatBannedRights({ untilDate: 0 }),
|
|
1371
|
+
}));
|
|
1372
|
+
}
|
|
1373
|
+
else if (entity instanceof Api.Chat) {
|
|
1374
|
+
await this.client.invoke(new Api.messages.DeleteChatUser({ chatId: entity.id, userId: inputUser }));
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
async banUser(chatId, userId) {
|
|
1378
|
+
if (!this.client)
|
|
1379
|
+
throw new Error("Not connected");
|
|
1380
|
+
const entity = await this.resolveChat(chatId);
|
|
1381
|
+
const user = await this.client.getEntity(userId);
|
|
1382
|
+
if (!(user instanceof Api.User))
|
|
1383
|
+
throw new Error("Target is not a user");
|
|
1384
|
+
if (!(entity instanceof Api.Channel))
|
|
1385
|
+
throw new Error("Ban is only supported for supergroups and channels");
|
|
1386
|
+
const inputUser = new Api.InputUser({ userId: user.id, accessHash: user.accessHash ?? bigInt.zero });
|
|
1387
|
+
await this.client.invoke(new Api.channels.EditBanned({
|
|
1388
|
+
channel: entity,
|
|
1389
|
+
participant: inputUser,
|
|
1390
|
+
bannedRights: new Api.ChatBannedRights({ untilDate: 0, viewMessages: true }),
|
|
1391
|
+
}));
|
|
1392
|
+
}
|
|
1393
|
+
async unbanUser(chatId, userId) {
|
|
1394
|
+
if (!this.client)
|
|
1395
|
+
throw new Error("Not connected");
|
|
1396
|
+
const entity = await this.resolveChat(chatId);
|
|
1397
|
+
const user = await this.client.getEntity(userId);
|
|
1398
|
+
if (!(user instanceof Api.User))
|
|
1399
|
+
throw new Error("Target is not a user");
|
|
1400
|
+
if (!(entity instanceof Api.Channel))
|
|
1401
|
+
throw new Error("Unban is only supported for supergroups and channels");
|
|
1402
|
+
const inputUser = new Api.InputUser({ userId: user.id, accessHash: user.accessHash ?? bigInt.zero });
|
|
1403
|
+
await this.client.invoke(new Api.channels.EditBanned({
|
|
1404
|
+
channel: entity,
|
|
1405
|
+
participant: inputUser,
|
|
1406
|
+
bannedRights: new Api.ChatBannedRights({ untilDate: 0 }),
|
|
1407
|
+
}));
|
|
1408
|
+
}
|
|
1409
|
+
async editGroup(chatId, options) {
|
|
1410
|
+
if (!this.client)
|
|
1411
|
+
throw new Error("Not connected");
|
|
1412
|
+
const entity = await this.resolveChat(chatId);
|
|
1413
|
+
if (options.title) {
|
|
1414
|
+
if (entity instanceof Api.Channel) {
|
|
1415
|
+
await this.client.invoke(new Api.channels.EditTitle({ channel: entity, title: options.title }));
|
|
1416
|
+
}
|
|
1417
|
+
else if (entity instanceof Api.Chat) {
|
|
1418
|
+
await this.client.invoke(new Api.messages.EditChatTitle({ chatId: entity.id, title: options.title }));
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
if (options.description != null) {
|
|
1422
|
+
await this.client.invoke(new Api.messages.EditChatAbout({ peer: entity, about: options.description }));
|
|
1423
|
+
}
|
|
1424
|
+
if (options.photoPath) {
|
|
1425
|
+
const fileData = readFileSync(options.photoPath);
|
|
1426
|
+
const uploaded = await this.client.uploadFile({
|
|
1427
|
+
file: new CustomFile(options.photoPath, fileData.length, options.photoPath, fileData),
|
|
1428
|
+
workers: 1,
|
|
1429
|
+
});
|
|
1430
|
+
const inputPhoto = new Api.InputChatUploadedPhoto({ file: uploaded });
|
|
1431
|
+
if (entity instanceof Api.Channel) {
|
|
1432
|
+
await this.client.invoke(new Api.channels.EditPhoto({ channel: entity, photo: inputPhoto }));
|
|
1433
|
+
}
|
|
1434
|
+
else if (entity instanceof Api.Chat) {
|
|
1435
|
+
await this.client.invoke(new Api.messages.EditChatPhoto({ chatId: entity.id, photo: inputPhoto }));
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
async leaveGroup(chatId) {
|
|
1440
|
+
if (!this.client)
|
|
1441
|
+
throw new Error("Not connected");
|
|
1442
|
+
const entity = await this.resolveChat(chatId);
|
|
1443
|
+
if (entity instanceof Api.Channel) {
|
|
1444
|
+
await this.client.invoke(new Api.channels.LeaveChannel({ channel: entity }));
|
|
1445
|
+
}
|
|
1446
|
+
else if (entity instanceof Api.Chat) {
|
|
1447
|
+
await this.client.invoke(new Api.messages.DeleteChatUser({
|
|
1448
|
+
chatId: entity.id,
|
|
1449
|
+
userId: new Api.InputUserSelf(),
|
|
1450
|
+
}));
|
|
1451
|
+
}
|
|
1452
|
+
else {
|
|
1453
|
+
throw new Error("Target is not a group or channel");
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
async setAdmin(chatId, userId, options) {
|
|
1457
|
+
if (!this.client)
|
|
1458
|
+
throw new Error("Not connected");
|
|
1459
|
+
const entity = await this.resolveChat(chatId);
|
|
1460
|
+
if (!(entity instanceof Api.Channel))
|
|
1461
|
+
throw new Error("Set admin is only supported for supergroups and channels");
|
|
1462
|
+
const user = await this.client.getEntity(userId);
|
|
1463
|
+
if (!(user instanceof Api.User))
|
|
1464
|
+
throw new Error("Target is not a user");
|
|
1465
|
+
const inputUser = new Api.InputUser({ userId: user.id, accessHash: user.accessHash ?? bigInt.zero });
|
|
1466
|
+
await this.client.invoke(new Api.channels.EditAdmin({
|
|
1467
|
+
channel: entity,
|
|
1468
|
+
userId: inputUser,
|
|
1469
|
+
adminRights: new Api.ChatAdminRights({
|
|
1470
|
+
changeInfo: true,
|
|
1471
|
+
postMessages: true,
|
|
1472
|
+
editMessages: true,
|
|
1473
|
+
deleteMessages: true,
|
|
1474
|
+
banUsers: true,
|
|
1475
|
+
inviteUsers: true,
|
|
1476
|
+
pinMessages: true,
|
|
1477
|
+
manageCall: true,
|
|
1478
|
+
}),
|
|
1479
|
+
rank: options?.title ?? "",
|
|
1480
|
+
}));
|
|
1481
|
+
}
|
|
1482
|
+
async removeAdmin(chatId, userId) {
|
|
1483
|
+
if (!this.client)
|
|
1484
|
+
throw new Error("Not connected");
|
|
1485
|
+
const entity = await this.resolveChat(chatId);
|
|
1486
|
+
if (!(entity instanceof Api.Channel))
|
|
1487
|
+
throw new Error("Remove admin is only supported for supergroups and channels");
|
|
1488
|
+
const user = await this.client.getEntity(userId);
|
|
1489
|
+
if (!(user instanceof Api.User))
|
|
1490
|
+
throw new Error("Target is not a user");
|
|
1491
|
+
const inputUser = new Api.InputUser({ userId: user.id, accessHash: user.accessHash ?? bigInt.zero });
|
|
1492
|
+
await this.client.invoke(new Api.channels.EditAdmin({
|
|
1493
|
+
channel: entity,
|
|
1494
|
+
userId: inputUser,
|
|
1495
|
+
adminRights: new Api.ChatAdminRights({}),
|
|
1496
|
+
rank: "",
|
|
1497
|
+
}));
|
|
1498
|
+
}
|
|
1264
1499
|
}
|
package/dist/tools/chats.js
CHANGED
|
@@ -145,4 +145,164 @@ export function registerChatTools(server, telegram) {
|
|
|
145
145
|
return fail(e);
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
|
+
server.registerTool("telegram-leave-group", {
|
|
149
|
+
description: "Leave a Telegram group or channel",
|
|
150
|
+
inputSchema: {
|
|
151
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
152
|
+
},
|
|
153
|
+
annotations: WRITE,
|
|
154
|
+
}, async ({ chatId }) => {
|
|
155
|
+
const err = await requireConnection(telegram);
|
|
156
|
+
if (err)
|
|
157
|
+
return fail(new Error(err));
|
|
158
|
+
try {
|
|
159
|
+
await telegram.leaveGroup(chatId);
|
|
160
|
+
return ok(`Left chat ${chatId}`);
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
return fail(e);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
server.registerTool("telegram-invite-to-group", {
|
|
167
|
+
description: "Invite users to a Telegram group or channel",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
170
|
+
users: z.array(z.string()).describe("Usernames or IDs to invite"),
|
|
171
|
+
},
|
|
172
|
+
annotations: WRITE,
|
|
173
|
+
}, async ({ chatId, users }) => {
|
|
174
|
+
const err = await requireConnection(telegram);
|
|
175
|
+
if (err)
|
|
176
|
+
return fail(new Error(err));
|
|
177
|
+
try {
|
|
178
|
+
const result = await telegram.inviteToGroup(chatId, users);
|
|
179
|
+
const lines = [];
|
|
180
|
+
if (result.invited.length > 0)
|
|
181
|
+
lines.push(`Invited: ${result.invited.join(", ")}`);
|
|
182
|
+
if (result.failed.length > 0)
|
|
183
|
+
lines.push(`Failed: ${result.failed.join(", ")}`);
|
|
184
|
+
return ok(lines.join("\n") || "No users processed");
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
return fail(e);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
server.registerTool("telegram-kick-user", {
|
|
191
|
+
description: "Kick a user from a Telegram group (removes without permanent ban)",
|
|
192
|
+
inputSchema: {
|
|
193
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
194
|
+
userId: z.string().describe("User ID or username to kick"),
|
|
195
|
+
},
|
|
196
|
+
annotations: WRITE,
|
|
197
|
+
}, async ({ chatId, userId }) => {
|
|
198
|
+
const err = await requireConnection(telegram);
|
|
199
|
+
if (err)
|
|
200
|
+
return fail(new Error(err));
|
|
201
|
+
try {
|
|
202
|
+
await telegram.kickUser(chatId, userId);
|
|
203
|
+
return ok(`Kicked ${userId} from ${chatId}`);
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
return fail(e);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
server.registerTool("telegram-ban-user", {
|
|
210
|
+
description: "Ban a user from a supergroup or channel (permanent until unbanned)",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
213
|
+
userId: z.string().describe("User ID or username to ban"),
|
|
214
|
+
},
|
|
215
|
+
annotations: WRITE,
|
|
216
|
+
}, async ({ chatId, userId }) => {
|
|
217
|
+
const err = await requireConnection(telegram);
|
|
218
|
+
if (err)
|
|
219
|
+
return fail(new Error(err));
|
|
220
|
+
try {
|
|
221
|
+
await telegram.banUser(chatId, userId);
|
|
222
|
+
return ok(`Banned ${userId} from ${chatId}`);
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
return fail(e);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
server.registerTool("telegram-unban-user", {
|
|
229
|
+
description: "Unban a previously banned user from a supergroup or channel",
|
|
230
|
+
inputSchema: {
|
|
231
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
232
|
+
userId: z.string().describe("User ID or username to unban"),
|
|
233
|
+
},
|
|
234
|
+
annotations: WRITE,
|
|
235
|
+
}, async ({ chatId, userId }) => {
|
|
236
|
+
const err = await requireConnection(telegram);
|
|
237
|
+
if (err)
|
|
238
|
+
return fail(new Error(err));
|
|
239
|
+
try {
|
|
240
|
+
await telegram.unbanUser(chatId, userId);
|
|
241
|
+
return ok(`Unbanned ${userId} in ${chatId}`);
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
return fail(e);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
server.registerTool("telegram-edit-group", {
|
|
248
|
+
description: "Edit a group's title, description, or photo",
|
|
249
|
+
inputSchema: {
|
|
250
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
251
|
+
title: z.string().optional().describe("New group title"),
|
|
252
|
+
description: z.string().optional().describe("New group description (supergroups only)"),
|
|
253
|
+
photoPath: z.string().optional().describe("Absolute path to new group photo image file"),
|
|
254
|
+
},
|
|
255
|
+
annotations: WRITE,
|
|
256
|
+
}, async ({ chatId, title, description, photoPath }) => {
|
|
257
|
+
const err = await requireConnection(telegram);
|
|
258
|
+
if (err)
|
|
259
|
+
return fail(new Error(err));
|
|
260
|
+
try {
|
|
261
|
+
await telegram.editGroup(chatId, { title, description, photoPath });
|
|
262
|
+
const changed = [title && "title", description != null && "description", photoPath && "photo"].filter(Boolean);
|
|
263
|
+
return ok(`Updated ${changed.join(", ")} for ${chatId}`);
|
|
264
|
+
}
|
|
265
|
+
catch (e) {
|
|
266
|
+
return fail(e);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
server.registerTool("telegram-set-admin", {
|
|
270
|
+
description: "Promote a user to admin in a supergroup or channel with full permissions",
|
|
271
|
+
inputSchema: {
|
|
272
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
273
|
+
userId: z.string().describe("User ID or username to promote"),
|
|
274
|
+
title: z.string().optional().describe("Custom admin title"),
|
|
275
|
+
},
|
|
276
|
+
annotations: WRITE,
|
|
277
|
+
}, async ({ chatId, userId, title }) => {
|
|
278
|
+
const err = await requireConnection(telegram);
|
|
279
|
+
if (err)
|
|
280
|
+
return fail(new Error(err));
|
|
281
|
+
try {
|
|
282
|
+
await telegram.setAdmin(chatId, userId, { title });
|
|
283
|
+
return ok(`Promoted ${userId} to admin in ${chatId}${title ? ` (${title})` : ""}`);
|
|
284
|
+
}
|
|
285
|
+
catch (e) {
|
|
286
|
+
return fail(e);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
server.registerTool("telegram-remove-admin", {
|
|
290
|
+
description: "Remove admin rights from a user in a supergroup or channel",
|
|
291
|
+
inputSchema: {
|
|
292
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
293
|
+
userId: z.string().describe("User ID or username to demote"),
|
|
294
|
+
},
|
|
295
|
+
annotations: WRITE,
|
|
296
|
+
}, async ({ chatId, userId }) => {
|
|
297
|
+
const err = await requireConnection(telegram);
|
|
298
|
+
if (err)
|
|
299
|
+
return fail(new Error(err));
|
|
300
|
+
try {
|
|
301
|
+
await telegram.removeAdmin(chatId, userId);
|
|
302
|
+
return ok(`Removed admin rights from ${userId} in ${chatId}`);
|
|
303
|
+
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
return fail(e);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
148
308
|
}
|
package/package.json
CHANGED