Spaces:
Running
Running
SilverElixir commited on
Commit ·
00f233d
1
Parent(s): fb2a2f0
Block banned users in user commands and lang callback
Browse files- lumen_commands.py +15 -1
- 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
|
| 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
|