diff --git a/photo_bot/handlers.py b/photo_bot/handlers.py
index eaa72b0..f69dd4b 100644
--- a/photo_bot/handlers.py
+++ b/photo_bot/handlers.py
@@ -1,3 +1,4 @@
+# /opt/trueconf_bot/photo_bot/handlers.py
import os
import asyncio
import logging
@@ -14,19 +15,11 @@ from trueconf.types import FSInputFile
import config.config as config
from utils import states
from utils.menu import MENU_TEXT
-from photo_bot.photo_ai_checker import verify_photo_with_ai
-from utils.texts import (
- EMOJI_DIGITS,
- PHOTO_MAIN_MENU_TEXT,
- PHOTO_UNKNOWN_CMD_TEXT,
- photo_error_handling,
- PHOTO_UPLOADING_TEXT,
- photo_success_sent,
- photo_cancel_text,
- PHOTO_PROCESSING_TEXT,
- photo_preview_ready,
- photo_error_processing,
-)
+from utils.texts import * # 👈 Импортируем все тексты и константы проекта
+
+# 🔌 Бизнес-логика обработки и ИИ-валидации
+from photo_bot.photo_processor import prepare_ad_photo
+from photo_bot.ai_validator import validate_photo_ai
# 🔌 Импортируем централизованную функцию сбора статистики из main
from utils.stats_logger import log_menu_stats
@@ -47,7 +40,7 @@ def log_and_notify_photo_error(user_id: str, action: str, error_details: str):
"""Записывает системную ошибку в лог-файл и отправляет email-алерт администраторам"""
global last_email_time
- log_dir = "/opt/trueconf_bot/photo_bot/logs"
+ log_dir = os.path.dirname(os.path.abspath(__file__))
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, "photo_errors.log")
@@ -184,48 +177,76 @@ async def photo_module_handler(msg: Message):
return
else:
- await msg.answer(PHOTO_UNKNOWN_CMD_TEXT, parse_mode="html")
+ await msg.answer(system_error_text(EMOJI_DIGITS), parse_mode="html")
return
- # --- ПРИЕМ НОВОГО ФОТО (ТЕСТОВЫЙ РЕЖИМ: ТОЛЬКО ИИ) ---
+ # --- ПРИЕМ НОВОГО ФОТО ---
if is_attachment:
file_id = getattr(msg.content, "file_id", None)
- raw_data = None
+ ready_path = f"/tmp/{user_login}.bmp"
+ raw_path = f"/tmp/raw_{user_login}.webp"
- await msg.answer("🤖 Отправка фото на проверку в ИИ (srvkem-ii-03:8083)...", parse_mode="html")
+ await msg.answer(PHOTO_PROCESSING_TEXT, parse_mode="html")
try:
- downloaded_file = await states.tc_bot.download_file_by_id(file_id)
- raw_data = downloaded_file
+ # 1. Скачиваем байты файла из TrueConf
+ file_bytes = await states.tc_bot.download_file_by_id(file_id)
- # Отправляем фото напрямую в ИИ Gemma 12B Vision
- ai_result = await verify_photo_with_ai(raw_data)
+ # 2. Сохраняем байты во временный файл на диск
+ def save_raw_bytes(b_data, target_path):
+ with open(target_path, "wb") as f:
+ f.write(b_data)
- # 🛡️ Защита от неожиданного формата (гарантирует отсутствие NameError)
- if not isinstance(ai_result, dict):
- ai_result = {"is_valid": False, "reason": "Некорректный формат ответа от ИИ."}
+ await asyncio.to_thread(save_raw_bytes, file_bytes, raw_path)
- is_valid = ai_result.get("is_valid", False)
- reason = ai_result.get("reason", "Нет ответа от модели.")
+ # 3. Техническая конвертация в BMP без геометрических проверок
+ def convert_to_bmp_only(src_path, dst_path):
+ with Image.open(src_path) as img:
+ img.convert("RGB").save(dst_path, format="BMP")
+
+ await asyncio.to_thread(convert_to_bmp_only, raw_path, ready_path)
+
+ # 4. ИИ-ПРОВЕРКА ЧЕРЕЗ GEMMA 4 VISION (srvkem-ii-03)
+ ai_res = await validate_photo_ai(raw_path)
+
+ if ai_res["success"] and ai_res["approved"]:
+ # --- УСПЕШНАЯ ПРОВЕРКА ИИ ---
+ log_menu_stats(user_id, "Photo Bot", "Загрузка фото: Одобрено ИИ")
+ pending_photos[user_id] = ready_path
+ preview_path = f"/tmp/prev_{user_login}.jpg"
+
+ try:
+ Image.open(ready_path).save(preview_path, format="JPEG")
+ p_main = FSInputFile(preview_path, filename="preview.jpg")
+ p_thumb = FSInputFile(preview_path, filename="preview_thumb.jpg")
+ await msg.answer_photo(p_main, p_thumb)
+ except Exception as lib_err:
+ await asyncio.to_thread(log_and_notify_photo_error, user_id, "Генерация превью", str(lib_err))
+ finally:
+ if os.path.exists(preview_path):
+ os.remove(preview_path)
+
+ await asyncio.sleep(0.5)
+ await msg.answer(photo_preview_ready(), parse_mode="html")
- if not is_valid:
- await msg.answer(
- f"❌ [ИИ Отклонил]\n\nПричина: {reason}",
- parse_mode="html"
- )
else:
+ # --- ИИ ОТКЛОНИЛ ФОТО ИЛИ ПРОИЗОШЛА ОШИБКА ---
+ log_menu_stats(user_id, "Photo Bot", "Загрузка фото: Отклонено ИИ")
+
+ reason_text = ai_res["text"] if ai_res["success"] else ai_res.get("error", "Не удалось проверить фото через ИИ.")
+
await msg.answer(
- f"✅ [ИИ Одобрил]\n\nВердикт: Фотография полностью соответствует требованиям.",
+ f"❌ Фотография не прошла проверку\n\n{reason_text}",
parse_mode="html"
)
except Exception as e:
- logger.exception(f"Ошибка при передаче фото в ИИ: {e}")
await handle_photo_error(msg, user_id, "Скачивание и обработка файла", str(e))
finally:
- if raw_data and isinstance(raw_data, str) and os.path.exists(raw_data):
+ if raw_path and os.path.exists(raw_path):
try:
- os.remove(raw_data)
- except Exception:
- pass
+ os.remove(raw_path)
+ except Exception as cleanup_err:
+ await asyncio.to_thread(log_and_notify_photo_error, user_id, "Очистка временных файлов", str(cleanup_err))
return
+