Files
remnawave-bedolaga-telegram…/app/handlers/admin/blacklist.py
T
Fringg 9de34900a2 fix: comprehensive html.escape() for all user/admin data in Telegram HTML messages
Bot uses default HTML parse mode — all messages are HTML-parsed by Telegram.
Added html.escape() to all user-controlled and admin-controlled strings
before interpolation into HTML messages to prevent injection and parse errors.

49 files, ~250+ injection points fixed:
- user.full_name, first_name across all handlers and services
- tariff.name/description in purchase flow, admin panel, auto-purchase service
- campaign.name, start_parameter in admin and user-facing handlers
- group.name, promo_group.name across promo management
- contest.title, prize_text, leaderboard names (including public channels)
- transaction.description (contains raw user.full_name from referral service)
- restriction_reason across all balance and subscription handlers
- ticket.title, message_text, poll.title, poll.description
- welcome text template placeholders (first_name, username)
- maintenance reason, admin_name, selected_prize.display_name

New helpers in app/utils/formatting.py:
- safe_html_name() for escaping display names
- user_html_link() replacing 15+ duplicated inline link patterns
2026-03-23 08:06:15 +03:00

277 lines
12 KiB
Python

"""
Обработчики админ-панели для управления черным списком
"""
import html
import structlog
from aiogram import types
from aiogram.filters import StateFilter
from aiogram.fsm.context import FSMContext
from app.database.models import User
from app.services.blacklist_service import blacklist_service
from app.states import BlacklistStates
from app.utils.decorators import admin_required, error_handler
logger = structlog.get_logger(__name__)
@admin_required
@error_handler
async def show_blacklist_settings(callback: types.CallbackQuery, db_user: User, state: FSMContext):
"""
Показывает настройки черного списка
"""
logger.info('Вызван обработчик show_blacklist_settings для пользователя', from_user_id=callback.from_user.id)
is_enabled = blacklist_service.is_blacklist_check_enabled()
github_url = blacklist_service.get_blacklist_github_url()
blacklist_count = len(await blacklist_service.get_all_blacklisted_users())
status_text = '✅ Включена' if is_enabled else '❌ Отключена'
url_text = github_url or 'Не задан'
text = f"""
🔐 <b>Настройки черного списка</b>
Статус: {status_text}
URL к черному списку: <code>{url_text}</code>
Количество записей: {blacklist_count}
Действия:
"""
keyboard = [
[
types.InlineKeyboardButton(
text='🔄 Обновить список' if is_enabled else '🔄 Обновить (откл.)',
callback_data='admin_blacklist_update',
)
],
[
types.InlineKeyboardButton(
text='📋 Просмотреть список' if is_enabled else '📋 Просмотр (откл.)',
callback_data='admin_blacklist_view',
)
],
[
types.InlineKeyboardButton(
text='✏️ URL к GitHub' if not github_url else '✏️ Изменить URL', callback_data='admin_blacklist_set_url'
)
],
[
types.InlineKeyboardButton(
text='✅ Включить' if not is_enabled else '❌ Отключить', callback_data='admin_blacklist_toggle'
)
],
[types.InlineKeyboardButton(text='⬅️ Назад к пользователям', callback_data='admin_users')],
]
await callback.message.edit_text(text, reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard))
await callback.answer()
@admin_required
@error_handler
async def toggle_blacklist(callback: types.CallbackQuery, db_user: User, state: FSMContext):
"""
Переключает статус проверки черного списка
"""
# Текущая реализация использует настройки из .env
# Для полной реализации нужно будет создать сервис настроек
is_enabled = blacklist_service.is_blacklist_check_enabled()
# В реальной реализации нужно будет изменить настройку в базе данных
# или в системе настроек, но сейчас просто покажем статус
new_status = not is_enabled
status_text = 'включена' if new_status else 'отключена'
await callback.message.edit_text(
f'Статус проверки черного списка: {status_text}\n\n'
f'Для изменения статуса проверки черного списка измените значение\n'
f'<code>BLACKLIST_CHECK_ENABLED</code> в файле <code>.env</code>',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[types.InlineKeyboardButton(text='🔄 Обновить статус', callback_data='admin_blacklist_settings')],
[types.InlineKeyboardButton(text='⬅️ Назад', callback_data='admin_blacklist_settings')],
]
),
)
await callback.answer()
@admin_required
@error_handler
async def update_blacklist(callback: types.CallbackQuery, db_user: User, state: FSMContext):
"""
Обновляет черный список из GitHub
"""
success, message = await blacklist_service.force_update_blacklist()
if success:
await callback.message.edit_text(
f'{message}',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[types.InlineKeyboardButton(text='📋 Просмотреть список', callback_data='admin_blacklist_view')],
[types.InlineKeyboardButton(text='🔄 Ручное обновление', callback_data='admin_blacklist_update')],
[types.InlineKeyboardButton(text='⬅️ Назад', callback_data='admin_blacklist_settings')],
]
),
)
else:
await callback.message.edit_text(
f'❌ Ошибка обновления: {message}',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[types.InlineKeyboardButton(text='🔄 Повторить', callback_data='admin_blacklist_update')],
[types.InlineKeyboardButton(text='⬅️ Назад', callback_data='admin_blacklist_settings')],
]
),
)
await callback.answer()
@admin_required
@error_handler
async def show_blacklist_users(callback: types.CallbackQuery, db_user: User, state: FSMContext):
"""
Показывает список пользователей в черном списке
"""
blacklist_users = await blacklist_service.get_all_blacklisted_users()
if not blacklist_users:
text = 'Черный список пуст'
else:
text = f'🔐 <b>Черный список ({len(blacklist_users)} записей)</b>\n\n'
# Показываем первые 20 записей
for i, (tg_id, username, reason) in enumerate(blacklist_users[:20], 1):
text += f'{i}. <code>{tg_id}</code> {html.escape(username or "")}{html.escape(reason or "")}\n'
if len(blacklist_users) > 20:
text += f'\n... и еще {len(blacklist_users) - 20} записей'
await callback.message.edit_text(
text,
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[types.InlineKeyboardButton(text='🔄 Обновить', callback_data='admin_blacklist_view')],
[types.InlineKeyboardButton(text='⬅️ Назад', callback_data='admin_blacklist_settings')],
]
),
)
await callback.answer()
@admin_required
@error_handler
async def start_set_blacklist_url(callback: types.CallbackQuery, db_user: User, state: FSMContext):
"""
Начинает процесс установки URL к черному списку
"""
current_url = blacklist_service.get_blacklist_github_url() or 'не задан'
await callback.message.edit_text(
f'Введите новый URL к файлу черного списка на GitHub\n\n'
f'Текущий URL: {current_url}\n\n'
f'Пример: https://raw.githubusercontent.com/username/repository/main/blacklist.txt\n\n'
f'Для отмены используйте команду /cancel',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[[types.InlineKeyboardButton(text='⬅️ Назад', callback_data='admin_blacklist_settings')]]
),
)
await state.set_state(BlacklistStates.waiting_for_blacklist_url)
await callback.answer()
@admin_required
@error_handler
async def process_blacklist_url(message: types.Message, db_user: User, state: FSMContext):
"""
Обрабатывает введенный URL к черному списку
"""
# Обрабатываем сообщение только если бот ожидает ввод URL
if await state.get_state() != BlacklistStates.waiting_for_blacklist_url.state:
return
url = message.text.strip()
# В реальной реализации нужно сохранить URL в систему настроек
# В текущей реализации просто выводим сообщение
if url.lower() in ['/cancel', 'отмена', 'cancel']:
await message.answer(
'Настройка URL отменена',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text='🔐 Настройки черного списка', callback_data='admin_blacklist_settings'
)
]
]
),
)
await state.clear()
return
# Проверяем, что URL выглядит корректно
if not url.startswith(('http://', 'https://')):
await message.answer(
'❌ Некорректный URL. URL должен начинаться с http:// или https://',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text='🔐 Настройки черного списка', callback_data='admin_blacklist_settings'
)
]
]
),
)
return
# В реальной системе здесь нужно сохранить URL в базу данных настроек
# или в систему конфигурации
await message.answer(
f'✅ URL к черному списку установлен:\n<code>{url}</code>\n\n'
f'Для применения изменений перезапустите бота или измените значение\n'
f'<code>BLACKLIST_GITHUB_URL</code> в файле <code>.env</code>',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[types.InlineKeyboardButton(text='🔄 Обновить список', callback_data='admin_blacklist_update')],
[
types.InlineKeyboardButton(
text='🔐 Настройки черного списка', callback_data='admin_blacklist_settings'
)
],
]
),
)
await state.clear()
def register_blacklist_handlers(dp):
"""
Регистрация обработчиков черного списка
"""
# Обработчик показа настроек черного списка
# Этот обработчик нужно будет вызывать из меню пользователей или отдельно
dp.callback_query.register(show_blacklist_settings, lambda c: c.data == 'admin_blacklist_settings')
# Обработчики для взаимодействия с черным списком
dp.callback_query.register(toggle_blacklist, lambda c: c.data == 'admin_blacklist_toggle')
dp.callback_query.register(update_blacklist, lambda c: c.data == 'admin_blacklist_update')
dp.callback_query.register(show_blacklist_users, lambda c: c.data == 'admin_blacklist_view')
dp.callback_query.register(start_set_blacklist_url, lambda c: c.data == 'admin_blacklist_set_url')
# Обработчик сообщений для установки URL (работает только в нужном состоянии)
dp.message.register(process_blacklist_url, StateFilter(BlacklistStates.waiting_for_blacklist_url))