API 0.5.1/ Minor changes and improvements
This commit is contained in:
+1
-1
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
],
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
],
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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())],
|
||||
]
|
||||
)
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
],
|
||||
|
||||
@@ -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}")]
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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 — навсегда):",
|
||||
|
||||
@@ -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 +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(
|
||||
|
||||
@@ -21,6 +21,7 @@ TRIAL_SUB = "🎁 Пробная подписка"
|
||||
TRIAL_BONUS = "🚀 Активировать пробный период"
|
||||
MY_SUB = "🔐 Моя подписка"
|
||||
RENEW_SUB = "🔄 Обновить подписку"
|
||||
ADMIN_BTN = "📊 Администратор"
|
||||
|
||||
ADDONS_BUTTON_DEVICES = "➕ Добавить устройства"
|
||||
ADDONS_BUTTON_TRAFFIC = "➕ Добавить трафик"
|
||||
|
||||
+6
-2
@@ -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
@@ -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="💰 Ввести сумму доната",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user