323 lines
14 KiB
Python
323 lines
14 KiB
Python
#!/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 <name> <schedule> <prompt> - добавить задачу
|
||
/cron run <id> - выполнить задачу немедленно
|
||
/cron remove <id> - удалить задачу
|
||
/cron toggle <id> - включить/выключить задачу
|
||
"""
|
||
user_id = update.effective_user.id
|
||
args = context.args
|
||
|
||
# Получаем cron инструмент
|
||
cron_tool = tools_registry.get_tool('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 <name> <schedule> <prompt>`"
|
||
|
||
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 <name> <schedule> <prompt> [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 <id>`")
|
||
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 <id>`")
|
||
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 <id>`")
|
||
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 <name> <schedule> <prompt>` - добавить задачу\n"
|
||
"• `/cron run <id>` - выполнить задачу\n"
|
||
"• `/cron remove <id>` - удалить задачу\n"
|
||
"• `/cron toggle <id>` - включить/выключить задачу",
|
||
parse_mode="Markdown"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.exception(f"Ошибка в команде /cron: {e}")
|
||
await update.message.reply_text(f"❌ Ошибка: {e}")
|