2024-10-07 10:02:09,131 - root - INFO - Пользователь 389669884 запустил бота.
2024-10-07 10:02:09,132 - root - INFO - Пользователь 389669884 вернулся в главное меню.
2024-10-07 10:02:09,383 - root - INFO - Попытка изменить сообщение без изменений или невозможно редактировать.
2024-10-07 10:02:09,384 - aiogram.event - INFO - Update id=852506246 is handled. Duration 253 ms by bot id=8110647148


и главное меню не открыватеся при комнде /start
так, давай знаешьк ак сделаем, представь, что ты лучший в мире программист пайтон, у тебя есть вот этот код, максимально его улучши, оптимизируй, модернизируй, но чтобы функционал оставлся такой же, думай, сколько тебе влезет, я никуда не тороплюсь:

import asyncio
import logging
import aiohttp
from aiogram import Router, types, F
from aiogram.exceptions import TelegramBadRequest
from aiogram.filters.command import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import StatesGroup, State
from aiogram.utils.keyboard import InlineKeyboardBuilder
from datetime import datetime

from .api import SmsHubAPI  # Проверьте корректность пути импорта
from .data import (
    get_user_api_key,
    set_user_api_key,
    delete_user_api_key
)

EXCHANGE_RATE_API_URL = "https://api.exchangerate-api.com/v4/latest/USD"

router = Router()

async def get_usd_to_rub_rate():
    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(EXCHANGE_RATE_API_URL) as response:
                if response.status == 200:
                    data = await response.json()
                    return data['rates'].get('RUB')
                else:
                    logging.error(f"Ошибка при запросе курса валют: {response.status}")
                    return None
    except Exception as e:
        logging.error(f"Ошибка при запросе курса валют: {e}")
        return None

class UserSettings(StatesGroup):
    entering_api_key = State()

class SmsOrderState(StatesGroup):
    waiting_for_service = State()
    waiting_for_max_price = State()
    waiting_for_sms = State()

@router.message(Command(commands=['start']))
async def start_handler(message: types.Message):
    logging.info(f"Пользователь {message.from_user.id} запустил бота.")
    await show_main_menu(message)

async def show_main_menu(message: types.Message):
    logging.info(f"Пользователь {message.from_user.id} вернулся в главное меню.")
    keyboard = InlineKeyboardBuilder()
    keyboard.button(text="💰 Проверить баланс", callback_data="check_balance")
    keyboard.button(text="📲 Получить SMS", callback_data="get_sms")
    keyboard.button(text="⚙️ Настройки", callback_data="settings")

    await edit_message_text_if_changed(message, "Добро пожаловать! Выберите действие:", reply_markup=keyboard.as_markup())

async def edit_message_text_if_changed(message: types.Message, new_text: str, reply_markup=None, parse_mode=None):
    try:
        if message.text != new_text or message.reply_markup != reply_markup:
            await message.edit_text(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
    except TelegramBadRequest as e:
        if "message is not modified" in str(e) or "message can't be edited" in str(e):
            logging.info("Попытка изменить сообщение без изменений или невозможно редактировать.")
        else:
            logging.error(f"Ошибка при редактировании сообщения: {e}")
            await message.answer("Произошла ошибка при обновлении сообщения.")

@router.callback_query(F.data == "settings")
async def settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        user_api_key = await get_user_api_key(callback_query.from_user.id)
        keyboard = InlineKeyboardBuilder()

        if user_api_key:
            keyboard.button(text="✏️ Изменить API ключ", callback_data="edit_api_key")
            keyboard.button(text="❌ Удалить API ключ", callback_data="delete_api_key")
            keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
            await edit_message_text_if_changed(
                callback_query.message,
                f"Ваш текущий API ключ: `{user_api_key}`",
                reply_markup=keyboard.as_markup(),
                parse_mode="Markdown"
            )
        else:
            keyboard.button(text="❌ Отмена", callback_data="cancel_settings")
            await edit_message_text_if_changed(
                callback_query.message,
                "API ключ не настроен. Введите новый ключ:",
                reply_markup=keyboard.as_markup()
            )
            await state.set_state(UserSettings.entering_api_key)
    except TelegramBadRequest as e:
        logging.error(f"Ошибка в обработчике настроек: {e}")

@router.message(UserSettings.entering_api_key)
async def set_user_api_key_handler(message: types.Message, state: FSMContext):
    logging.info("Обработчик ввода нового API ключа активирован.")
    user_api_key = message.text.strip()
    logging.info(f"Пользователь {message.from_user.id} ввел API ключ: {user_api_key}")

    try:
        smshub_api = SmsHubAPI(user_api_key)
        balance_response = await smshub_api.get_balance()

        if balance_response and balance_response.startswith("ACCESS_BALANCE:"):
            balance = balance_response.split(":")[1]
            logging.info(f"API ключ пользователя {message.from_user.id} успешно проверен. Баланс: {balance}")
            await set_user_api_key(message.from_user.id, user_api_key)
            await message.answer("Ваш API ключ успешно сохранен и проверен!")
            await show_main_menu(message)
            await state.clear()
        else:
            logging.warning(f"Введен неверный API ключ пользователем {message.from_user.id}. Ответ: {balance_response}")
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="❌ Отмена", callback_data="cancel_settings")
            await message.answer("Неверный API ключ. Пожалуйста, попробуйте снова или убедитесь, что ваш API ключ правильный:", reply_markup=keyboard.as_markup())
    except Exception as e:
        logging.error(f"Ошибка при проверке API ключа пользователя {message.from_user.id}: {e}")
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="❌ Отмена", callback_data="cancel_settings")
        await message.answer("Произошла ошибка при проверке ключа. Пожалуйста, попробуйте снова.", reply_markup=keyboard.as_markup())

@router.callback_query(F.data == "edit_api_key")
async def edit_api_key_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="❌ Отмена", callback_data="cancel_settings")
        await edit_message_text_if_changed(
            callback_query.message,
            "Введите новый API ключ для работы с сервисом:",
            reply_markup=keyboard.as_markup()
        )
        await state.set_state(UserSettings.entering_api_key)
    except TelegramBadRequest as e:
        logging.error(f"Ошибка в обработчике редактирования API ключа: {e}")

@router.callback_query(F.data == "delete_api_key")
async def delete_api_key_handler(callback_query: types.CallbackQuery):
    try:
        await delete_user_api_key(callback_query.from_user.id)
        await edit_message_text_if_changed(
            callback_query.message,
            "API ключ успешно удалён."
        )
        await show_main_menu(callback_query.message)
    except TelegramBadRequest as e:
        logging.error(f"Ошибка в обработчике удаления API ключа: {e}")

@router.callback_query(F.data == "back_to_main_menu")
async def back_to_main_menu_handler(callback_query: types.CallbackQuery):
    try:
        await show_main_menu(callback_query.message)
    except Exception as e:
        logging.error(f"Ошибка при попытке вернуть пользователя в главное меню: {e}")
        await callback_query.message.answer("Не удалось вернуться в главное меню. Пожалуйста, попробуйте снова.")

@router.callback_query(F.data == "cancel_settings")
async def cancel_settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await state.clear()
        await edit_message_text_if_changed(
            callback_query.message,
            "Настройки отменены. Вы вернулись в главное меню."
        )
        await show_main_menu(callback_query.message)
    except TelegramBadRequest as e:
        logging.error(f"Ошибка при отмене настроек: {e}")

@router.callback_query(F.data == "check_balance")
async def balance_handler(callback_query: types.CallbackQuery):
    try:
        user_api_key = await get_user_api_key(callback_query.from_user.id)

        if not user_api_key:
            await edit_message_text_if_changed(
                callback_query.message,
                "Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
                reply_markup=callback_query.message.reply_markup
            )
            return

        smshub_api = SmsHubAPI(user_api_key)
        logging.info(f"Пользователь {callback_query.from_user.id} запросил баланс.")
        
        balance_response = await smshub_api.get_balance()
        usd_to_rub_rate = await get_usd_to_rub_rate()
        if balance_response and balance_response.startswith("ACCESS_BALANCE:"):
            balance_usd = float(balance_response.split(":")[1])
            balance_rub = balance_usd * usd_to_rub_rate if usd_to_rub_rate is not None else "невозможно получить курс"
            current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            new_message_text = f"Ваш баланс: ${balance_usd:.2f} (приблизительно ₽{balance_rub:.2f}) на {current_time}"

            await edit_message_text_if_changed(
                callback_query.message,
                new_message_text,
                reply_markup=callback_query.message.reply_markup
            )
        else:
            logging.error(f"Не удалось получить баланс. Ответ: {balance_response}")
            await edit_message_text_if_changed(
                callback_query.message,
                "Не удалось получить баланс. Попробуйте снова.", 
                reply_markup=callback_query.message.reply_markup
            )
    except TelegramBadRequest as e:
        logging.error(f"Ошибка при обработке баланса: {e}")

@router.callback_query(F.data == "get_sms")
async def get_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        user_api_key = await get_user_api_key(callback_query.from_user.id)

        if not user_api_key:
            await edit_message_text_if_changed(
                callback_query.message,
                "Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
                reply_markup=callback_query.message.reply_markup
            )
            return

        logging.info(f"Пользователь {callback_query.from_user.id} начал процесс получения номера.")
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="❌ Отмена", callback_data="cancel_service")

        await edit_message_text_if_changed(
            callback_query.message,
            "Введите сервис для получения номера (например, wa для WhatsApp):",
            reply_markup=keyboard.as_markup()
        )
        await state.set_state(SmsOrderState.waiting_for_service)
    except TelegramBadRequest as e:
        logging.error(f"Ошибка при обработке получения SMS: {e}")

@router.callback_query(F.data == "cancel_service")
async def cancel_service_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        logging.info(f"Пользователь {callback_query.from_user.id} отменил операцию.")
        await edit_message_text_if_changed(
            callback_query.message,
            "Операция отменена. Вы вернулись в главное меню."
        )
        await show_main_menu(callback_query.message)
        await state.clear()
    except TelegramBadRequest as e:
        logging.error(f"Ошибка при отмене сервиса: {e}")

@router.message(SmsOrderState.waiting_for_service)
async def service_handler(message: types.Message, state: FSMContext):
    service = message.text.strip().lower()
    logging.info(f"Пользователь {message.from_user.id} ввел сервис: {service}")

    try:
        user_api_key = await get_user_api_key(message.from_user.id)
        smshub_api = SmsHubAPI(user_api_key)
        country_code = "0"
        usd_to_rub_rate = await get_usd_to_rub_rate()

        price_info = await smshub_api.get_price_for_service(service, country=country_code)
        
        if price_info and usd_to_rub_rate is not None:
            prices = [float(price) for price in price_info.keys()]
            min_price_usd = min(prices)
            max_price_usd = max(prices)
            min_price_rub = min_price_usd * usd_to_rub_rate
            max_price_rub = max_price_usd * usd_to_rub_rate

            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="❌ Отмена", callback_data="cancel_service")

            await message.answer(
                f"Текущие цены для сервиса {service}: от ${min_price_usd:.4f} (₽{min_price_rub:.2f}) до ${max_price_usd:.4f} (₽{max_price_rub:.2f}). Введите максимальную стоимость (например, 10):",
                reply_markup=keyboard.as_markup()
            )
            await state.update_data(service=service, min_price=min_price_usd, max_price=max_price_usd)
            await state.set_state(SmsOrderState.waiting_for_max_price)
        else:
            await message.answer("Сервис недоступен или введен неверно, или не удалось получить курс валют. Попробуйте снова.")
            logging.error(f"Ошибка получения данных для сервиса '{service}' или курса валют.")
            await show_main_menu(message)
    except TelegramBadRequest as e:
        logging.error(f"Ошибка в обработчике сервиса: {e}")

@router.message(SmsOrderState.waiting_for_max_price)
async def max_price_handler(message: types.Message, state: FSMContext):
    max_price = message.text.strip()
    logging.info(f"Пользователь {message.from_user.id} указал максимальную стоимость: {max_price}")

    try:
        max_price_float = float(max_price)
    except ValueError:
        await message.answer("Пожалуйста, введите корректное числовое значение для максимальной стоимости.")
        return

    user_data = await state.get_data()
    service = user_data.get('service')
    min_price = user_data.get('min_price')
    max_price_limit = user_data.get('max_price')
    user_api_key = await get_user_api_key(message.from_user.id)

    try:
        if max_price_float < min_price:
            await message.answer(f"Максимальная стоимость должна быть хотя бы {min_price:.4f} $.")
            return

        smshub_api = SmsHubAPI(user_api_key)
        country_code = "0"

        result = await smshub_api.get_number(service, country_code, max_price=max_price_float)
        if result and result.startswith("ACCESS_NUMBER"):
            _, activation_id, number = result.split(":")
            logging.info(f"Пользователь {message.from_user.id} получил номер: {number}, активация ID: {activation_id}")
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="❌ Отмена", callback_data="cancel_sms_wait")
            await message.answer(f"Получен номер: {number}. Ожидание SMS...", reply_markup=keyboard.as_markup())
            await state.update_data(activation_id=activation_id)
            await state.set_state(SmsOrderState.waiting_for_sms)

            asyncio.create_task(poll_for_sms(message, activation_id, smshub_api, state))
        else:
            await message.answer("Не удалось получить номер. Пожалуйста, попробуйте снова.")
            logging.error(f"Неизвестный ответ при запросе номера: {result}")
            await show_main_menu(message)
    except Exception as e:
        logging.error(f"Ошибка в обработчике стоимости: {e}")

@router.callback_query(F.data == "cancel_sms_wait")
async def cancel_sms_wait_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        user_data = await state.get_data()
        activation_id = user_data.get('activation_id')
        user_api_key = await get_user_api_key(callback_query.from_user.id)

        if activation_id and user_api_key:
            smshub_api = SmsHubAPI(user_api_key)
            result = await smshub_api.cancel_activation(activation_id)
            if result and result == "ACCESS_CANCEL":
                await edit_message_text_if_changed(
                    callback_query.message,
                    "Активация успешно отменена. Вы вернулись в главное меню."
                )
                await show_main_menu(callback_query.message)
            else:
                await edit_message_text_if_changed(
                    callback_query.message,
                    "Не удалось отменить активацию. Попробуйте снова."
                )
        else:
            await edit_message_text_if_changed(
                callback_query.message,
                "Не найден ID активации для отмены."
            )

        await state.clear()
    except TelegramBadRequest as e:
        logging.error(f"Ошибка при отмене ожидания SMS: {e}")

@router.callback_query(F.data == "complete_activation")
async def complete_activation_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        user_data = await state.get_data()
        activation_id = user_data.get('activation_id')
        user_api_key = await get_user_api_key(callback_query.from_user.id)

        if activation_id and user_api_key:
            smshub_api = SmsHubAPI(user_api_key)
            result = await smshub_api.set_status(activation_id, status="6")
            if result and result == "ACCESS_ACTIVATION":
                await edit_message_text_if_changed(
                    callback_query.message,
                    "Активация успешно завершена. Вы вернулись в главное меню."
                )
                await show_main_menu(callback_query.message)
            else:
                await edit_message_text_if_changed(
                    callback_query.message,
                    "Не удалось завершить активацию. Попробуйте снова."
                )
        else:
            await edit_message_text_if_changed(
                callback_query.message,
                "Не найден ID активации для завершения."
            )

        await state.clear()
    except TelegramBadRequest as e:
        logging.error(f"Ошибка при завершении активации: {e}")

@router.callback_query(F.data == "request_another_sms")
async def request_another_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        user_data = await state.get_data()
        activation_id = user_data.get('activation_id')
        last_code = user_data.get('last_code', 'нет предыдущего кода')
        user_api_key = await get_user_api_key(callback_query.from_user.id)

        if activation_id and user_api_key:
            smshub_api = SmsHubAPI(user_api_key)
            result = await smshub_api.set_status(activation_id, status="3")
            if result and result == "ACCESS_RETRY_GET":
                keyboard = InlineKeyboardBuilder()
                keyboard.button(text="❌ Отмена", callback_data="cancel_sms_wait")
                await edit_message_text_if_changed(
                    callback_query.message,
                    f"Запрошено еще одно SMS. Ожидание SMS...\nПоследний полученный код: {last_code}",
                    reply_markup=keyboard.as_markup()
                )
                asyncio.create_task(poll_for_sms(callback_query.message, activation_id, smshub_api, state))
            else:
                await edit_message_text_if_changed(
                    callback_query.message,
                    "Не удалось запросить еще одно SMS. Попробуйте снова."
                )
        else:
            await edit_message_text_if_changed(
                callback_query.message,
                "Не найден ID активации для запроса."
            )
    except TelegramBadRequest as e:
        logging.error(f"Ошибка при запросе другого SMS: {e}")

async def poll_for_sms(message: types.Message, activation_id: str, smshub_api: SmsHubAPI, state: FSMContext):
    while True:
        await asyncio.sleep(3)

        status_response = await smshub_api.get_status(activation_id)
        
        if status_response:
            status_info = status_response.split(":", 1)
            status = status_info[0]

            if status == "STATUS_WAIT_CODE":
                logging.info(f"Waiting for SMS code for activation {activation_id}...")
                continue
            elif status.startswith("STATUS_WAIT_RETRY"):
                last_code = status_info[1] if len(status_info) > 1 else "нет предыдущего кода"
                logging.info(f"Waiting for another SMS code, last code: {last_code}")
                await state.update_data(last_code=last_code)
                continue
            elif status == "STATUS_CANCEL":
                await message.answer("Активация отменена.")
                break
            elif status.startswith("STATUS_OK"):
                code = status_info[1] if len(status_info) > 1 else "Код не получен"
                logging.info(f"Код активации получен для активации {activation_id}: {code}")
                await state.update_data(last_code=code)
                
                await offer_sms_options(message, activation_id, code)
                break
            else:
                logging.error(f"Unexpected activation status: {status_response}")
                await message.answer(f"Неизвестный статус активации: {status_response}.")
                break
        else:
            logging.error(f"Failed to get activation status: {status_response}")
            if "BAD_ACTION" in status_response or "BAD_KEY" in status_response:
                await message.answer("Не удалось получить статус активации. Пожалуйста, попробуйте позже.")
            break

async def offer_sms_options(message: types.Message, activation_id: str, code: str):
    keyboard = InlineKeyboardBuilder()
    keyboard.button(text="Завершить", callback_data="complete_activation")
    keyboard.button(text="Запросить еще одно SMS", callback_data="request_another_sms")
    keyboard.button(text="Назад в главное меню", callback_data="back_to_main_menu")
    
    await message.answer(
        f"Код активации: {code}\n\nВыберите действие для этого номера:",
        reply_markup=keyboard.as_markup()
    )