API 0.5.1/ Minor changes and improvements

This commit is contained in:
Vladless
2026-02-08 22:30:05 +03:00
parent 8693115571
commit 833721bd60
37 changed files with 242 additions and 190 deletions
+1 -1
View File
@@ -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",
+3 -3
View File
@@ -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),
+51 -10
View File
@@ -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
+1 -2
View File
@@ -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
+1 -3
View File
@@ -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
+2 -1
View File
@@ -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(),
)
)
+5 -4
View File
@@ -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(),
)
+2 -4
View File
@@ -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
):
+10 -9
View File
@@ -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()
)
],
]
+40 -50
View File
@@ -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 = "🎯 <b>Кому доступен купон?</b>"
kb = InlineKeyboardBuilder()
kb.button(text="👤 Всем", callback_data="coupon_audience_all")
@@ -80,44 +78,18 @@ 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 = (
"🎫 <b>Введите данные для создания купона в формате:</b>\n\n"
"📝 <i>код</i> 💰 <i>сумма</i> 🔢 <i>лимит</i>\n\n"
"Пример: <b>'COUPON1 50 5'</b>\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
if coupon_type == "days":
text = (
"🎫 <b>Введите данные для создания купона в формате:</b>\n\n"
"📝 <i>код</i> ⏳ <i>дни</i> 🔢 <i>лимит</i>\n\n"
@@ -125,7 +97,15 @@ async def handle_coupon_audience(callback_query: CallbackQuery, state: FSMContex
)
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 = (
"🎫 <b>Введите данные для создания купона в формате:</b>\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 = (
"🎫 <b>Введите данные для создания купона в формате:</b>\n\n"
"📝 <i>код</i> 💰 <i>сумма</i> 🔢 <i>лимит</i>\n\n"
"Пример: <b>'COUPON1 50 5'</b>\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"✅ Купон <b>{coupon_code}</b> создан!\n"
f"⏳ <b>{format_days(days)}</b>\n"
f"🔢 Лимит: <b>{usage_limit} раз</b>\n"
f"🎯 Доступ: <b>{audience_txt}</b>\n"
f"🔗 <b>Ссылка:</b> <code>{coupon_link}</code>\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"✅ Купон <b>{coupon_code}</b> создан!\n"
f"📉 Скидка: <b>{percent}%</b>\n"
f"🔢 Лимит: <b>{usage_limit} раз</b>\n"
f"🎯 Доступ: <b>{audience_txt}</b>\n"
f"✅ Купон <b>{coupon_code}</b> создан!\n📉 Скидка: <b>{percent}%</b>\n🔢 Лимит: <b>{usage_limit} раз</b>\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
+7 -6
View File
@@ -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())
+2 -2
View File
@@ -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()
+8 -7
View File
@@ -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()
+1 -1
View File
@@ -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(),
)
)
+4 -6
View File
@@ -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()
+9 -6
View File
@@ -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(),
)
)
@@ -122,7 +123,8 @@ def build_providers_order_kb(sorted_names: list[str]) -> InlineKeyboardMarkup:
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(),
)
)
@@ -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()
+2 -1
View File
@@ -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(),
)
],
+3 -2
View File
@@ -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(),
)
)
+8 -11
View File
@@ -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())],
]
)
+3 -2
View File
@@ -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(),
)
],
+6 -5
View File
@@ -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}")]
]
),
)
+3 -2
View File
@@ -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(),
)
)
+4 -3
View File
@@ -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="✏️ Введите причину <b>постоянной блокировки</b> (или <code>-</code>, чтобы пропустить):",
@@ -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="✏️ Введите причину <b>временной блокировки</b> (или <code>-</code>, чтобы пропустить):",
@@ -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 — навсегда):",
+12 -12
View File
@@ -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(
"<b>📦 Редактирование базы тарифа</b>\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(
"<b>➕ Редактирование докупки</b>\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(),
)
)
+7 -6
View File
@@ -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(
+1
View File
@@ -21,6 +21,7 @@ TRIAL_SUB = "🎁 Пробная подписка"
TRIAL_BONUS = "🚀 Активировать пробный период"
MY_SUB = "🔐 Моя подписка"
RENEW_SUB = "🔄 Обновить подписку"
ADMIN_BTN = "📊 Администратор"
ADDONS_BUTTON_DEVICES = "➕ Добавить устройства"
ADDONS_BUTTON_TRAFFIC = " Добавить трафик"
+6 -2
View File
@@ -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:
+2 -2
View File
@@ -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="💰 Ввести сумму доната",
+13 -9
View File
@@ -167,16 +167,19 @@ 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")
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))
@@ -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,
+2 -2
View File
@@ -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(
+1 -1
View File
@@ -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()
+1
View File
@@ -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:
+7 -2
View File
@@ -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 = "<b>💳 История операций:</b>\n\n<blockquote>"
history_text = f"{BALANCE_HISTORY_HEADER}</b>\n\n<blockquote>"
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)
+1 -3
View File
@@ -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]:
+2 -1
View File
@@ -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(),
)
)
+2 -1
View File
@@ -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)