savagedb_bot/handlers/vin_handlers.py

472 lines
21 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# VIN-обработчики: декодирование, проверка, поиск фотографий
import logging
from typing import Optional, List
from aiogram import Router
from aiogram.types import Message, CallbackQuery, FSInputFile, InputMediaPhoto
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext
from database import DatabaseManager
from utils.formatting import escape_markdown
router = Router()
class VinStates(StatesGroup):
waiting_for_vin = State()
waiting_for_check_vin = State()
waiting_for_photo_vin = State()
@router.callback_query(lambda c: c.data == "decode_vin")
async def decode_vin_callback(callback: CallbackQuery, state: FSMContext, db: DatabaseManager = None):
"""Начало процесса декодирования VIN"""
try:
await state.set_state(VinStates.waiting_for_vin)
builder = InlineKeyboardBuilder()
builder.button(text="🏠 Main Menu", callback_data="main_menu")
await callback.message.edit_text(
"🔍 **VIN Decode Service**\n\nPlease enter the VIN number (17 characters):\n\nExample: `1HGBH41JXMN109186`",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
await callback.answer()
except Exception as e:
logging.error(f"Error in decode_vin_callback: {e}")
await callback.answer("Произошла ошибка", show_alert=True)
@router.callback_query(lambda c: c.data == "check_vin")
async def check_vin_callback(callback: CallbackQuery, state: FSMContext, db: DatabaseManager = None):
"""Начало процесса проверки VIN"""
try:
await state.set_state(VinStates.waiting_for_check_vin)
builder = InlineKeyboardBuilder()
builder.button(text="🏠 Main Menu", callback_data="main_menu")
await callback.message.edit_text(
"🚗 **VIN Check Service**\n\nPlease enter the VIN number to check salvage history:\n\nExample: `1HGBH41JXMN109186`",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
await callback.answer()
except Exception as e:
logging.error(f"Error in check_vin_callback: {e}")
await callback.answer("Произошла ошибка", show_alert=True)
@router.callback_query(lambda c: c.data == "search_car_photo")
async def search_car_photo_callback(callback: CallbackQuery, state: FSMContext, db: DatabaseManager = None):
"""Начало процесса поиска фотографий"""
try:
await state.set_state(VinStates.waiting_for_photo_vin)
builder = InlineKeyboardBuilder()
builder.button(text="🏠 Main Menu", callback_data="main_menu")
await callback.message.edit_text(
"📸 **Photo Search Service**\n\nPlease enter the VIN number to search for vehicle photos:\n\nExample: `1HGBH41JXMN109186`",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
await callback.answer()
except Exception as e:
logging.error(f"Error in search_car_photo_callback: {e}")
await callback.answer("Произошла ошибка", show_alert=True)
@router.callback_query(lambda c: c.data and c.data.startswith("check_vin:"))
async def check_vin_direct_callback(callback: CallbackQuery, db: DatabaseManager = None):
"""Прямая проверка VIN из кнопки после декодирования"""
try:
vin = callback.data.split(":", 1)[1]
if not vin or len(vin) != 17:
await callback.answer("Некорректный VIN", show_alert=True)
return
# Получаем информацию о VIN и количество записей
vin_info = await db.get_vin_info(vin)
salvage_records = await db.get_salvage_records(vin)
if not vin_info:
builder = InlineKeyboardBuilder()
builder.button(text="🏠 Main Menu", callback_data="main_menu")
await callback.message.edit_text(
f"❌ **VIN не найден**\n\nVIN `{escape_markdown(vin)}` не найден в базе данных.",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
await callback.answer()
return
year, make, model, engine, body_style, fuel_type = vin_info
salvage_count = len(salvage_records) if salvage_records else 0
response_text = f"🚗 **{year} {make} {model}**\n\n"
response_text += f"📊 **Records found:** {salvage_count}\n\n"
builder = InlineKeyboardBuilder()
if salvage_count > 0:
response_text += "✅ Salvage records found for this vehicle."
builder.button(text="🚗 Get Detailed Report ($2.99)", callback_data=f"pay_check_detailed:{vin}")
else:
response_text += "✅ No salvage records found for this vehicle."
builder.button(text="📸 Search Photos", callback_data=f"search_photos:{vin}")
builder.button(text="🔍 Check Another VIN", callback_data="check_vin")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
await callback.message.edit_text(
response_text,
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
await callback.answer()
except Exception as e:
logging.error(f"Error in check_vin_direct_callback: {e}")
await callback.answer("Произошла ошибка при проверке VIN", show_alert=True)
@router.callback_query(lambda c: c.data and c.data.startswith("search_photos:"))
async def search_photos_direct_callback(callback: CallbackQuery, db: DatabaseManager = None):
"""Прямой поиск фотографий из кнопки после декодирования"""
try:
vin = callback.data.split(":", 1)[1]
if not vin or len(vin) != 17:
await callback.answer("Некорректный VIN", show_alert=True)
return
# Получаем информацию о VIN и количество фотографий
vin_info = await db.get_vin_info(vin)
photo_paths = await db.get_photo_paths(vin)
if not vin_info:
builder = InlineKeyboardBuilder()
builder.button(text="🏠 Main Menu", callback_data="main_menu")
await callback.message.edit_text(
f"❌ **VIN не найден**\n\nVIN `{escape_markdown(vin)}` не найден в базе данных.",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
await callback.answer()
return
year, make, model, engine, body_style, fuel_type = vin_info
photo_count = len(photo_paths) if photo_paths else 0
response_text = f"🚗 **{year} {make} {model}**\n\n"
builder = InlineKeyboardBuilder()
if photo_count > 0:
response_text += f"📸 **{photo_count} photos** found for this vehicle."
builder.button(text="📸 Get Photos ($1.99)", callback_data=f"pay_photos:{vin}")
else:
response_text += "📸 No photos found for this vehicle."
builder.button(text="🚗 Check History", callback_data=f"check_vin:{vin}")
builder.button(text="📸 Search Another VIN", callback_data="search_car_photo")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
await callback.message.edit_text(
response_text,
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
await callback.answer()
except Exception as e:
logging.error(f"Error in search_photos_direct_callback: {e}")
await callback.answer("Произошла ошибка при поиске фотографий", show_alert=True)
@router.message(VinStates.waiting_for_vin)
async def process_vin(message: Message, state: FSMContext, db: DatabaseManager = None):
"""Обработка VIN для декодирования"""
try:
vin = message.text.strip().upper()
if len(vin) != 17:
builder = InlineKeyboardBuilder()
builder.button(text="🏠 Main Menu", callback_data="main_menu")
await message.answer(
"❌ **Invalid VIN**\n\nVIN number must be exactly 17 characters.\nPlease try again:",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
return
await state.clear()
# Получаем реальную информацию о VIN
vin_info = await db.get_vin_info(vin)
if not vin_info:
builder = InlineKeyboardBuilder()
builder.button(text="🔍 Try Another VIN", callback_data="decode_vin")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
await message.answer(
f"❌ **VIN not found**\n\nVIN `{escape_markdown(vin)}` not found in database.",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
return
year, make, model, engine, body_style, fuel_type = vin_info
# Создаем клавиатуру с действиями
builder = InlineKeyboardBuilder()
builder.button(text="🔍 Check History", callback_data=f"check_vin:{vin}")
builder.button(text="📸 Search Photos", callback_data=f"search_photos:{vin}")
builder.button(text="🔍 Decode Another VIN", callback_data="decode_vin")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
# Формируем текст ответа
response_text = f"✅ **VIN Decoded Successfully**\n\n"
response_text += f"**VIN:** `{vin}`\n"
response_text += f"**Year:** {year}\n"
response_text += f"**Make:** {make}\n"
response_text += f"**Model:** {model}\n"
if engine and str(engine) != "None":
response_text += f"**Engine:** {engine}\n"
if body_style and str(body_style) != "None":
response_text += f"**Body Style:** {body_style}\n"
if fuel_type and str(fuel_type) != "None":
response_text += f"**Fuel Type:** {fuel_type}\n"
response_text += f"\nChoose an action:"
await message.answer(
response_text,
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
except Exception as e:
logging.error(f"Error in process_vin: {e}")
await message.answer("Произошла ошибка при обработке VIN")
@router.message(VinStates.waiting_for_check_vin)
async def process_check_vin(message: Message, state: FSMContext, db: DatabaseManager = None):
"""Обработка VIN для проверки истории аварий"""
try:
vin = message.text.strip().upper()
if len(vin) != 17 or not vin.isalnum() or any(c in vin for c in ["I", "O", "Q"]):
builder = InlineKeyboardBuilder()
builder.button(text="🏠 Main Menu", callback_data="main_menu")
await message.answer(
"❌ **Invalid VIN**\n\nVIN number must be exactly 17 characters (letters and numbers, no I, O, Q).\nPlease try again:",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
return
await state.clear()
# Получаем информацию о VIN и количество записей
vin_info = await db.get_vin_info(vin)
salvage_records = await db.get_salvage_records(vin)
if not vin_info:
builder = InlineKeyboardBuilder()
builder.button(text="🔍 Try Another VIN", callback_data="check_vin")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
await message.answer(
f"❌ **VIN not found**\n\nVIN `{escape_markdown(vin)}` not found in database.",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
return
year, make, model, engine, body_style, fuel_type = vin_info
salvage_count = len(salvage_records) if salvage_records else 0
# Формируем текст ответа
response_text = f"🚗 **{year} {make} {model}**\n\n"
response_text += f"📊 **Records found in database:** {salvage_count}\n\n"
# Создаем клавиатуру в зависимости от наличия записей
builder = InlineKeyboardBuilder()
if salvage_count > 0:
response_text += "✅ Salvage records found for this vehicle."
builder.button(text="🚗 Get Detailed Report ($2.99)", callback_data=f"pay_check_detailed:{vin}")
builder.button(text="🔍 Try Another VIN", callback_data="check_vin")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
else:
response_text += " **No salvage records found for this VIN**"
builder.button(text="🔍 Try Another VIN", callback_data="check_vin")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
await message.answer(
response_text,
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
except Exception as e:
logging.error(f"Error in process_check_vin: {e}")
await message.answer("❌ Error retrieving data from database. Please try again later.")
@router.message(VinStates.waiting_for_photo_vin)
async def process_photo_vin(message: Message, state: FSMContext, db: DatabaseManager = None):
"""Обработка VIN для поиска фотографий"""
try:
vin = message.text.strip().upper()
if len(vin) != 17 or not vin.isalnum() or any(c in vin for c in ["I", "O", "Q"]):
builder = InlineKeyboardBuilder()
builder.button(text="🏠 Main Menu", callback_data="main_menu")
await message.answer(
"❌ **Invalid VIN**\n\nVIN number must be exactly 17 characters (letters and numbers, no I, O, Q).\nPlease try again:",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
return
await state.clear()
# Получаем информацию о VIN и количество фотографий
vin_info = await db.get_vin_info(vin)
photo_paths = await db.get_photo_paths(vin)
if not vin_info:
builder = InlineKeyboardBuilder()
builder.button(text="📸 Try Another VIN", callback_data="search_car_photo")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
await message.answer(
f"❌ **VIN not found**\n\nVIN `{escape_markdown(vin)}` not found in database.",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
return
year, make, model, engine, body_style, fuel_type = vin_info
photo_count = len(photo_paths) if photo_paths else 0
# Формируем ответ в зависимости от наличия фотографий
builder = InlineKeyboardBuilder()
if photo_count > 0:
# Есть фотографии - показываем информацию и кнопку оплаты
response_text = f"🚗 **{year} {make} {model}**\n\n"
response_text += f"📸 **Photo Information**\n\n"
response_text += f"🖼️ **{photo_count} damage photos** found in our database for this vehicle.\n"
response_text += f"These photos show the actual condition and damage of the vehicle during auction."
builder.button(text="📸 Get Photos ($1.99)", callback_data=f"pay_photos:{vin}")
builder.button(text="📸 Try Another VIN", callback_data="search_car_photo")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
else:
# Нет фотографий
response_text = f"🚗 **{year} {make} {model}**\n\n"
response_text += f"📸 **No damage photos found** for this VIN in our database."
builder.button(text="📸 Try Another VIN", callback_data="search_car_photo")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
await message.answer(
response_text,
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
except Exception as e:
logging.error(f"Error in process_photo_vin: {e}")
await message.answer("❌ Error retrieving data from database. Please try again later.")
async def send_vehicle_photos(message: Message, vin: str, photo_paths: List[str], make: str, model: str, year: str):
"""Отправка фотографий автомобиля пользователю"""
try:
from utils.photo_utils import prepare_photo_paths
logging.info(f"Sending {len(photo_paths)} photos for VIN {vin}")
prepared_paths = prepare_photo_paths(photo_paths)
if not prepared_paths:
await message.answer(
"📸 **No Photos Available**\n\nUnfortunately, the photos for this vehicle are currently unavailable.",
parse_mode="Markdown"
)
return
# Отправляем фотографии группами по 10
photo_batch_size = 10
total_batches = (len(prepared_paths) + photo_batch_size - 1) // photo_batch_size
for batch_num in range(total_batches):
start_idx = batch_num * photo_batch_size
end_idx = min(start_idx + photo_batch_size, len(prepared_paths))
batch_paths = prepared_paths[start_idx:end_idx]
media_group = []
for i, photo_path in enumerate(batch_paths):
try:
if batch_num == 0 and i == 0:
caption = f"📸 **Vehicle Photos**\n**VIN:** {vin}\n**Vehicle:** {year} {make} {model}\n**Photos:** {len(prepared_paths)} total"
media_group.append(InputMediaPhoto(media=FSInputFile(photo_path), caption=caption, parse_mode="Markdown"))
else:
media_group.append(InputMediaPhoto(media=FSInputFile(photo_path)))
except Exception as e:
logging.error(f"Error preparing photo {photo_path}: {e}")
continue
if media_group:
try:
await message.answer_media_group(media_group)
logging.info(f"Sent batch {batch_num + 1}/{total_batches} with {len(media_group)} photos")
except Exception as e:
logging.error(f"Error sending photo batch {batch_num + 1}: {e}")
builder = InlineKeyboardBuilder()
builder.button(text="📸 Search More Photos", callback_data="search_car_photo")
builder.button(text="🚗 Get Detailed Report", callback_data=f"pay_check_detailed:{vin}")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
await message.answer(
f"✅ **Photos sent successfully!**\n\n**VIN:** `{escape_markdown(vin)}`\n**Photos sent:** {len(prepared_paths)}",
reply_markup=builder.as_markup(),
parse_mode="Markdown"
)
except Exception as e:
logging.error(f"Error in send_vehicle_photos: {e}")
await message.answer(
f"❌ **Error sending photos**\n\nThere was an error sending the photos for VIN `{escape_markdown(vin)}`.",
parse_mode="Markdown"
)