472 lines
21 KiB
Python
472 lines
21 KiB
Python
# 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"
|
||
) |