- Импортирован модуль logging для отладки. - Закомментированы строки логирования запросов и результатов в методе fetch_vin_info. - Добавлены кнопки для взаимодействия с пользователем после успешной оплаты.
319 lines
14 KiB
Python
319 lines
14 KiB
Python
import asyncio
|
|
from os import getenv
|
|
import logging
|
|
# import json
|
|
from aiogram import Bot, Dispatcher
|
|
from aiogram.filters import Command
|
|
from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, LabeledPrice, PreCheckoutQuery
|
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
|
from aiogram.fsm.state import State, StatesGroup
|
|
from aiogram.fsm.context import FSMContext
|
|
from db import OracleDatabase
|
|
from middlewares.db import DbSessionMiddleware
|
|
|
|
|
|
if getenv("DEBUG",'0') == '1':
|
|
logging.basicConfig(level=logging.INFO)
|
|
else:
|
|
logging.basicConfig(level=logging.WARNING)
|
|
|
|
|
|
TOKEN = getenv("BOT_TOKEN")
|
|
BOTNAME = getenv("BOT_NAME")
|
|
# SALVAGEDB_TOKEN=getenv("SALVAGEDB_TOKEN",'1234567890')
|
|
|
|
oracle_db = OracleDatabase(
|
|
user= getenv("db_user"),
|
|
password= getenv("db_password"),
|
|
dsn= getenv("db_dsn")
|
|
)
|
|
|
|
|
|
dp = Dispatcher()
|
|
|
|
class VinStates(StatesGroup):
|
|
waiting_for_vin = State()
|
|
|
|
|
|
# Command handler
|
|
@dp.message(Command("start"))
|
|
async def command_start_handler(message: Message) -> None:
|
|
welcome_text = (
|
|
"Welcome to SalvagedbBot — your trusted assistant for vehicle history checks via VIN!\n\n"
|
|
"🔍 What You Can Discover:\n\n"
|
|
"• Salvage or junk status\n"
|
|
"• Damage from hail, flood, or fire\n"
|
|
"• Mileage discrepancies or odometer rollback\n"
|
|
"• Gray market vehicle status\n\n"
|
|
"We don't claim that a vehicle has a salvage title, but we provide information indicating possible past damages, helping you make informed decisions."
|
|
)
|
|
builder = InlineKeyboardBuilder()
|
|
builder.button(text="Decode VIN", callback_data="decode_vin")
|
|
builder.button(text="Check VIN", callback_data="check_vin")
|
|
builder.button(text="Search car Photo", callback_data="search_car_photo")
|
|
builder.adjust(3)
|
|
builder.button(text="Help", callback_data="help")
|
|
builder.button(text="Prices", callback_data="prices")
|
|
builder.button(text="Go Salvagedb.com", url="https://salvagedb.com")
|
|
builder.adjust(3, 2)
|
|
await message.answer(welcome_text, reply_markup=builder.as_markup())
|
|
|
|
|
|
@dp.callback_query(lambda c: c.data == "decode_vin")
|
|
async def decode_vin_callback(callback: CallbackQuery, state: FSMContext):
|
|
await callback.message.answer("Please enter the vehicle VIN.")
|
|
await state.set_state(VinStates.waiting_for_vin)
|
|
await callback.answer()
|
|
|
|
|
|
@dp.callback_query(lambda c: c.data == "main_menu")
|
|
async def main_menu_callback(callback: CallbackQuery, state: FSMContext):
|
|
await state.clear()
|
|
await command_start_handler(callback.message)
|
|
await callback.answer()
|
|
|
|
|
|
@dp.callback_query(lambda c: c.data and c.data.startswith("pay_detailed_info:"))
|
|
async def pay_detailed_info_callback(callback: CallbackQuery):
|
|
# Extract VIN from callback data
|
|
vin = callback.data.split(":")[1]
|
|
|
|
prices = [LabeledPrice(label="Detailed VIN Report", amount=1)]
|
|
|
|
await callback.bot.send_invoice(
|
|
chat_id=callback.message.chat.id,
|
|
title="Detailed VIN Information",
|
|
description="Get comprehensive vehicle detailed information for 1 Telegram Star",
|
|
payload=f"detailed_vin_info:{vin}", # Include VIN in payload
|
|
provider_token="", # Empty for Telegram Stars
|
|
currency="XTR", # Telegram Stars currency
|
|
prices=prices
|
|
)
|
|
await callback.answer()
|
|
|
|
|
|
@dp.message(VinStates.waiting_for_vin)
|
|
async def process_vin(message: Message, state: FSMContext, db: OracleDatabase):
|
|
vin = message.text.strip().upper()
|
|
if len(vin) == 17 and vin.isalnum() and all(c not in vin for c in ["I", "O", "Q"]):
|
|
try:
|
|
make, model, year, cnt = await db.fetch_vin_info(vin)
|
|
logging.info(f"Decode VIN 1st step: make: {make}, model: {model}, year: {year}, cnt: {cnt}")
|
|
response_text = f"🚗 **{year} {make} {model}**\n\n"
|
|
if cnt == 0:
|
|
response_text = "** Unable to decode VIN, possibly incorrect **"
|
|
|
|
# Create keyboard based on cnt value
|
|
builder = InlineKeyboardBuilder()
|
|
builder.button(text="Try another VIN", callback_data="decode_vin")
|
|
builder.button(text="Back to Main Menu", callback_data="main_menu")
|
|
|
|
if cnt > 9:
|
|
builder.button(text="Get detailed info. Pay 1 ⭐️", callback_data=f"pay_detailed_info:{vin}", pay=True)
|
|
builder.adjust(1, 1, 1) # Each button on separate row
|
|
else:
|
|
builder.adjust(1, 1) # Each button on separate row
|
|
|
|
await message.answer(response_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
|
|
|
except Exception as e:
|
|
logging.error(f"Database error for VIN {vin}: {e}")
|
|
await message.answer("Error retrieving data from database. Please try again later.")
|
|
await state.clear()
|
|
else:
|
|
await message.answer("Invalid VIN. Please enter a valid 17-character VIN (letters and numbers, no I, O, Q).")
|
|
|
|
|
|
@dp.pre_checkout_query()
|
|
async def pre_checkout_handler(pre_checkout_query: PreCheckoutQuery):
|
|
await pre_checkout_query.answer(ok=True)
|
|
|
|
|
|
@dp.message(lambda message: message.successful_payment)
|
|
async def successful_payment_handler(message: Message, db: OracleDatabase):
|
|
payload = message.successful_payment.invoice_payload
|
|
|
|
if payload.startswith("detailed_vin_info:"):
|
|
vin = payload.split(":")[1]
|
|
|
|
try:
|
|
# Get detailed information from database
|
|
detailed_info = await db.fetch_detailed_vin_info(vin)
|
|
|
|
if detailed_info and detailed_info['all_params']:
|
|
params = detailed_info['all_params']
|
|
|
|
# Format the detailed report with all categories
|
|
report = f"🚗 **{params.get('model_year', 'N/A')} {params.get('make', 'N/A')} {params.get('model', 'N/A')}**\n"
|
|
if params.get('trim'):
|
|
report += f"{params.get('trim')}\n"
|
|
report += "\n"
|
|
|
|
# BASIC CHARACTERISTICS
|
|
if detailed_info['basic_characteristics']:
|
|
report += "📋 **BASIC CHARACTERISTICS**\n"
|
|
for key, data in detailed_info['basic_characteristics'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# ENGINE AND POWERTRAIN
|
|
if detailed_info['engine_and_powertrain']:
|
|
report += "🔧 **ENGINE AND POWERTRAIN**\n"
|
|
for key, data in detailed_info['engine_and_powertrain'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# TRANSMISSION
|
|
if detailed_info['transmission']:
|
|
report += "⚙️ **TRANSMISSION**\n"
|
|
for key, data in detailed_info['transmission'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# ACTIVE SAFETY
|
|
if detailed_info['active_safety']:
|
|
report += "🛡️ **ACTIVE SAFETY**\n"
|
|
for key, data in detailed_info['active_safety'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# PASSIVE SAFETY
|
|
if detailed_info['passive_safety']:
|
|
report += "🚗 **PASSIVE SAFETY**\n"
|
|
for key, data in detailed_info['passive_safety'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# DIMENSIONS AND CONSTRUCTION
|
|
if detailed_info['dimensions_and_construction']:
|
|
report += "📏 **DIMENSIONS AND CONSTRUCTION**\n"
|
|
for key, data in detailed_info['dimensions_and_construction'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# BRAKE SYSTEM
|
|
if detailed_info['brake_system']:
|
|
report += "🔧 **BRAKE SYSTEM**\n"
|
|
for key, data in detailed_info['brake_system'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# LIGHTING
|
|
if detailed_info['lighting']:
|
|
report += "💡 **LIGHTING**\n"
|
|
for key, data in detailed_info['lighting'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# ADDITIONAL FEATURES
|
|
if detailed_info['additional_features']:
|
|
report += "✨ **ADDITIONAL FEATURES**\n"
|
|
for key, data in detailed_info['additional_features'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# MANUFACTURING AND LOCALIZATION
|
|
if detailed_info['manufacturing_and_localization']:
|
|
report += "🏭 **MANUFACTURING AND LOCALIZATION**\n"
|
|
for key, data in detailed_info['manufacturing_and_localization'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# NCSA DATA
|
|
if detailed_info['ncsa_data']:
|
|
report += "📊 **NCSA DATA**\n"
|
|
for key, data in detailed_info['ncsa_data'].items():
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
# TECHNICAL INFORMATION AND ERRORS
|
|
if detailed_info['technical_information_and_errors']:
|
|
report += "⚠️ **TECHNICAL INFORMATION AND ERRORS**\n"
|
|
for key, data in detailed_info['technical_information_and_errors'].items():
|
|
if data['value'] == "0":
|
|
report += f"✅ **No errors found**\n"
|
|
else:
|
|
report += f"• **{data['param_name']}:** {data['value']}\n"
|
|
report += "\n"
|
|
|
|
report += "---\n"
|
|
report += f"📋 **VIN:** {vin}\n"
|
|
report += f"💰 **Transaction ID:** {message.successful_payment.telegram_payment_charge_id}"
|
|
|
|
# Create keyboard with action buttons
|
|
builder = InlineKeyboardBuilder()
|
|
builder.button(text="Try another VIN", callback_data="decode_vin")
|
|
builder.button(text="Back to Main Menu", callback_data="main_menu")
|
|
builder.adjust(2) # Two buttons on one row
|
|
|
|
await message.answer(report, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
|
else:
|
|
# No detailed information found - refund the payment
|
|
try:
|
|
await message.bot.refund_star_payment(
|
|
user_id=message.from_user.id,
|
|
telegram_payment_charge_id=message.successful_payment.telegram_payment_charge_id
|
|
)
|
|
await message.answer(
|
|
"❌ No detailed information found for this VIN in our database.\n"
|
|
"💰 Your payment has been automatically refunded.\n"
|
|
"Please verify the VIN and try again."
|
|
)
|
|
logging.info(f"Refund successful for user {message.from_user.id} - no data found for VIN {vin}")
|
|
except Exception as refund_error:
|
|
logging.error(f"Failed to refund payment for user {message.from_user.id}: {refund_error}")
|
|
await message.answer(
|
|
"❌ No detailed information found for this VIN.\n"
|
|
"⚠️ Please contact support with this transaction ID for a refund:\n"
|
|
f"🆔 {message.successful_payment.telegram_payment_charge_id}"
|
|
)
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error getting detailed VIN info for {vin}: {e}")
|
|
|
|
# Attempt to refund the payment due to service error
|
|
try:
|
|
await message.bot.refund_star_payment(
|
|
user_id=message.from_user.id,
|
|
telegram_payment_charge_id=message.successful_payment.telegram_payment_charge_id
|
|
)
|
|
await message.answer(
|
|
"❌ Error retrieving detailed information from our database.\n"
|
|
"💰 Your payment has been automatically refunded.\n"
|
|
"Please try again later or contact support if the issue persists."
|
|
)
|
|
logging.info(f"Refund successful for user {message.from_user.id}, charge_id: {message.successful_payment.telegram_payment_charge_id}")
|
|
except Exception as refund_error:
|
|
logging.error(f"Failed to refund payment for user {message.from_user.id}: {refund_error}")
|
|
await message.answer(
|
|
"❌ Error retrieving detailed information from our database.\n"
|
|
"⚠️ Please contact support immediately with this transaction ID for a manual refund:\n"
|
|
f"🆔 {message.successful_payment.telegram_payment_charge_id}"
|
|
)
|
|
else:
|
|
await message.answer(
|
|
f"✅ Payment successful! Thank you for your purchase.\n"
|
|
f"Transaction ID: {message.successful_payment.telegram_payment_charge_id}"
|
|
)
|
|
|
|
|
|
async def on_startup():
|
|
await oracle_db.connect()
|
|
dp.message.middleware(DbSessionMiddleware(oracle_db))
|
|
|
|
|
|
async def on_shutdown():
|
|
await oracle_db.close()
|
|
|
|
|
|
# Run the bot
|
|
async def main() -> None:
|
|
bot = Bot(token=TOKEN)
|
|
dp.startup.register(on_startup)
|
|
dp.shutdown.register(on_shutdown)
|
|
await dp.start_polling(bot)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|
|
|