mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-23 21:01:17 +00:00
1381 lines
46 KiB
Python
1381 lines
46 KiB
Python
import math
|
||
import time
|
||
from typing import Iterable, List, Tuple
|
||
|
||
from aiogram import Dispatcher, F, types
|
||
from aiogram.filters import BaseFilter, StateFilter
|
||
from aiogram.fsm.context import FSMContext
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.database.models import User
|
||
from app.localization.texts import get_texts
|
||
from app.config import settings
|
||
from app.services.remnawave_service import RemnaWaveService
|
||
from app.services.payment_service import PaymentService
|
||
from app.services.tribute_service import TributeService
|
||
from app.services.system_settings_service import bot_configuration_service
|
||
from app.states import BotConfigStates
|
||
from app.utils.decorators import admin_required, error_handler
|
||
from app.utils.currency_converter import currency_converter
|
||
from app.external.telegram_stars import TelegramStarsService
|
||
|
||
|
||
CATEGORY_PAGE_SIZE = 10
|
||
SETTINGS_PAGE_SIZE = 8
|
||
|
||
|
||
CATEGORY_GROUP_DEFINITIONS: Tuple[Tuple[str, str, Tuple[str, ...]], ...] = (
|
||
(
|
||
"telegram_bot",
|
||
"🤖 Telegram бот",
|
||
("SUPPORT", "ADMIN_NOTIFICATIONS", "ADMIN_REPORTS", "CHANNEL"),
|
||
),
|
||
(
|
||
"database",
|
||
"🗄️ База данных",
|
||
("DATABASE", "POSTGRES", "SQLITE", "REDIS"),
|
||
),
|
||
(
|
||
"remnawave",
|
||
"🌊 Remnawave API",
|
||
("REMNAWAVE",),
|
||
),
|
||
(
|
||
"subscriptions",
|
||
"🪙 Подписки и тарифы",
|
||
(
|
||
"TRIAL",
|
||
"PAID_SUBSCRIPTION",
|
||
"SUBSCRIPTIONS_GLOBAL",
|
||
"TRAFFIC",
|
||
"PERIODS",
|
||
"SUBSCRIPTION_PRICES",
|
||
"TRAFFIC_PACKAGES",
|
||
"DISCOUNTS",
|
||
"REFERRAL",
|
||
"AUTOPAY",
|
||
),
|
||
),
|
||
(
|
||
"payments",
|
||
"💳 Платежные системы",
|
||
("TELEGRAM", "TRIBUTE", "YOOKASSA", "CRYPTOBOT", "MULENPAY", "PAL24", "PAYMENT"),
|
||
),
|
||
(
|
||
"interface",
|
||
"🎨 Интерфейс и UX",
|
||
("INTERFACE_BRANDING", "INTERFACE_SUBSCRIPTION", "CONNECT_BUTTON", "HAPP", "SKIP"),
|
||
),
|
||
(
|
||
"monitoring",
|
||
"📣 Мониторинг и уведомления",
|
||
("MONITORING", "NOTIFICATIONS"),
|
||
),
|
||
(
|
||
"operations",
|
||
"🛠️ Статусы и обслуживание",
|
||
("SERVER", "MAINTENANCE"),
|
||
),
|
||
(
|
||
"localization",
|
||
"🈯 Локализация",
|
||
("LOCALIZATION",),
|
||
),
|
||
(
|
||
"extras",
|
||
"🧩 Дополнительные настройки",
|
||
("ADDITIONAL",),
|
||
),
|
||
(
|
||
"reliability",
|
||
"💾 Бекапы и обновления",
|
||
("BACKUP", "VERSION"),
|
||
),
|
||
(
|
||
"technical",
|
||
"🧰 Технические",
|
||
("LOG", "WEBHOOK", "DEBUG"),
|
||
),
|
||
)
|
||
|
||
CATEGORY_FALLBACK_KEY = "other"
|
||
CATEGORY_FALLBACK_TITLE = "📦 Прочие настройки"
|
||
|
||
|
||
async def _store_setting_context(
|
||
state: FSMContext,
|
||
*,
|
||
key: str,
|
||
group_key: str,
|
||
category_page: int,
|
||
settings_page: int,
|
||
) -> None:
|
||
await state.update_data(
|
||
setting_key=key,
|
||
setting_group_key=group_key,
|
||
setting_category_page=category_page,
|
||
setting_settings_page=settings_page,
|
||
botcfg_origin="bot_config",
|
||
botcfg_timestamp=time.time(),
|
||
)
|
||
|
||
|
||
class BotConfigInputFilter(BaseFilter):
|
||
def __init__(self, timeout: float = 300.0) -> None:
|
||
self.timeout = timeout
|
||
|
||
async def __call__(
|
||
self,
|
||
message: types.Message,
|
||
state: FSMContext,
|
||
) -> bool:
|
||
if not message.text or message.text.startswith("/"):
|
||
return False
|
||
|
||
if message.chat.type != "private":
|
||
return False
|
||
|
||
data = await state.get_data()
|
||
|
||
if data.get("botcfg_origin") != "bot_config":
|
||
return False
|
||
|
||
if not data.get("setting_key"):
|
||
return False
|
||
|
||
timestamp = data.get("botcfg_timestamp")
|
||
if timestamp is None:
|
||
return True
|
||
|
||
try:
|
||
return (time.time() - float(timestamp)) <= self.timeout
|
||
except (TypeError, ValueError):
|
||
return False
|
||
|
||
|
||
def _chunk(buttons: Iterable[types.InlineKeyboardButton], size: int) -> Iterable[List[types.InlineKeyboardButton]]:
|
||
buttons_list = list(buttons)
|
||
for index in range(0, len(buttons_list), size):
|
||
yield buttons_list[index : index + size]
|
||
|
||
|
||
def _parse_category_payload(payload: str) -> Tuple[str, str, int, int]:
|
||
parts = payload.split(":")
|
||
group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY
|
||
category_key = parts[2] if len(parts) > 2 else ""
|
||
|
||
def _safe_int(value: str, default: int = 1) -> int:
|
||
try:
|
||
return max(1, int(value))
|
||
except (TypeError, ValueError):
|
||
return default
|
||
|
||
category_page = _safe_int(parts[3]) if len(parts) > 3 else 1
|
||
settings_page = _safe_int(parts[4]) if len(parts) > 4 else 1
|
||
return group_key, category_key, category_page, settings_page
|
||
|
||
|
||
def _parse_group_payload(payload: str) -> Tuple[str, int]:
|
||
parts = payload.split(":")
|
||
group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY
|
||
try:
|
||
page = max(1, int(parts[2]))
|
||
except (IndexError, ValueError):
|
||
page = 1
|
||
return group_key, page
|
||
|
||
|
||
def _get_grouped_categories() -> List[Tuple[str, str, List[Tuple[str, str, int]]]]:
|
||
categories = bot_configuration_service.get_categories()
|
||
categories_map = {key: (label, count) for key, label, count in categories}
|
||
used: set[str] = set()
|
||
grouped: List[Tuple[str, str, List[Tuple[str, str, int]]]] = []
|
||
|
||
for group_key, title, category_keys in CATEGORY_GROUP_DEFINITIONS:
|
||
items: List[Tuple[str, str, int]] = []
|
||
for category_key in category_keys:
|
||
if category_key in categories_map:
|
||
label, count = categories_map[category_key]
|
||
items.append((category_key, label, count))
|
||
used.add(category_key)
|
||
if items:
|
||
grouped.append((group_key, title, items))
|
||
|
||
remaining = [
|
||
(key, label, count)
|
||
for key, (label, count) in categories_map.items()
|
||
if key not in used
|
||
]
|
||
|
||
if remaining:
|
||
remaining.sort(key=lambda item: item[1])
|
||
grouped.append((CATEGORY_FALLBACK_KEY, CATEGORY_FALLBACK_TITLE, remaining))
|
||
|
||
return grouped
|
||
|
||
|
||
def _build_groups_keyboard() -> types.InlineKeyboardMarkup:
|
||
grouped = _get_grouped_categories()
|
||
rows: list[list[types.InlineKeyboardButton]] = []
|
||
|
||
for group_key, title, items in grouped:
|
||
total = sum(count for _, _, count in items)
|
||
rows.append(
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text=f"{title} ({total})",
|
||
callback_data=f"botcfg_group:{group_key}:1",
|
||
)
|
||
]
|
||
)
|
||
|
||
rows.append(
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="⬅️ Назад",
|
||
callback_data="admin_submenu_settings",
|
||
)
|
||
]
|
||
)
|
||
|
||
return types.InlineKeyboardMarkup(inline_keyboard=rows)
|
||
|
||
|
||
def _build_categories_keyboard(
|
||
group_key: str,
|
||
group_title: str,
|
||
categories: List[Tuple[str, str, int]],
|
||
page: int = 1,
|
||
) -> types.InlineKeyboardMarkup:
|
||
total_pages = max(1, math.ceil(len(categories) / CATEGORY_PAGE_SIZE))
|
||
page = max(1, min(page, total_pages))
|
||
|
||
start = (page - 1) * CATEGORY_PAGE_SIZE
|
||
end = start + CATEGORY_PAGE_SIZE
|
||
sliced = categories[start:end]
|
||
|
||
rows: list[list[types.InlineKeyboardButton]] = []
|
||
rows.append(
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text=f"— {group_title} —",
|
||
callback_data="botcfg_group:noop",
|
||
)
|
||
]
|
||
)
|
||
|
||
buttons: List[types.InlineKeyboardButton] = []
|
||
for category_key, label, count in sliced:
|
||
button_text = f"{label} ({count})"
|
||
buttons.append(
|
||
types.InlineKeyboardButton(
|
||
text=button_text,
|
||
callback_data=f"botcfg_cat:{group_key}:{category_key}:{page}:1",
|
||
)
|
||
)
|
||
|
||
for chunk in _chunk(buttons, 2):
|
||
rows.append(list(chunk))
|
||
|
||
if total_pages > 1:
|
||
nav_row: list[types.InlineKeyboardButton] = []
|
||
if page > 1:
|
||
nav_row.append(
|
||
types.InlineKeyboardButton(
|
||
text="⬅️",
|
||
callback_data=f"botcfg_group:{group_key}:{page - 1}",
|
||
)
|
||
)
|
||
nav_row.append(
|
||
types.InlineKeyboardButton(
|
||
text=f"{page}/{total_pages}",
|
||
callback_data="botcfg_group:noop",
|
||
)
|
||
)
|
||
if page < total_pages:
|
||
nav_row.append(
|
||
types.InlineKeyboardButton(
|
||
text="➡️",
|
||
callback_data=f"botcfg_group:{group_key}:{page + 1}",
|
||
)
|
||
)
|
||
rows.append(nav_row)
|
||
|
||
rows.append(
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="⬅️ К разделам",
|
||
callback_data="admin_bot_config",
|
||
)
|
||
]
|
||
)
|
||
|
||
return types.InlineKeyboardMarkup(inline_keyboard=rows)
|
||
|
||
|
||
def _build_settings_keyboard(
|
||
category_key: str,
|
||
group_key: str,
|
||
category_page: int,
|
||
language: str,
|
||
page: int = 1,
|
||
) -> types.InlineKeyboardMarkup:
|
||
definitions = bot_configuration_service.get_settings_for_category(category_key)
|
||
total_pages = max(1, math.ceil(len(definitions) / SETTINGS_PAGE_SIZE))
|
||
page = max(1, min(page, total_pages))
|
||
|
||
start = (page - 1) * SETTINGS_PAGE_SIZE
|
||
end = start + SETTINGS_PAGE_SIZE
|
||
sliced = definitions[start:end]
|
||
|
||
rows: list[list[types.InlineKeyboardButton]] = []
|
||
texts = get_texts(language)
|
||
|
||
if category_key == "REMNAWAVE":
|
||
rows.append(
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="🔌 Проверить подключение",
|
||
callback_data=(
|
||
f"botcfg_test_remnawave:{group_key}:{category_key}:{category_page}:{page}"
|
||
),
|
||
)
|
||
]
|
||
)
|
||
|
||
test_payment_buttons: list[list[types.InlineKeyboardButton]] = []
|
||
|
||
def _test_button(text: str, method: str) -> types.InlineKeyboardButton:
|
||
return types.InlineKeyboardButton(
|
||
text=text,
|
||
callback_data=(
|
||
f"botcfg_test_payment:{method}:{group_key}:{category_key}:{category_page}:{page}"
|
||
),
|
||
)
|
||
|
||
if category_key == "YOOKASSA":
|
||
label = texts.t("PAYMENT_CARD_YOOKASSA", "💳 Банковская карта (YooKassa)")
|
||
test_payment_buttons.append([_test_button(f"{label} · тест", "yookassa")])
|
||
elif category_key == "TRIBUTE":
|
||
label = texts.t("PAYMENT_CARD_TRIBUTE", "💳 Банковская карта (Tribute)")
|
||
test_payment_buttons.append([_test_button(f"{label} · тест", "tribute")])
|
||
elif category_key == "MULENPAY":
|
||
label = texts.t("PAYMENT_CARD_MULENPAY", "💳 Банковская карта (Mulen Pay)")
|
||
test_payment_buttons.append([_test_button(f"{label} · тест", "mulenpay")])
|
||
elif category_key == "PAL24":
|
||
label = texts.t("PAYMENT_CARD_PAL24", "💳 Банковская карта (PayPalych)")
|
||
test_payment_buttons.append([_test_button(f"{label} · тест", "pal24")])
|
||
elif category_key == "TELEGRAM":
|
||
label = texts.t("PAYMENT_TELEGRAM_STARS", "⭐ Telegram Stars")
|
||
test_payment_buttons.append([_test_button(f"{label} · тест", "stars")])
|
||
elif category_key == "CRYPTOBOT":
|
||
label = texts.t("PAYMENT_CRYPTOBOT", "🪙 Криптовалюта (CryptoBot)")
|
||
test_payment_buttons.append([_test_button(f"{label} · тест", "cryptobot")])
|
||
|
||
if test_payment_buttons:
|
||
rows.extend(test_payment_buttons)
|
||
|
||
for definition in sliced:
|
||
value_preview = bot_configuration_service.format_value_for_list(definition.key)
|
||
button_text = f"{definition.display_name} · {value_preview}"
|
||
if len(button_text) > 64:
|
||
button_text = button_text[:63] + "…"
|
||
callback_token = bot_configuration_service.get_callback_token(definition.key)
|
||
rows.append(
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text=button_text,
|
||
callback_data=(
|
||
f"botcfg_setting:{group_key}:{category_page}:{page}:{callback_token}"
|
||
),
|
||
)
|
||
]
|
||
)
|
||
|
||
if total_pages > 1:
|
||
nav_row: list[types.InlineKeyboardButton] = []
|
||
if page > 1:
|
||
nav_row.append(
|
||
types.InlineKeyboardButton(
|
||
text="⬅️",
|
||
callback_data=(
|
||
f"botcfg_cat:{group_key}:{category_key}:{category_page}:{page - 1}"
|
||
),
|
||
)
|
||
)
|
||
nav_row.append(
|
||
types.InlineKeyboardButton(
|
||
text=f"{page}/{total_pages}", callback_data="botcfg_cat_page:noop"
|
||
)
|
||
)
|
||
if page < total_pages:
|
||
nav_row.append(
|
||
types.InlineKeyboardButton(
|
||
text="➡️",
|
||
callback_data=(
|
||
f"botcfg_cat:{group_key}:{category_key}:{category_page}:{page + 1}"
|
||
),
|
||
)
|
||
)
|
||
rows.append(nav_row)
|
||
|
||
rows.append([
|
||
types.InlineKeyboardButton(
|
||
text="⬅️ К категориям",
|
||
callback_data=f"botcfg_group:{group_key}:{category_page}",
|
||
)
|
||
])
|
||
|
||
return types.InlineKeyboardMarkup(inline_keyboard=rows)
|
||
|
||
|
||
def _build_setting_keyboard(
|
||
key: str,
|
||
group_key: str,
|
||
category_page: int,
|
||
settings_page: int,
|
||
) -> types.InlineKeyboardMarkup:
|
||
definition = bot_configuration_service.get_definition(key)
|
||
rows: list[list[types.InlineKeyboardButton]] = []
|
||
callback_token = bot_configuration_service.get_callback_token(key)
|
||
|
||
choice_options = bot_configuration_service.get_choice_options(key)
|
||
if choice_options:
|
||
current_value = bot_configuration_service.get_current_value(key)
|
||
choice_buttons: list[types.InlineKeyboardButton] = []
|
||
for option in choice_options:
|
||
choice_token = bot_configuration_service.get_choice_token(key, option.value)
|
||
if choice_token is None:
|
||
continue
|
||
button_text = option.label
|
||
if current_value == option.value and not button_text.startswith("✅"):
|
||
button_text = f"✅ {button_text}"
|
||
choice_buttons.append(
|
||
types.InlineKeyboardButton(
|
||
text=button_text,
|
||
callback_data=(
|
||
f"botcfg_choice:{group_key}:{category_page}:{settings_page}:{callback_token}:{choice_token}"
|
||
),
|
||
)
|
||
)
|
||
|
||
for chunk in _chunk(choice_buttons, 2):
|
||
rows.append(list(chunk))
|
||
|
||
if definition.python_type is bool:
|
||
rows.append([
|
||
types.InlineKeyboardButton(
|
||
text="🔁 Переключить",
|
||
callback_data=(
|
||
f"botcfg_toggle:{group_key}:{category_page}:{settings_page}:{callback_token}"
|
||
),
|
||
)
|
||
])
|
||
|
||
rows.append([
|
||
types.InlineKeyboardButton(
|
||
text="✏️ Изменить",
|
||
callback_data=(
|
||
f"botcfg_edit:{group_key}:{category_page}:{settings_page}:{callback_token}"
|
||
),
|
||
)
|
||
])
|
||
|
||
if bot_configuration_service.has_override(key):
|
||
rows.append([
|
||
types.InlineKeyboardButton(
|
||
text="♻️ Сбросить",
|
||
callback_data=(
|
||
f"botcfg_reset:{group_key}:{category_page}:{settings_page}:{callback_token}"
|
||
),
|
||
)
|
||
])
|
||
|
||
rows.append([
|
||
types.InlineKeyboardButton(
|
||
text="⬅️ Назад",
|
||
callback_data=(
|
||
f"botcfg_cat:{group_key}:{definition.category_key}:{category_page}:{settings_page}"
|
||
),
|
||
)
|
||
])
|
||
|
||
return types.InlineKeyboardMarkup(inline_keyboard=rows)
|
||
|
||
|
||
def _render_setting_text(key: str) -> str:
|
||
summary = bot_configuration_service.get_setting_summary(key)
|
||
|
||
lines = [
|
||
"🧩 <b>Настройка</b>",
|
||
f"<b>Название:</b> {summary['name']}",
|
||
f"<b>Ключ:</b> <code>{summary['key']}</code>",
|
||
f"<b>Категория:</b> {summary['category_label']}",
|
||
f"<b>Тип:</b> {summary['type']}",
|
||
f"<b>Текущее значение:</b> {summary['current']}",
|
||
f"<b>Значение по умолчанию:</b> {summary['original']}",
|
||
f"<b>Переопределено в БД:</b> {'✅ Да' if summary['has_override'] else '❌ Нет'}",
|
||
]
|
||
|
||
choices = bot_configuration_service.get_choice_options(key)
|
||
if choices:
|
||
current_raw = bot_configuration_service.get_current_value(key)
|
||
lines.append("")
|
||
lines.append("<b>Доступные значения:</b>")
|
||
for option in choices:
|
||
marker = "✅" if current_raw == option.value else "•"
|
||
value_display = bot_configuration_service.format_value(option.value)
|
||
description = option.description or ""
|
||
if description:
|
||
lines.append(
|
||
f"{marker} {option.label} — <code>{value_display}</code>\n {description}"
|
||
)
|
||
else:
|
||
lines.append(f"{marker} {option.label} — <code>{value_display}</code>")
|
||
|
||
return "\n".join(lines)
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_bot_config_menu(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
):
|
||
keyboard = _build_groups_keyboard()
|
||
await callback.message.edit_text(
|
||
"🧩 <b>Конфигурация бота</b>\n\nВыберите раздел настроек:",
|
||
reply_markup=keyboard,
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_bot_config_group(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
):
|
||
group_key, page = _parse_group_payload(callback.data)
|
||
grouped = _get_grouped_categories()
|
||
group_lookup = {key: (title, items) for key, title, items in grouped}
|
||
|
||
if group_key not in group_lookup:
|
||
await callback.answer("Эта группа больше недоступна", show_alert=True)
|
||
return
|
||
|
||
group_title, items = group_lookup[group_key]
|
||
keyboard = _build_categories_keyboard(group_key, group_title, items, page)
|
||
await callback.message.edit_text(
|
||
f"🧩 <b>{group_title}</b>\n\nВыберите категорию настроек:",
|
||
reply_markup=keyboard,
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_bot_config_category(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
):
|
||
group_key, category_key, category_page, settings_page = _parse_category_payload(
|
||
callback.data
|
||
)
|
||
definitions = bot_configuration_service.get_settings_for_category(category_key)
|
||
|
||
if not definitions:
|
||
await callback.answer("В этой категории пока нет настроек", show_alert=True)
|
||
return
|
||
|
||
category_label = definitions[0].category_label
|
||
keyboard = _build_settings_keyboard(
|
||
category_key,
|
||
group_key,
|
||
category_page,
|
||
db_user.language,
|
||
settings_page,
|
||
)
|
||
await callback.message.edit_text(
|
||
f"🧩 <b>{category_label}</b>\n\nВыберите настройку для просмотра:",
|
||
reply_markup=keyboard,
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def test_remnawave_connection(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
):
|
||
parts = callback.data.split(":", 5)
|
||
group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY
|
||
category_key = parts[2] if len(parts) > 2 else "REMNAWAVE"
|
||
|
||
try:
|
||
category_page = max(1, int(parts[3])) if len(parts) > 3 else 1
|
||
except ValueError:
|
||
category_page = 1
|
||
|
||
try:
|
||
settings_page = max(1, int(parts[4])) if len(parts) > 4 else 1
|
||
except ValueError:
|
||
settings_page = 1
|
||
|
||
service = RemnaWaveService()
|
||
result = await service.test_api_connection()
|
||
|
||
status = result.get("status")
|
||
message: str
|
||
|
||
if status == "connected":
|
||
message = "✅ Подключение успешно"
|
||
elif status == "not_configured":
|
||
message = f"⚠️ {result.get('message', 'RemnaWave API не настроен')}"
|
||
else:
|
||
base_message = result.get("message", "Ошибка подключения")
|
||
status_code = result.get("status_code")
|
||
if status_code:
|
||
message = f"❌ {base_message} (HTTP {status_code})"
|
||
else:
|
||
message = f"❌ {base_message}"
|
||
|
||
definitions = bot_configuration_service.get_settings_for_category(category_key)
|
||
if definitions:
|
||
keyboard = _build_settings_keyboard(
|
||
category_key,
|
||
group_key,
|
||
category_page,
|
||
db_user.language,
|
||
settings_page,
|
||
)
|
||
try:
|
||
await callback.message.edit_reply_markup(reply_markup=keyboard)
|
||
except Exception:
|
||
# ignore inability to refresh markup, main result shown in alert
|
||
pass
|
||
|
||
await callback.answer(message, show_alert=True)
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def test_payment_provider(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
):
|
||
parts = callback.data.split(":", 6)
|
||
method = parts[1] if len(parts) > 1 else ""
|
||
group_key = parts[2] if len(parts) > 2 else CATEGORY_FALLBACK_KEY
|
||
category_key = parts[3] if len(parts) > 3 else "PAYMENT"
|
||
|
||
try:
|
||
category_page = max(1, int(parts[4])) if len(parts) > 4 else 1
|
||
except ValueError:
|
||
category_page = 1
|
||
|
||
try:
|
||
settings_page = max(1, int(parts[5])) if len(parts) > 5 else 1
|
||
except ValueError:
|
||
settings_page = 1
|
||
|
||
language = db_user.language
|
||
texts = get_texts(language)
|
||
payment_service = PaymentService(callback.bot)
|
||
|
||
message_text: str
|
||
|
||
async def _refresh_markup() -> None:
|
||
definitions = bot_configuration_service.get_settings_for_category(category_key)
|
||
if definitions:
|
||
keyboard = _build_settings_keyboard(
|
||
category_key,
|
||
group_key,
|
||
category_page,
|
||
language,
|
||
settings_page,
|
||
)
|
||
try:
|
||
await callback.message.edit_reply_markup(reply_markup=keyboard)
|
||
except Exception:
|
||
pass
|
||
|
||
if method == "yookassa":
|
||
if not settings.is_yookassa_enabled():
|
||
await callback.answer("❌ YooKassa отключена", show_alert=True)
|
||
return
|
||
|
||
amount_kopeks = 10 * 100
|
||
description = settings.get_balance_payment_description(amount_kopeks)
|
||
payment_result = await payment_service.create_yookassa_payment(
|
||
db=db,
|
||
user_id=db_user.id,
|
||
amount_kopeks=amount_kopeks,
|
||
description=f"Тестовый платеж (админ): {description}",
|
||
metadata={
|
||
"user_telegram_id": str(db_user.telegram_id),
|
||
"purpose": "admin_test_payment",
|
||
"provider": "yookassa",
|
||
},
|
||
)
|
||
|
||
if not payment_result or not payment_result.get("confirmation_url"):
|
||
await callback.answer("❌ Не удалось создать тестовый платеж YooKassa", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
confirmation_url = payment_result["confirmation_url"]
|
||
message_text = (
|
||
"🧪 <b>Тестовый платеж YooKassa</b>\n\n"
|
||
f"💰 Сумма: {texts.format_price(amount_kopeks)}\n"
|
||
f"🆔 ID: {payment_result['yookassa_payment_id']}"
|
||
)
|
||
reply_markup = types.InlineKeyboardMarkup(
|
||
inline_keyboard=[
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="💳 Оплатить картой",
|
||
url=confirmation_url,
|
||
)
|
||
],
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="📊 Проверить статус",
|
||
callback_data=f"check_yookassa_{payment_result['local_payment_id']}",
|
||
)
|
||
],
|
||
]
|
||
)
|
||
await callback.message.answer(message_text, reply_markup=reply_markup, parse_mode="HTML")
|
||
await callback.answer("✅ Ссылка на платеж YooKassa отправлена", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
if method == "tribute":
|
||
if not settings.TRIBUTE_ENABLED:
|
||
await callback.answer("❌ Tribute отключен", show_alert=True)
|
||
return
|
||
|
||
tribute_service = TributeService(callback.bot)
|
||
try:
|
||
payment_url = await tribute_service.create_payment_link(
|
||
user_id=db_user.telegram_id,
|
||
amount_kopeks=10 * 100,
|
||
description="Тестовый платеж Tribute (админ)",
|
||
)
|
||
except Exception:
|
||
payment_url = None
|
||
|
||
if not payment_url:
|
||
await callback.answer("❌ Не удалось создать платеж Tribute", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
message_text = (
|
||
"🧪 <b>Тестовый платеж Tribute</b>\n\n"
|
||
f"💰 Сумма: {texts.format_price(10 * 100)}\n"
|
||
"🔗 Нажмите кнопку ниже, чтобы открыть ссылку на оплату."
|
||
)
|
||
reply_markup = types.InlineKeyboardMarkup(
|
||
inline_keyboard=[
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="💳 Перейти к оплате",
|
||
url=payment_url,
|
||
)
|
||
]
|
||
]
|
||
)
|
||
await callback.message.answer(message_text, reply_markup=reply_markup, parse_mode="HTML")
|
||
await callback.answer("✅ Ссылка на платеж Tribute отправлена", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
if method == "mulenpay":
|
||
if not settings.is_mulenpay_enabled():
|
||
await callback.answer("❌ MulenPay отключен", show_alert=True)
|
||
return
|
||
|
||
amount_kopeks = 1 * 100
|
||
payment_result = await payment_service.create_mulenpay_payment(
|
||
db=db,
|
||
user_id=db_user.id,
|
||
amount_kopeks=amount_kopeks,
|
||
description="Тестовый платеж MulenPay (админ)",
|
||
language=language,
|
||
)
|
||
|
||
if not payment_result or not payment_result.get("payment_url"):
|
||
await callback.answer("❌ Не удалось создать платеж MulenPay", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
payment_url = payment_result["payment_url"]
|
||
message_text = (
|
||
"🧪 <b>Тестовый платеж MulenPay</b>\n\n"
|
||
f"💰 Сумма: {texts.format_price(amount_kopeks)}\n"
|
||
f"🆔 ID: {payment_result['mulen_payment_id']}"
|
||
)
|
||
reply_markup = types.InlineKeyboardMarkup(
|
||
inline_keyboard=[
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="💳 Перейти к оплате",
|
||
url=payment_url,
|
||
)
|
||
],
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="📊 Проверить статус",
|
||
callback_data=f"check_mulenpay_{payment_result['local_payment_id']}",
|
||
)
|
||
],
|
||
]
|
||
)
|
||
await callback.message.answer(message_text, reply_markup=reply_markup, parse_mode="HTML")
|
||
await callback.answer("✅ Ссылка на платеж MulenPay отправлена", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
if method == "pal24":
|
||
if not settings.is_pal24_enabled():
|
||
await callback.answer("❌ PayPalych отключен", show_alert=True)
|
||
return
|
||
|
||
amount_kopeks = 10 * 100
|
||
payment_result = await payment_service.create_pal24_payment(
|
||
db=db,
|
||
user_id=db_user.id,
|
||
amount_kopeks=amount_kopeks,
|
||
description="Тестовый платеж PayPalych (админ)",
|
||
language=language or "ru",
|
||
)
|
||
|
||
if not payment_result or not payment_result.get("link_url") and not payment_result.get("link_page_url"):
|
||
await callback.answer("❌ Не удалось создать платеж PayPalych", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
payment_url = payment_result.get("link_page_url") or payment_result.get("link_url")
|
||
message_text = (
|
||
"🧪 <b>Тестовый платеж PayPalych</b>\n\n"
|
||
f"💰 Сумма: {texts.format_price(amount_kopeks)}\n"
|
||
f"🆔 Bill ID: {payment_result['bill_id']}"
|
||
)
|
||
reply_markup = types.InlineKeyboardMarkup(
|
||
inline_keyboard=[
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="🏦 Перейти к оплате (СБП)",
|
||
url=payment_url,
|
||
)
|
||
],
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="📊 Проверить статус",
|
||
callback_data=f"check_pal24_{payment_result['local_payment_id']}",
|
||
)
|
||
],
|
||
]
|
||
)
|
||
await callback.message.answer(message_text, reply_markup=reply_markup, parse_mode="HTML")
|
||
await callback.answer("✅ Ссылка на платеж PayPalych отправлена", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
if method == "stars":
|
||
if not settings.TELEGRAM_STARS_ENABLED:
|
||
await callback.answer("❌ Telegram Stars отключены", show_alert=True)
|
||
return
|
||
|
||
stars_rate = settings.get_stars_rate()
|
||
amount_kopeks = max(1, int(round(stars_rate * 100)))
|
||
payload = f"admin_stars_test_{db_user.id}_{int(time.time())}"
|
||
try:
|
||
invoice_link = await payment_service.create_stars_invoice(
|
||
amount_kopeks=amount_kopeks,
|
||
description="Тестовый платеж Telegram Stars (админ)",
|
||
payload=payload,
|
||
)
|
||
except Exception:
|
||
invoice_link = None
|
||
|
||
if not invoice_link:
|
||
await callback.answer("❌ Не удалось создать платеж Telegram Stars", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
stars_amount = TelegramStarsService.calculate_stars_from_rubles(amount_kopeks / 100)
|
||
message_text = (
|
||
"🧪 <b>Тестовый платеж Telegram Stars</b>\n\n"
|
||
f"💰 Сумма: {texts.format_price(amount_kopeks)}\n"
|
||
f"⭐ К оплате: {stars_amount}"
|
||
)
|
||
reply_markup = types.InlineKeyboardMarkup(
|
||
inline_keyboard=[
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text=texts.t("PAYMENT_TELEGRAM_STARS", "⭐ Открыть счет"),
|
||
url=invoice_link,
|
||
)
|
||
]
|
||
]
|
||
)
|
||
await callback.message.answer(message_text, reply_markup=reply_markup, parse_mode="HTML")
|
||
await callback.answer("✅ Ссылка на платеж Stars отправлена", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
if method == "cryptobot":
|
||
if not settings.is_cryptobot_enabled():
|
||
await callback.answer("❌ CryptoBot отключен", show_alert=True)
|
||
return
|
||
|
||
amount_rubles = 100.0
|
||
try:
|
||
current_rate = await currency_converter.get_usd_to_rub_rate()
|
||
except Exception:
|
||
current_rate = None
|
||
|
||
if not current_rate or current_rate <= 0:
|
||
current_rate = 100.0
|
||
|
||
amount_usd = round(amount_rubles / current_rate, 2)
|
||
if amount_usd < 1:
|
||
amount_usd = 1.0
|
||
|
||
payment_result = await payment_service.create_cryptobot_payment(
|
||
db=db,
|
||
user_id=db_user.id,
|
||
amount_usd=amount_usd,
|
||
asset=settings.CRYPTOBOT_DEFAULT_ASSET,
|
||
description=f"Тестовый платеж CryptoBot {amount_rubles:.0f} ₽ ({amount_usd:.2f} USD)",
|
||
payload=f"admin_cryptobot_test_{db_user.id}_{int(time.time())}",
|
||
)
|
||
|
||
if not payment_result:
|
||
await callback.answer("❌ Не удалось создать платеж CryptoBot", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
payment_url = (
|
||
payment_result.get("bot_invoice_url")
|
||
or payment_result.get("mini_app_invoice_url")
|
||
or payment_result.get("web_app_invoice_url")
|
||
)
|
||
|
||
if not payment_url:
|
||
await callback.answer("❌ Не удалось получить ссылку на оплату CryptoBot", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
amount_kopeks = int(amount_rubles * 100)
|
||
message_text = (
|
||
"🧪 <b>Тестовый платеж CryptoBot</b>\n\n"
|
||
f"💰 Сумма к зачислению: {texts.format_price(amount_kopeks)}\n"
|
||
f"💵 К оплате: {amount_usd:.2f} USD\n"
|
||
f"🪙 Актив: {payment_result['asset']}"
|
||
)
|
||
reply_markup = types.InlineKeyboardMarkup(
|
||
inline_keyboard=[
|
||
[
|
||
types.InlineKeyboardButton(text="🪙 Открыть счет", url=payment_url)
|
||
],
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text="📊 Проверить статус",
|
||
callback_data=f"check_cryptobot_{payment_result['local_payment_id']}",
|
||
)
|
||
],
|
||
]
|
||
)
|
||
await callback.message.answer(message_text, reply_markup=reply_markup, parse_mode="HTML")
|
||
await callback.answer("✅ Ссылка на платеж CryptoBot отправлена", show_alert=True)
|
||
await _refresh_markup()
|
||
return
|
||
|
||
await callback.answer("❌ Неизвестный способ тестирования платежа", show_alert=True)
|
||
await _refresh_markup()
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_bot_config_setting(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
state: FSMContext,
|
||
):
|
||
parts = callback.data.split(":", 4)
|
||
group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY
|
||
try:
|
||
category_page = max(1, int(parts[2])) if len(parts) > 2 else 1
|
||
except ValueError:
|
||
category_page = 1
|
||
try:
|
||
settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1
|
||
except ValueError:
|
||
settings_page = 1
|
||
token = parts[4] if len(parts) > 4 else ""
|
||
try:
|
||
key = bot_configuration_service.resolve_callback_token(token)
|
||
except KeyError:
|
||
await callback.answer("Эта настройка больше недоступна", show_alert=True)
|
||
return
|
||
text = _render_setting_text(key)
|
||
keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page)
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
await _store_setting_context(
|
||
state,
|
||
key=key,
|
||
group_key=group_key,
|
||
category_page=category_page,
|
||
settings_page=settings_page,
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def start_edit_setting(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
state: FSMContext,
|
||
):
|
||
parts = callback.data.split(":", 4)
|
||
group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY
|
||
try:
|
||
category_page = max(1, int(parts[2])) if len(parts) > 2 else 1
|
||
except ValueError:
|
||
category_page = 1
|
||
try:
|
||
settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1
|
||
except ValueError:
|
||
settings_page = 1
|
||
token = parts[4] if len(parts) > 4 else ""
|
||
try:
|
||
key = bot_configuration_service.resolve_callback_token(token)
|
||
except KeyError:
|
||
await callback.answer("Эта настройка больше недоступна", show_alert=True)
|
||
return
|
||
definition = bot_configuration_service.get_definition(key)
|
||
|
||
summary = bot_configuration_service.get_setting_summary(key)
|
||
texts = get_texts(db_user.language)
|
||
|
||
instructions = [
|
||
"✏️ <b>Редактирование настройки</b>",
|
||
f"Название: {summary['name']}",
|
||
f"Ключ: <code>{summary['key']}</code>",
|
||
f"Тип: {summary['type']}",
|
||
f"Текущее значение: {summary['current']}",
|
||
"\nОтправьте новое значение сообщением.",
|
||
]
|
||
|
||
if definition.is_optional:
|
||
instructions.append("Отправьте 'none' или оставьте пустым для сброса на значение по умолчанию.")
|
||
|
||
instructions.append("Для отмены отправьте 'cancel'.")
|
||
|
||
await callback.message.edit_text(
|
||
"\n".join(instructions),
|
||
reply_markup=types.InlineKeyboardMarkup(
|
||
inline_keyboard=[
|
||
[
|
||
types.InlineKeyboardButton(
|
||
text=texts.BACK,
|
||
callback_data=(
|
||
f"botcfg_setting:{group_key}:{category_page}:{settings_page}:{token}"
|
||
),
|
||
)
|
||
]
|
||
]
|
||
),
|
||
)
|
||
|
||
await _store_setting_context(
|
||
state,
|
||
key=key,
|
||
group_key=group_key,
|
||
category_page=category_page,
|
||
settings_page=settings_page,
|
||
)
|
||
await state.set_state(BotConfigStates.waiting_for_value)
|
||
await callback.answer()
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def handle_edit_setting(
|
||
message: types.Message,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
state: FSMContext,
|
||
):
|
||
data = await state.get_data()
|
||
key = data.get("setting_key")
|
||
group_key = data.get("setting_group_key", CATEGORY_FALLBACK_KEY)
|
||
category_page = data.get("setting_category_page", 1)
|
||
settings_page = data.get("setting_settings_page", 1)
|
||
|
||
if not key:
|
||
await message.answer("Не удалось определить редактируемую настройку. Попробуйте снова.")
|
||
await state.clear()
|
||
return
|
||
|
||
try:
|
||
value = bot_configuration_service.parse_user_value(key, message.text or "")
|
||
except ValueError as error:
|
||
await message.answer(f"⚠️ {error}")
|
||
return
|
||
|
||
await bot_configuration_service.set_value(db, key, value)
|
||
await db.commit()
|
||
|
||
text = _render_setting_text(key)
|
||
keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page)
|
||
await message.answer("✅ Настройка обновлена")
|
||
await message.answer(text, reply_markup=keyboard)
|
||
await state.clear()
|
||
await _store_setting_context(
|
||
state,
|
||
key=key,
|
||
group_key=group_key,
|
||
category_page=category_page,
|
||
settings_page=settings_page,
|
||
)
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def handle_direct_setting_input(
|
||
message: types.Message,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
state: FSMContext,
|
||
):
|
||
data = await state.get_data()
|
||
|
||
key = data.get("setting_key")
|
||
group_key = data.get("setting_group_key", CATEGORY_FALLBACK_KEY)
|
||
category_page = int(data.get("setting_category_page", 1) or 1)
|
||
settings_page = int(data.get("setting_settings_page", 1) or 1)
|
||
|
||
if not key:
|
||
return
|
||
|
||
try:
|
||
value = bot_configuration_service.parse_user_value(key, message.text or "")
|
||
except ValueError as error:
|
||
await message.answer(f"⚠️ {error}")
|
||
return
|
||
|
||
await bot_configuration_service.set_value(db, key, value)
|
||
await db.commit()
|
||
|
||
text = _render_setting_text(key)
|
||
keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page)
|
||
await message.answer("✅ Настройка обновлена")
|
||
await message.answer(text, reply_markup=keyboard)
|
||
|
||
await state.clear()
|
||
await _store_setting_context(
|
||
state,
|
||
key=key,
|
||
group_key=group_key,
|
||
category_page=category_page,
|
||
settings_page=settings_page,
|
||
)
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def reset_setting(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
state: FSMContext,
|
||
):
|
||
parts = callback.data.split(":", 4)
|
||
group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY
|
||
try:
|
||
category_page = max(1, int(parts[2])) if len(parts) > 2 else 1
|
||
except ValueError:
|
||
category_page = 1
|
||
try:
|
||
settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1
|
||
except ValueError:
|
||
settings_page = 1
|
||
token = parts[4] if len(parts) > 4 else ""
|
||
try:
|
||
key = bot_configuration_service.resolve_callback_token(token)
|
||
except KeyError:
|
||
await callback.answer("Эта настройка больше недоступна", show_alert=True)
|
||
return
|
||
await bot_configuration_service.reset_value(db, key)
|
||
await db.commit()
|
||
|
||
text = _render_setting_text(key)
|
||
keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page)
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
await _store_setting_context(
|
||
state,
|
||
key=key,
|
||
group_key=group_key,
|
||
category_page=category_page,
|
||
settings_page=settings_page,
|
||
)
|
||
await callback.answer("Сброшено к значению по умолчанию")
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def toggle_setting(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
state: FSMContext,
|
||
):
|
||
parts = callback.data.split(":", 4)
|
||
group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY
|
||
try:
|
||
category_page = max(1, int(parts[2])) if len(parts) > 2 else 1
|
||
except ValueError:
|
||
category_page = 1
|
||
try:
|
||
settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1
|
||
except ValueError:
|
||
settings_page = 1
|
||
token = parts[4] if len(parts) > 4 else ""
|
||
try:
|
||
key = bot_configuration_service.resolve_callback_token(token)
|
||
except KeyError:
|
||
await callback.answer("Эта настройка больше недоступна", show_alert=True)
|
||
return
|
||
current = bot_configuration_service.get_current_value(key)
|
||
new_value = not bool(current)
|
||
await bot_configuration_service.set_value(db, key, new_value)
|
||
await db.commit()
|
||
|
||
text = _render_setting_text(key)
|
||
keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page)
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
await _store_setting_context(
|
||
state,
|
||
key=key,
|
||
group_key=group_key,
|
||
category_page=category_page,
|
||
settings_page=settings_page,
|
||
)
|
||
await callback.answer("Обновлено")
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def apply_setting_choice(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession,
|
||
state: FSMContext,
|
||
):
|
||
parts = callback.data.split(":", 5)
|
||
group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY
|
||
try:
|
||
category_page = max(1, int(parts[2])) if len(parts) > 2 else 1
|
||
except ValueError:
|
||
category_page = 1
|
||
try:
|
||
settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1
|
||
except ValueError:
|
||
settings_page = 1
|
||
token = parts[4] if len(parts) > 4 else ""
|
||
choice_token = parts[5] if len(parts) > 5 else ""
|
||
|
||
try:
|
||
key = bot_configuration_service.resolve_callback_token(token)
|
||
except KeyError:
|
||
await callback.answer("Эта настройка больше недоступна", show_alert=True)
|
||
return
|
||
|
||
try:
|
||
value = bot_configuration_service.resolve_choice_token(key, choice_token)
|
||
except KeyError:
|
||
await callback.answer("Это значение больше недоступно", show_alert=True)
|
||
return
|
||
|
||
await bot_configuration_service.set_value(db, key, value)
|
||
await db.commit()
|
||
|
||
text = _render_setting_text(key)
|
||
keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page)
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
await _store_setting_context(
|
||
state,
|
||
key=key,
|
||
group_key=group_key,
|
||
category_page=category_page,
|
||
settings_page=settings_page,
|
||
)
|
||
await callback.answer("Значение обновлено")
|
||
|
||
|
||
def register_handlers(dp: Dispatcher) -> None:
|
||
dp.callback_query.register(
|
||
show_bot_config_menu,
|
||
F.data == "admin_bot_config",
|
||
)
|
||
dp.callback_query.register(
|
||
show_bot_config_group,
|
||
F.data.startswith("botcfg_group:") & (~F.data.endswith(":noop")),
|
||
)
|
||
dp.callback_query.register(
|
||
show_bot_config_category,
|
||
F.data.startswith("botcfg_cat:"),
|
||
)
|
||
dp.callback_query.register(
|
||
test_remnawave_connection,
|
||
F.data.startswith("botcfg_test_remnawave:"),
|
||
)
|
||
dp.callback_query.register(
|
||
test_payment_provider,
|
||
F.data.startswith("botcfg_test_payment:"),
|
||
)
|
||
dp.callback_query.register(
|
||
show_bot_config_setting,
|
||
F.data.startswith("botcfg_setting:"),
|
||
)
|
||
dp.callback_query.register(
|
||
start_edit_setting,
|
||
F.data.startswith("botcfg_edit:"),
|
||
)
|
||
dp.callback_query.register(
|
||
reset_setting,
|
||
F.data.startswith("botcfg_reset:"),
|
||
)
|
||
dp.callback_query.register(
|
||
toggle_setting,
|
||
F.data.startswith("botcfg_toggle:"),
|
||
)
|
||
dp.callback_query.register(
|
||
apply_setting_choice,
|
||
F.data.startswith("botcfg_choice:"),
|
||
)
|
||
dp.message.register(
|
||
handle_direct_setting_input,
|
||
StateFilter(None),
|
||
F.text,
|
||
BotConfigInputFilter(),
|
||
)
|
||
dp.message.register(
|
||
handle_edit_setting,
|
||
BotConfigStates.waiting_for_value,
|
||
)
|
||
|