Oct 11 18:16:24 vps1707806210 systemd[1]: Started My SMS Bot Service.
Oct 11 18:16:28 vps1707806210 python[33878]: Traceback (most recent call last):
Oct 11 18:16:28 vps1707806210 python[33878]:   File "/root/sms/main.py", line 7, in <module>
Oct 11 18:16:28 vps1707806210 python[33878]:     from bot.handlers import register_handlers
Oct 11 18:16:28 vps1707806210 python[33878]:   File "/root/sms/bot/handlers/__init__.py", line 3, in <module>
Oct 11 18:16:28 vps1707806210 python[33878]:     from .start import register_start_handlers
Oct 11 18:16:28 vps1707806210 python[33878]:   File "/root/sms/bot/handlers/start.py", line 9, in <module>
Oct 11 18:16:28 vps1707806210 python[33878]:     from .main_menu import send_main_menu
Oct 11 18:16:28 vps1707806210 python[33878]:   File "/root/sms/bot/handlers/main_menu.py", line 7, in <module>
Oct 11 18:16:28 vps1707806210 python[33878]:     from bot.utils.functions import safe_edit_or_send_message
Oct 11 18:16:28 vps1707806210 python[33878]:   File "/root/sms/bot/utils.py", line 9, in <module>
Oct 11 18:16:28 vps1707806210 python[33878]:     from bot.handlers.activation import user_activations
Oct 11 18:16:28 vps1707806210 python[33878]:   File "/root/sms/bot/handlers/activation.py", line 14, in <module>
Oct 11 18:16:28 vps1707806210 python[33878]:     from ..utils import safe_edit_or_send_message, safe_delete_message, update_main_menu
Oct 11 18:16:28 vps1707806210 python[33878]: ImportError: cannot import name 'safe_edit_or_send_message' from partially initialized module 'bot.utils' (most likely due to a circular import) (/root/sms/bot/utils.py)
Oct 11 18:16:28 vps1707806210 systemd[1]: my_smsbot.service: Main process exited, code=exited, status=1/FAILURE
Oct 11 18:16:28 vps1707806210 systemd[1]: my_smsbot.service: Failed with result 'exit-code'.
Oct 11 18:16:28 vps1707806210 systemd[1]: my_smsbot.service: Consumed 3.851s CPU time.
Oct 11 18:16:28 vps1707806210 systemd[1]: my_smsbot.service: Scheduled restart job, restart counter is at 73.
Oct 11 18:16:28 vps1707806210 systemd[1]: Stopped My SMS Bot Service.
Oct 11 18:16:28 vps1707806210 systemd[1]: my_smsbot.service: Consumed 3.851s CPU time.
Oct 11 18:16:28 vps1707806210 systemd[1]: Started My SMS Bot Service.










# /bot/handlers/activation.py

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 ..database import get_user_api_key, archive_activation, activation_archive
from ..api import SmsHubAPI
from ..constants.services import SERVICE_DISPLAY_NAMES
from ..utils import safe_edit_or_send_message, safe_delete_message, update_main_menu

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):
    """Мониторинг статуса активации, проверка каждые 3 секунды."""
    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, "Не удалось получить статус активации. Попробуйте позже.")
                await update_main_menu(message=types.Message(chat=types.Chat(id=user_id)), 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 == "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, "Активация была отменена.")
                await update_main_menu(message=types.Message(chat=types.Chat(id=user_id)), 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"
                )
                await update_main_menu(message=types.Message(chat=types.Chat(id=user_id)), 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)
        await update_main_menu(message=types.Message(chat=types.Chat(id=user_id)), 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)
        await update_main_menu(message=types.Message(chat=types.Chat(id=user_id)), 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()
        )
        await update_main_menu(message=types.Message(chat=types.Chat(id=user_id)), 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"))








# /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.functions import safe_edit_or_send_message
from .activation import user_activations  # Ensure this path is correct
from ..database import activation_archive  # Ensure this path is correct

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 ..database 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 ..constants.services import SERVICE_DISPLAY_NAMES
from ..constants.countries import COUNTRIES
from ..constants.operators import OPERATOR_DISPLAY_NAMES
from .main_menu import send_main_menu
from .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'))












# /bot/utils.py

import logging
import time
import aiohttp
from aiogram import types
from aiogram.fsm.context import FSMContext
from typing import Optional
from bot.handlers.activation import user_activations
from ..database import activation_archive  # Adjust according to your project's file structure

logger = logging.getLogger(__name__)

EXCHANGE_RATE_API_URL = "https://api.exchangerate-api.com/v4/latest/USD"
USD_TO_RUB_RATE_TTL = 3600  # Cache rate for 1 hour

_cached_usd_to_rub_rate: Optional[float] = None
_cached_usd_to_rub_rate_time: float = 0.0

async def get_usd_to_rub_rate() -> Optional[float]:
    """Get current USD to RUB rate with caching."""
    global _cached_usd_to_rub_rate, _cached_usd_to_rub_rate_time
    now = time.time()
    if _cached_usd_to_rub_rate and (now - _cached_usd_to_rub_rate_time < USD_TO_RUB_RATE_TTL):
        return _cached_usd_to_rub_rate
    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(EXCHANGE_RATE_API_URL) as response:
                data = await response.json()
                rate = data.get('rates', {}).get('RUB')
                if rate:
                    _cached_usd_to_rub_rate = rate
                    _cached_usd_to_rub_rate_time = now
                    return rate
                else:
                    logger.error("RUB rate not found in response.")
                    return None
    except Exception as e:
        logger.error(f"Error fetching exchange rate: {e}")
        return None

async def safe_edit_or_send_message(message: types.Message, new_text: str, state: FSMContext, reply_markup=None, parse_mode=None):
    """Safely edit or send a new message."""
    try:
        await message.edit_text(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
        await state.update_data(last_message_id=message.message_id)
    except Exception as e:
        logger.error(f"Failed to edit message: {e}")
        data = await state.get_data()
        last_message_id = data.get('last_message_id')
        if last_message_id:
            try:
                await message.bot.delete_message(chat_id=message.chat.id, message_id=last_message_id)
            except Exception as del_exc:
                logger.error(f"Failed to delete message: {del_exc}")
        sent_message = await message.answer(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
        await state.update_data(last_message_id=sent_message.message_id)

async def safe_delete_message(bot: types.Bot, chat_id: int, message_id: int) -> None:
    """Safely delete a message."""
    try:
        await bot.delete_message(chat_id=chat_id, message_id=message_id)
    except Exception as e:
        logger.error(f"Failed to delete message: {e}")

async def update_main_menu(message: types.Message, state: FSMContext):
    """Update main menu to reflect current activation count."""
    logger.info(f"Updating main menu for user {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 = types.InlineKeyboardMarkup()
    keyboard.add(types.InlineKeyboardButton(text="💰 Проверить баланс", callback_data="check_balance"))
    keyboard.add(types.InlineKeyboardButton(text="📲 Получить SMS", callback_data="get_sms"))
    keyboard.add(types.InlineKeyboardButton(text=f"📋 Текущие активации ({current_count})", callback_data="current_activations"))
    keyboard.add(types.InlineKeyboardButton(text=f"🗃️ Архив активаций ({archived_count})", callback_data="activation_archive"))
    keyboard.add(types.InlineKeyboardButton(text="⚙️ Настройки", callback_data="settings"))

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

async def on_startup(dispatcher: types.Dispatcher):
    """Function executed on bot startup."""
    logger.info("Bot successfully started.")
    # Here background tasks can be started if needed