import logging from datetime import datetime from sqlalchemy import select, update from database.db import AsyncSessionLocal from database.models import WeeklyDigestInbox import google.generativeai as genai import os import config logger = logging.getLogger(__name__) class PersonalInboxService: @staticmethod async def add_to_inbox(user_id: int, text: str, url: str = None, media_type: str = 'text') -> bool: """Добавить запись в личный инбокс""" try: async with AsyncSessionLocal() as session: item = WeeklyDigestInbox( user_id=user_id, message_text=text, url=url, media_type=media_type, created_at=datetime.utcnow(), status='pending' ) session.add(item) await session.commit() logger.info(f"Добавлена запись в инбокс для пользователя {user_id}: {text[:30]}... [URL: {url}]") return True except Exception as e: logger.error(f"Ошибка при добавлении в инбокс: {e}") return False @staticmethod async def get_pending_items(user_id: int): """Получить все необработанные записи пользователя""" try: async with AsyncSessionLocal() as session: stmt = select(WeeklyDigestInbox).where( WeeklyDigestInbox.user_id == user_id, WeeklyDigestInbox.status == 'pending' ).order_by(WeeklyDigestInbox.created_at.asc()) result = await session.execute(stmt) return result.scalars().all() except Exception as e: logger.error(f"Ошибка при получении записей из инбокса: {e}") return [] @staticmethod async def clear_pending_items(user_id: int) -> bool: """Очистить (пометить отправленными) все записи пользователя""" try: async with AsyncSessionLocal() as session: stmt = update(WeeklyDigestInbox).where( WeeklyDigestInbox.user_id == user_id, WeeklyDigestInbox.status == 'pending' ).values(status='sent') await session.execute(stmt) await session.commit() return True except Exception as e: logger.error(f"Ошибка при очистке инбокса: {e}") return False @staticmethod async def generate_weekly_digest(items) -> str: """Сгенерировать сводный дайджест с помощью Gemini API""" api_key = os.environ.get("GOOGLE_API_KEY") if not api_key: logger.warning("GOOGLE_API_KEY не задан. Генерация дайджеста через ИИ невозможна.") return PersonalInboxService._format_fallback_digest(items) try: genai.configure(api_key=api_key) model = genai.GenerativeModel('gemini-1.5-flash') except Exception as e: logger.error(f"Ошибка инициализации Gemini: {e}") return PersonalInboxService._format_fallback_digest(items) # Подготовка списка для промпта items_list = [] for idx, item in enumerate(items, 1): date_str = item.created_at.strftime('%d.%m.%Y %H:%M') content = f"Запись #{idx} [Дата: {date_str}, Тип: {item.media_type}]:\n" if item.message_text: content += f"Текст/Описание: {item.message_text}\n" if item.url: content += f"Ссылка: {item.url}\n" items_list.append(content) items_text = "\n---\n".join(items_list) prompt = f""" Ты - умный персональный ИИ-ассистент Александра. Твоя задача - составить еженедельную субботнюю сводку (дайджест) по материалам, которые он прислал тебе за неделю. Список присланных материалов: {items_text} ПРАВИЛА ОФОРМЛЕНИЯ СВОДКИ (ОЧЕНЬ ВАЖНО): 1. Отвечай строго на РУССКОМ языке. 2. Используй красивую HTML-разметку, подходящую для Telegram (используй теги , , , ). Не используй markdown-символы типа ** или *. 3. Раздели материалы по логическим категориям, используя эмодзи. Подходящие категории: - 📚 Выучить / Изучить! (для обучающих материалов, статей, документации, гайдов) - 👀 Проверить / Тестировать! (для технических инструментов, репозиториев github, настроек серверов, команд) - 📤 Отправить / Переслать! (если Александр прямо написал переслать кому-то, или это интересная новость для других) - 📝 Заметки и Идеи (для текстовых заметок без ссылок, мыслей, набросков) 4. Для каждого пункта сделай ОЧЕНЬ краткую выжимку (буквально одно предложение) и укажи дату добавления. 5. Если есть ссылка (URL), ОБЯЗАТЕЛЬНО сделай её кликабельной в формате Название/Источник. 6. В конце добавь краткое бодрое пожелание отличных выходных. """ try: response = model.generate_content(prompt) return response.text.strip() except Exception as e: logger.error(f"Ошибка генерации дайджеста в Gemini: {e}") return PersonalInboxService._format_fallback_digest(items) @staticmethod def _format_fallback_digest(items) -> str: """Форматирование резервного дайджеста, если Gemini недоступен""" lines = ["📅 ЕЖЕНЕДЕЛЬНАЯ СВОДКА (Резервная копия)\n"] for idx, item in enumerate(items, 1): date_str = item.created_at.strftime('%d.%m.%Y') line = f"{idx}. " if item.url: line += f'Ссылка' else: line += "Заметка" line += f" ({date_str}):\n" if item.message_text: line += f"{item.message_text[:100]}" lines.append(line) lines.append("\n💡 Модуль ИИ временно недоступен. Выше представлен простой список ваших ссылок. Приятных выходных!") return "\n".join(lines) @staticmethod async def send_weekly_digest(bot) -> bool: """Собрать и отправить еженедельный дайджест администратору""" user_id = config.ADMIN_USER_ID items = await PersonalInboxService.get_pending_items(user_id) if not items: logger.info("Нет записей в инбоксе для формирования дайджеста.") return False logger.info(f"Формирование еженедельного дайджеста из {len(items)} записей для {user_id}...") digest_text = await PersonalInboxService.generate_weekly_digest(items) try: await bot.send_message( chat_id=user_id, text=digest_text, parse_mode='HTML', disable_web_page_preview=True ) # Помечаем отправленными await PersonalInboxService.clear_pending_items(user_id) logger.info("Еженедельный дайджест успешно отправлен и записи очищены.") return True except Exception as e: logger.error(f"Ошибка при отправке дайджеста: {e}") return False