162 lines
8.6 KiB
Python
162 lines
8.6 KiB
Python
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
|