""" Обработчики для еженедельного дайджеста """ 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}" )