mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-26 06:12:00 +00:00
Rules editor crashed when preview truncated mid-HTML tag (e.g. <blockquote> cut to <blockquo), causing Telegram parse error. Strip HTML tags before truncating preview text. Also fix MissingGreenlet in build_topup_success_keyboard: fall back to a direct DB query instead of showing wrong button text.
342 lines
16 KiB
Python
342 lines
16 KiB
Python
import logging
|
||
import re
|
||
|
||
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 = logging.getLogger(__name__)
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_rules_management(callback: types.CallbackQuery, db_user: User, db: AsyncSession):
|
||
text = """
|
||
📋 <b>Управление правилами сервиса</b>
|
||
|
||
Текущие правила показываются пользователям при регистрации и в главном меню.
|
||
|
||
Выберите действие:
|
||
"""
|
||
|
||
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⚠️ <b>Внимание:</b> В правилах найдена ошибка HTML: {error_msg}'
|
||
|
||
await callback.message.edit_text(
|
||
f'📋 <b>Текущие правила сервиса</b>\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(f'Ошибка при показе правил: {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 = (
|
||
'✏️ <b>Редактирование правил</b>\n\n'
|
||
f'<b>Текущие правила:</b>\n<code>{preview}</code>\n\n'
|
||
'Отправьте новый текст правил сервиса.\n\n'
|
||
'<i>Поддерживается HTML разметка. Все теги будут проверены перед сохранением.</i>\n\n'
|
||
'💡 <b>Совет:</b> Нажмите /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(f'Ошибка при начале редактирования правил: {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'❌ <b>Ошибка в HTML разметке:</b>\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'📋 <b>Предварительный просмотр новых правил:</b>\n\n{new_rules}\n\n'
|
||
preview_text += '⚠️ <b>Внимание!</b> Новые правила будут показываться всем пользователям.\n\n'
|
||
preview_text += 'Сохранить изменения?'
|
||
|
||
if len(preview_text) > 4000:
|
||
preview_text = (
|
||
'📋 <b>Предварительный просмотр новых правил:</b>\n\n'
|
||
f'{_safe_preview(new_rules, 500)}\n\n'
|
||
f'⚠️ <b>Внимание!</b> Новые правила будут показываться всем пользователям.\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(f'Ошибка при показе превью правил: {e}')
|
||
await message.answer(
|
||
'⚠️ <b>Подтверждение сохранения правил</b>\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'❌ <b>Ошибка при сохранении:</b>\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(
|
||
'✅ <b>Правила сервиса успешно обновлены!</b>\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(f'Правила сервиса обновлены администратором {db_user.telegram_id}')
|
||
await callback.answer()
|
||
|
||
except Exception as e:
|
||
logger.error(f'Ошибка сохранения правил: {e}')
|
||
await callback.message.edit_text(
|
||
'❌ <b>Ошибка при сохранении правил</b>\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(
|
||
'🗑️ <b>Очистка правил сервиса</b>\n\n'
|
||
'⚠️ <b>ВНИМАНИЕ!</b> Вы собираетесь полностью удалить все правила сервиса.\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(
|
||
'✅ <b>Правила успешно очищены!</b>\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(f'Правила очищены администратором {db_user.telegram_id}')
|
||
await callback.answer()
|
||
|
||
except Exception as e:
|
||
logger.error(f'Ошибка при очистке правил: {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'ℹ️ <b>Справка по HTML форматированию</b>\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)
|