diff --git a/handlers/admin/admin_users.py b/handlers/admin/admin_users.py
index 41abeece..38841988 100644
--- a/handlers/admin/admin_users.py
+++ b/handlers/admin/admin_users.py
@@ -6,20 +6,20 @@ from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery
-from filters.admin import IsAdminFilter
from config import TOTAL_GB
from database import delete_user_data, get_client_id_by_email, get_servers_from_db, restore_trial, update_key_expiry
+from filters.admin import IsAdminFilter
from handlers.keys.key_utils import (
delete_key_from_cluster,
delete_key_from_db,
- renew_key_in_cluster,
+ renew_key_in_cluster, update_subscription,
)
from handlers.utils import sanitize_key_name
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
from keyboards.admin.users_kb import build_user_edit_kb, build_key_edit_kb, build_key_delete_kb, \
build_user_delete_kb, AdminUserEditorCallback, build_editor_kb, build_users_balance_kb, \
- build_users_balance_change_kb
+ build_users_balance_change_kb, build_user_key_kb
from logger import logger
router = Router()
@@ -203,6 +203,7 @@ async def handle_trial_restore(
session: Any
):
tg_id = callback_data.tg_id
+
await restore_trial(tg_id, session)
await callback_query.message.edit_text(
text="✅ Триал успешно восстановлен!",
@@ -406,9 +407,11 @@ async def handle_change_expiry(
state: FSMContext
):
email = callback_data.data
+
await callback_query.message.edit_text(
text=f"✍️ Введите новое время истечения для ключа {email} в формате YYYY-MM-DD HH:MM:SS:"
)
+
await state.update_data(tg_id=callback_data.tg_id, email=email)
await state.set_state(UserEditorState.waiting_for_expiry_time)
@@ -490,6 +493,29 @@ async def handle_expiry_time_input(
await state.clear()
+@router.callback_query(
+ AdminUserEditorCallback.filter(F.action == "users_update_key"),
+ IsAdminFilter()
+)
+async def handle_update_key(
+ callback_query: CallbackQuery,
+ callback_data: AdminUserEditorCallback,
+ session: Any
+):
+ tg_id = callback_data.tg_id
+ email = callback_data.data
+
+ try:
+ await update_subscription(tg_id, email, session)
+ await handle_key_edit(callback_query, callback_data, session)
+ except Exception as e:
+ logger.error(f"Ошибка при обновлении ключа {email} администратором: {e}")
+ await callback_query.message.answer(
+ text=f"❗ Произошла ошибка при обновлении ключа: {e}",
+ reply_markup=build_user_key_kb(tg_id, email)
+ )
+
+
@router.callback_query(
AdminUserEditorCallback.filter(F.action == "users_delete_key"),
IsAdminFilter()
diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py
index 47f9401c..6b78e266 100644
--- a/handlers/keys/key_utils.py
+++ b/handlers/keys/key_utils.py
@@ -1,10 +1,12 @@
import asyncio
+from typing import Any
from py3xui import AsyncApi
from client import add_client, delete_client, extend_client_key
-from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, SUPERNODE, TOTAL_GB
-from database import get_servers_from_db
+from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, SUPERNODE, TOTAL_GB, PUBLIC_LINK
+from database import get_servers_from_db, store_key
+from handlers.utils import get_least_loaded_cluster
from logger import logger
@@ -73,8 +75,6 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
raise e
-
-
async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, total_gb):
try:
servers = await get_servers_from_db()
@@ -227,3 +227,53 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id
f"Ошибка при обновлении ключа на серверах кластера {cluster_id} для {client_id}: {e}"
)
raise e
+
+
+async def update_subscription(tg_id: int, email: str, session: Any) -> None:
+ record = await session.fetchrow(
+ """
+ SELECT k.key, k.expiry_time, k.email, k.server_id, k.client_id
+ FROM keys k
+ WHERE k.tg_id = $1 AND k.email = $2
+ """,
+ tg_id,
+ email,
+ )
+
+ if not record:
+ raise ValueError(f"The key {email} does not exist in database")
+
+ expiry_time = record["expiry_time"]
+ client_id = record["client_id"]
+ public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
+
+ await session.execute(
+ """
+ DELETE FROM keys
+ WHERE tg_id = $1 AND email = $2
+ """,
+ tg_id,
+ email,
+ )
+
+ least_loaded_cluster_id = await get_least_loaded_cluster()
+
+ await asyncio.gather(
+ update_key_on_cluster(
+ tg_id,
+ client_id,
+ email,
+ expiry_time,
+ least_loaded_cluster_id,
+ )
+ )
+
+ await store_key(
+ tg_id,
+ client_id,
+ email,
+ expiry_time,
+ public_link,
+ server_id=least_loaded_cluster_id,
+ session=session,
+ )
diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py
index 8a270a7d..15ab61d3 100644
--- a/handlers/keys/keys.py
+++ b/handlers/keys/keys.py
@@ -7,6 +7,7 @@ from typing import Any
from aiogram import F, Router, types
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
+from handlers.payments.yookassa_pay import process_custom_amount_input
from bot import bot
from config import (
@@ -26,7 +27,6 @@ from database import (
get_balance,
get_servers_from_db,
save_temporary_data,
- store_key,
update_balance,
update_key_expiry,
)
@@ -42,10 +42,9 @@ from handlers.keys.key_utils import (
delete_key_from_cluster,
delete_key_from_db,
renew_key_in_cluster,
- update_key_on_cluster,
+ update_subscription,
)
from handlers.payments.robokassa_pay import handle_custom_amount_input
-from handlers.payments.yookassa_pay import process_custom_amount_input
from handlers.texts import (
DISCOUNTS,
KEY_NOT_FOUND_MSG,
@@ -53,7 +52,7 @@ from handlers.texts import (
SUCCESS_RENEWAL_MSG,
key_message,
)
-from handlers.utils import get_least_loaded_cluster, handle_error
+from handlers.utils import handle_error
from logger import logger
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
@@ -64,7 +63,7 @@ router = Router()
@router.callback_query(F.data == "view_keys")
@router.message(F.text == "/subs")
async def process_callback_or_message_view_keys(
- callback_query_or_message: types.Message | types.CallbackQuery, session: Any
+ callback_query_or_message: types.Message | types.CallbackQuery, session: Any
):
if isinstance(callback_query_or_message, types.CallbackQuery):
chat_id = callback_query_or_message.message.chat.id
@@ -124,7 +123,7 @@ def build_keys_response(records):
async def send_with_optional_image(
- send_message, send_photo, image_path, text, keyboard
+ send_message, send_photo, image_path, text, keyboard
):
"""
Отправляет сообщение с изображением, если файл существует. В противном случае отправляет только текст.
@@ -268,67 +267,16 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
@router.callback_query(F.data.startswith("update_subscription|"))
async def process_callback_update_subscription(
- callback_query: types.CallbackQuery, session: Any
+ callback_query: types.CallbackQuery, session: Any
):
tg_id = callback_query.message.chat.id
email = callback_query.data.split("|")[1]
+
try:
- record = await session.fetchrow(
- """
- SELECT k.key, k.expiry_time, k.email, k.server_id, k.client_id
- FROM keys k
- WHERE k.tg_id = $1 AND k.email = $2
- """,
- tg_id,
- email,
- )
-
- if record:
- expiry_time = record["expiry_time"]
- client_id = record["client_id"]
- public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
-
- try:
- await session.execute(
- """
- DELETE FROM keys
- WHERE tg_id = $1 AND email = $2
- """,
- tg_id,
- email,
- )
- except Exception as delete_error:
- await callback_query.message.answer(
- f"Ошибка при удалении старой подписки: {delete_error}",
- )
- return
-
- least_loaded_cluster_id = await get_least_loaded_cluster()
-
- await asyncio.gather(
- update_key_on_cluster(
- tg_id,
- client_id,
- email,
- expiry_time,
- least_loaded_cluster_id,
- )
- )
-
- await store_key(
- tg_id,
- client_id,
- email,
- expiry_time,
- public_link,
- server_id=least_loaded_cluster_id,
- session=session,
- )
-
- await process_callback_view_key(callback_query, session)
- else:
- await callback_query.message.answer("Ключ не найден в базе данных.")
+ await update_subscription(tg_id, email, session)
+ await process_callback_view_key(callback_query, session)
except Exception as e:
+ logger.error(f"Ошибка при обновлении ключа {email} пользователем: {e}")
await handle_error(
tg_id, callback_query, f"Ошибка при обновлении подписки: {e}"
)
@@ -436,7 +384,7 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery, sessio
@router.callback_query(F.data.startswith("confirm_delete|"))
async def process_callback_confirm_delete(
- callback_query: types.CallbackQuery, session: Any
+ callback_query: types.CallbackQuery, session: Any
):
email = callback_query.data.split("|")[1]
try:
@@ -523,7 +471,8 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery, sessi
if balance < cost:
required_amount = cost - balance
- logger.info(f"[RENEW] Пользователю {tg_id} не хватает {required_amount}₽. Запуск доплаты через {USE_NEW_PAYMENT_FLOW}")
+ logger.info(
+ f"[RENEW] Пользователю {tg_id} не хватает {required_amount}₽. Запуск доплаты через {USE_NEW_PAYMENT_FLOW}")
await save_temporary_data(
session,
@@ -604,4 +553,3 @@ async def complete_key_renewal(tg_id, client_id, email, new_expiry_time, total_g
logger.info(f"[RENEW] Ключ {client_id} успешно продлён на {plan} мес. для пользователя {tg_id}.")
await renew_key_on_servers()
-
diff --git a/keyboards/admin/users_kb.py b/keyboards/admin/users_kb.py
index 8d01bde4..22133aaa 100644
--- a/keyboards/admin/users_kb.py
+++ b/keyboards/admin/users_kb.py
@@ -141,10 +141,24 @@ def build_user_delete_kb(tg_id: int):
return builder.as_markup()
+def build_user_key_kb(tg_id: int, email: str) -> InlineKeyboardMarkup:
+ builder = InlineKeyboardBuilder()
+ builder.button(
+ text=f"🔙 Назад",
+ callback_data=AdminUserEditorCallback(
+ action="users_key_edit",
+ tg_id=tg_id,
+ data=email
+ ).pack()
+ )
+ builder.adjust(1)
+ return builder.as_markup()
+
+
def build_key_edit_kb(key_details: dict, email: str) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.button(
- text="⏳ Изменить время истечения",
+ text="⏳ Время истечения",
callback_data=AdminUserEditorCallback(
action="users_change_expiry",
data=email,
@@ -152,7 +166,15 @@ def build_key_edit_kb(key_details: dict, email: str) -> InlineKeyboardMarkup:
).pack()
)
builder.button(
- text="❌ Удалить ключ",
+ text="🔄 Перевыпустить",
+ callback_data=AdminUserEditorCallback(
+ action="users_update_key",
+ data=email,
+ tg_id=key_details["tg_id"]
+ ).pack()
+ )
+ builder.button(
+ text="❌ Удалить",
callback_data=AdminUserEditorCallback(
action="users_delete_key",
data=email,