turns-00031.parquet:35904
e6bcd067e41e6d627ce9dce2
turn 1/1o1-preview-2024-09-12EnglishRussia4102 words
degenerate_repetitionAbsentFinal dense release
USER
так, смотри, есть у меня код бота, нужно в нем внести некие правки. когда нажимаешь получить смс, то появлется "Введите сервис для получения номера (например, wa для WhatsApp):", с одной кнопкой "Отмена", даавай сюда добавим вторую кнопку, давай сделаем их в один ряд друг с другом и вторая кнопка это будет кнопка, который при нажатии будет открывать ссылку https://teletype.in/@diagzone/NQd96Y8cPQe для просмотра доступных сервисов, т.е. по факту ничего она делать для бота не будет, просто при ее нажатии октырвается ссылка и пришли мне полный сиправленный код:
import asyncio
import logging
import time
from datetime import datetime
from typing import Optional, Dict
import aiohttp
from aiogram import Bot, Router, types, F
from aiogram.exceptions import TelegramBadRequest, TelegramAPIError
from aiogram.filters.command import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from .api import SmsHubAPI # Убедитесь, что путь импорта корректен
from .data import (
get_user_api_key,
set_user_api_key,
delete_user_api_key
)
EXCHANGE_RATE_API_URL = "https://api.exchangerate-api.com/v4/latest/USD"
USD_TO_RUB_RATE_TTL = 3600 # Кэшируем курс на 1 час
router = Router()
logger = logging.getLogger(__name__)
# Переменные для кэширования курса
_cached_usd_to_rub_rate = None
_cached_usd_to_rub_rate_time = 0
# Словарь для хранения активных активаций пользователей: {user_id: {activation_id: ActivationData}}
user_activations: Dict[int, Dict[str, Dict]] = {}
async def get_usd_to_rub_rate() -> Optional[float]:
"""Получить текущий курс USD к RUB с кэшированием."""
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 не найден в ответе.")
return None
except Exception as e:
logger.error(f"Ошибка при запросе курса валют: {e}")
return None
class UserSettings(StatesGroup):
entering_api_key = State()
class SmsOrderState(StatesGroup):
waiting_for_service = State()
waiting_for_max_price = State()
# Хелпер-функция для безопасного редактирования сообщения
async def safe_edit_message(message: types.Message, new_text: str, reply_markup=None, parse_mode=None):
try:
await message.edit_text(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
except (TelegramBadRequest, TelegramAPIError):
await message.answer(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
@router.message(Command('start'))
async def start_handler(message: types.Message):
logger.info(f"Пользователь {message.from_user.id} запустил бота.")
await send_main_menu(message)
async def send_main_menu(message: types.Message):
logger.info(f"Пользователь {message.from_user.id} вернулся в главное меню.")
keyboard = InlineKeyboardBuilder()
keyboard.button(text="💰 Проверить баланс", callback_data="check_balance")
keyboard.button(text="📲 Получить SMS", callback_data="get_sms")
keyboard.button(text="📋 Текущие активации", callback_data="current_activations")
keyboard.button(text="⚙️ Настройки", callback_data="settings")
keyboard.adjust(1)
await safe_edit_message(
message,
"Добро пожаловать! Выберите действие:",
reply_markup=keyboard.as_markup()
)
@router.callback_query(F.data == "settings")
async def settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
user_api_key = await get_user_api_key(callback_query.from_user.id)
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="back_to_main_menu")
keyboard.adjust(1)
message_text = (
f"Ваш текущий API ключ: `{user_api_key}`" if user_api_key else "API ключ не настроен."
)
await safe_edit_message(
callback_query.message,
message_text,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
await state.clear()
@router.callback_query(F.data.in_({"edit_api_key", "add_api_key"}))
async def input_api_key_handler(callback_query: types.CallbackQuery, state: FSMContext):
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_settings")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Введите ваш API ключ для работы с сервисом:",
reply_markup=keyboard.as_markup()
)
await state.set_state(UserSettings.entering_api_key)
@router.callback_query(F.data == "delete_api_key")
async def delete_api_key_handler(callback_query: types.CallbackQuery):
await delete_user_api_key(callback_query.from_user.id)
await safe_edit_message(
callback_query.message,
"API ключ успешно удалён."
)
await send_main_menu(callback_query.message)
@router.callback_query(F.data == "back_to_main_menu")
async def back_to_main_menu_handler(callback_query: types.CallbackQuery, state: FSMContext):
await state.clear()
await send_main_menu(callback_query.message)
@router.callback_query(F.data == "cancel_settings")
async def cancel_settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
await state.clear()
# Возвращаем пользователя в меню настроек
await settings_handler(callback_query, state)
@router.message(UserSettings.entering_api_key)
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]
await set_user_api_key(message.from_user.id, user_api_key)
await message.answer(f"Ваш API ключ успешно сохранён и проверен! Баланс: {balance}")
await state.clear()
await send_main_menu(message)
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()
)
@router.callback_query(F.data == "check_balance")
async def balance_handler(callback_query: types.CallbackQuery):
user_api_key = await get_user_api_key(callback_query.from_user.id)
if not user_api_key:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="⚙️ Настройки", callback_data="settings")
keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
reply_markup=keyboard.as_markup()
)
return
smshub_api = SmsHubAPI(user_api_key)
logger.info(f"Пользователь {callback_query.from_user.id} запросил баланс.")
balance_response = await smshub_api.get_balance()
usd_to_rub_rate = await get_usd_to_rub_rate()
if balance_response and balance_response.startswith("ACCESS_BALANCE:"):
balance_usd = float(balance_response.split(":")[1])
balance_rub = balance_usd * usd_to_rub_rate if usd_to_rub_rate else "недоступно"
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
message_text = (
f"Ваш баланс: ${balance_usd:.4f} (приблизительно ₽{balance_rub:.2f}) на {current_time}"
)
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔄 Обновить баланс", callback_data="check_balance")
keyboard.button(text="🔙 Вернуться в меню", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
message_text,
reply_markup=keyboard.as_markup()
)
else:
logger.error(f"Не удалось получить баланс. Ответ: {balance_response}")
await safe_edit_message(
callback_query.message,
"Не удалось получить баланс. Попробуйте снова.",
reply_markup=callback_query.message.reply_markup
)
@router.callback_query(F.data == "get_sms")
async def get_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
user_api_key = await get_user_api_key(callback_query.from_user.id)
if not user_api_key:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="⚙️ Настройки", callback_data="settings")
keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
reply_markup=keyboard.as_markup()
)
return
logger.info(f"Пользователь {callback_query.from_user.id} начал процесс получения номера.")
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Введите сервис для получения номера (например, wa для WhatsApp):",
reply_markup=keyboard.as_markup()
)
await state.set_state(SmsOrderState.waiting_for_service)
@router.callback_query(F.data == "cancel_operation")
async def cancel_operation_handler(callback_query: types.CallbackQuery, state: FSMContext):
logger.info(f"Пользователь {callback_query.from_user.id} отменил операцию.")
await state.clear()
await send_main_menu(callback_query.message)
@router.message(SmsOrderState.waiting_for_service)
async def service_handler(message: types.Message, state: FSMContext):
service = message.text.strip().lower()
logger.info(f"Пользователь {message.from_user.id} выбрал сервис: {service}")
user_api_key = await get_user_api_key(message.from_user.id)
smshub_api = SmsHubAPI(user_api_key)
country_code = "0"
usd_to_rub_rate = await get_usd_to_rub_rate()
try:
price_info = await smshub_api.get_price_for_service(service, country=country_code)
if not price_info:
await message.answer("Сервис недоступен или введён неверно. Пожалуйста, попробуйте снова.")
await state.clear()
await send_main_menu(message)
return
prices = [float(price) for price in price_info.keys()]
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)
await message.answer(
f"Текущие цены для сервиса {service}: от ${min_price_usd:.4f} (₽{min_price_rub:.2f}) "
f"до ${max_price_usd:.4f} (₽{max_price_rub:.2f}). Введите максимальную стоимость в $ "
f"(например, 10):",
reply_markup=keyboard.as_markup()
)
await state.update_data(service=service, min_price=min_price_usd)
await state.set_state(SmsOrderState.waiting_for_max_price)
except Exception as e:
logger.error(f"Ошибка при получении цен для сервиса '{service}': {e}")
await message.answer("Произошла ошибка при получении цен. Попробуйте снова.")
await state.clear()
await send_main_menu(message)
@router.message(SmsOrderState.waiting_for_max_price)
async def max_price_handler(message: types.Message, state: FSMContext):
max_price_input = message.text.strip()
logger.info(f"Пользователь {message.from_user.id} указал максимальную стоимость: {max_price_input}")
try:
max_price = float(max_price_input)
except ValueError:
await message.answer("Пожалуйста, введите корректное число для максимальной стоимости.")
return
user_data = await state.get_data()
service = user_data['service']
min_price = user_data['min_price']
user_api_key = await get_user_api_key(message.from_user.id)
smshub_api = SmsHubAPI(user_api_key)
country_code = "0"
if max_price < min_price:
await message.answer(f"Максимальная стоимость должна быть не менее {min_price:.4f} $.")
return
result = await smshub_api.get_number(service, country=country_code, max_price=max_price)
if result and result.startswith("ACCESS_NUMBER"):
_, activation_id, number = result.split(":")
logger.info(f"Пользователь {message.from_user.id} получил номер: {number}, активация ID: {activation_id}")
if message.from_user.id not in user_activations:
user_activations[message.from_user.id] = {}
user_activations[message.from_user.id][activation_id] = {
'number': number,
'service': service,
'status': 'Ожидание SMS',
'task': None,
'last_update': time.time()
}
keyboard = InlineKeyboardBuilder()
keyboard.button(text="📋 Управлять активацией", callback_data=f"manage_activation:{activation_id}")
keyboard.button(text="🔙 Главное меню", callback_data="back_to_main_menu")
keyboard.adjust(1)
await message.answer(
f"Получен номер: {number}. Ожидание SMS...\nВы можете управлять активацией или вернуться в главное меню.",
reply_markup=keyboard.as_markup()
)
await state.clear()
task = asyncio.create_task(poll_for_sms(
bot=message.bot,
user_id=message.from_user.id,
chat_id=message.chat.id,
activation_id=activation_id,
smshub_api=smshub_api
))
user_activations[message.from_user.id][activation_id]['task'] = task
else:
logger.error(f"Не удалось получить номер. Ответ: {result}")
await message.answer("Не удалось получить номер. Попробуйте снова.")
await state.clear()
await send_main_menu(message)
@router.callback_query(F.data == "current_activations")
async def current_activations_handler(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
activations = user_activations.get(user_id, {})
if not activations:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"У вас нет активных активаций.",
reply_markup=keyboard.as_markup()
)
return
keyboard = InlineKeyboardBuilder()
for activation_id, data in activations.items():
keyboard.button(
text=f"{data['service']} ({data['number']}) - {data['status']}",
callback_data=f"manage_activation:{activation_id}"
)
keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Ваши текущие активации:",
reply_markup=keyboard.as_markup()
)
@router.callback_query(F.data.startswith("manage_activation"))
async def manage_activation_handler(callback_query: types.CallbackQuery):
user_id = 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_message(
callback_query.message,
"Активация не найдена.",
reply_markup=callback_query.message.reply_markup
)
return
keyboard = InlineKeyboardBuilder()
# Формируем клавиатуру в зависимости от статуса активации
if activation['status'] == 'Ожидание SMS':
# Только отменить активацию
keyboard.button(text="❌ Отменить активацию", callback_data=f"cancel_activation:{activation_id}")
elif activation['status'].startswith('Код получен'):
# Запросить еще SMS или завершить активацию
keyboard.button(text="🔄 Запросить еще SMS", callback_data=f"request_another_sms:{activation_id}")
keyboard.button(text="✅ Завершить активацию", callback_data=f"complete_activation:{activation_id}")
elif activation['status'].startswith('Ожидание повторного SMS'):
# Завершить или отменить активацию
keyboard.button(text="✅ Завершить активацию", callback_data=f"complete_activation:{activation_id}")
keyboard.button(text="❌ Отменить активацию", callback_data=f"cancel_activation:{activation_id}")
else:
# Другие статусы
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="current_activations")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
f"Активация {activation_id}:\nСервис: {activation['service']}\nНомер: {activation['number']}\nСтатус: {activation['status']}",
reply_markup=keyboard.as_markup()
)
@router.callback_query(F.data.startswith("cancel_activation"))
async def cancel_activation_handler(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
activation_id = callback_query.data.split(":")[1]
user_api_key = await 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.get('task')
if task:
task.cancel()
# Отменяем активацию на стороне API
result = await smshub_api.cancel_activation(activation_id)
logger.info(f"Активация {activation_id} отменена на стороне API: {result}")
# Удаляем активацию из списка
del activations[activation_id]
# Создаём новую клавиатуру с кнопкой "🔙 Назад"
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔙 Назад", callback_data="current_activations")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Активация успешно отменена.",
reply_markup=keyboard.as_markup()
)
else:
await safe_edit_message(
callback_query.message,
"Активация не найдена.",
reply_markup=callback_query.message.reply_markup
)
@router.callback_query(F.data.startswith("request_another_sms"))
async def request_another_sms_handler(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
activation_id = callback_query.data.split(":")[1]
user_api_key = await get_user_api_key(user_id)
smshub_api = SmsHubAPI(user_api_key)
activations = user_activations.get(user_id, {})
activation = activations.get(activation_id)
if not activation:
await safe_edit_message(
callback_query.message,
"Активация не найдена.",
reply_markup=callback_query.message.reply_markup
)
return
result = await smshub_api.set_status(activation_id, status="3")
if result == "ACCESS_RETRY_GET":
# Отменяем предыдущую задачу, если она есть
task = activation.get('task')
if task:
task.cancel()
# Запускаем новую задачу
task = asyncio.create_task(poll_for_sms(
bot=callback_query.message.bot,
user_id=user_id,
chat_id=callback_query.from_user.id,
activation_id=activation_id,
smshub_api=smshub_api
))
activation['task'] = task
activation['status'] = 'Ожидание повторного SMS'
await safe_edit_message(
callback_query.message,
"Запрошен повторный SMS. Ожидание SMS...",
reply_markup=callback_query.message.reply_markup
)
else:
logger.error(f"Не удалось запросить повторное SMS. Ответ: {result}")
await safe_edit_message(
callback_query.message,
"Не удалось запросить повторный SMS. Попробуйте снова.",
reply_markup=callback_query.message.reply_markup
)
@router.callback_query(F.data.startswith("complete_activation"))
async def complete_activation_handler(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
activation_id = callback_query.data.split(":")[1]
user_api_key = await get_user_api_key(user_id)
smshub_api = SmsHubAPI(user_api_key)
activations = user_activations.get(user_id, {})
activation = activations.get(activation_id)
if not activation:
await safe_edit_message(
callback_query.message,
"Активация не найдена.",
reply_markup=callback_query.message.reply_markup
)
return
result = await smshub_api.set_status(activation_id, status="6")
if result == "ACCESS_ACTIVATION":
# Отменяем задачу, если она есть
task = activation.get('task')
if task:
task.cancel()
# Удаляем активацию из списка
del activations[activation_id]
# Создаём новую клавиатуру с кнопкой "🔙 Назад"
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔙 Назад", callback_data="current_activations")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Активация успешно завершена.",
reply_markup=keyboard.as_markup()
)
else:
logger.error(f"Не удалось завершить активацию. Ответ: {result}")
await safe_edit_message(
callback_query.message,
"Не удалось завершить активацию. Попробуйте снова.",
reply_markup=callback_query.message.reply_markup
)
async def poll_for_sms(bot: Bot, user_id: int, chat_id: int, activation_id: str, smshub_api: SmsHubAPI):
activations = user_activations.get(user_id, {})
activation = activations.get(activation_id)
if not activation:
logger.error(f"Активация {activation_id} не найдена для пользователя {user_id}.")
return
try:
max_wait_time = 300 # Максимальное время ожидания в секундах
poll_interval = 3 # Интервал опроса в секундах
elapsed_time = 0
while elapsed_time < max_wait_time:
await asyncio.sleep(poll_interval)
elapsed_time += poll_interval
status_response = await smshub_api.get_status(activation_id)
if not status_response:
logger.error("Нет ответа при запросе статуса активации.")
await bot.send_message(chat_id, "Не удалось получить статус активации. Попробуйте позже.")
return
status_parts = status_response.split(":", 1)
status = status_parts[0]
if status == "STATUS_WAIT_CODE":
logger.debug(f"Ожидание SMS кода для активации {activation_id}.")
activation['last_update'] = time.time()
continue
elif status.startswith("STATUS_WAIT_RETRY"):
last_code = status_parts[1] if len(status_parts) > 1 else "нет предыдущего кода"
logger.info(f"Ожидание повторного SMS кода, последний код: {last_code}")
activation['status'] = f'Ожидание повторного SMS (последний код: {last_code})'
activation['last_update'] = time.time()
continue
elif status == "STATUS_CANCEL":
await bot.send_message(chat_id, "Активация была отменена.")
activation['status'] = 'Активация отменена'
return
elif status == "STATUS_OK":
code = status_parts[1] if len(status_parts) > 1 else "Код не получен"
logger.info(f"Получен код активации для {activation_id}: {code}")
activation['status'] = f'Код получен: {code}'
activation['last_update'] = time.time()
await bot.send_message(
chat_id,
f"Код активации для номера {activation['number']}: {code}"
)
return
else:
logger.error(f"Непредвиденный статус активации: {status_response}")
activation['status'] = f'Неизвестный статус: {status}'
await bot.send_message(chat_id, f"Неизвестный статус активации: {status}")
return
# Если время ожидания истекло, функция завершается без изменения статуса активации
except asyncio.CancelledError:
logger.info(f"Задача poll_for_sms для активации {activation_id} была отменена.")
except Exception as e:
logger.error(f"Ошибка в poll_for_sms: {e}")
activation['status'] = 'Ошибка при получении SMS'
await bot.send_message(chat_id, "Произошла ошибка при получении SMS.")
# Фоновая задача для периодической проверки статусов активаций
async def check_activations_status():
while True:
await asyncio.sleep(300) # Проверяем каждые 5 минут
logger.info("Проверка статусов активных активаций...")
for user_id, activations in user_activations.copy().items():
for activation_id, activation in activations.copy().items():
try:
user_api_key = await get_user_api_key(user_id)
if not user_api_key:
continue
smshub_api = SmsHubAPI(user_api_key)
status_response = await smshub_api.get_status(activation_id)
if not status_response:
continue
status_parts = status_response.split(":", 1)
status = status_parts[0]
if status == "STATUS_CANCEL":
# Удаляем активацию
logger.info(f"Активация {activation_id} была отменена на стороне сервиса.")
task = activation.get('task')
if task:
task.cancel()
del activations[activation_id]
elif activation['status'].startswith("Код получен"):
# Проверяем, прошло ли достаточно времени
elapsed_since_update = time.time() - activation.get('last_update', 0)
if elapsed_since_update > 1800: # 30 минут
logger.info(f"Активация {activation_id} завершена по истечении времени.")
task = activation.get('task')
if task:
task.cancel()
del activations[activation_id]
except Exception as e:
logger.error(f"Ошибка при проверке активации {activation_id}: {e}")
# Функция для запуска фоновых задач при старте бота
async def on_startup(dispatcher):
asyncio.create_task(check_activations_status())
ASSISTANT
Вот обновленный код бота с внесенными правками. Теперь при нажатии на кнопку "Получить SMS" появляется сообщение "Введите сервис для получения номера (например, wa для WhatsApp):" с двумя кнопками в одном ряду: "❌ Отмена" и "🛠️ Доступные сервисы". При нажатии на кнопку "🛠️ Доступные сервисы" будет открываться указанная вами ссылка.
```python
import asyncio
import logging
import time
from datetime import datetime
from typing import Optional, Dict
import aiohttp
from aiogram import Bot, Router, types, F
from aiogram.exceptions import TelegramBadRequest, TelegramAPIError
from aiogram.filters.command import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from .api import SmsHubAPI # Убедитесь, что путь импорта корректен
from .data import (
get_user_api_key,
set_user_api_key,
delete_user_api_key
)
EXCHANGE_RATE_API_URL = "https://api.exchangerate-api.com/v4/latest/USD"
USD_TO_RUB_RATE_TTL = 3600 # Кэшируем курс на 1 час
router = Router()
logger = logging.getLogger(__name__)
# Переменные для кэширования курса
_cached_usd_to_rub_rate = None
_cached_usd_to_rub_rate_time = 0
# Словарь для хранения активных активаций пользователей: {user_id: {activation_id: ActivationData}}
user_activations: Dict[int, Dict[str, Dict]] = {}
async def get_usd_to_rub_rate() -> Optional[float]:
"""Получить текущий курс USD к RUB с кэшированием."""
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 не найден в ответе.")
return None
except Exception as e:
logger.error(f"Ошибка при запросе курса валют: {e}")
return None
class UserSettings(StatesGroup):
entering_api_key = State()
class SmsOrderState(StatesGroup):
waiting_for_service = State()
waiting_for_max_price = State()
# Хелпер-функция для безопасного редактирования сообщения
async def safe_edit_message(message: types.Message, new_text: str, reply_markup=None, parse_mode=None):
try:
await message.edit_text(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
except (TelegramBadRequest, TelegramAPIError):
await message.answer(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
@router.message(Command('start'))
async def start_handler(message: types.Message):
logger.info(f"Пользователь {message.from_user.id} запустил бота.")
await send_main_menu(message)
async def send_main_menu(message: types.Message):
logger.info(f"Пользователь {message.from_user.id} вернулся в главное меню.")
keyboard = InlineKeyboardBuilder()
keyboard.button(text="💰 Проверить баланс", callback_data="check_balance")
keyboard.button(text="📲 Получить SMS", callback_data="get_sms")
keyboard.button(text="📋 Текущие активации", callback_data="current_activations")
keyboard.button(text="⚙️ Настройки", callback_data="settings")
keyboard.adjust(1)
await safe_edit_message(
message,
"Добро пожаловать! Выберите действие:",
reply_markup=keyboard.as_markup()
)
@router.callback_query(F.data == "settings")
async def settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
user_api_key = await get_user_api_key(callback_query.from_user.id)
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="back_to_main_menu")
keyboard.adjust(1)
message_text = (
f"Ваш текущий API ключ: `{user_api_key}`" if user_api_key else "API ключ не настроен."
)
await safe_edit_message(
callback_query.message,
message_text,
reply_markup=keyboard.as_markup(),
parse_mode="Markdown"
)
await state.clear()
@router.callback_query(F.data.in_({"edit_api_key", "add_api_key"}))
async def input_api_key_handler(callback_query: types.CallbackQuery, state: FSMContext):
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_settings")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Введите ваш API ключ для работы с сервисом:",
reply_markup=keyboard.as_markup()
)
await state.set_state(UserSettings.entering_api_key)
@router.callback_query(F.data == "delete_api_key")
async def delete_api_key_handler(callback_query: types.CallbackQuery):
await delete_user_api_key(callback_query.from_user.id)
await safe_edit_message(
callback_query.message,
"API ключ успешно удалён."
)
await send_main_menu(callback_query.message)
@router.callback_query(F.data == "back_to_main_menu")
async def back_to_main_menu_handler(callback_query: types.CallbackQuery, state: FSMContext):
await state.clear()
await send_main_menu(callback_query.message)
@router.callback_query(F.data == "cancel_settings")
async def cancel_settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
await state.clear()
# Возвращаем пользователя в меню настроек
await settings_handler(callback_query, state)
@router.message(UserSettings.entering_api_key)
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]
await set_user_api_key(message.from_user.id, user_api_key)
await message.answer(f"Ваш API ключ успешно сохранён и проверен! Баланс: {balance}")
await state.clear()
await send_main_menu(message)
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()
)
@router.callback_query(F.data == "check_balance")
async def balance_handler(callback_query: types.CallbackQuery):
user_api_key = await get_user_api_key(callback_query.from_user.id)
if not user_api_key:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="⚙️ Настройки", callback_data="settings")
keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
reply_markup=keyboard.as_markup()
)
return
smshub_api = SmsHubAPI(user_api_key)
logger.info(f"Пользователь {callback_query.from_user.id} запросил баланс.")
balance_response = await smshub_api.get_balance()
usd_to_rub_rate = await get_usd_to_rub_rate()
if balance_response and balance_response.startswith("ACCESS_BALANCE:"):
balance_usd = float(balance_response.split(":")[1])
balance_rub = balance_usd * usd_to_rub_rate if usd_to_rub_rate else "недоступно"
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
message_text = (
f"Ваш баланс: ${balance_usd:.4f} (приблизительно ₽{balance_rub:.2f}) на {current_time}"
)
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔄 Обновить баланс", callback_data="check_balance")
keyboard.button(text="🔙 Вернуться в меню", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
message_text,
reply_markup=keyboard.as_markup()
)
else:
logger.error(f"Не удалось получить баланс. Ответ: {balance_response}")
await safe_edit_message(
callback_query.message,
"Не удалось получить баланс. Попробуйте снова.",
reply_markup=callback_query.message.reply_markup
)
@router.callback_query(F.data == "get_sms")
async def get_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
user_api_key = await get_user_api_key(callback_query.from_user.id)
if not user_api_key:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="⚙️ Настройки", callback_data="settings")
keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
reply_markup=keyboard.as_markup()
)
return
logger.info(f"Пользователь {callback_query.from_user.id} начал процесс получения номера.")
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.button(text="🛠️ Доступные сервисы", url="https://teletype.in/@diagzone/NQd96Y8cPQe")
keyboard.adjust(2) # Размещаем две кнопки в одном ряду
await safe_edit_message(
callback_query.message,
"Введите сервис для получения номера (например, wa для WhatsApp):",
reply_markup=keyboard.as_markup()
)
await state.set_state(SmsOrderState.waiting_for_service)
@router.callback_query(F.data == "cancel_operation")
async def cancel_operation_handler(callback_query: types.CallbackQuery, state: FSMContext):
logger.info(f"Пользователь {callback_query.from_user.id} отменил операцию.")
await state.clear()
await send_main_menu(callback_query.message)
@router.message(SmsOrderState.waiting_for_service)
async def service_handler(message: types.Message, state: FSMContext):
service = message.text.strip().lower()
logger.info(f"Пользователь {message.from_user.id} выбрал сервис: {service}")
user_api_key = await get_user_api_key(message.from_user.id)
smshub_api = SmsHubAPI(user_api_key)
country_code = "0"
usd_to_rub_rate = await get_usd_to_rub_rate()
try:
price_info = await smshub_api.get_price_for_service(service, country=country_code)
if not price_info:
await message.answer("Сервис недоступен или введён неверно. Пожалуйста, попробуйте снова.")
await state.clear()
await send_main_menu(message)
return
prices = [float(price) for price in price_info.keys()]
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)
await message.answer(
f"Текущие цены для сервиса {service}: от ${min_price_usd:.4f} (₽{min_price_rub:.2f}) "
f"до ${max_price_usd:.4f} (₽{max_price_rub:.2f}). Введите максимальную стоимость в $ "
f"(например, 10):",
reply_markup=keyboard.as_markup()
)
await state.update_data(service=service, min_price=min_price_usd)
await state.set_state(SmsOrderState.waiting_for_max_price)
except Exception as e:
logger.error(f"Ошибка при получении цен для сервиса '{service}': {e}")
await message.answer("Произошла ошибка при получении цен. Попробуйте снова.")
await state.clear()
await send_main_menu(message)
@router.message(SmsOrderState.waiting_for_max_price)
async def max_price_handler(message: types.Message, state: FSMContext):
max_price_input = message.text.strip()
logger.info(f"Пользователь {message.from_user.id} указал максимальную стоимость: {max_price_input}")
try:
max_price = float(max_price_input)
except ValueError:
await message.answer("Пожалуйста, введите корректное число для максимальной стоимости.")
return
user_data = await state.get_data()
service = user_data['service']
min_price = user_data['min_price']
user_api_key = await get_user_api_key(message.from_user.id)
smshub_api = SmsHubAPI(user_api_key)
country_code = "0"
if max_price < min_price:
await message.answer(f"Максимальная стоимость должна быть не менее {min_price:.4f} $.")
return
result = await smshub_api.get_number(service, country=country_code, max_price=max_price)
if result and result.startswith("ACCESS_NUMBER"):
_, activation_id, number = result.split(":")
logger.info(f"Пользователь {message.from_user.id} получил номер: {number}, активация ID: {activation_id}")
if message.from_user.id not in user_activations:
user_activations[message.from_user.id] = {}
user_activations[message.from_user.id][activation_id] = {
'number': number,
'service': service,
'status': 'Ожидание SMS',
'task': None,
'last_update': time.time()
}
keyboard = InlineKeyboardBuilder()
keyboard.button(text="📋 Управлять активацией", callback_data=f"manage_activation:{activation_id}")
keyboard.button(text="🔙 Главное меню", callback_data="back_to_main_menu")
keyboard.adjust(1)
await message.answer(
f"Получен номер: {number}. Ожидание SMS...\nВы можете управлять активацией или вернуться в главное меню.",
reply_markup=keyboard.as_markup()
)
await state.clear()
task = asyncio.create_task(poll_for_sms(
bot=message.bot,
user_id=message.from_user.id,
chat_id=message.chat.id,
activation_id=activation_id,
smshub_api=smshub_api
))
user_activations[message.from_user.id][activation_id]['task'] = task
else:
logger.error(f"Не удалось получить номер. Ответ: {result}")
await message.answer("Не удалось получить номер. Попробуйте снова.")
await state.clear()
await send_main_menu(message)
@router.callback_query(F.data == "current_activations")
async def current_activations_handler(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
activations = user_activations.get(user_id, {})
if not activations:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"У вас нет активных активаций.",
reply_markup=keyboard.as_markup()
)
return
keyboard = InlineKeyboardBuilder()
for activation_id, data in activations.items():
keyboard.button(
text=f"{data['service']} ({data['number']}) - {data['status']}",
callback_data=f"manage_activation:{activation_id}"
)
keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Ваши текущие активации:",
reply_markup=keyboard.as_markup()
)
@router.callback_query(F.data.startswith("manage_activation"))
async def manage_activation_handler(callback_query: types.CallbackQuery):
user_id = 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_message(
callback_query.message,
"Активация не найдена.",
reply_markup=callback_query.message.reply_markup
)
return
keyboard = InlineKeyboardBuilder()
# Формируем клавиатуру в зависимости от статуса активации
if activation['status'] == 'Ожидание SMS':
# Только отменить активацию
keyboard.button(text="❌ Отменить активацию", callback_data=f"cancel_activation:{activation_id}")
elif activation['status'].startswith('Код получен'):
# Запросить еще SMS или завершить активацию
keyboard.button(text="🔄 Запросить еще SMS", callback_data=f"request_another_sms:{activation_id}")
keyboard.button(text="✅ Завершить активацию", callback_data=f"complete_activation:{activation_id}")
elif activation['status'].startswith('Ожидание повторного SMS'):
# Завершить или отменить активацию
keyboard.button(text="✅ Завершить активацию", callback_data=f"complete_activation:{activation_id}")
keyboard.button(text="❌ Отменить активацию", callback_data=f"cancel_activation:{activation_id}")
else:
# Другие статусы
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="current_activations")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
f"Активация {activation_id}:\nСервис: {activation['service']}\nНомер: {activation['number']}\nСтатус: {activation['status']}",
reply_markup=keyboard.as_markup()
)
@router.callback_query(F.data.startswith("cancel_activation"))
async def cancel_activation_handler(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
activation_id = callback_query.data.split(":")[1]
user_api_key = await 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.get('task')
if task:
task.cancel()
# Отменяем активацию на стороне API
result = await smshub_api.cancel_activation(activation_id)
logger.info(f"Активация {activation_id} отменена на стороне API: {result}")
# Удаляем активацию из списка
del activations[activation_id]
# Создаём новую клавиатуру с кнопкой "🔙 Назад"
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔙 Назад", callback_data="current_activations")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Активация успешно отменена.",
reply_markup=keyboard.as_markup()
)
else:
await safe_edit_message(
callback_query.message,
"Активация не найдена.",
reply_markup=callback_query.message.reply_markup
)
@router.callback_query(F.data.startswith("request_another_sms"))
async def request_another_sms_handler(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
activation_id = callback_query.data.split(":")[1]
user_api_key = await get_user_api_key(user_id)
smshub_api = SmsHubAPI(user_api_key)
activations = user_activations.get(user_id, {})
activation = activations.get(activation_id)
if not activation:
await safe_edit_message(
callback_query.message,
"Активация не найдена.",
reply_markup=callback_query.message.reply_markup
)
return
result = await smshub_api.set_status(activation_id, status="3")
if result == "ACCESS_RETRY_GET":
# Отменяем предыдущую задачу, если она есть
task = activation.get('task')
if task:
task.cancel()
# Запускаем новую задачу
task = asyncio.create_task(poll_for_sms(
bot=callback_query.message.bot,
user_id=user_id,
chat_id=callback_query.from_user.id,
activation_id=activation_id,
smshub_api=smshub_api
))
activation['task'] = task
activation['status'] = 'Ожидание повторного SMS'
await safe_edit_message(
callback_query.message,
"Запрошен повторный SMS. Ожидание SMS...",
reply_markup=callback_query.message.reply_markup
)
else:
logger.error(f"Не удалось запросить повторное SMS. Ответ: {result}")
await safe_edit_message(
callback_query.message,
"Не удалось запросить повторный SMS. Попробуйте снова.",
reply_markup=callback_query.message.reply_markup
)
@router.callback_query(F.data.startswith("complete_activation"))
async def complete_activation_handler(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
activation_id = callback_query.data.split(":")[1]
user_api_key = await get_user_api_key(user_id)
smshub_api = SmsHubAPI(user_api_key)
activations = user_activations.get(user_id, {})
activation = activations.get(activation_id)
if not activation:
await safe_edit_message(
callback_query.message,
"Активация не найдена.",
reply_markup=callback_query.message.reply_markup
)
return
result = await smshub_api.set_status(activation_id, status="6")
if result == "ACCESS_ACTIVATION":
# Отменяем задачу, если она есть
task = activation.get('task')
if task:
task.cancel()
# Удаляем активацию из списка
del activations[activation_id]
# Создаём новую клавиатуру с кнопкой "🔙 Назад"
keyboard = InlineKeyboardBuilder()
keyboard.button(text="🔙 Назад", callback_data="current_activations")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Активация успешно завершена.",
reply_markup=keyboard.as_markup()
)
else:
logger.error(f"Не удалось завершить активацию. Ответ: {result}")
await safe_edit_message(
callback_query.message,
"Не удалось завершить активацию. Попробуйте снова.",
reply_markup=callback_query.message.reply_markup
)
async def poll_for_sms(bot: Bot, user_id: int, chat_id: int, activation_id: str, smshub_api: SmsHubAPI):
activations = user_activations.get(user_id, {})
activation = activations.get(activation_id)
if not activation:
logger.error(f"Активация {activation_id} не найдена для пользователя {user_id}.")
return
try:
max_wait_time = 300 # Максимальное время ожидания в секундах
poll_interval = 3 # Интервал опроса в секундах
elapsed_time = 0
while elapsed_time < max_wait_time:
await asyncio.sleep(poll_interval)
elapsed_time += poll_interval
status_response = await smshub_api.get_status(activation_id)
if not status_response:
logger.error("Нет ответа при запросе статуса активации.")
await bot.send_message(chat_id, "Не удалось получить статус активации. Попробуйте позже.")
return
status_parts = status_response.split(":", 1)
status = status_parts[0]
if status == "STATUS_WAIT_CODE":
logger.debug(f"Ожидание SMS кода для активации {activation_id}.")
activation['last_update'] = time.time()
continue
elif status.startswith("STATUS_WAIT_RETRY"):
last_code = status_parts[1] if len(status_parts) > 1 else "нет предыдущего кода"
logger.info(f"Ожидание повторного SMS кода, последний код: {last_code}")
activation['status'] = f'Ожидание повторного SMS (последний код: {last_code})'
activation['last_update'] = time.time()
continue
elif status == "STATUS_CANCEL":
await bot.send_message(chat_id, "Активация была отменена.")
activation['status'] = 'Активация отменена'
return
elif status == "STATUS_OK":
code = status_parts[1] if len(status_parts) > 1 else "Код не получен"
logger.info(f"Получен код активации для {activation_id}: {code}")
activation['status'] = f'Код получен: {code}'
activation['last_update'] = time.time()
await bot.send_message(
chat_id,
f"Код активации для номера {activation['number']}: {code}"
)
return
else:
logger.error(f"Непредвиденный статус активации: {status_response}")
activation['status'] = f'Неизвестный статус: {status}'
await bot.send_message(chat_id, f"Неизвестный статус активации: {status}")
return
# Если время ожидания истекло, функция завершается без изменения статуса активации
except asyncio.CancelledError:
logger.info(f"Задача poll_for_sms для активации {activation_id} была отменена.")
except Exception as e:
logger.error(f"Ошибка в poll_for_sms: {e}")
activation['status'] = 'Ошибка при получении SMS'
await bot.send_message(chat_id, "Произошла ошибка при получении SMS.")
# Фоновая задача для периодической проверки статусов активаций
async def check_activations_status():
while True:
await asyncio.sleep(300) # Проверяем каждые 5 минут
logger.info("Проверка статусов активных активаций...")
for user_id, activations in user_activations.copy().items():
for activation_id, activation in activations.copy().items():
try:
user_api_key = await get_user_api_key(user_id)
if not user_api_key:
continue
smshub_api = SmsHubAPI(user_api_key)
status_response = await smshub_api.get_status(activation_id)
if not status_response:
continue
status_parts = status_response.split(":", 1)
status = status_parts[0]
if status == "STATUS_CANCEL":
# Удаляем активацию
logger.info(f"Активация {activation_id} была отменена на стороне сервиса.")
task = activation.get('task')
if task:
task.cancel()
del activations[activation_id]
elif activation['status'].startswith("Код получен"):
# Проверяем, прошло ли достаточно времени
elapsed_since_update = time.time() - activation.get('last_update', 0)
if elapsed_since_update > 1800: # 30 минут
logger.info(f"Активация {activation_id} завершена по истечении времени.")
task = activation.get('task')
if task:
task.cancel()
del activations[activation_id]
except Exception as e:
logger.error(f"Ошибка при проверке активации {activation_id}: {e}")
# Функция для запуска фоновых задач при старте бота
async def on_startup(dispatcher):
asyncio.create_task(check_activations_status())
```
**Изменения внесены в функцию `get_sms_handler`:**
```python
@router.callback_query(F.data == "get_sms")
async def get_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
user_api_key = await get_user_api_key(callback_query.from_user.id)
if not user_api_key:
keyboard = InlineKeyboardBuilder()
keyboard.button(text="⚙️ Настройки", callback_data="settings")
keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
keyboard.adjust(1)
await safe_edit_message(
callback_query.message,
"Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
reply_markup=keyboard.as_markup()
)
return
logger.info(f"Пользователь {callback_query.from_user.id} начал процесс получения номера.")
keyboard = InlineKeyboardBuilder()
keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
keyboard.button(text="🛠️ Доступные сервисы", url="https://teletype.in/@diagzone/NQd96Y8cPQe")
keyboard.adjust(2) # Размещаем две кнопки в одном ряду
await safe_edit_message(
callback_query.message,
"Введите сервис для получения номера (например, wa для WhatsApp):",
reply_markup=keyboard.as_markup()
)
await state.set_state(SmsOrderState.waiting_for_service)
```
Теперь у вас есть две кнопки в одном ряду, и при нажатии на кнопку "🛠️ Доступные сервисы" будет открываться указанная ссылка для просмотра доступных сервисов.