#!/usr/bin/env python3 """Обработчики команд бота (/start, /menu, /help, /settings, /cron).""" import logging from telegram import Update from telegram.ext import ContextTypes # Импорты из модулей bot/ from bot.config import config, state_manager, server_manager, menu_builder from bot.utils.decorators import check_access from bot.tools import tools_registry from bot.ai_agent import ai_agent logger = logging.getLogger(__name__) @check_access async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработка команды /start.""" user = update.effective_user logger.info(f"Пользователь {user.username} ({user.id}) запустил бота") state_manager.reset(user.id) # Показать текущую директорию и сервер working_dir = config.working_directory server = server_manager.get("local") server_desc = server.description if server else "localhost" await update.message.reply_text( f"👋 Привет, {user.first_name}!\n\n" f"{config.icon} *{config.name}*\n" f"_{config.description}_\n\n" f"*Просто отправьте CLI команду в чат* — я её выполню!\n\n" f"🖥️ *Текущий сервер:* `{server_desc}`\n" f"📁 *Рабочая директория:* `{working_dir}`\n\n" f"Используйте `cd путь` для смены директории.\n" f"Или выберите сервер в меню.\n" f"Команда /help покажет справку.", parse_mode="Markdown", reply_markup=menu_builder.get_keyboard("main", user_id=update.effective_user.id) ) @check_access async def menu_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработка команды /menu - показывает главное меню.""" user = update.effective_user state = state_manager.get(user.id) # Не сбрасываем состояние - сохраняем ai_chat_mode и другие настройки state.current_menu = "main" # Показать текущую директорию и сервер working_dir = state.working_directory or config.working_directory server = server_manager.get(state.current_server) server_desc = server.description if server else state.current_server await update.message.reply_text( f"🏠 *Главное меню*\n\n" f"🖥️ *Сервер:* `{server_desc}`\n" f"📁 *Директория:* `{working_dir}`\n\n" f"Выберите действие:", parse_mode="Markdown", reply_markup=menu_builder.get_keyboard("main", user_id=update.effective_user.id, state=state) ) @check_access async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработка команды /help.""" help_text = f""" 📖 *Справка по боту {config.name}* *Как использовать:* Просто отправьте любую CLI команду в чат — бот выполнит её! *Примеры:* • `ls -la` — список файлов • `pwd` — текущая директория • `df -h` — свободное место на диске • `git status` — статус git *Навигация по директориям:* • `cd путь` — сменить директорию (например, `cd git/project`) • `cd ..` — на уровень вверх • `cd ~` — в домашнюю директорию • `pwd` — показать текущую директорию *Кнопки меню:* • 📋 Предустановленные команды — быстрые команды по категориям • ⚙️ Настройки бота — изменение имени, описания, иконки • ℹ️ О боте — информация *Команды управления:* /start — Запустить бота, главное меню /menu — Показать главное меню с кнопками /help — Эта справка /settings — Настройки *Безопасность:* Команды выполняются от вашего имени. Будьте осторожны с деструктивными командами! """ await update.message.reply_text(help_text, parse_mode="Markdown") @check_access async def settings_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработка команды /settings.""" state = state_manager.get(update.effective_user.id) state.current_menu = "settings" await update.message.reply_text( "⚙️ *Настройки бота*", parse_mode="Markdown", reply_markup=menu_builder.get_keyboard("settings") ) @check_access async def cron_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """ Обработка команды /cron - управление задачами. Использование: /cron list - показать все задачи /cron add - добавить задачу /cron run - выполнить задачу немедленно /cron remove - удалить задачу /cron toggle - включить/выключить задачу """ user_id = update.effective_user.id args = context.args # Получаем cron инструмент cron_tool = tools_registry.get('cron_tool') if not cron_tool: await update.message.reply_text("❌ Ошибка: cron инструмент не найден") return # Парсим команду if not args: # По умолчанию показываем список задач action = 'list' else: action = args[0].lower() try: if action == 'list': # Показать все задачи пользователя result = await cron_tool.execute(action='list', user_id=user_id) if result.success and result.data: output = "⏰ **Ваши задачи:**\n\n" for job in result.data: status = "✅" if job.get('enabled') else "❌" notify_icon = "🔔" if job.get('notify') else "🔕" log_icon = "📝" if job.get('log_results') else "🚫" output += f"{status} **{job.get('name', 'Без названия')}** (ID: {job.get('id')})\n" output += f" {notify_icon}{log_icon} Промпт: _{job.get('prompt', '')[:100]}_{'...' if len(job.get('prompt', '')) > 100 else ''}\n" output += f" Расписание: `{job.get('schedule', '')}`\n" if job.get('next_run'): output += f" Следующий запуск: {job.get('next_run')}\n" if job.get('last_run'): output += f" Последний запуск: {job.get('last_run')}\n" output += "\n" if not output.strip(): output = "📭 У вас пока нет задач.\n\nДобавьте задачу командой:\n`/cron add `" await update.message.reply_text(output, parse_mode="Markdown") else: await update.message.reply_text("📭 У вас пока нет задач.") elif action == 'add': if len(args) < 4: await update.message.reply_text( "❌ **Недостаточно аргументов**\n\n" "**Использование:**\n" "`/cron add [notify] [log]`\n\n" "**Примеры:**\n" "`/cron check_disk Ежедневно проверять диск на сервере`\n" "`/cron news hourly Что нового в Linux сегодня`\n\n" "**Расписание:**\n" "• `@hourly` - каждый час\n" "• `@daily` - каждый день\n" "• `@weekly` - каждую неделю\n" "• `*/5 * * * *` - каждые 5 минут", parse_mode="Markdown" ) return name = args[1] schedule = args[2] # Промпт может содержать пробелы - берём всё после schedule prompt = ' '.join(args[3:]) # Парсим опциональные параметры notify = 'notify' in prompt.lower() log_results = 'no_log' not in prompt.lower() and 'без_лога' not in prompt.lower() result = await cron_tool.execute( action='add', name=name, prompt=prompt, schedule=schedule, user_id=user_id, notify=notify, log_results=log_results ) if result.success: notify_status = "🔔 Уведомлять" if notify else "🔕 Без уведомлений" log_status = "📝 Логировать" if log_results else "🚫 Без логов" await update.message.reply_text( f"✅ **Задача добавлена:**\n" f"• ID: {result.data.get('id')}\n" f"• Название: {name}\n" f"• Промпт: _{prompt}_\n" f"• Расписание: `{schedule}`\n" f"• {notify_status}, {log_status}\n" f"• Следующий запуск: {result.data.get('next_run', 'N/A')}", parse_mode="Markdown" ) else: await update.message.reply_text(f"❌ Ошибка: {result.error}") elif action == 'run': if len(args) < 2: await update.message.reply_text("❌ Укажите ID задачи: `/cron run `") return try: job_id = int(args[1]) except ValueError: await update.message.reply_text("❌ ID должен быть числом") return status_msg = await update.message.reply_text("⏳ Выполняю задачу...") # Выполняем задачу через AI-агент result = await cron_tool.execute( action='run', job_id=job_id, ai_agent=ai_agent, user_id=user_id ) await status_msg.delete() if result.success: result_text = result.metadata.get('result_text', 'Задача выполнена') tool_used = result.data.get('tool_used', 'не указан') await update.message.reply_text( f"✅ **Задача выполнена:**\n\n{result_text}\n\n🔧 Инструмент: {tool_used}", parse_mode="Markdown" ) else: await update.message.reply_text(f"❌ Ошибка: {result.error}") elif action == 'remove': if len(args) < 2: await update.message.reply_text("❌ Укажите ID задачи: `/cron remove `") return try: job_id = int(args[1]) except ValueError: await update.message.reply_text("❌ ID должен быть числом") return result = await cron_tool.execute(action='remove', job_id=job_id) if result.success: await update.message.reply_text(f"✅ Задача удалена: ID {job_id}") else: await update.message.reply_text(f"❌ Ошибка: {result.error}") elif action == 'toggle': if len(args) < 2: await update.message.reply_text("❌ Укажите ID задачи: `/cron toggle `") return try: job_id = int(args[1]) except ValueError: await update.message.reply_text("❌ ID должен быть числом") return # Получаем текущее состояние задачи list_result = await cron_tool.execute(action='list', user_id=user_id) current_state = True for job in list_result.data: if job['id'] == job_id: current_state = job.get('enabled', True) break new_state = not current_state result = await cron_tool.execute(action='toggle', job_id=job_id, enabled=new_state) if result.success: state_text = "включена" if new_state else "выключена" await update.message.reply_text(f"✅ Задача ID {job_id} {state_text}") else: await update.message.reply_text(f"❌ Ошибка: {result.error}") else: await update.message.reply_text( "❌ Неизвестная команда.\n\n" "**Доступные команды:**\n" "• `/cron list` - показать все задачи\n" "• `/cron add ` - добавить задачу\n" "• `/cron run ` - выполнить задачу\n" "• `/cron remove ` - удалить задачу\n" "• `/cron toggle ` - включить/выключить задачу", parse_mode="Markdown" ) except Exception as e: logger.exception(f"Ошибка в команде /cron: {e}") await update.message.reply_text(f"❌ Ошибка: {e}")