Skip to content

Commit 6edccf5

Browse files
committed
Clear bot file
1 parent b499297 commit 6edccf5

File tree

4 files changed

+244
-234
lines changed

4 files changed

+244
-234
lines changed

src/bot.ts

Lines changed: 27 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -1,221 +1,11 @@
11
import { logger } from './logger';
22
import 'dotenv/config';
3-
import { Bot, Context } from 'grammy';
4-
import { Database } from './database';
5-
import CursorOfficialApi from './cursor-official-api';
3+
import { Context } from 'grammy';
64
import Agent from './agent';
7-
import { transcribeAudio, convertTelegramImageToCursorFormat } from './utils';
5+
import { transcribeAudio, convertTelegramImageToCursorFormat, safeSendMessage, safeReply, isUserAllowed } from './utils';
86
import { saveImagesToCache, getImagesFromCache } from './image-cache';
9-
10-
const bot = new Bot(process.env.BOT_TOKEN || '');
11-
const db = new Database();
12-
const apiKey = process.env.CURSOR_API_KEY || '';
13-
if (!apiKey) {
14-
throw new Error('No CURSOR_API_KEY configured for monitoring');
15-
}
16-
const cursorApi = new CursorOfficialApi({ apiKey });
17-
// Official API uses CURSOR_API_KEY; no cookie initialization required
18-
19-
// Safe message sending with retry and fallback
20-
async function safeSendMessage(
21-
chatId: number,
22-
text: string,
23-
options: any = {},
24-
maxRetries: number = 3
25-
): Promise<void> {
26-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
27-
try {
28-
await bot.api.sendMessage(chatId, text, options);
29-
return; // Success
30-
} catch (error: any) {
31-
logger.error(`Attempt ${attempt}/${maxRetries} failed:`, error);
32-
33-
// If it's a parsing error, try fallback formats
34-
if (error.description?.includes("can't parse entities")) {
35-
// First try HTML if we were using Markdown
36-
if (options.parse_mode === 'Markdown') {
37-
logger.info('Markdown parsing failed, trying HTML...');
38-
try {
39-
const htmlOptions = { ...options, parse_mode: 'HTML' };
40-
// Convert basic Markdown to HTML
41-
const htmlText = text
42-
.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>')
43-
.replace(/\*(.*?)\*/g, '<i>$1</i>')
44-
.replace(/`(.*?)`/g, '<code>$1</code>');
45-
await bot.api.sendMessage(chatId, htmlText, htmlOptions);
46-
return; // Success with HTML
47-
} catch (htmlError) {
48-
logger.error('HTML also failed:', htmlError);
49-
}
50-
}
51-
52-
// Finally try plain text
53-
logger.info('Trying plain text...');
54-
try {
55-
const plainOptions = { ...options };
56-
delete plainOptions.parse_mode;
57-
await bot.api.sendMessage(chatId, text, plainOptions);
58-
return; // Success with plain text
59-
} catch (plainError) {
60-
logger.error('Plain text also failed:', plainError);
61-
}
62-
}
63-
64-
// If this is the last attempt, throw the error
65-
if (attempt === maxRetries) {
66-
throw error;
67-
}
68-
69-
// Wait before retry (exponential backoff)
70-
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
71-
}
72-
}
73-
}
74-
75-
// Safe context reply with retry and fallback
76-
async function safeReply(
77-
ctx: Context,
78-
text: string,
79-
options: any = {},
80-
maxRetries: number = 3
81-
): Promise<void> {
82-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
83-
try {
84-
await ctx.reply(text, options);
85-
return; // Success
86-
} catch (error: any) {
87-
logger.error(`Reply attempt ${attempt}/${maxRetries} failed:`, error);
88-
89-
// If it's a parsing error, try fallback formats
90-
if (error.description?.includes("can't parse entities")) {
91-
// First try HTML if we were using Markdown
92-
if (options.parse_mode === 'Markdown') {
93-
logger.info('Markdown parsing failed, trying HTML reply...');
94-
try {
95-
const htmlOptions = { ...options, parse_mode: 'HTML' };
96-
// Convert basic Markdown to HTML
97-
const htmlText = text
98-
.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>')
99-
.replace(/\*(.*?)\*/g, '<i>$1</i>')
100-
.replace(/`(.*?)`/g, '<code>$1</code>');
101-
await ctx.reply(htmlText, htmlOptions);
102-
return; // Success with HTML
103-
} catch (htmlError) {
104-
logger.error('HTML reply also failed:', htmlError);
105-
}
106-
}
107-
108-
// Finally try plain text
109-
logger.info('Trying plain text reply...');
110-
try {
111-
const plainOptions = { ...options };
112-
delete plainOptions.parse_mode;
113-
await ctx.reply(text, plainOptions);
114-
return; // Success with plain text
115-
} catch (plainError) {
116-
logger.error('Plain text reply also failed:', plainError);
117-
}
118-
}
119-
120-
// If this is the last attempt, throw the error
121-
if (attempt === maxRetries) {
122-
throw error;
123-
}
124-
125-
// Wait before retry (exponential backoff)
126-
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
127-
}
128-
}
129-
}
130-
131-
// Access control via ENV
132-
function isUserAllowed(userId: number): boolean {
133-
const allowed = process.env.ALLOWED_USERS;
134-
if (!allowed) return true;
135-
return allowed.split(',').map(x => x.trim()).includes(userId.toString());
136-
}
137-
138-
// Background task monitoring
139-
async function monitorTasks() {
140-
const tasks = await db.getActiveTasks();
141-
142-
for (const task of tasks) {
143-
try {
144-
const agent = await cursorApi.getAgent(task.composer_id);
145-
const oldStatus = task.status;
146-
const newStatus = agent.status;
147-
148-
const keyboard = {
149-
inline_keyboard: [
150-
[
151-
{
152-
text: '🔍 Check Task Details',
153-
url: `https://cursor.com/agents?selectedBcId=${task.composer_id}`
154-
},
155-
{
156-
text: '📁 Open Repository',
157-
url: task.repo_url
158-
}
159-
]
160-
]
161-
};
162-
163-
if (oldStatus !== newStatus) {
164-
await db.updateTask(task.id, newStatus);
165-
166-
// Notify user if task completed or failed
167-
if (newStatus === 'FINISHED') {
168-
169-
170-
await safeSendMessage(
171-
task.chat_id,
172-
`✅ *Task completed!*\n\n*${task.task_description}*\n\nRepo: ${task.repo_url}\nComposer: \`${task.composer_id}\``,
173-
{
174-
parse_mode: 'Markdown',
175-
reply_markup: keyboard
176-
}
177-
);
178-
} else if (newStatus === 'ERROR') {
179-
await safeSendMessage(
180-
task.chat_id,
181-
`❌ *Task failed!*\n\n*${task.task_description}*\n\nRepo: ${task.repo_url}\nComposer: \`${task.composer_id}\``,
182-
{
183-
parse_mode: 'Markdown',
184-
reply_markup: keyboard
185-
}
186-
);
187-
} else if (newStatus === 'EXPIRED') {
188-
await safeSendMessage(
189-
task.chat_id,
190-
`⏱️ *Task expired!*\n\n*${task.task_description}*\n\nRepo: ${task.repo_url}\nComposer: \`${task.composer_id}\``,
191-
{
192-
parse_mode: 'Markdown',
193-
reply_markup: keyboard
194-
}
195-
);
196-
} else if (newStatus === 'RUNNING') {
197-
await safeSendMessage(
198-
task.chat_id,
199-
`🔄 *Task is now running*\n\n*${task.task_description}*\n\nRepo: ${task.repo_url}\nComposer: \`${task.composer_id}\``,
200-
{
201-
parse_mode: 'Markdown'
202-
}
203-
);
204-
} else {
205-
await safeSendMessage(
206-
task.chat_id,
207-
`🔄 *Task status updated to ${newStatus}*\n\n*${task.task_description}*\n\nRepo: ${task.repo_url}\nComposer: \`${task.composer_id}\``,
208-
{
209-
parse_mode: 'Markdown'
210-
}
211-
);
212-
}
213-
}
214-
} catch (error) {
215-
logger.error(`Error monitoring task ${task.id}:`, error);
216-
}
217-
}
218-
}
7+
import { bot, db, cursorApi } from './env';
8+
import { monitorTasks } from './monitor';
2199

22010
// Common message processing function
22111
async function processUserMessage(
@@ -283,8 +73,6 @@ let monitorInterval: NodeJS.Timeout | null = null;
28373

28474
// Bot handlers
28575
bot.use(async (ctx, next) => {
286-
logger.info('ctx.from', ctx.from);
287-
logger.info('ctx.chat', ctx.chat);
28876
if (!ctx.from || !ctx.chat) return;
28977

29078
// Middleware to check if user is allowed
@@ -294,6 +82,20 @@ bot.use(async (ctx, next) => {
29482
return;
29583
}
29684

85+
const mentionOnlyMode = process.env.MENTION_ONLY_MODE === 'true';
86+
if (mentionOnlyMode) {
87+
const botInfo = await ctx.api.getMe();
88+
const botMention = `@${botInfo.username}`;
89+
const isDirectMessage = ctx.chat?.type === 'private';
90+
const isMentioned = ctx.message?.text?.includes(botMention);
91+
92+
// Only respond if it's a direct message or bot is mentioned
93+
if (!isDirectMessage && !isMentioned) {
94+
logger.info('Skipping message - mention-only mode enabled and bot not mentioned');
95+
return;
96+
}
97+
}
98+
29799
// Save user and chat info (no allowed field)
298100
await db.createUser({
299101
id: ctx.from.id,
@@ -347,22 +149,28 @@ bot.command('tasks', async (ctx) => {
347149
});
348150

349151
bot.command('clear', async (ctx) => {
350-
351152
try {
352153
await db.clearHistory(ctx.from!.id, ctx.chat!.id);
353-
await safeReply(ctx, 'History cleared successfully');
154+
await safeReply(ctx, 'Chat history cleared successfully');
354155
} catch (error) {
355156
logger.error('Error clearing history:', error);
356-
await safeReply(ctx, 'Failed to clear history');
157+
await safeReply(ctx, 'Failed to clear chat history');
357158
}
358159
});
359160

161+
bot.command('models', async (ctx) => {
162+
const models = await cursorApi.listModels();
163+
await safeReply(ctx, `*Available Models for Cursor Agent:*\n\n${models.models.join('\n')}`);
164+
});
165+
360166
bot.command('help', async (ctx) => {
361167
await safeReply(ctx, `🤖 *Cursor AI Task Bot Help*
362168
363169
*Available Commands:*
364170
/start - Start the bot
365171
/tasks - Show your active tasks
172+
/clear - Clear chat history
173+
/models - Show available models
366174
/help - Show this help
367175
368176
*Usage Examples:*
@@ -383,21 +191,6 @@ bot.on('message:text', async (ctx) => {
383191

384192
const message = ctx.message.text;
385193

386-
// Check if mention-only mode is enabled
387-
const mentionOnlyMode = process.env.MENTION_ONLY_MODE === 'true';
388-
if (mentionOnlyMode) {
389-
const botInfo = await ctx.api.getMe();
390-
const botMention = `@${botInfo.username}`;
391-
const isDirectMessage = ctx.chat?.type === 'private';
392-
const isMentioned = message.includes(botMention);
393-
394-
// Only respond if it's a direct message or bot is mentioned
395-
if (!isDirectMessage && !isMentioned) {
396-
logger.info('Skipping message - mention-only mode enabled and bot not mentioned');
397-
return;
398-
}
399-
}
400-
401194
try {
402195
await processUserMessage(ctx, message);
403196
} catch (error) {

src/env.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Bot } from "grammy";
2+
import { Database } from "./database";
3+
import CursorOfficialApi from "./cursor-official-api";
4+
5+
export const bot = new Bot(process.env.BOT_TOKEN || '');
6+
export const db = new Database();
7+
const apiKey = process.env.CURSOR_API_KEY || '';
8+
if (!apiKey) {
9+
throw new Error('No CURSOR_API_KEY configured for monitoring');
10+
}
11+
export const cursorApi = new CursorOfficialApi({ apiKey });

0 commit comments

Comments
 (0)