USER
можно полный исправленный код:
import asyncio
import logging
import time
import json
from pathlib import Path
from typing import Optional, List, Tuple
from aiogram import Dispatcher, types, F
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from ..data import get_user_api_key, get_user_operator, get_user_country
from ..api import SmsHubAPI
from ..utils import get_usd_to_rub_rate, safe_edit_or_send_message
from .start import send_main_menu
from .activation import user_activations, poll_for_sms
from ..constants.operators import OPERATOR_DISPLAY_NAMES
from ..constants.countries import COUNTRIES
from ..constants.services import SERVICE_DISPLAY_NAMES
logger = logging.getLogger(__name__)
class SmsOrderState(StatesGroup):
waiting_for_service = State()
waiting_for_max_price = State()
SERVICES: List[Tuple[str, str]] = list(SERVICE_DISPLAY_NAMES.items())
POPULARITY_FILE = Path("service_popularity.json")
def load_popularity_data() -> dict:
if POPULARITY_FILE.exists():
with open(POPULARITY_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
def save_popularity_data(popularity_data: dict):
with open(POPULARITY_FILE, 'w', encoding='utf-8') as f:
json.dump(popularity_data, f, ensure_ascii=False, indent=4)
def update_service_popularity(service_code: str):
popularity_data = load_popularity_data()
if service_code in popularity_data:
popularity_data[service_code] += 1
else:
popularity_data[service_code] = 1
save_popularity_data(popularity_data)
def get_sorted_services(available_services: List[str]) -> List[Tuple[str, str]]:
popularity_data = load_popularity_data()
sorted_services = sorted(
[(code, name) for code, name in SERVICES if code in available_services],
key=lambda service: popularity_data.get(service[0], 0),
reverse=True
)
return sorted_services
def get_service_pages(services: List[Tuple[str, str]], page_size: int = 18) -> List[List[Tuple[str, str]]]:
pages = []
for i in range(0, len(services), page_size):
pages.append(services[i:i + page_size])
return pages
def build_service_keyboard(page_number: int, available_services: List[str]) -> InlineKeyboardBuilder:
sorted_services = get_sorted_services(available_services)
services_pages = get_service_pages(sorted_services)
keyboard = InlineKeyboardBuilder()
if page_number < len(services_pages):
current_page = services_pages[page_number]
for code, name in current_page:
keyboard.button(text=name, callback_data=f"service_select:{code}")
keyboard.adjust(3)
if len(services_pages) > 1: # Only show navigation buttons if more than one page exists
if page_number == 0:
keyboard.row(types.InlineKeyboardButton(text="➡️ Вперед", callback_data=f"service_page:{page_number + 1}"))
elif page_number == len(services_pages) - 1:
keyboard.row(types.InlineKeyboardButton(text="⬅️ Назад", callback_data=f"service_page:{page_number - 1}"))
else:
keyboard.row(
types.InlineKeyboardButton(text="⬅️ Назад", callback_data=f"service_page:{page_number - 1}"),
types.InlineKeyboardButton(text="➡️ Вперед", callback_data=f"service_page:{page_number + 1}")
)
keyboard.row(types.InlineKeyboardButton(text="🔙 Назад в меню", callback_data="back_to_main_menu"))
return keyboard
def get_service_message_text(total_services_with_numbers: int, total_services_for_country: int, country_name: str, operator_name: str) -> str:
return (
f"🔢 *Общее кол-во сервисов:* `{total_services_for_country}`\n"
f"💼 *Доступные сервисы (где есть номера):* `{total_services_with_numbers}`\n"
f"— Нажмите на кнопку, чтобы выбрать сервис.\n"
f"— Или введите название, чтобы найти сервис.\n\n"
f"🐾 _Текущая страна:_ `{country_name}`\n"
f"📡 _Текущий оператор:_ `{operator_name}`\n"
f"(Эти параметры можно изменить в настройках)"
)
async def fetch_all_services(user_api_key: str, user_country_code: str, state: FSMContext) -> Tuple[Optional[List[str]], int, int]:
smshub_api = SmsHubAPI(user_api_key)
max_attempts = 3
last_exception = None
available_services = []
services_with_numbers = set()
for attempt in range(1, max_attempts + 1):
try:
numbers_status = await smshub_api.get_numbers_status(country=user_country_code)
if numbers_status:
# Все сервисы и сервисы с доступными номерами
available_services = [service.split('_')[0] for service in numbers_status.keys()]
services_with_numbers = {service.split('_')[0] for service, count in numbers_status.items() if int(count) > 0}
break
except Exception as e:
last_exception = e
logger.error(f"Ошибка при запросе статуса всех сервисов, попытка {attempt}: {e}")
data = await state.get_data()
if data.get("cancel_request"):
logger.info("Запрос на получение всех сервисов был отменен.")
return None, 0, 0
progress_text = f"Попытка {attempt}/{max_attempts} получения списка всех сервисов..."
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=data.get('message'),
new_text=progress_text,
state=state,
reply_markup=keyboard.as_markup()
)
await asyncio.sleep(5)
if last_exception:
logger.error(f"Ошибка при запросе всех сервисов: {last_exception}")
return None, 0, 0
return available_services, len(services_with_numbers), len(available_services)
async def get_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
user_api_key = get_user_api_key(callback_query.from_user.id)
user_country_code = get_user_country(callback_query.from_user.id) or "0"
user_operator = get_user_operator(callback_query.from_user.id) or "any"
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} начал процесс получения номера.")
await state.update_data(message=callback_query.message, cancel_request=False)
available_services, total_services_with_numbers, total_services_for_country = await fetch_all_services(user_api_key, user_country_code, state)
country_name = COUNTRIES.get(user_country_code, 'Неизвестно')
operator_name = OPERATOR_DISPLAY_NAMES.get(user_operator, 'Любой')
await state.update_data(total_services_with_numbers=total_services_with_numbers,
total_services_for_country=total_services_for_country, # Обновите здесь
country_name=country_name,
operator_name=operator_name)
if available_services is None:
await send_main_menu(callback_query.message, state)
return
keyboard = build_service_keyboard(page_number=0, available_services=available_services)
message_text = get_service_message_text(total_services_with_numbers, total_services_for_country, country_name, operator_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(SmsOrderState.waiting_for_service)
await state.update_data(available_services=available_services)
async def go_back_to_service_selection(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
logger.info(f"Пользователь {callback_query.from_user.id} отменил операцию.")
await state.update_data(cancel_request=True)
data = await state.get_data()
current_task = data.get('current_task')
if current_task:
current_task.cancel()
# Обновите актуальные данные здесь
available_services = data.get('available_services', [])
total_services_with_numbers = data.get('total_services_with_numbers', len(available_services))
total_services_for_country = data.get('total_services_for_country', len(available_services))
country_name = data.get('country_name', 'Неизвестно')
operator_name = data.get('operator_name', 'Любой')
keyboard = build_service_keyboard(page_number=0, available_services=available_services)
message_text = get_service_message_text(total_services_with_numbers, total_services_for_country, country_name, operator_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(SmsOrderState.waiting_for_service)
async def back_to_main_menu(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
logger.info(f"Пользователь {callback_query.from_user.id} вернулся в главное меню.")
await state.set_state(None)
await send_main_menu(callback_query.message, state)
async def service_page_handler(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
page_number = int(callback_query.data.split(":")[1])
data = await state.get_data()
available_services = data.get('available_services', [])
total_services_with_numbers = data.get('total_services_with_numbers', len(available_services))
total_services_for_country = data.get('total_services_for_country', len(available_services))
country_name = data.get('country_name', 'Неизвестно')
operator_name = data.get('operator_name', 'Любой')
keyboard = build_service_keyboard(page_number, available_services)
message_text = get_service_message_text(total_services_with_numbers, total_services_for_country, country_name, operator_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"
)
async def service_selected_handler(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
service_code = callback_query.data.split(":")[1]
update_service_popularity(service_code)
await state.update_data(selected_service=service_code, cancel_request=False)
task = asyncio.create_task(service_handler_logic(
callback_query.message, state, user_id=callback_query.from_user.id, service_code=service_code, is_service_selection=True))
await state.update_data(current_task=task)
async def service_handler(message: types.Message, state: FSMContext, user_id: Optional[int] = None):
try:
await message.delete()
except Exception as e:
logger.error(f"Не удалось удалить сообщение пользователя: {e}")
user_input = message.text.strip().lower()
if user_id is None:
user_id = message.from_user.id
logger.info(f"Пользователь {user_id} ввел сервис: {user_input}")
data = await state.get_data()
available_services = data.get('available_services', [])
matched_services = [
(code, name) for code, name in SERVICE_DISPLAY_NAMES.items()
if code in available_services and (user_input in name.lower() or user_input == code.lower())
]
if matched_services:
pages = get_service_pages(matched_services)
page_number = 0
keyboard = build_page_keyboard(pages, page_number)
await state.update_data(matched_services=matched_services)
await safe_edit_or_send_message(
message=message,
new_text="Пожалуйста, выберите один из представленных сервисов:",
state=state,
reply_markup=keyboard.as_markup()
)
else:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔙 Назад к сервисам", callback_data="service_page:0")
await safe_edit_or_send_message(
message=message,
new_text="Сервис не найден. Попробуйте ввести код или полное название сервиса.",
state=state,
reply_markup=keyboard.as_markup()
)
def build_page_keyboard(service_pages, page_number: int) -> InlineKeyboardBuilder:
keyboard = InlineKeyboardBuilder()
if page_number < len(service_pages):
current_page = service_pages[page_number]
for code, name in current_page:
keyboard.button(text=name, callback_data=f"service_select:{code}")
keyboard.adjust(3)
if len(service_pages) > 1: # Only show navigation buttons if more than one page exists
if page_number == 0:
keyboard.row(types.InlineKeyboardButton(text="➡️ Вперед", callback_data=f"search_page:{page_number + 1}"))
elif page_number == len(service_pages) - 1:
keyboard.row(types.InlineKeyboardButton(text="⬅️ Назад", callback_data=f"search_page:{page_number - 1}"))
else:
keyboard.row(
types.InlineKeyboardButton(text="⬅️ Назад", callback_data=f"search_page:{page_number - 1}"),
types.InlineKeyboardButton(text="➡️ Вперед", callback_data=f"search_page:{page_number + 1}")
)
keyboard.row(types.InlineKeyboardButton(text="🔙 Назад к сервисам", callback_data="service_page:0"))
return keyboard
async def handle_search_page_navigation(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
page_number = int(callback_query.data.split(":")[1])
data = await state.get_data()
matched_services = data.get('matched_services', [])
pages = get_service_pages(matched_services)
keyboard = build_page_keyboard(pages, page_number)
await safe_edit_or_send_message(
message=callback_query.message,
new_text="Пожалуйста, выберите один из представленных сервисов:",
state=state,
reply_markup=keyboard.as_markup()
)
async def service_handler_logic(message: types.Message, state: FSMContext, user_id: int, service_code: str, is_service_selection: bool = False):
logger.info(f"Пользователь {user_id} выбрал сервис: {service_code}")
user_api_key = get_user_api_key(user_id)
user_operator = get_user_operator(user_id) or "any"
user_country_code = get_user_country(user_id) or "0"
country_name = COUNTRIES.get(user_country_code, user_country_code)
if not user_api_key:
await safe_edit_or_send_message(
message=message,
new_text="Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
state=state
)
await state.set_state(None)
await send_main_menu(message, state)
return
smshub_api = SmsHubAPI(user_api_key)
usd_to_rub_rate = await get_usd_to_rub_rate()
max_attempts = 3
success = False
last_exception = None
try:
for attempt in range(1, max_attempts + 1):
data = await state.get_data()
if data.get("cancel_request"):
logger.info(f"Запрос для пользователя {user_id} был отменен.")
return
try:
progress_text = f"Идет запрос {attempt}/{max_attempts}..."
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=message,
new_text=progress_text,
state=state,
reply_markup=keyboard.as_markup()
)
price_info = await smshub_api.get_price_for_service(service_code, country=user_country_code)
numbers_status = await smshub_api.get_numbers_status(country=user_country_code, operator=user_operator)
if data.get("cancel_request"):
logger.info(f"Запрос для пользователя {user_id} был отменен после получения данных.")
return
if price_info and numbers_status:
service_key = f"{service_code}_0"
available_numbers = int(numbers_status.get(service_key, 0) or 0)
if available_numbers > 0:
prices = [float(price_str) for price_str in price_info.keys() if price_str is not None]
if prices:
min_price_usd = min(prices)
max_price_usd = max(prices)
min_price_rub = min_price_usd * usd_to_rub_rate if usd_to_rub_rate else 0
max_price_rub = max_price_usd * usd_to_rub_rate if usd_to_rub_rate else 0
operator_display = OPERATOR_DISPLAY_NAMES.get(user_operator, user_operator)
service_display = SERVICE_DISPLAY_NAMES.get(service_code, service_code)
keyboard = InlineKeyboardBuilder()
keyboard.button(text=f"⬇️ Мин. цена ({min_price_usd:.4f}$)", callback_data=f"select_price:{min_price_usd}")
keyboard.button(text=f"⬆️ Макс. цена ({max_price_usd:.4f}$)", callback_data=f"select_price:{max_price_usd}")
keyboard.adjust(2)
keyboard.row(types.InlineKeyboardButton(text="❌ Отмена", callback_data="cancel_operation"))
message_text = (
f"*Страна:* `{country_name}`\n"
f"*Доступно номеров:* `{available_numbers}`\n"
f"*Оператор:* `{operator_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"_Выберите максимальную допустимую стоимость покупки с помощью кнопок ниже или введите сумму вручную (например: 0.5). Все суммы указаны в долларах ($):_"
)
await safe_edit_or_send_message(
message=message,
new_text=message_text,
state=state,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
await state.update_data(
service=service_code,
service_display=service_display,
country_name=country_name,
available_numbers=available_numbers,
operator_display=operator_display,
min_price=min_price_usd,
max_price_usd=max_price_usd,
min_price_rub=min_price_rub,
max_price_rub=max_price_rub,
usd_to_rub_rate=usd_to_rub_rate,
user_id=user_id
)
await state.set_state(SmsOrderState.waiting_for_max_price)
success = True
break
else:
logger.info(f"Попытка {attempt}: Цены не найдены.")
last_exception = "Цены не найдены"
else:
logger.info(f"Попытка {attempt}: Нет доступных номеров.")
last_exception = "Нет доступных номеров"
except Exception as e:
logger.error(f"Ошибка при получении данных для сервиса '{service_code}', попытка {attempt}: {e}")
last_exception = e
await asyncio.sleep(5)
except asyncio.CancelledError:
logger.info(f"Задача для пользователя {user_id} была отменена.")
return
if not success:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="Назад", callback_data="service_page:0")
await safe_edit_or_send_message(
message=message,
new_text=f"Произошла ошибка при обработке запроса: {last_exception}. Попробуйте снова.",
state=state,
reply_markup=keyboard.as_markup()
)
async def price_selected_handler(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
price = float(callback_query.data.split(":")[1])
await state.update_data(max_price=price)
await process_number_purchase(callback_query.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', message.from_user.id)
try:
await message.delete()
except Exception as e:
logger.error(f"Не удалось удалить сообщение пользователя: {e}")
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', 0)
max_price_usd = user_data.get('max_price_usd', 0)
min_price_rub = user_data.get('min_price_rub', 0)
max_price_rub = user_data.get('max_price_rub', 0)
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=message,
new_text=(
f"Пожалуйста, введите корректное число для максимальной стоимости. "
f"От `{min_price:.4f}` $ (₽{min_price_rub:.2f}) до `{max_price_usd:.4f}` $ (₽{max_price_rub:.2f})."
),
state=state,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
return
if max_price < data.get('min_price', 0):
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=message,
new_text=f"Максимальная стоимость должна быть не менее `{data.get('min_price', 0):.4f}` $.",
state=state,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
return
await state.update_data(max_price=max_price)
await process_number_purchase(message, state)
async def process_number_purchase(message: types.Message, state: FSMContext):
user_data = await state.get_data()
user_id = user_data.get('user_id', message.from_user.id)
service = user_data['service']
service_display = user_data['service_display']
country_name = user_data['country_name']
operator_display = user_data['operator_display']
max_price = user_data['max_price']
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"
max_attempts = 3
last_exception = None
try:
for attempt in range(1, max_attempts + 1):
data = await state.get_data()
if data.get("cancel_request"):
logger.info(f"Запрос на получение номера для пользователя {user_id} был отменен.")
return
progress_text = f"Идет запрос {attempt}/{max_attempts} на получение номера..."
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=message,
new_text=progress_text,
state=state,
reply_markup=keyboard.as_markup()
)
user_operator = get_user_operator(user_id) or "any"
if data.get("cancel_request"):
logger.info(f"Запрос на получение номера для пользователя {user_id} был отменен перед выполнением.")
return
result = await smshub_api.get_number(service, country=user_country_code, max_price=max_price, operator=user_operator)
if data.get("cancel_request"):
logger.info(f"Запрос на получение номера для пользователя {user_id} был отменен.")
return
if result and result.startswith("ACCESS_NUMBER"):
_, activation_id, number = result.split(":")
logger.info(f"Пользователь {user_id} получил номер: {number}, активация ID: {activation_id}")
if user_id not in user_activations:
user_activations[user_id] = {}
user_activations[user_id][activation_id] = {
'number': number,
'status': 'Ожидание SMS',
'service': service,
'service_display': service_display,
'country_name': country_name,
'last_update': time.time(),
'task': None
}
keyboard = InlineKeyboardBuilder()
keyboard.button(text="📂 Текущие активации", callback_data="current_activations")
keyboard.button(text="❌ Отменить активацию", callback_data=f"cancel_activation:{activation_id}")
keyboard.button(text="🔙 Главное меню", callback_data="back_to_main_menu")
keyboard.adjust(1)
message_text = (
f"*Получен номер:* `{number}`\n"
f"*Страна:* `{country_name}`\n"
f"*Оператор:* `{operator_display}`\n"
f"*Сервис:* `{service_display}`\n"
f"*Максимальная стоимость:* `{max_price:.2f}` $ (₽{max_price * user_data['usd_to_rub_rate']:.2f})\n"
f"*Статус:* *Ожидание SMS*\n"
f"Вы можете управлять активацией или вернуться в главное меню."
)
sent_message = await safe_edit_or_send_message(
message=message,
new_text=message_text,
state=state,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
user_activations[user_id][activation_id]['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
return
elif result == "NO_NUMBERS":
logger.info(f"Попытка {attempt} из {max_attempts}: нет доступных номеров.")
last_exception = result
if attempt == max_attempts:
suggestion = (
"Попробуйте выбрать 'Любой оператор' в настройках или увеличить максимальную цену."
if user_operator != "any" else
"Попробуйте увеличить максимальную цену или повторите попытку позже."
)
keyboard = InlineKeyboardBuilder()
keyboard.button(text="Назад", callback_data="service_page:0")
await safe_edit_or_send_message(
message=message,
new_text=f"Нет доступных номеров для оператора {operator_display} по данной цене.\n{suggestion}",
state=state,
reply_markup=keyboard.as_markup()
)
return
else:
raise Exception("Неожиданный ответ API")
await asyncio.sleep(5)
except asyncio.CancelledError:
logger.info(f"Задача для пользователя {user_id} была отменена.")
return
if last_exception and last_exception != "NO_NUMBERS":
keyboard = InlineKeyboardBuilder()
keyboard.button(text="Назад", callback_data="service_page:0")
await safe_edit_or_send_message(
message=message,
new_text=f"Произошла ошибка при обработке запроса: {last_exception}. Попробуйте снова.",
state=state,
reply_markup=keyboard.as_markup()
)
def register_sms_handlers(dp: Dispatcher):
dp.callback_query.register(get_sms_handler, F.data == "get_sms")
dp.callback_query.register(go_back_to_service_selection, F.data == "cancel_operation")
dp.callback_query.register(back_to_main_menu, F.data == "back_to_main_menu")
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.callback_query.register(handle_search_page_navigation, F.data.startswith("search_page:"))
dp.callback_query.register(price_selected_handler, F.data.startswith("select_price:"))
dp.message.register(service_handler, SmsOrderState.waiting_for_service)
dp.message.register(max_price_handler, SmsOrderState.waiting_for_max_price)ASSISTANT
Sure! Below is the complete code with modifications to showcase page numbers in the navigation buttons, allowing users to know the current page they are on, together with the total number of pages available. I'll also include a handler for ignoring the "page info" button to ensure it does not cause any errors or unexpected behavior.
```python
import asyncio
import logging
import time
import json
from pathlib import Path
from typing import Optional, List, Tuple
from aiogram import Dispatcher, types, F
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from ..data import get_user_api_key, get_user_operator, get_user_country
from ..api import SmsHubAPI
from ..utils import get_usd_to_rub_rate, safe_edit_or_send_message
from .start import send_main_menu
from .activation import user_activations, poll_for_sms
from ..constants.operators import OPERATOR_DISPLAY_NAMES
from ..constants.countries import COUNTRIES
from ..constants.services import SERVICE_DISPLAY_NAMES
logger = logging.getLogger(__name__)
class SmsOrderState(StatesGroup):
waiting_for_service = State()
waiting_for_max_price = State()
SERVICES: List[Tuple[str, str]] = list(SERVICE_DISPLAY_NAMES.items())
POPULARITY_FILE = Path("service_popularity.json")
def load_popularity_data() -> dict:
if POPULARITY_FILE.exists():
with open(POPULARITY_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
def save_popularity_data(popularity_data: dict):
with open(POPULARITY_FILE, 'w', encoding='utf-8') as f:
json.dump(popularity_data, f, ensure_ascii=False, indent=4)
def update_service_popularity(service_code: str):
popularity_data = load_popularity_data()
if service_code in popularity_data:
popularity_data[service_code] += 1
else:
popularity_data[service_code] = 1
save_popularity_data(popularity_data)
def get_sorted_services(available_services: List[str]) -> List[Tuple[str, str]]:
popularity_data = load_popularity_data()
sorted_services = sorted(
[(code, name) for code, name in SERVICES if code in available_services],
key=lambda service: popularity_data.get(service[0], 0),
reverse=True
)
return sorted_services
def get_service_pages(services: List[Tuple[str, str]], page_size: int = 18) -> List[List[Tuple[str, str]]]:
pages = []
for i in range(0, len(services), page_size):
pages.append(services[i:i + page_size])
return pages
def build_service_keyboard(page_number: int, available_services: List[str]) -> InlineKeyboardBuilder:
sorted_services = get_sorted_services(available_services)
services_pages = get_service_pages(sorted_services)
keyboard = InlineKeyboardBuilder()
if page_number < len(services_pages):
current_page = services_pages[page_number]
for code, name in current_page:
keyboard.button(text=name, callback_data=f"service_select:{code}")
keyboard.adjust(3)
if len(services_pages) > 1: # Only show navigation buttons if more than one page exists
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}"))
# Include page number information
nav_buttons.append(types.InlineKeyboardButton(text=f"Страница {page_number + 1}/{len(services_pages)}", callback_data="ignore"))
keyboard.row(*nav_buttons)
keyboard.row(types.InlineKeyboardButton(text="🔙 Назад в меню", callback_data="back_to_main_menu"))
return keyboard
def get_service_message_text(total_services_with_numbers: int, total_services_for_country: int, country_name: str, operator_name: str) -> str:
return (
f"🔢 *Общее кол-во сервисов:* `{total_services_for_country}`\n"
f"💼 *Доступные сервисы (где есть номера):* `{total_services_with_numbers}`\n"
f"— Нажмите на кнопку, чтобы выбрать сервис.\n"
f"— Или введите название, чтобы найти сервис.\n\n"
f"🐾 _Текущая страна:_ `{country_name}`\n"
f"📡 _Текущий оператор:_ `{operator_name}`\n"
f"(Эти параметры можно изменить в настройках)"
)
async def fetch_all_services(user_api_key: str, user_country_code: str, state: FSMContext) -> Tuple[Optional[List[str]], int, int]:
smshub_api = SmsHubAPI(user_api_key)
max_attempts = 3
last_exception = None
available_services = []
services_with_numbers = set()
for attempt in range(1, max_attempts + 1):
try:
numbers_status = await smshub_api.get_numbers_status(country=user_country_code)
if numbers_status:
# Все сервисы и сервисы с доступными номерами
available_services = [service.split('_')[0] for service in numbers_status.keys()]
services_with_numbers = {service.split('_')[0] for service, count in numbers_status.items() if int(count) > 0}
break
except Exception as e:
last_exception = e
logger.error(f"Ошибка при запросе статуса всех сервисов, попытка {attempt}: {e}")
data = await state.get_data()
if data.get("cancel_request"):
logger.info("Запрос на получение всех сервисов был отменен.")
return None, 0, 0
progress_text = f"Попытка {attempt}/{max_attempts} получения списка всех сервисов..."
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=data.get('message'),
new_text=progress_text,
state=state,
reply_markup=keyboard.as_markup()
)
await asyncio.sleep(5)
if last_exception:
logger.error(f"Ошибка при запросе всех сервисов: {last_exception}")
return None, 0, 0
return available_services, len(services_with_numbers), len(available_services)
async def get_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
user_api_key = get_user_api_key(callback_query.from_user.id)
user_country_code = get_user_country(callback_query.from_user.id) or "0"
user_operator = get_user_operator(callback_query.from_user.id) or "any"
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} начал процесс получения номера.")
await state.update_data(message=callback_query.message, cancel_request=False)
available_services, total_services_with_numbers, total_services_for_country = await fetch_all_services(user_api_key, user_country_code, state)
country_name = COUNTRIES.get(user_country_code, 'Неизвестно')
operator_name = OPERATOR_DISPLAY_NAMES.get(user_operator, 'Любой')
await state.update_data(total_services_with_numbers=total_services_with_numbers,
total_services_for_country=total_services_for_country, # Обновите здесь
country_name=country_name,
operator_name=operator_name)
if available_services is None:
await send_main_menu(callback_query.message, state)
return
keyboard = build_service_keyboard(page_number=0, available_services=available_services)
message_text = get_service_message_text(total_services_with_numbers, total_services_for_country, country_name, operator_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(SmsOrderState.waiting_for_service)
await state.update_data(available_services=available_services)
async def go_back_to_service_selection(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
logger.info(f"Пользователь {callback_query.from_user.id} отменил операцию.")
await state.update_data(cancel_request=True)
data = await state.get_data()
current_task = data.get('current_task')
if current_task:
current_task.cancel()
# Обновите актуальные данные здесь
available_services = data.get('available_services', [])
total_services_with_numbers = data.get('total_services_with_numbers', len(available_services))
total_services_for_country = data.get('total_services_for_country', len(available_services))
country_name = data.get('country_name', 'Неизвестно')
operator_name = data.get('operator_name', 'Любой')
keyboard = build_service_keyboard(page_number=0, available_services=available_services)
message_text = get_service_message_text(total_services_with_numbers, total_services_for_country, country_name, operator_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(SmsOrderState.waiting_for_service)
async def back_to_main_menu(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
logger.info(f"Пользователь {callback_query.from_user.id} вернулся в главное меню.")
await state.set_state(None)
await send_main_menu(callback_query.message, state)
async def service_page_handler(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
page_number = int(callback_query.data.split(":")[1])
data = await state.get_data()
available_services = data.get('available_services', [])
total_services_with_numbers = data.get('total_services_with_numbers', len(available_services))
total_services_for_country = data.get('total_services_for_country', len(available_services))
country_name = data.get('country_name', 'Неизвестно')
operator_name = data.get('operator_name', 'Любой')
keyboard = build_service_keyboard(page_number, available_services)
message_text = get_service_message_text(total_services_with_numbers, total_services_for_country, country_name, operator_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"
)
async def service_selected_handler(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
service_code = callback_query.data.split(":")[1]
update_service_popularity(service_code)
await state.update_data(selected_service=service_code, cancel_request=False)
task = asyncio.create_task(service_handler_logic(
callback_query.message, state, user_id=callback_query.from_user.id, service_code=service_code, is_service_selection=True))
await state.update_data(current_task=task)
async def service_handler(message: types.Message, state: FSMContext, user_id: Optional[int] = None):
try:
await message.delete()
except Exception as e:
logger.error(f"Не удалось удалить сообщение пользователя: {e}")
user_input = message.text.strip().lower()
if user_id is None:
user_id = message.from_user.id
logger.info(f"Пользователь {user_id} ввел сервис: {user_input}")
data = await state.get_data()
available_services = data.get('available_services', [])
matched_services = [
(code, name) for code, name in SERVICE_DISPLAY_NAMES.items()
if code in available_services and (user_input in name.lower() or user_input == code.lower())
]
if matched_services:
pages = get_service_pages(matched_services)
page_number = 0
keyboard = build_page_keyboard(pages, page_number)
await state.update_data(matched_services=matched_services)
await safe_edit_or_send_message(
message=message,
new_text="Пожалуйста, выберите один из представленных сервисов:",
state=state,
reply_markup=keyboard.as_markup()
)
else:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔙 Назад к сервисам", callback_data="service_page:0")
await safe_edit_or_send_message(
message=message,
new_text="Сервис не найден. Попробуйте ввести код или полное название сервиса.",
state=state,
reply_markup=keyboard.as_markup()
)
def build_page_keyboard(service_pages, page_number: int) -> InlineKeyboardBuilder:
keyboard = InlineKeyboardBuilder()
if page_number < len(service_pages):
current_page = service_pages[page_number]
for code, name in current_page:
keyboard.button(text=name, callback_data=f"service_select:{code}")
keyboard.adjust(3)
if len(service_pages) > 1: # Only show navigation buttons if more than one page exists
nav_buttons = []
if page_number > 0:
nav_buttons.append(types.InlineKeyboardButton(text="⬅️ Назад", callback_data=f"search_page:{page_number - 1}"))
if page_number < len(service_pages) - 1:
nav_buttons.append(types.InlineKeyboardButton(text="➡️ Вперед", callback_data=f"search_page:{page_number + 1}"))
# Include page number information
nav_buttons.append(types.InlineKeyboardButton(text=f"Страница {page_number + 1}/{len(service_pages)}", callback_data="ignore"))
keyboard.row(*nav_buttons)
keyboard.row(types.InlineKeyboardButton(text="🔙 Назад к сервисам", callback_data="service_page:0"))
return keyboard
async def handle_search_page_navigation(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
page_number = int(callback_query.data.split(":")[1])
data = await state.get_data()
matched_services = data.get('matched_services', [])
pages = get_service_pages(matched_services)
keyboard = build_page_keyboard(pages, page_number)
await safe_edit_or_send_message(
message=callback_query.message,
new_text="Пожалуйста, выберите один из представленных сервисов:",
state=state,
reply_markup=keyboard.as_markup()
)
async def service_handler_logic(message: types.Message, state: FSMContext, user_id: int, service_code: str, is_service_selection: bool = False):
logger.info(f"Пользователь {user_id} выбрал сервис: {service_code}")
user_api_key = get_user_api_key(user_id)
user_operator = get_user_operator(user_id) or "any"
user_country_code = get_user_country(user_id) or "0"
country_name = COUNTRIES.get(user_country_code, user_country_code)
if not user_api_key:
await safe_edit_or_send_message(
message=message,
new_text="Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
state=state
)
await state.set_state(None)
await send_main_menu(message, state)
return
smshub_api = SmsHubAPI(user_api_key)
usd_to_rub_rate = await get_usd_to_rub_rate()
max_attempts = 3
success = False
last_exception = None
try:
for attempt in range(1, max_attempts + 1):
data = await state.get_data()
if data.get("cancel_request"):
logger.info(f"Запрос для пользователя {user_id} был отменен.")
return
try:
progress_text = f"Идет запрос {attempt}/{max_attempts}..."
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=message,
new_text=progress_text,
state=state,
reply_markup=keyboard.as_markup()
)
price_info = await smshub_api.get_price_for_service(service_code, country=user_country_code)
numbers_status = await smshub_api.get_numbers_status(country=user_country_code, operator=user_operator)
if data.get("cancel_request"):
logger.info(f"Запрос для пользователя {user_id} был отменен после получения данных.")
return
if price_info and numbers_status:
service_key = f"{service_code}_0"
available_numbers = int(numbers_status.get(service_key, 0) or 0)
if available_numbers > 0:
prices = [float(price_str) for price_str in price_info.keys() if price_str is not None]
if prices:
min_price_usd = min(prices)
max_price_usd = max(prices)
min_price_rub = min_price_usd * usd_to_rub_rate if usd_to_rub_rate else 0
max_price_rub = max_price_usd * usd_to_rub_rate if usd_to_rub_rate else 0
operator_display = OPERATOR_DISPLAY_NAMES.get(user_operator, user_operator)
service_display = SERVICE_DISPLAY_NAMES.get(service_code, service_code)
keyboard = InlineKeyboardBuilder()
keyboard.button(text=f"⬇️ Мин. цена ({min_price_usd:.4f}$)", callback_data=f"select_price:{min_price_usd}")
keyboard.button(text=f"⬆️ Макс. цена ({max_price_usd:.4f}$)", callback_data=f"select_price:{max_price_usd}")
keyboard.adjust(2)
keyboard.row(types.InlineKeyboardButton(text="❌ Отмена", callback_data="cancel_operation"))
message_text = (
f"*Страна:* `{country_name}`\n"
f"*Доступно номеров:* `{available_numbers}`\n"
f"*Оператор:* `{operator_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"_Выберите максимальную допустимую стоимость покупки с помощью кнопок ниже или введите сумму вручную (например: 0.5). Все суммы указаны в долларах ($):_"
)
await safe_edit_or_send_message(
message=message,
new_text=message_text,
state=state,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
await state.update_data(
service=service_code,
service_display=service_display,
country_name=country_name,
available_numbers=available_numbers,
operator_display=operator_display,
min_price=min_price_usd,
max_price_usd=max_price_usd,
min_price_rub=min_price_rub,
max_price_rub=max_price_rub,
usd_to_rub_rate=usd_to_rub_rate,
user_id=user_id
)
await state.set_state(SmsOrderState.waiting_for_max_price)
success = True
break
else:
logger.info(f"Попытка {attempt}: Цены не найдены.")
last_exception = "Цены не найдены"
else:
logger.info(f"Попытка {attempt}: Нет доступных номеров.")
last_exception = "Нет доступных номеров"
except Exception as e:
logger.error(f"Ошибка при получении данных для сервиса '{service_code}', попытка {attempt}: {e}")
last_exception = e
await asyncio.sleep(5)
except asyncio.CancelledError:
logger.info(f"Задача для пользователя {user_id} была отменена.")
return
if not success:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="Назад", callback_data="service_page:0")
await safe_edit_or_send_message(
message=message,
new_text=f"Произошла ошибка при обработке запроса: {last_exception}. Попробуйте снова.",
state=state,
reply_markup=keyboard.as_markup()
)
async def price_selected_handler(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.answer()
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
price = float(callback_query.data.split(":")[1])
await state.update_data(max_price=price)
await process_number_purchase(callback_query.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', message.from_user.id)
try:
await message.delete()
except Exception as e:
logger.error(f"Не удалось удалить сообщение пользователя: {e}")
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', 0)
max_price_usd = user_data.get('max_price_usd', 0)
min_price_rub = user_data.get('min_price_rub', 0)
max_price_rub = user_data.get('max_price_rub', 0)
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=message,
new_text=(
f"Пожалуйста, введите корректное число для максимальной стоимости. "
f"От `{min_price:.4f}` $ (₽{min_price_rub:.2f}) до `{max_price_usd:.4f}` $ (₽{max_price_rub:.2f})."
),
state=state,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
return
if max_price < data.get('min_price', 0):
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=message,
new_text=f"Максимальная стоимость должна быть не менее `{data.get('min_price', 0):.4f}` $.",
state=state,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
return
await state.update_data(max_price=max_price)
await process_number_purchase(message, state)
async def process_number_purchase(message: types.Message, state: FSMContext):
user_data = await state.get_data()
user_id = user_data.get('user_id', message.from_user.id)
service = user_data['service']
service_display = user_data['service_display']
country_name = user_data['country_name']
operator_display = user_data['operator_display']
max_price = user_data['max_price']
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"
max_attempts = 3
last_exception = None
try:
for attempt in range(1, max_attempts + 1):
data = await state.get_data()
if data.get("cancel_request"):
logger.info(f"Запрос на получение номера для пользователя {user_id} был отменен.")
return
progress_text = f"Идет запрос {attempt}/{max_attempts} на получение номера..."
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_or_send_message(
message=message,
new_text=progress_text,
state=state,
reply_markup=keyboard.as_markup()
)
user_operator = get_user_operator(user_id) or "any"
if data.get("cancel_request"):
logger.info(f"Запрос на получение номера для пользователя {user_id} был отменен перед выполнением.")
return
result = await smshub_api.get_number(service, country=user_country_code, max_price=max_price, operator=user_operator)
if data.get("cancel_request"):
logger.info(f"Запрос на получение номера для пользователя {user_id} был отменен.")
return
if result and result.startswith("ACCESS_NUMBER"):
_, activation_id, number = result.split(":")
logger.info(f"Пользователь {user_id} получил номер: {number}, активация ID: {activation_id}")
if user_id not in user_activations:
user_activations[user_id] = {}
user_activations[user_id][activation_id] = {
'number': number,
'status': 'Ожидание SMS',
'service': service,
'service_display': service_display,
'country_name': country_name,
'last_update': time.time(),
'task': None
}
keyboard = InlineKeyboardBuilder()
keyboard.button(text="📂 Текущие активации", callback_data="current_activations")
keyboard.button(text="❌ Отменить активацию", callback_data=f"cancel_activation:{activation_id}")
keyboard.button(text="🔙 Главное меню", callback_data="back_to_main_menu")
keyboard.adjust(1)
message_text = (
f"*Получен номер:* `{number}`\n"
f"*Страна:* `{country_name}`\n"
f"*Оператор:* `{operator_display}`\n"
f"*Сервис:* `{service_display}`\n"
f"*Максимальная стоимость:* `{max_price:.2f}` $ (₽{max_price * user_data['usd_to_rub_rate']:.2f})\n"
f"*Статус:* *Ожидание SMS*\n"
f"Вы можете управлять активацией или вернуться в главное меню."
)
sent_message = await safe_edit_or_send_message(
message=message,
new_text=message_text,
state=state,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
user_activations[user_id][activation_id]['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
return
elif result == "NO_NUMBERS":
logger.info(f"Попытка {attempt} из {max_attempts}: нет доступных номеров.")
last_exception = result
if attempt == max_attempts:
suggestion = (
"Попробуйте выбрать 'Любой оператор' в настройках или увеличить максимальную цену."
if user_operator != "any" else
"Попробуйте увеличить максимальную цену или повторите попытку позже."
)
keyboard = InlineKeyboardBuilder()
keyboard.button(text="Назад", callback_data="service_page:0")
await safe_edit_or_send_message(
message=message,
new_text=f"Нет доступных номеров для оператора {operator_display} по данной цене.\n{suggestion}",
state=state,
reply_markup=keyboard.as_markup()
)
return
else:
raise Exception("Неожиданный ответ API")
await asyncio.sleep(5)
except asyncio.CancelledError:
logger.info(f"Задача для пользователя {user_id} была отменена.")
return
if last_exception and last_exception != "NO_NUMBERS":
keyboard = InlineKeyboardBuilder()
keyboard.button(text="Назад", callback_data="service_page:0")
await safe_edit_or_send_message(
message=message,
new_text=f"Произошла ошибка при обработке запроса: {last_exception}. Попробуйте снова.",
state=state,
reply_markup=keyboard.as_markup()
)
# Add a handler for the "ignore" callback to silently ignore it
async def ignore_callback_handler(callback_query: types.CallbackQuery):
try:
await callback_query.answer() # Just answer to confirm the action
except Exception as e:
logger.warning(f"Не удалось ответить на callback_query: {e}")
def register_sms_handlers(dp: Dispatcher):
dp.callback_query.register(get_sms_handler, F.data == "get_sms")
dp.callback_query.register(go_back_to_service_selection, F.data == "cancel_operation")
dp.callback_query.register(back_to_main_menu, F.data == "back_to_main_menu")
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.callback_query.register(handle_search_page_navigation, F.data.startswith("search_page:"))
dp.callback_query.register(price_selected_handler, F.data.startswith("select_price:"))
dp.callback_query.register(ignore_callback_handler, F.data == "ignore")
dp.message.register(service_handler, SmsOrderState.waiting_for_service)
dp.message.register(max_price_handler, SmsOrderState.waiting_for_max_price)
```
### Key Modifications
- **Page Navigation Information**:
- Each navigation button row now includes a button labeled with the current page and total page count, e.g., "Страница 1/3". This provides users with context about their position in the list of services.
- **Ignore Callback Handler**:
- A handler (`ignore_callback_handler`) has been added to answer `callback_query` that are meant solely for displaying page information without triggering any action.
These enhancements should provide a better user interface, making navigation more intuitive and informative.