Files
Fringg 1f0fef114b refactor: complete structlog migration with contextvars, kwargs, and logging hardening
- Add ContextVarsMiddleware for automatic user_id/chat_id/username binding
  via structlog contextvars (aiogram) and http_method/http_path (FastAPI)
- Use bound_contextvars() context manager instead of clear_contextvars()
  to safely restore previous state instead of wiping all context
- Register ContextVarsMiddleware as outermost middleware (before GlobalError)
  so all error logs include user context
- Replace structlog.get_logger() with structlog.get_logger(__name__) across
  270 calls in 265 files for meaningful logger names
- Switch wrapper_class from BoundLogger to make_filtering_bound_logger()
  for pre-processor level filtering (performance optimization)
- Migrate 1411 %-style positional arg logger calls to structlog kwargs
  style across 161 files via AST script
- Migrate log_rotation_service.py from stdlib logging to structlog
- Add payment module prefixes to TelegramNotifierProcessor.IGNORED_LOGGER_PREFIXES
  and ExcludePaymentFilter.PAYMENT_MODULES to prevent payment data leaking
  to Telegram notifications and general log files
- Fix LoggingMiddleware: add from_user null-safety for channel posts,
  switch time.time() to time.monotonic() for duration measurement
- Remove duplicate logger assignments in purchase.py, config.py,
  inline.py, and admin/payments.py
2026-02-16 09:18:12 +03:00

173 lines
7.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Модуль для массовой блокировки пользователей по списку Telegram ID
"""
import structlog
from aiogram import Bot
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.crud.user import get_user_by_telegram_id
from app.database.models import UserStatus
from app.services.admin_notification_service import AdminNotificationService
from app.services.user_service import UserService
logger = structlog.get_logger(__name__)
class BulkBanService:
"""
Сервис для массовой блокировки пользователей по списку Telegram ID
"""
def __init__(self):
self.user_service = UserService()
async def ban_users_by_telegram_ids(
self,
db: AsyncSession,
admin_user_id: int,
telegram_ids: list[int],
reason: str = 'Заблокирован администратором по списку',
bot: Bot = None,
notify_admin: bool = True,
admin_name: str = 'Администратор',
) -> tuple[int, int, list[int]]:
"""
Массовая блокировка пользователей по Telegram ID
Args:
db: Асинхронная сессия базы данных
admin_user_id: ID администратора, который осуществляет блокировку
telegram_ids: Список Telegram ID для блокировки
reason: Причина блокировки
bot: Бот для отправки уведомлений
notify_admin: Отправлять ли уведомления администратору
admin_name: Имя администратора для логирования
Returns:
Кортеж из (успешно заблокированных, не найденных, список ID с ошибками)
"""
successfully_banned = 0
not_found_users = []
error_ids = []
for telegram_id in telegram_ids:
try:
# Получаем пользователя по Telegram ID
user = await get_user_by_telegram_id(db, telegram_id)
if not user:
logger.warning('Пользователь с Telegram ID не найден', telegram_id=telegram_id)
not_found_users.append(telegram_id)
continue
# Проверяем, что пользователь не заблокирован уже
if user.status == UserStatus.BLOCKED.value:
logger.info('Пользователь уже заблокирован', telegram_id=telegram_id)
continue
# Блокируем пользователя
ban_success = await self.user_service.block_user(db, user.id, admin_user_id, reason)
if ban_success:
successfully_banned += 1
logger.info('Пользователь успешно заблокирован', telegram_id=telegram_id)
# Отправляем уведомление пользователю, если возможно
if bot:
try:
await bot.send_message(
chat_id=telegram_id,
text=(
f'🚫 <b>Ваш аккаунт заблокирован</b>\n\n'
f'Причина: {reason}\n\n'
f'Если вы считаете, что блокировка произошла ошибочно, '
f'обратитесь в поддержку.'
),
parse_mode='HTML',
)
except Exception as e:
logger.warning(
'Не удалось отправить уведомление пользователю', telegram_id=telegram_id, error=e
)
else:
logger.error('Не удалось заблокировать пользователя', telegram_id=telegram_id)
error_ids.append(telegram_id)
except Exception as e:
logger.error('Ошибка при блокировке пользователя', telegram_id=telegram_id, error=e)
error_ids.append(telegram_id)
# Отправляем уведомление администратору
if notify_admin and bot:
try:
admin_notification_service = AdminNotificationService(bot)
await admin_notification_service.send_bulk_ban_notification(
admin_user_id, successfully_banned, len(not_found_users), len(error_ids), admin_name
)
except Exception as e:
logger.error('Ошибка при отправке уведомления администратору', error=e)
logger.info(
'Массовая блокировка завершена: успешно=, не найдено=, ошибки',
successfully_banned=successfully_banned,
not_found_users_count=len(not_found_users),
error_ids_count=len(error_ids),
)
return successfully_banned, len(not_found_users), error_ids
async def parse_telegram_ids_from_text(self, text: str) -> list[int]:
"""
Парсит Telegram ID из текста. Поддерживает различные форматы:
- по одному ID на строку
- через запятую
- через пробелы
- с @username (если username соответствует формату ID)
"""
if not text:
return []
# Удаляем лишние пробелы и разбиваем по переносам строк
lines = text.strip().split('\n')
ids = []
for line in lines:
# Убираем комментарии и лишние пробелы
line = line.strip()
if not line or line.startswith('#'):
continue
# Разбиваем строку по запятым или пробелам
tokens = line.replace(',', ' ').split()
for token in tokens:
token = token.strip()
# Убираем символ @ если присутствует
token = token.removeprefix('@')
# Проверяем, является ли токен числом (Telegram ID)
try:
telegram_id = int(token)
if telegram_id > 0: # Telegram ID должны быть положительными
ids.append(telegram_id)
except ValueError:
# Пропускаем, если не является числом
continue
# Убираем дубликаты, сохранив порядок
unique_ids = []
seen = set()
for tid in ids:
if tid not in seen:
unique_ids.append(tid)
seen.add(tid)
return unique_ids
# Создаем глобальный экземпляр сервиса
bulk_ban_service = BulkBanService()