Spaces:
Running
Running
SilverElixir commited on
Commit ·
959974b
1
Parent(s): 217e497
Media drops are honest: fetchable attachment failure replies error, entry log for media
Browse files- bot.py +8 -0
- lumen_message_core.py +5 -0
- tests/test_bot_state.py +44 -0
bot.py
CHANGED
|
@@ -1169,6 +1169,14 @@ async def handle_message(message: Message) -> None:
|
|
| 1169 |
is_private = message.chat.type == ChatType.PRIVATE if message.chat else True
|
| 1170 |
is_guest = is_guest_message(message)
|
| 1171 |
mentioned = message_mentions_bot(message)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1172 |
|
| 1173 |
# Если бот не упомянут в группе, это просто контекст — обрабатываем без локов и ожидания
|
| 1174 |
if not is_private and not is_guest and not mentioned:
|
|
|
|
| 1169 |
is_private = message.chat.type == ChatType.PRIVATE if message.chat else True
|
| 1170 |
is_guest = is_guest_message(message)
|
| 1171 |
mentioned = message_mentions_bot(message)
|
| 1172 |
+
# Входящее медиа логируем всегда: молчаливый дроп таких сообщений (прод 23.09.2026 — войс
|
| 1173 |
+
# в личке обработан за 0 мс без следа) иначе не диагностировать вообще.
|
| 1174 |
+
_in_src = _msg_media_source(message)
|
| 1175 |
+
if _in_src is not None:
|
| 1176 |
+
log.info(
|
| 1177 |
+
"[in] media message: chat=%s kind=%s has_text=%s",
|
| 1178 |
+
chat_id, type(_in_src).__name__, bool(message.text or message.caption),
|
| 1179 |
+
)
|
| 1180 |
|
| 1181 |
# Если бот не упомянут в группе, это просто контекст — обрабатываем без локов и ожидания
|
| 1182 |
if not is_private and not is_guest and not mentioned:
|
lumen_message_core.py
CHANGED
|
@@ -277,6 +277,11 @@ async def _handle_message_core(message: Message, extra_media: list[tuple[bytes,
|
|
| 277 |
clean_prompt = "Подробно перескажи и опиши содержание этого YouTube-видео."
|
| 278 |
|
| 279 |
if not clean_prompt and not media_tuple and not youtube_url_to_analyze:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
if mentioned:
|
| 281 |
await bot._tg_call(message.reply, bot._t(message.chat.id, "status_listening"))
|
| 282 |
return
|
|
|
|
| 277 |
clean_prompt = "Подробно перескажи и опиши содержание этого YouTube-видео."
|
| 278 |
|
| 279 |
if not clean_prompt and not media_tuple and not youtube_url_to_analyze:
|
| 280 |
+
src = _msg_media_source(message)
|
| 281 |
+
if src is not None and type(src).__name__ != "Sticker":
|
| 282 |
+
# Вложение было, но скачать/распознать не вышло — честно говорим, а не "Слушаю" в пустоту.
|
| 283 |
+
await bot._safe_reply(message, bot._model_error_text("fallback", bot._chat_lang(message.chat.id)))
|
| 284 |
+
return
|
| 285 |
if mentioned:
|
| 286 |
await bot._tg_call(message.reply, bot._t(message.chat.id, "status_listening"))
|
| 287 |
return
|
tests/test_bot_state.py
CHANGED
|
@@ -615,6 +615,50 @@ def test_voice_message_falls_back_to_gemini_audio_on_transcribe_failure(rate_gua
|
|
| 615 |
bot.chat_state.pop(123, None)
|
| 616 |
|
| 617 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
def test_ordinary_question_gets_no_file_notice(rate_guard_setup, monkeypatch):
|
| 619 |
message = rate_guard_setup()
|
| 620 |
message.text = "столица Венгрии?"
|
|
|
|
| 615 |
bot.chat_state.pop(123, None)
|
| 616 |
|
| 617 |
|
| 618 |
+
def test_unfetchable_attachment_gets_honest_error_not_silence(rate_guard_setup, monkeypatch):
|
| 619 |
+
# Вложение есть, а скачать не вышло — честная ошибка вместо "Слушаю" в пустоту (прод 23.09.2026).
|
| 620 |
+
message = rate_guard_setup()
|
| 621 |
+
message.text = ""
|
| 622 |
+
message.photo = [SimpleNamespace(file_id="dead", mime_type=None, file_name=None)]
|
| 623 |
+
replied = []
|
| 624 |
+
|
| 625 |
+
async def fake_resolve(message, state, clean_prompt, *, is_private):
|
| 626 |
+
return None, "", "", None
|
| 627 |
+
|
| 628 |
+
async def fake_safe_reply(message, text, **kwargs):
|
| 629 |
+
replied.append(text)
|
| 630 |
+
|
| 631 |
+
monkeypatch.setattr(bot, "_resolve_incoming_media", fake_resolve)
|
| 632 |
+
monkeypatch.setattr(bot, "_safe_reply", fake_safe_reply)
|
| 633 |
+
asyncio.run(bot._handle_message_core(message))
|
| 634 |
+
assert len(replied) == 1
|
| 635 |
+
assert "Слушаю" not in replied[0]
|
| 636 |
+
bot.chat_state.pop(123, None)
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
def test_sticker_without_text_keeps_old_listening_behavior(rate_guard_setup, monkeypatch):
|
| 640 |
+
# Стикер — не ошибка скачивания: прежнее поведение без изменений.
|
| 641 |
+
message = rate_guard_setup()
|
| 642 |
+
message.text = ""
|
| 643 |
+
Sticker = type("Sticker", (), {})
|
| 644 |
+
message.sticker = Sticker()
|
| 645 |
+
called = []
|
| 646 |
+
|
| 647 |
+
async def fake_resolve(message, state, clean_prompt, *, is_private):
|
| 648 |
+
return None, "", "", None
|
| 649 |
+
|
| 650 |
+
async def fake_tg_call(method, *args, **kwargs):
|
| 651 |
+
called.append(args[0] if args else "")
|
| 652 |
+
return SimpleNamespace()
|
| 653 |
+
|
| 654 |
+
monkeypatch.setattr(bot, "_resolve_incoming_media", fake_resolve)
|
| 655 |
+
monkeypatch.setattr(bot, "_tg_call", fake_tg_call)
|
| 656 |
+
monkeypatch.setattr(bot, "message_mentions_bot", lambda message: True)
|
| 657 |
+
asyncio.run(bot._handle_message_core(message))
|
| 658 |
+
assert any("Слушаю" in str(c) for c in called)
|
| 659 |
+
bot.chat_state.pop(123, None)
|
| 660 |
+
|
| 661 |
+
|
| 662 |
def test_ordinary_question_gets_no_file_notice(rate_guard_setup, monkeypatch):
|
| 663 |
message = rate_guard_setup()
|
| 664 |
message.text = "столица Венгрии?"
|