Compare commits
1 Commits
master
...
fix-vin-ha
| Author | SHA1 | Date | |
|---|---|---|---|
| c29772d5e4 |
@ -72,7 +72,8 @@ env.example
|
||||
*.db
|
||||
*.sqlite3
|
||||
data/
|
||||
|
||||
@.cmd
|
||||
wiki/*
|
||||
# Бекапы и архивы
|
||||
*.bak
|
||||
*.backup
|
||||
|
||||
58
autostart.txt
Normal file
58
autostart.txt
Normal 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("Обычный старт.")
|
||||
Если хочешь, я могу помочь с конкретной логикой — например, регистрацией, проверкой в базе данных, меню и т.д.
|
||||
@ -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:
|
||||
"""
|
||||
Сохраняет или обновляет данные пользователя в базе данных
|
||||
|
||||
@ -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="🔍 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(
|
||||
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:",
|
||||
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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user