diff --git a/src/bot/main.py b/src/bot/main.py index 183a531..6d71f48 100644 --- a/src/bot/main.py +++ b/src/bot/main.py @@ -9,6 +9,7 @@ from telegram import InlineKeyboardButton, InlineKeyboardMarkup from config.config import get_settings from src.tools.orchestrator import Orchestrator from src.bot.states import chat_state, ChatMode +from src.scheduler.scheduler import SchedulerManager logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", @@ -18,6 +19,7 @@ logger = logging.getLogger(__name__) settings = get_settings() orchestrator = Orchestrator() +scheduler_manager = None async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): @@ -41,7 +43,8 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): "/cancel - Отменить текущее действие\n" "/qwen <текст> - Задать вопрос qwen-code\n" "/open <текст> - Задать вопрос opencode\n" - "/forget - Очистить историю чата\n\n" + "/forget - Очистить историю чата\n" + "/remind <текст> <время> - Создать напоминание\n\n" f"Текущий режим: {'с подтверждением' if mode == ChatMode.CONFIRM else 'автономный'}\n" f"Инструмент по умолчанию: {current_tool}" ) @@ -203,6 +206,48 @@ async def forget_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("История чата очищена.") +async def remind_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + if not context.args: + await update.message.reply_text( + "Использование: /remind <текст> <время>\n" + "Пример: /remind Позвонить врачу через 1 час" + ) + return + + text = " ".join(context.args) + chat_id = update.effective_chat.id + + from datetime import datetime, timedelta + + time_keywords = { + "минут": 1, + "минуту": 1, + "час": 60, + "часа": 60, + "часов": 60, + "день": 1440, + "дня": 1440, + "дней": 1440 + } + + minutes = 60 + for keyword, value in time_keywords.items(): + if keyword in text.lower(): + import re + match = re.search(r'(\d+)\s*' + keyword, text.lower()) + if match: + minutes = int(match.group(1)) * value + break + + from datetime import datetime + run_at = datetime.now() + timedelta(minutes=minutes) + + scheduler_manager.add_reminder(chat_id, text, run_at) + await update.message.reply_text( + f"Напоминание установлено на {run_at.strftime('%H:%M %d.%m.%Y')}" + ) + + async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): prompt = update.message.text chat_id = update.effective_chat.id @@ -234,6 +279,8 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): def main(): + global scheduler_manager + builder = Application.builder() builder.token(settings.telegram_bot_token) @@ -243,6 +290,9 @@ def main(): application = builder.build() + scheduler_manager = SchedulerManager(application.bot, orchestrator) + scheduler_manager.start() + application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("help", help_command)) application.add_handler(CommandHandler("mode", mode_command)) @@ -251,6 +301,7 @@ def main(): application.add_handler(CommandHandler("qwen", qwen_command)) application.add_handler(CommandHandler("open", open_command)) application.add_handler(CommandHandler("forget", forget_command)) + application.add_handler(CommandHandler("remind", remind_command)) application.add_handler(CallbackQueryHandler(confirm_callback)) application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) diff --git a/src/scheduler/__init__.py b/src/scheduler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/scheduler/scheduler.py b/src/scheduler/scheduler.py new file mode 100644 index 0000000..9431b98 --- /dev/null +++ b/src/scheduler/scheduler.py @@ -0,0 +1,98 @@ +import logging +from typing import Dict, List +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler.triggers.interval import IntervalTrigger +from apscheduler.triggers.date import DateTrigger +from datetime import datetime, timedelta +from config.config import get_settings + +logger = logging.getLogger(__name__) +settings = get_settings() + + +class SchedulerManager: + def __init__(self, bot, orchestrator): + self.bot = bot + self.orchestrator = orchestrator + self.scheduler = AsyncIOScheduler() + self.reminders: Dict[int, List[dict]] = {} + + async def generate_idea(self): + logger.info("Запуск генерации идей") + + for chat_id in self.orchestrator.memory.collection.get().get("metadatas", []): + try: + chat_id_int = int(chat_id.get("chat_id", 0)) + if not chat_id_int: + continue + + context = self.orchestrator.memory.get_context_for_prompt(chat_id_int) + if not context: + continue + + idea_prompt = f"""На основе нашего разговора, придумай полезную идею, совет или вопрос для пользователя. + +История разговора: +{context} + +Ответь кратко (1-2 предложения), без лишних объяснений.""" + + idea, success = await self.orchestrator.ask(idea_prompt, chat_id_int) + + if success: + await self.bot.send_message( + chat_id=chat_id_int, + text=f"💡 Идея для вас:\n\n{idea}" + ) + except Exception as e: + logger.error(f"Ошибка генерации идеи для чата {chat_id}: {e}") + + def start(self): + if settings.scheduler_enabled: + self.scheduler.add_job( + self.generate_idea, + trigger=IntervalTrigger(hours=settings.idea_interval_hours), + id="idea_generator", + name="Генерация идей", + replace_existing=True + ) + self.scheduler.start() + logger.info("Планировщик запущен") + + def stop(self): + self.scheduler.shutdown() + logger.info("Планировщик остановлен") + + def add_reminder(self, chat_id: int, text: str, run_at: datetime): + if chat_id not in self.reminders: + self.reminders[chat_id] = [] + + job = self.scheduler.add_job( + self._send_reminder, + trigger=DateTrigger(run_date=run_at), + args=[chat_id, text], + id=f"reminder_{chat_id}_{len(self.reminders[chat_id])}" + ) + + self.reminders[chat_id].append({ + "job_id": job.id, + "text": text, + "run_at": run_at + }) + + logger.info(f"Напоминание добавлено для чата {chat_id}: {text} в {run_at}") + + async def _send_reminder(self, chat_id: int, text: str): + try: + await self.bot.send_message(chat_id=chat_id, text=f"🔔 Напоминание:\n\n{text}") + + if chat_id in self.reminders: + self.reminders[chat_id] = [ + r for r in self.reminders[chat_id] + if r["text"] != text + ] + except Exception as e: + logger.error(f"Ошибка отправки напоминания: {e}") + + def get_reminders(self, chat_id: int) -> List[dict]: + return self.reminders.get(chat_id, [])