diff --git a/api/schemas/__init__.py b/api/schemas/__init__.py index dabb5fbc..08a2a565 100644 --- a/api/schemas/__init__.py +++ b/api/schemas/__init__.py @@ -1,7 +1,6 @@ from .coupons import CouponBase, CouponResponse, CouponUpdate, CouponUsageResponse from .gifts import GiftBase, GiftResponse, GiftUpdate, GiftUsageResponse from .keys import KeyDetailsResponse, KeyResponse -from .settings import SettingResponse, SettingUpsert from .misc import ( BlockedUserResponse, ManualBanResponse, @@ -12,5 +11,6 @@ from .misc import ( ) from .referrals import ReferralResponse from .servers import ServerBase, ServerResponse, ServerUpdate +from .settings import SettingResponse, SettingUpsert from .tariffs import TariffBase, TariffResponse, TariffUpdate from .users import UserBase, UserResponse, UserUpdate diff --git a/handlers/admin/clusters/cluster_sync.py b/handlers/admin/clusters/cluster_sync.py index 6cd8c6fc..d907d233 100644 --- a/handlers/admin/clusters/cluster_sync.py +++ b/handlers/admin/clusters/cluster_sync.py @@ -286,7 +286,7 @@ async def handle_sync_server( try: if key["panel_type"] == "remnawave": tariff = tariffs_cache.get(key["tariff_id"]) if key["tariff_id"] else None - + if tariff and server_info: if tariff.get("subgroup_title") and tariff.get("subgroup_title") not in server_info.get( "tariff_subgroups", [] @@ -313,7 +313,7 @@ async def handle_sync_server( current_traffic_limit_gb_from_key = key.get("current_traffic_limit") selected_device_limit_from_key = key.get("selected_device_limit") selected_traffic_limit_gb_from_key = key.get("selected_traffic_limit") - + if tariff: if current_traffic_limit_gb_from_key is not None: traffic_limit_bytes = int(current_traffic_limit_gb_from_key * 1024**3) @@ -499,9 +499,9 @@ async def handle_sync_cluster( batch_size = 50 total_keys = len(keys_to_sync) processed_count = 0 - + for batch_start in range(0, total_keys, batch_size): - batch = keys_to_sync[batch_start:batch_start + batch_size] + batch = keys_to_sync[batch_start : batch_start + batch_size] batch_end = batch_start + len(batch) logger.info(f"[Sync] Обработка батча {batch_start}-{batch_end} из {total_keys}") @@ -516,7 +516,7 @@ async def handle_sync_cluster( current_traffic_limit_gb_from_key = key.get("current_traffic_limit") selected_device_limit_from_key = key.get("selected_device_limit") selected_traffic_limit_gb_from_key = key.get("selected_traffic_limit") - + if tariff: if current_traffic_limit_gb_from_key is not None: traffic_limit_bytes = int(current_traffic_limit_gb_from_key * 1024**3) @@ -533,11 +533,13 @@ async def handle_sync_cluster( hwid_limit = int(selected_device_limit_from_key) else: hwid_limit = tariff.get("device_limit") - + subgroup_title = tariff.get("subgroup_title") expire_iso = ( - datetime.utcfromtimestamp(key["expiry_time"] / 1000).replace(tzinfo=timezone.utc).isoformat() + datetime.utcfromtimestamp(key["expiry_time"] / 1000) + .replace(tzinfo=timezone.utc) + .isoformat() ) if use_country_selection: @@ -599,7 +601,14 @@ async def handle_sync_cluster( "hwid_limit": hwid_limit, } else: - return {"key": key, "success": False, "needs_recreate": True, "tariff": tariff, "traffic_limit_bytes": traffic_limit_bytes, "hwid_limit": hwid_limit} + return { + "key": key, + "success": False, + "needs_recreate": True, + "tariff": tariff, + "traffic_limit_bytes": traffic_limit_bytes, + "hwid_limit": hwid_limit, + } except Exception as e: logger.error(f"[Sync] Ошибка API для {key.get('email')}: {e}") @@ -610,24 +619,24 @@ async def handle_sync_cluster( bulk_updates = [] recreate_tasks = [] - + for result in results: if isinstance(result, Exception): logger.error(f"[Sync] Exception в батче: {result}") continue - + if not isinstance(result, dict): continue - + key = result.get("key") if not key: continue - + try: if result.get("success") and result.get("new_link"): new_link = result["new_link"] tariff = result.get("tariff") - + key_value = await make_aggregated_link( session=session, cluster_all=cluster_servers, @@ -644,10 +653,10 @@ async def handle_sync_cluster( "remnawave_link": new_link, "key": key_value, }) - + elif result.get("needs_recreate"): recreate_tasks.append((key, result)) - + except Exception as e: logger.error(f"[Sync] Ошибка подготовки для {key.get('email')}: {e}") @@ -682,7 +691,7 @@ async def handle_sync_cluster( delete(Key).where(Key.tg_id == key["tg_id"], Key.client_id == key["client_id"]) ) await session.commit() - + cluster_id_for_recreate = key["server_id"] if use_country_selection else cluster_name await create_key_on_cluster( cluster_id_for_recreate, @@ -707,7 +716,7 @@ async def handle_sync_cluster( processed_count = batch_end progress_percent = int((processed_count / total_keys) * 100) progress_bar = "█" * (progress_percent // 5) + "░" * (20 - progress_percent // 5) - + try: await callback_query.message.edit_text( text=( @@ -719,20 +728,19 @@ async def handle_sync_cluster( ) except Exception: pass - + else: for key in keys_to_sync: try: traffic_limit_bytes = 0 hwid_limit = 0 - subgroup_title = None tariff = tariffs_cache.get(key["tariff_id"]) if key["tariff_id"] else None current_device_limit_from_key = key.get("current_device_limit") current_traffic_limit_gb_from_key = key.get("current_traffic_limit") selected_device_limit_from_key = key.get("selected_device_limit") selected_traffic_limit_gb_from_key = key.get("selected_traffic_limit") - + if tariff: if current_traffic_limit_gb_from_key is not None: traffic_limit_bytes = int(current_traffic_limit_gb_from_key * 1024**3) @@ -742,15 +750,15 @@ async def handle_sync_cluster( traffic_limit_bytes = int(tariff.get("traffic_limit") * 1024**3) else: traffic_limit_bytes = 0 - + if current_device_limit_from_key is not None: hwid_limit = int(current_device_limit_from_key) elif selected_device_limit_from_key is not None: hwid_limit = int(selected_device_limit_from_key) else: hwid_limit = tariff.get("device_limit") - - subgroup_title = tariff.get("subgroup_title") + + tariff.get("subgroup_title") elif key["tariff_id"]: logger.warning( f"[Sync] Ключ {key['client_id']} с несуществующим тарифом ID={key['tariff_id']} — " diff --git a/handlers/admin/management/__init__.py b/handlers/admin/management/__init__.py index 5ad3d475..407200c6 100644 --- a/handlers/admin/management/__init__.py +++ b/handlers/admin/management/__init__.py @@ -9,6 +9,6 @@ from . import ( domain, # noqa: F401 file_upload, # noqa: F401 import_3xui, # noqa: F401 + import_remnawave, # noqa: F401 maintenance, # noqa: F401 - import_remnawave # noqa: F401 ) diff --git a/handlers/admin/settings/keyboard.py b/handlers/admin/settings/keyboard.py index 9c96be26..02938277 100644 --- a/handlers/admin/settings/keyboard.py +++ b/handlers/admin/settings/keyboard.py @@ -1,8 +1,9 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.utils.keyboard import InlineKeyboardBuilder -from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn from core.settings.money_config import get_currency_mode + +from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn from .settings_config import ( BUTTON_TITLES, MODES_TITLES, @@ -217,4 +218,3 @@ def build_settings_money_kb(money_state: dict[str, object]) -> InlineKeyboardMar ) return builder.as_markup() - diff --git a/handlers/admin/settings/settings_tariffs.py b/handlers/admin/settings/settings_tariffs.py index 46c0c906..90dd04a9 100644 --- a/handlers/admin/settings/settings_tariffs.py +++ b/handlers/admin/settings/settings_tariffs.py @@ -77,10 +77,18 @@ def build_tariffs_settings_text() -> str: "⚙️ Настройки тарификации", "", f"• Понижение условий при изменении: {'включено' if allow_downgrade else 'выключено'}", - f"• Режим доплат пакетами: {format_pack_mode_label(pack_mode)}", + f"• Режим доплат: {format_pack_mode_label(pack_mode)}", f"• Перерасчёт при докупке: {'да' if recalc_enabled else 'нет'}", "", - "Нажмите «Режим пакетов», чтобы подробнее настроить доплаты к активной подписке.", + "ℹ️ Как работают режимы доплат:", + "", + "🔹 Пакеты", + "
Позволяет докупать трафик и устройства к активной подписке неограниченное количество раз.", + "При продлении подписки все параметры возвращаются к исходно сконфигурированному тарифу.", + "\n\n🔹 Базовый конфигуратор (выкл. режим пакетов)", + "
Позволяет выбрать параметры тарифа из доступных опций.", + "Выбранные условия сохраняются и будут использоваться при последующих продлениях.", + "При включённом понижении клиент сможет понижать условия тарифа.", ] return "\n".join(lines) @@ -145,14 +153,14 @@ def build_tariffs_packs_text() -> str: "При активной подписке можно продавать не новый тариф, а доплаты к текущим лимитам.", "", "Режимы:", + "
", "• Выкл — доплаты пакетами отключены, работает обычный конфигуратор лимитов (клиент меняет конфигурацию тарифа для продления).", "• Только трафик — пользователь докупает ГБ до продления, количество устройств не меняется.", "• Только устройства — пользователь докупает устройства до продления, лимит трафика не меняется.", "• Трафик и устройства — можно одновременно докупать и трафик, и устройства до продления как единый пакет.", + f"\nТекущий режим: {format_pack_mode_label(current)}", "", - f"Текущий режим: {format_pack_mode_label(current)}", - "", - "Выберите режим, который будет использоваться при доплате за расширение уже существующей подписки.", + "Выберите режим, который покажет варианты для доплаты.\n ❗Доплаты не переносятся при продлении.", ] return "\n".join(lines) diff --git a/handlers/admin/tariffs/tariff_configurator/traffic_pricing.py b/handlers/admin/tariffs/tariff_configurator/traffic_pricing.py index 8b443f87..99db0a95 100644 --- a/handlers/admin/tariffs/tariff_configurator/traffic_pricing.py +++ b/handlers/admin/tariffs/tariff_configurator/traffic_pricing.py @@ -1,6 +1,7 @@ from datetime import datetime from aiogram import F +from aiogram.exceptions import TelegramBadRequest from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery, Message from sqlalchemy import select @@ -146,7 +147,12 @@ async def clear_traffic_overrides(callback: CallbackQuery, state: FSMContext, se await session.commit() text, markup = build_traffic_overrides_screen(tariff) - await callback.message.edit_text(text=text, reply_markup=markup) + + try: + await callback.message.edit_text(text=text, reply_markup=markup) + except TelegramBadRequest as e: + if "message is not modified" not in str(e): + raise @router.message(TariffConfigState.entering_traffic_overrides, IsAdminFilter()) diff --git a/handlers/admin/users/keyboard.py b/handlers/admin/users/keyboard.py index 31c002ec..f49bf5bd 100644 --- a/handlers/admin/users/keyboard.py +++ b/handlers/admin/users/keyboard.py @@ -262,7 +262,9 @@ def build_key_edit_kb(key_details: dict, email: str, is_configurable: bool = Fal ) builder.button( text="🔁 Пересоздать", - callback_data=AdminUserEditorCallback(action="users_recreate_key", data=email, tg_id=key_details["tg_id"]).pack(), + callback_data=AdminUserEditorCallback( + action="users_recreate_key", data=email, tg_id=key_details["tg_id"] + ).pack(), ) builder.button( text="📦 Тариф", diff --git a/handlers/admin/users/users_keys.py b/handlers/admin/users/users_keys.py index 8adf8d83..5729d7bd 100644 --- a/handlers/admin/users/users_keys.py +++ b/handlers/admin/users/users_keys.py @@ -14,8 +14,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession -from config import USE_COUNTRY_SELECTION, REMNAWAVE_TOKEN_LOGIN_ENABLED, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD -from panels.remnawave import RemnawaveAPI +from config import REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, REMNAWAVE_TOKEN_LOGIN_ENABLED, USE_COUNTRY_SELECTION from core.bootstrap import MODES_CONFIG from database import ( delete_key, @@ -43,6 +42,7 @@ from handlers.utils import generate_random_email, handle_error from hooks.hook_buttons import insert_hook_buttons from hooks.processors import process_admin_key_edit_menu from logger import logger +from panels.remnawave import RemnawaveAPI from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn, build_admin_back_kb from .keyboard import ( @@ -738,9 +738,7 @@ async def handle_recreate_key_confirm( ) return - remnawave_servers = [ - s for s in cluster if s.get("panel_type", "3x-ui").lower() == "remnawave" - ] + remnawave_servers = [s for s in cluster if s.get("panel_type", "3x-ui").lower() == "remnawave"] if not remnawave_servers: await callback_query.message.edit_text( @@ -1396,8 +1394,8 @@ async def handle_admin_unfreeze_subscription( async def change_expiry_time(expiry_time: int, email: str, session: AsyncSession) -> Exception | None: result = await session.execute( select( - Key.client_id, - Key.tariff_id, + Key.client_id, + Key.tariff_id, Key.server_id, Key.current_device_limit, Key.current_traffic_limit, @@ -1551,7 +1549,7 @@ async def render_config_menu(callback_query: CallbackQuery, state: FSMContext, s extra_traf_str = f" + {extra_traffic} ГБ (докуплено)" if extra_traffic > 0 else "" text += f"📊 Трафик: {base_traffic} ГБ{extra_traf_str}\n" else: - text += f"📊 Трафик: безлимит\n" + text += "📊 Трафик: безлимит\n" text += "\nВыберите что редактировать:" @@ -1771,7 +1769,7 @@ async def handle_cfg_input_addon(message: Message, state: FSMContext, session: A extra_traf_str = f" + {extra_traffic} ГБ (докуплено)" if extra_traffic > 0 else "" text += f"📊 Трафик: {base_traffic} ГБ{extra_traf_str}\n" else: - text += f"📊 Трафик: безлимит\n" + text += "📊 Трафик: безлимит\n" text += "\nВыберите что редактировать:" @@ -1820,7 +1818,7 @@ async def handle_cfg_save(callback_query: CallbackQuery, state: FSMContext, sess tariff_base_traffic = tariff.get("traffic_limit") or 0 extra_base_traffic = max(0, (base_traffic or 0) - tariff_base_traffic) if base_traffic else 0 traffic_extra_price = extra_base_traffic * traffic_step - + selected_price = base_price + devices_extra_price + traffic_extra_price result = await session.execute(select(Key).where(Key.email == email)) diff --git a/handlers/keys/key_mode/key_discount_mode.py b/handlers/keys/key_mode/key_discount_mode.py index b6f2a747..a6fa16f5 100644 --- a/handlers/keys/key_mode/key_discount_mode.py +++ b/handlers/keys/key_mode/key_discount_mode.py @@ -74,7 +74,7 @@ async def handle_discount_entry(callback: CallbackQuery, session: AsyncSession): tariffs = await get_tariffs(session=session, group_code=group_code) except Exception as e: logger.error(f"[DISCOUNT] Не удалось получить обычные тарифы: {e}") - + if not tariffs: await callback.message.edit_text("❌ Тарифы временно недоступны.") return @@ -152,7 +152,7 @@ async def handle_ultra_discount(callback: CallbackQuery, session: AsyncSession): tariffs = await get_tariffs(session=session, group_code=group_code) except Exception as e: logger.error(f"[DISCOUNT_MAX] Не удалось получить обычные тарифы: {e}") - + if not tariffs: await callback.message.edit_text("❌ Тарифы временно недоступны.") return diff --git a/handlers/keys/key_renew.py b/handlers/keys/key_renew.py index db5f5dcd..f74f58f8 100644 --- a/handlers/keys/key_renew.py +++ b/handlers/keys/key_renew.py @@ -339,7 +339,9 @@ async def show_tariffs_in_renew_subgroup(callback: CallbackQuery, state: FSMCont filtered = [t for t in tariffs if t["subgroup_title"] == subgroup and t["is_active"]] if not filtered and discount_info.get("available"): - logger.warning(f"[RENEW_SUBGROUP] Нет тарифов со скидкой {group_code} в подгруппе '{subgroup}', fallback на {original_group_code}") + logger.warning( + f"[RENEW_SUBGROUP] Нет тарифов со скидкой {group_code} в подгруппе '{subgroup}', fallback на {original_group_code}" + ) group_code = original_group_code subgroup = await find_subgroup_by_hash(session, subgroup_hash, group_code) if subgroup: @@ -700,11 +702,13 @@ async def complete_key_renewal( new_tariff_device_limit = tariff.get("device_limit") new_tariff_traffic_limit_bytes = tariff.get("traffic_limit") - new_tariff_traffic_limit_gb = int(new_tariff_traffic_limit_bytes / GB) if new_tariff_traffic_limit_bytes else None - - selected_device_limit_db = key_info.get("selected_device_limit") - selected_traffic_limit_db = key_info.get("selected_traffic_limit") - current_traffic_limit_db = key_info.get("current_traffic_limit") + new_tariff_traffic_limit_gb = ( + int(new_tariff_traffic_limit_bytes / GB) if new_tariff_traffic_limit_bytes else None + ) + + key_info.get("selected_device_limit") + key_info.get("selected_traffic_limit") + key_info.get("current_traffic_limit") if new_tariff_device_limit is None: final_device_limit = None @@ -813,9 +817,9 @@ async def complete_key_renewal( effective_client_id = key_row["client_id"] if key_row else client_id await update_key_expiry(session, effective_client_id, new_expiry_time) - + update_values = {"tariff_id": tariff_id} - + if not tariff.get("configurable"): if new_tariff_device_limit is None: update_values["selected_device_limit"] = None @@ -830,7 +834,7 @@ async def complete_key_renewal( else: update_values["selected_traffic_limit"] = new_tariff_traffic_limit_gb update_values["current_traffic_limit"] = final_traffic_limit - + await session.execute(update(Key).where(Key.email == email).values(**update_values)) await update_balance(session, tg_id, -cost) diff --git a/handlers/notifications/general_notifications.py b/handlers/notifications/general_notifications.py index f37bf4e9..07de525c 100644 --- a/handlers/notifications/general_notifications.py +++ b/handlers/notifications/general_notifications.py @@ -1,7 +1,9 @@ import asyncio + from datetime import datetime, timedelta import pytz + from aiogram import Bot, Router from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker diff --git a/handlers/notifications/hot_leads_notifications.py b/handlers/notifications/hot_leads_notifications.py index 1bb91f5f..26217f64 100644 --- a/handlers/notifications/hot_leads_notifications.py +++ b/handlers/notifications/hot_leads_notifications.py @@ -55,7 +55,9 @@ async def notify_hot_leads(bot: Bot, session: AsyncSession): discount_tariffs = await get_tariffs(session, group_code="discounts") active_discount_tariffs = [t for t in discount_tariffs if t.get("is_active")] if not active_discount_tariffs: - logger.warning(f"[HOT LEAD] Пропуск шага 2 для {tg_id}: нет активных тарифов со скидкой (discounts)") + logger.warning( + f"[HOT LEAD] Пропуск шага 2 для {tg_id}: нет активных тарифов со скидкой (discounts)" + ) continue keyboard = build_hot_lead_kb() @@ -110,7 +112,9 @@ async def notify_hot_leads(bot: Bot, session: AsyncSession): discount_max_tariffs = await get_tariffs(session, group_code="discounts_max") active_discount_max_tariffs = [t for t in discount_max_tariffs if t.get("is_active")] if not active_discount_max_tariffs: - logger.warning(f"[HOT LEAD] Пропуск шага 3 для {tg_id}: нет активных тарифов с максимальной скидкой (discounts_max)") + logger.warning( + f"[HOT LEAD] Пропуск шага 3 для {tg_id}: нет активных тарифов с максимальной скидкой (discounts_max)" + ) continue keyboard = build_hot_lead_kb(final=True) diff --git a/handlers/tariffs/addons/key_addons_main.py b/handlers/tariffs/addons/key_addons_main.py index aa8e4076..ed00b46c 100644 --- a/handlers/tariffs/addons/key_addons_main.py +++ b/handlers/tariffs/addons/key_addons_main.py @@ -81,8 +81,8 @@ async def render_addons_screen(callback: CallbackQuery, state: FSMContext, sessi tariff_name = tariff.get("name") or "подписка" - raw_device_options = cfg.get("device_options") or tariff.get("device_options") or [] - raw_traffic_options = cfg.get("traffic_options_gb") or tariff.get("traffic_options_gb") or [] + raw_device_options = cfg.get("device_options") or [] + raw_traffic_options = cfg.get("traffic_options_gb") or [] try: device_options = sorted( @@ -115,9 +115,9 @@ async def render_addons_screen(callback: CallbackQuery, state: FSMContext, sessi continue has_device_option = bool(device_int_options) - has_device_choice = len(device_int_options) > 1 - has_traffic_option = bool(traffic_int_options) + + has_device_choice = len(device_int_options) > 1 has_traffic_choice = len(traffic_int_options) > 1 if selected_devices is None and has_device_option: @@ -340,60 +340,6 @@ async def start_key_addons(callback: CallbackQuery, state: FSMContext, session: raw_device_options = cfg.get("device_options") or tariff.get("device_options") or [] raw_traffic_options = cfg.get("traffic_options_gb") or tariff.get("traffic_options_gb") or [] - device_int_options: list[int] = [] - for value in raw_device_options: - try: - device_int_options.append(int(value)) - except (TypeError, ValueError): - continue - - traffic_int_options: list[int] = [] - for value in raw_traffic_options: - try: - traffic_int_options.append(int(value)) - except (TypeError, ValueError): - continue - - base_device_limit = cfg.get("base_device_limit") - if base_device_limit is None: - base_device_limit = tariff.get("device_limit") - if base_device_limit is not None: - try: - base_device_int = int(base_device_limit) - if base_device_int not in device_int_options: - raw_device_options.append(base_device_int) - device_int_options.append(base_device_int) - except (TypeError, ValueError): - pass - - device_overrides_cfg = cfg.get("device_price_overrides") or tariff.get("device_overrides") or {} - if "0" in device_overrides_cfg and 0 not in device_int_options: - raw_device_options.append(0) - device_int_options.append(0) - - base_traffic_gb = cfg.get("base_traffic_gb") - if base_traffic_gb is None: - raw_limit = tariff.get("traffic_limit") - if raw_limit: - raw_limit = int(raw_limit) - if raw_limit >= GB: - base_traffic_gb = int(raw_limit / GB) - else: - base_traffic_gb = raw_limit - if base_traffic_gb is not None: - try: - base_traffic_int = int(base_traffic_gb) - if base_traffic_int not in traffic_int_options: - raw_traffic_options.append(base_traffic_int) - traffic_int_options.append(base_traffic_int) - except (TypeError, ValueError): - pass - - traffic_overrides_cfg = cfg.get("traffic_price_overrides") or tariff.get("traffic_overrides") or {} - if "0" in traffic_overrides_cfg and 0 not in traffic_int_options: - raw_traffic_options.append(0) - traffic_int_options.append(0) - try: device_options = sorted( raw_device_options, @@ -444,8 +390,13 @@ async def start_key_addons(callback: CallbackQuery, state: FSMContext, session: else (int(selected_traffic_limit_db) if selected_traffic_limit_db is not None else base_traffic_gb_from_tariff) ) - current_devices_for_price = int(current_devices) if current_devices is not None else None - current_traffic_gb_for_price = int(current_traffic_gb) if current_traffic_gb is not None else None + has_device_option = bool(device_options) + has_traffic_option = bool(traffic_options) + + current_devices_for_price = int(current_devices) if current_devices is not None and has_device_option else None + current_traffic_gb_for_price = ( + int(current_traffic_gb) if current_traffic_gb is not None and has_traffic_option else None + ) config_price_for_current = calculate_config_price( tariff=tariff, @@ -468,10 +419,8 @@ async def start_key_addons(callback: CallbackQuery, state: FSMContext, session: ) cfg_for_state = dict(cfg) - if device_options: - cfg_for_state["device_options"] = device_options - if traffic_options: - cfg_for_state["traffic_options_gb"] = traffic_options + cfg_for_state["device_options"] = device_options + cfg_for_state["traffic_options_gb"] = traffic_options await state.update_data( addon_key_email=email, diff --git a/handlers/tariffs/addons/key_addons_pack.py b/handlers/tariffs/addons/key_addons_pack.py index b24e57cd..67dd10a9 100644 --- a/handlers/tariffs/addons/key_addons_pack.py +++ b/handlers/tariffs/addons/key_addons_pack.py @@ -43,6 +43,7 @@ from .utils import ( format_traffic_label, ) + router = Router() diff --git a/handlers/tariffs/addons/utils.py b/handlers/tariffs/addons/utils.py index 9a5ead9d..98c3119a 100644 --- a/handlers/tariffs/addons/utils.py +++ b/handlers/tariffs/addons/utils.py @@ -73,7 +73,7 @@ def calc_remaining_ratio_seconds(expiry_time: Any, tariff: dict) -> tuple[int, i if isinstance(expiry_time, datetime): expiry_dt = expiry_time - elif isinstance(expiry_time, (int, float)): + elif isinstance(expiry_time, int | float): ts = float(expiry_time) if ts > 10_000_000_000: ts = ts / 1000.0 diff --git a/handlers/tariffs/buy/key_tariffs.py b/handlers/tariffs/buy/key_tariffs.py index 493e7be2..686349c3 100644 --- a/handlers/tariffs/buy/key_tariffs.py +++ b/handlers/tariffs/buy/key_tariffs.py @@ -51,8 +51,11 @@ def calculate_config_price( base_price = int(tariff.get("price_rub") or 0) - raw_device_options = cfg.get("device_options") or tariff.get("device_options") or [] - raw_traffic_options = cfg.get("traffic_options_gb") or tariff.get("traffic_options_gb") or [] + raw_device_options = tariff.get("device_options") + raw_traffic_options = tariff.get("traffic_options_gb") + + raw_device_options = raw_device_options if isinstance(raw_device_options, list) else [] + raw_traffic_options = raw_traffic_options if isinstance(raw_traffic_options, list) else [] device_values: list[int] = [] for value in raw_device_options: @@ -298,21 +301,19 @@ async def render_user_config_screen( logger.warning(f"[TARIFF_CFG] render_user_config_screen tariff_not_found: tariff_id={tariff_id}") return - raw_device_options = cfg.get("device_options") or tariff.get("device_options") or [] + raw_device_options = tariff.get("device_options") + raw_traffic_options = tariff.get("traffic_options_gb") + + raw_device_options = raw_device_options if isinstance(raw_device_options, list) else [] + raw_traffic_options = raw_traffic_options if isinstance(raw_traffic_options, list) else [] + try: - device_options = sorted( - raw_device_options, - key=lambda v: (int(v) == 0, int(v)), - ) + device_options = sorted(raw_device_options, key=lambda v: (int(v) == 0, int(v))) except (TypeError, ValueError): device_options = raw_device_options - raw_traffic_options = cfg.get("traffic_options_gb") or tariff.get("traffic_options_gb") or [] try: - traffic_options = sorted( - raw_traffic_options, - key=lambda v: (int(v) == 0, int(v)), - ) + traffic_options = sorted(raw_traffic_options, key=lambda v: (int(v) == 0, int(v))) except (TypeError, ValueError): traffic_options = raw_traffic_options @@ -536,76 +537,19 @@ async def start_user_tariff_configurator( """Запускает конфигуратор тарифа для пользователя.""" cfg = normalize_tariff_config(tariff) - raw_device_options = cfg.get("device_options") or tariff.get("device_options") or [] - raw_traffic_options = cfg.get("traffic_options_gb") or tariff.get("traffic_options_gb") or [] + raw_device_options = tariff.get("device_options") + raw_traffic_options = tariff.get("traffic_options_gb") - device_int_options: list[int] = [] - for value in raw_device_options: - try: - device_int_options.append(int(value)) - except (TypeError, ValueError): - continue - - traffic_int_options: list[int] = [] - for value in raw_traffic_options: - try: - traffic_int_options.append(int(value)) - except (TypeError, ValueError): - continue - - base_device_limit = cfg.get("base_device_limit") - if base_device_limit is None: - base_device_limit = tariff.get("device_limit") - if base_device_limit is not None: - try: - base_device_int = int(base_device_limit) - if base_device_int not in device_int_options: - raw_device_options.append(base_device_int) - device_int_options.append(base_device_int) - except (TypeError, ValueError): - pass - - device_overrides_cfg = cfg.get("device_price_overrides") or tariff.get("device_overrides") or {} - if "0" in device_overrides_cfg and 0 not in device_int_options: - raw_device_options.append(0) - device_int_options.append(0) - - base_traffic_gb = cfg.get("base_traffic_gb") - if base_traffic_gb is None: - raw_limit = tariff.get("traffic_limit") - if raw_limit: - raw_limit = int(raw_limit) - if raw_limit >= GB: - base_traffic_gb = int(raw_limit / GB) - else: - base_traffic_gb = raw_limit - if base_traffic_gb is not None: - try: - base_traffic_int = int(base_traffic_gb) - if base_traffic_int not in traffic_int_options: - raw_traffic_options.append(base_traffic_int) - traffic_int_options.append(base_traffic_int) - except (TypeError, ValueError): - pass - - traffic_overrides_cfg = cfg.get("traffic_price_overrides") or tariff.get("traffic_overrides") or {} - if "0" in traffic_overrides_cfg and 0 not in traffic_int_options: - raw_traffic_options.append(0) - traffic_int_options.append(0) + raw_device_options = raw_device_options if isinstance(raw_device_options, list) else [] + raw_traffic_options = raw_traffic_options if isinstance(raw_traffic_options, list) else [] try: - device_options = sorted( - raw_device_options, - key=lambda v: (int(v) == 0, int(v)), - ) + device_options = sorted(raw_device_options, key=lambda v: (int(v) == 0, int(v))) except (TypeError, ValueError): device_options = raw_device_options try: - traffic_options = sorted( - raw_traffic_options, - key=lambda v: (int(v) == 0, int(v)), - ) + traffic_options = sorted(raw_traffic_options, key=lambda v: (int(v) == 0, int(v))) except (TypeError, ValueError): traffic_options = raw_traffic_options @@ -626,10 +570,8 @@ async def start_user_tariff_configurator( return cfg_for_state = dict(cfg) - if device_options: - cfg_for_state["device_options"] = device_options - if traffic_options: - cfg_for_state["traffic_options_gb"] = traffic_options + cfg_for_state["device_options"] = device_options + cfg_for_state["traffic_options_gb"] = traffic_options data = await state.get_data() renew_mode = data.get("renew_mode") @@ -686,21 +628,19 @@ async def finalize_config_and_purchase(callback_query: CallbackQuery, state: FSM duration_days = int(tariff.get("duration_days") or 30) - raw_device_options = cfg.get("device_options") or tariff.get("device_options") or [] + raw_device_options = tariff.get("device_options") + raw_traffic_options = tariff.get("traffic_options_gb") + + raw_device_options = raw_device_options if isinstance(raw_device_options, list) else [] + raw_traffic_options = raw_traffic_options if isinstance(raw_traffic_options, list) else [] + try: - device_options = sorted( - raw_device_options, - key=lambda v: (int(v) == 0, int(v)), - ) + device_options = sorted(raw_device_options, key=lambda v: (int(v) == 0, int(v))) except (TypeError, ValueError): device_options = raw_device_options - raw_traffic_options = cfg.get("traffic_options_gb") or tariff.get("traffic_options_gb") or [] try: - traffic_options = sorted( - raw_traffic_options, - key=lambda v: (int(v) == 0, int(v)), - ) + traffic_options = sorted(raw_traffic_options, key=lambda v: (int(v) == 0, int(v))) except (TypeError, ValueError): traffic_options = raw_traffic_options diff --git a/middlewares/admin.py b/middlewares/admin.py index f0f3271a..b568f2d7 100644 --- a/middlewares/admin.py +++ b/middlewares/admin.py @@ -13,7 +13,7 @@ from database.models import Admin class AdminMiddleware(BaseMiddleware): """Проверяет, является ли пользователь администратором.""" - _admin_ids: set[int] = set(ADMIN_ID) if isinstance(ADMIN_ID, (list, tuple)) else {ADMIN_ID} + _admin_ids: set[int] = set(ADMIN_ID) if isinstance(ADMIN_ID, list | tuple) else {ADMIN_ID} async def __call__( self, diff --git a/middlewares/direct_start_blocker.py b/middlewares/direct_start_blocker.py index b798c829..eb58cd43 100644 --- a/middlewares/direct_start_blocker.py +++ b/middlewares/direct_start_blocker.py @@ -1,4 +1,5 @@ import time + from collections.abc import Awaitable, Callable from typing import Any diff --git a/middlewares/user.py b/middlewares/user.py index b5fb7b2f..4b9dcdf7 100644 --- a/middlewares/user.py +++ b/middlewares/user.py @@ -61,13 +61,11 @@ class UserMiddleware(BaseMiddleware): return db_user def _fingerprint(self, user: User) -> str: - return "|".join( - [ - str(user.id), - user.username or "", - user.first_name or "", - user.last_name or "", - user.language_code or "", - "1" if user.is_bot else "0", - ] - ) + return "|".join([ + str(user.id), + user.username or "", + user.first_name or "", + user.last_name or "", + user.language_code or "", + "1" if user.is_bot else "0", + ])