Oct 13 13:17:17 vps1707806210 systemd[1]: Started My SMS Bot Service.
Oct 13 13:17:21 vps1707806210 python[13477]: 2024-10-13 13:17:21,103 - __main__ - INFO - Запуск бота...
Oct 13 13:17:21 vps1707806210 python[13477]: 2024-10-13 13:17:21,103 - bot.utils - INFO - Бот успешно запущен.
Oct 13 13:17:21 vps1707806210 python[13477]: 2024-10-13 13:17:21,103 - aiogram.dispatcher - INFO - Start polling
Oct 13 13:17:21 vps1707806210 python[13477]: 2024-10-13 13:17:21,280 - aiogram.dispatcher - INFO - Run polling for bot @SMSHubApiBot id=7625597085 - 'SMS Hub'
Oct 13 13:17:23 vps1707806210 python[13477]: 2024-10-13 13:17:23,805 - bot.handlers.start - INFO - Пользователь 389669884 запустил бота.
Oct 13 13:17:23 vps1707806210 python[13477]: 2024-10-13 13:17:23,806 - bot.handlers.main_menu - INFO - Пользователь 389669884 вернулся в главное меню.
Oct 13 13:17:24 vps1707806210 python[13477]: 2024-10-13 13:17:24,025 - bot.utils - ERROR - Не удалось редактировать сообщение: Telegram server says - Bad Request: message can't be edited
Oct 13 13:17:24 vps1707806210 python[13477]: 2024-10-13 13:17:24,379 - aiogram.event - INFO - Update id=480218608 is handled. Duration 574 ms by bot id=7625597085
Oct 13 13:17:25 vps1707806210 python[13477]: 2024-10-13 13:17:25,228 - bot.handlers.sms - INFO - Пользователь 389669884 начал процесс получения номера.
Oct 13 13:17:25 vps1707806210 python[13477]: 2024-10-13 13:17:25,463 - aiogram.event - INFO - Update id=480218609 is handled. Duration 443 ms by bot id=7625597085
Oct 13 13:17:26 vps1707806210 python[13477]: 2024-10-13 13:17:26,660 - bot.handlers.sms - INFO - Пользователь 389669884 выбрал сервис: ya
Oct 13 13:17:27 vps1707806210 python[13477]: 2024-10-13 13:17:27,307 - bot.api - INFO - Запрос цен для сервиса: ya, страна: 0, валюта: 643
Oct 13 13:17:27 vps1707806210 python[13477]: 2024-10-13 13:17:27,485 - bot.api - INFO - Цены для сервиса ya: {'0': {'ya': {'0.0206': 18, '0.0228': 9, '0.0311': 11, '0.0396': 8, '0.0403': 121, '0.0408': 8, '0.0415': 133, '0.0416': 5604, '0.0417': 2017, '0.0448': 12, '0.0464': 10, '0.0488': 66, '0.0532': 895, '0.0555': 201, '0.0609': 29, '0.0611': 143}}}
Oct 13 13:17:27 vps1707806210 python[13477]: 2024-10-13 13:17:27,486 - bot.api - INFO - Запрос статуса номеров для страны: 0, оператор: any
Oct 13 13:17:27 vps1707806210 python[13477]: 2024-10-13 13:17:27,728 - bot.api - INFO - Статус номеров: {'aoo_0': '21673', 'pp_0': '32300', 'aon_0': '21537', 'baa_0': '18487', 'ps_0': '21785', 'aos_0': '21476', 'aor_0': '21486', 'aou_0': '21589', 'pv_0': '21623', 'aot_0': '16644', 'pw_0': '21620', 'aov_0': '21542', 'qf_0': '30893', 'qh_0': '32188', 'bax_0': '28161', 'baw_0': '28394', 'qj_0': '32235', 'apj_0': '21580', 'qp_0': '21687', 'qq_0': '32546', 'qr_0': '31564', 'qs_0': '21687', 'apq_0': '31822', 'ae_0': '27197', 'qv_0': '21847', 'qw_0': '12488', 'ah_0': '16805', 'qx_0': '22121', 'ai_0': '17408', 'apw_0': '21597', 'qz_0': '32706', 'apy_0': '21573', 'am_0': '23518', 'an_0': '28141', 'bbl_0': '18201', 'ao_0': '17358', 'bbo_0': '28625', 'aqa_0': '21675', 'rc_0': '22583', 'bbs_0': '28365', 'rd_0': '31533', 'at_0': '21788', 're_0': '32635', 'aqd_0': '21591', 'av_0': '6514', 'bbt_0': '28550', 'aw_0': '32118', 'bbv_0': '28374', 'rj_0': '32597', 'rl_0': '32456', 'rm_0': '32588', 'rn_0': '21743', 'ro_0': '21638', 'ba_0': '21528', 'rq_0': '21763', 'aqq_0': '21303', 'bd_0': '23116', 'aqr_0': '21608', 'bcf_0': '28327', 'aqt_0': '21674', 'bh_0': '21757', 'bcg_0': '28510', 'bi_0': '22171', 'bl_0': '32708', 'bm_0': '21844', 'bo_0': '32706', 'bco_0': '28460', 'sb_0': '31914', 'br_0': '16151', 'sd_0': '32472', 'bv_0': '32884', 'sg_0': '13145', 'aat_0': '21634', 'bw_0': '21707', 'sh_0': '32539', 'bz_0': '30793', 'sn_0': '31229', 'arr_0': '21603', 'bdb_0': '18057', 'st_0': '31862', 'abd_0': '15630', 'bde_0': '18135', 'ce_0': '22514', 'sv_0': '32874', 'arv_0': '21589', 'bdi_0': '18060', 'sz_0': '21791', 'abl_0': '21691', 'bdm_0': '18280', 'abk_0': '16712', 'cn_0': '32792', 'abn_0': '21617', 'abm_0': '21796', 'ta_0': '17174', 'cr_0': '21803', 'tc_0': '24537', 'asb_0': '23580', 'ct_0': '21777', 'cu_0': '21860', 'tg_0': '5260', 'th_0': '32617', 'ti_0': '31890', 'asi_0': '21502', 'cy_0': '31310', 'abw_0': '21605', 'tk_0': '31337', 'abz_0': '21861', 'ask_0': '21675', 'tl_0': '32530', 'aby_0': '21065', 'tn_0': '32611', 'da_0': '13360', 'aca_0': '16789', 'ts_0': '32610', 'dd_0': '22448', 'de_0': '32118', 'df_0': '32716', 'tv_0': '21811', 'acd_0': '16636', 'tw_0': '32661', 'acg_0': '16374', 'dh_0': '32780', 'tx_0': '32703', 'ty_0': '21677', 'dj_0': '21554', 'tz_0': '21473', 'ach_0': '16333', 'bep_0': '18718', 'dp_0': '32076', 'ua_0': '17648', 'ub_0': '5420', 'beq_0': '18766', 'ds_0': '28484', 'dt_0': '17238', 'bes_0': '18766', 'ate_0': '21675', 'acw_0': '16615', 'ui_0': '31345', 'bez_0': '18766', 'dz_0': '32649', 'uk_0': '32702', 'um_0': '21606', 'uq_0': '21638', 'ato_0': '21675', 'atq_0': '21664', 'ut_0': '21638', 'ee_0': '32226', 'uu_0': '5392', 'atu_0': '14704', 'ux_0': '21574', 'atx_0': '21674', 'en_0': '32706', 'vb_0': '21623', 'adr_0': '16589', 'es_0': '32157', 'vd_0': '32528', 'et_0': '32716', 've_0': '32001', 'adv_0': '16492', 'aug_0': '20435', 'ew_0': '31657', 'vh_0': '21514', 'auf_0': '21603', 'vi_0': '10349', 'vj_0': '21682', 'adw_0': '16460', 'vk_0': '1896', 'adz_0': '16764', 'vl_0': '21687', 'ady_0': '17028', 'vm_0': '32696', 'vp_0': '32714', 'vq_0': '21638', 'fb_0': '30353', 'fd_0': '28841', 'aee_0': '16714', 'auu_0': '21661', 'ff_0': '32312', 'aut_0': '21377', 'aeg_0': '16553', 'auv_0': '21675', 'vz_0': '22548', 'aeh_0': '17025', 'aek_0': '16792', 'aej_0': '16606', 'ael_0': '17044', 'wa_0': '13714', 'wb_0': '27230', 'wc_0': '32543', 'fs_0': '32568', 'wd_0': '31218', 'aes_0': '16727', 'ft_0': '13953', 'we_0': '34533', 'fu_0': '32784', 'aeu_0': '17238', 'fx_0': '32692', 'fy_0': '32117', 'fz_0': '32258', 'wp_0': '31829', 'gb_0': '32676', 'avr_0': '16606', 'afa_0': '17037', 'wt_0': '32560', 'wu_0': '17110', 'wx_0': '30798', 'wy_0': '21513', 'wz_0': '21684', 'gk_0': '21463', 'afl_0': '16796', 'gm_0': '32534', 'go_0': '21612', 'gp_0': '32424', 'xa_0': '21683', 'afp_0': '16732', 'afo_0': '16899', 'gr_0': '17415', 'xc_0': '21638', 'awc_0': '16606', 'gs_0': '22114', 'awb_0': '18878', 'xe_0': '32037', 'awe_0': '16594', 'awd_0': '16553', 'gw_0': '32706', 'afu_0': '16699', 'awf_0': '16605', 'gx_0': '32101', 'gz_0': '32533', 'xk_0': '32979', 'awk_0': '16503', 'xm_0': '20965', 'xn_0': '21676', 'xo_0': '21402', 'xp_0': '21624', 'hb_0': '32308', 'xr_0': '32736', 'agd_0': '24006', 'hg_0': '32608', 'aww_0': '16522', 'xx_0': '22186', 'awy_0': '16461', 'awx_0': '16519', 'ho_0': '22408', 'ya_0': '27', 'agn_0': '16904', 'yd_0': '20904', 'ye_0': '21523', 'yf_0': '32776', 'agu_0': '16688', 'agt_0': '16802', 'hw_0': '30761', 'yh_0': '21568', 'hx_0': '32521', 'axg_0': '26858', 'hz_0': '31958', 'yk_0': '31406', 'yl_0': '31858', 'ym_0': '25736', 'yo_0': '32495', 'yq_0': '21347', 'ahb_0': '16997', 'yu_0': '32549', 'ahc_0': '16631', 'ig_0': '28947', 'yw_0': '31523', 'ahe_0': '16811', 'axu_0': '26910', 'ij_0': '32468', 'yz_0': '31895', 'axy_0': '26995', 'il_0': '32435', 'ahl_0': '17249', 'im_0': '34687', 'ahk_0': '16845', 'ahn_0': '16482', 'io_0': '32402', 'ip_0': '32297', 'za_0': '31381', 'iq_0': '32622', 'ayg_0': '26990', 'iw_0': '21849', 'zh_0': '32595', 'ahx_0': '16843', 'ahz_0': '16557', 'ayk_0': '26864', 'zo_0': '32487', 'zp_0': '21656', 'ja_0': '22359', 'ayq_0': '26934', 'jc_0': '31740', 'zs_0': '32792', 'aie_0': '16848', 'jf_0': '31922', 'zx_0': '21698', 'aif_0': '16852', 'aim_0': '16705', 'ail_0': '16737', 'jo_0': '21950', 'jp_0': '21860', 'ain_0': '16858', 'jr_0': '25349', 'js_0': '21860', 'azd_0': '26859', 'jv_0': '21835', 'jy_0': '21683', 'aiy_0': '16778', 'aiz_0': '16859', 'ajb_0': '17001', 'kd_0': '21681', 'ajd_0': '15925', 'ke_0': '31677', 'ajc_0': '16544', 'azs_0': '28263', 'kf_0': '32542', 'kh_0': '21514', 'ajg_0': '16621', 'kj_0': '21783', 'kk_0': '22236', 'aji_0': '16446', 'ajl_0': '16860', 'kt_0': '32979', 'ajt_0': '16763', 'kz_0': '32642', 'akb_0': '16860', 'ake_0': '16855', 'lf_0': '32543', 'akf_0': '16787', 'li_0': '32623', 'lm_0': '21767', 'ls_0': '32651', 'lt_0': '32348', 'lv_0': '32444', 'lx_0': '32492', 'lz_0': '22186', 'ma_0': '14068', 'mb_0': '32505', 'md_0': '14581', 'me_0': '32910', 'mg_0': '20588', 'mi_0': '21698', 'mj_0': '32761', 'mm_0': '29253', 'mo_0': '22246', 'mt_0': '29099', 'mv_0': '22120', 'mw_0': '21686', 'my_0': '32455', 'mz_0': '21618', 'nc_0': '22440', 'nf_0': '32617', 'ng_0': '21449', 'ami_0': '16882', 'nj_0': '21811', 'amk_0': '16810', 'amn_0': '16737', 'nq_0': '31945', 'ns_0': '21598', 'nt_0': '20226', 'nv_0': '32592', 'nw_0': '21623', 'amy_0': '16395', 'amx_0': '16754', 'of_0': '17319', 'og_0': '31715', 'oi_0': '32081', 'oj_0': '22365', 'ok_0': '24060', 'ol_0': '32629', 'oo_0': '21687', 'anp_0': '16645', 'ot_0': '26329', 'ov_0': '21584', 'ow_0': '21560', 'aoi_0': '16502', 'pj_0': '21857', 'aoh_0': '15604', 'aok_0': '21620', 'aoj_0': '21593', 'pm_0': '32619', 'aom_0': '16538', 'pn_0': '21778', 'aol_0': '21538', 'po_0': '29162'}
Oct 13 13:17:27 vps1707806210 python[13477]: 2024-10-13 13:17:27,811 - bot.handlers.sms - ERROR - Ошибка при получении данных для сервиса 'ya': 'NoneType' object has no attribute 'message_id'
Oct 13 13:17:27 vps1707806210 python[13477]: 2024-10-13 13:17:27,889 - bot.handlers.main_menu - INFO - Пользователь 389669884 вернулся в главное меню.
Oct 13 13:17:27 vps1707806210 python[13477]: 2024-10-13 13:17:27,969 - aiogram.event - INFO - Update id=480218610 is handled. Duration 1434 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, safe_delete_message
from .main_menu import send_main_menu
from .activation import user_activations, poll_for_sms
from ..constants import OPERATOR_DISPLAY_NAMES, COUNTRIES, SERVICE_DISPLAY_NAMES

logger = logging.getLogger(__name__)

# Глобальные настройки повторных попыток
MAX_RETRIES = 3
RETRY_DELAY = 2  # секунды

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="cancel_operation", 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 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 cancel_operation_handler(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.set_state(None)
    await send_main_menu(callback_query.message, state)

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="service_page:0")
            await safe_edit_or_send_message(
                message=message,
                new_text="Пожалуйста, выберите один из представленных сервисов:",
                state=state,
                reply_markup=keyboard.as_markup()
            )
            return
        else:
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="🔙 Назад к сервисам", callback_data="service_page:0")
            await safe_edit_or_send_message(
                message=message,
                new_text="Сервис не найден. Попробуйте ввести код или полное название сервиса.",
                state=state,
                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:
        await safe_edit_or_send_message(
            message=message,
            new_text="Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
            state=state
        )
        await state.set_state(None)
        await send_main_menu(message, state)
        return

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

    loading_message = await safe_edit_or_send_message(
        message=message,
        new_text="⏳ Пожалуйста, подождите, идет обработка вашего запроса...",
        state=state
    )

    try:
        price_info = None
        for attempt in range(MAX_RETRIES):
            try:
                price_info = await smshub_api.get_price_for_service(service_code, country=user_country_code)
                if price_info:
                    break
                else:
                    logger.warning(f"Попытка {attempt + 1} не удалась, повтор через {RETRY_DELAY} секунд.")
                    await asyncio.sleep(RETRY_DELAY)
            except Exception as e:
                logger.error(f"Ошибка попытки {attempt + 1} получить информацию о ценах: {e}")
                await asyncio.sleep(RETRY_DELAY)
        else:
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="Назад", callback_data="service_page:0")
            await safe_edit_or_send_message(
                message=message,
                new_text="Не удалось получить информацию о ценах для выбранного сервиса. Пожалуйста, попробуйте другой сервис или повторите попытку позже.",
                state=state,
                reply_markup=keyboard.as_markup()
            )
            return

        numbers_status = None
        for attempt in range(MAX_RETRIES):
            try:
                numbers_status = await smshub_api.get_numbers_status(country=user_country_code, operator=user_operator)
                if numbers_status:
                    break
                else:
                    logger.warning(f"Попытка {attempt + 1} не удалась, повтор через {RETRY_DELAY} секунд.")
                    await asyncio.sleep(RETRY_DELAY)
            except Exception as e:
                logger.error(f"Ошибка попытки {attempt + 1} получить статус номеров: {e}")
                await asyncio.sleep(RETRY_DELAY)
        else:
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="Назад", callback_data="service_page:0")
            await safe_edit_or_send_message(
                message=message,
                new_text="Не удалось получить информацию о доступных номерах. Попробуйте снова позже.",
                state=state,
                reply_markup=keyboard.as_markup()
            )
            return

        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="service_page:0")
            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="service_page:0")
            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

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

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

        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):_"
        )

        sent_message = 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(last_message_id=sent_message.message_id)

        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=max_price_usd,
            min_price_rub=min_price_rub,
            max_price_rub=max_price_rub,
            user_id=user_id
        )
        await state.set_state(SmsOrderState.waiting_for_max_price)
    except Exception as e:
        logger.error(f"Ошибка при получении данных для сервиса '{service_code}': {e}")
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="Назад", callback_data="service_page:0")
        await safe_edit_or_send_message(
            message=message,
            new_text="Произошла ошибка при обработке запроса. Попробуйте снова.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
        await state.set_state(None)
        await send_main_menu(message, state)
    finally:
        if loading_message:
            await safe_delete_message(message.bot, message.chat.id, loading_message.message_id)

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

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

    user_operator = get_user_operator(user_id) or "any"
    loading_message = await safe_edit_or_send_message(
        message=message,
        new_text="⏳ Пожалуйста, подождите, идет получение номера...",
        state=state
    )

    result = None
    for attempt in range(MAX_RETRIES):
        try:
            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"):
                break
            elif not result:
                logger.warning(f"Попытка {attempt + 1} не удалась, повтор через {RETRY_DELAY} секунд.")
                await asyncio.sleep(RETRY_DELAY)
        except Exception as e:
            logger.error(f"Ошибка попытки {attempt + 1} получить номер: {e}")
            await asyncio.sleep(RETRY_DELAY)
    else:
        logger.error(f"Не удалось получить номер после {MAX_RETRIES} попыток.")
        suggestion = "Попробуйте увеличить максимальную цену или повторите попытку позже."
        await safe_edit_or_send_message(
            message=message,
            new_text=f"Не удалось получить номер для оператора {operator_display} по данной цене.\n{suggestion}",
            state=state
        )
        await send_main_menu(message, state)

    if loading_message:
        await safe_delete_message(message.bot, message.chat.id, loading_message.message_id)

    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)

        last_message_id = user_data.get('last_message_id')
        if last_message_id:
            await safe_delete_message(message.bot, message.chat.id, last_message_id)

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

        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
    else:
        logger.error(f"Не удалось получить номер. Ответ: {result}")
        suggestion = "Попробуйте увеличить максимальную цену или повторите попытку позже."
        await safe_edit_or_send_message(
            message=message,
            new_text=f"Не удалось получить номер для оператора {operator_display} по данной цене.\n{suggestion}",
            state=state
        )
        await send_main_menu(message, state)

def register_sms_handlers(dp: Dispatcher):
    dp.callback_query.register(get_sms_handler, F.data == "get_sms")
    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)