mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Add CLAUDE.md and apiResultTypes.ts for Telegram MCP documentation and type definitions
- Created CLAUDE.md to provide comprehensive guidance on the project, including architecture, commands, and environment variables. - Introduced apiResultTypes.ts to define TypeScript interfaces for various API responses from the Telegram MTProto API, enhancing type safety and clarity in tool implementations. - Updated multiple Telegram MCP tools to utilize the new type definitions, improving consistency and maintainability across the codebase. These changes establish a solid foundation for documentation and type safety, facilitating better development practices and integration with the Telegram API.
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Summary
|
||||
|
||||
Cross-platform crypto community communication platform built with **Tauri v2** (React 19 + Rust). Targets desktop (Windows, macOS) and mobile (Android, iOS). Features deep Telegram integration via MTProto, real-time Socket.io communication, and an MCP (Model Context Protocol) tool system for AI-driven Telegram interactions.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Frontend dev server only (port 1420)
|
||||
npm run dev
|
||||
|
||||
# Desktop dev with hot-reload (starts Vite + Tauri)
|
||||
npm run tauri dev
|
||||
|
||||
# Production build (TypeScript compile + Vite build + Tauri bundle)
|
||||
npm run tauri build
|
||||
|
||||
# Debug build with .app bundle (required for deep link testing on macOS)
|
||||
npm run tauri build -- --debug --bundles app
|
||||
|
||||
# Android
|
||||
npm run tauri android dev
|
||||
npm run tauri android build
|
||||
|
||||
# iOS
|
||||
npm run tauri ios dev
|
||||
npm run tauri ios build
|
||||
|
||||
# Rust checks
|
||||
cargo check --manifest-path src-tauri/Cargo.toml
|
||||
cargo clippy --manifest-path src-tauri/Cargo.toml
|
||||
```
|
||||
|
||||
No test framework is currently configured. No ESLint or Prettier configuration exists in the repo.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Provider Chain (App.tsx)
|
||||
|
||||
The app wraps in this order: `Redux Provider` → `PersistGate` → `SocketProvider` → `TelegramProvider` → `BrowserRouter` → `AppRoutes`. This ordering matters because Socket.io and Telegram providers depend on Redux auth state.
|
||||
|
||||
### State Management (Redux Toolkit + Persist)
|
||||
|
||||
State lives in `src/store/` using Redux Toolkit slices:
|
||||
- **authSlice** — JWT token, onboarding completion flag (persisted)
|
||||
- **userSlice** — user profile
|
||||
- **socketSlice** — connection status, socket ID
|
||||
- **telegramSlice** — connection/auth status, chats, messages, threads (selectively persisted; loading/error states excluded)
|
||||
|
||||
Redux Persist stores auth and telegram state to localStorage. The telegram slice has a complex nested structure in `src/store/telegram/` with separate files for types, reducers, extraReducers, and thunks.
|
||||
|
||||
### Service Layer (Singletons)
|
||||
|
||||
- **mtprotoService** (`src/services/mtprotoService.ts`) — Telegram MTProto client via `telegram` npm package. Session stored in localStorage as `telegram_session`. Auto-retries FLOOD_WAIT up to 60s.
|
||||
- **socketService** (`src/services/socketService.ts`) — Socket.io client. Auth token passed in socket `auth` object (not query string). Transports: polling first, then WebSocket.
|
||||
- **apiClient** (`src/services/apiClient.ts`) — HTTP client for REST backend.
|
||||
|
||||
### MCP System (`src/lib/mcp/`)
|
||||
|
||||
Model Context Protocol implementation for AI tool execution over Socket.io:
|
||||
- `transport.ts` — Socket.io JSON-RPC 2.0 transport with 30s timeout
|
||||
- `telegram/server.ts` — TelegramMCPServer manages 99 tool definitions
|
||||
- `telegram/tools/` — Individual tool files (one per Telegram API operation)
|
||||
- Tools use `big-integer` library for Telegram's large integer IDs
|
||||
|
||||
### Routing (`src/AppRoutes.tsx`)
|
||||
|
||||
```
|
||||
/ → Welcome (public)
|
||||
/login → Login (public)
|
||||
/onboarding → Onboarding (protected, requires auth, not yet onboarded)
|
||||
/home → Home (protected, requires auth + onboarded)
|
||||
* → DefaultRedirect (routes based on auth state)
|
||||
```
|
||||
|
||||
`PublicRoute` redirects authenticated users away. `ProtectedRoute` enforces auth and optionally onboarding status.
|
||||
|
||||
### Deep Link Auth Flow
|
||||
|
||||
Web-to-desktop handoff using `outsourced://` URL scheme:
|
||||
1. User authenticates in browser
|
||||
2. Browser redirects to `outsourced://auth?token=<loginToken>`
|
||||
3. Tauri catches the deep link, Rust `exchange_token` command calls backend via `reqwest` (bypasses CORS)
|
||||
4. Backend returns `sessionToken` + user object
|
||||
5. App stores session in Redux, navigates to onboarding/home
|
||||
|
||||
Key file: `src/utils/desktopDeepLinkListener.ts` (lazy-loaded in `main.tsx`). Uses `localStorage.deepLinkHandled` flag to prevent infinite reload loops. Deep links do NOT work in `tauri dev` on macOS — must use built `.app` bundle.
|
||||
|
||||
### Rust Backend (`src-tauri/src/lib.rs`)
|
||||
|
||||
Minimal — two Tauri commands:
|
||||
- `greet` — demo command
|
||||
- `exchange_token` — CORS-free HTTP POST to backend for token exchange
|
||||
|
||||
Deep link plugin registered at setup. `register_all()` called only on Windows/Linux (panics on macOS).
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Set in `.env` (Vite exposes `VITE_*` prefixed vars):
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `VITE_BACKEND_URL` | Backend API URL (default: `http://localhost:5005`) |
|
||||
| `VITE_TELEGRAM_API_ID` | Telegram MTProto API ID |
|
||||
| `VITE_TELEGRAM_API_HASH` | Telegram MTProto API hash |
|
||||
| `VITE_TELEGRAM_BOT_USERNAME` | Telegram bot username |
|
||||
| `VITE_TELEGRAM_BOT_ID` | Telegram bot numeric ID |
|
||||
| `VITE_DEBUG` | Debug mode flag |
|
||||
|
||||
Production defaults are in `src/utils/config.ts`.
|
||||
|
||||
## Key Patterns
|
||||
|
||||
- **Node polyfills**: Vite config (`vite.config.ts`) polyfills `buffer`, `process`, `util`, `os`, `crypto`, `stream` for the `telegram` npm package which requires Node APIs.
|
||||
- **Telegram IDs**: Use `big-integer` library, not native JS numbers (Telegram IDs exceed `Number.MAX_SAFE_INTEGER`).
|
||||
- **MCP tool files**: Each tool in `src/lib/mcp/telegram/tools/` exports a handler conforming to `TelegramMCPToolHandler` interface. Tool names are typed in `src/lib/mcp/telegram/types.ts`.
|
||||
- **Tauri IPC**: Frontend calls Rust via `invoke()` from `@tauri-apps/api/core`. Rust commands are registered in `generate_handler![]` macro.
|
||||
- **CORS workaround**: External HTTP requests from the WebView hit CORS. Use Rust `reqwest` via Tauri commands instead of browser `fetch()`.
|
||||
|
||||
## Platform Gotchas
|
||||
|
||||
- **macOS deep links**: Require `.app` bundle (not `tauri dev`). Clear WebKit caches when debugging stale content: `rm -rf ~/Library/WebKit/com.megamind.tauri-app ~/Library/Caches/com.megamind.tauri-app`
|
||||
- **Cargo caching**: May serve stale frontend assets on incremental builds. Run `cargo clean --manifest-path src-tauri/Cargo.toml` if the app shows outdated UI.
|
||||
- **`window.__TAURI__`**: Not available at module load time. Use dynamic `import()` and try/catch for Tauri plugin calls.
|
||||
@@ -0,0 +1,130 @@
|
||||
import type bigInt from "big-integer";
|
||||
|
||||
/** User object as returned by Telegram MTProto API */
|
||||
export interface ApiUser {
|
||||
id: bigInt.BigInteger;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
username?: string;
|
||||
phone?: string;
|
||||
bot?: boolean;
|
||||
status?: {
|
||||
className?: string;
|
||||
wasOnline?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/** Result containing chats array (CreateChat, CreateChannel, ImportChatInvite) */
|
||||
export interface ResultWithChats {
|
||||
chats?: Array<{ id: bigInt.BigInteger; title?: string }>;
|
||||
}
|
||||
|
||||
/** Full user info from users.GetFullUser */
|
||||
export interface FullUserResult {
|
||||
fullUser?: {
|
||||
about?: string;
|
||||
botInfo?: {
|
||||
description?: string;
|
||||
commands?: Array<{ command: string; description: string }>;
|
||||
};
|
||||
};
|
||||
users?: ApiUser[];
|
||||
}
|
||||
|
||||
/** Sticker sets from messages.GetAllStickers */
|
||||
export interface StickerSetsResult {
|
||||
sets?: Array<{ id: bigInt.BigInteger; title?: string; count?: number }>;
|
||||
}
|
||||
|
||||
/** Admin log from channels.GetAdminLog */
|
||||
export interface AdminLogResult {
|
||||
events?: Array<{ date?: number; action?: { className?: string } }>;
|
||||
}
|
||||
|
||||
/** Forum topics from channels.GetForumTopics */
|
||||
export interface ForumTopicsResult {
|
||||
topics?: Array<{ id: number; title?: string }>;
|
||||
}
|
||||
|
||||
/** Privacy rules from account.GetPrivacy */
|
||||
export interface PrivacyResult {
|
||||
rules?: Array<{ className?: string }>;
|
||||
}
|
||||
|
||||
/** Inline bot results from messages.GetInlineBotResults */
|
||||
export interface InlineBotResults {
|
||||
results?: Array<{ title?: string; description?: string }>;
|
||||
}
|
||||
|
||||
/** Chat invite from messages.ExportChatInvite */
|
||||
export interface ChatInviteResult {
|
||||
link?: string;
|
||||
}
|
||||
|
||||
/** Updates result (drafts, reactions) */
|
||||
export interface UpdatesResult {
|
||||
updates?: Array<{
|
||||
draft?: { message?: string };
|
||||
peer?: { userId?: number; chatId?: number; channelId?: number };
|
||||
reactions?: {
|
||||
results?: Array<{
|
||||
reaction?: { emoticon?: string; className?: string };
|
||||
count?: number;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
/** Bot callback answer */
|
||||
export interface BotCallbackAnswer {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/** Contact import result */
|
||||
export interface ImportContactsResult {
|
||||
imported?: unknown[];
|
||||
}
|
||||
|
||||
/** Contact ID entry */
|
||||
export interface ContactIdEntry {
|
||||
userId?: number;
|
||||
}
|
||||
|
||||
/** Photo object from the API */
|
||||
export interface ApiPhoto {
|
||||
id: bigInt.BigInteger;
|
||||
date?: number;
|
||||
accessHash?: bigInt.BigInteger;
|
||||
fileReference?: Buffer;
|
||||
}
|
||||
|
||||
/** API message shape (from search results) */
|
||||
export interface ApiMessage {
|
||||
id: number;
|
||||
message?: string;
|
||||
date?: number;
|
||||
pinned?: boolean;
|
||||
}
|
||||
|
||||
/** Reply markup types for inline buttons */
|
||||
export interface ReplyMarkupRow {
|
||||
buttons?: Array<{ text?: string }>;
|
||||
}
|
||||
|
||||
export interface MessageWithReplyMarkup {
|
||||
id: string | number;
|
||||
replyMarkup?: { rows?: ReplyMarkupRow[] };
|
||||
}
|
||||
|
||||
/** Bot command input shape */
|
||||
export interface BotCommandInput {
|
||||
command?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/** Contact input shape (for importContacts) */
|
||||
export interface ContactInput {
|
||||
phone?: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { optString } from "../args";
|
||||
import type { ResultWithChats } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "create_channel",
|
||||
@@ -50,7 +51,7 @@ export async function createChannel(
|
||||
);
|
||||
});
|
||||
|
||||
const channelId = (result as any)?.chats?.[0]?.id ?? "unknown";
|
||||
const channelId = (result as unknown as ResultWithChats)?.chats?.[0]?.id ?? "unknown";
|
||||
const type = megagroup ? "Supergroup" : "Channel";
|
||||
return {
|
||||
content: [
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { ResultWithChats } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "create_group",
|
||||
@@ -52,7 +53,7 @@ export async function createGroup(
|
||||
return client.invoke(new Api.messages.CreateChat({ title, users }));
|
||||
});
|
||||
|
||||
const chatId = (result as any)?.chats?.[0]?.id ?? "unknown";
|
||||
const chatId = (result as unknown as ResultWithChats)?.chats?.[0]?.id ?? "unknown";
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: `Group "${title}" created. Chat ID: ${chatId}` },
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import type { ApiPhoto } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "delete_profile_photo",
|
||||
@@ -33,7 +34,7 @@ export async function deleteProfilePhoto(
|
||||
return { content: [{ type: 'text', text: 'No profile photo to delete.' }] };
|
||||
}
|
||||
|
||||
const photo = photos.photos[0] as any;
|
||||
const photo = photos.photos[0] as unknown as ApiPhoto;
|
||||
|
||||
await mtprotoService.withFloodWaitHandling(async () => {
|
||||
await client.invoke(
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { optString } from '../args';
|
||||
import type { ChatInviteResult } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'export_chat_invite',
|
||||
@@ -50,7 +51,7 @@ export async function exportChatInvite(
|
||||
);
|
||||
});
|
||||
|
||||
const link = (result as any)?.link;
|
||||
const link = (result as unknown as ChatInviteResult)?.link;
|
||||
if (!link) {
|
||||
return { content: [{ type: 'text', text: 'Could not create invite link.' }], isError: true };
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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: "export_contacts",
|
||||
@@ -31,7 +32,7 @@ export async function exportContacts(
|
||||
return { content: [{ type: "text", text: "No contacts to export." }] };
|
||||
}
|
||||
|
||||
const contacts = result.users.map((u: any) => ({
|
||||
const contacts = result.users.map((u: ApiUser) => ({
|
||||
id: String(u.id),
|
||||
firstName: u.firstName ?? "",
|
||||
lastName: u.lastName ?? "",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import type { ApiUser } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_admins",
|
||||
@@ -36,7 +37,7 @@ export async function getAdmins(
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
|
||||
let admins: any[] = [];
|
||||
let admins: ApiUser[] = [];
|
||||
|
||||
if (chat.type === "channel" || chat.type === "supergroup") {
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -52,7 +53,7 @@ export async function getAdmins(
|
||||
);
|
||||
});
|
||||
if (result && "users" in result && Array.isArray(result.users)) {
|
||||
admins = result.users;
|
||||
admins = result.users as unknown as ApiUser[];
|
||||
}
|
||||
} else {
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -61,7 +62,7 @@ export async function getAdmins(
|
||||
);
|
||||
});
|
||||
if (result && "users" in result && Array.isArray(result.users)) {
|
||||
admins = result.users;
|
||||
admins = result.users as unknown as ApiUser[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ export async function getAdmins(
|
||||
return { content: [{ type: "text", text: "No admins found." }] };
|
||||
}
|
||||
|
||||
const lines = admins.map((u: any) => {
|
||||
const lines = admins.map((u: ApiUser) => {
|
||||
const name =
|
||||
[u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown";
|
||||
const username = u.username ? `@${u.username}` : "";
|
||||
|
||||
@@ -7,6 +7,7 @@ import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { optNumber } from "../args";
|
||||
import bigInt from "big-integer";
|
||||
import type { ApiUser } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_banned_users",
|
||||
@@ -73,7 +74,7 @@ export async function getBannedUsers(
|
||||
return { content: [{ type: "text", text: "No banned users found." }] };
|
||||
}
|
||||
|
||||
const lines = result.users.map((u: any) => {
|
||||
const lines = result.users.map((u: ApiUser) => {
|
||||
const name =
|
||||
[u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown";
|
||||
const username = u.username ? `@${u.username}` : "";
|
||||
|
||||
@@ -4,6 +4,7 @@ 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',
|
||||
@@ -34,7 +35,7 @@ export async function getBlockedUsers(
|
||||
return { content: [{ type: 'text', text: 'No blocked users.' }] };
|
||||
}
|
||||
|
||||
const lines = result.users.map((u: any) => {
|
||||
const lines = result.users.map((u: ApiUser) => {
|
||||
const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown';
|
||||
const username = u.username ? `@${u.username}` : '';
|
||||
return `ID: ${u.id} | ${name} ${username}`.trim();
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { FullUserResult } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_bot_info",
|
||||
@@ -32,8 +33,8 @@ export async function getBotInfo(
|
||||
);
|
||||
});
|
||||
|
||||
const fullUser = (result as any)?.fullUser;
|
||||
const user = (result as any)?.users?.[0];
|
||||
const fullUser = (result as unknown as FullUserResult)?.fullUser;
|
||||
const user = (result as unknown as FullUserResult)?.users?.[0];
|
||||
|
||||
if (!user) {
|
||||
return { content: [{ type: 'text', text: 'Bot not found: ' + botId }], isError: true };
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import type { ContactIdEntry } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_contact_ids',
|
||||
@@ -26,7 +27,7 @@ export async function getContactIds(
|
||||
return { content: [{ type: 'text', text: 'No contact IDs found.' }] };
|
||||
}
|
||||
|
||||
const ids = result.map((c: any) => String(c.userId ?? c));
|
||||
const ids = result.map((c: ContactIdEntry) => String(c.userId ?? c));
|
||||
return { content: [{ type: 'text', text: ids.length + ' contacts:\n' + ids.join('\n') }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { UpdatesResult } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_drafts",
|
||||
@@ -21,7 +22,7 @@ export async function getDrafts(
|
||||
return client.invoke(new Api.messages.GetAllDrafts());
|
||||
});
|
||||
|
||||
const updates = result as any;
|
||||
const updates = result as unknown as UpdatesResult;
|
||||
if (!updates || !updates.updates || updates.updates.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No drafts found.' }] };
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import { optNumber } from '../args';
|
||||
import type { InlineBotResults } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_gif_search",
|
||||
@@ -41,12 +42,12 @@ export async function getGifSearch(
|
||||
);
|
||||
});
|
||||
|
||||
const results = (result as any)?.results;
|
||||
const results = (result as unknown as InlineBotResults)?.results;
|
||||
if (!results || !Array.isArray(results) || results.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No GIFs found for: ' + query }] };
|
||||
}
|
||||
|
||||
const lines = results.slice(0, limit).map((r: any, i: number) => {
|
||||
const lines = results.slice(0, limit).map((r, i: number) => {
|
||||
const title = r.title ?? r.description ?? 'GIF ' + (i + 1);
|
||||
return (i + 1) + '. ' + title;
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { validateId } from "../../validation";
|
||||
import { getChatById } from "../telegramApi";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { ChatInviteResult } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_invite_link",
|
||||
@@ -45,7 +46,7 @@ export async function getInviteLink(
|
||||
);
|
||||
});
|
||||
|
||||
const link = (result as any)?.link;
|
||||
const link = (result as unknown as ChatInviteResult)?.link;
|
||||
if (!link) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Could not generate invite link." }],
|
||||
|
||||
@@ -5,6 +5,7 @@ import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { UpdatesResult } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_message_reactions',
|
||||
@@ -47,7 +48,7 @@ export async function getMessageReactions(
|
||||
);
|
||||
});
|
||||
|
||||
const updates = result as any;
|
||||
const updates = result as unknown as UpdatesResult;
|
||||
if (!updates || !updates.updates || updates.updates.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No reactions found on message ' + messageId + '.' }] };
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import { optNumber } from "../args";
|
||||
import bigInt from "big-integer";
|
||||
import type { ApiUser } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_participants",
|
||||
@@ -39,7 +40,7 @@ export async function getParticipants(
|
||||
const client = mtprotoService.getClient();
|
||||
const entity = chat.username ? chat.username : chat.id;
|
||||
|
||||
let participants: any[] = [];
|
||||
let participants: ApiUser[] = [];
|
||||
|
||||
if (chat.type === "channel" || chat.type === "supergroup") {
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -55,7 +56,7 @@ export async function getParticipants(
|
||||
);
|
||||
});
|
||||
if (result && "users" in result && Array.isArray(result.users)) {
|
||||
participants = result.users;
|
||||
participants = result.users as unknown as ApiUser[];
|
||||
}
|
||||
} else {
|
||||
const result = await mtprotoService.withFloodWaitHandling(async () => {
|
||||
@@ -64,7 +65,7 @@ export async function getParticipants(
|
||||
);
|
||||
});
|
||||
if (result && "users" in result && Array.isArray(result.users)) {
|
||||
participants = result.users;
|
||||
participants = result.users as unknown as ApiUser[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +73,7 @@ export async function getParticipants(
|
||||
return { content: [{ type: "text", text: "No participants found." }] };
|
||||
}
|
||||
|
||||
const lines = participants.map((u: any) => {
|
||||
const lines = participants.map((u: ApiUser) => {
|
||||
const name =
|
||||
[u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown";
|
||||
const username = u.username ? `@${u.username}` : "";
|
||||
|
||||
@@ -6,6 +6,7 @@ import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import type { ApiMessage } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_pinned_messages',
|
||||
@@ -53,7 +54,7 @@ export async function getPinnedMessages(
|
||||
);
|
||||
|
||||
if ('messages' in result && Array.isArray(result.messages)) {
|
||||
pinnedLines = result.messages.map((msg: any) => {
|
||||
pinnedLines = (result.messages as unknown as ApiMessage[]).map((msg) => {
|
||||
const id = msg.id ?? '?';
|
||||
const text = msg.message ?? '[Media/No text]';
|
||||
const date = msg.date ? new Date(msg.date * 1000).toISOString() : 'unknown';
|
||||
@@ -64,7 +65,7 @@ export async function getPinnedMessages(
|
||||
// Fallback: check cached messages for pinned flag
|
||||
const allMessages = await getMessages(chatId, 500, 0);
|
||||
if (allMessages) {
|
||||
const pinned = allMessages.filter((m: any) => m.pinned);
|
||||
const pinned = allMessages.filter((m) => (m as unknown as ApiMessage).pinned);
|
||||
pinnedLines = pinned.map((msg) => {
|
||||
const f = formatMessage(msg);
|
||||
return `ID: ${f.id} | Date: ${f.date} | ${f.text || '[Media/No text]'}`;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { PrivacyResult } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_privacy_settings",
|
||||
@@ -41,12 +42,12 @@ export async function getPrivacySettings(
|
||||
return client.invoke(new Api.account.GetPrivacy({ key }));
|
||||
});
|
||||
|
||||
const rules = (result as any)?.rules;
|
||||
const rules = (result as unknown as PrivacyResult)?.rules;
|
||||
if (!rules || !Array.isArray(rules)) {
|
||||
return { content: [{ type: 'text', text: 'No privacy rules found for ' + keyStr + '.' }] };
|
||||
}
|
||||
|
||||
const lines = rules.map((r: any) => r.className ?? 'Unknown rule');
|
||||
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(
|
||||
|
||||
@@ -7,6 +7,7 @@ import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import { optNumber } from '../args';
|
||||
import type { AdminLogResult } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_recent_actions",
|
||||
@@ -52,12 +53,12 @@ export async function getRecentActions(
|
||||
);
|
||||
});
|
||||
|
||||
const events = (result as any)?.events;
|
||||
const events = (result as unknown as AdminLogResult)?.events;
|
||||
if (!events || !Array.isArray(events) || events.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No recent actions found.' }] };
|
||||
}
|
||||
|
||||
const lines = events.map((e: any) => {
|
||||
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;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import type { StickerSetsResult } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "get_sticker_sets",
|
||||
@@ -22,12 +23,12 @@ export async function getStickerSets(
|
||||
return client.invoke(new Api.messages.GetAllStickers({ hash: bigInt(0) }));
|
||||
});
|
||||
|
||||
const sets = (result as any)?.sets;
|
||||
const sets = (result as unknown as StickerSetsResult)?.sets;
|
||||
if (!sets || !Array.isArray(sets) || sets.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No sticker sets found.' }] };
|
||||
}
|
||||
|
||||
const lines = sets.map((s: any) => {
|
||||
const lines = sets.map((s) => {
|
||||
return 'ID: ' + s.id + ' | ' + (s.title ?? 'Untitled') + ' (' + (s.count ?? 0) + ' stickers)';
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import bigInt from 'big-integer';
|
||||
import { optNumber } from '../args';
|
||||
import type { ApiPhoto } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_user_photos',
|
||||
@@ -45,7 +46,7 @@ export async function getUserPhotos(
|
||||
return { content: [{ type: 'text', text: 'No photos found.' }] };
|
||||
}
|
||||
|
||||
const lines = result.photos.map((photo: any, i: number) => {
|
||||
const lines = result.photos.map((photo: ApiPhoto, i: number) => {
|
||||
const date = photo.date ? new Date(photo.date * 1000).toISOString() : 'unknown';
|
||||
return 'Photo ' + (i + 1) + ': ID ' + photo.id + ' | Date: ' + date;
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { validateId } from '../../validation';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { ApiUser } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'get_user_status',
|
||||
@@ -36,7 +37,7 @@ export async function getUserStatus(
|
||||
return { content: [{ type: 'text', text: 'User ' + userId + ' not found.' }], isError: true };
|
||||
}
|
||||
|
||||
const user = result[0] as any;
|
||||
const user = result[0] as unknown as ApiUser;
|
||||
const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown';
|
||||
let statusText = 'unknown';
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { TelegramMCPContext } from "../types";
|
||||
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import type { ResultWithChats } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "import_chat_invite",
|
||||
@@ -37,7 +38,7 @@ export async function importChatInvite(
|
||||
return client.invoke(new Api.messages.ImportChatInvite({ hash }));
|
||||
});
|
||||
|
||||
const chatTitle = (result as any)?.chats?.[0]?.title ?? "unknown";
|
||||
const chatTitle = (result as unknown as ResultWithChats)?.chats?.[0]?.title ?? "unknown";
|
||||
return { content: [{ type: "text", text: `Joined chat: ${chatTitle}` }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from "../../errorHandler";
|
||||
import { mtprotoService } from "../../../../services/mtprotoService";
|
||||
import { Api } from "telegram";
|
||||
import bigInt from "big-integer";
|
||||
import type { ContactInput, ImportContactsResult } from "../apiResultTypes";
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "import_contacts",
|
||||
@@ -48,7 +49,7 @@ export async function importContacts(
|
||||
}
|
||||
|
||||
const inputContacts = contactsArg.map(
|
||||
(c: any, i: number) =>
|
||||
(c: ContactInput, i: number) =>
|
||||
new Api.InputPhoneContact({
|
||||
clientId: bigInt(i),
|
||||
phone: String(c.phone ?? ""),
|
||||
@@ -65,7 +66,7 @@ export async function importContacts(
|
||||
);
|
||||
});
|
||||
|
||||
const imported = (result as any)?.imported?.length ?? 0;
|
||||
const imported = (result as unknown as ImportContactsResult)?.imported?.length ?? 0;
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { TelegramMCPContext } from '../types';
|
||||
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { ResultWithChats } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'join_chat_by_link',
|
||||
@@ -37,7 +38,7 @@ export async function joinChatByLink(
|
||||
return client.invoke(new Api.messages.ImportChatInvite({ hash }));
|
||||
});
|
||||
|
||||
const chatTitle = (result as any)?.chats?.[0]?.title ?? 'unknown';
|
||||
const chatTitle = (result as unknown as ResultWithChats)?.chats?.[0]?.title ?? 'unknown';
|
||||
return { content: [{ type: 'text', text: `Joined chat: ${chatTitle}` }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
|
||||
@@ -4,6 +4,7 @@ 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',
|
||||
@@ -26,7 +27,7 @@ export async function listContacts(
|
||||
return { content: [{ type: 'text', text: 'No contacts found.' }] };
|
||||
}
|
||||
|
||||
const lines = result.users.map((u: any) => {
|
||||
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}` : '';
|
||||
|
||||
@@ -3,6 +3,7 @@ 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';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "list_inline_buttons",
|
||||
@@ -35,7 +36,7 @@ export async function listInlineButtons(
|
||||
const messages = await getMessages(chatId, 200, 0);
|
||||
if (!messages) return { content: [{ type: 'text', text: 'No messages found.' }] };
|
||||
|
||||
const msg = messages.find((m) => String(m.id) === String(messageId)) as any;
|
||||
const msg = messages.find((m) => String(m.id) === String(messageId)) as unknown as MessageWithReplyMarkup | undefined;
|
||||
if (!msg) return { content: [{ type: 'text', text: 'Message ' + messageId + ' not found in cache.' }], isError: true };
|
||||
|
||||
if (!msg.replyMarkup || !msg.replyMarkup.rows) {
|
||||
@@ -43,9 +44,9 @@ export async function listInlineButtons(
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
msg.replyMarkup.rows.forEach((row: any, ri: number) => {
|
||||
msg.replyMarkup.rows.forEach((row: ReplyMarkupRow, ri: number) => {
|
||||
if (row.buttons) {
|
||||
row.buttons.forEach((btn: any, bi: number) => {
|
||||
row.buttons.forEach((btn, bi: number) => {
|
||||
lines.push('Row ' + ri + ', Button ' + bi + ': "' + (btn.text ?? '?') + '"');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { ForumTopicsResult } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: 'list_topics',
|
||||
@@ -42,12 +43,12 @@ export async function listTopics(
|
||||
);
|
||||
});
|
||||
|
||||
const topics = (result as any)?.topics;
|
||||
const topics = (result as unknown as ForumTopicsResult)?.topics;
|
||||
if (!topics || !Array.isArray(topics) || topics.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No forum topics found.' }] };
|
||||
}
|
||||
|
||||
const lines = topics.map((t: any) => {
|
||||
const lines = topics.map((t) => {
|
||||
return 'ID: ' + t.id + ' | ' + (t.title ?? 'Untitled');
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { validateId } from '../../validation';
|
||||
import { getChatById } from '../telegramApi';
|
||||
import { mtprotoService } from '../../../../services/mtprotoService';
|
||||
import { Api } from 'telegram';
|
||||
import type { BotCallbackAnswer } from '../apiResultTypes';
|
||||
|
||||
export const tool: MCPTool = {
|
||||
name: "press_inline_button",
|
||||
@@ -51,7 +52,7 @@ export async function pressInlineButton(
|
||||
);
|
||||
});
|
||||
|
||||
const answer = (result as any)?.message ?? 'Button pressed (no response message).';
|
||||
const answer = (result as unknown as BotCallbackAnswer)?.message ?? 'Button pressed (no response message).';
|
||||
return { content: [{ type: 'text', text: answer }] };
|
||||
} catch (error) {
|
||||
return logAndFormatError(
|
||||
|
||||
@@ -4,6 +4,7 @@ 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',
|
||||
@@ -38,7 +39,7 @@ export async function searchContacts(
|
||||
return { content: [{ type: 'text', text: `No contacts found for "${query}".` }] };
|
||||
}
|
||||
|
||||
const lines = result.users.map((u: any) => {
|
||||
const lines = result.users.map((u: ApiUser) => {
|
||||
const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown';
|
||||
const username = u.username ? `@${u.username}` : '';
|
||||
return `ID: ${u.id} | ${name} ${username}`.trim();
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { TelegramMCPContext } from "../types";
|
||||
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",
|
||||
@@ -27,7 +28,7 @@ export async function setBotCommands(
|
||||
|
||||
const client = mtprotoService.getClient();
|
||||
|
||||
const botCommands = cmds.map((c: any) =>
|
||||
const botCommands = cmds.map((c: BotCommandInput) =>
|
||||
new Api.BotCommand({ command: String(c.command ?? ''), description: String(c.description ?? '') }),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user