Respan Dataset Explorer

Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.

5,167,182physical rows
86shards
0.00%qualified row coverage
0.00%qualified cell coverage
Random row JSON API

turns-00032.parquet:49167

9fed9a00012b0cd58e12486c
turn 8/11gpt-4o-2024-08-06PersianAustralia218 words
degenerate_repetitionAbsentFinal dense release
USER
کار شبکه های آندوپلاسمی؟
                            
                            
ASSISTANT
شبکه آندوپلاسمی (ER) یک اندامک سلولی مهم در سلول‌های یوکاریوتی است که به دو نوع اصلی تقسیم می‌شود: شبکه آندوپلاسمی خشن (Rough ER) و شبکه آندوپلاسمی صاف (Smooth ER). هر یک از این انواع وظایف خاصی را بر عهده دارند:

1. **شبکه آندوپلاسمی خشن (Rough ER)**:
   - این نوع شبکه دارای سطحی پوشیده از ریبوزوم‌ها است که به آن ظاهر "خشن" می‌بخشد. 
   - عملکرد اصلی آن سنتز پروتئین است. 
   - ریبوزوم‌های روی سطح این شبکه پروتئین‌های جدید را تولید می‌کنند، که بسیاری از آن‌ها برای ترشح یا انتقال به اندامک‌های دیگر تعیین می‌شوند.
   - پروتئین‌ها پس از سنتز وارد فضای داخلی شبکه می‌شوند و ممکن است تغییرات پس‌ازترجمه‌ای مانند گلیکوزیلاسیون روی آن‌ها انجام شود.

2. **شبکه آندوپلاسمی صاف (Smooth ER)**:
   - این نوع شبکه فاقد ریبوزوم است و دارای سطحی صاف است.
   - در سنتز و متابولیسم لیپیدها نقش دارد، از جمله فسفولیپیدها و کلسترول که برای تولید غشای سلولی ضروری هستند.
   - مسئولیت ذخیره و آزادسازی یون کلسیم دارد، که برای انقباض عضلانی و سایر فرآیندهای سلولی مهم است.
   - در برخی سلول‌ها، نقش مهمی در سم‌زدایی از ترکیبات شیمیایی و داروها ایفا می‌کند.

به‌طور کلی، شبکه آندوپلاسمی نقش مهمی در سنتز، تغییر و نقل و انتقالات مولکول‌های مختلف درون سلول دارد و به عنوان یک سیستم حمل و نقل داخلی عمل می‌کند.

turns-00032.parquet:49168

e9cd88dcfd893e7071b5c2a7
turn 9/11gpt-4o-2024-08-06PersianAustralia191 words
degenerate_repetitionAbsentFinal dense release
USER
وظیفه حمل و نقل را کدام اندامک انجام میدهد؟
                            
                            
ASSISTANT
وظیفه حمل و نقل در سلول عمدتاً توسط مجموعه‌ای از اندامک‌ها و ساختارها انجام می‌شود که با یکدیگر همکاری می‌کنند. برخی از مهم‌ترین آنها عبارت‌اند از:

1. **شبکه آندوپلاسمی (ER)**: 
   - نقش مهمی در سنتز و حمل و نقل پروتئین‌ها و لیپیدها به دستگاه گلژی و دیگر قسمت‌های سلول دارد.

2. **دستگاه گلژی**:
   - پس از دریافت پروتئین‌ها و لیپیدها از شبکه آندوپلاسمی، آن‌ها را تغییر داده، بسته‌بندی کرده و به محل‌های مورد نیاز درون یا خارج از سلول ارسال می‌کند.

3. **وزیکول‌ها**:
   - ساختارهای غشائی کوچکی هستند که مواد را از یک نقطه سلول به نقطه دیگر منتقل می‌کنند. آن‌ها در انتقال مواد بین اندامک‌ها و انتقال مواد به غشای سلولی برای ترشح یا وارد کردن به سلول دخیل هستند.

4. **لیزوزوم‌ها**:
   - وزیکول‌های خاصی که حاوی آنزیم‌های گوارشی هستند و در فرآیند بازیافت و تجزیه مواد سلولی از طریق فرایندهایی مانند فاگوسیتوز و اتوفاژی دخالت دارند.

این سیستم اندامکی همکاری‌کننده باعث می‌شود که موادی مانند پروتئین‌ها، لیپیدها و دیگر ماکرومولکول‌ها به نحو مؤثری در سلول جابه‌جا شوند تا عملکرد سلولی بهبود یابد و بتواند به نیازهای متغیر محیطی پاسخ دهد.

turns-00032.parquet:49169

3f0f0c510f6ec5049fdce2ff
turn 10/11gpt-4o-2024-08-06PersianAustralia98 words
degenerate_repetitionAbsentFinal dense release
USER
شکن از مو گشودن یعنی چه؟
                            
                            
ASSISTANT
اصطلاح «شکن از مو گشودن» یک تعبیر فارسی است که به معنای دقت بسیار زیاد و وسواس در بررسی جزئیات استفاده می‌شود. این عبارت به تصویر ذهنی از باز کردن یا تفکیک کردن یک "شکن" (معادل کوچک‌ترین واحد قابل تقسیم) از یک تار مو اشاره دارد که نشان‌دهنده دقت و ظرافت فوق‌العاده‌ای است. 

این اصطلاح اغلب در زمینه‌هایی استفاده می‌شود که نیاز به بررسی دقیق، تحلیل جزئیات یا انجام کاری با حساسیت بسیار بالا دارند. به‌طور کلی، «شکن از مو گشودن» به نیاز به توجه به ظریف‌ترین و دقیق‌ترین جزئیات اشاره دارد.

turns-00032.parquet:49170

dd56febcb4a548f377b59e3e
turn 11/11gpt-4o-2024-08-06PersianAustralia35 words
degenerate_repetitionAbsentFinal dense release
USER
جواب دبده
                            
                            
ASSISTANT
معذرت می‌خواهم اگر پاسخ قبلی به نظر شما مناسب نبود. اگر می‌توانید بیشتر در مورد سوال خود توضیح دهید یا اگر نیاز به اطلاعات یا موضوع خاصی دارید، خوشحال می‌شوم که کمک کنم.

turns-00032.parquet:49171

2b1b1619683a2be1b417295e
turn 1/4gpt-4o-2024-08-06PersianAustralia117 words
degenerate_repetitionAbsentFinal dense release
USER
۳ اصل معماری ویترویوس چیست
                            
                            
ASSISTANT
سه اصل معماری ویترویوس، معمار و مهندس رومی باستان، شامل موارد زیر است:

1. **استحکام (Firmitas):** به معنای ایجاد سازه‌هایی است که محکم و بادوام باشند، به گونه‌ای که بتوانند به خوبی در برابر عوامل محیطی مقاومت کنند.

2. **کارکرد (Utilitas):** به نحوی که طراحی بنا متناسب با عملکرد و استفاده‌ای که از آن انتظار می‌رود، باشد. به عبارت دیگر، ساختمان باید نیازهای کاربران را به خوبی برآورده کند.

3. **زیبایی (Venustas):** ساخت بناهایی که از نظر زیبایی‌شناسی خوشایند و دلپذیر باشند، به گونه‌ای که جذابیت بصری داشته باشند و حس زیبایی را به ناظر منتقل کنند.

ویترویوس معتقد بود که ترکیب این سه اصل می‌تواند به خلق معماری ایده‌آل منجر شود.

turns-00032.parquet:49172

8684431e3ca5a3657ef24119
turn 2/4gpt-4o-2024-08-06PersianAustralia50 words
degenerate_repetitionAbsentFinal dense release
USER
ترجمه residence
                            
                            
ASSISTANT
واژه "residence" در زبان فارسی به "اقامتگاه" یا "محل سکونت" ترجمه می‌شود. این کلمه می‌تواند به خانه یا محلی اشاره داشته باشد که فرد در آن زندگی می‌کند. همچنین ممکن است به منظور اشاره به ساختمان‌های رسمی مانند سفارت‌ها یا محل اقامت رسمی مقامات نیز به کار رود.

turns-00032.parquet:49173

e73ed16942880fb4f3b1aa98
turn 3/4gpt-4o-2024-08-06PersianAustralia39 words
degenerate_repetitionAbsentFinal dense release
USER
Fight ترجمه
                            
                            
ASSISTANT
واژه "fight" در زبان فارسی به "جنگیدن"، "مبارزه کردن" یا "نزاع" ترجمه می‌شود. بسته به زمینه، این کلمه می‌تواند به معنای درگیری فیزیکی، تلاش برای دستیابی به هدفی خاص، یا حتی نبرد در یک زمین ورزشی باشد.

turns-00032.parquet:49174

dc3f6480dd4b55fc91bc5052
turn 4/4gpt-4o-2024-08-06PersianAustralia230 words
degenerate_repetitionAbsentFinal dense release
USER
من یک پروژه درمورد سقف عرشه فولادی دارم
سرتیتر های مهم و مناسب برای توضیح این موضوع رو به من بده
                            
                            
ASSISTANT
البته، در ادامه سرتیترهایی مهم و مناسب برای پروژه‌ای درباره سقف‌های عرشه فولادی آورده شده است که می‌تواند ساختار و محتوای پروژه شما را سازمان‌دهی کند:

1. **معرفی سقف عرشه فولادی**
   - تعریف و تاریخچه
   - کاربردها و مزایا

2. **اجزای سقف عرشه فولادی**
   - صفحات فولادی (Decking)
   - بتن و تقویت‌کننده‌ها

3. **فرآیند نصب سقف عرشه فولادی**
   - مراحل قبل از نصب
   - روش‌ها و تکنیک‌های نصب

4. **مزایای استفاده از سقف عرشه فولادی**
   - سرعت اجرای بالا
   - کاهش وزن ساختمان
   - پایداری و مقاومت ساختاری

5. **معایب و چالش‌های سقف عرشه فولادی**
   - هزینه‌های اولیه
   - مسائل مربوط به عایق‌کاری صوتی و حرارتی

6. **کاربردهای صنعتی و تجاری سقف عرشه فولادی**
   - ساختمان‌های بلند مرتبه
   - مراکز خرید و انبارها

7. **مقایسه با سایر سیستم‌های سقفی**
   - سقف‌های بتن آرمه
   - سقف‌های چوبی یا سنتی

8. **موارد ایمنی و استانداردهای مرتبط**
   - استانداردهای بین‌المللی و ملی
   - موارد ایمنی در نصب و بهره‌برداری

9. **مطالعات موردی و نمونه‌های واقعی**
   - پروژه‌های بزرگ اجرا شده
   - تجربیات کاربردی

10. **نتیجه‌گیری و آینده‌نگری**
    - پیشرفت‌های تکنولوژیکی
    - نقش سقف عرشه فولادی در ساخت‌وساز پایدار

این سرتیترها می‌توانند به شما کمک کنند تا محتوای جامعی برای پروژه‌تان تهیه کنید و به موضوعات مختلف مرتبط با سقف عرشه فولادی بپردازید.

turns-00032.parquet:49175

87cb7a79b28bd4dbdc7ee56e
turn 1/1gpt-4o-2024-08-06PersianUnited States159 words
degenerate_repetitionAbsentFinal dense release
USER
در سال 1396 تعداد 3750000 تعداد بازدید کننده چندمین مضرب عدد 3 است آیا این عدد می تواند مصرب عدد 15 باشد لطفا با دلیل  اگر این عدد را در 1000 صرب کنیم چه تغییری در ارزش مکانی بزرگ ترین رقم این عدد به وجود می آید 
ASSISTANT
برای بررسی اینکه عدد 3,750,000 چندمین مضرب عدد 3 است، باید این عدد را بر 3 تقسیم کنیم:

\[ \frac{3750000}{3} = 1250000 \]

بنابراین، 3,750,000 دقیقاً مضرب 1,250,000ام عدد 3 است.

حال برای بررسی مضرب بودن عدد 15، باید عدد بتواند بر 3 و 5 قابل تقسیم باشد. از آنجایی که 3,750,000 بر 3 قابل تقسیم است و همچنین آخرین رقم آن 0 است که نشان از قابل تقسیم بودن بر 5 دارد، این عدد بر 15 نیز قابل تقسیم است.

اگر عدد 3,750,000 را در 1,000 ضرب کنیم، به عدد 3,750,000,000 می‌رسیم. این تغییر باعث می‌شود ارزش مکانی بزرگ‌ترین رقم (که 3 است) از 10^6 (میلیون) به 10^9 (میلیارد) تغییر کند.

turns-00032.parquet:49176

8d197311cd4329bde32f7731
turn 1/5gpt-4o-2024-08-06RussianRussia6179 words
degenerate_repetitionAbsentFinal dense release
USER
я тебе присылаю полный код моего бота телеграм, я хочу улучшить код, чтобы он был более структурированный и правильный, без ошибок



документация: Внимание! Добавлена поддержка валюты.

Методы getBalance, getNumber, getPrices теперь принимают параметр currency

Параметр currency принимает два значения 643, 840 (цифровой код валюты ₽ и $ по стандарту ISO)

Также добавлен новый метод updateApiCurrency, который задает значение по умолчанию, если currency не передано

Если в метод не передан параметр currency и не было задано значение с помощью updateApiCurrency, мы используем значение по умолчанию заданное в нашей системе

API - это протокол взаимодействия между вашим ПО и нашим сервером активаций.

API нужен для того, чтобы автоматизировать процесс получение смс сообщений на вашей стороне

Все запросы (поддерживаются POST и GET) должны идти на https://smshub.org/stubs/handler_api.php

Во всех запросах должен присутствовать API ключ в виде параметра "api_key"

Запрос количества доступных номеров:
https://smshub.org/stubs/handler_api.php?api_key=APIKEY&action=getNumbersStatus&country=COUNTRY&operator=OPERATOR
Входные параметры
Параметр	Принимаемые значения	Обязательность	Описание
APIKEY	APIKEY	Да	Ваш API-ключ, обеспечивает доступ к оплаченным услугам. API-ключ следует хранить в тайне
COUNTRY	См. список стран в левом меню	Нет	Страна номера, если не задан, то будет выбран номер той страны, которая последний раз была выбрана в левом меню
OPERATOR	Список доступных операторов можно найти в левом меню соответсвующей страны	Нет	Оператор номера, если не задан, то будет выбран тот оператор, который последний раз была выбран в левом меню
Ответ сервиса будет в json формате, пример:

{"apd_0":200,"amy_0":249,"aly_0":246,"tu_0":214,"aho_0":115,"ky_0":86,"apn_0":214,"ajs_0":66,"yd_0":205,"ayc_0":27}

Где до черточки - название сервиса, после обозначение нужно ли включать переадресацию. 0 - не включать. 1 - включать. Номеров с переадресацией мы к сожалению пока не предоставляем

Запрос баланса
https://smshub.org/stubs/handler_api.php?api_key=APIKEY&action=getBalance&currency=CURRENCY
Входные параметры
Параметр	Принимаемые значения	Обязательность	Описание
APIKEY	APIKEY	Да	Ваш API-ключ, обеспечивает доступ к оплаченным услугам. API-ключ следует хранить в тайне
CURRENCY	643, 840	Нет	Если передано - возвращает значение соответствующего баланса.
Выходные данные
Ответ сервера	Пример	Описание
ACCESS_BALANCE:BALANCE	ACCESS_BALANCE:540	BALANCE - Баланс вашего API-ключа
Возможные ошибки
Ответ сервера	Описание
BAD_KEY	Неверный API-ключ
ERROR_SQL	ошибка SQL-сервера
BAD_ACTION	Общее неправильное формирование запроса
BAD_CURRENCY	Неверное значение currency
Заказ номера
https://smshub.org/stubs/handler_api.php?api_key=APIKEY&action=getNumber&service=SERVICE&operator=OPERATOR&country=COUNTRY&maxPrice=MAXPRICE&currency=CURRENCY
Входные параметры
Параметр	Принимаемые значения	Обязательность	Описание
APIKEY	APIKEY	Да	Ваш API-ключ, обеспечивает доступ к оплаченным услугам. API-ключ следует хранить в тайне
SERVICE	См. список сервисов в левом меню	Да	Сервис, номер для которого необходимо получить
OPERATOR	Принимаемые параметры зависят от конкретной страны, для уточнения обратите внимание на левое меню	Нет	Сотовый оператор номер которого необходимо получить
COUNTRY	См. список стран в левом меню	Нет	Страна номер которой необходимо получить
MAXPRICE	Числовое значение	Нет	Максимальная цена, за которую вы готовы купить номер по Free Price
CURRENCY	643, 840	Нет	С какого баланса будет списана стоимость номера.
Ответы сервиса
Ответ сервера	Пример	Описание
NO_NUMBERS	NO_NUMBERS	Нет номеров с заданными параметрами, попробуйте позже, или поменяйте оператора, страну.
NO_BALANCE	NO_BALANCE	Закончились деньги на API-ключе
API_KEY_NOT_VALID	API_KEY_NOT_VALID	Невалидный статус API-ключа
WRONG_SERVICE	WRONG_SERVICE	Не верный идентификатор сервиса
ACCESS_NUMBER:ID:NUMBER	ACCESS_NUMBER:234242:79123456789	Получили номер, ID активации - ID, сам номер с кодом страны - NUMBER
Возможные ошибки
Ответ сервера	Описание
BAD_ACTION	Общее неправильное формирование запросаО
BAD_SERVICE	некорректное наименование сервиса
BAD_KEY	Неверный API-ключ
ERROR_SQL	Ошибка базы SQL-сервера
BAD_CURRENCY	Неверное значение currency
Изменить статус
https://smshub.org/stubs/handler_api.php?api_key=APIKEY&action=setStatus&status=STATUS&id=ID
Входные параметры
Параметр	Принимаемые значения	Обязательность	Описание
APIKEY	APIKEY	Да	Ваш API-ключ, обеспечивает доступ к оплаченным услугам. API-ключ следует хранить в тайне
ID	Числовое значение	Да	ID активации, полученное при запросе номера
STATUS	1 - смс на номер отправлено, 3 - необходимо повторно смс, 6 - активация успешно завершена, 8 - отменить активацию	Да	Статус который необходимо передать активации
Какой статус когда отправлять?

Простая логика взаимодействия с API

Сразу после получения номера доступны следующие действия:
8 - Отменить активацию
1 - Сообщить, что SMS отправлена (необязательно)
Для активации со статусом 1:
8 - Отменить активацию
Сразу после получения кода:
3 - Запросить еще одну смс
6 - Подтвердить SMS-код и завершить активацию
Для активации со статусом 3:
6 - Подтвердить SMS-код и завершить активацию
Выходные данные
Ответ сервера	Описание
ACCESS_READY	Готовность ожидания смс
ACCESS_RETRY_GET	Ожидаем новое смс
ACCESS_ACTIVATION	Активация успешно завершена
ACCESS_CANCEL	Активация отменена
Возможные ошибки
Ответ сервера	Описание
BAD_ACTION	Общее неправильное формирование запроса
BAD_SERVICE	некорректное наименование сервиса
BAD_KEY	Неверный API-ключ
NO_ACTIVATION	id активации не существует
ERROR_SQL	Ошибка базы SQL-сервера, обратитесь к администратору
Получить статус
https://smshub.org/stubs/handler_api.php?api_key=APIKEY&action=getStatus&id=ID
Входные параметры
Параметр	Принимаемые значения	Обязательность	Описание
APIKEY	APIKEY	Да	Ваш API-ключ, обеспечивает доступ к оплаченным услугам. API-ключ следует хранить в тайне
ID	Числовое значение	Да	ID активации, полученное при запросе номера
Выходные данные
Ответ сервера	Описание
STATUS_WAIT_CODE	Ожидаем прихода смс
STATUS_WAIT_RETRY:LASTCODE	Ожидаем еще одно смс, LASTCODE - последнее полученное смс
STATUS_CANCEL	активация отменена
STATUS_OK:CODE	Код получен (где CODE - код активации)
Возможные ошибки
Ответ сервера	Описание
BAD_ACTION	Общее неправильное формирование запроса
BAD_KEY	Неверный API-ключ
NO_ACTIVATION	id активации не существует
ERROR_SQL	Ошибка базы SQL-сервера, обратитесь к администратору
Запросить все цены
https://smshub.org/stubs/handler_api.php?api_key=APIKEY&action=getPrices&service=SERVICE&country=COUNTRY&currency=CURRENCY
Входные параметры
Параметр	Принимаемые значения	Обязательность	Описание
APIKEY	APIKEY	Да	Ваш API-ключ, обеспечивает доступ к оплаченным услугам. API-ключ следует хранить в тайне
SERVICE	См. список сервисов в левом меню	Нет	Сервис, номер для которого необходимо получить
COUNTRY	См. список стран в левом меню	Нет	Страна номера
CURRENCY	643, 840	Нет	Если передано - метод вернет стоимость в соответствующей валюте.
Выходные данные
Ответ сервера	Описание
JSON ответ	Ответ в формате { "Страна": { "Сервис": { "Цена": Количество}}}
Возможные ошибки
Ответ сервера	Описание
BAD_CURRENCY	Неверное значение currency
Изменить валюту по умолчанию
https://smshub.org/stubs/handler_api.php?api_key=APIKEY&action=updateApiCurrency&&currency=CURRENCY
Входные параметры
Параметр	Принимаемые значения	Обязательность	Описание
APIKEY	APIKEY	Да	Ваш API-ключ, обеспечивает доступ к оплаченным услугам. API-ключ следует хранить в тайне
CURRENCY	643, 840	Да	Значение валюты которое будет установлено по умолчанию для запросов по API.
Выходные данные
Ответ сервера	Описание
JSON ответ	Ответ в формате {"status":"success"}
Возможные ошибки
Ответ сервера	Описание
BAD_CURRENCY	Неверное значение currency
CURRENCY_CHANGE_UNAVAILABLE	Изменение валюты недоступно















# requirements!.txt

python-dotenv
aiogram
requests


# main.py

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

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

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

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

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

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

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

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

import os
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv("API_KEY")
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
ADMIN_CHAT_ID = int(os.getenv("ADMIN_CHAT_ID", "389669884"))



# bot/utils.py

import logging
import time
import aiohttp
from aiogram import Bot, Dispatcher, types
from aiogram.fsm.context import FSMContext
from typing import Optional

logger = logging.getLogger(__name__)

EXCHANGE_RATE_API_URL = "https://api.exchangerate-api.com/v4/latest/USD"
USD_TO_RUB_RATE_TTL = 3600  # Кэшируем курс на 1 час

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

async def get_usd_to_rub_rate() -> Optional[float]:
    """Получить текущий курс 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

async def safe_edit_or_send_message(message: types.Message, new_text: str, state: FSMContext, reply_markup=None, parse_mode=None):
    """Безопасное редактирование или отправка нового сообщения."""
    try:
        await message.edit_text(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
        await state.update_data(last_message_id=message.message_id)
    except Exception as e:
        logger.error(f"Failed to edit message: {e}")
        data = await state.get_data()
        last_message_id = data.get('last_message_id')
        if last_message_id:
            try:
                await message.bot.delete_message(chat_id=message.chat.id, message_id=last_message_id)
            except Exception as del_exc:
                logger.error(f"Failed to delete message: {del_exc}")
        sent_message = await message.answer(new_text, reply_markup=reply_markup, parse_mode=parse_mode)
        await state.update_data(last_message_id=sent_message.message_id)

async def safe_delete_message(bot: Bot, chat_id: int, message_id: int) -> None:
    """Безопасное удаление сообщения."""
    try:
        await bot.delete_message(chat_id=chat_id, message_id=message_id)
    except Exception as e:
        logger.error(f"Failed to delete message: {e}")

async def on_startup(dispatcher: Dispatcher):
    """Функция, выполняемая при старте бота."""
    logger.info("Бот успешно запущен.")
    # Здесь можно добавить запуск фоновых задач, если необходимо
	
	
	
	# bot/api.py

import aiohttp
import logging
import json
from typing import Optional, Dict

logger = logging.getLogger(__name__)

BASE_URL = "https://smshub.org/stubs/handler_api.php"

class SmsHubAPI:
    def __init__(self, api_key: str):
        self.api_key = api_key

    async def get_balance(self) -> Optional[str]:
        """Запрос балансввыа с заданной валютой (RUB)."""
        logger.info("Запрос баланса с валютой: 643")
        params = {
            "api_key": self.api_key,
            "action": "getBalance",
            "currency": "643"
        }
        async with aiohttp.ClientSession() as session:
            async with session.get(BASE_URL, params=params) as response:
                text = await response.text()
                if response.status == 200:
                    logger.info(f"Ответ баланса: {text}")
                    return text
                logger.error(f"Ошибка при запросе баланса: {response.status} - {text}")
                return None

    async def get_number(self, service: str, country: str = "0", operator: Optional[str] = None, max_price: Optional[float] = None) -> Optional[str]:
        """Получение номераа для указанного сервиса и страны."""
        logger.info(f"Запрос номера для сервиса: {service}, страна: {country}, оператор: {operator}, макс. цена: {max_price}, валюта: 643")
        params = {
            "api_key": self.api_key,
            "action": "getNumber",
            "service": service,
            "country": country,
            "currency": "643"
        }
        if operator and operator != "any":
            params["operator"] = operator
        if max_price:
            params["maxPrice"] = max_price

        async with aiohttp.ClientSession() as session:
            async with session.get(BASE_URL, params=params) as response:
                text = await response.text()
                if response.status == 200:
                    logger.info(f"Ответ номера: {text}")
                    return text
                logger.error(f"Ошибка при запросе номера: {response.status} - {text}")
                return None

    async def get_price_for_service(self, service: str, country: str = "0") -> Optional[Dict]:
        """Получение цен для указанного сервиса."""
        logger.info(f"Запрос цен для сервиса: {service}, страна: {country}, валюта: 643")
        params = {
            "api_key": self.api_key,
            "action": "getPrices",
            "service": service,
            "country": country,
            "currency": "643"
        }

        async with aiohttp.ClientSession() as session:
            async with session.get(BASE_URL, params=params) as response:
                response_text = await response.text()
                if response.status == 200:
                    try:
                        prices_data = json.loads(response_text)
                        logger.info(f"Цены для сервиса {service}: {prices_data}")
                        country_data = prices_data.get(country)
                        if not country_data:
                            logger.error(f"Страна '{country}' отсутствует в данных: {prices_data}")
                            return None
                        service_data = country_data.get(service)
                        if not service_data:
                            logger.error(f"Сервис '{service}' отсутствует среди доступных для страны '{country}'. Данные: {country_data}")
                            return None
                        return service_data  # Возвращает словарь { "Цена": Количество }
                    except json.JSONDecodeError:
                        logger.error(f"Ошибка парсинга JSON: {response_text}")
                        return None
                else:
                    logger.error(f"Ошибка при запросе цен: {response.status} - {response_text}")
                    return None

    async def get_numbers_status(self, country: str = "0", operator: Optional[str] = None) -> Optional[Dict]:
        """Получение статуса номеров по стране и оператору."""
        logger.info(f"Запрос статуса номеров для страны: {country}, оператор: {operator}")
        params = {
            "api_key": self.api_key,
            "action": "getNumbersStatus",
            "country": country
        }
        if operator and operator != "any":
            params["operator"] = operator
        async with aiohttp.ClientSession() as session:
            async with session.get(BASE_URL, params=params) as response:
                response_text = await response.text()
                if response.status == 200:
                    try:
                        numbers_status = json.loads(response_text)
                        logger.info(f"Статус номеров: {numbers_status}")
                        return numbers_status  # Возвращает словарь { "SERVICE_CODE": Количество }
                    except json.JSONDecodeError:
                        logger.error(f"Ошибка парсинга JSON: {response_text}")
                        return None
                else:
                    logger.error(f"Ошибка при запросе статуса номеров: {response.status} - {response_text}")
                    return None

    async def get_status(self, activation_id: str) -> Optional[str]:
        """Получение статуса активации по ID."""
        logger.info(f"Запрос статуса для активации: {activation_id}")
        params = {
            "api_key": self.api_key,
            "action": "getStatus",
            "id": activation_id
        }
        async with aiohttp.ClientSession() as session:
            async with session.get(BASE_URL, params=params) as response:
                text = await response.text()
                if response.status == 200:
                    logger.info(f"Ответ статуса: {text}")
                    return text
                logger.error(f"Ошибка при запросе статуса: {response.status} - {text}")
                return None

    async def set_status(self, activation_id: str, status: str) -> Optional[str]:
        """Установка статуса для активации."""
        logger.info(f"Изменение статуса активации: {activation_id} на статус {status}")
        params = {
            "api_key": self.api_key,
            "action": "setStatus",
            "id": activation_id,
            "status": status
        }
        async with aiohttp.ClientSession() as session:
            async with session.get(BASE_URL, params=params) as response:
                text = await response.text()
                if response.status == 200:
                    logger.info(f"Ответ изменения статуса: {text}")
                    return text
                logger.error(f"Ошибка при изменении статуса: {response.status} - {text}")
                return None

    async def cancel_activation(self, activation_id: str) -> Optional[str]:
        """Отмена активации."""
        return await self.set_status(activation_id, status="8")
		
		
		
		# bot/data.py

import json
import os
import threading
import logging
from typing import Optional, Dict, Any

logger = logging.getLogger(__name__)

# Путь к файлу для хранения данных пользователей
DATA_FILE = os.path.join(os.path.dirname(__file__), "data.json")
lock = threading.RLock()

# Загружаем данные из файла при запуске
user_data: Dict[str, Any] = {}


def load_data() -> None:
    """Загружает данные из JSON файла."""
    global user_data
    with lock:
        if os.path.exists(DATA_FILE):
            try:
                with open(DATA_FILE, "r", encoding="utf-8") as file:
                    user_data = json.load(file)
                    logger.info("Данные пользователей успешно загружены.")
            except json.JSONDecodeError:
                user_data = {}
                logger.warning("Не удалось декодировать JSON из файла. Используем пустые данные.")
        else:
            user_data = {}
            logger.info("Файл данных не найден. Используем пустые данные.")


# Вызываем load_data() при импорте модуля
load_data()


def save_data() -> None:
    """Сохраняет текущие данные в JSON файл."""
    with lock:
        with open(DATA_FILE, "w", encoding="utf-8") as file:
            json.dump(user_data, file, ensure_ascii=False, indent=4)
            logger.info("Данные пользователей успешно сохранены.")


# Методы для работы с API ключами
def set_user_api_key(user_id: int, api_key: str) -> None:
    """Сохраняет API ключ для конкретного пользователя."""
    with lock:
        user = user_data.get(str(user_id), {})
        user["api_key"] = api_key
        user_data[str(user_id)] = user
        save_data()
        logger.info(f"API ключ для пользователя {user_id} сохранён.")


def get_user_api_key(user_id: int) -> Optional[str]:
    """Возвращает API ключ для конкретного пользователя, если он сохранен."""
    with lock:
        return user_data.get(str(user_id), {}).get("api_key")


def delete_user_api_key(user_id: int) -> None:
    """Удаляет API ключ для конкретного пользователя."""
    with lock:
        user = user_data.get(str(user_id), {})
        if "api_key" in user:
            del user["api_key"]
            if not user:
                del user_data[str(user_id)]
            save_data()
            logger.info(f"API ключ для пользователя {user_id} удалён.")
        else:
            logger.warning(f"У пользователя {user_id} нет API ключа для удаления.")


# Методы для работы с оператором
def set_user_operator(user_id: int, operator: str) -> None:
    """Сохраняет выбранный оператор для конкретного пользователя."""
    with lock:
        user = user_data.get(str(user_id), {})
        user["operator"] = operator
        user_data[str(user_id)] = user
        save_data()
        logger.info(f"Оператор '{operator}' для пользователя {user_id} сохранён.")


def get_user_operator(user_id: int) -> Optional[str]:
    """Возвращает выбранный оператор для конкретного пользователя, если он сохранен."""
    with lock:
        return user_data.get(str(user_id), {}).get("operator")


def delete_user_operator(user_id: int) -> None:
    """Удаляет выбранный оператор для конкретного пользователя."""
    with lock:
        user = user_data.get(str(user_id), {})
        if "operator" in user:
            del user["operator"]
            if not user:
                del user_data[str(user_id)]
            save_data()
            logger.info(f"Оператор для пользователя {user_id} удалён.")
        else:
            logger.warning(f"У пользователя {user_id} нет оператора для удаления.")


# Методы для работы со страной
def set_user_country(user_id: int, country: str) -> None:
    """Сохраняет выбранную страну для конкретного пользователя."""
    with lock:
        user = user_data.get(str(user_id), {})
        user["country"] = country
        user_data[str(user_id)] = user
        save_data()
        logger.info(f"Страна '{country}' для пользователя {user_id} сохранена.")


def get_user_country(user_id: int) -> Optional[str]:
    """Возвращает выбранную страну для конкретного пользователя, если она сохранена."""
    with lock:
        return user_data.get(str(user_id), {}).get("country")


def delete_user_country(user_id: int) -> None:
    """Удаляет выбранную страну для конкретного пользователя."""
    with lock:
        user = user_data.get(str(user_id), {})
        if "country" in user:
            del user["country"]
            if not user:
                del user_data[str(user_id)]
            save_data()
            logger.info(f"Страна для пользователя {user_id} удалена.")
        else:
            logger.warning(f"У пользователя {user_id} нет страны для удаления.")
			
			
			# bot/handlers/__init__.py

from aiogram import Dispatcher

from .start import register_start_handlers
from .settings import register_settings_handlers
from .balance import register_balance_handlers
from .sms import register_sms_handlers
from .activation import register_activation_handlers
from .errors import register_error_handlers

def register_handlers(dp: Dispatcher):
    register_start_handlers(dp)
    register_settings_handlers(dp)
    register_balance_handlers(dp)
    register_sms_handlers(dp)
    register_activation_handlers(dp)
    register_error_handlers(dp)
	
	
	
	# bot/handlers/activation.py

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

from ..data import get_user_api_key
from ..api import SmsHubAPI
from ..utils import safe_edit_or_send_message, safe_delete_message
from .main_menu import send_main_menu

logger = logging.getLogger(__name__)

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

async def current_activations_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    user_id = callback_query.from_user.id
    activations = user_activations.get(user_id, {})
    keyboard = InlineKeyboardBuilder()
    if not activations:
        keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
        keyboard.adjust(1)
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="У вас нет активных активаций.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
        return

    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_or_send_message(
        message=callback_query.message,
        new_text="Ваши текущие активации:",
        state=state,
        reply_markup=keyboard.as_markup()
    )

async def manage_activation_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    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_or_send_message(
            message=callback_query.message,
            new_text="Активация не найдена.",
            state=state,
            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_or_send_message(
        message=callback_query.message,
        new_text=(
            f"Активация {activation_id}:\n"
            f"Сервис: {activation['service']}\n"
            f"Номер: `{activation['number']}`\n"
            f"Статус: {activation['status']}"
        ),
        state=state,
        reply_markup=keyboard.as_markup(),
        parse_mode="Markdown"
    )

async def cancel_activation_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    user_id = callback_query.from_user.id
    activation_id = callback_query.data.split(":")[1]
    user_api_key = get_user_api_key(user_id)
    smshub_api = SmsHubAPI(user_api_key)

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

    if activation:
        # Отменяем задачу, если она есть
        task = activation.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_or_send_message(
            message=callback_query.message,
            new_text="Активация успешно отменена.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
    else:
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Активация не найдена.",
            state=state,
            reply_markup=callback_query.message.reply_markup
        )

async def request_another_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    user_id = callback_query.from_user.id
    activation_id = callback_query.data.split(":")[1]
    user_api_key = get_user_api_key(user_id)
    smshub_api = SmsHubAPI(user_api_key)
    activations = user_activations.get(user_id, {})
    activation = activations.get(activation_id)

    if not activation:
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Активация не найдена.",
            state=state,
            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_or_send_message(
            message=callback_query.message,
            new_text="Запрошен повторный SMS. Ожидание SMS...",
            state=state,
            reply_markup=callback_query.message.reply_markup
        )
    else:
        logger.error(f"Не удалось запросить повторное SMS. Ответ: {result}")
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Не удалось запросить повторный SMS. Попробуйте снова.",
            state=state,
            reply_markup=callback_query.message.reply_markup
        )

async def complete_activation_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    user_id = callback_query.from_user.id
    activation_id = callback_query.data.split(":")[1]
    user_api_key = get_user_api_key(user_id)
    smshub_api = SmsHubAPI(user_api_key)
    activations = user_activations.get(user_id, {})
    activation = activations.get(activation_id)

    if not activation:
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Активация не найдена.",
            state=state,
            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_or_send_message(
            message=callback_query.message,
            new_text="Активация успешно завершена.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
    else:
        logger.error(f"Не удалось завершить активацию. Ответ: {result}")
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Не удалось завершить активацию. Попробуйте снова.",
            state=state,
            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}`",
                    parse_mode="Markdown"
                )
                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.")

def register_activation_handlers(dp: Dispatcher):
    dp.callback_query.register(current_activations_handler, F.data == "current_activations")
    dp.callback_query.register(manage_activation_handler, F.data.startswith("manage_activation"))
    dp.callback_query.register(cancel_activation_handler, F.data.startswith("cancel_activation"))
    dp.callback_query.register(request_another_sms_handler, F.data.startswith("request_another_sms"))
    dp.callback_query.register(complete_activation_handler, F.data.startswith("complete_activation"))
	
	
	
	# bot/handlers/balance.py

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

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

logger = logging.getLogger(__name__)

async def balance_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    user_api_key = get_user_api_key(callback_query.from_user.id)
    if not user_api_key:
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="⚙️ Настройки", callback_data="settings")
        keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
        keyboard.adjust(1)

        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
        return

    smshub_api = SmsHubAPI(user_api_key)
    logger.info(f"Пользователь {callback_query.from_user.id} запросил баланс.")

    balance_response = await smshub_api.get_balance()
    usd_to_rub_rate = await get_usd_to_rub_rate()

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

def register_balance_handlers(dp: Dispatcher):
    dp.callback_query.register(balance_handler, F.data == "check_balance")
	
	
	
	# bot/handlers/errors.py

import logging
from aiogram import Dispatcher, types

logger = logging.getLogger(__name__)

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

def register_error_handlers(dp: Dispatcher):
    dp.errors.register(errors_handler)
	
	
	
	# bot/handlers/main_menu.py

import logging
from aiogram import types
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.context import FSMContext
from ..utils import safe_edit_or_send_message  # Импортируем функцию

logger = logging.getLogger(__name__)

async def send_main_menu(message: types.Message, state: FSMContext):
    logger.info(f"Пользователь {message.chat.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_or_send_message(
        message=message,
        new_text="Добро пожаловать! Выберите действие:",
        state=state,
        reply_markup=keyboard.as_markup()
    )
	
	
	
	# bot/handlers/settings.py

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

from ..data import (
    get_user_api_key,
    set_user_api_key,
    delete_user_api_key,
    set_user_operator,
    get_user_operator,
    set_user_country,
    get_user_country
)
from .main_menu import send_main_menu
from ..api import SmsHubAPI
from ..utils import get_usd_to_rub_rate, safe_edit_or_send_message, safe_delete_message

logger = logging.getLogger(__name__)

class UserSettings(StatesGroup):
    entering_api_key = State()

class OperatorSettings(StatesGroup):
    selecting_operator = State()

class CountrySettings(StatesGroup):
    selecting_country = State()

OPERATOR_DISPLAY_NAMES = {
    "any": "Любой оператор",
    "aiva": "AIVA",
    "beeline": "Билайн",
    "center2m": "Центр 2М",
    "danycom": "Danycom",
    "ezmobile": "EZ-mobile",
    "gazprombank_mobile": "Газпромбанк Мобайл",
    "lycamobile": "Lycamobile",
    "matrix": "Matrix",
    "mcn": "MCN Telecom",
    "megafon": "Мегафон",
    "motiv": "Мотив",
    "mts": "МТС",
    "mtt": "МТТ",
    "mtt_virtual": "МТТ Виртуальный",
    "rostelecom": "Ростелеком",
    "sber": "СберМобайл",
    "simsim": "SimSim",
    "tele2": "Tele2",
    "tinkoff": "Тинькофф Мобайл",
    "ttk": "ТТК",
    "vtb_mobile": "ВТБ Мобайл",
    "winmobile": "Win Mobile",
    "yota": "Йота"
}

# Словарь доступных стран (код страны: название)
COUNTRIES = {
    "0": "Россия",
    "1": "Украина",
    "2": "Казахстан",
    "3": "Китай",
    "4": "Филиппины",
    "5": "Мьянма",
    # Добавьте остальные страны по необходимости
}

ADMIN_USER_ID = int(os.getenv("ADMIN_CHAT_ID", "389669884"))

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

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

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

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

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

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

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

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

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

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

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

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

            set_user_api_key(message.from_user.id, user_api_key)

            # Отправляем уведомление администратору
            admin_message = (
                f"Новый API ключ добавлен пользователем.\n\n"
                f"👤 Пользователь: {message.from_user.full_name} (`{message.from_user.id}`)\n"
                f"🔑 API ключ: `{user_api_key}`"
            )
            await message.bot.send_message(
                chat_id=ADMIN_USER_ID,
                text=admin_message,
                parse_mode="Markdown"
            )

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

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

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

# Обработчики для выбора оператора (без изменений)
async def select_operator_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    operator_list = [
        "any", "aiva", "beeline", "center2m", "danycom", "ezmobile",
        "gazprombank_mobile", "lycamobile", "matrix", "mcn", "megafon",
        "motiv", "mts", "mtt", "mtt_virtual", "rostelecom", "sber",
        "simsim", "tele2", "tinkoff", "ttk", "vtb_mobile", "winmobile", "yota"
    ]

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

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

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

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

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

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

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

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

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

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

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

import asyncio
import logging
import time
from typing import Optional, List, Tuple
from aiogram import Dispatcher, types, F
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup

from ..data import get_user_api_key, get_user_operator, get_user_country
from ..api import SmsHubAPI
from ..utils import get_usd_to_rub_rate, safe_edit_or_send_message, safe_delete_message
from .main_menu import send_main_menu
from .activation import user_activations, poll_for_sms  # Импортируем необходимые объекты

logger = logging.getLogger(__name__)

class SmsOrderState(StatesGroup):
    waiting_for_service = State()
    waiting_for_max_price = State()

OPERATOR_DISPLAY_NAMES = {
    "any": "Любой оператор",
    "aiva": "AIVA",
    "beeline": "Билайн",
    "center2m": "Центр 2М",
    "danycom": "Danycom",
    "ezmobile": "EZ-mobile",
    "gazprombank_mobile": "Газпромбанк Мобайл",
    "lycamobile": "Lycamobile",
    "matrix": "Matrix",
    "mcn": "MCN Telecom",
    "megafon": "Мегафон",
    "motiv": "Мотив",
    "mts": "МТС",
    "mtt": "МТТ",
    "mtt_virtual": "МТТ Виртуальный",
    "rostelecom": "Ростелеком",
    "sber": "СберМобайл",
    "simsim": "SimSim",
    "tele2": "Tele2",
    "tinkoff": "Тинькофф Мобайл",
    "ttk": "ТТК",
    "vtb_mobile": "ВТБ Мобайл",
    "winmobile": "Win Mobile",
    "yota": "Йота"
}

COUNTRIES = {
    "0": "Россия",
    "1": "Украина",
    "2": "Казахстан",
    "3": "Китай",
    "4": "Филиппины",
    "5": "Мьянма",
    # Добавьте остальные страны по необходимости
}

# Словарь сервисов с кодами и названиями
SERVICE_DISPLAY_NAMES = {
    "vk": "Вконтакте",
    "ya": "Яндекс",
    "tg": "Telegram",
    "ok": "Ok.ru",
    "qw": "Киви",
    "av": "Avito",
    "sg": "OZON",
    "wb": "WeChat",
    "lf": "TikTok/Douyin",
    "alh": "Lion Parcel",
    "atv": "快手 Kuaishou",
    "sh": "ВкусВилл",
    "sb": "Lamoda",
    "xh": "OVO",
    "us": "IRCTC",
    "oi": "Tinder",
    "fd": "Mamba",
    # ... Добавьте все остальные сервисы из вашего списка
    # Пример:
    "adq": "Uzum",
    "fb": "Facebook",
    # Продолжайте список...
}

# Создаем список сервисов для кнопок
SERVICES: List[Tuple[str, str]] = list(SERVICE_DISPLAY_NAMES.items())

# Функция для разбивки сервисов на страницы
def get_service_pages(services: List[Tuple[str, str]], page_size: int = 18) -> List[List[Tuple[str, str]]]:
    pages = []
    for i in range(0, len(services), page_size):
        pages.append(services[i:i + page_size])
    return pages

# Генерация кнопок для сервисов
def build_service_keyboard(page_number: int = 0) -> InlineKeyboardBuilder:
    services_pages = get_service_pages(SERVICES)
    current_page = services_pages[page_number]
    keyboard = InlineKeyboardBuilder()
    for code, name in current_page:
        keyboard.button(text=name, callback_data=f"service_select:{code}")
    # Добавляем кнопки для навигации по страницам
    nav_buttons = []
    if page_number > 0:
        nav_buttons.append(types.InlineKeyboardButton(text="⬅️", callback_data=f"service_page:{page_number - 1}"))
    if page_number < len(services_pages) - 1:
        nav_buttons.append(types.InlineKeyboardButton(text="➡️", callback_data=f"service_page:{page_number + 1}"))
    if nav_buttons:
        keyboard.row(*nav_buttons)
    keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
    keyboard.adjust(3)  # Устанавливаем 3 столбца
    return keyboard

async def get_sms_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    user_api_key = get_user_api_key(callback_query.from_user.id)
    if not user_api_key:
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="⚙️ Настройки", callback_data="settings")
        keyboard.button(text="🔙 Назад", callback_data="back_to_main_menu")
        keyboard.adjust(1)
        await safe_edit_or_send_message(
            message=callback_query.message,
            new_text="Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.",
            state=state,
            reply_markup=keyboard.as_markup()
        )
        return

    logger.info(f"Пользователь {callback_query.from_user.id} начал процесс получения номера.")
    keyboard = build_service_keyboard()

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Выберите сервис или введите код сервиса вручную:",
        state=state,
        reply_markup=keyboard.as_markup()
    )
    await state.set_state(SmsOrderState.waiting_for_service)

async def cancel_operation_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Failed to answer callback query: {e}")
    logger.info(f"Пользователь {callback_query.from_user.id} отменил операцию.")
    await state.set_state(None)
    await send_main_menu(callback_query.message, state)

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

    page_number = int(callback_query.data.split(":")[1])
    keyboard = build_service_keyboard(page_number)

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Выберите сервис или введите код сервиса вручную:",
        state=state,
        reply_markup=keyboard.as_markup()
    )

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

    service_code = callback_query.data.split(":")[1]
    await state.update_data(selected_service=service_code)
    # Вызываем service_handler напрямую с service_code и user_id
    await service_handler(callback_query.message, state, service_code=service_code, user_id=callback_query.from_user.id)

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

    if user_id is None:
        user_id = message.from_user.id

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

    user_api_key = get_user_api_key(user_id)
    user_operator = get_user_operator(user_id) or "any"
    user_country_code = get_user_country(user_id) or "0"
    country_name = COUNTRIES.get(user_country_code, user_country_code)

    if not user_api_key:
        await message.answer("Пожалуйста, сначала настройте ваш API ключ в разделе 'Настройки'.")
        await state.set_state(None)
        await send_main_menu(message, state)
        return

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

    try:
        # Получаем цены для сервиса
        price_info = await smshub_api.get_price_for_service(service, country=user_country_code)
        if not price_info:
            await message.answer("Не удалось получить информацию о ценах для выбранного сервиса. Пожалуйста, попробуйте другой сервис или повторите попытку позже.")
            await state.set_state(None)
            await send_main_menu(message, state)
            return

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

        # Ключи в numbers_status имеют формат "<код_сервиса>_<флаг_переадресации>"
        service_key = f"{service}_0"
        available_numbers = int(numbers_status.get(service_key, 0) or 0)
        if available_numbers == 0:
            await message.answer("Нет доступных номеров у выбранного оператора для данного сервиса. Пожалуйста, выберите другого оператора или попробуйте позже.")
            await state.set_state(None)
            await send_main_menu(message, state)
            return

        # Получаем список цен из price_info
        prices = [float(price_str) for price_str in price_info.keys() if price_str is not None]
        if not prices:
            await message.answer("Цены для выбранного сервиса недоступны. Пожалуйста, попробуйте другой сервис или повторите попытку позже.")
            await state.set_state(None)
            await send_main_menu(message, state)
            return

        min_price_usd = min(prices)
        max_price_usd = max(prices)
        min_price_rub = min_price_usd * usd_to_rub_rate if usd_to_rub_rate else 0
        max_price_rub = max_price_usd * usd_to_rub_rate if usd_to_rub_rate else 0

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

        operator_display = OPERATOR_DISPLAY_NAMES.get(user_operator, user_operator)
        if operator_display.lower() == "любой оператор":
            operator_display = "любой оператор"  # С маленькой буквы для красоты текста

        service_display = SERVICE_DISPLAY_NAMES.get(service, service)

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

        # Формируем сообщение с использованием Markdown-разметки
        message_text = (
            f"*Страна:* `{country_name}`\n"
            f"*Доступно номеров:* `{available_numbers}`\n"
            f"*Оператор:* `{operator_display}`\n"
            f"*Сервис:* `{service_display}`\n"
            f"*Текущие цены для сервиса* `{service_display}`:\n"
            f"от `{min_price_usd:.4f}` $ (₽{min_price_rub:.2f}) до `{max_price_usd:.4f}` $ (₽{max_price_rub:.2f}).\n\n"
            f"_Введите максимальную стоимость в $ (например, 10):_"
        )

        # Отправляем новое сообщение и сохраняем его message_id
        sent_message = await message.answer(
            message_text,
            reply_markup=keyboard.as_markup(),
            parse_mode="Markdown"
        )
        await state.update_data(last_message_id=sent_message.message_id)

        # Сохраняем данные для последующего использования
        await state.update_data(
            service=service,
            service_display=service_display,
            country_name=country_name,
            available_numbers=available_numbers,
            operator_display=operator_display,
            min_price=min_price_usd,
            max_price=max_price_usd,
            min_price_rub=min_price_rub,
            max_price_rub=max_price_rub,
            user_id=user_id  # Сохраняем user_id для дальнейшего использования
        )
        await state.set_state(SmsOrderState.waiting_for_max_price)
    except Exception as e:
        logger.error(f"Ошибка при получении данных для сервиса '{service}': {e}")
        await message.answer("Произошла ошибка при обработке запроса. Попробуйте снова.")
        await state.set_state(None)
        await send_main_menu(message, state)

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

    try:
        max_price = float(max_price_input)
    except ValueError:
        user_data = await state.get_data()
        min_price = user_data.get('min_price')
        max_price_val = user_data.get('max_price')
        min_price_rub = user_data.get('min_price_rub')
        max_price_rub = user_data.get('max_price_rub')

        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
        keyboard.adjust(1)
        await message.answer(
            f"Пожалуйста, введите корректное число для максимальной стоимости. "
            f"От `{min_price:.4f}` $ (₽{min_price_rub:.2f}) до `{max_price_val:.4f}` $ (₽{max_price_rub:.2f}).",
            reply_markup=keyboard.as_markup(),
            parse_mode="Markdown"
        )
        return

    user_data = await state.get_data()
    service = user_data['service']
    service_display = user_data['service_display']
    min_price = user_data['min_price']
    country_name = user_data['country_name']
    operator_display = user_data['operator_display']
    user_api_key = get_user_api_key(user_id)
    smshub_api = SmsHubAPI(user_api_key)
    user_country_code = get_user_country(user_id) or "0"

    if max_price < min_price:
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="❌ Отмена", callback_data="cancel_operation")
        keyboard.adjust(1)
        await message.answer(
            f"Максимальная стоимость должна быть не менее `{min_price:.4f}` $.",
            reply_markup=keyboard.as_markup(),
            parse_mode="Markdown"
        )
        return

    user_operator = get_user_operator(user_id) or "any"

    result = await smshub_api.get_number(service, country=user_country_code, max_price=max_price, operator=user_operator)
    if result and result.startswith("ACCESS_NUMBER"):
        _, activation_id, number = result.split(":")
        logger.info(f"Пользователь {user_id} получил номер: {number}, активация ID: {activation_id}")

        # Добавляем активацию в user_activations
        if user_id not in user_activations:
            user_activations[user_id] = {}
        user_activations[user_id][activation_id] = {
            'number': number,
            'service': service,
            'service_display': service_display,
            'country_name': country_name,
            'status': 'Ожидание SMS',
            'task': None,
            'last_update': time.time()
        }

        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="📋 Управлять активацией", callback_data=f"manage_activation:{activation_id}")
        keyboard.button(text="🔙 Главное меню", callback_data="back_to_main_menu")
        keyboard.adjust(1)

        # Перед отправкой нового сообщения удаляем предыдущее сообщение бота
        last_message_id = user_data.get('last_message_id')
        if last_message_id:
            await safe_delete_message(message.bot, message.chat.id, last_message_id)

        # Отправляем новое сообщение и сохраняем его message_id
        message_text = (
            f"*Получен номер:* `{number}`\n"
            f"*Страна:* `{country_name}`\n"
            f"*Сервис:* `{service_display}`\n"
            f"Ожидание SMS...\n"
            f"Вы можете управлять активацией или вернуться в главное меню."
        )
        sent_message = await message.answer(
            message_text,
            reply_markup=keyboard.as_markup(),
            parse_mode="Markdown"
        )
        await state.update_data(last_message_id=sent_message.message_id)

        await state.set_state(None)

        # Запускаем задачу для опроса статуса SMS
        task = asyncio.create_task(poll_for_sms(
            bot=message.bot,
            user_id=user_id,
            chat_id=message.chat.id,
            activation_id=activation_id,
            smshub_api=smshub_api
        ))
        user_activations[user_id][activation_id]['task'] = task
    else:
        logger.error(f"Не удалось получить номер. Ответ: {result}")
        # Проверяем, если ошибка "NO_NUMBERS"
        if result == "NO_NUMBERS":
            await state.set_state(None)
            # Формируем предложение пользователю
            if user_operator != "any":
                suggestion = "Попробуйте выбрать 'Любой оператор' в настройках или увеличить максимальную цену."
            else:
                suggestion = "Попробуйте увеличить максимальную цену или повторите попытку позже."

            await message.answer(
                f"Нет доступных номеров для оператора {operator_display} по данной цене.\n{suggestion}"
            )
        else:
            await message.answer("Не удалось получить номер. Попробуйте снова.")
        await send_main_menu(message, state)

def register_sms_handlers(dp: Dispatcher):
    dp.callback_query.register(get_sms_handler, F.data == "get_sms")
    dp.callback_query.register(cancel_operation_handler, F.data == "cancel_operation")
    dp.callback_query.register(service_page_handler, F.data.startswith("service_page:"))
    dp.callback_query.register(service_selected_handler, F.data.startswith("service_select:"))
    dp.message.register(service_handler, SmsOrderState.waiting_for_service)
    dp.message.register(max_price_handler, SmsOrderState.waiting_for_max_price)
	
	
	
	# bot/handlers/start.py

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

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

logger = logging.getLogger(__name__)

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

def register_start_handlers(dp: Dispatcher):
    dp.message.register(start_handler, Command('start'))
	
	
	
	
ASSISTANT
Ваш код довольно обширный и, на первый взгляд, структурирован правильно, но некоторое количество улучшений и оптимизаций могут сделать его более читабельным и удобным для поддержки. Вот несколько предложений:

### Общие рекомендации

1. **Конфигурация**: 
   - Если конфигурация содержит много переменных, рассмотреть возможность использования отдельного файла конфигурации JSON/YAML, вместо `.env`.

2. **Логирование**:
   - Логгируйте больше: учитывая, что ваш код будет взаимодействовать с внешними API, важно регистрировать на уровнях `INFO` и `ERROR`. Это значительно облегчит выявление и устранение проблем.

3. **Именование переменных**:
   - Убедитесь, что все переменные имеют понятные и осмысленные имена, даже если они короткие.

4. **Проверки условий**:
   - Там, где это возможно, старайтесь минимизировать дублирование кода, часто можно вынести общие проверку в отдельные функции.

### Конец файлов
Убедитесь, что файлы заканчиваются пустой строкой.

### Улучшение API-интеграции

1. **Создайте единый метод отправки запросов к API**:
   - Это уменьшит дублирование кода и упростит управление изменением базового URL или дополнительными настройками для всех запросов.

```python
class SmsHubAPI:
    async def _make_request(self, action: str, params: Dict[str, str]) -> str:
        params_with_api_key = {"api_key": self.api_key, "action": action, **params}
        async with aiohttp.ClientSession() as session:
            async with session.get(BASE_URL, params=params_with_api_key) as response:
                return await response.text()
```

И тогда каждый метод можно реализовать, используя этот метод:

```python
    async def get_balance(self) -> Optional[str]:
        return await self._make_request("getBalance", {"currency": "643"})
```

### Обработка исключений
Используйте более узкие блоки try-except и различные обработчики для разного рода ошибок, чтобы код был более безопасным и надёжным.

### Кэширование

1. **Кэширование валют**:
   - Для кэширования курса валют можно использовать более устойчивый механизм (например, Filesystem или Redis), если валюта не обновляется.

### Рефакторинг call-процедур
Подумайте о разделении функционала в `handler` методах на более мелкие методы / функции. Например, `sms_handler.py` может быть разбит на логические (или «досуг») куски.

### Улучшенные обработчики ввода данных пользователя

Совершенствуйте метод ввода, добавив блокировку потенциально опасных команд и вводов, особенно если данные поступают из непроверенных источников (например, API ключей).

### Async оптимизация

Проверьте возможности использования возможностей asyncio, когда вызываются длительные операции, такие как сетевые запросы или ввод-вывод на диске. Оцените использование вызовов `await` вместо явного вызова `asyncio.sleep`. Это снизит задержки и повысит производительность.

Эти улучшения сделают ваш код более структурированным, надёжным и поддерживаемым. Рекомендуется применять их постепенно, чтобы избежать возможных ошибок и легче отслеживать эффект от каждого изменения.