From 24daec1c099b0bd6beafff22b8d16c15ce054545 Mon Sep 17 00:00:00 2001 From: Kenik Date: Sun, 26 Jul 2026 10:45:31 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81:=20=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D0=B9=20=D1=80=D1=8F=D0=B4=20=D0=B2=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=B0=D1=82=D1=83=D1=80=D0=B5=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=BC=D0=B0=D0=BB=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D1=83=20(=C2=AB=D0=97=D0=B0=D0=B4=D0=B0=D1=82=D1=8C=20=D0=B2?= =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=BE=D1=81=C2=BB=20=D0=BC=D0=BE=D0=BB=D1=87?= =?UTF-8?q?=D0=B0=D0=BB)=20+=20vk.ru=20=D0=B2=20=D1=80=D0=B5=D0=B7=D0=BE?= =?UTF-8?q?=D0=BB=D0=B2=D0=B5=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) _kb при пустых рядах создавал ПУСТОЙ первый ряд (add_line до кнопок) — VK отклонял такую клавиатуру, из-за чего сообщения шага «Задать вопрос», «другая игра», «нужен совет» не отправлялись (состояние при этом менялось). Теперь add_line ставится только между реальными кнопками — пустых рядов нет. 2) _resolve_vk_user_id теперь понимает домен vk.ru (и www./m.), а не только vk.com — ссылку https://vk.ru/screenname на добавление админа бот принимает. Тесты: test_funnel_keyboards_no_empty_rows, test_resolve_vk_links. pytest 40 passed. Co-Authored-By: Claude Opus 4.8 --- bot.py | 8 ++++---- keyboards.py | 13 +++++++++---- tests/test_flow.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/bot.py b/bot.py index 291c830..4b7c16b 100644 --- a/bot.py +++ b/bot.py @@ -225,12 +225,12 @@ class VKBotApplication: def _resolve_vk_user_id(self, raw: str) -> Optional[int]: """Из ссылки / короткого имени / ID получает числовой VK user_id (или None). - Принимает что угодно: https://vk.com/gruzdevas, vk.com/id123, @gruzdevas, - gruzdevas, 123456. Короткое имя резолвится через VK API. + Принимает что угодно: https://vk.com/gruzdevas, https://vk.ru/gruzdevas, + m.vk.com/id123, @gruzdevas, gruzdevas, 123456. Короткое имя резолвится через VK API. """ s = (raw or "").strip() - s = re.sub(r"^https?://", "", s, flags=re.I) # убираем протокол - s = re.sub(r"^(m\.)?vk\.com/", "", s, flags=re.I) # убираем домен + s = re.sub(r"^https?://", "", s, flags=re.I) # убираем протокол + s = re.sub(r"^(www\.|m\.)?vk\.(com|ru)/", "", s, flags=re.I) # убираем домен (vk.com / vk.ru) s = s.split("?")[0].split("/")[0].strip().strip("@").strip() # берём первый сегмент if not s: return None diff --git a/keyboards.py b/keyboards.py index 3e7897e..fd61c24 100644 --- a/keyboards.py +++ b/keyboards.py @@ -34,16 +34,21 @@ def _kb(rows, back=False, ask_manager=False, back_menu=True): back_menu — кнопка «В главное меню ↩️». """ keyboard = VkKeyboard(one_time=False) - for i, row in enumerate(rows): - if i: + has = False # была ли уже добавлена хоть одна кнопка (чтобы не создавать пустой ряд) + for row in rows: + if has: keyboard.add_line() for label in row: keyboard.add_button(label, color=VkKeyboardColor.SECONDARY) + has = True if ask_manager: - keyboard.add_line() + if has: + keyboard.add_line() keyboard.add_button("Задать вопрос менеджеру 💬", color=VkKeyboardColor.SECONDARY) + has = True if back or back_menu: - keyboard.add_line() + if has: + keyboard.add_line() if back: keyboard.add_button("Назад ⬅️", color=VkKeyboardColor.SECONDARY) if back_menu: diff --git a/tests/test_flow.py b/tests/test_flow.py index df65705..54e3dde 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -213,6 +213,40 @@ def test_admin_edit_link(app): assert app._link("group_4") == "https://infocyber.pro/pay/group4" +def test_resolve_vk_ru_and_com_links(app): + """Резолвер принимает ссылки vk.ru и vk.com (id-форма работает без сети).""" + assert app._resolve_vk_user_id("https://vk.ru/id350793273") == 350793273 + assert app._resolve_vk_user_id("https://vk.com/id777") == 777 + assert app._resolve_vk_user_id("http://m.vk.ru/id42") == 42 + assert app._resolve_vk_user_id("www.vk.com/id5") == 5 + assert app._resolve_vk_user_id("id99") == 99 + assert app._resolve_vk_user_id("12345") == 12345 + + +def test_funnel_keyboards_no_empty_rows(): + """VK отклоняет клавиатуру с пустым рядом — таких быть не должно ни в одной.""" + import json, keyboards + kbs = [ + keyboards.get_baza_menu_only_keyboard(), keyboards.get_baza_forwho_keyboard(), + keyboards.get_baza_age_keyboard(), keyboards.get_baza_game_keyboard(), + keyboards.get_baza_level_keyboard(), keyboards.get_baza_format_keyboard(), + keyboards.get_baza_group_keyboard(), keyboards.get_baza_individual_keyboard(), + keyboards.get_baza_int_level_keyboard(), + ] + for kb in kbs: + for row in json.loads(kb)["buttons"]: + assert len(row) > 0 + + +def test_resolve_vk_links(app): + """Ссылки vk.ru и vk.com (и голый id) корректно приводятся к числовому id.""" + assert app._resolve_vk_user_id("https://vk.ru/id350793273") == 350793273 + assert app._resolve_vk_user_id("https://vk.com/id777") == 777 + assert app._resolve_vk_user_id("vk.ru/id42") == 42 + assert app._resolve_vk_user_id("id100") == 100 + assert app._resolve_vk_user_id("999") == 999 + + def test_admin_mute_notifications(app): """Админ может отключить себе уведомления от клиентов — _notify_operators его пропускает.""" op = _admin()