322 lines
13 KiB
Python
322 lines
13 KiB
Python
"""
|
||
Обработчики для еженедельного дайджеста
|
||
"""
|
||
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"📰 <b>Дайджест #{digest_data['week_number']}/{digest_data['year']}</b>\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):
|
||
"""Утвердить и отправить дайджест"""
|
||
await callback.answer()
|
||
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"📰 <b>Еженедельный дайджест</b>\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"📰 <b>Еженедельный дайджест</b>\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"✅ <b>Дайджест отправлен!</b>\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):
|
||
"""Отклонить дайджест"""
|
||
await callback.answer()
|
||
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):
|
||
"""Редактировать текст дайджеста"""
|
||
await callback.answer()
|
||
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"📰 <b>Дайджест за неделю готов!</b>\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}"
|
||
)
|