SilverElixir commited on
Commit
959974b
·
1 Parent(s): 217e497

Media drops are honest: fetchable attachment failure replies error, entry log for media

Browse files
Files changed (3) hide show
  1. bot.py +8 -0
  2. lumen_message_core.py +5 -0
  3. 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 = "столица Венгрии?"