SilverElixir commited on
Commit
00f233d
·
1 Parent(s): fb2a2f0

Block banned users in user commands and lang callback

Browse files
Files changed (2) hide show
  1. lumen_commands.py +15 -1
  2. tests/test_bot_commands.py +33 -0
lumen_commands.py CHANGED
@@ -46,6 +46,8 @@ log = logging.getLogger("bot")
46
 
47
  async def cmd_start(message: Message) -> None:
48
  import bot
 
 
49
  # Лимит и здесь: хендлер зарегистрирован раньше общего catch-all, поэтому
50
  # сообщение до _reject_rate_limited_message не доходило — спамер получал
51
  # бесконечные ответы в группе, расходуя прокси-трафик (аудит 26.09.2026).
@@ -155,6 +157,8 @@ async def inline_draw(message: Message, prompt: str) -> None:
155
 
156
  async def cmd_draw(message: Message) -> None:
157
  import bot
 
 
158
  prompt = message.text.partition(" ")[2].strip() if message.text else ""
159
  if not prompt:
160
  await bot._safe_reply(message, bot._t(message.chat.id, "draw_empty"))
@@ -397,6 +401,8 @@ async def inline_tts(message: Message, text: str) -> None:
397
 
398
  async def cmd_tts(message: Message) -> None:
399
  import bot
 
 
400
  text = message.text.partition(" ")[2].strip() if message.text else ""
401
  if not text:
402
  await bot._safe_reply(message, bot._t(message.chat.id, "tts_empty"))
@@ -409,6 +415,8 @@ async def cmd_tts(message: Message) -> None:
409
  async def cmd_reset(message: Message) -> None:
410
  """Сброс истории чата. В меню. Личка — всем, группа — админам/владельцу."""
411
  import bot
 
 
412
  requester_id = message.from_user.id if message.from_user else None
413
  if not await bot._is_privileged_in_chat(message.chat.type, message.chat.id, requester_id):
414
  await bot._tg_call(
@@ -428,6 +436,8 @@ async def cmd_reset(message: Message) -> None:
428
  # ── Язык системных сообщений (/lang) — ответы ИИ не трогает (см. RESPONSE LANGUAGE в system_prompt.py). Права как у /reset; кнопки без флагов/эмодзи, текущий — с ✓, меню по алфавиту кода.
429
  async def cmd_lang(message: Message) -> None:
430
  import bot
 
 
431
  lang = bot._chat_lang(message.chat.id)
432
  rows = []
433
  codes = list(SUPPORTED_LANGS)
@@ -450,6 +460,10 @@ async def cmd_lang(message: Message) -> None:
450
  async def handle_lang_callback(query: CallbackQuery) -> None:
451
  """Кнопка языка ("lang:<код>", влезает в лимит 64 байт): права, сохранение, подтверждение на новом языке."""
452
  import bot
 
 
 
 
453
  data = query.data or ""
454
  if not data.startswith("lang:"):
455
  return
@@ -921,7 +935,7 @@ async def _send_pick_question(message: Message, scenario: str, original_text: st
921
  async def handle_pick_callback(query: CallbackQuery) -> None:
922
  """Кнопки-уточнения: чужие отклоняем, протухшие известные перевыпускаем разок, выбор дописываем к запросу и гоним обычным путём (_handle_message_core)."""
923
  import bot
924
- if query.from_user is not None and bot._is_banned(query.from_user.id):
925
  # Забаненный и кнопками не отвечает: иначе игнор обходился живыми пиками.
926
  with contextlib.suppress(Exception):
927
  await query.answer()
 
46
 
47
  async def cmd_start(message: Message) -> None:
48
  import bot
49
+ if getattr(message, "from_user", None) is not None and bot._is_banned(message.from_user.id):
50
+ return
51
  # Лимит и здесь: хендлер зарегистрирован раньше общего catch-all, поэтому
52
  # сообщение до _reject_rate_limited_message не доходило — спамер получал
53
  # бесконечные ответы в группе, расходуя прокси-трафик (аудит 26.09.2026).
 
157
 
158
  async def cmd_draw(message: Message) -> None:
159
  import bot
160
+ if getattr(message, "from_user", None) is not None and bot._is_banned(message.from_user.id):
161
+ return
162
  prompt = message.text.partition(" ")[2].strip() if message.text else ""
163
  if not prompt:
164
  await bot._safe_reply(message, bot._t(message.chat.id, "draw_empty"))
 
401
 
402
  async def cmd_tts(message: Message) -> None:
403
  import bot
404
+ if getattr(message, "from_user", None) is not None and bot._is_banned(message.from_user.id):
405
+ return
406
  text = message.text.partition(" ")[2].strip() if message.text else ""
407
  if not text:
408
  await bot._safe_reply(message, bot._t(message.chat.id, "tts_empty"))
 
415
  async def cmd_reset(message: Message) -> None:
416
  """Сброс истории чата. В меню. Личка — всем, группа — админам/владельцу."""
417
  import bot
418
+ if getattr(message, "from_user", None) is not None and bot._is_banned(message.from_user.id):
419
+ return
420
  requester_id = message.from_user.id if message.from_user else None
421
  if not await bot._is_privileged_in_chat(message.chat.type, message.chat.id, requester_id):
422
  await bot._tg_call(
 
436
  # ── Язык системных сообщений (/lang) — ответы ИИ не трогает (см. RESPONSE LANGUAGE в system_prompt.py). Права как у /reset; кнопки без флагов/эмодзи, текущий — с ✓, меню по алфавиту кода.
437
  async def cmd_lang(message: Message) -> None:
438
  import bot
439
+ if getattr(message, "from_user", None) is not None and bot._is_banned(message.from_user.id):
440
+ return
441
  lang = bot._chat_lang(message.chat.id)
442
  rows = []
443
  codes = list(SUPPORTED_LANGS)
 
460
  async def handle_lang_callback(query: CallbackQuery) -> None:
461
  """Кнопка языка ("lang:<код>", влезает в лимит 64 байт): права, сохранение, подтверждение на новом языке."""
462
  import bot
463
+ if getattr(query, "from_user", None) is not None and bot._is_banned(query.from_user.id):
464
+ with contextlib.suppress(Exception):
465
+ await query.answer()
466
+ return
467
  data = query.data or ""
468
  if not data.startswith("lang:"):
469
  return
 
935
  async def handle_pick_callback(query: CallbackQuery) -> None:
936
  """Кнопки-уточнения: чужие отклоняем, протухшие известные перевыпускаем разок, выбор дописываем к запросу и гоним обычным путём (_handle_message_core)."""
937
  import bot
938
+ if getattr(query, "from_user", None) is not None and bot._is_banned(query.from_user.id):
939
  # Забаненный и кнопками не отвечает: иначе игнор обходился живыми пиками.
940
  with contextlib.suppress(Exception):
941
  await query.answer()
tests/test_bot_commands.py CHANGED
@@ -1545,6 +1545,39 @@ def test_pick_callback_ignores_banned_user(monkeypatch):
1545
  assert answered == [True]
1546
 
1547
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1548
  def test_selftest_llm_head_gemini_success_counts_quota_and_outcome(monkeypatch):
1549
  # Проба Gemini как у остальных: успех в квоту и сброс счётчика карантина.
1550
  import lumen_model_speed
 
1545
  assert answered == [True]
1546
 
1547
 
1548
+ def test_banned_user_commands_send_nothing(monkeypatch):
1549
+ # Обход бана через прямые команды: catch-all с _is_banned не ловит
1550
+ # зарегистрированные раньше cmd_*, каждая проверяет первой строкой.
1551
+ from unittest.mock import AsyncMock
1552
+ monkeypatch.setattr(bot, "OWNER_ID", 106002)
1553
+ banned_id = 607002
1554
+ bot._ban_user(banned_id)
1555
+ try:
1556
+ calls = []
1557
+ async def fake_tg_call(method, *args, **kwargs):
1558
+ calls.append(True)
1559
+ return SimpleNamespace()
1560
+ monkeypatch.setattr(bot, "_tg_call", fake_tg_call)
1561
+ monkeypatch.setattr(bot, "_safe_reply", AsyncMock(side_effect=lambda *a, **k: calls.append(True)))
1562
+ monkeypatch.setattr(bot, "_send_text", AsyncMock(side_effect=lambda *a, **k: calls.append(True)))
1563
+ cases = [
1564
+ (bot.cmd_start, "/start"),
1565
+ (bot.cmd_draw, "/draw кот"),
1566
+ (bot.cmd_tts, "/tts привет"),
1567
+ (bot.cmd_reset, "/reset"),
1568
+ (bot.cmd_lang, "/lang"),
1569
+ ]
1570
+ for func, text in cases:
1571
+ calls.clear()
1572
+ incoming = _FakeIncomingMessage(999805)
1573
+ incoming.from_user = SimpleNamespace(id=banned_id)
1574
+ incoming.text = text
1575
+ asyncio.run(func(incoming))
1576
+ assert calls == [], func.__name__
1577
+ finally:
1578
+ bot._unban_user(banned_id)
1579
+
1580
+
1581
  def test_selftest_llm_head_gemini_success_counts_quota_and_outcome(monkeypatch):
1582
  # Проба Gemini как у остальных: успех в квоту и сброс счётчика карантина.
1583
  import lumen_model_speed