Исправлены критические ошибки в обработчиках VIN и UserManager - добавлен метод add_user_if_not_exists, исправлены кнопки Check History и Search Photos, добавлены обработчики состояний

This commit is contained in:
Vlad 2025-06-08 19:13:51 +03:00
parent e479537d16
commit c29772d5e4
4 changed files with 468 additions and 9 deletions

View File

@ -72,7 +72,8 @@ env.example
*.db
*.sqlite3
data/
@.cmd
wiki/*
# Бекапы и архивы
*.bak
*.backup

58
autostart.txt Normal file
View File

@ -0,0 +1,58 @@
Чтобы Telegram-бот автоматически запускал логику при первом входе пользователя, нужно обрабатывать команду /start, которую Telegram автоматически отправляет боту при первом открытии чата или при нажатии на кнопку "Start".
Однако Telegram не поддерживает "автозапуск бота без действия пользователя" — пользователь обязательно должен нажать "Start". Это сделано из соображений безопасности и предотвращения спама.
Что можно реализовать:
Обработка /start, чтобы при первом входе пользователь получал приветствие или стартовую логику.
Использование force_reply или inline-кнопок для вовлечения пользователя.
Пример на Python с использованием python-telegram-bot:
python
Копировать
Редактировать
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Привет! Я твой бот. Чем могу помочь?")
if __name__ == '__main__':
app = Application.builder().token("YOUR_BOT_TOKEN").build()
app.add_handler(CommandHandler("start", start))
print("Бот запущен...")
app.run_polling()
Если нужен "автозапуск" при повторном входе:
Telegram не отправляет /start при каждом заходе, но вы можете:
Проверять, есть ли пользователь в базе, и если нет — значит это первый вход.
Или предложить пользователю в меню нажать кнопку "Начать".
Альтернатива: "Deep linking"
Вы можете создать ссылку вида:
arduino
Копировать
Редактировать
https://t.me/your_bot_username?start=welcome
При нажатии на неё, Telegram автоматически отправит:
bash
Копировать
Редактировать
/start welcome
И вы можете обрабатывать этот параметр:
python
Копировать
Редактировать
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
args = context.args
if args and args[0] == "welcome":
await update.message.reply_text("Добро пожаловать по ссылке!")
else:
await update.message.reply_text("Обычный старт.")
Если хочешь, я могу помочь с конкретной логикой — например, регистрацией, проверкой в базе данных, меню и т.д.

View File

@ -11,6 +11,80 @@ from .base import OracleDatabase
class UserManager(OracleDatabase):
"""Класс для управления пользователями"""
async def add_user_if_not_exists(self, user_id: int, username: str, first_name: str, last_name: str) -> bool:
"""
Добавляет пользователя в базу данных, если его еще нет
Упрощенная версия save_user для совместимости
"""
def _add_user():
try:
with self._get_connection() as conn:
with conn.cursor() as cur:
# Проверяем, существует ли пользователь
cur.execute("SELECT id FROM bot_users WHERE id = :user_id", {"user_id": user_id})
existing_user = cur.fetchone()
if not existing_user:
# Создаем нового пользователя
insert_query = """
INSERT INTO bot_users (
id, first_name, last_name, username, language_code,
is_bot, is_premium, added_to_attachment_menu,
registration_source, first_interaction_date,
last_interaction_date, interaction_count
) VALUES (
:user_id, :first_name, :last_name, :username, :language_code,
:is_bot, :is_premium, :added_to_attachment_menu,
:registration_source, SYSDATE, SYSDATE, 1
)
"""
params = {
"user_id": user_id,
"first_name": first_name,
"last_name": last_name,
"username": username,
"language_code": "en", # По умолчанию
"is_bot": 0,
"is_premium": 0,
"added_to_attachment_menu": 0,
"registration_source": "bot"
}
cur.execute(insert_query, params)
else:
# Обновляем данные существующего пользователя
update_query = """
UPDATE bot_users SET
first_name = :first_name,
last_name = :last_name,
username = :username,
last_interaction_date = SYSDATE,
interaction_count = interaction_count + 1
WHERE id = :user_id
"""
params = {
"user_id": user_id,
"first_name": first_name,
"last_name": last_name,
"username": username
}
cur.execute(update_query, params)
conn.commit()
return True
except Exception as e:
logging.error(f"SQL Error in add_user_if_not_exists:")
logging.error(f"User ID: {user_id}")
logging.error(f"Error: {e}")
raise
try:
return await self._execute_query(_add_user)
except Exception as e:
logging.error(f"Error adding user {user_id}: {e}")
return False
async def save_user(self, user: User, interaction_source: str = "bot") -> bool:
"""
Сохраняет или обновляет данные пользователя в базе данных

View File

@ -40,6 +40,161 @@ async def decode_vin_callback(callback: CallbackQuery, state: FSMContext, db: Da
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 для декодирования"""
@ -59,21 +214,50 @@ async def process_vin(message: Message, state: FSMContext, db: DatabaseManager =
await state.clear()
# Заглушка для декодирования VIN
# Получаем реальную информацию о VIN
vin_info = await db.get_vin_info(vin)
if not 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="🔍 Try Another VIN", callback_data="decode_vin")
builder.button(text="🏠 Main Menu", callback_data="main_menu")
builder.adjust(1)
await message.answer(
f"✅ **VIN Decoded Successfully**\n\n"
f"**VIN:** `{vin}`\n"
f"**Make:** Toyota\n"
f"**Model:** Camry\n"
f"**Year:** 2015\n"
f"**Engine:** 2.5L\n\n"
f"Choose an action:",
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"
)
@ -83,6 +267,148 @@ async def process_vin(message: Message, state: FSMContext, db: DatabaseManager =
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: