diff --git a/api/main.py b/api/main.py
index 82d8b3f1..82684327 100644
--- a/api/main.py
+++ b/api/main.py
@@ -3,7 +3,7 @@ from api.routes import users, keys, coupons, servers, tariffs, gifts, referrals,
app = FastAPI(
title="SoloBot API (Alpha)",
- version="0.5.0",
+ version="0.5.1",
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json",
diff --git a/api/routes/base_crud.py b/api/routes/base_crud.py
index 52535ff8..0d9a6d73 100644
--- a/api/routes/base_crud.py
+++ b/api/routes/base_crud.py
@@ -1,6 +1,6 @@
from typing import Any
-from fastapi import APIRouter, Depends, HTTPException, Path, Query
+from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm.attributes import InstrumentedAttribute
@@ -104,7 +104,7 @@ def generate_crud_router(
@router.post("/", response_model=schema_response)
async def create(
- payload: Any,
+ payload: Any = Body(...),
admin: Admin = Depends(verify_admin_token),
session: AsyncSession = Depends(get_session),
):
@@ -122,7 +122,7 @@ def generate_crud_router(
@router.patch(f"/{{{parameter_name}}}", response_model=schema_response)
async def update(
- payload: Any,
+ payload: Any = Body(...),
value: int | str = Path(..., alias=parameter_name),
admin: Admin = Depends(verify_admin_token),
session: AsyncSession = Depends(get_session),
diff --git a/api/schemas/coupons.py b/api/schemas/coupons.py
index b7bbbfbf..348e6163 100644
--- a/api/schemas/coupons.py
+++ b/api/schemas/coupons.py
@@ -11,15 +11,34 @@ class CouponBase(BaseModel):
is_used: bool = False
days: int | None = Field(default=None)
+ percent: int | None = Field(default=None)
+ max_discount_amount: int | None = Field(default=None)
+ min_order_amount: int | None = Field(default=None)
+
+ new_users_only: bool = False
+
@model_validator(mode="after")
- def check_exactly_one_of_amount_or_days(self) -> "CouponBase":
+ def check_coupon_type(self) -> "CouponBase":
has_amount = self.amount not in (None, 0)
has_days = self.days is not None
+ has_percent = self.percent is not None
+
+ provided = int(has_amount) + int(has_days) + int(has_percent)
+ if provided != 1:
+ raise ValueError("Coupon must have exactly one of: 'amount', 'days', 'percent'")
+
+ if has_days and self.days is not None and self.days <= 0:
+ raise ValueError("'days' must be > 0")
+
+ if has_percent and self.percent is not None and not (1 <= self.percent <= 100):
+ raise ValueError("'percent' must be between 1 and 100")
+
+ if has_percent:
+ if self.min_order_amount is not None and self.min_order_amount < 0:
+ raise ValueError("'min_order_amount' must be >= 0")
+ if self.max_discount_amount is not None and self.max_discount_amount < 0:
+ raise ValueError("'max_discount_amount' must be >= 0")
- if has_amount and has_days:
- raise ValueError("Coupon must have exactly one of: 'amount' or 'days'")
- if not has_amount and not has_days:
- raise ValueError("Coupon must have exactly one of: 'amount' or 'days'")
return self
@@ -38,12 +57,34 @@ class CouponUpdate(BaseModel):
is_used: bool | None = None
days: int | None = Field(default=None)
+ percent: int | None = None
+ max_discount_amount: int | None = None
+ min_order_amount: int | None = None
+
+ new_users_only: bool | None = None
+
@model_validator(mode="after")
- def validate_amount_or_days(self) -> "CouponUpdate":
- if self.amount is None and self.days is None:
- return self
- if self.amount is not None and self.days is not None:
- raise ValueError("Specify only one of: 'amount' or 'days'")
+ def validate_coupon_update(self) -> "CouponUpdate":
+ has_amount = self.amount not in (None, 0)
+ has_days = self.days is not None
+ has_percent = self.percent is not None
+
+ provided = int(has_amount) + int(has_days) + int(has_percent)
+ if provided > 1:
+ raise ValueError("Specify only one of: 'amount', 'days', 'percent'")
+
+ if has_days and self.days is not None and self.days <= 0:
+ raise ValueError("'days' must be > 0")
+
+ if has_percent and self.percent is not None and not (1 <= self.percent <= 100):
+ raise ValueError("'percent' must be between 1 and 100")
+
+ if has_percent:
+ if self.min_order_amount is not None and self.min_order_amount < 0:
+ raise ValueError("'min_order_amount' must be >= 0")
+ if self.max_discount_amount is not None and self.max_discount_amount < 0:
+ raise ValueError("'max_discount_amount' must be >= 0")
+
return self
diff --git a/docker-compose.yml b/docker-compose.yml
index 4c1e3a48..0b6610b8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,6 +5,5 @@ services:
restart: unless-stopped
network_mode: host
volumes:
- - /etc/machine-id:/etc/machine-id:ro
- - /var/lib/dbus/machine-id:/var/lib/dbus/machine-id:ro
+ - /:/host:ro
diff --git a/filters/admin.py b/filters/admin.py
index 769fe3fc..84a3e4ba 100644
--- a/filters/admin.py
+++ b/filters/admin.py
@@ -13,9 +13,7 @@ class IsAdminFilter(BaseFilter):
try:
async with async_session_maker() as session:
- result = await session.execute(
- select(exists().where(Admin.tg_id == event.from_user.id))
- )
+ result = await session.execute(select(exists().where(Admin.tg_id == event.from_user.id)))
return result.scalar()
except (Exception,):
return False
diff --git a/handlers/admin/ads/keyboard.py b/handlers/admin/ads/keyboard.py
index 2333076a..fe0aed29 100644
--- a/handlers/admin/ads/keyboard.py
+++ b/handlers/admin/ads/keyboard.py
@@ -3,6 +3,7 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from ..panel.keyboard import build_admin_back_btn
+from handlers.buttons import BACK
class AdminAdsCallback(CallbackData, prefix="admin_ads"):
@@ -43,7 +44,7 @@ def build_ads_list_kb(ads: list, current_page: int, total_pages: int) -> InlineK
if current_page > 1:
pagination_buttons.append(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminAdsCallback(action="list", code=f"{current_page - 1}").pack(),
)
)
diff --git a/handlers/admin/bans/keyboard.py b/handlers/admin/bans/keyboard.py
index a203ad04..aabac00a 100644
--- a/handlers/admin/bans/keyboard.py
+++ b/handlers/admin/bans/keyboard.py
@@ -1,6 +1,7 @@
from aiogram.utils.keyboard import InlineKeyboardBuilder
from ..panel.keyboard import AdminPanelCallback
+from handlers.buttons import BACK
def build_bans_kb():
@@ -19,7 +20,7 @@ def build_bans_kb():
callback_data=AdminPanelCallback(action="bans_manual_menu").pack(),
)
builder.button(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="management").pack(),
)
@@ -43,7 +44,7 @@ def build_blocked_users_kb():
callback_data=AdminPanelCallback(action="bans_clear_blocked").pack(),
)
builder.button(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="bans").pack(),
)
@@ -67,7 +68,7 @@ def build_shadow_bans_kb():
callback_data=AdminPanelCallback(action="bans_clear_shadow").pack(),
)
builder.button(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="bans").pack(),
)
@@ -87,7 +88,7 @@ def build_manual_bans_kb():
callback_data=AdminPanelCallback(action="bans_clear_manual").pack(),
)
builder.button(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="bans").pack(),
)
diff --git a/handlers/admin/clusters/cluster_tariffs.py b/handlers/admin/clusters/cluster_tariffs.py
index 6b0d0423..5e55938c 100644
--- a/handlers/admin/clusters/cluster_tariffs.py
+++ b/handlers/admin/clusters/cluster_tariffs.py
@@ -186,9 +186,7 @@ async def choose_tariffs(
await callback.message.edit_text("❌ Для этой группы нет доступных тарифов.")
return
- servers_q = await session.execute(
- select(Server.id).where(Server.server_name.in_(selected_servers))
- )
+ servers_q = await session.execute(select(Server.id).where(Server.server_name.in_(selected_servers)))
server_ids = [row[0] for row in servers_q.fetchall()]
current_bindings_q = await session.execute(
@@ -244,7 +242,7 @@ async def toggle_tariff_selection(
)
-@router.callback_query(AdminClusterCallback.filter(F.action == "apply_tariffs"),IsAdminFilter())
+@router.callback_query(AdminClusterCallback.filter(F.action == "apply_tariffs"), IsAdminFilter())
async def apply_tariffs(
callback: CallbackQuery, callback_data: AdminClusterCallback, session: AsyncSession, state: FSMContext
):
diff --git a/handlers/admin/clusters/keyboard.py b/handlers/admin/clusters/keyboard.py
index 734af93d..5d82e023 100644
--- a/handlers/admin/clusters/keyboard.py
+++ b/handlers/admin/clusters/keyboard.py
@@ -4,6 +4,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn
from ..servers.keyboard import AdminServerCallback
+from handlers.buttons import BACK
class AdminClusterCallback(CallbackData, prefix="admin_cluster"):
@@ -65,7 +66,7 @@ def build_manage_cluster_kb(cluster_servers: list, cluster_name: str) -> InlineK
)
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack(),
)
)
@@ -97,7 +98,7 @@ def build_attach_tariff_kb(cluster_name: str) -> InlineKeyboardMarkup:
)
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack(),
)
)
@@ -114,7 +115,7 @@ def build_legacy_reset_kb(cluster_name: str) -> InlineKeyboardMarkup:
)
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminClusterCallback(action="attach_tariff_menu", data=cluster_name).pack(),
)
)
@@ -157,7 +158,7 @@ def build_select_subgroup_servers_kb(
)
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminClusterCallback(action="attach_tariff_menu", data=cluster_name).pack(),
)
)
@@ -273,7 +274,7 @@ def build_cluster_management_kb(cluster_name: str) -> InlineKeyboardMarkup:
callback_data=AdminClusterCallback(action="set_tariff", data=cluster_name).pack(),
)
)
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data=AdminPanelCallback(action="clusters").pack()))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data=AdminPanelCallback(action="clusters").pack()))
return builder.as_markup()
@@ -321,7 +322,7 @@ def build_tariff_group_selection_kb(cluster_name: str, groups: list[tuple[int, s
)
builder.row(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack(),
)
)
@@ -363,7 +364,7 @@ def build_select_group_servers_kb(cluster_name: str, cluster_servers: list, sele
)
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack(),
)
)
@@ -383,7 +384,7 @@ def build_tariff_group_selection_for_servers_kb(
)
builder.row(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminClusterCallback(action="set_group", data=cluster_name).pack(),
)
)
@@ -402,7 +403,7 @@ def build_availability_kb(cluster_name: str) -> InlineKeyboardMarkup:
],
[
InlineKeyboardButton(
- text="⬅️ Назад", callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack()
+ text=BACK, callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack()
)
],
]
diff --git a/handlers/admin/coupons/coupons_handler.py b/handlers/admin/coupons/coupons_handler.py
index e04812db..9d60f9ff 100644
--- a/handlers/admin/coupons/coupons_handler.py
+++ b/handlers/admin/coupons/coupons_handler.py
@@ -64,9 +64,7 @@ async def handle_coupons_create(callback_query: CallbackQuery, state: FSMContext
await state.set_state(AdminCouponsState.waiting_for_coupon_type)
-async def show_coupon_audience_step(callback_query: CallbackQuery, state: FSMContext, coupon_type: str):
- await state.update_data(coupon_type=coupon_type)
-
+async def show_coupon_audience_step(callback_query: CallbackQuery, state: FSMContext):
text = "🎯 Кому доступен купон?"
kb = InlineKeyboardBuilder()
kb.button(text="👤 Всем", callback_data="coupon_audience_all")
@@ -80,52 +78,34 @@ async def show_coupon_audience_step(callback_query: CallbackQuery, state: FSMCon
@router.callback_query(F.data == "coupon_type_balance", IsAdminFilter())
async def handle_balance_coupon_selection(callback_query: CallbackQuery, state: FSMContext):
- await show_coupon_audience_step(callback_query, state, "balance")
+ await state.update_data(coupon_type="balance")
+ await show_coupon_audience_step(callback_query, state)
@router.callback_query(F.data == "coupon_type_days", IsAdminFilter())
async def handle_days_coupon_selection(callback_query: CallbackQuery, state: FSMContext):
- await show_coupon_audience_step(callback_query, state, "days")
-
-
-@router.callback_query(F.data == "coupon_type_percent", IsAdminFilter())
-async def handle_percent_coupon_selection(callback_query: CallbackQuery, state: FSMContext):
- await show_coupon_audience_step(callback_query, state, "percent")
-
-
-@router.callback_query(F.data.in_(("coupon_audience_all", "coupon_audience_new")), IsAdminFilter())
-async def handle_coupon_audience(callback_query: CallbackQuery, state: FSMContext):
- data = await state.get_data()
- coupon_type = data.get("coupon_type")
- if coupon_type not in ("balance", "days", "percent"):
- await callback_query.answer("Ошибка: тип купона не найден", show_alert=True)
- return
-
- await state.update_data(new_users_only=callback_query.data == "coupon_audience_new")
+ await state.update_data(coupon_type="days", new_users_only=False)
kb = InlineKeyboardBuilder()
kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack())
kb.adjust(1)
- if coupon_type == "balance":
- text = (
- "🎫 Введите данные для создания купона в формате:\n\n"
- "📝 код 💰 сумма 🔢 лимит\n\n"
- "Пример: 'COUPON1 50 5'\n\n"
- )
- await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup())
- await state.set_state(AdminCouponsState.waiting_for_balance_data)
- return
+ text = (
+ "🎫 Введите данные для создания купона в формате:\n\n"
+ "📝 код ⏳ дни 🔢 лимит\n\n"
+ "Пример: 'DAYS10 10 50'\n\n"
+ )
+ await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup())
+ await state.set_state(AdminCouponsState.waiting_for_days_data)
- if coupon_type == "days":
- text = (
- "🎫 Введите данные для создания купона в формате:\n\n"
- "📝 код ⏳ дни 🔢 лимит\n\n"
- "Пример: 'DAYS10 10 50'\n\n"
- )
- await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup())
- await state.set_state(AdminCouponsState.waiting_for_days_data)
- return
+
+@router.callback_query(F.data == "coupon_type_percent", IsAdminFilter())
+async def handle_percent_coupon_selection(callback_query: CallbackQuery, state: FSMContext):
+ await state.update_data(coupon_type="percent", new_users_only=False)
+
+ kb = InlineKeyboardBuilder()
+ kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack())
+ kb.adjust(1)
text = (
"🎫 Введите данные для создания купона в формате:\n\n"
@@ -137,6 +117,29 @@ async def handle_coupon_audience(callback_query: CallbackQuery, state: FSMContex
await state.set_state(AdminCouponsState.waiting_for_percent_data)
+@router.callback_query(F.data.in_(("coupon_audience_all", "coupon_audience_new")), IsAdminFilter())
+async def handle_coupon_audience(callback_query: CallbackQuery, state: FSMContext):
+ data = await state.get_data()
+ coupon_type = data.get("coupon_type")
+ if coupon_type != "balance":
+ await callback_query.answer("Ошибка: режим доступен только для купонов на баланс", show_alert=True)
+ return
+
+ await state.update_data(new_users_only=callback_query.data == "coupon_audience_new")
+
+ kb = InlineKeyboardBuilder()
+ kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack())
+ kb.adjust(1)
+
+ text = (
+ "🎫 Введите данные для создания купона в формате:\n\n"
+ "📝 код 💰 сумма 🔢 лимит\n\n"
+ "Пример: 'COUPON1 50 5'\n\n"
+ )
+ await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup())
+ await state.set_state(AdminCouponsState.waiting_for_balance_data)
+
+
@router.message(AdminCouponsState.waiting_for_balance_data, IsAdminFilter())
async def handle_balance_coupon_input(message: Message, state: FSMContext, session: Any):
text = message.text.strip()
@@ -239,16 +242,13 @@ async def handle_days_coupon_input(message: Message, state: FSMContext, session:
return
try:
- data = await state.get_data()
- new_users_only = bool(data.get("new_users_only"))
-
ok = await create_coupon(
session,
coupon_code,
0,
usage_limit,
days=days,
- new_users_only=new_users_only,
+ new_users_only=False,
percent=None,
)
if not ok:
@@ -256,13 +256,11 @@ async def handle_days_coupon_input(message: Message, state: FSMContext, session:
return
coupon_link = f"https://t.me/{USERNAME_BOT}?start=coupons_{coupon_code}"
- audience_txt = "🆕 Только новым" if new_users_only else "👤 Всем"
text = (
f"✅ Купон {coupon_code} создан!\n"
f"⏳ {format_days(days)}\n"
f"🔢 Лимит: {usage_limit} раз\n"
- f"🎯 Доступ: {audience_txt}\n"
f"🔗 Ссылка: {coupon_link}\n"
)
@@ -311,29 +309,21 @@ async def handle_percent_coupon_input(message: Message, state: FSMContext, sessi
return
try:
- data = await state.get_data()
- new_users_only = bool(data.get("new_users_only"))
-
ok = await create_coupon(
session,
coupon_code,
0,
usage_limit,
days=None,
- new_users_only=new_users_only,
+ new_users_only=False,
percent=percent,
)
if not ok:
await message.answer("❌ Купон с таким кодом уже существует.", reply_markup=kb.as_markup())
return
- audience_txt = "🆕 Только новым" if new_users_only else "👤 Всем"
-
text = (
- f"✅ Купон {coupon_code} создан!\n"
- f"📉 Скидка: {percent}%\n"
- f"🔢 Лимит: {usage_limit} раз\n"
- f"🎯 Доступ: {audience_txt}\n"
+ f"✅ Купон {coupon_code} создан!\n📉 Скидка: {percent}%\n🔢 Лимит: {usage_limit} раз\n"
)
kb = InlineKeyboardBuilder()
@@ -431,7 +421,7 @@ async def update_coupons_list(message, session: Any, page: int = 1):
await message.edit_text(text=text, reply_markup=kb)
-@router.inline_query(F.query.startswith("coupon_"),)
+@router.inline_query(F.query.startswith("coupon_"))
async def inline_coupon_handler(inline_query: InlineQuery, session: Any):
if not INLINE_MODE:
return
diff --git a/handlers/admin/gifts/gifts_handler.py b/handlers/admin/gifts/gifts_handler.py
index 66e66f81..62a8154a 100644
--- a/handlers/admin/gifts/gifts_handler.py
+++ b/handlers/admin/gifts/gifts_handler.py
@@ -16,6 +16,7 @@ from logger import logger
from ..panel.keyboard import AdminPanelCallback
from .keyboard import build_admin_gifts_kb, build_gifts_list_kb
+from handlers.buttons import BACK
router = Router()
@@ -41,7 +42,7 @@ async def admin_create_gift_step1(callback: CallbackQuery, session: AsyncSession
if not tariffs:
builder = InlineKeyboardBuilder()
- builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action="gifts").pack())
await callback.message.edit_text("❌ Нет активных тарифов в группе 'gifts'.", reply_markup=builder.as_markup())
return
@@ -74,7 +75,7 @@ async def admin_create_gift_step1(callback: CallbackQuery, session: AsyncSession
)
)
- builder.row(types.InlineKeyboardButton(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack()))
+ builder.row(types.InlineKeyboardButton(text=BACK, callback_data=AdminPanelCallback(action="gifts").pack()))
await callback.message.edit_text("🎁 Выберите тариф для подарка:", reply_markup=builder.as_markup())
@@ -109,7 +110,7 @@ async def admin_gift_show_tariffs_in_subgroup(callback: CallbackQuery, session:
)
)
- builder.row(types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_gift_create"))
+ builder.row(types.InlineKeyboardButton(text=BACK, callback_data="admin_gift_create"))
await edit_or_send_message(
target_message=callback.message,
@@ -129,7 +130,7 @@ async def handle_tariff_selection(callback: CallbackQuery, state: FSMContext):
await state.set_state(GiftCreationState.waiting_for_limit_input_or_unlimited)
kb = InlineKeyboardBuilder()
- kb.button(text="🔙 Назад", callback_data="admin_gift_create")
+ kb.button(text=BACK, callback_data="admin_gift_create")
await callback.message.edit_text(
"🔢 Введите максимальное количество активаций подарка:", reply_markup=kb.as_markup()
)
@@ -184,7 +185,7 @@ async def show_gift_list(callback: CallbackQuery, session: AsyncSession, page: i
if not gifts:
builder = InlineKeyboardBuilder()
- builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action="gifts").pack())
await callback.message.edit_text("❌ Подарки не найдены.", reply_markup=builder.as_markup())
return
@@ -229,7 +230,7 @@ async def view_gift(callback: CallbackQuery, session: AsyncSession):
builder = InlineKeyboardBuilder()
builder.button(text="🗑 Удалить", callback_data=f"gift_delete|{gift_id}")
- builder.button(text="🔙 Назад", callback_data="admin_gifts_all")
+ builder.button(text=BACK, callback_data="admin_gifts_all")
await callback.message.edit_text(text, reply_markup=builder.as_markup())
diff --git a/handlers/admin/gifts/keyboard.py b/handlers/admin/gifts/keyboard.py
index f38eb52c..e4007f18 100644
--- a/handlers/admin/gifts/keyboard.py
+++ b/handlers/admin/gifts/keyboard.py
@@ -27,7 +27,7 @@ def build_gift_tariffs_kb(tariffs: list[Tariff]) -> InlineKeyboardMarkup:
text=f"{tariff.name} — {tariff.duration_days // 30} мес.",
callback_data=f"admin_gift_confirm|{tariff.id}",
)
- builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action="gifts").pack())
builder.adjust(1)
return builder.as_markup()
@@ -61,6 +61,6 @@ def build_gifts_list_kb(gifts: list[Gift], page: int, total: int) -> InlineKeybo
if nav:
builder.row(*nav)
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack()))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data=AdminPanelCallback(action="gifts").pack()))
return builder.as_markup()
diff --git a/handlers/admin/management/keyboard.py b/handlers/admin/management/keyboard.py
index 0c2f4578..82dae946 100644
--- a/handlers/admin/management/keyboard.py
+++ b/handlers/admin/management/keyboard.py
@@ -2,6 +2,7 @@ from aiogram.types import InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from core.bootstrap import MANAGEMENT_CONFIG
+from handlers.buttons import BACK
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn
@@ -74,7 +75,7 @@ def build_database_kb() -> InlineKeyboardMarkup:
def build_back_to_db_menu() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
- builder.button(text="⬅️ Назад", callback_data=AdminPanelCallback(action="back_to_db_menu").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action="back_to_db_menu").pack())
builder.adjust(1)
return builder.as_markup()
@@ -84,7 +85,7 @@ def build_export_db_sources_kb() -> InlineKeyboardMarkup:
builder.button(text="🌀 Remnawave", callback_data=AdminPanelCallback(action="export_remnawave").pack())
builder.button(text="🧩 3x-ui", callback_data=AdminPanelCallback(action="request_3xui_file").pack())
- builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="back_to_db_menu").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action="back_to_db_menu").pack())
builder.adjust(1)
return builder.as_markup()
@@ -115,7 +116,7 @@ def build_single_admin_menu(tg_id: int, role: str = "moderator") -> InlineKeyboa
text="🎟 Выпустить токен", callback_data=AdminPanelCallback(action=f"generate_token|{tg_id}").pack()
)
- builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="admins").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action="admins").pack())
builder.adjust(1)
return builder.as_markup()
@@ -125,14 +126,14 @@ def build_role_selection_kb(tg_id: int) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.button(text="👑 superadmin", callback_data=AdminPanelCallback(action=f"set_role|{tg_id}|superadmin").pack())
builder.button(text="🛡 moderator", callback_data=AdminPanelCallback(action=f"set_role|{tg_id}|moderator").pack())
- builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action=f"admin_menu|{tg_id}").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action=f"admin_menu|{tg_id}").pack())
builder.adjust(1)
return builder.as_markup()
def build_admin_back_kb_to_admins() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
- builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="admins").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action="admins").pack())
builder.adjust(1)
return builder.as_markup()
@@ -140,7 +141,7 @@ def build_admin_back_kb_to_admins() -> InlineKeyboardMarkup:
def build_token_result_kb(token: str) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.button(text="📋 Скопировать токен", switch_inline_query_current_chat=token)
- builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="admins").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action="admins").pack())
builder.adjust(1)
return builder.as_markup()
@@ -150,6 +151,6 @@ def build_post_import_kb() -> InlineKeyboardMarkup:
builder.button(
text="🔁 Перевыпустить подписки", callback_data=AdminPanelCallback(action="resync_after_import").pack()
)
- builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="back_to_db_menu").pack())
+ builder.button(text=BACK, callback_data=AdminPanelCallback(action="back_to_db_menu").pack())
builder.adjust(1)
return builder.as_markup()
diff --git a/handlers/admin/module/keyboard.py b/handlers/admin/module/keyboard.py
index cbe10b4d..006835c9 100644
--- a/handlers/admin/module/keyboard.py
+++ b/handlers/admin/module/keyboard.py
@@ -29,7 +29,7 @@ def build_modules_kb(page: int, total_pages: int, items: list[tuple[str, str | N
if page > 1:
nav.append(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="modules", page=page - 1).pack(),
)
)
diff --git a/handlers/admin/servers/keyboard.py b/handlers/admin/servers/keyboard.py
index 18249e93..a908eb86 100644
--- a/handlers/admin/servers/keyboard.py
+++ b/handlers/admin/servers/keyboard.py
@@ -37,7 +37,7 @@ def build_manage_server_kb(server_name: str, cluster_name: str, enabled: bool) -
)
builder.button(
- text="🔙 Назад",
+ text=BACK,
callback_data=f"cluster_servers|{cluster_name}",
)
@@ -70,9 +70,7 @@ def build_edit_server_fields_kb(server_name: str, server_data: dict) -> InlineKe
builder.row(InlineKeyboardButton(text="⚙️ Тип панели", callback_data=f"select_panel_type|{server_name}"))
builder.row(
- InlineKeyboardButton(
- text="⬅️ Назад", callback_data=AdminServerCallback(action="manage", data=server_name).pack()
- )
+ InlineKeyboardButton(text=BACK, callback_data=AdminServerCallback(action="manage", data=server_name).pack())
)
return builder.as_markup()
@@ -83,7 +81,7 @@ def build_panel_type_selection_kb(server_name: str) -> InlineKeyboardMarkup:
inline_keyboard=[
[InlineKeyboardButton(text="🌐 3x-ui", callback_data=f"set_panel_type|{server_name}|3x-ui")],
[InlineKeyboardButton(text="🌀 remnawave", callback_data=f"set_panel_type|{server_name}|remnawave")],
- [InlineKeyboardButton(text="⬅️ Назад", callback_data=f"edit_server|{server_name}")],
+ [InlineKeyboardButton(text=BACK, callback_data=f"edit_server|{server_name}")],
]
)
@@ -94,7 +92,7 @@ def build_cluster_selection_kb(server_name: str, clusters: list[str]) -> InlineK
for cluster in clusters:
builder.row(InlineKeyboardButton(text=cluster, callback_data=f"set_cluster|{server_name}|{cluster}"))
- builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"edit_server|{server_name}"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data=f"edit_server|{server_name}"))
return builder.as_markup()
diff --git a/handlers/admin/settings/keyboard.py b/handlers/admin/settings/keyboard.py
index 8c0c0bd6..36a80ac5 100644
--- a/handlers/admin/settings/keyboard.py
+++ b/handlers/admin/settings/keyboard.py
@@ -2,6 +2,7 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from core.settings.money_config import get_currency_mode
+from handlers.buttons import BACK
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn
from .settings_config import (
@@ -44,7 +45,7 @@ def build_toggle_section_keyboard(
builder.row(
InlineKeyboardButton(
- text="Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action=back_action).pack(),
)
)
@@ -117,12 +118,13 @@ def build_providers_order_kb(sorted_names: list[str]) -> InlineKeyboardMarkup:
for idx, name in enumerate(sorted_names):
title = PAYMENT_PROVIDER_TITLES.get(name, name)
- pos = idx + 1
+ pos = idx + 1
builder.row(
InlineKeyboardButton(
text="⬆️",
callback_data=AdminPanelCallback(
- action="settings_order_up", page=pos,
+ action="settings_order_up",
+ page=pos,
).pack(),
),
InlineKeyboardButton(
@@ -134,7 +136,8 @@ def build_providers_order_kb(sorted_names: list[str]) -> InlineKeyboardMarkup:
InlineKeyboardButton(
text="⬇️",
callback_data=AdminPanelCallback(
- action="settings_order_down", page=pos,
+ action="settings_order_down",
+ page=pos,
).pack(),
),
)
@@ -147,7 +150,7 @@ def build_providers_order_kb(sorted_names: list[str]) -> InlineKeyboardMarkup:
)
builder.row(
InlineKeyboardButton(
- text="Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="settings_cashboxes").pack(),
)
)
@@ -192,7 +195,7 @@ def build_settings_notifications_intervals_kb(notifications_state: dict[str, obj
builder.row(
InlineKeyboardButton(
- text="Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="settings_notifications").pack(),
)
)
@@ -261,7 +264,7 @@ def build_settings_money_kb(money_state: dict[str, object]) -> InlineKeyboardMar
builder.row(
InlineKeyboardButton(
- text="Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="settings").pack(),
)
)
diff --git a/handlers/admin/settings/settings_cashboxes.py b/handlers/admin/settings/settings_cashboxes.py
index 4e476a82..437d9f34 100644
--- a/handlers/admin/settings/settings_cashboxes.py
+++ b/handlers/admin/settings/settings_cashboxes.py
@@ -68,6 +68,7 @@ async def toggle_cashbox_setting(
)
await callback.answer("Настройка обновлена")
+
@router.callback_query(AdminPanelCallback.filter(F.action == "settings_providers_order"))
async def open_providers_order_menu(callback: CallbackQuery, session: AsyncSession) -> None:
sorted_names = _get_sorted_provider_names()
diff --git a/handlers/admin/settings/settings_money.py b/handlers/admin/settings/settings_money.py
index 8c3adc7e..a1b0f7e2 100644
--- a/handlers/admin/settings/settings_money.py
+++ b/handlers/admin/settings/settings_money.py
@@ -6,6 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from core.bootstrap import MONEY_CONFIG, update_money_config
from filters.admin import IsAdminFilter
+from handlers.buttons import BACK
from ..panel.keyboard import AdminPanelCallback
from .keyboard import MONEY_FIELDS, build_settings_money_kb
@@ -182,7 +183,7 @@ async def open_currency_mode_menu(callback: CallbackQuery) -> None:
],
[
InlineKeyboardButton(
- text="Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="settings_money").pack(),
)
],
diff --git a/handlers/admin/settings/settings_tariffs.py b/handlers/admin/settings/settings_tariffs.py
index 5705fe4a..227a115e 100644
--- a/handlers/admin/settings/settings_tariffs.py
+++ b/handlers/admin/settings/settings_tariffs.py
@@ -4,6 +4,7 @@ from aiogram import F, Router
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from sqlalchemy.ext.asyncio import AsyncSession
+from handlers.buttons import BACK
from core.settings.tariffs_config import TARIFFS_CONFIG, update_tariffs_config
from filters.admin import IsAdminFilter
@@ -61,7 +62,7 @@ def build_tariffs_settings_kb() -> InlineKeyboardMarkup:
builder.row(
InlineKeyboardButton(
- text="Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="settings").pack(),
)
)
@@ -137,7 +138,7 @@ def build_tariffs_packs_kb() -> InlineKeyboardMarkup:
builder.row(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="settings_tariffs").pack(),
)
)
diff --git a/handlers/admin/tariffs/keyboard.py b/handlers/admin/tariffs/keyboard.py
index 2420754d..dd4f28b9 100644
--- a/handlers/admin/tariffs/keyboard.py
+++ b/handlers/admin/tariffs/keyboard.py
@@ -3,6 +3,7 @@ from collections import defaultdict
from aiogram.filters.callback_data import CallbackData
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
+from handlers.buttons import BACK
from database.tariffs import create_subgroup_hash
@@ -33,7 +34,7 @@ def build_tariff_menu_kb() -> InlineKeyboardMarkup:
callback_data=AdminTariffCallback(action="arrange").pack(),
)
)
- builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=AdminPanelCallback(action="admin").pack()))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data=AdminPanelCallback(action="admin").pack()))
return builder.as_markup()
@@ -60,7 +61,7 @@ def build_tariff_arrangement_groups_kb(groups: list[str]) -> InlineKeyboardMarku
builder.row(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminTariffCallback(action="list").pack(),
)
)
@@ -116,7 +117,7 @@ def build_tariffs_arrangement_kb(group_code: str, tariffs: list) -> InlineKeyboa
builder.row(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="tariffs").pack(),
)
)
@@ -146,7 +147,7 @@ def build_tariff_groups_kb(groups: list[str]) -> InlineKeyboardMarkup:
row = []
builder.row(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminPanelCallback(action="tariffs").pack(),
)
)
@@ -196,7 +197,7 @@ def build_tariff_list_kb(tariffs: list[dict]) -> InlineKeyboardMarkup:
builder.row(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminTariffCallback(action="list").pack(),
)
)
@@ -251,7 +252,7 @@ def build_single_tariff_kb(
],
[
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminTariffCallback(action=f"group|{group_code}").pack()
if group_code
else AdminTariffCallback(action="list").pack(),
@@ -297,10 +298,6 @@ def build_edit_tariff_fields_kb(tariff_id: int) -> InlineKeyboardMarkup:
)
],
[InlineKeyboardButton(text="🔘 Активность", callback_data=f"toggle_active|{tariff_id}")],
- [
- InlineKeyboardButton(
- text="⬅️ Назад", callback_data=AdminTariffCallback(action=f"view|{tariff_id}").pack()
- )
- ],
+ [InlineKeyboardButton(text=BACK, callback_data=AdminTariffCallback(action=f"view|{tariff_id}").pack())],
]
)
diff --git a/handlers/admin/tariffs/tariff_manage.py b/handlers/admin/tariffs/tariff_manage.py
index 75e09cf4..5aa3d532 100644
--- a/handlers/admin/tariffs/tariff_manage.py
+++ b/handlers/admin/tariffs/tariff_manage.py
@@ -18,6 +18,7 @@ from database.models import Gift, Key, Server, Tariff
from filters.admin import IsAdminFilter
from ..panel.keyboard import AdminPanelCallback
+from handlers.buttons import BACK
from . import router
from .keyboard import (
AdminTariffCallback,
@@ -331,7 +332,7 @@ async def confirm_tariff_deletion(callback: CallbackQuery, callback_data: AdminT
inline_keyboard=[
[
InlineKeyboardButton(
- text="⬅️ Назад", callback_data=AdminTariffCallback(action=f"view|{tariff_id}").pack()
+ text=BACK, callback_data=AdminTariffCallback(action=f"view|{tariff_id}").pack()
)
]
]
@@ -465,7 +466,7 @@ async def ask_new_value(callback: CallbackQuery, state: FSMContext):
],
[
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminTariffCallback(action=f"view|{tariff_id}").pack(),
)
],
diff --git a/handlers/admin/tariffs/tariff_subgroups.py b/handlers/admin/tariffs/tariff_subgroups.py
index bfa784ac..1c2e5396 100644
--- a/handlers/admin/tariffs/tariff_subgroups.py
+++ b/handlers/admin/tariffs/tariff_subgroups.py
@@ -9,6 +9,7 @@ from aiogram.types import (
Message,
)
from aiogram.utils.keyboard import InlineKeyboardBuilder
+from handlers.buttons import BACK
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
@@ -40,7 +41,7 @@ async def start_subgrouping(callback: CallbackQuery, state: FSMContext, session:
inline_keyboard=[
[
InlineKeyboardButton(
- text="⬅️ Назад", callback_data=AdminTariffCallback(action=f"group|{group_code}").pack()
+ text=BACK, callback_data=AdminTariffCallback(action=f"group|{group_code}").pack()
)
]
]
@@ -211,7 +212,7 @@ async def view_subgroup_tariffs(callback: CallbackQuery, session: AsyncSession):
builder.row(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminTariffCallback(action=f"group|{group_code}").pack(),
)
)
@@ -296,7 +297,7 @@ async def save_new_subgroup_title(message: Message, state: FSMContext, session:
inline_keyboard=[
[
InlineKeyboardButton(
- text="⬅️ Назад", callback_data=AdminTariffCallback(action=f"group|{group_code}").pack()
+ text=BACK, callback_data=AdminTariffCallback(action=f"group|{group_code}").pack()
)
]
]
@@ -355,7 +356,7 @@ async def perform_subgroup_deletion(callback: CallbackQuery, state: FSMContext,
inline_keyboard=[
[
InlineKeyboardButton(
- text="⬅️ Назад", callback_data=AdminTariffCallback(action=f"group|{group_code}").pack()
+ text=BACK, callback_data=AdminTariffCallback(action=f"group|{group_code}").pack()
)
]
]
@@ -387,7 +388,7 @@ async def start_edit_subgroup_tariffs(callback: CallbackQuery, state: FSMContext
"❌ Нет доступных тарифов для редактирования.",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
- [InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_subgroup|{subgroup_hash}|{group_code}")]
+ [InlineKeyboardButton(text=BACK, callback_data=f"view_subgroup|{subgroup_hash}|{group_code}")]
]
),
)
diff --git a/handlers/admin/users/keyboard.py b/handlers/admin/users/keyboard.py
index bfad1b01..b1456c13 100644
--- a/handlers/admin/users/keyboard.py
+++ b/handlers/admin/users/keyboard.py
@@ -5,6 +5,7 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
+from handlers.buttons import BACK
from config import HWID_RESET_BUTTON
from database import get_clusters
@@ -349,7 +350,7 @@ def build_hwid_menu_kb(email: str, tg_id: int) -> InlineKeyboardMarkup:
callback_data=AdminUserEditorCallback(action="users_hwid_reset", data=email, tg_id=tg_id).pack(),
)
builder.button(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminUserEditorCallback(action="users_key_edit", data=email, tg_id=tg_id).pack(),
)
builder.adjust(1)
@@ -431,7 +432,7 @@ def build_user_ban_type_kb(tg_id: int) -> InlineKeyboardMarkup:
builder.row(
InlineKeyboardButton(
- text="⬅️ Назад",
+ text=BACK,
callback_data=AdminUserEditorCallback(action="users_editor", tg_id=tg_id, edit=True).pack(),
)
)
diff --git a/handlers/admin/users/users_bans.py b/handlers/admin/users/users_bans.py
index bcd6edf4..ca1a224c 100644
--- a/handlers/admin/users/users_bans.py
+++ b/handlers/admin/users/users_bans.py
@@ -7,6 +7,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from sqlalchemy import delete
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.ext.asyncio import AsyncSession
+from handlers.buttons import BACK
from database.models import ManualBan
from filters.admin import IsAdminFilter
@@ -41,7 +42,7 @@ async def handle_ban_forever_start(callback: CallbackQuery, callback_data: Admin
await state.update_data(tg_id=callback_data.tg_id)
kb = InlineKeyboardBuilder()
- kb.row(build_editor_btn("⬅️ Назад", tg_id=callback_data.tg_id, edit=True))
+ kb.row(build_editor_btn(BACK, tg_id=callback_data.tg_id, edit=True))
await callback.message.edit_text(
text="✏️ Введите причину постоянной блокировки (или -, чтобы пропустить):",
@@ -97,7 +98,7 @@ async def handle_ban_temporary(callback: CallbackQuery, callback_data: AdminUser
await state.update_data(tg_id=callback_data.tg_id)
kb = InlineKeyboardBuilder()
- kb.row(build_editor_btn("⬅️ Назад", tg_id=callback_data.tg_id, edit=True))
+ kb.row(build_editor_btn(BACK, tg_id=callback_data.tg_id, edit=True))
await callback.message.edit_text(
text="✏️ Введите причину временной блокировки (или -, чтобы пропустить):",
@@ -114,7 +115,7 @@ async def handle_ban_reason_input(message: Message, state: FSMContext):
tg_id = user_data.get("tg_id")
kb = InlineKeyboardBuilder()
- kb.row(build_editor_btn("⬅️ Назад", tg_id=tg_id, edit=True))
+ kb.row(build_editor_btn(BACK, tg_id=tg_id, edit=True))
await message.answer(
"⏳ Введите срок блокировки в днях (0 — навсегда):",
diff --git a/handlers/admin/users/users_keys.py b/handlers/admin/users/users_keys.py
index 4a5b9ce0..2539ab52 100644
--- a/handlers/admin/users/users_keys.py
+++ b/handlers/admin/users/users_keys.py
@@ -3,7 +3,7 @@ import time
import uuid
from datetime import datetime, timedelta, timezone
-from core.settings.tariffs_config import normalize_tariff_config
+from handlers.buttons import BACK
import pytz
@@ -419,7 +419,7 @@ async def confirm_admin_key_reissue(callback_query: CallbackQuery, session: Asyn
)
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminUserEditorCallback(
action="users_key_edit",
tg_id=tg_id,
@@ -450,7 +450,7 @@ async def confirm_admin_key_reissue(callback_query: CallbackQuery, session: Asyn
)
builder.row(
InlineKeyboardButton(
- text="Назад",
+ text=BACK,
callback_data=f"users_key_edit|{email}",
)
)
@@ -503,7 +503,7 @@ async def admin_reissue_country(callback_query: CallbackQuery, session: AsyncSes
)
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminUserEditorCallback(
action="users_key_edit",
tg_id=tg_id,
@@ -588,7 +588,7 @@ async def handle_recreate_key_start(
)
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminUserEditorCallback(action="users_key_edit", tg_id=tg_id, data=email).pack(),
)
)
@@ -735,7 +735,7 @@ async def handle_recreate_key_confirm(
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminUserEditorCallback(
action="users_key_edit",
tg_id=tg_id,
@@ -1478,7 +1478,7 @@ async def render_config_menu(callback_query: CallbackQuery, state: FSMContext, s
builder.row(InlineKeyboardButton(text="💾 Сохранить", callback_data="cfg_save"))
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminUserEditorCallback(action="users_key_edit", data=email, tg_id=tg_id).pack(),
)
)
@@ -1499,7 +1499,7 @@ async def handle_cfg_edit_base(callback_query: CallbackQuery, state: FSMContext,
builder.row(InlineKeyboardButton(text="📱 Устройства", callback_data="cfg_base_devices"))
if traffic_options:
builder.row(InlineKeyboardButton(text="📊 Трафик", callback_data="cfg_base_traffic"))
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="cfg_back_menu"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data="cfg_back_menu"))
await callback_query.message.edit_text(
"📦 Редактирование базы тарифа\n\nВыберите параметр:",
@@ -1519,7 +1519,7 @@ async def handle_cfg_edit_addon(callback_query: CallbackQuery, state: FSMContext
builder.row(InlineKeyboardButton(text="📱 Устройства", callback_data="cfg_addon_devices"))
if traffic_options:
builder.row(InlineKeyboardButton(text="📊 Трафик", callback_data="cfg_addon_traffic"))
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="cfg_back_menu"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data="cfg_back_menu"))
await callback_query.message.edit_text(
"➕ Редактирование докупки\n\nВыберите параметр:",
@@ -1544,7 +1544,7 @@ async def handle_cfg_base_devices(callback_query: CallbackQuery, state: FSMConte
mark = " ✅" if int(opt) == int(base_devices) else ""
builder.button(text=f"{opt} устр.{mark}", callback_data=f"cfg_set_base_dev:{opt}")
builder.adjust(3)
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="cfg_back_menu"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data="cfg_back_menu"))
await state.set_state(UserEditorState.config_select_base)
await state.update_data(cfg_param="devices")
@@ -1568,7 +1568,7 @@ async def handle_cfg_base_traffic(callback_query: CallbackQuery, state: FSMConte
label = "безлимит" if opt == 0 else f"{opt} ГБ"
builder.button(text=f"{label}{mark}", callback_data=f"cfg_set_base_traf:{opt}")
builder.adjust(2)
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="cfg_back_menu"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data="cfg_back_menu"))
await state.set_state(UserEditorState.config_select_base)
await state.update_data(cfg_param="traffic")
@@ -1698,7 +1698,7 @@ async def handle_cfg_input_addon(message: Message, state: FSMContext, session: A
builder.row(InlineKeyboardButton(text="💾 Сохранить", callback_data="cfg_save"))
builder.row(
InlineKeyboardButton(
- text="🔙 Назад",
+ text=BACK,
callback_data=AdminUserEditorCallback(action="users_key_edit", data=email, tg_id=tg_id).pack(),
)
)
diff --git a/handlers/admin/users/users_tariffs.py b/handlers/admin/users/users_tariffs.py
index c53f31e3..3dc24f13 100644
--- a/handlers/admin/users/users_tariffs.py
+++ b/handlers/admin/users/users_tariffs.py
@@ -7,6 +7,7 @@ from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
+from handlers.buttons import BACK
from core.settings.tariffs_config import normalize_tariff_config
from database import get_tariff_by_id
@@ -70,7 +71,7 @@ async def handle_user_choose_tariff_group(
builder = InlineKeyboardBuilder()
for group_code in groups:
builder.button(text=group_code, callback_data=f"group:{group_code}")
- builder.button(text="🔙 Назад", callback_data="back:renew")
+ builder.button(text=BACK, callback_data="back:renew")
builder.adjust(1)
await callback_query.message.edit_text(
@@ -101,7 +102,7 @@ async def handle_user_choose_tariff(
builder = InlineKeyboardBuilder()
for tariff in tariffs:
builder.button(text=f"{tariff.name} – {int(tariff.price_rub)}₽", callback_data=f"confirm:{tariff.id}")
- builder.button(text="🔙 Назад", callback_data="back:group")
+ builder.button(text=BACK, callback_data="back:group")
builder.adjust(1)
await callback_query.message.edit_text(
@@ -271,7 +272,7 @@ async def handle_user_renew_confirm(
builder.row(b)
builder.row(InlineKeyboardButton(text="✅ Применить", callback_data=f"cfg_renew_apply|{tariff_id}"))
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="back:group"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data="back:group"))
devices_label = (
"Безлимит устройств"
@@ -456,7 +457,7 @@ async def handle_cfg_renew_devices(callback_query: CallbackQuery, state: FSMCont
builder.row(b)
builder.row(InlineKeyboardButton(text="✅ Применить", callback_data=f"cfg_renew_apply|{tariff_id}"))
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="back:group"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data="back:group"))
devices_label = "Безлимит устройств" if selected_devices <= 0 else f"{selected_devices} устройств"
traffic_label = (
@@ -573,7 +574,7 @@ async def handle_cfg_renew_traffic(callback_query: CallbackQuery, state: FSMCont
builder.row(b)
builder.row(InlineKeyboardButton(text="✅ Применить", callback_data=f"cfg_renew_apply|{tariff_id}"))
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="back:group"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data="back:group"))
devices_label = (
"Безлимит устройств"
@@ -708,7 +709,7 @@ async def handle_back_to_group(
builder = InlineKeyboardBuilder()
for group_code in groups:
builder.button(text=group_code, callback_data=f"group:{group_code}")
- builder.button(text="🔙 Назад", callback_data="back:renew")
+ builder.button(text=BACK, callback_data="back:renew")
builder.adjust(1)
await callback_query.message.edit_text(
diff --git a/handlers/buttons.py b/handlers/buttons.py
index be137aca..a5fa0ca0 100644
--- a/handlers/buttons.py
+++ b/handlers/buttons.py
@@ -21,6 +21,7 @@ TRIAL_SUB = "🎁 Пробная подписка"
TRIAL_BONUS = "🚀 Активировать пробный период"
MY_SUB = "🔐 Моя подписка"
RENEW_SUB = "🔄 Обновить подписку"
+ADMIN_BTN = "📊 Администратор"
ADDONS_BUTTON_DEVICES = "➕ Добавить устройства"
ADDONS_BUTTON_TRAFFIC = "➕ Добавить трафик"
diff --git a/handlers/coupons.py b/handlers/coupons.py
index a3a8cec1..dee47811 100644
--- a/handlers/coupons.py
+++ b/handlers/coupons.py
@@ -238,8 +238,12 @@ async def handle_key_extension(
tariff_gb = int(tariff["traffic_limit"]) if tariff and tariff.get("traffic_limit") else 0
tariff_devices = int(tariff["device_limit"]) if tariff and tariff.get("device_limit") else 0
- total_gb = int(key.current_traffic_limit) if getattr(key, "current_traffic_limit", None) is not None else tariff_gb
- device_limit = int(key.current_device_limit) if getattr(key, "current_device_limit", None) is not None else tariff_devices
+ total_gb = (
+ int(key.current_traffic_limit) if getattr(key, "current_traffic_limit", None) is not None else tariff_gb
+ )
+ device_limit = (
+ int(key.current_device_limit) if getattr(key, "current_device_limit", None) is not None else tariff_devices
+ )
key_subgroup = None
if tariff:
diff --git a/handlers/donate.py b/handlers/donate.py
index 7194a819..2d856df2 100644
--- a/handlers/donate.py
+++ b/handlers/donate.py
@@ -11,7 +11,7 @@ from aiogram.types import (
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import RUB_TO_XTR, STARS_BOT_URL
-from handlers.buttons import BACK, MAIN_MENU
+from handlers.buttons import BACK, MAIN_MENU, STARS_BOT
from logger import logger
from .utils import edit_or_send_message
@@ -31,7 +31,7 @@ async def process_donate(callback_query: CallbackQuery, state: FSMContext):
await state.clear()
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url=STARS_BOT_URL))
+ builder.row(InlineKeyboardButton(text=STARS_BOT, url=STARS_BOT_URL))
builder.row(
InlineKeyboardButton(
text="💰 Ввести сумму доната",
diff --git a/handlers/instructions/instructions.py b/handlers/instructions/instructions.py
index 78144f50..9d8481a9 100644
--- a/handlers/instructions/instructions.py
+++ b/handlers/instructions/instructions.py
@@ -167,17 +167,20 @@ async def process_macos_menu(callback_query: CallbackQuery, session: Any):
@router.callback_query(F.data.startswith("connect_tv|"))
async def process_connect_tv(callback_query: CallbackQuery, session: Any):
- key_name = callback_query.data.split("|")[1]
+ key_name = callback_query.data.split("|", 1)[1]
record = await get_key_details(session, key_name)
final_link = None
is_full_remnawave = False
use_webapp = False
+ is_remnawave_webapp = False
if record:
- server_name = record["server_id"]
+ server_name = record.get("server_id")
final_link = record.get("key") or record.get("remnawave_link")
- is_full_remnawave = await is_full_remnawave_cluster(server_name, session)
+
+ if server_name:
+ is_full_remnawave = await is_full_remnawave_cluster(server_name, session)
remnawave_webapp_enabled = bool(MODES_CONFIG.get("REMNAWAVE_WEBAPP_ENABLED", REMNAWAVE_WEBAPP))
happ_cryptolink_enabled = bool(MODES_CONFIG.get("HAPP_CRYPTOLINK_ENABLED", HAPP_CRYPTOLINK))
@@ -191,22 +194,23 @@ async def process_connect_tv(callback_query: CallbackQuery, session: Any):
)
is_remnawave_webapp = bool(is_full_remnawave and final_link and use_webapp and not happ_cryptolink_enabled)
- else:
- is_remnawave_webapp = False
- if is_remnawave_webapp:
- back_callback = f"view_key|{key_name}"
- else:
- back_callback = f"connect_device|{key_name}"
+ if not final_link:
+ await callback_query.answer("❌ Ссылка подписки не найдена", show_alert=True)
+ return
+
+ back_callback = f"view_key|{key_name}" if is_remnawave_webapp else f"connect_device|{key_name}"
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text=TV_CONTINUE, callback_data=f"continue_tv|{key_name}"))
builder.row(InlineKeyboardButton(text=BACK, callback_data=back_callback))
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
+ text = CONNECT_TV_TEXT.format(subscription_link=final_link)
+
await edit_or_send_message(
target_message=callback_query.message,
- text=CONNECT_TV_TEXT,
+ text=text,
reply_markup=builder.as_markup(),
media_path=None,
disable_web_page_preview=True,
diff --git a/handlers/keys/key_create.py b/handlers/keys/key_create.py
index 4e7539e5..34065db6 100644
--- a/handlers/keys/key_create.py
+++ b/handlers/keys/key_create.py
@@ -28,7 +28,7 @@ from database.models import Admin
from database.notifications import check_hot_lead_discount
from database.tariffs import create_subgroup_hash, find_subgroup_by_hash, get_tariffs
from handlers.admin.panel.keyboard import AdminPanelCallback
-from handlers.buttons import MAIN_MENU
+from handlers.buttons import MAIN_MENU, BACK
from handlers.texts import (
CREATING_CONNECTION_MSG,
DISCOUNT_OFFER_MESSAGE,
@@ -362,7 +362,7 @@ async def show_tariffs_in_subgroup_user(callback: CallbackQuery, state: FSMConte
callback_prefix="select_tariff_plan",
)
- builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_tariff_group_list"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data="back_to_tariff_group_list"))
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
await edit_or_send_message(
diff --git a/handlers/keys/key_renew.py b/handlers/keys/key_renew.py
index eac7f743..f0c3bf31 100644
--- a/handlers/keys/key_renew.py
+++ b/handlers/keys/key_renew.py
@@ -368,7 +368,7 @@ async def show_tariffs_in_renew_subgroup(callback: CallbackQuery, state: FSMCont
callback_prefix="renew_plan",
)
- builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"renew_key|{key_name}"))
+ builder.row(InlineKeyboardButton(text=BACK, callback_data=f"renew_key|{key_name}"))
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
hook_builder = InlineKeyboardBuilder()
diff --git a/handlers/keys/operations/renewal.py b/handlers/keys/operations/renewal.py
index 889f533c..4aabb84b 100644
--- a/handlers/keys/operations/renewal.py
+++ b/handlers/keys/operations/renewal.py
@@ -74,6 +74,7 @@ async def renew_on_remnawave(
if not await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD):
logger.error(f"{PANEL_REMNA} Не удалось войти в Remnawave API")
return False
+ hwid_device_limit = int(hwid_device_limit or 0)
if old_device_limit is not None and hwid_device_limit < old_device_limit:
try:
diff --git a/handlers/payments/pay.py b/handlers/payments/pay.py
index fa064a1c..14ce340c 100644
--- a/handlers/payments/pay.py
+++ b/handlers/payments/pay.py
@@ -19,7 +19,12 @@ from handlers.payments.currency_rates import format_for_user
from handlers.payments.providers import get_providers_with_hooks
from handlers.payments.stars.handlers import process_callback_pay_stars
from handlers.payments.tribute.handlers import process_callback_pay_tribute
-from handlers.texts import FAST_PAY_CHOOSE_CURRENCY, BALANCE_MANAGEMENT_TEXT, PAYMENT_METHODS_MSG
+from handlers.texts import (
+ FAST_PAY_CHOOSE_CURRENCY,
+ BALANCE_MANAGEMENT_TEXT,
+ PAYMENT_METHODS_MSG,
+ BALANCE_HISTORY_HEADER,
+)
from hooks.hook_buttons import insert_hook_buttons
from hooks.hooks import run_hooks
@@ -211,7 +216,7 @@ async def balance_history_handler(callback_query: CallbackQuery, session: Any):
if records:
language_code = getattr(callback_query.from_user, "language_code", None)
- history_text = "💳 История операций:\n\n
" + history_text = f"{BALANCE_HISTORY_HEADER}\n\n" for record in records: amount_rub = record["amount"] or 0 formatted_amount = await format_for_user(session, callback_query.from_user.id, amount_rub, language_code) diff --git a/handlers/payments/providers.py b/handlers/payments/providers.py index 4db18c55..34e078cf 100644 --- a/handlers/payments/providers.py +++ b/handlers/payments/providers.py @@ -79,9 +79,7 @@ def _get_effective_order(name: str, cfg: dict[str, Any]) -> int: def _sort_providers(providers: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]: """Сортирует провайдеров по полю 'order' (меньше = выше).""" - return dict( - sorted(providers.items(), key=lambda item: _get_effective_order(item[0], item[1])) - ) + return dict(sorted(providers.items(), key=lambda item: _get_effective_order(item[0], item[1]))) def sort_provider_names(names: list[str], providers_map: dict[str, dict[str, Any]]) -> list[str]: diff --git a/handlers/profile.py b/handlers/profile.py index eba92b26..3b15fa7e 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -28,6 +28,7 @@ from handlers.buttons import ( MY_SUB, MY_SUBS, TRIAL_SUB, + ADMIN_BTN, ) from handlers.payments.currency_rates import format_for_user from handlers.texts import ADD_SUBSCRIPTION_HINT @@ -135,7 +136,7 @@ async def process_callback_view_profile( if admin: builder.row( InlineKeyboardButton( - text="📊 Администратор", + text=ADMIN_BTN, callback_data=AdminPanelCallback(action="admin").pack(), ) ) diff --git a/handlers/start.py b/handlers/start.py index 9ab8db31..62a54dd3 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -39,6 +39,7 @@ from handlers.buttons import ( SUB_CHANELL_DONE, SUPPORT, TRIAL_SUB, + ADMIN_BTN, ) from handlers.captcha import generate_captcha from handlers.coupons import activate_coupon @@ -313,7 +314,7 @@ async def show_start_menu( kb.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL)) if admin: - kb.row(InlineKeyboardButton(text="📊 Администратор", callback_data=AdminPanelCallback(action="admin").pack())) + kb.row(InlineKeyboardButton(text=ADMIN_BTN, callback_data=AdminPanelCallback(action="admin").pack())) try: module_buttons = await run_hooks("start_menu", chat_id=message.chat.id, session=session)