"""
Обработчики для еженедельного дайджеста
"""
import logging
from datetime import datetime
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.fsm.context import FSMContext
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from config import ADMIN_USER_ID
from database.db import AsyncSessionLocal
from database.models import Digest
from services.digest_service import DigestService
logger = logging.getLogger(__name__)
router = Router()
# ============================================================================
# КОМАНДЫ ДЛЯ АДМИНА
# ============================================================================
@router.message(F.text == '📰 Сгенерировать дайджест')
@router.message(F.text.startswith('/digest'))
async def cmd_digest(message: Message):
"""Сгенерировать дайджест за текущую неделю"""
if message.from_user.id != ADMIN_USER_ID:
await message.answer("❌ Эта команда только для администратора")
return
await message.answer("⏳ Генерация дайджеста...")
try:
async with AsyncSessionLocal() as session:
service = DigestService(session)
# Генерируем
digest_data = await service.generate_weekly_digest()
if not digest_data:
await message.answer("❌ Не удалось сгенерировать дайджест")
return
# Сохраняем со статусом pending_approval
digest_id = await service.save_digest(digest_data, status='pending_approval')
# Отправляем превью админу
preview_text = digest_data['text']
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="✅ Утвердить и отправить", callback_data=f"digest_approve:{digest_id}")],
[InlineKeyboardButton(text="❌ Отклонить", callback_data=f"digest_reject:{digest_id}")],
[InlineKeyboardButton(text="✏️ Редактировать", callback_data=f"digest_edit:{digest_id}")],
[InlineKeyboardButton(text="🌐 Открыть в веб-панели", url="http://localhost:8000/digests")],
])
await message.answer(
f"📰 Дайджест #{digest_data['week_number']}/{digest_data['year']}\n"
f"🗓 Период: {digest_data['period_start'].strftime('%d.%m')} - {digest_data['period_end'].strftime('%d.%m.%Y')}\n\n"
f"{preview_text}\n\n"
f"⏳ Ожидает вашего утверждения",
parse_mode='HTML',
reply_markup=keyboard
)
except Exception as e:
logger.error(f"Ошибка генерации дайджеста: {e}")
await message.answer(f"❌ Ошибка: {e}")
@router.callback_query(F.data.startswith('digest_approve:'))
async def approve_digest(callback: CallbackQuery):
"""Утвердить и отправить дайджест"""
if callback.from_user.id != ADMIN_USER_ID:
return
digest_id = int(callback.data.split(':')[1])
await callback.message.edit_text(f"✅ Дайджест утверждён! Отправляем...")
try:
async with AsyncSessionLocal() as session:
service = DigestService(session)
# Обновляем статус
await service.update_digest_status(
digest_id,
'approved',
approved_by=callback.from_user.id
)
# Получаем дайджест
digest = await session.get(Digest, digest_id)
if not digest:
await callback.message.edit_text("❌ Дайджест не найден")
return
# Отправляем всем верифицированным
from database.models import User
stmt = select(User).where(User.verified == True)
result = await session.execute(stmt)
users = list(result.scalars().all())
sent_count = 0
error_count = 0
message_id = None
# Сначала в чат
try:
chat_msg = await callback.bot.send_message(
callback.message.chat.id if hasattr(callback.message, 'chat') else -1002608277768,
f"📰 Еженедельный дайджест\n\n{digest.digest_text}",
parse_mode='HTML'
)
message_id = chat_msg.message_id
except Exception as e:
logger.error(f"Ошибка отправки в чат: {e}")
# Потом пользователям
for user in users:
try:
await callback.bot.send_message(
user.user_id,
f"📰 Еженедельный дайджест\n\n{digest.digest_text}",
parse_mode='HTML'
)
sent_count += 1
except Exception as e:
logger.error(f"Ошибка отправки пользователю {user.user_id}: {e}")
error_count += 1
# Обновляем статус на sent
await service.update_digest_status(
digest_id,
'sent',
message_id=message_id
)
# Уведомляем админа
await callback.message.edit_text(
f"✅ Дайджест отправлен!\n\n"
f"ID: #{digest_id}\n"
f"Получателей: {sent_count}\n"
f"Ошибок: {error_count}\n"
f"Время: {datetime.utcnow().strftime('%H:%M:%S')}",
parse_mode='HTML'
)
except Exception as e:
logger.error(f"Ошибка отправки дайджеста: {e}")
await callback.message.edit_text(f"❌ Ошибка: {e}")
@router.callback_query(F.data.startswith('digest_reject:'))
async def reject_digest(callback: CallbackQuery, state: FSMContext):
"""Отклонить дайджест"""
if callback.from_user.id != ADMIN_USER_ID:
return
digest_id = int(callback.data.split(':')[1])
await state.update_data(reject_digest_id=digest_id)
await callback.message.edit_text(
"✏️ Введите причину отклонения (или /cancel для отмены):"
)
from aiogram.fsm.state import StatesGroup, State
class RejectState(StatesGroup):
waiting_reason = State()
await state.set_state(RejectState.waiting_reason)
@router.message(F.text == '/cancel')
async def cancel_action(message: Message, state: FSMContext):
"""Отменить действие"""
await message.answer("❌ Действие отменено")
await state.clear()
@router.message(F.state == 'RejectState:waiting_reason')
async def process_reject_reason(message: Message, state: FSMContext):
"""Обработка причины отклонения"""
from aiogram.fsm.state import StatesGroup, State
class RejectState(StatesGroup):
waiting_reason = State()
reason = message.text
data = await state.get_data()
digest_id = data.get('reject_digest_id')
try:
async with AsyncSessionLocal() as session:
service = DigestService(session)
await service.update_digest_status(
digest_id,
'rejected',
rejected_by=message.from_user.id,
rejection_reason=reason
)
await message.answer(f"❌ Дайджест #{digest_id} отклонён\nПричина: {reason}")
except Exception as e:
await message.answer(f"❌ Ошибка: {e}")
await state.clear()
@router.callback_query(F.data.startswith('digest_edit:'))
async def edit_digest(callback: CallbackQuery, state: FSMContext):
"""Редактировать текст дайджеста"""
if callback.from_user.id != ADMIN_USER_ID:
return
digest_id = int(callback.data.split(':')[1])
await state.update_data(edit_digest_id=digest_id)
await callback.message.edit_text(
"✏️ Отредактируйте текст дайджеста и отправьте его сообщением.\n\n"
"Или нажмите /cancel для отмены."
)
from aiogram.fsm.state import StatesGroup, State
class EditDigestState(StatesGroup):
waiting_text = State()
await state.set_state(EditDigestState.waiting_text)
@router.message(F.state == 'EditDigestState:waiting_text')
async def process_edit_text(message: Message, state: FSMContext):
"""Обработка отредактированного текста"""
from aiogram.fsm.state import StatesGroup, State
class EditDigestState(StatesGroup):
waiting_text = State()
new_text = message.text
data = await state.get_data()
digest_id = data.get('edit_digest_id')
try:
async with AsyncSessionLocal() as session:
service = DigestService(session)
await service.update_digest_text(digest_id, new_text)
await message.answer(f"✅ Текст дайджеста #{digest_id} обновлён!\n\nТеперь можете утвердить его в веб-панели.")
except Exception as e:
await message.answer(f"❌ Ошибка: {e}")
await state.clear()
# ============================================================================
# АВТОМАТИЧЕСКАЯ ГЕНЕРАЦИЯ (из scheduler)
# ============================================================================
async def auto_generate_digest(bot):
"""
Автоматическая генерация дайджеста (вызывается из scheduler)
"""
try:
async with AsyncSessionLocal() as session:
service = DigestService(session)
# Генерируем
digest_data = await service.generate_weekly_digest()
if not digest_data:
logger.warning("⚠️ Не удалось сгенерировать дайджест")
return
# Проверяем, нет ли уже дайджеста за эту неделю
check_stmt = select(Digest).where(
Digest.week_number == digest_data['week_number'],
Digest.year == digest_data['year']
)
result = await session.execute(check_stmt)
existing = result.scalar_one_or_none()
if existing:
logger.info(f"📰 Дайджест за неделю {digest_data['week_number']}/{digest_data['year']} уже существует")
return
# Сохраняем
digest_id = await service.save_digest(digest_data, status='pending_approval')
# Уведомляем админа
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🌐 Утвердить в веб-панели", url="http://localhost:8000/digests")],
])
await bot.send_message(
ADMIN_USER_ID,
f"📰 Дайджест за неделю готов!\n\n"
f"Неделя #{digest_data['week_number']}/{digest_data['year']}\n"
f"Период: {digest_data['period_start'].strftime('%d.%m')} - {digest_data['period_end'].strftime('%d.%m.%Y')}\n\n"
f"Зайдите в веб-панель для утверждения:\n"
f"http://localhost:8000/digests",
parse_mode='HTML',
reply_markup=keyboard
)
logger.info(f"✅ Дайджест #{digest_id} сгенерирован и ожидает утверждения")
except Exception as e:
logger.error(f"Ошибка авто-генерации дайджеста: {e}")
await bot.send_message(
ADMIN_USER_ID,
f"❌ Ошибка генерации дайджеста: {e}"
)