в этом коде есть ошибка, если я нажимаю получить какой-то из сервисов, появляется сообщение "Идет запрос 1/3...", все верно пока, но если я нажимаю "Отмена" под этим ообщением и появляется меню сервисов, то при нажатии повторно на какой-то сревис ничего не происходит, соообщение с сервисами при нажатии на кнопки просто как будто обновлятеся. в логах я вижу это:

Oct 14 11:28:02 vps1707806210 python[629]: 2024-10-14 11:28:02,184 - bot.handlers.sms - INFO - Пользователь 389669884 выбрал сервис: ya
Oct 14 11:28:02 vps1707806210 python[629]: 2024-10-14 11:28:02,188 - bot.handlers.sms - INFO - Запрос для пользователя 389669884 был отменен.
Oct 14 11:28:02 vps1707806210 python[629]: 2024-10-14 11:28:02,283 - bot.utils - ERROR - Ошибка при редактировании сообщения: Telegram server says - Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message.
Oct 14 11:28:02 vps1707806210 python[629]: 2024-10-14 11:28:02,415 - aiogram.event - INFO - Update id=480219580 is handled. Duration 413 ms by bot id=7625597085



import asyncio
import logging
import time
import json  # Добавьте этот импорт
from pathlib import Path
from typing import Optional, List, Tuple
from aiogram import Dispatcher, types, F
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup

# Импорт дополнительных модулей
from ..data import get_user_api_key, get_user_operator, get_user_country
from ..api import SmsHubAPI
from ..utils import get_usd_to_rub_rate, safe_edit_or_send_message
from ..constants import OPERATOR_DISPLAY_NAMES, COUNTRIES, SERVICE_DISPLAY_NAMES
from .activation import poll_for_sms, user_activations

logger = logging.getLogger(__name__)

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

SERVICES: List[Tuple[str, str]] = list(SERVICE_DISPLAY_NAMES.items())
POPULARITY_FILE = Path("service_popularity.json")

def load_popularity_data():
    if POPULARITY_FILE.exists():
        with open(POPULARITY_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {}

def save_popularity_data(popularity_data):
    with open(POPULARITY_FILE, 'w', encoding='utf-8') as f:
        json.dump(popularity_data, f, ensure_ascii=False, indent=4)

def update_service_popularity(service_code: str):
    popularity_data = load_popularity_data()
    if service_code in popularity_data:
        popularity_data[service_code] += 1
    else:
        popularity_data[service_code] = 1
    save_popularity_data(popularity_data)

def get_sorted_services():
    popularity_data = load_popularity_data()
    sorted_services = sorted(SERVICES, key=lambda service: popularity_data.get(service[0], 0), reverse=True)
    return sorted_services

def get_service_pages(services: List[Tuple[str, str]], page_size: int = 18) -> List[List[Tuple[str, str]]]:
    pages = []
    for i in range(0, len(services), page_size):
        pages.append(services[i:i + page_size])
    return pages

def build_service_keyboard(page_number: int = 0) -> InlineKeyboardBuilder:
    sorted_services = get_sorted_services()
    services_pages = get_service_pages(sorted_services)
    keyboard = InlineKeyboardBuilder()

    if page_number < len(services_pages):
        current_page = services_pages[page_number]

        for code, name in current_page:
            keyboard.button(text=name, callback_data=f"service_select:{code}")

        if page_number > 0:
            keyboard.button(text="⬅️ Назад", callback_data=f"service_page:{page_number - 1}", width=3)

        if page_number < len(services_pages) - 1:
            keyboard.button(text="➡️ Вперед", callback_data=f"service_page:{page_number + 1}", width=3)

    keyboard.button(text="🔙 Назад в меню", callback_data="back_to_main_menu", width=3)
    keyboard.adjust(3)
    return keyboard

async def get_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")

    user_api_key = get_user_api_key(callback_query.from_user.id)
    if not user_api_key:
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="⚙️ Настройки", callback_data="settings")
        keyboard.button(text="🔙 Назад в меню", callback_data="back_to_main_menu")
        keyboard.adjust(1)
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
        return

    logger.info(f"Пользователь {callback_query.from_user.id} начал процесс получения номера.")
    keyboard = build_service_keyboard()

    await state.update_data(cancel_request=False)

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=(
            "Выберите сервис, нажав на кнопку ниже.\n"
            "Если вы хотите, вы также можете ввести полное название сервиса или хотя бы часть его названия, чтобы получить результаты поиска."
        ),
        state=state,
        reply_markup=keyboard.as_markup()
    )
    await state.set_state(SmsOrderState.waiting_for_service)

async def go_back_to_service_selection(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")
    logger.info(f"Пользователь {callback_query.from_user.id} вернулся в главное меню.")
    
    await state.update_data(cancel_request=True)
    keyboard = build_service_keyboard()
    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Выберите сервис, нажав на кнопку ниже.",
        state=state,
        reply_markup=keyboard.as_markup()
    )

async def cancel_operation_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")

    await state.update_data(cancel_request=True)

    keyboard = build_service_keyboard()
    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Выберите сервис, нажав на кнопку ниже.",
        state=state,
        reply_markup=keyboard.as_markup()
    )

async def service_page_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")

    page_number = int(callback_query.data.split(":")[1])
    keyboard = build_service_keyboard(page_number)

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Выберите сервис или введите код сервиса вручную:",
        state=state,
        reply_markup=keyboard.as_markup()
    )

async def service_selected_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")

    service_code = callback_query.data.split(":")[1]
    update_service_popularity(service_code)

    await state.update_data(selected_service=service_code)
    await service_handler_logic(callback_query.message, state, user_id=callback_query.from_user.id, service_code=service_code, is_service_selection=True)

async def service_handler(message: types.Message, state: FSMContext, service_code: Optional[str] = None, user_id: Optional[int] = None):
    user_input = message.text.strip().lower()

    if user_id is None:
        user_id = message.from_user.id

    logger.info(f"Пользователь {user_id} ввел сервис: {user_input}")

    service_code = next((code for code, name in SERVICE_DISPLAY_NAMES.items() if name.lower() == user_input or code.lower() == user_input), None)

    if service_code is None:
        matched_services = [(code, name) for code, name in SERVICE_DISPLAY_NAMES.items() if user_input in name.lower()]

        if matched_services:
            keyboard = InlineKeyboardBuilder()
            for code, name in matched_services:
                keyboard.button(text=name, callback_data=f"service_select:{code}")

            keyboard.adjust(3)
            keyboard.button(text="🔙 Назад в меню", callback_data="back_to_main_menu")
            await message.answer("Пожалуйста, выберите один из представленных сервисов:", reply_markup=keyboard.as_markup())
            return
        else:
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="🔙 Назад в меню", callback_data="back_to_main_menu")
            await message.answer(
                "Сервис не найден. Попробуйте ввести код или полное название сервиса.", 
                reply_markup=keyboard.as_markup()
            )
            return
    else:
        await state.update_data(selected_service=service_code)
        await service_handler_logic(message, state, user_id=user_id, service_code=service_code)

async def service_handler_logic(message: types.Message, state: FSMContext, user_id: int, service_code: str, is_service_selection: bool = False):
    logger.info(f"Пользователь {user_id} выбрал сервис: {service_code}")

    user_api_key = get_user_api_key(user_id)
    user_operator = get_user_operator(user_id) or "any"
    user_country_code = get_user_country(user_id) or "0"
    country_name = COUNTRIES.get(user_country_code, user_country_code)

    if not user_api_key:
        keyboard = build_service_keyboard()
        await safe_edit_or_send_message(
            message=message,
            new_text="Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
        await state.set_state(None)
        return

    smshub_api = SmsHubAPI(user_api_key)
    usd_to_rub_rate = await get_usd_to_rub_rate()

    max_attempts = 3
    success = False
    last_exception = None

    for attempt in range(1, max_attempts + 1):
        data = await state.get_data()
        if data.get("cancel_request"):
            logger.info(f"Запрос для пользователя {user_id} был отменен.")
            keyboard = build_service_keyboard()
            await safe_edit_or_send_message(
                message=message,
                new_text="Выберите сервис, нажав на кнопку ниже.",
                state=state,
                reply_markup=keyboard.as_markup()
            )
            return

        try:
            progress_text = f"Идет запрос {attempt}/{max_attempts}..."
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
            keyboard.adjust(1)

            await safe_edit_or_send_message(
                message=message,
                new_text=progress_text,
                state=state,
                reply_markup=keyboard.as_markup()
            )

            price_info = await smshub_api.get_price_for_service(service_code, country=user_country_code)
            numbers_status = await smshub_api.get_numbers_status(country=user_country_code, operator=user_operator)

            if price_info and numbers_status:
                service_key = f"{service_code}_0"
                available_numbers = int(numbers_status.get(service_key, 0) or 0)
                if available_numbers == 0:
                    keyboard = InlineKeyboardBuilder()
                    keyboard.button(text="Назад в меню", callback_data="back_to_main_menu")
                    await safe_edit_or_send_message(
                        message=message,
                        new_text="Нет доступных номеров у выбранного оператора для данного сервиса. Пожалуйста, выберите другого оператора или попробуйте позже.",
                        state=state,
                        reply_markup=keyboard.as_markup()
                    )
                    return

                prices = [float(price_str) for price_str in price_info.keys() if price_str is not None]
                if not prices:
                    keyboard = InlineKeyboardBuilder()
                    keyboard.button(text="Назад в меню", callback_data="back_to_main_menu")
                    await safe_edit_or_send_message(
                        message=message,
                        new_text="Цены для выбранного сервиса недоступны. Пожалуйста, попробуйте другой сервис или повторите попытку позже.",
                        state=state,
                        reply_markup=keyboard.as_markup()
                    )
                    return

                min_price_usd = min(prices)
                max_price_usd = max(prices)
                min_price_rub = min_price_usd * usd_to_rub_rate if usd_to_rub_rate else 0
                max_price_rub = max_price_usd * usd_to_rub_rate if usd_to_rub_rate else 0

                operator_display = OPERATOR_DISPLAY_NAMES.get(user_operator, user_operator)
                service_display = SERVICE_DISPLAY_NAMES.get(service_code, service_code)

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

                message_text = (
                    f"*Страна:* `{country_name}`\n"
                    f"*Доступно номеров:* `{available_numbers}`\n"
                    f"*Оператор:* `{operator_display}`\n"
                    f"*Сервис:* `{service_display}`\n"
                    f"*Текущие цены для сервиса* `{service_display}`:\n"
                    f"от `{min_price_usd:.4f}` $ (₽{min_price_rub:.2f}) до `{max_price_usd:.4f}` $ (₽{max_price_rub:.2f}).\n\n"
                    f"_Введите максимальную стоимость в $ (например, 10):_"
                )

                await safe_edit_or_send_message(
                    message=message,
                    new_text=message_text,
                    state=state,
                    reply_markup=keyboard.as_markup(),
                    parse_mode="Markdown"
                )

                await state.update_data(
                    service=service_code,
                    service_display=service_display,
                    country_name=country_name,
                    available_numbers=available_numbers,
                    operator_display=operator_display,
                    min_price=min_price_usd,
                    max_price_usd=max_price_usd,
                    min_price_rub=min_price_rub,
                    max_price_rub=max_price_rub,
                    user_id=user_id,
                    cancel_request=False
                )

                await state.set_state(SmsOrderState.waiting_for_max_price)
                success = True
                break

        except Exception as e:
            logger.error(f"Ошибка при получении данных для сервиса '{service_code}', попытка {attempt}: {e}")
            last_exception = e
        await asyncio.sleep(5)

    if not success:
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="Назад в меню", callback_data="back_to_main_menu")
        await safe_edit_or_send_message(
            message=message,
            new_text="Не удалось получить информацию о ценах для выбранного сервиса. Пожалуйста, попробуйте другой сервис или повторите попытку позже.",
            state=state,
            reply_markup=keyboard.as_markup()
        )

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

    await message.delete()

    try:
        max_price = float(max_price_input)
    except ValueError:
        user_data = await state.get_data()
        min_price = user_data.get('min_price')
        max_price_val = user_data.get('max_price_usd')
        min_price_rub = user_data.get('min_price_rub')
        max_price_rub = user_data.get('max_price_rub')

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

        await safe_edit_or_send_message(
            message=message,
            new_text=(
                f"Пожалуйста, введите корректное число для максимальной стоимости. "
                f"От `{min_price:.4f}` $ (₽{min_price_rub:.2f}) до `{max_price_val:.4f}` $ (₽{max_price_rub:.2f})."
            ),
            state=state,
            reply_markup=keyboard.as_markup(),
            parse_mode="Markdown"
        )
        return

    user_data = await state.get_data()
    service = user_data['service']
    service_display = user_data['service_display']
    min_price = user_data['min_price']
    country_name = user_data['country_name']
    operator_display = user_data['operator_display']
    user_api_key = get_user_api_key(user_id)
    smshub_api = SmsHubAPI(user_api_key)
    user_country_code = get_user_country(user_id) or "0"

    if max_price < min_price:
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
        keyboard.adjust(1)

        await safe_edit_or_send_message(
            message=message,
            new_text=f"Максимальная стоимость должна быть не менее `{min_price:.4f}` $.",
            state=state,
            reply_markup=keyboard.as_markup(),
            parse_mode="Markdown"
        )
        return

    max_attempts = 3
    success = False
    last_exception = None

    for attempt in range(1, max_attempts + 1):
        data = await state.get_data()
        if data.get("cancel_request"):
            logger.info(f"Запрос на получение номера для пользователя {user_id} был отменен.")
            keyboard = build_service_keyboard()
            await safe_edit_or_send_message(
                message=message,
                new_text="Выберите сервис, нажав на кнопку ниже.",
                state=state,
                reply_markup=keyboard.as_markup()
            )
            return

        try:
            progress_text = f"Идет запрос {attempt}/{max_attempts} на получение номера..."
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
            keyboard.adjust(1)

            await safe_edit_or_send_message(
                message=message,
                new_text=progress_text,
                state=state,
                reply_markup=keyboard.as_markup()
            )

            user_operator = get_user_operator(user_id) or "any"
            result = await smshub_api.get_number(service, country=user_country_code, max_price=max_price, operator=user_operator)

            if result and result.startswith("ACCESS_NUMBER"):
                _, activation_id, number = result.split(":")
                logger.info(f"Пользователь {user_id} получил номер: {number}, активация ID: {activation_id}")

                if user_id not in user_activations:
                    user_activations[user_id] = {}
                user_activations[user_id][activation_id] = {
                    'number': number,
                    'service': service,
                    'service_display': service_display,
                    'country_name': country_name,
                    'status': 'Ожидание SMS',
                    'task': None,
                    'last_update': time.time()
                }

                keyboard = InlineKeyboardBuilder()
                keyboard.button(text="📋 Управлять активацией", callback_data=f"manage_activation:{activation_id}")
                keyboard.button(text="🔙 Главное меню", callback_data="back_to_main_menu")
                keyboard.adjust(1)

                message_text = (
                    f"*Получен номер:* `{number}`\n"
                    f"*Страна:* `{country_name}`\n"
                    f"*Сервис:* `{service_display}`\n"
                    f"Ожидание SMS...\n"
                    f"Вы можете управлять активацией или вернуться в главное меню."
                )

                await safe_edit_or_send_message(
                    message=message,
                    new_text=message_text,
                    state=state,
                    reply_markup=keyboard.as_markup(),
                    parse_mode="Markdown"
                )

                await state.set_state(None)

                task = asyncio.create_task(
                    poll_for_sms(
                        bot=message.bot,
                        user_id=user_id,
                        chat_id=message.chat.id,
                        activation_id=activation_id,
                        smshub_api=smshub_api
                    )
                )
                user_activations[user_id][activation_id]['task'] = task
                success = True
                break
            else:
                logger.error(f"Не удалось получить номер. Ответ: {result}")
                last_exception = result
                if result == "NO_NUMBERS":
                    await safe_edit_or_send_message(
                        message=message,
                        new_text=f"Нет доступных номеров для оператора {operator_display} по данной цене. Попробуйте выбрать 'Любой оператор' в настройках или увеличить максимальную цену.",
                        state=state
                    )
                else:
                    await safe_edit_or_send_message(
                        message=message,
                        new_text="Не удалось получить номер. Попробуйте снова.",
                        state=state
                    )
                keyboard = build_service_keyboard()
                await safe_edit_or_send_message(
                    message=message,
                    new_text="Выберите сервис, нажав на кнопку ниже.",
                    state=state,
                    reply_markup=keyboard.as_markup()
                )
                break
        except Exception as e:
            logger.error(f"Ошибка при попытке получения номера, попытка {attempt}: {e}")
            last_exception = e
        await asyncio.sleep(5)

    if not success:
        await safe_edit_or_send_message(
            message=message,
            new_text=f"Произошла ошибка при обработке запроса: {last_exception}. Попробуйте снова.",
            state=state
        )

def register_sms_handlers(dp: Dispatcher):
    dp.callback_query.register(get_sms_handler, F.data == "get_sms")
    dp.callback_query.register(go_back_to_service_selection, F.data == "back_to_main_menu")
    dp.callback_query.register(cancel_operation_handler, F.data == "cancel_operation")
    dp.callback_query.register(service_page_handler, F.data.startswith("service_page:"))
    dp.callback_query.register(service_selected_handler, F.data.startswith("service_select:"))
    dp.message.register(service_handler, SmsOrderState.waiting_for_service)
    dp.message.register(max_price_handler, SmsOrderState.waiting_for_max_price)