import re import structlog from aiogram import Dispatcher, F, types from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from app.database.crud.rules import clear_all_rules, create_or_update_rules, get_current_rules_content from app.database.models import User from app.states import AdminStates from app.utils.decorators import admin_required, error_handler from app.utils.validators import get_html_help_text, validate_html_tags def _safe_preview(html_text: str, limit: int = 500) -> str: """Создаёт превью текста, безопасно обрезая HTML-теги.""" plain = re.sub(r'<[^>]+>', '', html_text) if len(plain) <= limit: return plain return plain[:limit] + '...' logger = structlog.get_logger(__name__) @admin_required @error_handler async def show_rules_management(callback: types.CallbackQuery, db_user: User, db: AsyncSession): text = """ 📋 Управление правилами сервиса Текущие правила показываются пользователям при регистрации и в главном меню. Выберите действие: """ keyboard = [ [types.InlineKeyboardButton(text='📝 Редактировать правила', callback_data='admin_edit_rules')], [types.InlineKeyboardButton(text='👀 Просмотр правил', callback_data='admin_view_rules')], [types.InlineKeyboardButton(text='🗑️ Очистить правила', callback_data='admin_clear_rules')], [types.InlineKeyboardButton(text='ℹ️ Помощь по HTML', callback_data='admin_rules_help')], [types.InlineKeyboardButton(text='⬅️ Назад', callback_data='admin_submenu_settings')], ] await callback.message.edit_text(text, reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard)) await callback.answer() @admin_required @error_handler async def view_current_rules(callback: types.CallbackQuery, db_user: User, db: AsyncSession): try: current_rules = await get_current_rules_content(db, db_user.language) is_valid, error_msg = validate_html_tags(current_rules) warning = '' if not is_valid: warning = f'\n\n⚠️ Внимание: В правилах найдена ошибка HTML: {error_msg}' await callback.message.edit_text( f'📋 Текущие правила сервиса\n\n{current_rules}{warning}', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [types.InlineKeyboardButton(text='✏️ Редактировать', callback_data='admin_edit_rules')], [types.InlineKeyboardButton(text='🗑️ Очистить', callback_data='admin_clear_rules')], [types.InlineKeyboardButton(text='⬅️ Назад', callback_data='admin_rules')], ] ), ) await callback.answer() except Exception as e: logger.error('Ошибка при показе правил', error=e) await callback.message.edit_text( '❌ Ошибка при загрузке правил. Возможно, в тексте есть некорректные HTML теги.', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [types.InlineKeyboardButton(text='🗑️ Очистить правила', callback_data='admin_clear_rules')], [types.InlineKeyboardButton(text='⬅️ Назад', callback_data='admin_rules')], ] ), ) await callback.answer() @admin_required @error_handler async def start_edit_rules(callback: types.CallbackQuery, db_user: User, state: FSMContext, db: AsyncSession): try: current_rules = await get_current_rules_content(db, db_user.language) preview = _safe_preview(current_rules, 500) text = ( '✏️ Редактирование правил\n\n' f'Текущие правила:\n{preview}\n\n' 'Отправьте новый текст правил сервиса.\n\n' 'Поддерживается HTML разметка. Все теги будут проверены перед сохранением.\n\n' '💡 Совет: Нажмите /html_help для просмотра поддерживаемых тегов' ) await callback.message.edit_text( text, reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [types.InlineKeyboardButton(text='ℹ️ HTML помощь', callback_data='admin_rules_help')], [types.InlineKeyboardButton(text='❌ Отмена', callback_data='admin_rules')], ] ), ) await state.set_state(AdminStates.editing_rules_page) await callback.answer() except Exception as e: logger.error('Ошибка при начале редактирования правил', error=e) await callback.answer('❌ Ошибка при загрузке правил для редактирования', show_alert=True) @admin_required @error_handler async def process_rules_edit(message: types.Message, db_user: User, state: FSMContext, db: AsyncSession): new_rules = message.text if len(new_rules) > 4000: await message.answer('❌ Текст правил слишком длинный (максимум 4000 символов)') return is_valid, error_msg = validate_html_tags(new_rules) if not is_valid: await message.answer( f'❌ Ошибка в HTML разметке:\n{error_msg}\n\n' f'Пожалуйста, исправьте ошибки и отправьте текст заново.\n\n' f'💡 Используйте /html_help для просмотра правильного синтаксиса', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [types.InlineKeyboardButton(text='ℹ️ HTML помощь', callback_data='admin_rules_help')], [types.InlineKeyboardButton(text='❌ Отмена', callback_data='admin_rules')], ] ), ) return try: preview_text = f'📋 Предварительный просмотр новых правил:\n\n{new_rules}\n\n' preview_text += '⚠️ Внимание! Новые правила будут показываться всем пользователям.\n\n' preview_text += 'Сохранить изменения?' if len(preview_text) > 4000: preview_text = ( '📋 Предварительный просмотр новых правил:\n\n' f'{_safe_preview(new_rules, 500)}\n\n' f'⚠️ Внимание! Новые правила будут показываться всем пользователям.\n\n' f'Текст правил: {len(new_rules)} символов\n' f'Сохранить изменения?' ) await message.answer( preview_text, reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [ types.InlineKeyboardButton(text='✅ Сохранить', callback_data='admin_save_rules'), types.InlineKeyboardButton(text='❌ Отмена', callback_data='admin_rules'), ] ] ), ) await state.update_data(new_rules=new_rules) except Exception as e: logger.error('Ошибка при показе превью правил', error=e) await message.answer( '⚠️ Подтверждение сохранения правил\n\n' f'Новые правила готовы к сохранению ({len(new_rules)} символов).\n' f'HTML теги проверены и корректны.\n\n' f'Сохранить изменения?', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [ types.InlineKeyboardButton(text='✅ Сохранить', callback_data='admin_save_rules'), types.InlineKeyboardButton(text='❌ Отмена', callback_data='admin_rules'), ] ] ), ) await state.update_data(new_rules=new_rules) @admin_required @error_handler async def save_rules(callback: types.CallbackQuery, db_user: User, state: FSMContext, db: AsyncSession): data = await state.get_data() new_rules = data.get('new_rules') if not new_rules: await callback.answer('❌ Ошибка: текст правил не найден', show_alert=True) return is_valid, error_msg = validate_html_tags(new_rules) if not is_valid: await callback.message.edit_text( f'❌ Ошибка при сохранении:\n{error_msg}\n\nПравила не были сохранены из-за ошибок в HTML разметке.', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [types.InlineKeyboardButton(text='🔄 Попробовать снова', callback_data='admin_edit_rules')], [types.InlineKeyboardButton(text='📋 К правилам', callback_data='admin_rules')], ] ), ) await state.clear() await callback.answer() return try: await create_or_update_rules(db=db, content=new_rules, language=db_user.language) from app.localization.texts import clear_rules_cache clear_rules_cache() from app.localization.texts import refresh_rules_cache await refresh_rules_cache(db_user.language) await callback.message.edit_text( '✅ Правила сервиса успешно обновлены!\n\n' '✓ Новые правила сохранены в базе данных\n' '✓ HTML теги проверены и корректны\n' '✓ Кеш правил очищен и обновлен\n' '✓ Правила будут показываться пользователям\n\n' f'📊 Размер текста: {len(new_rules)} символов', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [types.InlineKeyboardButton(text='👀 Просмотреть', callback_data='admin_view_rules')], [types.InlineKeyboardButton(text='📋 К правилам', callback_data='admin_rules')], ] ), ) await state.clear() logger.info('Правила сервиса обновлены администратором', telegram_id=db_user.telegram_id) await callback.answer() except Exception as e: logger.error('Ошибка сохранения правил', error=e) await callback.message.edit_text( '❌ Ошибка при сохранении правил\n\nПроизошла ошибка при записи в базу данных. Попробуйте еще раз.', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [types.InlineKeyboardButton(text='🔄 Попробовать снова', callback_data='admin_save_rules')], [types.InlineKeyboardButton(text='📋 К правилам', callback_data='admin_rules')], ] ), ) await callback.answer() @admin_required @error_handler async def clear_rules_confirmation(callback: types.CallbackQuery, db_user: User, db: AsyncSession): await callback.message.edit_text( '🗑️ Очистка правил сервиса\n\n' '⚠️ ВНИМАНИЕ! Вы собираетесь полностью удалить все правила сервиса.\n\n' 'После очистки пользователи будут видеть стандартные правила по умолчанию.\n\n' 'Это действие нельзя отменить. Продолжить?', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [ types.InlineKeyboardButton(text='✅ Да, очистить', callback_data='admin_confirm_clear_rules'), types.InlineKeyboardButton(text='❌ Отмена', callback_data='admin_rules'), ] ] ), ) await callback.answer() @admin_required @error_handler async def confirm_clear_rules(callback: types.CallbackQuery, db_user: User, db: AsyncSession): try: await clear_all_rules(db, db_user.language) from app.localization.texts import clear_rules_cache clear_rules_cache() await callback.message.edit_text( '✅ Правила успешно очищены!\n\n' '✓ Все пользовательские правила удалены\n' '✓ Теперь используются стандартные правила\n' '✓ Кеш правил очищен\n\n' 'Пользователи будут видеть правила по умолчанию.', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [types.InlineKeyboardButton(text='📝 Создать новые', callback_data='admin_edit_rules')], [types.InlineKeyboardButton(text='👀 Посмотреть текущие', callback_data='admin_view_rules')], [types.InlineKeyboardButton(text='📋 К правилам', callback_data='admin_rules')], ] ), ) logger.info('Правила очищены администратором', telegram_id=db_user.telegram_id) await callback.answer() except Exception as e: logger.error('Ошибка при очистке правил', error=e) await callback.answer('❌ Ошибка при очистке правил', show_alert=True) @admin_required @error_handler async def show_html_help(callback: types.CallbackQuery, db_user: User, db: AsyncSession): help_text = get_html_help_text() await callback.message.edit_text( f'ℹ️ Справка по HTML форматированию\n\n{help_text}', reply_markup=types.InlineKeyboardMarkup( inline_keyboard=[ [types.InlineKeyboardButton(text='📝 Редактировать правила', callback_data='admin_edit_rules')], [types.InlineKeyboardButton(text='⬅️ Назад', callback_data='admin_rules')], ] ), ) await callback.answer() def register_handlers(dp: Dispatcher): dp.callback_query.register(show_rules_management, F.data == 'admin_rules') dp.callback_query.register(view_current_rules, F.data == 'admin_view_rules') dp.callback_query.register(start_edit_rules, F.data == 'admin_edit_rules') dp.callback_query.register(save_rules, F.data == 'admin_save_rules') dp.callback_query.register(clear_rules_confirmation, F.data == 'admin_clear_rules') dp.callback_query.register(confirm_clear_rules, F.data == 'admin_confirm_clear_rules') dp.callback_query.register(show_html_help, F.data == 'admin_rules_help') dp.message.register(process_rules_edit, AdminStates.editing_rules_page)