ms для активации 715022668 была отменена.
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,091 - bot.database - INFO - Архив активаций успешно сохранен.
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,093 - bot.utils - INFO - Updating main menu for user 389669884.
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,093 - bot.utils - ERROR - Failed to edit message: Message object is not attached to a bot instance.
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,310 - bot.api - INFO - Ответ изменения статуса: ACCESS_CANCEL
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,310 - bot.handlers.activation - INFO - Активация 715022668 отменена на стороне API: ACCESS_CANCEL
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,313 - bot.database - INFO - Архив активаций успешно сохранен.
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,420 - bot.utils - INFO - Updating main menu for user 389669884.
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,421 - bot.utils - ERROR - Failed to edit message: Message object is not attached to a bot instance.
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,422 - aiogram.event - INFO - Update id=480216563 is not handled. Duration 425 ms by bot id=7625597085
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,422 - aiogram.event - ERROR - Cause exception while process update id=480216563 by bot id=7625597085
Oct 11 20:18:01 vps1707806210 python[37476]: RuntimeError: This method is not mounted to a any bot instance, please call it explicilty with bot instance `await bot(method)`
Oct 11 20:18:01 vps1707806210 python[37476]: or mount method to a bot instance `method.as_(bot)` and then call it `await method()`
Oct 11 20:18:01 vps1707806210 python[37476]: Traceback (most recent call last):
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/bot/utils.py", line 41, in safe_edit_or_send_message
Oct 11 20:18:01 vps1707806210 python[37476]:     raise RuntimeError("Message object is not attached to a bot instance.")
Oct 11 20:18:01 vps1707806210 python[37476]: RuntimeError: Message object is not attached to a bot instance.
Oct 11 20:18:01 vps1707806210 python[37476]: During handling of the above exception, another exception occurred:
Oct 11 20:18:01 vps1707806210 python[37476]: Traceback (most recent call last):
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/dispatcher.py", line 309, in _process_update
Oct 11 20:18:01 vps1707806210 python[37476]:     response = await self.feed_update(bot, update, **kwargs)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/dispatcher.py", line 158, in feed_update
Oct 11 20:18:01 vps1707806210 python[37476]:     response = await self.update.wrap_outer_middleware(
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/middlewares/error.py", line 25, in __call__
Oct 11 20:18:01 vps1707806210 python[37476]:     return await handler(event, data)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/middlewares/user_context.py", line 56, in __call__
Oct 11 20:18:01 vps1707806210 python[37476]:     return await handler(event, data)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/fsm/middleware.py", line 42, in __call__
Oct 11 20:18:01 vps1707806210 python[37476]:     return await handler(event, data)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/event/telegram.py", line 121, in trigger
Oct 11 20:18:01 vps1707806210 python[37476]:     return await wrapped_inner(event, kwargs)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/event/handler.py", line 43, in call
Oct 11 20:18:01 vps1707806210 python[37476]:     return await wrapped()
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/dispatcher.py", line 276, in _listen_update
Oct 11 20:18:01 vps1707806210 python[37476]:     return await self.propagate_event(update_type=update_type, event=event, **kwargs)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/router.py", line 146, in propagate_event
Oct 11 20:18:01 vps1707806210 python[37476]:     return await observer.wrap_outer_middleware(_wrapped, event=event, data=kwargs)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/router.py", line 141, in _wrapped
Oct 11 20:18:01 vps1707806210 python[37476]:     return await self._propagate_event(
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/router.py", line 166, in _propagate_event
Oct 11 20:18:01 vps1707806210 python[37476]:     response = await observer.trigger(event, **kwargs)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/event/telegram.py", line 121, in trigger
Oct 11 20:18:01 vps1707806210 python[37476]:     return await wrapped_inner(event, kwargs)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/dispatcher/event/handler.py", line 43, in call
Oct 11 20:18:01 vps1707806210 python[37476]:     return await wrapped()
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/bot/handlers/activation.py", line 343, in cancel_activation_handler
Oct 11 20:18:01 vps1707806210 python[37476]:     await update_main_menu(message=message, state=None)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/bot/utils.py", line 85, in update_main_menu
Oct 11 20:18:01 vps1707806210 python[37476]:     await safe_edit_or_send_message(
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/bot/utils.py", line 58, in safe_edit_or_send_message
Oct 11 20:18:01 vps1707806210 python[37476]:     sent_message = await message.answer(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/methods/base.py", line 89, in __await__
Oct 11 20:18:01 vps1707806210 python[37476]:     raise RuntimeError(
Oct 11 20:18:01 vps1707806210 python[37476]: RuntimeError: This method is not mounted to a any bot instance, please call it explicilty with bot instance `await bot(method)`
Oct 11 20:18:01 vps1707806210 python[37476]: or mount method to a bot instance `method.as_(bot)` and then call it `await method()`
Oct 11 20:18:01 vps1707806210 python[37476]: 2024-10-11 20:18:01,428 - asyncio - ERROR - Task exception was never retrieved
Oct 11 20:18:01 vps1707806210 python[37476]: future: <Task finished name='Task-39' coro=<poll_for_sms() done, defined at /root/sms/bot/handlers/activation.py:58> exception=RuntimeError('This method is not mounted to a any bot instance, please call it explicilty with bot instance `await bot(method)`\nor mount method to a bot instance `method.as_(bot)` and then call it `await method()`')>
Oct 11 20:18:01 vps1707806210 python[37476]: Traceback (most recent call last):
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/bot/handlers/activation.py", line 70, in poll_for_sms
Oct 11 20:18:01 vps1707806210 python[37476]:     await asyncio.sleep(3)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/usr/lib/python3.10/asyncio/tasks.py", line 605, in sleep
Oct 11 20:18:01 vps1707806210 python[37476]:     return await future
Oct 11 20:18:01 vps1707806210 python[37476]: asyncio.exceptions.CancelledError
Oct 11 20:18:01 vps1707806210 python[37476]: During handling of the above exception, another exception occurred:
Oct 11 20:18:01 vps1707806210 python[37476]: Traceback (most recent call last):
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/bot/utils.py", line 41, in safe_edit_or_send_message
Oct 11 20:18:01 vps1707806210 python[37476]:     raise RuntimeError("Message object is not attached to a bot instance.")
Oct 11 20:18:01 vps1707806210 python[37476]: RuntimeError: Message object is not attached to a bot instance.
Oct 11 20:18:01 vps1707806210 python[37476]: During handling of the above exception, another exception occurred:
Oct 11 20:18:01 vps1707806210 python[37476]: Traceback (most recent call last):
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/bot/handlers/activation.py", line 145, in poll_for_sms
Oct 11 20:18:01 vps1707806210 python[37476]:     await update_main_menu(message=message, state=None)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/bot/utils.py", line 85, in update_main_menu
Oct 11 20:18:01 vps1707806210 python[37476]:     await safe_edit_or_send_message(
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/bot/utils.py", line 58, in safe_edit_or_send_message
Oct 11 20:18:01 vps1707806210 python[37476]:     sent_message = await message.answer(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
Oct 11 20:18:01 vps1707806210 python[37476]:   File "/root/sms/venv/lib/python3.10/site-packages/aiogram/methods/base.py", line 89, in __await__
Oct 11 20:18:01 vps1707806210 python[37476]:     raise RuntimeError(
Oct 11 20:18:01 vps1707806210 python[37476]: RuntimeError: This method is not mounted to a any bot instance, please call it explicilty with bot instance `await bot(method)`
Oct 11 20:18:01 vps1707806210 python[37476]: or mount method to a bot instance `method.as_(bot)` and then call it `await method()`
^C




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


import asyncio
import logging
from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.memory import MemoryStorage

from config.config import TELEGRAM_TOKEN
from bot.handlers import register_handlers
from bot.utils import on_startup
from bot.database import clean_archive
from aiogram.client.bot import DefaultBotProperties

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)

async def main():
    # Инициализация бота и диспетчера
    bot = Bot(token=TELEGRAM_TOKEN, default=DefaultBotProperties(parse_mode='HTML'))
    storage = MemoryStorage()
    dp = Dispatcher(storage=storage)

    # Регистрация обработчиков
    register_handlers(dp)

    # Регистрация функции on_startup
    dp.startup.register(on_custom_startup)

    try:
        logger.info("Запуск бота...")
        await dp.start_polling(bot)
    finally:
        await bot.close()

async def on_custom_startup(dispatcher: Dispatcher):
    """Функция, выполняемая при старте бота."""
    logger.info("Бот успешно запущен.")
    clean_archive()

if __name__ == "__main__":
    asyncio.run(main())


import logging
import asyncio
import time
from typing import Dict, List, Any
from aiogram import Dispatcher, types, F, Bot
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.context import FSMContext
from aiogram.types import Message, User, Chat

from bot.database import get_user_api_key, archive_activation, activation_archive
from bot.api import SmsHubAPI
from bot.constants.services import SERVICE_DISPLAY_NAMES
from bot.utils import safe_edit_or_send_message, safe_delete_message

logger = logging.getLogger(__name__)

user_activations: Dict[str, Dict[str, Dict[str, Any]]] = {}

def initialize_activation(user_id, activation_id, number, service, service_display):
    """Инициализация активации, добавляющей запись в словарь активаций."""
    if user_id not in user_activations:
        user_activations[user_id] = {}
    if activation_id not in user_activations[user_id]:
        user_activations[user_id][activation_id] = {
            'number': number,
            'service': service,
            'service_display': service_display,
            'status_history': [],
            'task': None,
            'current_status': 'Ожидание SMS',
            'last_update': time.time()
        }
    logger.info(f"Инициализирована активация {activation_id} для пользователя {user_id}.")

def add_status_to_history(activation, new_status: str):
    """Добавление значительных изменений статуса в историю активации."""
    if 'status_history' not in activation:
        activation['status_history'] = []

    current_history = activation['status_history']
    current_status = activation['current_status']

    if new_status.startswith("STATUS_WAIT_CODE") and current_status != "Ожидание SMS":
        activation['current_status'] = "Ожидание SMS"
        if not current_history or current_history[-1] != "Ожидание SMS":
            current_history.append("Ожидание SMS")
    elif new_status.startswith("STATUS_WAIT_RETRY"):
        _, last_code = new_status.split(":", 1)
        activation['current_status'] = f"Ожидание повторного SMS (последний код: {last_code})"
    elif new_status.startswith("STATUS_OK"):
        _, code = new_status.split(":", 1)
        activation['current_status'] = f"Код получен: {code}"
        current_history.append(f"Код получен: {code}")
    elif new_status == "STATUS_CANCEL":
        activation['current_status'] = "Активация отменена"
        current_history.append("Активация отменена")

async def poll_for_sms(bot: Bot, user_id: int, chat_id: int, activation_id: str, smshub_api: SmsHubAPI):
    from bot.utils import update_main_menu  # Отложенный импорт для предотвращения циклического импорта

    activations = user_activations.get(user_id, {})
    activation = activations.get(activation_id)

    if not activation:
        logger.error(f"Активация {activation_id} не найдена для пользователя {user_id}.")
        return

    try:
        while True:
            await asyncio.sleep(3)

            status_response = await smshub_api.get_status(activation_id)
            if not status_response:
                logger.error("Нет ответа при запросе статуса активации.")
                task = activation.pop('task', None)
                if task:
                    task.cancel()
                archive_activation(user_id, activation_id, activation)
                del activations[activation_id]
                await bot.send_message(chat_id, "Не удалось получить статус активации. Попробуйте позже.")
                user = User(id=user_id, is_bot=False, first_name="User")
                chat = Chat(id=chat_id, type="private")
                message = Message(message_id=1, date=int(time.time()), chat=chat, from_user=user, text="Обновление")
                await update_main_menu(message=message, state=None)
                return

            add_status_to_history(activation, status_response)

            if status_response.startswith("STATUS_WAIT_CODE"):
                logger.debug(f"Ожидание SMS кода для активации {activation_id}.")
                activation['last_update'] = time.time()
                continue

            elif status_response.startswith("STATUS_WAIT_RETRY"):
                _, last_code = status_response.split(":", 1)
                logger.info(f"Ожидание повторного SMS кода, последний код: {last_code}")
                activation['last_update'] = time.time()
                continue

            elif status_response.startswith("STATUS_CANCEL"):
                logger.info(f"Активация {activation_id} была отменена.")
                task = activation.pop('task', None)
                if task:
                    task.cancel()
                add_status_to_history(activation, 'Активация отменена')
                archive_activation(user_id, activation_id, activation)
                del activations[activation_id]
                await bot.send_message(chat_id, "Активация была отменена.")
                user = User(id=user_id, is_bot=False, first_name="User")
                chat = Chat(id=chat_id, type="private")
                message = Message(message_id=1, date=int(time.time()), chat=chat, from_user=user, text="Обновление")
                await update_main_menu(message=message, state=None)
                return

            elif status_response.startswith("STATUS_OK"):
                _, code = status_response.split(":", 1)
                logger.info(f"Получен код активации для {activation_id}: {code}")
                add_status_to_history(activation, f'Код получен: {code}')
                task = activation.pop('task', None)
                if task:
                    task.cancel()
                archive_activation(user_id, activation_id, activation)
                del activations[activation_id]
                await bot.send_message(
                    chat_id,
                    f"Код активации для номера `{activation['number']}`: `{code}`",
                    parse_mode="Markdown"
                )
                user = User(id=user_id, is_bot=False, first_name="User")
                chat = Chat(id=chat_id, type="private")
                message = Message(message_id=1, date=int(time.time()), chat=chat, from_user=user, text="Обновление")
                await update_main_menu(message=message, state=None)
                return

    except asyncio.CancelledError:
        logger.info(f"Задача poll_for_sms для активации {activation_id} была отменена.")
        task = activation.pop('task', None)
        if task:
            task.cancel()
        add_status_to_history(activation, 'Активация отменена')
        archive_activation(user_id, activation_id, activation)
        user = User(id=user_id, is_bot=False, first_name="User")
        chat = Chat(id=chat_id, type="private")
        message = Message(message_id=1, date=int(time.time()), chat=chat, from_user=user, text="Обновление")
        await update_main_menu(message=message, state=None)

    except Exception as e:
        logger.error(f"Ошибка в poll_for_sms: {e}")
        add_status_to_history(activation, 'Ошибка при получении SMS')
        await bot.send_message(chat_id, "Произошла ошибка при получении SMS.")
        task = activation.pop('task', None)
        if task:
            task.cancel()
        archive_activation(user_id, activation_id, activation)
        user = User(id=user_id, is_bot=False, first_name="User")
        chat = Chat(id=chat_id, type="private")
        message = Message(message_id=1, date=int(time.time()), chat=chat, from_user=user, text="Обновление")
        await update_main_menu(message=message, state=None)

def paginate_activations(activations: Dict[str, Any], page_number: int, page_size: int = 10) -> List[Dict[str, Any]]:
    """Функция для пагинации активаций для отображения пользователю."""
    start_idx = page_number * page_size
    end_idx = start_idx + page_size
    items = list(activations.items())[start_idx:end_idx]
    return items

async def current_activations_handler(callback_query: types.CallbackQuery, state: FSMContext):
    """Обработчик запроса текущих активаций для конкретного пользователя."""
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на запрос callback: {e}")

    user_id = str(callback_query.from_user.id)
    page_number = int(callback_query.data.split(":")[1]) if ":" in callback_query.data else 0
    activations = user_activations.get(user_id, {})
    keyboard = InlineKeyboardBuilder()

    if not activations:
        keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
        keyboard.adjust(1)
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="У вас нет активных активаций.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
        return

    paginated_activations = paginate_activations(activations, page_number)
    for activation_id, data in paginated_activations:
        service_display_name = SERVICE_DISPLAY_NAMES.get(data['service'], data['service'])
        current_status = data['current_status']
        keyboard.button(
            text=f"{service_display_name} ({data['number']}) - {current_status}",
            callback_data=f"manage_activation:{activation_id}"
        )

    if page_number > 0:
        keyboard.button(text="⬅️", callback_data=f"current_activations:{page_number - 1}")
    if len(activations) > (page_number + 1) * 10:
        keyboard.button(text="➡️", callback_data=f"current_activations:{page_number + 1}")

    keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
    keyboard.adjust(1)

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Ваши текущие активации:",
        state=state,
        reply_markup=keyboard.as_markup()
    )

async def manage_activation_handler(callback_query: types.CallbackQuery, state: FSMContext):
    """Обработчик управления активацией. Позволяет видеть детали и управлять активацией."""
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на запрос callback: {e}")

    user_id = str(callback_query.from_user.id)
    activation_id = callback_query.data.split(":")[1]
    activations = user_activations.get(user_id, {})
    activation = activations.get(activation_id)

    if not activation:
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Активация не найдена.",
            state=state,
            reply_markup=callback_query.message.reply_markup
        )
        return

    keyboard = InlineKeyboardBuilder()

    current_status = activation['current_status']
    if current_status == 'Ожидание SMS':
        keyboard.button(text="❌ Отменить активацию", callback_data=f"cancel_activation:{activation_id}")
    elif current_status.startswith('Код получен') or current_status.startswith('Ожидание повторного SMS'):
        keyboard.button(text="✅ Завершить активацию", callback_data=f"complete_activation:{activation_id}")
        keyboard.button(text="❌ Отменить активацию", callback_data=f"cancel_activation:{activation_id}")

    keyboard.button(text="История статусов", callback_data=f"status_history:{activation_id}")
    keyboard.button(text="🔙 Назад", callback_data=f"current_activations")
    keyboard.adjust(1)

    service_display_name = SERVICE_DISPLAY_NAMES.get(activation['service'], activation['service'])

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=(
            f"*Сервис:* `{service_display_name}`\n"
            f"*Номер:* `{activation['number']}`\n"
            f"*Статус:* `{current_status}`"
        ),
        state=state,
        reply_markup=keyboard.as_markup(),
        parse_mode="Markdown"
    )

async def handle_status_history(callback_query: types.CallbackQuery, state: FSMContext):
    """Обработчик отображения истории статусов для выбранной активации."""
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на запрос callback: {e}")

    user_id = str(callback_query.from_user.id)
    activation_id = callback_query.data.split(":")[1]
    activations = user_activations.get(user_id, {})
    activation = activations.get(activation_id)

    if not activation:
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="История статусов не найдена.",
            state=state,
            reply_markup=callback_query.message.reply_markup
        )
        return

    status_history = activation.get('status_history', [])
    status_history_text = "\n".join(
        [f"{i + 1}. {status}" for i, status in enumerate(status_history)]
    )

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=(
            f"*История статусов активации:*\n{status_history_text or 'Нет статусов'}"
        ),
        state=state,
        reply_markup=callback_query.message.reply_markup,
        parse_mode="Markdown"
    )

async def cancel_activation_handler(callback_query: types.CallbackQuery, state: FSMContext):
    """Обработчик отмены активации."""
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на запрос callback: {e}")

    user_id = str(callback_query.from_user.id)
    activation_id = callback_query.data.split(":")[1]
    user_api_key = get_user_api_key(user_id)
    smshub_api = SmsHubAPI(user_api_key)

    activations = user_activations.get(user_id, {})
    activation = activations.get(activation_id)

    if activation:
        task = activation.pop('task', None)
        if task:
            task.cancel()

        result = await smshub_api.cancel_activation(activation_id)
        logger.info(f"Активация {activation_id} отменена на стороне API: {result}")

        if 'status_history' not in activation:
            activation['status_history'] = []
        add_status_to_history(activation, 'Активация отменена')
        archive_activation(user_id, activation_id, activation)

        del activations[activation_id]

        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="🔙 Назад", callback_data="current_activations")
        keyboard.adjust(1)

        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Активация успешно отменена.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
        from bot.utils import update_main_menu
        chat_id = callback_query.message.chat.id  # исправлено: chat_id определяется из данных callback_query
        user = User(id=user_id, is_bot=False, first_name="User")
        chat = Chat(id=chat_id, type="private")
        message = Message(message_id=1, date=int(time.time()), chat=chat, from_user=user, text="Обновление")
        await update_main_menu(message=message, state=None)
    else:
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Активация не найдена.",
            state=state,
            reply_markup=callback_query.message.reply_markup
        )

async def activation_archive_handler(callback_query: types.CallbackQuery, state: FSMContext):
    """Обработчик запроса архивированных активаций для конкретного пользователя."""
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на запрос callback: {e}")

    user_id = str(callback_query.from_user.id)
    page_number = int(callback_query.data.split(":")[1]) if ":" in callback_query.data else 0
    archive = activation_archive.get(user_id, {})
    keyboard = InlineKeyboardBuilder()

    if not archive:
        keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
        keyboard.adjust(1)
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Ваш архив активаций пуст.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
        return

    paginated_archive = paginate_activations(archive, page_number)
    for activation_id, data in paginated_archive:
        service_display_name = SERVICE_DISPLAY_NAMES.get(data['service'], data['service'])
        latest_status = data['status_history'][-1] if 'status_history' in data and data['status_history'] else 'Нет статуса'
        keyboard.button(
            text=f"{service_display_name} ({data['number']}) - {latest_status}",
            callback_data=f"archived_activation:{activation_id}"
        )

    if page_number > 0:
        keyboard.button(text="⬅️", callback_data=f"activation_archive:{page_number - 1}")
    if len(archive) > (page_number + 1) * 10:
        keyboard.button(text="➡️", callback_data=f"activation_archive:{page_number + 1}")

    keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
    keyboard.adjust(1)

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Ваши архивированные активации:",
        state=state,
        reply_markup=keyboard.as_markup()
    )

async def archived_activation_handler(callback_query: types.CallbackQuery, state: FSMContext):
    """Обработчик открытия архивированной активации с полной историей статусов."""
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на запрос callback: {e}")

    user_id = str(callback_query.from_user.id)
    activation_id = callback_query.data.split(":")[1]
    archive = activation_archive.get(user_id, {})
    activation = archive.get(activation_id)

    if not activation:
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Активация не найдена в архиве.",
            state=state,
            reply_markup=callback_query.message.reply_markup
        )
        return

    service_display_name = SERVICE_DISPLAY_NAMES.get(activation['service'], activation['service'])
    status_history_text = "\n".join(
        [f"{i + 1}. {status}" for i, status in enumerate(activation.get('status_history', []))]
    )

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=(
            f"*Сервис:* `{service_display_name}`\n"
            f"*Номер:* `{activation['number']}`\n"
            f"*История статусов:*\n{status_history_text or 'Нет статусов'}"
        ),
        state=state,
        reply_markup=callback_query.message.reply_markup,
        parse_mode="Markdown"
    )

def register_activation_handlers(dp: Dispatcher):
    """Регистрация всех обработчиков, связанных с активациями и их управлением."""
    dp.callback_query.register(current_activations_handler, F.data.startswith("current_activations"))
    dp.callback_query.register(manage_activation_handler, F.data.startswith("manage_activation"))
    dp.callback_query.register(cancel_activation_handler, F.data.startswith("cancel_activation"))
    dp.callback_query.register(activation_archive_handler, F.data.startswith("activation_archive"))
    dp.callback_query.register(archived_activation_handler, F.data.startswith("archived_activation"))
    dp.callback_query.register(handle_status_history, F.data.startswith("status_history"))


import logging
from aiogram import Dispatcher, types, F
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.context import FSMContext
from datetime import datetime

from ..database import get_user_api_key
from ..api import SmsHubAPI
from ..utils import get_usd_to_rub_rate, safe_edit_or_send_message

logger = logging.getLogger(__name__)

async def balance_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer 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

    smshub_api = SmsHubAPI(user_api_key)
    logger.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 else "недоступно"
        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        message_text = (
            f"Ваш баланс на {current_time}:\n"
            f"`{balance_usd:.4f}`$ (приблизительно `{balance_rub:.2f}`₽)"
        )
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="🔄 Обновить баланс", callback_data="check_balance")
        keyboard.button(text="🔙 Вернуться в меню", callback_data="back_to_main_menu")
        keyboard.adjust(1)
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text=message_text,
            state=state,
            reply_markup=keyboard.as_markup(),
            parse_mode="Markdown"
        )
    else:
        logger.error(f"Не удалось получить баланс. Ответ: {balance_response}")
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Не удалось получить баланс. Попробуйте снова.",
            state=state,
            reply_markup=callback_query.message.reply_markup
        )

def register_balance_handlers(dp: Dispatcher):
    dp.callback_query.register(balance_handler, F.data == "check_balance")


import logging
from aiogram import Dispatcher, types

logger = logging.getLogger(__name__)

async def errors_handler(update: types.Update, exception: Exception):
    logger.exception(f"Exception occurred: {exception}")
    return True

def register_error_handlers(dp: Dispatcher):
    dp.errors.middleware(errors_handler)


# /bot/handlers/main_menu.py

import logging
from aiogram import types
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.context import FSMContext
from bot.utils import safe_edit_or_send_message
from bot.handlers.activation import user_activations
from bot.database import activation_archive

logger = logging.getLogger(__name__)

async def update_main_menu(message: types.Message, state: FSMContext):
    """Обновляет главное меню для отображения текущего количества активаций."""
    logger.info(f"Обновление главного меню для пользователя {message.chat.id}.")
    user_id = str(message.chat.id)
    current_count = len(user_activations.get(user_id, {}))
    archived_count = len(activation_archive.get(user_id, {}))

    keyboard = InlineKeyboardBuilder()
    keyboard.button(text="💰 Проверить баланс", callback_data="check_balance")
    keyboard.button(text="📲 Получить SMS", callback_data="get_sms")
    keyboard.button(text=f"📋 Текущие активации ({current_count})", callback_data="current_activations")
    keyboard.button(text=f"🗃️ Архив активаций ({archived_count})", callback_data="activation_archive")
    keyboard.button(text="⚙️ Настройки", callback_data="settings")
    keyboard.adjust(1)

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

async def send_main_menu(message: types.Message, state: FSMContext):
    """Отправляет пользователю главное меню."""
    await update_main_menu(message, state)


# /bot/handlers/settings.py

import logging
from aiogram import Dispatcher, types, F
from aiogram.fsm.context import FSMContext
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.state import State, StatesGroup

from ..database import (
    get_user_api_key,
    set_user_api_key,
    delete_user_api_key,
    set_user_operator,
    get_user_operator,
    set_user_country,
    get_user_country
)
from ..constants.operators import OPERATOR_DISPLAY_NAMES
from ..constants.countries import COUNTRIES
from .main_menu import send_main_menu
from ..api import SmsHubAPI
from ..utils import get_usd_to_rub_rate, safe_edit_or_send_message, safe_delete_message

logger = logging.getLogger(__name__)

class UserSettings(StatesGroup):
    entering_api_key = State()

class OperatorSettings(StatesGroup):
    selecting_operator = State()

class CountrySettings(StatesGroup):
    selecting_country = State()

async def settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

async def show_settings_menu(callback_query: types.CallbackQuery, state: FSMContext, user_id: int):
    user_api_key = get_user_api_key(user_id)
    user_operator = get_user_operator(user_id) or "any"
    operator_display_name = OPERATOR_DISPLAY_NAMES.get(user_operator, user_operator)
    user_country_code = get_user_country(user_id) or "0"
    country_display_name = COUNTRIES.get(user_country_code, user_country_code)

    keyboard = InlineKeyboardBuilder()
    if user_api_key:
        keyboard.button(text="✏️ Изменить API ключ", callback_data="edit_api_key")
        keyboard.button(text="❌ Удалить API ключ", callback_data="delete_api_key")
    else:
        keyboard.button(text="➕ Добавить API ключ", callback_data="add_api_key")

    keyboard.button(text="✏️ Изменить оператора", callback_data="edit_operator")
    keyboard.button(text="✏️ Изменить страну", callback_data="edit_country")
    keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
    keyboard.adjust(1)

    message_text = (
        f"Ваш текущий API ключ: `{user_api_key}`\n" if user_api_key else "API ключ не настроен.\n"
    )
    message_text += f"Текущий оператор: `{operator_display_name}`\n"
    message_text += f"Текущая страна: `{country_display_name}`"

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

async def input_api_key_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    keyboard = InlineKeyboardBuilder()
    keyboard.button(text="❌ Отмена", callback_data="cancel_settings")
    keyboard.adjust(1)

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Введите ваш API ключ для работы с сервисом:",
        state=state,
        reply_markup=keyboard.as_markup()
    )
    await state.set_state(UserSettings.entering_api_key)

async def delete_user_api_key_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    logger.info(f"Пользователь {callback_query.from_user.id} запросил удаление API ключа.")
    try:
        delete_user_api_key(callback_query.from_user.id)
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="API ключ успешно удалён.",
            state=state
        )
        await send_main_menu(callback_query.message, state)
        await state.set_state(None)
    except Exception as e:
        logger.exception(f"Ошибка при удалении API ключа: {e}")
        await callback_query.message.answer("Не удалось удалить API ключ. Попробуйте снова.")

async def back_to_main_menu_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    await state.set_state(None)
    await send_main_menu(callback_query.message, state)

async def cancel_settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    await state.set_state(None)
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

async def set_user_api_key_handler(message: types.Message, state: FSMContext):
    user_api_key = message.text.strip()
    logger.info(f"Пользователь {message.from_user.id} ввёл API ключ.")
    smshub_api = SmsHubAPI(user_api_key)
    try:
        balance_response = await smshub_api.get_balance()
        if balance_response and balance_response.startswith("ACCESS_BALANCE:"):
            balance = balance_response.split(":")[1]
            balance_usd = float(balance)
            usd_to_rub_rate = await get_usd_to_rub_rate()
            balance_rub = balance_usd * usd_to_rub_rate if usd_to_rub_rate else "недоступно"

            set_user_api_key(message.from_user.id, user_api_key)

            # Проверяем, что ключ действительно сохранен
            saved_api_key = get_user_api_key(message.from_user.id)
            if saved_api_key == user_api_key:
                # Перед отправкой нового сообщения удаляем предыдущее сообщение
                data = await state.get_data()
                last_message_id = data.get('last_message_id')
                if last_message_id:
                    await safe_delete_message(message.bot, message.chat.id, last_message_id)

                sent_message = await message.answer(
                    f"Ваш API ключ успешно сохранён и проверен!\n"
                    f"Баланс: `{balance_usd:.4f}`$ (приблизительно `{balance_rub:.2f}`₽)"
                )
                # Сохраняем новое сообщение бота
                await state.update_data(last_message_id=sent_message.message_id)
            else:
                await message.answer("Произошла ошибка при сохранении API ключа. Пожалуйста, попробуйте снова.")
                return

            await state.set_state(None)
            # Возвращаемся в главное меню
            await send_main_menu(message, state)
        else:
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="❌ Отмена", callback_data="cancel_settings")
            keyboard.adjust(1)
            await message.answer(
                "Неверный API ключ. Пожалуйста, попробуйте снова или отмените операцию.",
                reply_markup=keyboard.as_markup()
            )
    except Exception as e:
        logger.error(f"Ошибка при проверке API ключа: {e}")
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="❌ Отмена", callback_data="cancel_settings")
        keyboard.adjust(1)
        await message.answer(
            "Произошла ошибка при проверке ключа. Пожалуйста, попробуйте снова.",
            reply_markup=keyboard.as_markup()
        )

# Обработчики для выбора оператора (без изменений)
async def select_operator_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    operator_list = list(OPERATOR_DISPLAY_NAMES.keys())

    # Создаем клавиатуру с операторами в 2 столбца
    keyboard = InlineKeyboardBuilder()
    for op in operator_list:
        display_name = OPERATOR_DISPLAY_NAMES.get(op, op)
        keyboard.button(text=display_name, callback_data=f"operator:{op}")
    keyboard.button(text="❌ Отмена", callback_data="cancel_operator_selection")
    keyboard.adjust(2)  # Устанавливаем 2 столбца

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Выберите оператора из списка:",
        state=state,
        reply_markup=keyboard.as_markup()
    )
    await state.set_state(OperatorSettings.selecting_operator)

async def cancel_operator_selection_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    await state.set_state(None)
    # Возвращаемся в меню настроек
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

async def operator_selected_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    operator = callback_query.data.split(":")[1]
    set_user_operator(callback_query.from_user.id, operator)
    display_name = OPERATOR_DISPLAY_NAMES.get(operator, operator)
    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=f"Вы выбрали оператора: {display_name}",
        state=state
    )
    await state.set_state(None)
    # Возвращаемся в меню настроек
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

# Обработчики для выбора страны
async def select_country_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")

    # Создаем клавиатуру с доступными странами
    keyboard = InlineKeyboardBuilder()
    for country_code, country_name in COUNTRIES.items():
        keyboard.button(text=country_name, callback_data=f"country:{country_code}")
    keyboard.button(text="❌ Отмена", callback_data="cancel_country_selection")
    keyboard.adjust(2)  # Устанавливаем 2 столбца

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Выберите страну из списка:",
        state=state,
        reply_markup=keyboard.as_markup()
    )
    await state.set_state(CountrySettings.selecting_country)

async def country_selected_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")

    country_code = callback_query.data.split(":")[1]
    set_user_country(callback_query.from_user.id, country_code)
    country_name = COUNTRIES.get(country_code, country_code)
    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=f"Вы выбрали страну: {country_name}",
        state=state
    )
    await state.set_state(None)
    # Возвращаемся в меню настроек
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

async def cancel_country_selection_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    await state.set_state(None)
    # Возвращаемся в меню настроек
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

def register_settings_handlers(dp: Dispatcher):
    dp.callback_query.register(settings_handler, F.data == "settings")
    dp.callback_query.register(input_api_key_handler, F.data.in_({"edit_api_key", "add_api_key"}))
    dp.callback_query.register(delete_user_api_key_handler, F.data == "delete_api_key")
    dp.callback_query.register(back_to_main_menu_handler, F.data == "back_to_main_menu")
    dp.callback_query.register(cancel_settings_handler, F.data == "cancel_settings")
    dp.message.register(set_user_api_key_handler, UserSettings.entering_api_key)
    dp.callback_query.register(select_operator_handler, F.data == "edit_operator")
    dp.callback_query.register(cancel_operator_selection_handler, F.data == "cancel_operator_selection")
    dp.callback_query.register(operator_selected_handler, OperatorSettings.selecting_operator, F.data.startswith("operator:"))
    dp.callback_query.register(select_country_handler, F.data == "edit_country")
    dp.callback_query.register(country_selected_handler, CountrySettings.selecting_country, F.data.startswith("country:"))
    dp.callback_query.register(cancel_country_selection_handler, F.data == "cancel_country_selection")


# /bot/handlers/sms.py

import asyncio
import logging
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 bot.database import get_user_api_key, get_user_operator, get_user_country
from bot.api import SmsHubAPI
from bot.utils import get_usd_to_rub_rate, safe_edit_or_send_message, safe_delete_message
from bot.constants.services import SERVICE_DISPLAY_NAMES
from bot.constants.countries import COUNTRIES
from bot.constants.operators import OPERATOR_DISPLAY_NAMES
from bot.handlers.main_menu import send_main_menu
from bot.handlers.activation import initialize_activation, user_activations, poll_for_sms

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

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

def build_service_keyboard(page_number: int = 0) -> InlineKeyboardBuilder:
    services_pages = get_service_pages(SERVICES)
    current_page = services_pages[page_number]
    keyboard = InlineKeyboardBuilder()
    for code, name in current_page:
        keyboard.button(text=name, callback_data=f"service_select:{code}")
    nav_buttons = []
    if page_number > 0:
        nav_buttons.append(types.InlineKeyboardButton(text="⬅️", callback_data=f"service_page:{page_number - 1}"))
    if page_number < len(services_pages) - 1:
        nav_buttons.append(types.InlineKeyboardButton(text="➡️", callback_data=f"service_page:{page_number + 1}"))
    if nav_buttons:
        keyboard.row(*nav_buttons)
    keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
    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"Failed to answer callback query: {e}")
    user_api_key = get_user_api_key(str(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="Выберите сервис или введите код сервиса вручную:",
        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"Failed to answer 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"Failed to answer 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"Failed to answer callback query: {e}")

    service_code = callback_query.data.split(":")[1]
    await state.update_data(selected_service=service_code)
    await service_handler(callback_query.message, state, service_code=service_code, user_id=str(callback_query.from_user.id))

async def service_handler(message: types.Message, state: FSMContext, service_code: Optional[str] = None, user_id: Optional[str] = None):
    if service_code is not None:
        service = service_code.strip().lower()
    else:
        service = message.text.strip().lower()

    if user_id is None:
        user_id = str(message.from_user.id)

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

    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 message.answer("Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.")
        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()

    try:
        price_info = await smshub_api.get_price_for_service(service, country=user_country_code)
        if not price_info:
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
            keyboard.adjust(1)

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

        numbers_status = await smshub_api.get_numbers_status(country=user_country_code, operator=user_operator)
        if not numbers_status:
            await message.answer("Не удалось получить информацию о доступных номерах. Попробуйте снова позже.")
            await state.set_state(None)
            await send_main_menu(message, state)
            return

        service_key = f"{service}_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")
            keyboard.adjust(1)

            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:
            await message.answer("Цены для выбранного сервиса недоступны. Пожалуйста, попробуйте другой сервис или повторите попытку позже.")
            await state.set_state(None)
            await send_main_menu(message, state)
            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)
        if operator_display.lower() == "любой оператор":
            operator_display = "любой оператор"

        service_display = SERVICE_DISPLAY_NAMES.get(service, service)

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

        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 message.answer(
            message_text,
            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,
            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
        )
        await state.set_state(SmsOrderState.waiting_for_max_price)
    except Exception as e:
        logger.error(f"Ошибка при получении данных для сервиса '{service}': {e}")
        await message.answer("Произошла ошибка при обработке запроса. Попробуйте снова.")
        await state.set_state(None)
        await send_main_menu(message, state)

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', str(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 message.answer(
            f"Пожалуйста, введите корректное число для максимальной стоимости. "
            f"От `{min_price:.4f}` $ (₽{min_price_rub:.2f}) до `{max_price_val:.4f}` $ (₽{max_price_rub:.2f}).",
            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']
    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 < user_data['min_price']:
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
        keyboard.adjust(1)
        await message.answer(
            f"Максимальная стоимость должна быть не менее `{user_data['min_price']:.4f}` $.",
            reply_markup=keyboard.as_markup(),
            parse_mode="Markdown"
        )
        return

    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}")

        # Initialize activation with helper function
        initialize_activation(user_id, activation_id, number, service, service_display)

        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 message.answer(
            message_text,
            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}")
        if result == "NO_NUMBERS":
            await state.set_state(None)
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
            keyboard.adjust(1)

            try:
                await safe_edit_or_send_message(
                    message=message,
                    new_text=(
                        f"Нет доступных номеров для оператора {operator_display} по данной цене.\n"
                        f"Попробуйте выбрать 'Любой оператор' в настройках или увеличить максимальную цену."
                    ),
                    state=state,
                    reply_markup=keyboard.as_markup()
                )
            except Exception as e:
                logger.error(f"Failed to send no numbers message: {e}")
        else:
            await message.answer("Не удалось получить номер. Попробуйте снова.")
        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)


# /bot/handlers/start.py

import logging
from aiogram import Dispatcher, types
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext

from ..database import set_user_operator, get_user_operator
from .main_menu import send_main_menu

logger = logging.getLogger(__name__)

async def start_handler(message: types.Message, state: FSMContext):
    logger.info(f"Пользователь {message.from_user.id} запустил бота.")
    # Устанавливаем оператор "any" по умолчанию, если не установлен
    user_operator = get_user_operator(message.from_user.id)
    if not user_operator:
        set_user_operator(message.from_user.id, "any")
    await send_main_menu(message, state)

def register_start_handlers(dp: Dispatcher):
    dp.message.register(start_handler, Command('start'))