savagedb_bot/main.py
Vlad b1ba974b44 Добавлено логирование в класс OracleDatabase и обновлены обработчики сообщений в main.py:
- Импортирован модуль logging для отладки.
- Закомментированы строки логирования запросов и результатов в методе fetch_vin_info.
- Добавлены кнопки для взаимодействия с пользователем после успешной оплаты.
2025-05-25 02:11:35 +03:00

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())