From 03daf1b0a4cf10b892137ac3b52c6f83e58a06f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:48:47 +0000 Subject: [PATCH 1/4] Initial plan From e4376da6ccbbb729742c9cac51e88688ccf1fe09 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:54:23 +0000 Subject: [PATCH 2/4] Implement flexible ban functionality accepting Telegram ID or username Co-authored-by: MarkShidran <15670678+MarkShidran@users.noreply.github.com> --- handlers/admin/ban_handlers.py | 14 ++- handlers/admin/validators/validators.py | 113 +++++++++++++++++++----- 2 files changed, 101 insertions(+), 26 deletions(-) diff --git a/handlers/admin/ban_handlers.py b/handlers/admin/ban_handlers.py index 39adc0b..025ffef 100644 --- a/handlers/admin/ban_handlers.py +++ b/handlers/admin/ban_handlers.py @@ -45,7 +45,10 @@ async def ban_list_add(message: types.Message): logger.info("Начало процесса добавления пользователя в бан.") await bot.send_message( message.from_user.id, - "Введите id пользователя, которого необходимо забанить:", + "Введите идентификатор пользователя, которого необходимо забанить:\n" + "• Внутренний ID базы данных (например: 123)\n" + "• Telegram ID (например: 987654321)\n" + "• Username (например: @username)", reply_markup=admin_cancel_markup(), ) await AdminData.user_ban.set() @@ -119,7 +122,10 @@ async def ban_list_remove(message: types.Message): logger.info("Начало процесса вывода пользователя из бана.") await bot.send_message( message.from_user.id, - "Введите id пользователя, которого необходимо убрать из бан листа:", + "Введите идентификатор пользователя, которого необходимо убрать из бан листа:\n" + "• Внутренний ID базы данных (например: 123)\n" + "• Telegram ID (например: 987654321)\n" + "• Username (например: @username)", reply_markup=admin_cancel_markup(), ) await AdminData.user_unban.set() @@ -168,7 +174,9 @@ async def comment_to_unban_answer(message: types.Message, state: FSMContext): async def save_to_unban(unbanned_user_id, comment): """Сохранение в БД, что пользователь выведен из бана.""" with Session() as db_session: - db_session.query(BanList).filter(BanList.banned_user_id == unbanned_user_id).update( + db_session.query(BanList).filter( + BanList.banned_user_id == unbanned_user_id + ).update( { "ban_status": 0, "date_of_unban": datetime.date.today(), diff --git a/handlers/admin/validators/validators.py b/handlers/admin/validators/validators.py index abda22f..9d10e24 100644 --- a/handlers/admin/validators/validators.py +++ b/handlers/admin/validators/validators.py @@ -4,7 +4,7 @@ from sqlalchemy import exists from controllerBD.db_loader import Session -from controllerBD.models import Users +from controllerBD.models import Username, Users from handlers.user.ban_check import check_id_in_ban_with_status from loader import bot, logger @@ -17,37 +17,104 @@ async def comment_validator(text): async def ban_validator(message: types.Message): - """Валидация id пользователя для добавления в бан.""" - if re.fullmatch(r"^\d{1,10}$", message.text): - if await check_id_in_base(message.text): - if not await check_id_in_ban_with_status(message.text, 1): - logger.info("Валидация пройдена.") - return True - await bot.send_message(message.from_user.id, "Пользователь уже забаннен.") - return False + """Валидация пользователя для добавления в бан.""" + success, db_id, result_message = await resolve_user_input_to_db_id(message.text) + + if not success: + await bot.send_message(message.from_user.id, result_message) + return False + + # Проверяем, не забаннен ли уже пользователь + if await check_id_in_ban_with_status(db_id, 1): await bot.send_message( - message.from_user.id, "Пользователя с таким id не существует." + message.from_user.id, f"Пользователь уже забаннен. {result_message}" ) return False - await bot.send_message(message.from_user.id, "Неверный ввод, введите число.") - return False + + logger.info(f"Валидация пройдена. {result_message}") + # Сохраняем найденный DB ID в message.text для дальнейшего использования + message.text = str(db_id) + return True async def unban_validator(message: types.Message): - """Валидация id пользователя для вывода из бана.""" - if re.fullmatch(r"^\d{1,10}$", message.text): - if await check_id_in_base(message.text): - if await check_id_in_ban_with_status(message.text, 1): - logger.info("Валидация пройдена.") - return True - await bot.send_message(message.from_user.id, "Пользователь не забаннен.") - return False + """Валидация пользователя для вывода из бана.""" + success, db_id, result_message = await resolve_user_input_to_db_id(message.text) + + if not success: + await bot.send_message(message.from_user.id, result_message) + return False + + # Проверяем, забаннен ли пользователь + if not await check_id_in_ban_with_status(db_id, 1): await bot.send_message( - message.from_user.id, "Пользователя с таким id не существует." + message.from_user.id, f"Пользователь не забаннен. {result_message}" ) return False - await bot.send_message(message.from_user.id, "Неверный ввод, введите число.") - return False + + logger.info(f"Валидация пройдена. {result_message}") + # Сохраняем найденный DB ID в message.text для дальнейшего использования + message.text = str(db_id) + return True + + +async def resolve_user_input_to_db_id(user_input: str): + """ + Пытается определить внутренний ID пользователя по различным типам ввода: + - Внутренний ID базы данных (числовой) + - Telegram ID (числовой, но больше) + - Username (начинается с @) + + Возвращает кортеж (success: bool, db_id: int | None, message: str) + """ + user_input = user_input.strip() + + with Session() as db_session: + # Случай 1: Ввод - это username (начинается с @) + if user_input.startswith("@"): + username = user_input[1:] # Убираем @ + user_record = ( + db_session.query(Username).filter(Username.username == username).first() + ) + if user_record: + return ( + True, + user_record.id, + f"Найден пользователь с username @{username}", + ) + return False, None, f"Пользователь с username @{username} не найден" + + # Случай 2: Числовой ввод + if re.fullmatch(r"^\d+$", user_input): + user_id = int(user_input) + + # Сначала проверяем, является ли это внутренним ID базы данных + internal_user = db_session.query(Users).filter(Users.id == user_id).first() + if internal_user: + return ( + True, + user_id, + f"Найден пользователь по внутреннему ID: {internal_user.name}", + ) + + # Если не найден как внутренний ID, проверяем как Telegram ID + telegram_user = ( + db_session.query(Users).filter(Users.teleg_id == user_id).first() + ) + if telegram_user: + return ( + True, + telegram_user.id, + f"Найден пользователь по Telegram ID: {telegram_user.name}", + ) + + return ( + False, + None, + f"Пользователь с ID {user_id} не найден ни в внутренней базе, ни среди Telegram ID", + ) + + return False, None, "Неверный формат ввода. Введите число (ID) или username с @" async def check_id_in_base(user_id): From 88eed9b26dd77e678454480062e0b49ee933e346 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:56:34 +0000 Subject: [PATCH 3/4] Add input length validation to maintain backward compatibility Co-authored-by: MarkShidran <15670678+MarkShidran@users.noreply.github.com> --- handlers/admin/validators/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/admin/validators/validators.py b/handlers/admin/validators/validators.py index 9d10e24..cff9fd4 100644 --- a/handlers/admin/validators/validators.py +++ b/handlers/admin/validators/validators.py @@ -85,7 +85,7 @@ async def resolve_user_input_to_db_id(user_input: str): return False, None, f"Пользователь с username @{username} не найден" # Случай 2: Числовой ввод - if re.fullmatch(r"^\d+$", user_input): + if re.fullmatch(r"^\d{1,15}$", user_input): user_id = int(user_input) # Сначала проверяем, является ли это внутренним ID базы данных From 5d1be300f3750ea98edade36f324fcc7a8ec0ee6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:48:58 +0000 Subject: [PATCH 4/4] Fix ID collision issue by detecting ambiguous cases and providing disambiguation Co-authored-by: MarkShidran <15670678+MarkShidran@users.noreply.github.com> --- handlers/admin/ban_handlers.py | 10 +++--- handlers/admin/validators/validators.py | 48 ++++++++++++++++++++----- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/handlers/admin/ban_handlers.py b/handlers/admin/ban_handlers.py index 025ffef..1e3c436 100644 --- a/handlers/admin/ban_handlers.py +++ b/handlers/admin/ban_handlers.py @@ -46,9 +46,10 @@ async def ban_list_add(message: types.Message): await bot.send_message( message.from_user.id, "Введите идентификатор пользователя, которого необходимо забанить:\n" - "• Внутренний ID базы данных (например: 123)\n" + "• Внутренний ID базы данных (например: 123 или db:123)\n" "• Telegram ID (например: 987654321)\n" - "• Username (например: @username)", + "• Username (например: @username)\n" + "\n💡 При совпадении ID используйте db:123 для точного указания внутреннего ID", reply_markup=admin_cancel_markup(), ) await AdminData.user_ban.set() @@ -123,9 +124,10 @@ async def ban_list_remove(message: types.Message): await bot.send_message( message.from_user.id, "Введите идентификатор пользователя, которого необходимо убрать из бан листа:\n" - "• Внутренний ID базы данных (например: 123)\n" + "• Внутренний ID базы данных (например: 123 или db:123)\n" "• Telegram ID (например: 987654321)\n" - "• Username (например: @username)", + "• Username (например: @username)\n" + "\n💡 При совпадении ID используйте db:123 для точного указания внутреннего ID", reply_markup=admin_cancel_markup(), ) await AdminData.user_unban.set() diff --git a/handlers/admin/validators/validators.py b/handlers/admin/validators/validators.py index cff9fd4..514f6e9 100644 --- a/handlers/admin/validators/validators.py +++ b/handlers/admin/validators/validators.py @@ -64,6 +64,7 @@ async def resolve_user_input_to_db_id(user_input: str): - Внутренний ID базы данных (числовой) - Telegram ID (числовой, но больше) - Username (начинается с @) + - Явный внутренний ID (db:123) для разрешения коллизий Возвращает кортеж (success: bool, db_id: int | None, message: str) """ @@ -84,30 +85,61 @@ async def resolve_user_input_to_db_id(user_input: str): ) return False, None, f"Пользователь с username @{username} не найден" - # Случай 2: Числовой ввод + # Случай 2: Явное указание внутреннего ID (db:123) + if user_input.startswith("db:"): + try: + db_id = int(user_input[3:]) + internal_user = ( + db_session.query(Users).filter(Users.id == db_id).first() + ) + if internal_user: + return ( + True, + db_id, + f"Найден пользователь по явно указанному внутреннему ID: {internal_user.name}", + ) + return False, None, f"Пользователь с внутренним ID {db_id} не найден" + except ValueError: + return False, None, "Неверный формат после 'db:'. Используйте db:123" + + # Случай 3: Числовой ввод (может быть коллизия) if re.fullmatch(r"^\d{1,15}$", user_input): user_id = int(user_input) - # Сначала проверяем, является ли это внутренним ID базы данных + # Проверяем оба типа ID одновременно для обнаружения коллизий internal_user = db_session.query(Users).filter(Users.id == user_id).first() - if internal_user: + telegram_user = ( + db_session.query(Users).filter(Users.teleg_id == user_id).first() + ) + + # Случай 1: Найден только по внутреннему ID + if internal_user and not telegram_user: return ( True, user_id, f"Найден пользователь по внутреннему ID: {internal_user.name}", ) - # Если не найден как внутренний ID, проверяем как Telegram ID - telegram_user = ( - db_session.query(Users).filter(Users.teleg_id == user_id).first() - ) - if telegram_user: + # Случай 2: Найден только по Telegram ID + if telegram_user and not internal_user: return ( True, telegram_user.id, f"Найден пользователь по Telegram ID: {telegram_user.name}", ) + # Случай 3: КОЛЛИЗИЯ - найден и по внутреннему ID, и по Telegram ID + if internal_user and telegram_user: + # Приоритет отдаем Telegram ID, так как это чаще используется админами + return ( + True, + telegram_user.id, + f"⚠️ ВНИМАНИЕ: ID {user_id} найден у двух пользователей! " + f"Выбран по Telegram ID: {telegram_user.name}. " + f"Для точности используйте @username или укажите 'db:{user_id}' для внутреннего ID.", + ) + + # Случай 4: Не найден ни по одному типу ID return ( False, None,