mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
Fix/telegram login (#4)
* Add UserProvider to App component and enhance user data fetching logic - Integrated UserProvider into the App component to manage user state. - Updated UserProvider to fetch current user data on token availability and handle token expiration by clearing the token on fetch failure. - Improved user data fetching logic for better error handling and user experience. * Enhance Telegram connection handling and error management - Added logic to handle Telegram account mismatch, providing user feedback and resetting connection state. - Improved user verification during authentication to ensure the logged-in account matches the Telegram account. - Refactored connection status checks and error handling for better user experience. - Introduced a method to clear session and disconnect when account mismatch occurs. - Updated polling mechanism for authentication status to ensure timely updates. This update improves the robustness of the Telegram connection process and enhances user feedback during authentication failures. * ran prettier * Refactor socket and auth state management for user-specific handling - Updated socket state management to support multiple users by introducing user-specific selectors and actions. - Refactored connection status handling in the socket service to dispatch user-specific status updates. - Enhanced onboarding state management to track completion per user, allowing for more granular control. - Introduced new selectors for socket and auth states to improve readability and maintainability. - Updated relevant components to utilize the new user-specific state management. This update improves the application's ability to handle multiple users and enhances the overall user experience during onboarding and socket connections. * Enhance MTProto client session management and introduce user-specific session handling - Updated the MTProtoService to manage sessions per user by storing session data under a user-specific key. - Modified the initialize method to accept a userId parameter, allowing for user-specific session initialization and management. - Improved session loading and saving logic to ensure proper handling of user sessions. - Added a utility function to generate session keys based on userId for better maintainability. This update enhances the application's ability to handle multiple user sessions effectively. * Refactor Telegram state management for user-specific handling - Introduced user-specific state management in the Telegram store, allowing for better handling of multiple users. - Updated selectors to retrieve state based on the current user, improving data encapsulation and reducing global state dependencies. - Enhanced reducers and thunks to ensure actions are dispatched with user context, maintaining user-specific data integrity. - Modified persistence configuration to store Telegram state scoped by user, ensuring isolated state management. This update significantly improves the application's ability to manage multiple user sessions effectively and enhances overall user experience. * Enhance MTProtoService connection handling with user-specific initialization - Updated the checkConnection method to accept an optional userId parameter, allowing for user-specific connection initialization. - Modified the connection logic to ensure proper initialization only occurs when a userId is provided, improving session management. - Refactored related thunks to use a consistent naming convention for userId parameters, enhancing code clarity. This update improves the handling of user sessions in the MTProtoService, aligning with recent enhancements in user-specific state management. * Refactor Telegram connection handling for user-specific management - Updated the TelegramConnectionModal to utilize user-specific identifiers for connection status and authentication processes, enhancing session management. - Refactored related components and selectors to ensure userId is consistently passed and utilized, improving clarity and maintainability. - Enhanced the ConnectionsPanel and ConnectStep to check for saved sessions based on userId, allowing for better handling of multiple user connections. This update significantly improves the application's ability to manage user-specific Telegram connections and enhances the overall user experience. * Refactor Telegram connection logic to simplify session management - Removed unnecessary userId checks and localStorage interactions from ConnectionsPanel and ConnectStep components, streamlining the connection status determination. - Updated MTProtoService to manage session data through Redux instead of localStorage, enhancing consistency and maintainability. - Improved session loading and saving logic to focus solely on Redux state, ensuring better integration with user-specific session handling. This update enhances the clarity and efficiency of the Telegram connection management process, aligning with recent improvements in user-specific state management. * Update CLAUDE.md to clarify localStorage usage and emphasize Redux for state management - Revised documentation to discourage the use of localStorage and sessionStorage for app state, advocating for Redux and Redux Persist instead. - Added guidelines for removing existing localStorage usage and migrating relevant data to Redux. - Updated service layer documentation to reflect changes in session management, specifying that session data is now stored in Redux rather than localStorage. - Enhanced clarity on deep link handling and the prevention of infinite reload loops. This update aligns with recent improvements in user-specific state management and reinforces best practices for state handling. * Enhance onboarding process with error handling and API integration - Updated the Onboarding component to include an API call for marking onboarding as complete, with error handling to provide user feedback. - Refactored the GetStartedStep component to support asynchronous completion handling, including loading state and error messages. - Introduced a new onboardingComplete method in the userApi service to facilitate the onboarding completion process. This update improves the user experience during onboarding by ensuring proper error management and feedback. * Enhance TelegramConnectionModal with socket connection management and improved initialization flow - Integrated socket connection handling in the TelegramConnectionModal to ensure the socket is connected when the modal opens, enhancing real-time features. - Refactored the initialization logic to streamline the connection process, including user verification and error handling. - Introduced a reference to track the QR code flow state, preventing multiple initiations and improving user experience during authentication. This update improves the reliability of the Telegram connection process and enhances user feedback during the onboarding experience. * Refactor Telegram connection checks for improved logic consistency - Updated the ConnectionsPanel and ConnectStep components to refine the logic for determining if a Telegram connection is established, ensuring that both session string and authentication status are required for a valid connection. - Simplified className definitions in the UI components for better readability and maintainability. This update enhances the clarity of connection status checks and improves the overall user experience during the onboarding process. * Refactor Telegram authentication flow and improve error handling - Simplified the TelegramLoginButton component by removing complex authentication logic and replacing it with a direct link to the Telegram bot for login. - Enhanced the Login page to consume a login token from the URL, providing better error handling and user feedback during the authentication process. - Introduced a new API service for consuming login tokens, ensuring secure retrieval of JWT tokens from the backend. This update streamlines the authentication experience and improves error management, enhancing overall user experience during login.
This commit is contained in:
+29
-29
@@ -2,42 +2,43 @@
|
||||
* Error handling utilities for MCP server
|
||||
*/
|
||||
|
||||
import type { MCPToolResult } from './types';
|
||||
import { ValidationError } from './validation';
|
||||
import type { MCPToolResult } from "./types";
|
||||
import { ValidationError } from "./validation";
|
||||
|
||||
export enum ErrorCategory {
|
||||
CHAT = 'CHAT',
|
||||
MSG = 'MSG',
|
||||
CONTACT = 'CONTACT',
|
||||
GROUP = 'GROUP',
|
||||
MEDIA = 'MEDIA',
|
||||
PROFILE = 'PROFILE',
|
||||
AUTH = 'AUTH',
|
||||
ADMIN = 'ADMIN',
|
||||
VALIDATION = 'VALIDATION',
|
||||
SEARCH = 'SEARCH',
|
||||
DRAFT = 'DRAFT',
|
||||
CHAT = "CHAT",
|
||||
MSG = "MSG",
|
||||
CONTACT = "CONTACT",
|
||||
GROUP = "GROUP",
|
||||
MEDIA = "MEDIA",
|
||||
PROFILE = "PROFILE",
|
||||
AUTH = "AUTH",
|
||||
ADMIN = "ADMIN",
|
||||
VALIDATION = "VALIDATION",
|
||||
SEARCH = "SEARCH",
|
||||
DRAFT = "DRAFT",
|
||||
}
|
||||
|
||||
function generateErrorCode(
|
||||
functionName: string,
|
||||
category?: ErrorCategory | string,
|
||||
): string {
|
||||
if (category === 'VALIDATION-001' || category === ErrorCategory.VALIDATION) {
|
||||
return 'VALIDATION-001';
|
||||
if (category === "VALIDATION-001" || category === ErrorCategory.VALIDATION) {
|
||||
return "VALIDATION-001";
|
||||
}
|
||||
|
||||
const prefix = category
|
||||
? typeof category === 'string' && category.startsWith('VALIDATION')
|
||||
? typeof category === "string" && category.startsWith("VALIDATION")
|
||||
? category
|
||||
: category
|
||||
: 'GEN';
|
||||
: "GEN";
|
||||
|
||||
const hash = Math.abs(
|
||||
functionName.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0),
|
||||
) % 1000;
|
||||
const hash =
|
||||
Math.abs(
|
||||
functionName.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0),
|
||||
) % 1000;
|
||||
|
||||
return `${prefix}-ERR-${hash.toString().padStart(3, '0')}`;
|
||||
return `${prefix}-ERR-${hash.toString().padStart(3, "0")}`;
|
||||
}
|
||||
|
||||
export function logAndFormatError(
|
||||
@@ -50,8 +51,8 @@ export function logAndFormatError(
|
||||
const contextStr = context
|
||||
? Object.entries(context)
|
||||
.map(([k, v]) => `${k}=${String(v)}`)
|
||||
.join(', ')
|
||||
: '';
|
||||
.join(", ")
|
||||
: "";
|
||||
|
||||
console.error(
|
||||
`[MCP] Error in ${functionName} (${contextStr}) - Code: ${errorCode}`,
|
||||
@@ -64,20 +65,19 @@ export function logAndFormatError(
|
||||
: `An error occurred (code: ${errorCode}). Check logs for details.`;
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: userMessage }],
|
||||
content: [{ type: "text", text: userMessage }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function withErrorHandling<T extends (...args: unknown[]) => Promise<MCPToolResult>>(
|
||||
fn: T,
|
||||
category?: ErrorCategory,
|
||||
): T {
|
||||
export function withErrorHandling<
|
||||
T extends (...args: unknown[]) => Promise<MCPToolResult>,
|
||||
>(fn: T, category?: ErrorCategory): T {
|
||||
return (async (...args: Parameters<T>): Promise<MCPToolResult> => {
|
||||
try {
|
||||
return await fn(...args);
|
||||
} catch (error) {
|
||||
const functionName = fn.name || 'unknown';
|
||||
const functionName = fn.name || "unknown";
|
||||
return logAndFormatError(
|
||||
functionName,
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Used by MCP servers (e.g. telegram, gmail, etc.)
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
export * from './validation';
|
||||
export * from './errorHandler';
|
||||
export * from './logger';
|
||||
export { SocketIOMCPTransportImpl } from './transport';
|
||||
export * from "./types";
|
||||
export * from "./validation";
|
||||
export * from "./errorHandler";
|
||||
export * from "./logger";
|
||||
export { SocketIOMCPTransportImpl } from "./transport";
|
||||
|
||||
+11
-6
@@ -2,23 +2,28 @@
|
||||
* MCP logger - simple console logger with [MCP] prefix
|
||||
*/
|
||||
|
||||
type LogLevel = 'log' | 'warn' | 'error';
|
||||
type LogLevel = "log" | "warn" | "error";
|
||||
|
||||
const PREFIX = '[MCP]';
|
||||
const PREFIX = "[MCP]";
|
||||
|
||||
function log(level: LogLevel, message: string, ...data: unknown[]): void {
|
||||
const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
|
||||
const fn =
|
||||
level === "error"
|
||||
? console.error
|
||||
: level === "warn"
|
||||
? console.warn
|
||||
: console.log;
|
||||
fn(PREFIX, message, ...data);
|
||||
}
|
||||
|
||||
export function mcpLog(message: string, ...data: unknown[]): void {
|
||||
log('log', message, ...data);
|
||||
log("log", message, ...data);
|
||||
}
|
||||
|
||||
export function mcpWarn(message: string, ...data: unknown[]): void {
|
||||
log('warn', message, ...data);
|
||||
log("warn", message, ...data);
|
||||
}
|
||||
|
||||
export function mcpError(message: string, ...data: unknown[]): void {
|
||||
log('error', message, ...data);
|
||||
log("error", message, ...data);
|
||||
}
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
* Main entry point for Telegram MCP integration
|
||||
*/
|
||||
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import { TelegramMCPServer } from './server';
|
||||
import type { Socket } from "socket.io-client";
|
||||
import { TelegramMCPServer } from "./server";
|
||||
|
||||
let telegramMCPInstance: TelegramMCPServer | undefined;
|
||||
|
||||
export function initTelegramMCPServer(socket: Socket | null | undefined): TelegramMCPServer {
|
||||
export function initTelegramMCPServer(
|
||||
socket: Socket | null | undefined,
|
||||
): TelegramMCPServer {
|
||||
telegramMCPInstance = new TelegramMCPServer(socket);
|
||||
console.log('[MCP] Telegram MCP server initialized');
|
||||
console.log("[MCP] Telegram MCP server initialized");
|
||||
return telegramMCPInstance;
|
||||
}
|
||||
|
||||
@@ -18,19 +20,21 @@ export function getTelegramMCPServer(): TelegramMCPServer | undefined {
|
||||
return telegramMCPInstance;
|
||||
}
|
||||
|
||||
export function updateTelegramMCPServerSocket(socket: Socket | null | undefined): void {
|
||||
export function updateTelegramMCPServerSocket(
|
||||
socket: Socket | null | undefined,
|
||||
): void {
|
||||
if (telegramMCPInstance) {
|
||||
telegramMCPInstance.updateSocket(socket);
|
||||
console.log('[MCP] Telegram MCP server socket updated');
|
||||
console.log("[MCP] Telegram MCP server socket updated");
|
||||
}
|
||||
}
|
||||
|
||||
export function cleanupTelegramMCPServer(): void {
|
||||
if (telegramMCPInstance) {
|
||||
telegramMCPInstance = undefined;
|
||||
console.log('[MCP] Telegram MCP server cleaned up');
|
||||
console.log("[MCP] Telegram MCP server cleaned up");
|
||||
}
|
||||
}
|
||||
|
||||
export { toHumanReadableAction } from './toolActionParser';
|
||||
export type { TelegramMCPServer } from './server';
|
||||
export { toHumanReadableAction } from "./toolActionParser";
|
||||
export type { TelegramMCPServer } from "./server";
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
} from "../types";
|
||||
|
||||
import { store } from "../../../store";
|
||||
import { selectTelegramUserState } from "../../../store/telegramSelectors";
|
||||
import { ErrorCategory, logAndFormatError } from "../errorHandler";
|
||||
import { ValidationError } from "../validation";
|
||||
import { SocketIOMCPTransportImpl } from "../transport";
|
||||
@@ -104,7 +105,8 @@ export class TelegramMCPServer {
|
||||
};
|
||||
}
|
||||
|
||||
const telegramState: TelegramState = store.getState().telegram;
|
||||
const telegramState: TelegramState =
|
||||
selectTelegramUserState(store.getState());
|
||||
|
||||
try {
|
||||
return await toolHandler(args, {
|
||||
|
||||
@@ -3,13 +3,22 @@
|
||||
* Uses mtprotoService + Redux telegram state (alphahuman)
|
||||
*/
|
||||
|
||||
import { store } from '../../../store';
|
||||
import { mtprotoService } from '../../../services/mtprotoService';
|
||||
import { store } from "../../../store";
|
||||
import { mtprotoService } from "../../../services/mtprotoService";
|
||||
import {
|
||||
selectOrderedChats,
|
||||
selectCurrentUser,
|
||||
} from '../../../store/telegramSelectors';
|
||||
import type { TelegramChat, TelegramUser, TelegramMessage } from '../../../store/telegram/types';
|
||||
selectTelegramUserState,
|
||||
} from "../../../store/telegramSelectors";
|
||||
import type {
|
||||
TelegramChat,
|
||||
TelegramUser,
|
||||
TelegramMessage,
|
||||
} from "../../../store/telegram/types";
|
||||
|
||||
function getTelegramState() {
|
||||
return selectTelegramUserState(store.getState());
|
||||
}
|
||||
|
||||
export interface FormattedEntity {
|
||||
id: string;
|
||||
@@ -28,10 +37,6 @@ export interface FormattedMessage {
|
||||
media_type?: string;
|
||||
}
|
||||
|
||||
function getTelegramState() {
|
||||
return store.getState().telegram;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get chat by ID or username
|
||||
*/
|
||||
@@ -42,10 +47,15 @@ export function getChatById(chatId: string | number): TelegramChat | undefined {
|
||||
const chat = state.chats[idStr];
|
||||
if (chat) return chat;
|
||||
|
||||
if (typeof chatId === 'string' && (chatId.startsWith('@') || /^[a-zA-Z0-9_]+$/.test(chatId))) {
|
||||
const username = chatId.startsWith('@') ? chatId : `@${chatId}`;
|
||||
if (
|
||||
typeof chatId === "string" &&
|
||||
(chatId.startsWith("@") || /^[a-zA-Z0-9_]+$/.test(chatId))
|
||||
) {
|
||||
const username = chatId.startsWith("@") ? chatId : `@${chatId}`;
|
||||
return Object.values(state.chats).find(
|
||||
(c) => c.username && (c.username === username || c.username === username.slice(1)),
|
||||
(c) =>
|
||||
c.username &&
|
||||
(c.username === username || c.username === username.slice(1)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,7 +105,7 @@ export async function sendMessage(
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return undefined;
|
||||
|
||||
const entity = chat.username ? `@${chat.username.replace('@', '')}` : chat.id;
|
||||
const entity = chat.username ? `@${chat.username.replace("@", "")}` : chat.id;
|
||||
|
||||
if (replyToMessageId !== undefined) {
|
||||
const client = mtprotoService.getClient();
|
||||
@@ -129,8 +139,8 @@ export async function searchChats(query: string): Promise<TelegramChat[]> {
|
||||
const ordered = selectOrderedChats(state);
|
||||
const q = query.toLowerCase();
|
||||
return ordered.filter((c) => {
|
||||
const title = (c.title ?? '').toLowerCase();
|
||||
const un = (c.username ?? '').toLowerCase();
|
||||
const title = (c.title ?? "").toLowerCase();
|
||||
const un = (c.username ?? "").toLowerCase();
|
||||
return title.includes(q) || un.includes(q);
|
||||
});
|
||||
}
|
||||
@@ -146,23 +156,31 @@ export function getCurrentUser(): TelegramUser | undefined {
|
||||
/**
|
||||
* Format entity (chat or user) for display
|
||||
*/
|
||||
export function formatEntity(entity: TelegramChat | TelegramUser): FormattedEntity {
|
||||
if ('title' in entity) {
|
||||
export function formatEntity(
|
||||
entity: TelegramChat | TelegramUser,
|
||||
): FormattedEntity {
|
||||
if ("title" in entity) {
|
||||
const chat = entity as TelegramChat;
|
||||
const type = chat.type === 'channel' ? 'channel' : chat.type === 'supergroup' ? 'group' : chat.type;
|
||||
const type =
|
||||
chat.type === "channel"
|
||||
? "channel"
|
||||
: chat.type === "supergroup"
|
||||
? "group"
|
||||
: chat.type;
|
||||
return {
|
||||
id: chat.id,
|
||||
name: chat.title ?? 'Unknown',
|
||||
name: chat.title ?? "Unknown",
|
||||
type,
|
||||
username: chat.username,
|
||||
};
|
||||
}
|
||||
const user = entity as TelegramUser;
|
||||
const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown';
|
||||
const name =
|
||||
[user.firstName, user.lastName].filter(Boolean).join(" ") || "Unknown";
|
||||
return {
|
||||
id: user.id,
|
||||
name,
|
||||
type: 'user',
|
||||
type: "user",
|
||||
username: user.username,
|
||||
phone: user.phoneNumber,
|
||||
};
|
||||
@@ -175,7 +193,7 @@ export function formatMessage(message: TelegramMessage): FormattedMessage {
|
||||
const result: FormattedMessage = {
|
||||
id: message.id,
|
||||
date: new Date(message.date * 1000).toISOString(),
|
||||
text: message.message ?? '',
|
||||
text: message.message ?? "",
|
||||
};
|
||||
if (message.fromId) result.from_id = message.fromId;
|
||||
if (message.media?.type) {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
type ToolArguments = Record<string, unknown>;
|
||||
|
||||
function formatId(id: string | number | undefined, prefix = ''): string {
|
||||
if (!id) return '';
|
||||
function formatId(id: string | number | undefined, prefix = ""): string {
|
||||
if (!id) return "";
|
||||
const str = String(id);
|
||||
return prefix ? `${prefix} ${str}` : str;
|
||||
}
|
||||
@@ -15,7 +15,10 @@ function truncateText(text: string, maxLength = 50): string {
|
||||
return `${text.substring(0, maxLength)}...`;
|
||||
}
|
||||
|
||||
export function toHumanReadableAction(toolName: string, args: ToolArguments): string {
|
||||
export function toHumanReadableAction(
|
||||
toolName: string,
|
||||
args: ToolArguments,
|
||||
): string {
|
||||
const parser = toolParsers[toolName];
|
||||
if (parser) return parser(args);
|
||||
return `Executing ${toolName} with provided parameters`;
|
||||
@@ -23,165 +26,165 @@ export function toHumanReadableAction(toolName: string, args: ToolArguments): st
|
||||
|
||||
const toolParsers: Record<string, (args: ToolArguments) => string> = {
|
||||
send_message: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const message = truncateText((args.message as string) || '', 100);
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const message = truncateText((args.message as string) || "", 100);
|
||||
return `Send message to ${chatId}: "${message}"`;
|
||||
},
|
||||
reply_to_message: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
const text = truncateText((args.text as string) || '', 100);
|
||||
const text = truncateText((args.text as string) || "", 100);
|
||||
return `Reply to message ${messageId} in ${chatId}: "${text}"`;
|
||||
},
|
||||
edit_message: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
const newText = truncateText((args.new_text as string) || '', 100);
|
||||
const newText = truncateText((args.new_text as string) || "", 100);
|
||||
return `Edit message ${messageId} in ${chatId} to: "${newText}"`;
|
||||
},
|
||||
delete_message: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
return `Delete message ${messageId} from ${chatId}`;
|
||||
},
|
||||
forward_message: (args) => {
|
||||
const fromChat = formatId(args.from_chat_id as string | number, 'chat');
|
||||
const toChat = formatId(args.to_chat_id as string | number, 'chat');
|
||||
const fromChat = formatId(args.from_chat_id as string | number, "chat");
|
||||
const toChat = formatId(args.to_chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
return `Forward message ${messageId} from ${fromChat} to ${toChat}`;
|
||||
},
|
||||
pin_message: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
return `Pin message ${messageId} in ${chatId}`;
|
||||
},
|
||||
unpin_message: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
return `Unpin message ${messageId} in ${chatId}`;
|
||||
},
|
||||
mark_as_read: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Mark messages as read in ${chatId}`;
|
||||
},
|
||||
send_reaction: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
const reaction = (args.reaction as string) || '👍';
|
||||
const reaction = (args.reaction as string) || "👍";
|
||||
return `Add reaction ${reaction} to message ${messageId} in ${chatId}`;
|
||||
},
|
||||
remove_reaction: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
const reaction = (args.reaction as string) || '';
|
||||
const reaction = (args.reaction as string) || "";
|
||||
return `Remove reaction ${reaction} from message ${messageId} in ${chatId}`;
|
||||
},
|
||||
save_draft: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const text = truncateText((args.text as string) || '', 50);
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const text = truncateText((args.text as string) || "", 50);
|
||||
return `Save draft in ${chatId}: "${text}"`;
|
||||
},
|
||||
clear_draft: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Clear draft in ${chatId}`;
|
||||
},
|
||||
list_chats: (args) => {
|
||||
const chatType = (args.chat_type as string) || 'all';
|
||||
const chatType = (args.chat_type as string) || "all";
|
||||
const limit = (args.limit as number) || 20;
|
||||
return `List ${limit} ${chatType} chats`;
|
||||
},
|
||||
get_chat: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Get information for ${chatId}`;
|
||||
},
|
||||
create_group: (args) => {
|
||||
const title = (args.title as string) || 'Untitled Group';
|
||||
const title = (args.title as string) || "Untitled Group";
|
||||
const userCount = Array.isArray(args.user_ids) ? args.user_ids.length : 0;
|
||||
return `Create group "${title}" with ${userCount} member${userCount !== 1 ? 's' : ''}`;
|
||||
return `Create group "${title}" with ${userCount} member${userCount !== 1 ? "s" : ""}`;
|
||||
},
|
||||
create_channel: (args) => {
|
||||
const title = (args.title as string) || 'Untitled Channel';
|
||||
const title = (args.title as string) || "Untitled Channel";
|
||||
const description = args.description
|
||||
? `: ${truncateText((args.description as string) || '', 50)}`
|
||||
: '';
|
||||
? `: ${truncateText((args.description as string) || "", 50)}`
|
||||
: "";
|
||||
return `Create channel "${title}"${description}`;
|
||||
},
|
||||
edit_chat_title: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const newTitle = (args.new_title as string) || '';
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const newTitle = (args.new_title as string) || "";
|
||||
return `Change title of ${chatId} to "${newTitle}"`;
|
||||
},
|
||||
delete_chat_photo: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Remove photo from ${chatId}`;
|
||||
},
|
||||
edit_chat_photo: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Update photo for ${chatId}`;
|
||||
},
|
||||
leave_chat: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Leave ${chatId}`;
|
||||
},
|
||||
mute_chat: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const muteFor = args.mute_for ? ` for ${args.mute_for} seconds` : '';
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const muteFor = args.mute_for ? ` for ${args.mute_for} seconds` : "";
|
||||
return `Mute ${chatId}${muteFor}`;
|
||||
},
|
||||
unmute_chat: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Unmute ${chatId}`;
|
||||
},
|
||||
archive_chat: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Archive ${chatId}`;
|
||||
},
|
||||
unarchive_chat: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Unarchive ${chatId}`;
|
||||
},
|
||||
invite_to_group: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const userCount = Array.isArray(args.user_ids) ? args.user_ids.length : 0;
|
||||
return `Invite ${userCount} user${userCount !== 1 ? 's' : ''} to ${chatId}`;
|
||||
return `Invite ${userCount} user${userCount !== 1 ? "s" : ""} to ${chatId}`;
|
||||
},
|
||||
ban_user: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const userId = formatId(args.user_id as string | number, 'user');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const userId = formatId(args.user_id as string | number, "user");
|
||||
return `Ban ${userId} from ${chatId}`;
|
||||
},
|
||||
unban_user: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const userId = formatId(args.user_id as string | number, 'user');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const userId = formatId(args.user_id as string | number, "user");
|
||||
return `Unban ${userId} from ${chatId}`;
|
||||
},
|
||||
promote_admin: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const userId = formatId(args.user_id as string | number, 'user');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const userId = formatId(args.user_id as string | number, "user");
|
||||
return `Promote ${userId} to admin in ${chatId}`;
|
||||
},
|
||||
demote_admin: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const userId = formatId(args.user_id as string | number, 'user');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const userId = formatId(args.user_id as string | number, "user");
|
||||
return `Demote ${userId} from admin in ${chatId}`;
|
||||
},
|
||||
block_user: (args) => {
|
||||
const userId = formatId(args.user_id as string | number, 'user');
|
||||
const userId = formatId(args.user_id as string | number, "user");
|
||||
return `Block ${userId}`;
|
||||
},
|
||||
unblock_user: (args) => {
|
||||
const userId = formatId(args.user_id as string | number, 'user');
|
||||
const userId = formatId(args.user_id as string | number, "user");
|
||||
return `Unblock ${userId}`;
|
||||
},
|
||||
add_contact: (args) => {
|
||||
const firstName = (args.first_name as string) || '';
|
||||
const lastName = (args.last_name as string) || '';
|
||||
const phone = (args.phone_number as string) || '';
|
||||
const name = [firstName, lastName].filter(Boolean).join(' ') || phone;
|
||||
const firstName = (args.first_name as string) || "";
|
||||
const lastName = (args.last_name as string) || "";
|
||||
const phone = (args.phone_number as string) || "";
|
||||
const name = [firstName, lastName].filter(Boolean).join(" ") || phone;
|
||||
return `Add contact: ${name}`;
|
||||
},
|
||||
delete_contact: (args) => {
|
||||
const userId = formatId(args.user_id as string | number, 'user');
|
||||
const userId = formatId(args.user_id as string | number, "user");
|
||||
return `Delete contact ${userId}`;
|
||||
},
|
||||
list_contacts: (args) => {
|
||||
@@ -189,144 +192,159 @@ const toolParsers: Record<string, (args: ToolArguments) => string> = {
|
||||
return `List ${limit} contacts`;
|
||||
},
|
||||
search_contacts: (args) => {
|
||||
const query = truncateText((args.query as string) || '', 30);
|
||||
const query = truncateText((args.query as string) || "", 30);
|
||||
return `Search contacts for "${query}"`;
|
||||
},
|
||||
search_messages: (args) => {
|
||||
const chatId = args.chat_id ? formatId(args.chat_id as string | number, 'chat') : 'all chats';
|
||||
const query = truncateText((args.query as string) || '', 30);
|
||||
const chatId = args.chat_id
|
||||
? formatId(args.chat_id as string | number, "chat")
|
||||
: "all chats";
|
||||
const query = truncateText((args.query as string) || "", 30);
|
||||
const limit = (args.limit as number) || 20;
|
||||
return `Search for "${query}" in ${chatId} (limit: ${limit})`;
|
||||
},
|
||||
search_public_chats: (args) => {
|
||||
const query = truncateText((args.query as string) || '', 30);
|
||||
const query = truncateText((args.query as string) || "", 30);
|
||||
return `Search public chats for "${query}"`;
|
||||
},
|
||||
resolve_username: (args) => {
|
||||
const username = (args.username as string) || '';
|
||||
return `Resolve username @${username.replace('@', '')}`;
|
||||
const username = (args.username as string) || "";
|
||||
return `Resolve username @${username.replace("@", "")}`;
|
||||
},
|
||||
get_messages: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const limit = (args.limit as number) || 20;
|
||||
return `Get ${limit} messages from ${chatId}`;
|
||||
},
|
||||
list_messages: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const limit = (args.limit as number) || 20;
|
||||
return `List ${limit} messages from ${chatId}`;
|
||||
},
|
||||
get_history: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const limit = (args.limit as number) || 20;
|
||||
return `Get message history from ${chatId} (${limit} messages)`;
|
||||
},
|
||||
get_pinned_messages: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Get pinned messages from ${chatId}`;
|
||||
},
|
||||
get_message_context: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
const limit = (args.limit as number) || 5;
|
||||
return `Get context around message ${messageId} in ${chatId} (±${limit} messages)`;
|
||||
},
|
||||
list_topics: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `List topics in ${chatId}`;
|
||||
},
|
||||
get_participants: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const limit = (args.limit as number) || 100;
|
||||
return `Get ${limit} participants from ${chatId}`;
|
||||
},
|
||||
get_admins: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Get administrators of ${chatId}`;
|
||||
},
|
||||
get_banned_users: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Get banned users from ${chatId}`;
|
||||
},
|
||||
get_blocked_users: () => 'Get list of blocked users',
|
||||
get_blocked_users: () => "Get list of blocked users",
|
||||
get_invite_link: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Get invite link for ${chatId}`;
|
||||
},
|
||||
export_chat_invite: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Create invite link for ${chatId}`;
|
||||
},
|
||||
import_chat_invite: (args) => {
|
||||
const inviteHash = args.invite_hash ? truncateText((args.invite_hash as string) || '', 20) : '';
|
||||
const inviteHash = args.invite_hash
|
||||
? truncateText((args.invite_hash as string) || "", 20)
|
||||
: "";
|
||||
return `Join chat via invite link: ${inviteHash}`;
|
||||
},
|
||||
join_chat_by_link: (args) => {
|
||||
const inviteLink = args.invite_link ? truncateText((args.invite_link as string) || '', 30) : '';
|
||||
const inviteLink = args.invite_link
|
||||
? truncateText((args.invite_link as string) || "", 30)
|
||||
: "";
|
||||
return `Join chat via link: ${inviteLink}`;
|
||||
},
|
||||
subscribe_public_channel: (args) => {
|
||||
const username = (args.username as string) || '';
|
||||
return `Subscribe to public channel @${username.replace('@', '')}`;
|
||||
const username = (args.username as string) || "";
|
||||
return `Subscribe to public channel @${username.replace("@", "")}`;
|
||||
},
|
||||
update_profile: (args) => {
|
||||
const updates: string[] = [];
|
||||
if (args.first_name) updates.push(`first name: "${args.first_name as string}"`);
|
||||
if (args.last_name) updates.push(`last name: "${args.last_name as string}"`);
|
||||
if (args.bio) updates.push(`bio: "${truncateText((args.bio as string) || '', 30)}"`);
|
||||
return `Update profile (${updates.join(', ')})`;
|
||||
if (args.first_name)
|
||||
updates.push(`first name: "${args.first_name as string}"`);
|
||||
if (args.last_name)
|
||||
updates.push(`last name: "${args.last_name as string}"`);
|
||||
if (args.bio)
|
||||
updates.push(`bio: "${truncateText((args.bio as string) || "", 30)}"`);
|
||||
return `Update profile (${updates.join(", ")})`;
|
||||
},
|
||||
set_profile_photo: () => 'Set profile photo',
|
||||
delete_profile_photo: () => 'Delete profile photo',
|
||||
set_profile_photo: () => "Set profile photo",
|
||||
delete_profile_photo: () => "Delete profile photo",
|
||||
get_user_photos: (args) => {
|
||||
const userId = formatId(args.user_id as string | number, 'user');
|
||||
const userId = formatId(args.user_id as string | number, "user");
|
||||
const limit = (args.limit as number) || 20;
|
||||
return `Get ${limit} photos from ${userId}`;
|
||||
},
|
||||
get_user_status: (args) => {
|
||||
const userId = formatId(args.user_id as string | number, 'user');
|
||||
const userId = formatId(args.user_id as string | number, "user");
|
||||
return `Get status of ${userId}`;
|
||||
},
|
||||
get_me: () => 'Get current user information',
|
||||
get_me: () => "Get current user information",
|
||||
list_inline_buttons: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
return `Get inline buttons from message ${messageId} in ${chatId}`;
|
||||
},
|
||||
press_inline_button: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
const buttonText = args.button_text ? ` "${truncateText((args.button_text as string) || '', 30)}"` : '';
|
||||
const buttonText = args.button_text
|
||||
? ` "${truncateText((args.button_text as string) || "", 30)}"`
|
||||
: "";
|
||||
return `Press inline button${buttonText} on message ${messageId} in ${chatId}`;
|
||||
},
|
||||
create_poll: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const question = truncateText((args.question as string) || '', 50);
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const question = truncateText((args.question as string) || "", 50);
|
||||
const optionCount = Array.isArray(args.options) ? args.options.length : 0;
|
||||
return `Create poll in ${chatId}: "${question}" with ${optionCount} options`;
|
||||
},
|
||||
get_bot_info: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
return `Get bot information from ${chatId}`;
|
||||
},
|
||||
set_bot_commands: (args) => {
|
||||
const chatId = args.chat_id ? formatId(args.chat_id as string | number, 'chat') : 'all chats';
|
||||
const commandCount = Array.isArray(args.commands) ? args.commands.length : 0;
|
||||
const chatId = args.chat_id
|
||||
? formatId(args.chat_id as string | number, "chat")
|
||||
: "all chats";
|
||||
const commandCount = Array.isArray(args.commands)
|
||||
? args.commands.length
|
||||
: 0;
|
||||
return `Set ${commandCount} bot commands for ${chatId}`;
|
||||
},
|
||||
get_privacy_settings: () => 'Get privacy settings',
|
||||
get_privacy_settings: () => "Get privacy settings",
|
||||
set_privacy_settings: (args) => {
|
||||
const setting = (args.setting as string) || 'unknown';
|
||||
const value = (args.value as string) || '';
|
||||
const setting = (args.setting as string) || "unknown";
|
||||
const value = (args.value as string) || "";
|
||||
return `Set privacy setting "${setting}" to ${value}`;
|
||||
},
|
||||
get_media_info: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const messageId = args.message_id;
|
||||
return `Get media information from message ${messageId} in ${chatId}`;
|
||||
},
|
||||
get_recent_actions: (args) => {
|
||||
const chatId = formatId(args.chat_id as string | number, 'chat');
|
||||
const chatId = formatId(args.chat_id as string | number, "chat");
|
||||
const limit = (args.limit as number) || 20;
|
||||
return `Get ${limit} recent admin actions from ${chatId}`;
|
||||
},
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import { optString } from '../args';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import { optString } from "../args";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'add_contact',
|
||||
description: 'Add a contact to your Telegram account',
|
||||
name: "add_contact",
|
||||
description: "Add a contact to your Telegram account",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
phone: { type: 'string', description: 'Phone number' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
phone: { type: "string", description: "Phone number" },
|
||||
first_name: { type: "string", description: "First name" },
|
||||
last_name: { type: "string", description: "Last name" },
|
||||
},
|
||||
required: ['phone', 'first_name'],
|
||||
required: ["phone", "first_name"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,12 +25,21 @@ export async function addContact(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const phone = typeof args.phone === 'string' ? args.phone : '';
|
||||
const firstName = typeof args.first_name === 'string' ? args.first_name : '';
|
||||
const lastName = optString(args, 'last_name') ?? '';
|
||||
const phone = typeof args.phone === "string" ? args.phone : "";
|
||||
const firstName =
|
||||
typeof args.first_name === "string" ? args.first_name : "";
|
||||
const lastName = optString(args, "last_name") ?? "";
|
||||
|
||||
if (!phone) return { content: [{ type: 'text', text: 'phone is required' }], isError: true };
|
||||
if (!firstName) return { content: [{ type: 'text', text: 'first_name is required' }], isError: true };
|
||||
if (!phone)
|
||||
return {
|
||||
content: [{ type: "text", text: "phone is required" }],
|
||||
isError: true,
|
||||
};
|
||||
if (!firstName)
|
||||
return {
|
||||
content: [{ type: "text", text: "first_name is required" }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
@@ -49,10 +58,18 @@ export async function addContact(
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Contact ' + firstName + ' ' + lastName + ' (' + phone + ') added.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
"Contact " + firstName + " " + lastName + " (" + phone + ") added.",
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'add_contact',
|
||||
"add_contact",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { toInputPeer } from '../apiCastHelpers';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { toInputPeer } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'block_user',
|
||||
description: 'Block a user',
|
||||
name: "block_user",
|
||||
description: "Block a user",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
user_id: { type: 'string', description: 'User ID to block' },
|
||||
user_id: { type: "string", description: "User ID to block" },
|
||||
},
|
||||
required: ['user_id'],
|
||||
required: ["user_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function blockUser(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const userId = validateId(args.user_id, 'user_id');
|
||||
const userId = validateId(args.user_id, "user_id");
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -33,10 +33,12 @@ export async function blockUser(
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'User ' + userId + ' blocked.' }] };
|
||||
return {
|
||||
content: [{ type: "text", text: "User " + userId + " blocked." }],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'block_user',
|
||||
"block_user",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "clear_draft",
|
||||
@@ -23,10 +23,14 @@ export async function clearDraft(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -36,15 +40,19 @@ export async function clearDraft(
|
||||
await client.invoke(
|
||||
new Api.messages.SaveDraft({
|
||||
peer: inputPeer,
|
||||
message: '',
|
||||
message: "",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Draft cleared in chat ' + chatId + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "Draft cleared in chat " + chatId + "." },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'clear_draft',
|
||||
"clear_draft",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.DRAFT,
|
||||
);
|
||||
|
||||
@@ -52,7 +52,8 @@ export async function createChannel(
|
||||
);
|
||||
});
|
||||
|
||||
const channelId = narrow<ResultWithChats>(result)?.chats?.[0]?.id ?? "unknown";
|
||||
const channelId =
|
||||
narrow<ResultWithChats>(result)?.chats?.[0]?.id ?? "unknown";
|
||||
const type = megagroup ? "Supergroup" : "Channel";
|
||||
return {
|
||||
content: [
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "create_poll",
|
||||
@@ -26,15 +26,27 @@ export async function createPoll(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const question = typeof args.question === 'string' ? args.question : '';
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const question = typeof args.question === "string" ? args.question : "";
|
||||
const options = Array.isArray(args.options) ? args.options.map(String) : [];
|
||||
|
||||
if (!question) return { content: [{ type: 'text', text: 'question is required' }], isError: true };
|
||||
if (options.length < 2) return { content: [{ type: 'text', text: 'At least 2 options are required' }], isError: true };
|
||||
if (!question)
|
||||
return {
|
||||
content: [{ type: "text", text: "question is required" }],
|
||||
isError: true,
|
||||
};
|
||||
if (options.length < 2)
|
||||
return {
|
||||
content: [{ type: "text", text: "At least 2 options are required" }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -47,25 +59,29 @@ export async function createPoll(
|
||||
media: new Api.InputMediaPoll({
|
||||
poll: new Api.Poll({
|
||||
id: bigInt(0),
|
||||
question: new Api.TextWithEntities({ text: question, entities: [] }),
|
||||
answers: options.map((opt, i) =>
|
||||
new Api.PollAnswer({
|
||||
text: new Api.TextWithEntities({ text: opt, entities: [] }),
|
||||
option: Buffer.from([i]),
|
||||
}),
|
||||
question: new Api.TextWithEntities({
|
||||
text: question,
|
||||
entities: [],
|
||||
}),
|
||||
answers: options.map(
|
||||
(opt, i) =>
|
||||
new Api.PollAnswer({
|
||||
text: new Api.TextWithEntities({ text: opt, entities: [] }),
|
||||
option: Buffer.from([i]),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
message: '',
|
||||
message: "",
|
||||
randomId: bigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Poll created: ' + question }] };
|
||||
return { content: [{ type: "text", text: "Poll created: " + question }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'create_poll',
|
||||
"create_poll",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { toInputUser } from '../apiCastHelpers';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { toInputUser } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'delete_contact',
|
||||
description: 'Delete a contact from your Telegram account',
|
||||
name: "delete_contact",
|
||||
description: "Delete a contact from your Telegram account",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
user_id: { type: 'string', description: 'User ID to remove from contacts' },
|
||||
user_id: {
|
||||
type: "string",
|
||||
description: "User ID to remove from contacts",
|
||||
},
|
||||
},
|
||||
required: ['user_id'],
|
||||
required: ["user_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,7 +26,7 @@ export async function deleteContact(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const userId = validateId(args.user_id, 'user_id');
|
||||
const userId = validateId(args.user_id, "user_id");
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -35,10 +38,12 @@ export async function deleteContact(
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Contact ' + userId + ' deleted.' }] };
|
||||
return {
|
||||
content: [{ type: "text", text: "Contact " + userId + " deleted." }],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'delete_contact',
|
||||
"delete_contact",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'delete_message',
|
||||
description: 'Delete a message',
|
||||
name: "delete_message",
|
||||
description: "Delete a message",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
message_id: { type: 'number', description: 'Message ID' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
message_id: { type: "number", description: "Message ID" },
|
||||
},
|
||||
required: ['chat_id', 'message_id'],
|
||||
required: ["chat_id", "message_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,21 +23,27 @@ export async function deleteMessage(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
|
||||
if (messageId === undefined) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'message_id must be a positive integer' }],
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) {
|
||||
return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -48,11 +54,13 @@ export async function deleteMessage(
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Message ${messageId} deleted successfully.` }],
|
||||
content: [
|
||||
{ type: "text", text: `Message ${messageId} deleted successfully.` },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'delete_message',
|
||||
"delete_message",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import type { ApiPhoto } from '../apiResultTypes';
|
||||
import { narrow } from '../apiCastHelpers';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import type { ApiPhoto } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "delete_profile_photo",
|
||||
@@ -31,8 +31,15 @@ export async function deleteProfilePhoto(
|
||||
);
|
||||
});
|
||||
|
||||
if (!photos || !('photos' in photos) || !Array.isArray(photos.photos) || photos.photos.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No profile photo to delete.' }] };
|
||||
if (
|
||||
!photos ||
|
||||
!("photos" in photos) ||
|
||||
!Array.isArray(photos.photos) ||
|
||||
photos.photos.length === 0
|
||||
) {
|
||||
return {
|
||||
content: [{ type: "text", text: "No profile photo to delete." }],
|
||||
};
|
||||
}
|
||||
|
||||
const photo = narrow<ApiPhoto>(photos.photos[0]);
|
||||
@@ -40,15 +47,21 @@ export async function deleteProfilePhoto(
|
||||
await mtprotoService.withFloodWaitHandling(async () => {
|
||||
await client.invoke(
|
||||
new Api.photos.DeletePhotos({
|
||||
id: [new Api.InputPhoto({ id: photo.id, accessHash: photo.accessHash, fileReference: photo.fileReference })],
|
||||
id: [
|
||||
new Api.InputPhoto({
|
||||
id: photo.id,
|
||||
accessHash: photo.accessHash,
|
||||
fileReference: photo.fileReference,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Profile photo deleted.' }] };
|
||||
return { content: [{ type: "text", text: "Profile photo deleted." }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'delete_profile_photo',
|
||||
"delete_profile_photo",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.PROFILE,
|
||||
);
|
||||
|
||||
@@ -8,9 +8,9 @@ export const tool: MCPTool = {
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
file_path: { type: 'string', description: 'Path to the photo file' },
|
||||
file_path: { type: "string", description: "Path to the photo file" },
|
||||
},
|
||||
required: ["chat_id", 'file_path'],
|
||||
required: ["chat_id", "file_path"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -19,7 +19,12 @@ export async function editChatPhoto(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'edit_chat_photo requires file upload which is not supported via MCP text interface. Use the Telegram client directly.' }],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "edit_chat_photo requires file upload which is not supported via MCP text interface. Use the Telegram client directly.",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'edit_message',
|
||||
description: 'Edit an existing message',
|
||||
name: "edit_message",
|
||||
description: "Edit an existing message",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
message_id: { type: 'number', description: 'Message ID' },
|
||||
new_text: { type: 'string', description: 'New message text' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
message_id: { type: "number", description: "Message ID" },
|
||||
new_text: { type: "string", description: "New message text" },
|
||||
},
|
||||
required: ['chat_id', 'message_id', 'new_text'],
|
||||
required: ["chat_id", "message_id", "new_text"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,28 +24,34 @@ export async function editMessage(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const newText = typeof args.new_text === 'string' ? args.new_text : '';
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const newText = typeof args.new_text === "string" ? args.new_text : "";
|
||||
|
||||
if (messageId === undefined) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'message_id must be a positive integer' }],
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
if (!newText) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'new_text is required' }],
|
||||
content: [{ type: "text", text: "new_text is required" }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) {
|
||||
return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -56,11 +62,13 @@ export async function editMessage(
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Message ${messageId} edited successfully.` }],
|
||||
content: [
|
||||
{ type: "text", text: `Message ${messageId} edited successfully.` },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'edit_message',
|
||||
"edit_message",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { optString } from '../args';
|
||||
import type { ChatInviteResult } from '../apiResultTypes';
|
||||
import { narrow } from '../apiCastHelpers';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { optString } from "../args";
|
||||
import type { ChatInviteResult } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'export_chat_invite',
|
||||
description: 'Export a new chat invite link',
|
||||
name: "export_chat_invite",
|
||||
description: "Export a new chat invite link",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
title: { type: 'string', description: 'Link title' },
|
||||
expire_date: { type: 'number', description: 'Expiration timestamp' },
|
||||
usage_limit: { type: 'number', description: 'Max number of uses' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
title: { type: "string", description: "Link title" },
|
||||
expire_date: { type: "number", description: "Expiration timestamp" },
|
||||
usage_limit: { type: "number", description: "Max number of uses" },
|
||||
},
|
||||
required: ['chat_id'],
|
||||
required: ["chat_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,13 +29,19 @@ export async function exportChatInvite(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const title = optString(args, 'title');
|
||||
const expireDate = typeof args.expire_date === 'number' ? args.expire_date : undefined;
|
||||
const usageLimit = typeof args.usage_limit === 'number' ? args.usage_limit : undefined;
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const title = optString(args, "title");
|
||||
const expireDate =
|
||||
typeof args.expire_date === "number" ? args.expire_date : undefined;
|
||||
const usageLimit =
|
||||
typeof args.usage_limit === "number" ? args.usage_limit : undefined;
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -54,13 +60,18 @@ export async function exportChatInvite(
|
||||
|
||||
const link = narrow<ChatInviteResult>(result)?.link;
|
||||
if (!link) {
|
||||
return { content: [{ type: 'text', text: 'Could not create invite link.' }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: "Could not create invite link." }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: `Invite link created: ${link}` }] };
|
||||
return {
|
||||
content: [{ type: "text", text: `Invite link created: ${link}` }],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'export_chat_invite',
|
||||
"export_chat_invite",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.GROUP,
|
||||
);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'forward_message',
|
||||
description: 'Forward a message to another chat',
|
||||
name: "forward_message",
|
||||
description: "Forward a message to another chat",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
from_chat_id: { type: 'string', description: 'Source chat ID' },
|
||||
to_chat_id: { type: 'string', description: 'Target chat ID' },
|
||||
message_id: { type: 'number', description: 'Message ID' },
|
||||
from_chat_id: { type: "string", description: "Source chat ID" },
|
||||
to_chat_id: { type: "string", description: "Target chat ID" },
|
||||
message_id: { type: "number", description: "Message ID" },
|
||||
},
|
||||
required: ['from_chat_id', 'to_chat_id', 'message_id'],
|
||||
required: ["from_chat_id", "to_chat_id", "message_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,27 +24,38 @@ export async function forwardMessage(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const fromChatId = validateId(args.from_chat_id, 'from_chat_id');
|
||||
const toChatId = validateId(args.to_chat_id, 'to_chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const fromChatId = validateId(args.from_chat_id, "from_chat_id");
|
||||
const toChatId = validateId(args.to_chat_id, "to_chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
|
||||
if (messageId === undefined) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'message_id must be a positive integer' }],
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const fromChat = getChatById(fromChatId);
|
||||
if (!fromChat) {
|
||||
return { content: [{ type: 'text', text: `Source chat not found: ${fromChatId}` }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: `Source chat not found: ${fromChatId}` },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const toChat = getChatById(toChatId);
|
||||
if (!toChat) {
|
||||
return { content: [{ type: 'text', text: `Target chat not found: ${toChatId}` }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: `Target chat not found: ${toChatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const fromEntity = fromChat.username ? fromChat.username : fromChat.id;
|
||||
@@ -59,11 +70,16 @@ export async function forwardMessage(
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Message ${messageId} forwarded from ${fromChatId} to ${toChatId}.` }],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Message ${messageId} forwarded from ${fromChatId} to ${toChatId}.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'forward_message',
|
||||
"forward_message",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { optNumber } from '../args';
|
||||
import type { ApiUser } from '../apiResultTypes';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { optNumber } from "../args";
|
||||
import type { ApiUser } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_blocked_users',
|
||||
description: 'Get list of blocked users',
|
||||
name: "get_blocked_users",
|
||||
description: "Get list of blocked users",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: { type: 'number', description: 'Max results', default: 50 },
|
||||
limit: { type: "number", description: "Max results", default: 50 },
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -22,29 +22,33 @@ export async function getBlockedUsers(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const limit = optNumber(args, 'limit', 50);
|
||||
const limit = optNumber(args, "limit", 50);
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
return client.invoke(
|
||||
new Api.contacts.GetBlocked({ offset: 0, limit }),
|
||||
);
|
||||
return client.invoke(new Api.contacts.GetBlocked({ offset: 0, limit }));
|
||||
});
|
||||
|
||||
if (!result || !('users' in result) || !Array.isArray(result.users) || result.users.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No blocked users.' }] };
|
||||
if (
|
||||
!result ||
|
||||
!("users" in result) ||
|
||||
!Array.isArray(result.users) ||
|
||||
result.users.length === 0
|
||||
) {
|
||||
return { content: [{ type: "text", text: "No blocked users." }] };
|
||||
}
|
||||
|
||||
const lines = result.users.map((u: ApiUser) => {
|
||||
const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown';
|
||||
const username = u.username ? `@${u.username}` : '';
|
||||
const name =
|
||||
[u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown";
|
||||
const username = u.username ? `@${u.username}` : "";
|
||||
return `ID: ${u.id} | ${name} ${username}`.trim();
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_blocked_users',
|
||||
"get_blocked_users",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { FullUserResult } from '../apiResultTypes';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { FullUserResult } from "../apiResultTypes";
|
||||
import { toInputUser, narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
@@ -24,7 +24,7 @@ export async function getBotInfo(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const botId = validateId(args.chat_id, 'chat_id');
|
||||
const botId = validateId(args.chat_id, "chat_id");
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -38,30 +38,34 @@ export async function getBotInfo(
|
||||
const user = narrow<FullUserResult>(result)?.users?.[0];
|
||||
|
||||
if (!user) {
|
||||
return { content: [{ type: 'text', text: 'Bot not found: ' + botId }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: "Bot not found: " + botId }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown';
|
||||
const name =
|
||||
[user.firstName, user.lastName].filter(Boolean).join(" ") || "Unknown";
|
||||
const lines = [
|
||||
'Name: ' + name,
|
||||
'Username: @' + (user.username ?? 'N/A'),
|
||||
'ID: ' + user.id,
|
||||
'Bot: ' + (user.bot ? 'Yes' : 'No'),
|
||||
'About: ' + (fullUser?.about ?? 'N/A'),
|
||||
'Bot Info Description: ' + (fullUser?.botInfo?.description ?? 'N/A'),
|
||||
"Name: " + name,
|
||||
"Username: @" + (user.username ?? "N/A"),
|
||||
"ID: " + user.id,
|
||||
"Bot: " + (user.bot ? "Yes" : "No"),
|
||||
"About: " + (fullUser?.about ?? "N/A"),
|
||||
"Bot Info Description: " + (fullUser?.botInfo?.description ?? "N/A"),
|
||||
];
|
||||
|
||||
if (fullUser?.botInfo?.commands) {
|
||||
lines.push('Commands:');
|
||||
lines.push("Commands:");
|
||||
for (const cmd of fullUser.botInfo.commands) {
|
||||
lines.push(' /' + cmd.command + ' - ' + cmd.description);
|
||||
lines.push(" /" + cmd.command + " - " + cmd.description);
|
||||
}
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_bot_info',
|
||||
"get_bot_info",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -2,25 +2,25 @@
|
||||
* Get Chat tool - Get detailed information about a specific chat
|
||||
*/
|
||||
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { formatEntity, getChatById } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { formatEntity, getChatById } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_chat',
|
||||
description: 'Get detailed information about a specific chat',
|
||||
name: "get_chat",
|
||||
description: "Get detailed information about a specific chat",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: {
|
||||
type: 'string',
|
||||
description: 'The ID or username of the chat',
|
||||
type: "string",
|
||||
description: "The ID or username of the chat",
|
||||
},
|
||||
},
|
||||
required: ['chat_id'],
|
||||
required: ["chat_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,12 +29,12 @@ export async function getChat(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const chat = getChatById(chatId);
|
||||
|
||||
if (!chat) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `Chat not found: ${chatId}` }],
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
@@ -46,27 +46,27 @@ export async function getChat(
|
||||
result.push(`Title: ${entity.name}`);
|
||||
result.push(`Type: ${entity.type}`);
|
||||
if (entity.username) result.push(`Username: @${entity.username}`);
|
||||
if ('participantsCount' in chat && chat.participantsCount) {
|
||||
if ("participantsCount" in chat && chat.participantsCount) {
|
||||
result.push(`Participants: ${chat.participantsCount}`);
|
||||
}
|
||||
if ('unreadCount' in chat) {
|
||||
if ("unreadCount" in chat) {
|
||||
result.push(`Unread Messages: ${chat.unreadCount ?? 0}`);
|
||||
}
|
||||
|
||||
const lastMsg = chat.lastMessage;
|
||||
if (lastMsg) {
|
||||
const from = lastMsg.fromName ?? lastMsg.fromId ?? 'Unknown';
|
||||
const from = lastMsg.fromName ?? lastMsg.fromId ?? "Unknown";
|
||||
const date = new Date(lastMsg.date * 1000).toISOString();
|
||||
result.push(`Last Message: From ${from} at ${date}`);
|
||||
result.push(`Message: ${lastMsg.message || '[Media/No text]'}`);
|
||||
result.push(`Message: ${lastMsg.message || "[Media/No text]"}`);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: result.join('\n') }],
|
||||
content: [{ type: "text", text: result.join("\n") }],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_chat',
|
||||
"get_chat",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CHAT,
|
||||
);
|
||||
|
||||
@@ -2,21 +2,29 @@
|
||||
* Get Chats tool - Get a paginated list of chats
|
||||
*/
|
||||
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { optNumber } from '../args';
|
||||
import { formatEntity, getChats as getChatsApi } from '../telegramApi';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { optNumber } from "../args";
|
||||
import { formatEntity, getChats as getChatsApi } from "../telegramApi";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_chats',
|
||||
description: 'Get a paginated list of chats',
|
||||
name: "get_chats",
|
||||
description: "Get a paginated list of chats",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
page: { type: 'number', description: 'Page number (1-indexed)', default: 1 },
|
||||
page_size: { type: 'number', description: 'Number of chats per page', default: 20 },
|
||||
page: {
|
||||
type: "number",
|
||||
description: "Page number (1-indexed)",
|
||||
default: 1,
|
||||
},
|
||||
page_size: {
|
||||
type: "number",
|
||||
description: "Number of chats per page",
|
||||
default: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -26,15 +34,15 @@ export async function getChats(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const page = optNumber(args, 'page', 1);
|
||||
const pageSize = optNumber(args, 'page_size', 20);
|
||||
const page = optNumber(args, "page", 1);
|
||||
const pageSize = optNumber(args, "page_size", 20);
|
||||
const start = (page - 1) * pageSize;
|
||||
|
||||
const chats = await getChatsApi(pageSize + start);
|
||||
const paginatedChats = chats.slice(start, start + pageSize);
|
||||
|
||||
if (paginatedChats.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'Page out of range.' }] };
|
||||
return { content: [{ type: "text", text: "Page out of range." }] };
|
||||
}
|
||||
|
||||
const lines = paginatedChats.map((chat) => {
|
||||
@@ -42,10 +50,10 @@ export async function getChats(
|
||||
return `Chat ID: ${entity.id}, Title: ${entity.name}`;
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_chats',
|
||||
"get_chats",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CHAT,
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { store } from '../../../../store';
|
||||
import { selectOrderedChats } from '../../../../store/telegramSelectors';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { store } from "../../../../store";
|
||||
import { selectOrderedChats } from "../../../../store/telegramSelectors";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_contact_chats',
|
||||
description: 'Get all chats that are direct messages with contacts',
|
||||
inputSchema: { type: 'object', properties: {} },
|
||||
name: "get_contact_chats",
|
||||
description: "Get all chats that are direct messages with contacts",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
};
|
||||
|
||||
export async function getContactChats(
|
||||
@@ -17,21 +17,28 @@ export async function getContactChats(
|
||||
try {
|
||||
const state = store.getState();
|
||||
const chats = selectOrderedChats(state);
|
||||
const dmChats = chats.filter((c) => c.type === 'private');
|
||||
const dmChats = chats.filter((c) => c.type === "private");
|
||||
|
||||
if (dmChats.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No contact chats found.' }] };
|
||||
return { content: [{ type: "text", text: "No contact chats found." }] };
|
||||
}
|
||||
|
||||
const lines = dmChats.map((c) => {
|
||||
const username = c.username ? '@' + c.username : '';
|
||||
return ('ID: ' + c.id + ' | ' + (c.title ?? 'DM') + ' ' + username).trim();
|
||||
const username = c.username ? "@" + c.username : "";
|
||||
return (
|
||||
"ID: " +
|
||||
c.id +
|
||||
" | " +
|
||||
(c.title ?? "DM") +
|
||||
" " +
|
||||
username
|
||||
).trim();
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_contact_chats',
|
||||
"get_contact_chats",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import type { ContactIdEntry } from '../apiResultTypes';
|
||||
import { narrow } from '../apiCastHelpers';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import type { ContactIdEntry } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
type ContactIdResult = number | ContactIdEntry;
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_contact_ids',
|
||||
description: 'Get IDs of all contacts',
|
||||
inputSchema: { type: 'object', properties: {} },
|
||||
name: "get_contact_ids",
|
||||
description: "Get IDs of all contacts",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
};
|
||||
|
||||
export async function getContactIds(
|
||||
@@ -27,16 +27,20 @@ export async function getContactIds(
|
||||
});
|
||||
|
||||
if (!result || !Array.isArray(result) || result.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No contact IDs found.' }] };
|
||||
return { content: [{ type: "text", text: "No contact IDs found." }] };
|
||||
}
|
||||
|
||||
const ids = narrow<ContactIdResult[]>(result).map((c) =>
|
||||
String(typeof c === 'number' ? c : c.userId ?? c),
|
||||
String(typeof c === "number" ? c : (c.userId ?? c)),
|
||||
);
|
||||
return { content: [{ type: 'text', text: ids.length + ' contacts:\n' + ids.join('\n') }] };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: ids.length + " contacts:\n" + ids.join("\n") },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_contact_ids',
|
||||
"get_contact_ids",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { store } from '../../../../store';
|
||||
import { selectOrderedChats } from '../../../../store/telegramSelectors';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { store } from "../../../../store";
|
||||
import { selectOrderedChats } from "../../../../store/telegramSelectors";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_direct_chat_by_contact',
|
||||
description: 'Get direct message chat with a contact',
|
||||
name: "get_direct_chat_by_contact",
|
||||
description: "Get direct message chat with a contact",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
user_id: { type: 'string', description: 'User ID' },
|
||||
user_id: { type: "string", description: "User ID" },
|
||||
},
|
||||
required: ['user_id'],
|
||||
required: ["user_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,27 +22,42 @@ export async function getDirectChatByContact(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const userId = validateId(args.user_id, 'user_id');
|
||||
const userId = validateId(args.user_id, "user_id");
|
||||
const state = store.getState();
|
||||
const chats = selectOrderedChats(state);
|
||||
|
||||
const dmChat = chats.find(
|
||||
(c) => c.type === 'private' && String(c.id) === String(userId),
|
||||
(c) => c.type === "private" && String(c.id) === String(userId),
|
||||
);
|
||||
|
||||
if (!dmChat) {
|
||||
return { content: [{ type: 'text', text: 'No direct chat found with user ' + userId + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No direct chat found with user " + userId + ".",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: 'Chat ID: ' + dmChat.id + ' | Title: ' + (dmChat.title ?? 'DM') + ' | Username: ' + (dmChat.username ?? 'N/A'),
|
||||
}],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
"Chat ID: " +
|
||||
dmChat.id +
|
||||
" | Title: " +
|
||||
(dmChat.title ?? "DM") +
|
||||
" | Username: " +
|
||||
(dmChat.username ?? "N/A"),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_direct_chat_by_contact',
|
||||
"get_direct_chat_by_contact",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { UpdatesResult } from '../apiResultTypes';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { UpdatesResult } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
@@ -25,25 +25,29 @@ export async function getDrafts(
|
||||
|
||||
const updates = narrow<UpdatesResult>(result);
|
||||
if (!updates || !updates.updates || updates.updates.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No drafts found.' }] };
|
||||
return { content: [{ type: "text", text: "No drafts found." }] };
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
for (const update of updates.updates) {
|
||||
if (update.draft && update.draft.message) {
|
||||
const peerId = update.peer?.userId ?? update.peer?.chatId ?? update.peer?.channelId ?? '?';
|
||||
lines.push('Peer ' + peerId + ': ' + update.draft.message);
|
||||
const peerId =
|
||||
update.peer?.userId ??
|
||||
update.peer?.chatId ??
|
||||
update.peer?.channelId ??
|
||||
"?";
|
||||
lines.push("Peer " + peerId + ": " + update.draft.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (lines.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No drafts found.' }] };
|
||||
return { content: [{ type: "text", text: "No drafts found." }] };
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_drafts',
|
||||
"get_drafts",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.DRAFT,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { optNumber } from '../args';
|
||||
import type { InlineBotResults } from '../apiResultTypes';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { optNumber } from "../args";
|
||||
import type { InlineBotResults } from "../apiResultTypes";
|
||||
import { toInputUser, narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
@@ -14,7 +14,7 @@ export const tool: MCPTool = {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string", description: "Search query" },
|
||||
limit: { type: 'number', description: 'Max results', default: 10 },
|
||||
limit: { type: "number", description: "Max results", default: 10 },
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
@@ -25,38 +25,51 @@ export async function getGifSearch(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const query = typeof args.query === 'string' ? args.query : '';
|
||||
if (!query) return { content: [{ type: 'text', text: 'query is required' }], isError: true };
|
||||
const limit = optNumber(args, 'limit', 10);
|
||||
const query = typeof args.query === "string" ? args.query : "";
|
||||
if (!query)
|
||||
return {
|
||||
content: [{ type: "text", text: "query is required" }],
|
||||
isError: true,
|
||||
};
|
||||
const limit = optNumber(args, "limit", 10);
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
const bot = await client.getInputEntity('gif');
|
||||
const bot = await client.getInputEntity("gif");
|
||||
return client.invoke(
|
||||
new Api.messages.GetInlineBotResults({
|
||||
bot: toInputUser(bot),
|
||||
peer: new Api.InputPeerSelf(),
|
||||
query,
|
||||
offset: '',
|
||||
offset: "",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const results = narrow<InlineBotResults>(result)?.results;
|
||||
if (!results || !Array.isArray(results) || results.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No GIFs found for: ' + query }] };
|
||||
return {
|
||||
content: [{ type: "text", text: "No GIFs found for: " + query }],
|
||||
};
|
||||
}
|
||||
|
||||
const lines = results.slice(0, limit).map((r, i: number) => {
|
||||
const title = r.title ?? r.description ?? 'GIF ' + (i + 1);
|
||||
return (i + 1) + '. ' + title;
|
||||
const title = r.title ?? r.description ?? "GIF " + (i + 1);
|
||||
return i + 1 + ". " + title;
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.length + ' GIFs found:\n' + lines.join('\n') }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: lines.length + " GIFs found:\n" + lines.join("\n"),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_gif_search',
|
||||
"get_gif_search",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MEDIA,
|
||||
);
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { getChatById, getMessages, formatMessage } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { optNumber } from '../args';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { getChatById, getMessages, formatMessage } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
import { optNumber } from "../args";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_history',
|
||||
description: 'Get message history from a chat',
|
||||
name: "get_history",
|
||||
description: "Get message history from a chat",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
limit: { type: 'number', description: 'Number of messages', default: 20 },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
limit: { type: "number", description: "Number of messages", default: 20 },
|
||||
},
|
||||
required: ['chat_id'],
|
||||
required: ["chat_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,29 +23,34 @@ export async function getHistory(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const limit = optNumber(args, 'limit', 20);
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const limit = optNumber(args, "limit", 20);
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) {
|
||||
return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const messages = await getMessages(chatId, limit, 0);
|
||||
if (!messages || messages.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No messages found in this chat.' }] };
|
||||
return {
|
||||
content: [{ type: "text", text: "No messages found in this chat." }],
|
||||
};
|
||||
}
|
||||
|
||||
const lines = messages.map((msg) => {
|
||||
const f = formatMessage(msg);
|
||||
const from = msg.fromName ?? msg.fromId ?? 'Unknown';
|
||||
return `ID: ${f.id} | ${from} | ${f.date} | ${f.text || '[Media/No text]'}`;
|
||||
const from = msg.fromName ?? msg.fromId ?? "Unknown";
|
||||
return `ID: ${f.id} | ${from} | ${f.date} | ${f.text || "[Media/No text]"}`;
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_history',
|
||||
"get_history",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById, getMessages, formatMessage } from '../telegramApi';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById, getMessages, formatMessage } from "../telegramApi";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_last_interaction',
|
||||
description: 'Get the last message exchanged with a user or chat',
|
||||
name: "get_last_interaction",
|
||||
description: "Get the last message exchanged with a user or chat",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
},
|
||||
required: ['chat_id'],
|
||||
required: ["chat_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -21,31 +21,46 @@ export async function getLastInteraction(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) {
|
||||
return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const messages = await getMessages(chatId, 1, 0);
|
||||
if (!messages || messages.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No messages found in this chat.' }] };
|
||||
return {
|
||||
content: [{ type: "text", text: "No messages found in this chat." }],
|
||||
};
|
||||
}
|
||||
|
||||
const msg = messages[0];
|
||||
const f = formatMessage(msg);
|
||||
const from = msg.fromName ?? msg.fromId ?? 'Unknown';
|
||||
const from = msg.fromName ?? msg.fromId ?? "Unknown";
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: 'Last message in ' + (chat.title ?? chatId) + ':\nFrom: ' + from + ' | Date: ' + f.date + ' | ' + (f.text || '[Media/No text]'),
|
||||
}],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
"Last message in " +
|
||||
(chat.title ?? chatId) +
|
||||
":\nFrom: " +
|
||||
from +
|
||||
" | Date: " +
|
||||
f.date +
|
||||
" | " +
|
||||
(f.text || "[Media/No text]"),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_last_interaction',
|
||||
"get_last_interaction",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById, getMessages } from '../telegramApi';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById, getMessages } from "../telegramApi";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_media_info",
|
||||
@@ -22,35 +22,61 @@ export async function getMediaInfo(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
|
||||
if (messageId === undefined) {
|
||||
return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const messages = await getMessages(chatId, 200, 0);
|
||||
if (!messages) return { content: [{ type: 'text', text: 'No messages found.' }] };
|
||||
if (!messages)
|
||||
return { content: [{ type: "text", text: "No messages found." }] };
|
||||
|
||||
const msg = messages.find((m) => String(m.id) === String(messageId));
|
||||
if (!msg) return { content: [{ type: 'text', text: 'Message ' + messageId + ' not found in cache.' }], isError: true };
|
||||
if (!msg)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Message " + messageId + " not found in cache.",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
if (!msg.media) {
|
||||
return { content: [{ type: 'text', text: 'No media in message ' + messageId + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "No media in message " + messageId + "." },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const info = {
|
||||
...msg.media,
|
||||
type: msg.media.type ?? 'unknown',
|
||||
type: msg.media.type ?? "unknown",
|
||||
};
|
||||
|
||||
return { content: [{ type: 'text', text: JSON.stringify(info, null, 2) }] };
|
||||
return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_media_info',
|
||||
"get_media_info",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MEDIA,
|
||||
);
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { getChatById, getMessages, formatMessage } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { optNumber } from '../args';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { getChatById, getMessages, formatMessage } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
import { optNumber } from "../args";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_message_context',
|
||||
description: 'Get context around a specific message',
|
||||
name: "get_message_context",
|
||||
description: "Get context around a specific message",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
message_id: { type: 'number', description: 'Message ID' },
|
||||
limit: { type: 'number', description: 'Number of messages before/after', default: 5 },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
message_id: { type: "number", description: "Message ID" },
|
||||
limit: {
|
||||
type: "number",
|
||||
description: "Number of messages before/after",
|
||||
default: 5,
|
||||
},
|
||||
},
|
||||
required: ['chat_id', 'message_id'],
|
||||
required: ["chat_id", "message_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,33 +28,48 @@ export async function getMessageContext(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const contextSize = optNumber(args, 'limit', 5);
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const contextSize = optNumber(args, "limit", 5);
|
||||
|
||||
if (messageId === undefined) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'message_id must be a positive integer' }],
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) {
|
||||
return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const allMessages = await getMessages(chatId, 200, 0);
|
||||
if (!allMessages || allMessages.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No messages found in this chat.' }] };
|
||||
return {
|
||||
content: [{ type: "text", text: "No messages found in this chat." }],
|
||||
};
|
||||
}
|
||||
|
||||
const targetIndex = allMessages.findIndex((m) => String(m.id) === String(messageId));
|
||||
const targetIndex = allMessages.findIndex(
|
||||
(m) => String(m.id) === String(messageId),
|
||||
);
|
||||
if (targetIndex === -1) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `Message ${messageId} not found in cached messages.` }],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Message ${messageId} not found in cached messages.`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
@@ -61,15 +80,15 @@ export async function getMessageContext(
|
||||
|
||||
const lines = contextMessages.map((msg) => {
|
||||
const f = formatMessage(msg);
|
||||
const from = msg.fromName ?? msg.fromId ?? 'Unknown';
|
||||
const marker = String(msg.id) === String(messageId) ? ' >>> ' : ' ';
|
||||
return `${marker}ID: ${f.id} | ${from} | ${f.date} | ${f.text || '[Media/No text]'}`;
|
||||
const from = msg.fromName ?? msg.fromId ?? "Unknown";
|
||||
const marker = String(msg.id) === String(messageId) ? " >>> " : " ";
|
||||
return `${marker}ID: ${f.id} | ${from} | ${f.date} | ${f.text || "[Media/No text]"}`;
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_message_context',
|
||||
"get_message_context",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { UpdatesResult } from '../apiResultTypes';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { UpdatesResult } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_message_reactions',
|
||||
description: 'Get reactions on a message',
|
||||
name: "get_message_reactions",
|
||||
description: "Get reactions on a message",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
message_id: { type: 'number', description: 'Message ID' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
message_id: { type: "number", description: "Message ID" },
|
||||
},
|
||||
required: ['chat_id', 'message_id'],
|
||||
required: ["chat_id", "message_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -26,15 +26,27 @@ export async function getMessageReactions(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
|
||||
if (messageId === undefined) {
|
||||
return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -51,28 +63,49 @@ export async function getMessageReactions(
|
||||
|
||||
const updates = narrow<UpdatesResult>(result);
|
||||
if (!updates || !updates.updates || updates.updates.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No reactions found on message ' + messageId + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No reactions found on message " + messageId + ".",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
for (const update of updates.updates) {
|
||||
if (update.reactions && update.reactions.results) {
|
||||
for (const r of update.reactions.results) {
|
||||
const emoji = r.reaction?.emoticon ?? r.reaction?.className ?? '?';
|
||||
const emoji = r.reaction?.emoticon ?? r.reaction?.className ?? "?";
|
||||
const count = r.count ?? 0;
|
||||
lines.push(emoji + ': ' + count);
|
||||
lines.push(emoji + ": " + count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lines.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No reactions found on message ' + messageId + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No reactions found on message " + messageId + ".",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: 'Reactions on message ' + messageId + ':\n' + lines.join('\n') }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Reactions on message " + messageId + ":\n" + lines.join("\n"),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_message_reactions',
|
||||
"get_message_reactions",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { getChatById, getMessages, formatMessage } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import type { ApiMessage } from '../apiResultTypes';
|
||||
import { narrow } from '../apiCastHelpers';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { getChatById, getMessages, formatMessage } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import type { ApiMessage } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_pinned_messages',
|
||||
description: 'Get pinned messages from a chat',
|
||||
name: "get_pinned_messages",
|
||||
description: "Get pinned messages from a chat",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
|
||||
required: ['chat_id'],
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
},
|
||||
required: ["chat_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,11 +26,14 @@ export async function getPinnedMessages(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) {
|
||||
return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -41,7 +46,7 @@ export async function getPinnedMessages(
|
||||
const result = await client.invoke(
|
||||
new Api.messages.Search({
|
||||
peer: inputPeer,
|
||||
q: '',
|
||||
q: "",
|
||||
filter: new Api.InputMessagesFilterPinned(),
|
||||
minDate: 0,
|
||||
maxDate: 0,
|
||||
@@ -54,11 +59,13 @@ export async function getPinnedMessages(
|
||||
}),
|
||||
);
|
||||
|
||||
if ('messages' in result && Array.isArray(result.messages)) {
|
||||
if ("messages" in result && Array.isArray(result.messages)) {
|
||||
pinnedLines = narrow<ApiMessage[]>(result.messages).map((msg) => {
|
||||
const id = msg.id ?? '?';
|
||||
const text = msg.message ?? '[Media/No text]';
|
||||
const date = msg.date ? new Date(msg.date * 1000).toISOString() : 'unknown';
|
||||
const id = msg.id ?? "?";
|
||||
const text = msg.message ?? "[Media/No text]";
|
||||
const date = msg.date
|
||||
? new Date(msg.date * 1000).toISOString()
|
||||
: "unknown";
|
||||
return `ID: ${id} | Date: ${date} | ${text}`;
|
||||
});
|
||||
}
|
||||
@@ -69,19 +76,19 @@ export async function getPinnedMessages(
|
||||
const pinned = allMessages.filter((m) => narrow<ApiMessage>(m).pinned);
|
||||
pinnedLines = pinned.map((msg) => {
|
||||
const f = formatMessage(msg);
|
||||
return `ID: ${f.id} | Date: ${f.date} | ${f.text || '[Media/No text]'}`;
|
||||
return `ID: ${f.id} | Date: ${f.date} | ${f.text || "[Media/No text]"}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (pinnedLines.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No pinned messages found.' }] };
|
||||
return { content: [{ type: "text", text: "No pinned messages found." }] };
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: pinnedLines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: pinnedLines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_pinned_messages',
|
||||
"get_pinned_messages",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { PrivacyResult } from '../apiResultTypes';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { PrivacyResult } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
@@ -12,7 +12,12 @@ export const tool: MCPTool = {
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
key: { type: 'string', description: 'Privacy key: phone_number, last_seen, profile_photo, forwards, phone_call, chat_invite', default: 'last_seen' },
|
||||
key: {
|
||||
type: "string",
|
||||
description:
|
||||
"Privacy key: phone_number, last_seen, profile_photo, forwards, phone_call, chat_invite",
|
||||
default: "last_seen",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -22,7 +27,7 @@ export async function getPrivacySettings(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const keyStr = typeof args.key === 'string' ? args.key : 'last_seen';
|
||||
const keyStr = typeof args.key === "string" ? args.key : "last_seen";
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const keyMap: Record<string, Api.TypeInputPrivacyKey> = {
|
||||
@@ -36,7 +41,19 @@ export async function getPrivacySettings(
|
||||
|
||||
const key = keyMap[keyStr];
|
||||
if (!key) {
|
||||
return { content: [{ type: 'text', text: 'Unknown privacy key: ' + keyStr + '. Valid keys: ' + Object.keys(keyMap).join(', ') }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
"Unknown privacy key: " +
|
||||
keyStr +
|
||||
". Valid keys: " +
|
||||
Object.keys(keyMap).join(", "),
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -45,14 +62,25 @@ export async function getPrivacySettings(
|
||||
|
||||
const rules = narrow<PrivacyResult>(result)?.rules;
|
||||
if (!rules || !Array.isArray(rules)) {
|
||||
return { content: [{ type: 'text', text: 'No privacy rules found for ' + keyStr + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "No privacy rules found for " + keyStr + "." },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const lines = rules.map((r) => r.className ?? 'Unknown rule');
|
||||
return { content: [{ type: 'text', text: 'Privacy settings for ' + keyStr + ':\n' + lines.join('\n') }] };
|
||||
const lines = rules.map((r) => r.className ?? "Unknown rule");
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Privacy settings for " + keyStr + ":\n" + lines.join("\n"),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_privacy_settings',
|
||||
"get_privacy_settings",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.PROFILE,
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import { optNumber } from '../args';
|
||||
import type { AdminLogResult } from '../apiResultTypes';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import { optNumber } from "../args";
|
||||
import type { AdminLogResult } from "../apiResultTypes";
|
||||
import { toInputChannel, narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
@@ -28,14 +28,26 @@ export async function getRecentActions(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const limit = optNumber(args, 'limit', 20);
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const limit = optNumber(args, "limit", 20);
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
if (chat.type !== 'channel' && chat.type !== 'supergroup') {
|
||||
return { content: [{ type: 'text', text: 'Recent actions are only available for channels/supergroups.' }], isError: true };
|
||||
if (chat.type !== "channel" && chat.type !== "supergroup") {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Recent actions are only available for channels/supergroups.",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
@@ -46,7 +58,7 @@ export async function getRecentActions(
|
||||
return client.invoke(
|
||||
new Api.channels.GetAdminLog({
|
||||
channel: toInputChannel(inputChannel),
|
||||
q: '',
|
||||
q: "",
|
||||
maxId: bigInt(0),
|
||||
minId: bigInt(0),
|
||||
limit,
|
||||
@@ -56,19 +68,19 @@ export async function getRecentActions(
|
||||
|
||||
const events = narrow<AdminLogResult>(result)?.events;
|
||||
if (!events || !Array.isArray(events) || events.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No recent actions found.' }] };
|
||||
return { content: [{ type: "text", text: "No recent actions found." }] };
|
||||
}
|
||||
|
||||
const lines = events.map((e) => {
|
||||
const date = e.date ? new Date(e.date * 1000).toISOString() : 'unknown';
|
||||
const action = e.action?.className ?? 'unknown';
|
||||
return date + ' | ' + action;
|
||||
const date = e.date ? new Date(e.date * 1000).toISOString() : "unknown";
|
||||
const action = e.action?.className ?? "unknown";
|
||||
return date + " | " + action;
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_recent_actions',
|
||||
"get_recent_actions",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.ADMIN,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import type { StickerSetsResult } from '../apiResultTypes';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import type { StickerSetsResult } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
@@ -21,22 +21,39 @@ export async function getStickerSets(
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
return client.invoke(new Api.messages.GetAllStickers({ hash: bigInt(0) }));
|
||||
return client.invoke(
|
||||
new Api.messages.GetAllStickers({ hash: bigInt(0) }),
|
||||
);
|
||||
});
|
||||
|
||||
const sets = narrow<StickerSetsResult>(result)?.sets;
|
||||
if (!sets || !Array.isArray(sets) || sets.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No sticker sets found.' }] };
|
||||
return { content: [{ type: "text", text: "No sticker sets found." }] };
|
||||
}
|
||||
|
||||
const lines = sets.map((s) => {
|
||||
return 'ID: ' + s.id + ' | ' + (s.title ?? 'Untitled') + ' (' + (s.count ?? 0) + ' stickers)';
|
||||
return (
|
||||
"ID: " +
|
||||
s.id +
|
||||
" | " +
|
||||
(s.title ?? "Untitled") +
|
||||
" (" +
|
||||
(s.count ?? 0) +
|
||||
" stickers)"
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.length + ' sticker sets:\n' + lines.join('\n') }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: lines.length + " sticker sets:\n" + lines.join("\n"),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_sticker_sets',
|
||||
"get_sticker_sets",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MEDIA,
|
||||
);
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import { optNumber } from '../args';
|
||||
import type { ApiPhoto } from '../apiResultTypes';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import { optNumber } from "../args";
|
||||
import type { ApiPhoto } from "../apiResultTypes";
|
||||
import { toInputUser, narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_user_photos',
|
||||
description: 'Get profile photos of a user',
|
||||
name: "get_user_photos",
|
||||
description: "Get profile photos of a user",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
user_id: { type: 'string', description: 'User ID' },
|
||||
limit: { type: 'number', description: 'Max photos', default: 10 },
|
||||
user_id: { type: "string", description: "User ID" },
|
||||
limit: { type: "number", description: "Max photos", default: 10 },
|
||||
},
|
||||
required: ['user_id'],
|
||||
required: ["user_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -27,8 +27,8 @@ export async function getUserPhotos(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const userId = validateId(args.user_id, 'user_id');
|
||||
const limit = optNumber(args, 'limit', 10);
|
||||
const userId = validateId(args.user_id, "user_id");
|
||||
const limit = optNumber(args, "limit", 10);
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -43,19 +43,33 @@ export async function getUserPhotos(
|
||||
);
|
||||
});
|
||||
|
||||
if (!result || !('photos' in result) || !Array.isArray(result.photos) || result.photos.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No photos found.' }] };
|
||||
if (
|
||||
!result ||
|
||||
!("photos" in result) ||
|
||||
!Array.isArray(result.photos) ||
|
||||
result.photos.length === 0
|
||||
) {
|
||||
return { content: [{ type: "text", text: "No photos found." }] };
|
||||
}
|
||||
|
||||
const lines = narrow<ApiPhoto[]>(result.photos).map((photo, i: number) => {
|
||||
const date = photo.date ? new Date(photo.date * 1000).toISOString() : 'unknown';
|
||||
return 'Photo ' + (i + 1) + ': ID ' + photo.id + ' | Date: ' + date;
|
||||
const date = photo.date
|
||||
? new Date(photo.date * 1000).toISOString()
|
||||
: "unknown";
|
||||
return "Photo " + (i + 1) + ": ID " + photo.id + " | Date: " + date;
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.length + ' photos found:\n' + lines.join('\n') }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: lines.length + " photos found:\n" + lines.join("\n"),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_user_photos',
|
||||
"get_user_photos",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.PROFILE,
|
||||
);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { ApiUser } from '../apiResultTypes';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { ApiUser } from "../apiResultTypes";
|
||||
import { toInputUser, narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_user_status',
|
||||
description: 'Get online status of a user',
|
||||
name: "get_user_status",
|
||||
description: "Get online status of a user",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
user_id: { type: 'string', description: 'User ID' },
|
||||
user_id: { type: "string", description: "User ID" },
|
||||
},
|
||||
required: ['user_id'],
|
||||
required: ["user_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function getUserStatus(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const userId = validateId(args.user_id, 'user_id');
|
||||
const userId = validateId(args.user_id, "user_id");
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -35,28 +35,41 @@ export async function getUserStatus(
|
||||
});
|
||||
|
||||
if (!result || !Array.isArray(result) || result.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'User ' + userId + ' not found.' }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: "User " + userId + " not found." }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const user = narrow<ApiUser>(result[0]);
|
||||
const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown';
|
||||
let statusText = 'unknown';
|
||||
const name =
|
||||
[user.firstName, user.lastName].filter(Boolean).join(" ") || "Unknown";
|
||||
let statusText = "unknown";
|
||||
|
||||
if (user.status) {
|
||||
const s = user.status;
|
||||
if (s.className === 'UserStatusOnline') statusText = 'Online';
|
||||
else if (s.className === 'UserStatusOffline')
|
||||
statusText = 'Offline (last seen: ' + (s.wasOnline ? new Date(s.wasOnline * 1000).toISOString() : 'unknown') + ')';
|
||||
else if (s.className === 'UserStatusRecently') statusText = 'Recently';
|
||||
else if (s.className === 'UserStatusLastWeek') statusText = 'Last week';
|
||||
else if (s.className === 'UserStatusLastMonth') statusText = 'Last month';
|
||||
else statusText = s.className ?? 'unknown';
|
||||
if (s.className === "UserStatusOnline") statusText = "Online";
|
||||
else if (s.className === "UserStatusOffline")
|
||||
statusText =
|
||||
"Offline (last seen: " +
|
||||
(s.wasOnline
|
||||
? new Date(s.wasOnline * 1000).toISOString()
|
||||
: "unknown") +
|
||||
")";
|
||||
else if (s.className === "UserStatusRecently") statusText = "Recently";
|
||||
else if (s.className === "UserStatusLastWeek") statusText = "Last week";
|
||||
else if (s.className === "UserStatusLastMonth") statusText = "Last month";
|
||||
else statusText = s.className ?? "unknown";
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: name + ' (ID: ' + user.id + '): ' + statusText }] };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: name + " (ID: " + user.id + "): " + statusText },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'get_user_status',
|
||||
"get_user_status",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -39,7 +39,8 @@ export async function importChatInvite(
|
||||
return client.invoke(new Api.messages.ImportChatInvite({ hash }));
|
||||
});
|
||||
|
||||
const chatTitle = narrow<ResultWithChats>(result)?.chats?.[0]?.title ?? "unknown";
|
||||
const chatTitle =
|
||||
narrow<ResultWithChats>(result)?.chats?.[0]?.title ?? "unknown";
|
||||
return { content: [{ type: "text", text: `Joined chat: ${chatTitle}` }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
|
||||
@@ -67,7 +67,8 @@ export async function importContacts(
|
||||
);
|
||||
});
|
||||
|
||||
const imported = narrow<ImportContactsResult>(result)?.imported?.length ?? 0;
|
||||
const imported =
|
||||
narrow<ImportContactsResult>(result)?.imported?.length ?? 0;
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
|
||||
@@ -3,91 +3,154 @@
|
||||
* Each tool exports a 'tool' definition and a handler function
|
||||
*/
|
||||
|
||||
export { getChats, tool as getChatsTool } from './getChats';
|
||||
export { listChats, tool as listChatsTool } from './listChats';
|
||||
export { getChat, tool as getChatTool } from './getChat';
|
||||
export { createGroup, tool as createGroupTool } from './createGroup';
|
||||
export { inviteToGroup, tool as inviteToGroupTool } from './inviteToGroup';
|
||||
export { createChannel, tool as createChannelTool } from './createChannel';
|
||||
export { editChatTitle, tool as editChatTitleTool } from './editChatTitle';
|
||||
export { deleteChatPhoto, tool as deleteChatPhotoTool } from './deleteChatPhoto';
|
||||
export { leaveChat, tool as leaveChatTool } from './leaveChat';
|
||||
export { getParticipants, tool as getParticipantsTool } from './getParticipants';
|
||||
export { getAdmins, tool as getAdminsTool } from './getAdmins';
|
||||
export { getBannedUsers, tool as getBannedUsersTool } from './getBannedUsers';
|
||||
export { promoteAdmin, tool as promoteAdminTool } from './promoteAdmin';
|
||||
export { demoteAdmin, tool as demoteAdminTool } from './demoteAdmin';
|
||||
export { banUser, tool as banUserTool } from './banUser';
|
||||
export { unbanUser, tool as unbanUserTool } from './unbanUser';
|
||||
export { getInviteLink, tool as getInviteLinkTool } from './getInviteLink';
|
||||
export { exportChatInvite, tool as exportChatInviteTool } from './exportChatInvite';
|
||||
export { importChatInvite, tool as importChatInviteTool } from './importChatInvite';
|
||||
export { joinChatByLink, tool as joinChatByLinkTool } from './joinChatByLink';
|
||||
export { subscribePublicChannel, tool as subscribePublicChannelTool } from './subscribePublicChannel';
|
||||
export { muteChat, tool as muteChatTool } from './muteChat';
|
||||
export { unmuteChat, tool as unmuteChatTool } from './unmuteChat';
|
||||
export { archiveChat, tool as archiveChatTool } from './archiveChat';
|
||||
export { unarchiveChat, tool as unarchiveChatTool } from './unarchiveChat';
|
||||
export { getChats, tool as getChatsTool } from "./getChats";
|
||||
export { listChats, tool as listChatsTool } from "./listChats";
|
||||
export { getChat, tool as getChatTool } from "./getChat";
|
||||
export { createGroup, tool as createGroupTool } from "./createGroup";
|
||||
export { inviteToGroup, tool as inviteToGroupTool } from "./inviteToGroup";
|
||||
export { createChannel, tool as createChannelTool } from "./createChannel";
|
||||
export { editChatTitle, tool as editChatTitleTool } from "./editChatTitle";
|
||||
export {
|
||||
deleteChatPhoto,
|
||||
tool as deleteChatPhotoTool,
|
||||
} from "./deleteChatPhoto";
|
||||
export { leaveChat, tool as leaveChatTool } from "./leaveChat";
|
||||
export {
|
||||
getParticipants,
|
||||
tool as getParticipantsTool,
|
||||
} from "./getParticipants";
|
||||
export { getAdmins, tool as getAdminsTool } from "./getAdmins";
|
||||
export { getBannedUsers, tool as getBannedUsersTool } from "./getBannedUsers";
|
||||
export { promoteAdmin, tool as promoteAdminTool } from "./promoteAdmin";
|
||||
export { demoteAdmin, tool as demoteAdminTool } from "./demoteAdmin";
|
||||
export { banUser, tool as banUserTool } from "./banUser";
|
||||
export { unbanUser, tool as unbanUserTool } from "./unbanUser";
|
||||
export { getInviteLink, tool as getInviteLinkTool } from "./getInviteLink";
|
||||
export {
|
||||
exportChatInvite,
|
||||
tool as exportChatInviteTool,
|
||||
} from "./exportChatInvite";
|
||||
export {
|
||||
importChatInvite,
|
||||
tool as importChatInviteTool,
|
||||
} from "./importChatInvite";
|
||||
export { joinChatByLink, tool as joinChatByLinkTool } from "./joinChatByLink";
|
||||
export {
|
||||
subscribePublicChannel,
|
||||
tool as subscribePublicChannelTool,
|
||||
} from "./subscribePublicChannel";
|
||||
export { muteChat, tool as muteChatTool } from "./muteChat";
|
||||
export { unmuteChat, tool as unmuteChatTool } from "./unmuteChat";
|
||||
export { archiveChat, tool as archiveChatTool } from "./archiveChat";
|
||||
export { unarchiveChat, tool as unarchiveChatTool } from "./unarchiveChat";
|
||||
|
||||
export { getMessages, tool as getMessagesTool } from './getMessages';
|
||||
export { listMessages, tool as listMessagesTool } from './listMessages';
|
||||
export { listTopics, tool as listTopicsTool } from './listTopics';
|
||||
export { sendMessage, tool as sendMessageTool } from './sendMessage';
|
||||
export { replyToMessage, tool as replyToMessageTool } from './replyToMessage';
|
||||
export { editMessage, tool as editMessageTool } from './editMessage';
|
||||
export { deleteMessage, tool as deleteMessageTool } from './deleteMessage';
|
||||
export { forwardMessage, tool as forwardMessageTool } from './forwardMessage';
|
||||
export { pinMessage, tool as pinMessageTool } from './pinMessage';
|
||||
export { unpinMessage, tool as unpinMessageTool } from './unpinMessage';
|
||||
export { markAsRead, tool as markAsReadTool } from './markAsRead';
|
||||
export { getMessageContext, tool as getMessageContextTool } from './getMessageContext';
|
||||
export { getHistory, tool as getHistoryTool } from './getHistory';
|
||||
export { getPinnedMessages, tool as getPinnedMessagesTool } from './getPinnedMessages';
|
||||
export { sendReaction, tool as sendReactionTool } from './sendReaction';
|
||||
export { removeReaction, tool as removeReactionTool } from './removeReaction';
|
||||
export { getMessageReactions, tool as getMessageReactionsTool } from './getMessageReactions';
|
||||
export { listInlineButtons, tool as listInlineButtonsTool } from './listInlineButtons';
|
||||
export { pressInlineButton, tool as pressInlineButtonTool } from './pressInlineButton';
|
||||
export { saveDraft, tool as saveDraftTool } from './saveDraft';
|
||||
export { getDrafts, tool as getDraftsTool } from './getDrafts';
|
||||
export { clearDraft, tool as clearDraftTool } from './clearDraft';
|
||||
export { getMessages, tool as getMessagesTool } from "./getMessages";
|
||||
export { listMessages, tool as listMessagesTool } from "./listMessages";
|
||||
export { listTopics, tool as listTopicsTool } from "./listTopics";
|
||||
export { sendMessage, tool as sendMessageTool } from "./sendMessage";
|
||||
export { replyToMessage, tool as replyToMessageTool } from "./replyToMessage";
|
||||
export { editMessage, tool as editMessageTool } from "./editMessage";
|
||||
export { deleteMessage, tool as deleteMessageTool } from "./deleteMessage";
|
||||
export { forwardMessage, tool as forwardMessageTool } from "./forwardMessage";
|
||||
export { pinMessage, tool as pinMessageTool } from "./pinMessage";
|
||||
export { unpinMessage, tool as unpinMessageTool } from "./unpinMessage";
|
||||
export { markAsRead, tool as markAsReadTool } from "./markAsRead";
|
||||
export {
|
||||
getMessageContext,
|
||||
tool as getMessageContextTool,
|
||||
} from "./getMessageContext";
|
||||
export { getHistory, tool as getHistoryTool } from "./getHistory";
|
||||
export {
|
||||
getPinnedMessages,
|
||||
tool as getPinnedMessagesTool,
|
||||
} from "./getPinnedMessages";
|
||||
export { sendReaction, tool as sendReactionTool } from "./sendReaction";
|
||||
export { removeReaction, tool as removeReactionTool } from "./removeReaction";
|
||||
export {
|
||||
getMessageReactions,
|
||||
tool as getMessageReactionsTool,
|
||||
} from "./getMessageReactions";
|
||||
export {
|
||||
listInlineButtons,
|
||||
tool as listInlineButtonsTool,
|
||||
} from "./listInlineButtons";
|
||||
export {
|
||||
pressInlineButton,
|
||||
tool as pressInlineButtonTool,
|
||||
} from "./pressInlineButton";
|
||||
export { saveDraft, tool as saveDraftTool } from "./saveDraft";
|
||||
export { getDrafts, tool as getDraftsTool } from "./getDrafts";
|
||||
export { clearDraft, tool as clearDraftTool } from "./clearDraft";
|
||||
|
||||
export { listContacts, tool as listContactsTool } from './listContacts';
|
||||
export { searchContacts, tool as searchContactsTool } from './searchContacts';
|
||||
export { addContact, tool as addContactTool } from './addContact';
|
||||
export { deleteContact, tool as deleteContactTool } from './deleteContact';
|
||||
export { blockUser, tool as blockUserTool } from './blockUser';
|
||||
export { unblockUser, tool as unblockUserTool } from './unblockUser';
|
||||
export { getBlockedUsers, tool as getBlockedUsersTool } from './getBlockedUsers';
|
||||
export { listContacts, tool as listContactsTool } from "./listContacts";
|
||||
export { searchContacts, tool as searchContactsTool } from "./searchContacts";
|
||||
export { addContact, tool as addContactTool } from "./addContact";
|
||||
export { deleteContact, tool as deleteContactTool } from "./deleteContact";
|
||||
export { blockUser, tool as blockUserTool } from "./blockUser";
|
||||
export { unblockUser, tool as unblockUserTool } from "./unblockUser";
|
||||
export {
|
||||
getBlockedUsers,
|
||||
tool as getBlockedUsersTool,
|
||||
} from "./getBlockedUsers";
|
||||
|
||||
export { getMe, tool as getMeTool } from './getMe';
|
||||
export { updateProfile, tool as updateProfileTool } from './updateProfile';
|
||||
export { getUserPhotos, tool as getUserPhotosTool } from './getUserPhotos';
|
||||
export { getUserStatus, tool as getUserStatusTool } from './getUserStatus';
|
||||
export { getMe, tool as getMeTool } from "./getMe";
|
||||
export { updateProfile, tool as updateProfileTool } from "./updateProfile";
|
||||
export { getUserPhotos, tool as getUserPhotosTool } from "./getUserPhotos";
|
||||
export { getUserStatus, tool as getUserStatusTool } from "./getUserStatus";
|
||||
|
||||
export { searchPublicChats, tool as searchPublicChatsTool } from './searchPublicChats';
|
||||
export { searchMessages, tool as searchMessagesTool } from './searchMessages';
|
||||
export { resolveUsername, tool as resolveUsernameTool } from './resolveUsername';
|
||||
export {
|
||||
searchPublicChats,
|
||||
tool as searchPublicChatsTool,
|
||||
} from "./searchPublicChats";
|
||||
export { searchMessages, tool as searchMessagesTool } from "./searchMessages";
|
||||
export {
|
||||
resolveUsername,
|
||||
tool as resolveUsernameTool,
|
||||
} from "./resolveUsername";
|
||||
|
||||
export { getMediaInfo, tool as getMediaInfoTool } from './getMediaInfo';
|
||||
export { getRecentActions, tool as getRecentActionsTool } from './getRecentActions';
|
||||
export { createPoll, tool as createPollTool } from './createPoll';
|
||||
export { getBotInfo, tool as getBotInfoTool } from './getBotInfo';
|
||||
export { setBotCommands, tool as setBotCommandsTool } from './setBotCommands';
|
||||
export { getMediaInfo, tool as getMediaInfoTool } from "./getMediaInfo";
|
||||
export {
|
||||
getRecentActions,
|
||||
tool as getRecentActionsTool,
|
||||
} from "./getRecentActions";
|
||||
export { createPoll, tool as createPollTool } from "./createPoll";
|
||||
export { getBotInfo, tool as getBotInfoTool } from "./getBotInfo";
|
||||
export { setBotCommands, tool as setBotCommandsTool } from "./setBotCommands";
|
||||
|
||||
export { getPrivacySettings, tool as getPrivacySettingsTool } from './getPrivacySettings';
|
||||
export { setPrivacySettings, tool as setPrivacySettingsTool } from './setPrivacySettings';
|
||||
export {
|
||||
getPrivacySettings,
|
||||
tool as getPrivacySettingsTool,
|
||||
} from "./getPrivacySettings";
|
||||
export {
|
||||
setPrivacySettings,
|
||||
tool as setPrivacySettingsTool,
|
||||
} from "./setPrivacySettings";
|
||||
|
||||
export { setProfilePhoto, tool as setProfilePhotoTool } from './setProfilePhoto';
|
||||
export { deleteProfilePhoto, tool as deleteProfilePhotoTool } from './deleteProfilePhoto';
|
||||
export { editChatPhoto, tool as editChatPhotoTool } from './editChatPhoto';
|
||||
export {
|
||||
setProfilePhoto,
|
||||
tool as setProfilePhotoTool,
|
||||
} from "./setProfilePhoto";
|
||||
export {
|
||||
deleteProfilePhoto,
|
||||
tool as deleteProfilePhotoTool,
|
||||
} from "./deleteProfilePhoto";
|
||||
export { editChatPhoto, tool as editChatPhotoTool } from "./editChatPhoto";
|
||||
|
||||
export { getStickerSets, tool as getStickerSetsTool } from './getStickerSets';
|
||||
export { getGifSearch, tool as getGifSearchTool } from './getGifSearch';
|
||||
export { getStickerSets, tool as getStickerSetsTool } from "./getStickerSets";
|
||||
export { getGifSearch, tool as getGifSearchTool } from "./getGifSearch";
|
||||
|
||||
export { getContactIds, tool as getContactIdsTool } from './getContactIds';
|
||||
export { importContacts, tool as importContactsTool } from './importContacts';
|
||||
export { exportContacts, tool as exportContactsTool } from './exportContacts';
|
||||
export { getDirectChatByContact, tool as getDirectChatByContactTool } from './getDirectChatByContact';
|
||||
export { getContactChats, tool as getContactChatsTool } from './getContactChats';
|
||||
export { getLastInteraction, tool as getLastInteractionTool } from './getLastInteraction';
|
||||
export { getContactIds, tool as getContactIdsTool } from "./getContactIds";
|
||||
export { importContacts, tool as importContactsTool } from "./importContacts";
|
||||
export { exportContacts, tool as exportContactsTool } from "./exportContacts";
|
||||
export {
|
||||
getDirectChatByContact,
|
||||
tool as getDirectChatByContactTool,
|
||||
} from "./getDirectChatByContact";
|
||||
export {
|
||||
getContactChats,
|
||||
tool as getContactChatsTool,
|
||||
} from "./getContactChats";
|
||||
export {
|
||||
getLastInteraction,
|
||||
tool as getLastInteractionTool,
|
||||
} from "./getLastInteraction";
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { ResultWithChats } from '../apiResultTypes';
|
||||
import { narrow } from '../apiCastHelpers';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { ResultWithChats } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'join_chat_by_link',
|
||||
description: 'Join a chat using an invite link',
|
||||
name: "join_chat_by_link",
|
||||
description: "Join a chat using an invite link",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
link: { type: 'string', description: 'Invite link (e.g. https://t.me/+HASH or https://t.me/joinchat/HASH)' },
|
||||
link: {
|
||||
type: "string",
|
||||
description:
|
||||
"Invite link (e.g. https://t.me/+HASH or https://t.me/joinchat/HASH)",
|
||||
},
|
||||
},
|
||||
required: ['link'],
|
||||
required: ["link"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,8 +27,12 @@ export async function joinChatByLink(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const link = typeof args.link === 'string' ? args.link : '';
|
||||
if (!link) return { content: [{ type: 'text', text: 'link is required' }], isError: true };
|
||||
const link = typeof args.link === "string" ? args.link : "";
|
||||
if (!link)
|
||||
return {
|
||||
content: [{ type: "text", text: "link is required" }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
// Extract hash from link
|
||||
let hash = link;
|
||||
@@ -39,11 +47,12 @@ export async function joinChatByLink(
|
||||
return client.invoke(new Api.messages.ImportChatInvite({ hash }));
|
||||
});
|
||||
|
||||
const chatTitle = narrow<ResultWithChats>(result)?.chats?.[0]?.title ?? 'unknown';
|
||||
return { content: [{ type: 'text', text: `Joined chat: ${chatTitle}` }] };
|
||||
const chatTitle =
|
||||
narrow<ResultWithChats>(result)?.chats?.[0]?.title ?? "unknown";
|
||||
return { content: [{ type: "text", text: `Joined chat: ${chatTitle}` }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'join_chat_by_link',
|
||||
"join_chat_by_link",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.GROUP,
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import type { ApiUser } from '../apiResultTypes';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import type { ApiUser } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'list_contacts',
|
||||
description: 'List all contacts in your Telegram account',
|
||||
inputSchema: { type: 'object', properties: {} },
|
||||
name: "list_contacts",
|
||||
description: "List all contacts in your Telegram account",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
};
|
||||
|
||||
export async function listContacts(
|
||||
@@ -23,25 +23,26 @@ export async function listContacts(
|
||||
return client.invoke(new Api.contacts.GetContacts({ hash: bigInt(0) }));
|
||||
});
|
||||
|
||||
if (!result || !('users' in result) || !Array.isArray(result.users)) {
|
||||
return { content: [{ type: 'text', text: 'No contacts found.' }] };
|
||||
if (!result || !("users" in result) || !Array.isArray(result.users)) {
|
||||
return { content: [{ type: "text", text: "No contacts found." }] };
|
||||
}
|
||||
|
||||
const lines = result.users.map((u: ApiUser) => {
|
||||
const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown';
|
||||
const username = u.username ? `@${u.username}` : '';
|
||||
const phone = u.phone ? `+${u.phone}` : '';
|
||||
const name =
|
||||
[u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown";
|
||||
const username = u.username ? `@${u.username}` : "";
|
||||
const phone = u.phone ? `+${u.phone}` : "";
|
||||
return `ID: ${u.id} | ${name} ${username} ${phone}`.trim();
|
||||
});
|
||||
|
||||
if (lines.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No contacts found.' }] };
|
||||
return { content: [{ type: "text", text: "No contacts found." }] };
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'list_contacts',
|
||||
"list_contacts",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById, getMessages } from '../telegramApi';
|
||||
import type { MessageWithReplyMarkup, ReplyMarkupRow } from '../apiResultTypes';
|
||||
import { narrow } from '../apiCastHelpers';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById, getMessages } from "../telegramApi";
|
||||
import type { MessageWithReplyMarkup, ReplyMarkupRow } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "list_inline_buttons",
|
||||
@@ -24,44 +24,82 @@ export async function listInlineButtons(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
|
||||
if (messageId === undefined) {
|
||||
return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const messages = await getMessages(chatId, 200, 0);
|
||||
if (!messages) return { content: [{ type: 'text', text: 'No messages found.' }] };
|
||||
if (!messages)
|
||||
return { content: [{ type: "text", text: "No messages found." }] };
|
||||
|
||||
const found = messages.find((m) => String(m.id) === String(messageId));
|
||||
const msg = found ? narrow<MessageWithReplyMarkup>(found) : undefined;
|
||||
if (!msg) return { content: [{ type: 'text', text: 'Message ' + messageId + ' not found in cache.' }], isError: true };
|
||||
if (!msg)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Message " + messageId + " not found in cache.",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
if (!msg.replyMarkup || !msg.replyMarkup.rows) {
|
||||
return { content: [{ type: 'text', text: 'No inline buttons on message ' + messageId + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No inline buttons on message " + messageId + ".",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
msg.replyMarkup.rows.forEach((row: ReplyMarkupRow, ri: number) => {
|
||||
if (row.buttons) {
|
||||
row.buttons.forEach((btn, bi: number) => {
|
||||
lines.push('Row ' + ri + ', Button ' + bi + ': "' + (btn.text ?? '?') + '"');
|
||||
lines.push(
|
||||
"Row " + ri + ", Button " + bi + ': "' + (btn.text ?? "?") + '"',
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (lines.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No inline buttons on message ' + messageId + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No inline buttons on message " + messageId + ".",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'list_inline_buttons',
|
||||
"list_inline_buttons",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { ForumTopicsResult } from '../apiResultTypes';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { ForumTopicsResult } from "../apiResultTypes";
|
||||
import { toInputChannel, narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'list_topics',
|
||||
description: 'List topics in a forum chat',
|
||||
inputSchema: { type: 'object', properties: { chat_id: { type: 'string', description: 'Chat ID or username' } }, required: ['chat_id'] },
|
||||
name: "list_topics",
|
||||
description: "List topics in a forum chat",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
},
|
||||
required: ["chat_id"],
|
||||
},
|
||||
};
|
||||
|
||||
export async function listTopics(
|
||||
@@ -19,13 +25,25 @@ export async function listTopics(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
if (chat.type !== 'channel' && chat.type !== 'supergroup') {
|
||||
return { content: [{ type: 'text', text: 'Forum topics are only available for channels/supergroups.' }], isError: true };
|
||||
if (chat.type !== "channel" && chat.type !== "supergroup") {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Forum topics are only available for channels/supergroups.",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
@@ -46,17 +64,17 @@ export async function listTopics(
|
||||
|
||||
const topics = narrow<ForumTopicsResult>(result)?.topics;
|
||||
if (!topics || !Array.isArray(topics) || topics.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No forum topics found.' }] };
|
||||
return { content: [{ type: "text", text: "No forum topics found." }] };
|
||||
}
|
||||
|
||||
const lines = topics.map((t) => {
|
||||
return 'ID: ' + t.id + ' | ' + (t.title ?? 'Untitled');
|
||||
return "ID: " + t.id + " | " + (t.title ?? "Untitled");
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'list_topics',
|
||||
"list_topics",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.GROUP,
|
||||
);
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'mark_as_read',
|
||||
description: 'Mark messages as read in a chat',
|
||||
name: "mark_as_read",
|
||||
description: "Mark messages as read in a chat",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
|
||||
required: ['chat_id'],
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
},
|
||||
required: ["chat_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -20,11 +22,14 @@ export async function markAsRead(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) {
|
||||
return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -35,11 +40,13 @@ export async function markAsRead(
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Messages in chat ${chatId} marked as read.` }],
|
||||
content: [
|
||||
{ type: "text", text: `Messages in chat ${chatId} marked as read.` },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'mark_as_read',
|
||||
"mark_as_read",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'mute_chat',
|
||||
description: 'Mute notifications for a chat',
|
||||
name: "mute_chat",
|
||||
description: "Mute notifications for a chat",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
duration: { type: 'number', description: 'Mute duration in seconds (0 = forever)', default: 0 },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
duration: {
|
||||
type: "number",
|
||||
description: "Mute duration in seconds (0 = forever)",
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
required: ['chat_id'],
|
||||
required: ["chat_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,16 +28,21 @@ export async function muteChat(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const duration = typeof args.duration === 'number' ? args.duration : 0;
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const duration = typeof args.duration === "number" ? args.duration : 0;
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
|
||||
const muteUntil = duration === 0 ? 2147483647 : Math.floor(Date.now() / 1000) + duration;
|
||||
const muteUntil =
|
||||
duration === 0 ? 2147483647 : Math.floor(Date.now() / 1000) + duration;
|
||||
|
||||
await mtprotoService.withFloodWaitHandling(async () => {
|
||||
const inputPeer = await client.getInputEntity(entity);
|
||||
@@ -47,10 +56,10 @@ export async function muteChat(
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: `Chat ${chatId} muted.` }] };
|
||||
return { content: [{ type: "text", text: `Chat ${chatId} muted.` }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'mute_chat',
|
||||
"mute_chat",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CHAT,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { MCPToolResult } from '../../types';
|
||||
import type { MCPToolResult } from "../../types";
|
||||
|
||||
export function notImplemented(name: string): MCPToolResult {
|
||||
return {
|
||||
content: [{ type: 'text', text: `${name} is not implemented yet.` }],
|
||||
content: [{ type: "text", text: `${name} is not implemented yet.` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'pin_message',
|
||||
description: 'Pin a message in a chat',
|
||||
name: "pin_message",
|
||||
description: "Pin a message in a chat",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
message_id: { type: 'number', description: 'Message ID' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
message_id: { type: "number", description: "Message ID" },
|
||||
},
|
||||
required: ['chat_id', 'message_id'],
|
||||
required: ["chat_id", "message_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,21 +23,27 @@ export async function pinMessage(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
|
||||
if (messageId === undefined) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'message_id must be a positive integer' }],
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) {
|
||||
return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -48,11 +54,16 @@ export async function pinMessage(
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Message ${messageId} pinned in chat ${chatId}.` }],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Message ${messageId} pinned in chat ${chatId}.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'pin_message',
|
||||
"pin_message",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { BotCallbackAnswer } from '../apiResultTypes';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { BotCallbackAnswer } from "../apiResultTypes";
|
||||
import { narrow } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
@@ -27,17 +27,33 @@ export async function pressInlineButton(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
|
||||
const data = typeof args.button_text === 'string' ? args.button_text : '';
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const data = typeof args.button_text === "string" ? args.button_text : "";
|
||||
|
||||
if (messageId === undefined) {
|
||||
return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
if (!data) return { content: [{ type: 'text', text: 'button_text is required' }], isError: true };
|
||||
if (!data)
|
||||
return {
|
||||
content: [{ type: "text", text: "button_text is required" }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -48,16 +64,18 @@ export async function pressInlineButton(
|
||||
new Api.messages.GetBotCallbackAnswer({
|
||||
peer: inputPeer,
|
||||
msgId: messageId,
|
||||
data: Buffer.from(data, 'base64'),
|
||||
data: Buffer.from(data, "base64"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const answer = narrow<BotCallbackAnswer>(result)?.message ?? 'Button pressed (no response message).';
|
||||
return { content: [{ type: 'text', text: answer }] };
|
||||
const answer =
|
||||
narrow<BotCallbackAnswer>(result)?.message ??
|
||||
"Button pressed (no response message).";
|
||||
return { content: [{ type: "text", text: answer }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'press_inline_button',
|
||||
"press_inline_button",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'remove_reaction',
|
||||
description: 'Remove a reaction from a message',
|
||||
name: "remove_reaction",
|
||||
description: "Remove a reaction from a message",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
message_id: { type: 'number', description: 'Message ID' },
|
||||
reaction: { type: 'string', description: 'Reaction to remove' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
message_id: { type: "number", description: "Message ID" },
|
||||
reaction: { type: "string", description: "Reaction to remove" },
|
||||
},
|
||||
required: ['chat_id', 'message_id'],
|
||||
required: ["chat_id", "message_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,15 +25,27 @@ export async function removeReaction(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
|
||||
if (messageId === undefined) {
|
||||
return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -49,10 +61,17 @@ export async function removeReaction(
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Reaction removed from message ' + messageId + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Reaction removed from message " + messageId + ".",
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'remove_reaction',
|
||||
"remove_reaction",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -34,21 +34,22 @@ export async function replyToMessage(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const text = typeof args.text === 'string' ? args.text : '';
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const text = typeof args.text === "string" ? args.text : "";
|
||||
|
||||
if (messageId === undefined) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'message_id must be an integer' }],
|
||||
content: [{ type: "text", text: "message_id must be an integer" }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
if (!text) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'text is required' }],
|
||||
content: [{ type: "text", text: "text is required" }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
@@ -66,7 +67,7 @@ export async function replyToMessage(
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
type: "text",
|
||||
text: `Failed to reply to message ${messageId} in chat ${chatId}.`,
|
||||
},
|
||||
],
|
||||
@@ -77,7 +78,7 @@ export async function replyToMessage(
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
type: "text",
|
||||
text: `Replied to message ${messageId} in chat ${chatId}.`,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "save_draft",
|
||||
@@ -24,12 +24,20 @@ export async function saveDraft(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const text = typeof args.text === 'string' ? args.text : '';
|
||||
if (!text) return { content: [{ type: 'text', text: 'text is required' }], isError: true };
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const text = typeof args.text === "string" ? args.text : "";
|
||||
if (!text)
|
||||
return {
|
||||
content: [{ type: "text", text: "text is required" }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -44,10 +52,12 @@ export async function saveDraft(
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Draft saved in chat ' + chatId + '.' }] };
|
||||
return {
|
||||
content: [{ type: "text", text: "Draft saved in chat " + chatId + "." }],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'save_draft',
|
||||
"save_draft",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.DRAFT,
|
||||
);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { optNumber } from '../args';
|
||||
import type { ApiUser } from '../apiResultTypes';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { optNumber } from "../args";
|
||||
import type { ApiUser } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'search_contacts',
|
||||
description: 'Search contacts by name or username',
|
||||
name: "search_contacts",
|
||||
description: "Search contacts by name or username",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
limit: { type: 'number', description: 'Max results', default: 20 },
|
||||
query: { type: "string", description: "Search query" },
|
||||
limit: { type: "number", description: "Max results", default: 20 },
|
||||
},
|
||||
required: ['query'],
|
||||
required: ["query"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,31 +24,42 @@ export async function searchContacts(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const query = typeof args.query === 'string' ? args.query : '';
|
||||
const query = typeof args.query === "string" ? args.query : "";
|
||||
if (!query) {
|
||||
return { content: [{ type: 'text', text: 'query is required' }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: "query is required" }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
const limit = optNumber(args, 'limit', 20);
|
||||
const limit = optNumber(args, "limit", 20);
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
return client.invoke(new Api.contacts.Search({ q: query, limit }));
|
||||
});
|
||||
|
||||
if (!result || !('users' in result) || !Array.isArray(result.users) || result.users.length === 0) {
|
||||
return { content: [{ type: 'text', text: `No contacts found for "${query}".` }] };
|
||||
if (
|
||||
!result ||
|
||||
!("users" in result) ||
|
||||
!Array.isArray(result.users) ||
|
||||
result.users.length === 0
|
||||
) {
|
||||
return {
|
||||
content: [{ type: "text", text: `No contacts found for "${query}".` }],
|
||||
};
|
||||
}
|
||||
|
||||
const lines = result.users.map((u: ApiUser) => {
|
||||
const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown';
|
||||
const username = u.username ? `@${u.username}` : '';
|
||||
const name =
|
||||
[u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown";
|
||||
const username = u.username ? `@${u.username}` : "";
|
||||
return `ID: ${u.id} | ${name} ${username}`.trim();
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'search_contacts',
|
||||
"search_contacts",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'send_reaction',
|
||||
description: 'Send a reaction to a message',
|
||||
name: "send_reaction",
|
||||
description: "Send a reaction to a message",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
message_id: { type: 'number', description: 'Message ID' },
|
||||
reaction: { type: 'string', description: 'Reaction emoji' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
message_id: { type: "number", description: "Message ID" },
|
||||
reaction: { type: "string", description: "Reaction emoji" },
|
||||
},
|
||||
required: ['chat_id', 'message_id'],
|
||||
required: ["chat_id", "message_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,16 +25,29 @@ export async function sendReaction(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
|
||||
const emoji = typeof args.reaction === 'string' ? args.reaction : '\ud83d\udc4d';
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const emoji =
|
||||
typeof args.reaction === "string" ? args.reaction : "\ud83d\udc4d";
|
||||
|
||||
if (messageId === undefined) {
|
||||
return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: "Chat not found: " + chatId }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -50,10 +63,17 @@ export async function sendReaction(
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Reaction ' + emoji + ' sent to message ' + messageId + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Reaction " + emoji + " sent to message " + messageId + ".",
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'send_reaction',
|
||||
"send_reaction",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { BotCommandInput } from '../apiResultTypes';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { BotCommandInput } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "set_bot_commands",
|
||||
@@ -24,28 +24,43 @@ export async function setBotCommands(
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const cmds = Array.isArray(args.commands) ? args.commands : [];
|
||||
if (cmds.length === 0) return { content: [{ type: 'text', text: 'commands array is required' }], isError: true };
|
||||
if (cmds.length === 0)
|
||||
return {
|
||||
content: [{ type: "text", text: "commands array is required" }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const botCommands = cmds.map((c: BotCommandInput) =>
|
||||
new Api.BotCommand({ command: String(c.command ?? ''), description: String(c.description ?? '') }),
|
||||
const botCommands = cmds.map(
|
||||
(c: BotCommandInput) =>
|
||||
new Api.BotCommand({
|
||||
command: String(c.command ?? ""),
|
||||
description: String(c.description ?? ""),
|
||||
}),
|
||||
);
|
||||
|
||||
await mtprotoService.withFloodWaitHandling(async () => {
|
||||
await client.invoke(
|
||||
new Api.bots.SetBotCommands({
|
||||
scope: new Api.BotCommandScopeDefault(),
|
||||
langCode: '',
|
||||
langCode: "",
|
||||
commands: botCommands,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Bot commands updated: ' + cmds.length + ' commands.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Bot commands updated: " + cmds.length + " commands.",
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'set_bot_commands',
|
||||
"set_bot_commands",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.PROFILE,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "set_privacy_settings",
|
||||
@@ -22,11 +22,19 @@ export async function setPrivacySettings(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const keyStr = typeof args.setting === 'string' ? args.setting : '';
|
||||
const ruleStr = typeof args.value === 'string' ? args.value : '';
|
||||
const keyStr = typeof args.setting === "string" ? args.setting : "";
|
||||
const ruleStr = typeof args.value === "string" ? args.value : "";
|
||||
|
||||
if (!keyStr) return { content: [{ type: 'text', text: 'setting is required' }], isError: true };
|
||||
if (!ruleStr) return { content: [{ type: 'text', text: 'value is required' }], isError: true };
|
||||
if (!keyStr)
|
||||
return {
|
||||
content: [{ type: "text", text: "setting is required" }],
|
||||
isError: true,
|
||||
};
|
||||
if (!ruleStr)
|
||||
return {
|
||||
content: [{ type: "text", text: "value is required" }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
@@ -41,7 +49,10 @@ export async function setPrivacySettings(
|
||||
|
||||
const key = keyMap[keyStr];
|
||||
if (!key) {
|
||||
return { content: [{ type: 'text', text: 'Unknown privacy key: ' + keyStr }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: "Unknown privacy key: " + keyStr }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const ruleMap: Record<string, Api.TypeInputPrivacyRule> = {
|
||||
@@ -52,17 +63,35 @@ export async function setPrivacySettings(
|
||||
|
||||
const rule = ruleMap[ruleStr];
|
||||
if (!rule) {
|
||||
return { content: [{ type: 'text', text: 'Unknown rule: ' + ruleStr + '. Valid: allow_all, allow_contacts, disallow_all' }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
"Unknown rule: " +
|
||||
ruleStr +
|
||||
". Valid: allow_all, allow_contacts, disallow_all",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
await mtprotoService.withFloodWaitHandling(async () => {
|
||||
await client.invoke(new Api.account.SetPrivacy({ key, rules: [rule] }));
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: 'Privacy setting ' + keyStr + ' set to ' + ruleStr + '.' }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Privacy setting " + keyStr + " set to " + ruleStr + ".",
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'set_privacy_settings',
|
||||
"set_privacy_settings",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.PROFILE,
|
||||
);
|
||||
|
||||
@@ -7,9 +7,9 @@ export const tool: MCPTool = {
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file_path: { type: 'string', description: 'Path to the photo file' },
|
||||
file_path: { type: "string", description: "Path to the photo file" },
|
||||
},
|
||||
required: ['file_path'],
|
||||
required: ["file_path"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,7 +18,12 @@ export async function setProfilePhoto(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'set_profile_photo requires file upload which is not supported via MCP text interface. Use the Telegram client directly.' }],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "set_profile_photo requires file upload which is not supported via MCP text interface. Use the Telegram client directly.",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { toInputChannel, toInputPeer } from '../apiCastHelpers';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { toInputChannel, toInputPeer } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'unban_user',
|
||||
description: 'Unban a user from a group or channel',
|
||||
name: "unban_user",
|
||||
description: "Unban a user from a group or channel",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
user_id: { type: 'string', description: 'User ID to unban' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
user_id: { type: "string", description: "User ID to unban" },
|
||||
},
|
||||
required: ['chat_id', 'user_id'],
|
||||
required: ["chat_id", "user_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,14 +25,26 @@ export async function unbanUser(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const userId = validateId(args.user_id, 'user_id');
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const userId = validateId(args.user_id, "user_id");
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
if (!chat)
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
|
||||
if (chat.type !== 'channel' && chat.type !== 'supergroup') {
|
||||
return { content: [{ type: 'text', text: 'Unban is only available for channels/supergroups.' }], isError: true };
|
||||
if (chat.type !== "channel" && chat.type !== "supergroup") {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Unban is only available for channels/supergroups.",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
@@ -50,10 +62,17 @@ export async function unbanUser(
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: `User ${userId} unbanned from ${chat.title ?? chatId}.` }] };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `User ${userId} unbanned from ${chat.title ?? chatId}.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'unban_user',
|
||||
"unban_user",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.ADMIN,
|
||||
);
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { toInputPeer } from '../apiCastHelpers';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { toInputPeer } from "../apiCastHelpers";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'unblock_user',
|
||||
description: 'Unblock a user',
|
||||
name: "unblock_user",
|
||||
description: "Unblock a user",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
user_id: { type: 'string', description: 'User ID to unblock' },
|
||||
user_id: { type: "string", description: "User ID to unblock" },
|
||||
},
|
||||
required: ['user_id'],
|
||||
required: ["user_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function unblockUser(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const userId = validateId(args.user_id, 'user_id');
|
||||
const userId = validateId(args.user_id, "user_id");
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -33,10 +33,10 @@ export async function unblockUser(
|
||||
);
|
||||
});
|
||||
|
||||
return { content: [{ type: 'text', text: `User ${userId} unblocked.` }] };
|
||||
return { content: [{ type: "text", text: `User ${userId} unblocked.` }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'unblock_user',
|
||||
"unblock_user",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.CONTACT,
|
||||
);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { validateId } from "../../validation";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'unpin_message',
|
||||
description: 'Unpin a message in a chat',
|
||||
name: "unpin_message",
|
||||
description: "Unpin a message in a chat",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
chat_id: { type: 'string', description: 'Chat ID or username' },
|
||||
message_id: { type: 'number', description: 'Message ID' },
|
||||
chat_id: { type: "string", description: "Chat ID or username" },
|
||||
message_id: { type: "number", description: "Message ID" },
|
||||
},
|
||||
required: ['chat_id', 'message_id'],
|
||||
required: ["chat_id", "message_id"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,21 +24,27 @@ export async function unpinMessage(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const chatId = validateId(args.chat_id, 'chat_id');
|
||||
const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
const chatId = validateId(args.chat_id, "chat_id");
|
||||
const messageId =
|
||||
typeof args.message_id === "number" && Number.isInteger(args.message_id)
|
||||
? args.message_id
|
||||
: undefined;
|
||||
|
||||
if (messageId === undefined) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'message_id must be a positive integer' }],
|
||||
content: [
|
||||
{ type: "text", text: "message_id must be a positive integer" },
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const chat = getChatById(chatId);
|
||||
if (!chat) {
|
||||
return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
|
||||
return {
|
||||
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
@@ -56,11 +62,16 @@ export async function unpinMessage(
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Message ${messageId} unpinned in chat ${chatId}.` }],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Message ${messageId} unpinned in chat ${chatId}.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'unpin_message',
|
||||
"unpin_message",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.MSG,
|
||||
);
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import type { MCPTool, MCPToolResult } from '../../types';
|
||||
import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { optString } from '../args';
|
||||
import type { MCPTool, MCPToolResult } from "../../types";
|
||||
import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { optString } from "../args";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'update_profile',
|
||||
description: 'Update your Telegram profile',
|
||||
name: "update_profile",
|
||||
description: "Update your Telegram profile",
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
type: "object",
|
||||
properties: {
|
||||
first_name: { type: 'string', description: 'New first name' },
|
||||
last_name: { type: 'string', description: 'New last name' },
|
||||
about: { type: 'string', description: 'New bio/about text' },
|
||||
first_name: { type: "string", description: "New first name" },
|
||||
last_name: { type: "string", description: "New last name" },
|
||||
about: { type: "string", description: "New bio/about text" },
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -23,12 +23,20 @@ export async function updateProfile(
|
||||
_context: TelegramMCPContext,
|
||||
): Promise<MCPToolResult> {
|
||||
try {
|
||||
const firstName = optString(args, 'first_name');
|
||||
const lastName = optString(args, 'last_name');
|
||||
const about = optString(args, 'about');
|
||||
const firstName = optString(args, "first_name");
|
||||
const lastName = optString(args, "last_name");
|
||||
const about = optString(args, "about");
|
||||
|
||||
if (!firstName && !lastName && !about) {
|
||||
return { content: [{ type: 'text', text: 'At least one of first_name, last_name, or about is required.' }], isError: true };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "At least one of first_name, last_name, or about is required.",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
@@ -44,14 +52,18 @@ export async function updateProfile(
|
||||
});
|
||||
|
||||
const updates: string[] = [];
|
||||
if (firstName) updates.push('first_name: ' + firstName);
|
||||
if (lastName) updates.push('last_name: ' + lastName);
|
||||
if (about) updates.push('about: ' + about);
|
||||
if (firstName) updates.push("first_name: " + firstName);
|
||||
if (lastName) updates.push("last_name: " + lastName);
|
||||
if (about) updates.push("about: " + about);
|
||||
|
||||
return { content: [{ type: 'text', text: 'Profile updated: ' + updates.join(', ') }] };
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "Profile updated: " + updates.join(", ") },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
'update_profile',
|
||||
"update_profile",
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
ErrorCategory.PROFILE,
|
||||
);
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
* Handles communication between frontend MCP server and backend MCP client
|
||||
*/
|
||||
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import type { MCPRequest, MCPResponse, SocketIOMCPTransport } from './types';
|
||||
import { mcpWarn } from './logger';
|
||||
import type { Socket } from "socket.io-client";
|
||||
import type { MCPRequest, MCPResponse, SocketIOMCPTransport } from "./types";
|
||||
import { mcpWarn } from "./logger";
|
||||
|
||||
export class SocketIOMCPTransportImpl implements SocketIOMCPTransport {
|
||||
private socket: Socket | null | undefined;
|
||||
private requestHandlers = new Map<string | number, (response: MCPResponse) => void>();
|
||||
private readonly eventPrefix = 'mcp:';
|
||||
private requestHandlers = new Map<
|
||||
string | number,
|
||||
(response: MCPResponse) => void
|
||||
>();
|
||||
private readonly eventPrefix = "mcp:";
|
||||
private responseHandler = (response: MCPResponse): void => {
|
||||
const handler = this.requestHandlers.get(response.id);
|
||||
if (handler) {
|
||||
@@ -35,7 +38,7 @@ export class SocketIOMCPTransportImpl implements SocketIOMCPTransport {
|
||||
|
||||
emit(event: string, data: unknown): void {
|
||||
if (!this.socket?.connected) {
|
||||
mcpWarn('Cannot emit MCP event: socket not connected', { event });
|
||||
mcpWarn("Cannot emit MCP event: socket not connected", { event });
|
||||
return;
|
||||
}
|
||||
this.socket.emit(`${this.eventPrefix}${event}`, data);
|
||||
@@ -53,7 +56,7 @@ export class SocketIOMCPTransportImpl implements SocketIOMCPTransport {
|
||||
|
||||
async request(request: MCPRequest, timeoutMs = 30000): Promise<MCPResponse> {
|
||||
if (!this.socket?.connected) {
|
||||
throw new Error('Socket not connected');
|
||||
throw new Error("Socket not connected");
|
||||
}
|
||||
|
||||
return new Promise<MCPResponse>((resolve, reject) => {
|
||||
@@ -71,7 +74,7 @@ export class SocketIOMCPTransportImpl implements SocketIOMCPTransport {
|
||||
}
|
||||
});
|
||||
|
||||
this.emit('request', request);
|
||||
this.emit("request", request);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface MCPServerConfig {
|
||||
}
|
||||
|
||||
export interface MCPToolInputSchema {
|
||||
type: 'object';
|
||||
type: "object";
|
||||
properties: Record<string, unknown>;
|
||||
required?: string[];
|
||||
}
|
||||
@@ -27,21 +27,21 @@ export interface MCPToolCall {
|
||||
|
||||
export interface MCPToolResult {
|
||||
content: Array<{
|
||||
type: 'text';
|
||||
type: "text";
|
||||
text: string;
|
||||
}>;
|
||||
isError?: boolean;
|
||||
}
|
||||
|
||||
export interface MCPRequest {
|
||||
jsonrpc: '2.0';
|
||||
jsonrpc: "2.0";
|
||||
id: string | number;
|
||||
method: string;
|
||||
params?: unknown;
|
||||
}
|
||||
|
||||
export interface MCPResponse {
|
||||
jsonrpc: '2.0';
|
||||
jsonrpc: "2.0";
|
||||
id: string | number;
|
||||
result?: unknown;
|
||||
error?: {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
export class ValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ValidationError';
|
||||
this.name = "ValidationError";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,8 @@ export class ValidationError extends Error {
|
||||
* Validate chat_id or user_id parameter
|
||||
* Supports integer IDs, string IDs, and usernames
|
||||
*/
|
||||
export function validateId(
|
||||
value: unknown,
|
||||
paramName: string,
|
||||
): number | string {
|
||||
if (typeof value === 'number') {
|
||||
export function validateId(value: unknown, paramName: string): number | string {
|
||||
if (typeof value === "number") {
|
||||
if (!Number.isInteger(value) || value < -(2 ** 63) || value > 2 ** 63 - 1) {
|
||||
throw new ValidationError(
|
||||
`Invalid ${paramName}: ${value}. ID is out of the valid integer range.`,
|
||||
@@ -26,7 +23,7 @@ export function validateId(
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
if (typeof value === "string") {
|
||||
const intValue = Number.parseInt(value, 10);
|
||||
if (!Number.isNaN(intValue) && Number.isFinite(intValue)) {
|
||||
if (intValue < -(2 ** 63) || intValue > 2 ** 63 - 1) {
|
||||
@@ -38,7 +35,7 @@ export function validateId(
|
||||
}
|
||||
|
||||
if (/^@?[a-zA-Z0-9_]{5,}$/.test(value)) {
|
||||
return value.startsWith('@') ? value : `@${value}`;
|
||||
return value.startsWith("@") ? value : `@${value}`;
|
||||
}
|
||||
|
||||
throw new ValidationError(
|
||||
@@ -59,9 +56,7 @@ export function validateIdList(
|
||||
paramName: string,
|
||||
): Array<number | string> {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new ValidationError(
|
||||
`Invalid ${paramName}: must be an array of IDs.`,
|
||||
);
|
||||
throw new ValidationError(`Invalid ${paramName}: must be an array of IDs.`);
|
||||
}
|
||||
|
||||
return value.map((item: unknown, index: number) => {
|
||||
|
||||
Reference in New Issue
Block a user