domovoy_bot/services/personal_inbox_service.py

162 lines
8.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 (используй теги <b>, <i>, <code>, <a>). Не используй markdown-символы типа ** или *.
3. Раздели материалы по логическим категориям, используя эмодзи. Подходящие категории:
- 📚 <b>Выучить / Изучить!</b> (для обучающих материалов, статей, документации, гайдов)
- 👀 <b>Проверить / Тестировать!</b> (для технических инструментов, репозиториев github, настроек серверов, команд)
- 📤 <b>Отправить / Переслать!</b> (если Александр прямо написал переслать кому-то, или это интересная новость для других)
- 📝 <b>Заметки и Идеи</b> (для текстовых заметок без ссылок, мыслей, набросков)
4. Для каждого пункта сделай ОЧЕНЬ краткую выжимку (буквально одно предложение) и укажи дату добавления.
5. Если есть ссылка (URL), ОБЯЗАТЕЛЬНО сделай её кликабельной в формате <a href="URL">Название/Источник</a>.
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 = ["📅 <b>ЕЖЕНЕДЕЛЬНАЯ СВОДКА (Резервная копия)</b>\n"]
for idx, item in enumerate(items, 1):
date_str = item.created_at.strftime('%d.%m.%Y')
line = f"<b>{idx}.</b> "
if item.url:
line += f'<a href="{item.url}">Ссылка</a>'
else:
line += "Заметка"
line += f" ({date_str}):\n"
if item.message_text:
line += f"<i>{item.message_text[:100]}</i>"
lines.append(line)
lines.append("\n💡 <i>Модуль ИИ временно недоступен. Выше представлен простой список ваших ссылок. Приятных выходных!</i>")
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