redesign/bug fixes/admin trial
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import py3xui
|
||||
from loguru import logger
|
||||
|
||||
from config import TOTAL_GB
|
||||
|
||||
|
||||
async def add_client(
|
||||
xui,
|
||||
@@ -64,9 +66,14 @@ async def extend_client_key(xui, email: str, new_expiry_time: int, client_id: st
|
||||
logger.info(
|
||||
f"Обновление ключа клиента {client.email} с ID {client.id} до нового времени: {new_expiry_time}"
|
||||
)
|
||||
|
||||
client.id = client_id
|
||||
client.expiry_time = new_expiry_time
|
||||
|
||||
|
||||
if TOTAL_GB > 0:
|
||||
client.total_gb = TOTAL_GB
|
||||
logger.info(f"Установлен объем трафика для клиента {client.email}: {TOTAL_GB} ГБ")
|
||||
|
||||
await xui.client.update(client.id, client)
|
||||
logger.info(
|
||||
f"Ключ клиента {client.email} успешно продлён до {new_expiry_time}."
|
||||
@@ -76,6 +83,7 @@ async def extend_client_key(xui, email: str, new_expiry_time: int, client_id: st
|
||||
logger.error(f"Ошибка при обновлении клиента с email {email}: {e}")
|
||||
|
||||
|
||||
|
||||
async def delete_client(
|
||||
xui,
|
||||
email: str,
|
||||
|
||||
+15
-1
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
|
||||
from loguru import logger
|
||||
import asyncpg
|
||||
|
||||
from config import DATABASE_URL
|
||||
@@ -74,6 +74,20 @@ async def init_db():
|
||||
|
||||
await conn.close()
|
||||
|
||||
async def restore_trial(tg_id: int):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
await conn.execute(
|
||||
"UPDATE connections SET trial = 0 WHERE tg_id = $1", tg_id
|
||||
)
|
||||
except Exception as e:
|
||||
|
||||
logger.error(f"Ошибка при установке значения триала: {e}")
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
|
||||
|
||||
|
||||
async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
|
||||
@@ -3,7 +3,7 @@ from aiogram import Router, types
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.filters.admin import IsAdminFilter
|
||||
from loguru import logger
|
||||
|
||||
from bot import bot
|
||||
|
||||
@@ -3,17 +3,18 @@ from datetime import datetime
|
||||
|
||||
import asyncpg
|
||||
from aiogram import F, Router, types
|
||||
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.filters.admin import IsAdminFilter
|
||||
|
||||
from backup import backup_database
|
||||
from bot import bot
|
||||
from config import DATABASE_URL
|
||||
from handlers.commands import send_message_to_all_clients
|
||||
from config import DATABASE_URL, ADMIN_ID
|
||||
from handlers.admin.admin_commands import send_message_to_all_clients
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.filters.admin import IsAdminFilter
|
||||
from loguru import logger
|
||||
|
||||
from bot import bot
|
||||
from config import DATABASE_URL, SERVERS
|
||||
from database import get_client_id_by_email, update_key_expiry
|
||||
from database import get_client_id_by_email, update_key_expiry, restore_trial
|
||||
from handlers.admin.admin_panel import back_to_admin_menu
|
||||
from handlers.keys.key_utils import delete_key_from_server, renew_server_key
|
||||
from handlers.utils import sanitize_key_name
|
||||
@@ -55,7 +55,6 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
# Добавляем кнопки ключей
|
||||
for (email,) in key_records:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
@@ -63,14 +62,18 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
||||
)
|
||||
)
|
||||
|
||||
# Кнопка изменения баланса
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}"
|
||||
)
|
||||
)
|
||||
|
||||
# Кнопка возврата
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}"
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_user_editor")
|
||||
)
|
||||
@@ -90,6 +93,23 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
||||
await conn.close()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("restore_trial_"), IsAdminFilter())
|
||||
async def handle_restore_trial(callback_query: types.CallbackQuery):
|
||||
tg_id = int(callback_query.data.split("_")[2])
|
||||
|
||||
await restore_trial(tg_id)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад в меню администратора", callback_data="back_to_user_editor")
|
||||
)
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
"✅ Триал успешно восстановлен.",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("change_balance_"), IsAdminFilter())
|
||||
async def process_balance_change(callback_query: CallbackQuery, state: FSMContext):
|
||||
tg_id = int(callback_query.data.split("_")[2])
|
||||
@@ -239,7 +259,15 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
|
||||
)
|
||||
|
||||
if not user_records:
|
||||
await message.reply("🚫 Пользователь с указанным именем ключа не найден.")
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад в меню администратора", callback_data="back_to_user_editor")
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
"🚫 Пользователь с указанным именем ключа не найден.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from loguru import logger
|
||||
|
||||
import asyncpg
|
||||
from aiogram import F, Router
|
||||
@@ -9,7 +10,7 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message
|
||||
|
||||
from bot import bot, dp
|
||||
from config import APP_URL, DATABASE_URL, PUBLIC_LINK, SERVERS
|
||||
from config import DATABASE_URL, PUBLIC_LINK, SERVERS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, CONNECT_ANDROID, CONNECT_IOS
|
||||
from database import add_connection, get_balance, store_key, update_balance
|
||||
from handlers.instructions.instructions import send_instructions
|
||||
from handlers.keys.key_utils import create_key_on_server
|
||||
@@ -92,6 +93,8 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext):
|
||||
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
logger.info(f"User {tg_id} confirmed creation of a new key.")
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < RENEWAL_PLANS["1"]["price"]:
|
||||
replenish_button = InlineKeyboardButton(
|
||||
@@ -102,33 +105,38 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
"🔑 Пожалуйста, введите имя подключаемого устройства:"
|
||||
)
|
||||
logger.info(f"Balance for user {tg_id} is sufficient. Asking for device name.")
|
||||
|
||||
await callback_query.message.edit_text("🔑 Пожалуйста, введите имя подключаемого устройства:")
|
||||
await state.set_state(Form.waiting_for_key_name)
|
||||
logger.info(f"State set to waiting_for_key_name for user {tg_id}")
|
||||
await state.update_data(creating_new_key=True)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@dp.callback_query(F.data == "cancel_create_key")
|
||||
async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext):
|
||||
await process_callback_view_profile(callback_query, state)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(Form.waiting_for_key_name)
|
||||
async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
tg_id = message.from_user.id
|
||||
key_name = sanitize_key_name(message.text)
|
||||
|
||||
logger.info(f"User {tg_id} is attempting to create a key with the name: {key_name}")
|
||||
|
||||
if not key_name:
|
||||
await message.bot.send_message(
|
||||
tg_id, "📝 Пожалуйста, назовите устройство на английском языке."
|
||||
)
|
||||
logger.warning(f"User {tg_id} entered an invalid key name: {key_name}")
|
||||
return
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
logger.info(f"Checking if key name '{key_name}' already exists in the database.")
|
||||
existing_key = await conn.fetchrow(
|
||||
"SELECT * FROM keys WHERE email = $1", key_name.lower()
|
||||
)
|
||||
@@ -137,6 +145,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
tg_id,
|
||||
"❌ Упс! Это имя уже используется. Выберите другое уникальное название для ключа.",
|
||||
)
|
||||
logger.warning(f"Key name '{key_name}' already exists in the database for user {tg_id}.")
|
||||
await state.set_state(Form.waiting_for_key_name)
|
||||
return
|
||||
finally:
|
||||
@@ -149,6 +158,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
logger.info(f"Checking trial status for user {tg_id}.")
|
||||
existing_connection = await conn.fetchrow(
|
||||
"SELECT trial FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
@@ -159,6 +169,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
if trial_status == 0:
|
||||
expiry_time = current_time + timedelta(days=1, hours=3)
|
||||
logger.info(f"Assigned 1-day trial to user {tg_id}.")
|
||||
else:
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < RENEWAL_PLANS["1"]["price"]:
|
||||
@@ -171,32 +182,36 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
"💳 Недостаточно средств для создания подписки на новое устройство. Пополните баланс в личном кабинете.",
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
logger.warning(f"User {tg_id} has insufficient funds for key creation.")
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
|
||||
expiry_time = current_time + timedelta(days=30, hours=3)
|
||||
logger.info(f"User {tg_id} balance deducted for key creation.")
|
||||
|
||||
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
||||
public_link = f"{PUBLIC_LINK}{email}"
|
||||
|
||||
logger.info(f"Generated public link for the key: {public_link}")
|
||||
|
||||
button_profile = InlineKeyboardButton(
|
||||
text="👤 Личный кабинет", callback_data="view_profile"
|
||||
)
|
||||
button_iphone = InlineKeyboardButton(
|
||||
text="🍏 Подключить", url=f"{APP_URL}/?url=v2raytun://import/{public_link}"
|
||||
text="🍏 Подключить", url=f"{CONNECT_IOS}{public_link}"
|
||||
)
|
||||
button_android = InlineKeyboardButton(
|
||||
text="🤖 Подключить",
|
||||
url=f"{APP_URL}/?url=v2raytun://import-sub?url={public_link}",
|
||||
url=f"{CONNECT_ANDROID}{public_link}",
|
||||
)
|
||||
|
||||
button_download_ios = InlineKeyboardButton(
|
||||
text="🍏 Скачать", url="https://apps.apple.com/ru/app/v2raytun/id6476628951"
|
||||
text="🍏 Скачать", url=DOWNLOAD_IOS
|
||||
)
|
||||
button_download_android = InlineKeyboardButton(
|
||||
text="🤖 Скачать",
|
||||
url="https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru",
|
||||
url=DOWNLOAD_ANDROID,
|
||||
)
|
||||
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
@@ -211,6 +226,8 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
days = remaining_time.days
|
||||
key_message = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅")
|
||||
|
||||
logger.info(f"Sending key message to user {tg_id} with the public link.")
|
||||
|
||||
await message.bot.send_message(
|
||||
tg_id, key_message, parse_mode="HTML", reply_markup=keyboard
|
||||
)
|
||||
@@ -230,6 +247,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
logger.info(f"Updating trial status for user {tg_id} in the database.")
|
||||
existing_connection = await conn.fetchrow(
|
||||
"SELECT * FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
@@ -242,16 +260,19 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
logger.info(f"Storing key for user {tg_id} in the database.")
|
||||
await store_key(
|
||||
tg_id, client_id, email, expiry_timestamp, public_link, "all_servers"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error while creating the key for user {tg_id}: {e}")
|
||||
await message.bot.send_message(tg_id, f"❌ Ошибка при создании ключа: {e}")
|
||||
|
||||
await state.clear()
|
||||
|
||||
|
||||
|
||||
@dp.callback_query(F.data == "instructions")
|
||||
async def handle_instructions(callback_query: CallbackQuery):
|
||||
await send_instructions(callback_query)
|
||||
|
||||
+55
-23
@@ -9,7 +9,7 @@ from aiogram.types import BufferedInputFile
|
||||
from loguru import logger
|
||||
|
||||
from bot import bot
|
||||
from config import APP_URL, DATABASE_URL, PUBLIC_LINK, SERVERS
|
||||
from config import DATABASE_URL, PUBLIC_LINK, SERVERS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, CONNECT_ANDROID, CONNECT_IOS
|
||||
from database import delete_key, get_balance, store_key, update_balance, update_key_expiry
|
||||
from handlers.keys.key_utils import delete_key_from_db, delete_key_from_server, renew_server_key, update_key_on_server
|
||||
from handlers.texts import INSUFFICIENT_FUNDS_MSG, KEY_NOT_FOUND_MSG, NO_KEYS, PLAN_SELECTION_MSG, RENEWAL_PLANS, SUCCESS_RENEWAL_MSG, key_message
|
||||
@@ -56,16 +56,32 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
"<i>👆 Выберите устройство для управления подпиской:</i>"
|
||||
)
|
||||
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
image_path = os.path.join(os.path.dirname(__file__), "pic_keys.jpg")
|
||||
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
chat_id=tg_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"),
|
||||
caption=response_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=inline_keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text=response_message,
|
||||
reply_markup=inline_keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text=response_message,
|
||||
reply_markup=inline_keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
else:
|
||||
response_message = NO_KEYS
|
||||
create_key_button = types.InlineKeyboardButton(
|
||||
@@ -79,16 +95,31 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
inline_keyboard=[[create_key_button], [back_button]]
|
||||
)
|
||||
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text=response_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
image_path = os.path.join(os.path.dirname(__file__), "pic_keys.jpg")
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
chat_id=tg_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"),
|
||||
caption=response_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text=response_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
@@ -99,6 +130,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("view_key|"))
|
||||
async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
@@ -154,19 +186,19 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
|
||||
download_android_button = types.InlineKeyboardButton(
|
||||
text="🤖 Скачать",
|
||||
url="https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru",
|
||||
url=DOWNLOAD_ANDROID,
|
||||
)
|
||||
download_iphone_button = types.InlineKeyboardButton(
|
||||
text="🍏 Скачать",
|
||||
url="https://apps.apple.com/ru/app/v2raytun/id6476628951",
|
||||
url=DOWNLOAD_IOS,
|
||||
)
|
||||
|
||||
connect_iphone_button = types.InlineKeyboardButton(
|
||||
text="🍏 Подключить", url=f"{APP_URL}/?url=v2raytun://import/{key}"
|
||||
text="🍏 Подключить", url=f"{CONNECT_IOS}{key}"
|
||||
)
|
||||
connect_android_button = types.InlineKeyboardButton(
|
||||
text="🤖 Подключить",
|
||||
url=f"{APP_URL}/?url=v2raytun://import-sub?url={key}",
|
||||
url=f"{CONNECT_ANDROID}{key}",
|
||||
)
|
||||
|
||||
renew_button = types.InlineKeyboardButton(
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
+26
-46
@@ -217,13 +217,7 @@ async def notify_24h_keys(
|
||||
async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: float):
|
||||
logger.info("Проверка истекших ключей...")
|
||||
|
||||
current_time = datetime.utcnow().timestamp() * 1000
|
||||
adjusted_current_time = current_time + (3 * 60 * 60 * 1000)
|
||||
|
||||
logger.info(
|
||||
f"Текущее время: {current_time}, Скорректированное текущее время: {adjusted_current_time}"
|
||||
)
|
||||
|
||||
adjusted_current_time = current_time + (3 * 60 * 60 * 1000)
|
||||
expiring_keys = await conn.fetch(
|
||||
"""
|
||||
SELECT tg_id, client_id, expiry_time, email FROM keys
|
||||
@@ -231,7 +225,6 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
""",
|
||||
adjusted_current_time,
|
||||
)
|
||||
|
||||
logger.info(f"Найдено {len(expiring_keys)} истекающих ключей.")
|
||||
|
||||
for record in expiring_keys:
|
||||
@@ -239,17 +232,17 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
client_id = record["client_id"]
|
||||
email = record["email"]
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
expiry_time = record["expiry_time"]
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
logger.info(
|
||||
f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}"
|
||||
)
|
||||
|
||||
message_expired = (
|
||||
"❌ Ваша подписка {email} истекла и была удалена!\n\n"
|
||||
f"❌ Ваша подписка {email} истекла и была удалена!\n\n"
|
||||
"🔍 Перейдите в профиль для создания нового ключа.\n"
|
||||
"💡 Не откладывайте подключение VPN!"
|
||||
)
|
||||
@@ -265,9 +258,6 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
(datetime.utcnow() + timedelta(days=30)).timestamp() * 1000
|
||||
)
|
||||
await update_key_expiry(client_id, new_expiry_time)
|
||||
logger.info(
|
||||
f"Ключ для клиента {tg_id} продлен до {datetime.utcfromtimestamp(new_expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}."
|
||||
)
|
||||
|
||||
all_success = True
|
||||
for server_id in SERVERS:
|
||||
@@ -281,48 +271,38 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
)
|
||||
if not success:
|
||||
all_success = False
|
||||
logger.error(
|
||||
f"Не удалось продлить ключ для пользователя {tg_id} на сервере {server_id}."
|
||||
)
|
||||
logger.error(f"Не удалось продлить ключ для пользователя {tg_id} на сервере {server_id}.")
|
||||
|
||||
if all_success:
|
||||
try:
|
||||
await bot.send_message(
|
||||
tg_id, KEY_RENEWED, reply_markup=keyboard
|
||||
)
|
||||
logger.info(
|
||||
f"Ключ для пользователя {tg_id} успешно продлен на месяц на всех серверах."
|
||||
)
|
||||
await bot.send_message(tg_id, KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(f"Ключ для пользователя {tg_id} успешно продлен.")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при отправке уведомления о продлении ключа пользователю {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")
|
||||
|
||||
else:
|
||||
try:
|
||||
await bot.send_message(
|
||||
tg_id, message_expired, reply_markup=keyboard
|
||||
)
|
||||
await delete_key(client_id)
|
||||
|
||||
for server_id in SERVERS:
|
||||
xui = AsyncApi(
|
||||
SERVERS[server_id]["API_URL"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
success = await delete_client(xui, email, client_id)
|
||||
if success:
|
||||
logger.info(
|
||||
f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}."
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}."
|
||||
)
|
||||
await bot.send_message(tg_id, message_expired, reply_markup=keyboard)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении ключа для клиента {tg_id}: {e}")
|
||||
if "chat not found" in str(e):
|
||||
logger.warning(f"Чат для клиента {tg_id} не найден. Пропуск отправки сообщения.")
|
||||
|
||||
await delete_key(client_id)
|
||||
|
||||
for server_id in SERVERS:
|
||||
xui = AsyncApi(
|
||||
SERVERS[server_id]["API_URL"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
success = await delete_client(xui, email, client_id)
|
||||
if success:
|
||||
logger.info(f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}.")
|
||||
else:
|
||||
logger.error(f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}")
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiohttp import web
|
||||
from telegram import InlineKeyboardMarkup
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
|
||||
|
||||
from bot import bot
|
||||
from config import FREEKASSA_API_KEY, FREEKASSA_SHOP_ID
|
||||
|
||||
@@ -4,7 +4,8 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from loguru import logger
|
||||
from telegram import LabeledPrice
|
||||
|
||||
from aiogram.types import LabeledPrice
|
||||
|
||||
from bot import bot
|
||||
from config import RUB_TO_XTR
|
||||
|
||||
@@ -63,25 +63,25 @@ async def process_callback_pay_yookassa(
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for i in range(0, len(PAYMENT_OPTIONS), 2):
|
||||
if i + 1 < len(PAYMENT_OPTIONS):
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=PAYMENT_OPTIONS[i]["callback_data"],
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i + 1]["text"],
|
||||
callback_data=PAYMENT_OPTIONS[i + 1]["callback_data"],
|
||||
),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=PAYMENT_OPTIONS[i]["callback_data"],
|
||||
)
|
||||
for i in range(0, 4, 2):
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=PAYMENT_OPTIONS[i]["callback_data"],
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i + 1]["text"],
|
||||
callback_data=PAYMENT_OPTIONS[i + 1]["callback_data"],
|
||||
),
|
||||
)
|
||||
|
||||
for i in range(4, len(PAYMENT_OPTIONS)):
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=PAYMENT_OPTIONS[i]["callback_data"],
|
||||
)
|
||||
)
|
||||
|
||||
key_count = await get_key_count(tg_id)
|
||||
|
||||
@@ -107,6 +107,7 @@ async def process_callback_pay_yookassa(
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
|
||||
@router.callback_query(F.data == "back_to_profile")
|
||||
async def back_to_profile_handler(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
+32
-7
@@ -92,23 +92,48 @@ async def invite_handler(callback_query: types.CallbackQuery):
|
||||
|
||||
invite_message = invite_message_send(referral_link, referral_stats)
|
||||
|
||||
image_path = os.path.join(os.path.dirname(__file__), "pic_invite.jpg")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile")
|
||||
)
|
||||
|
||||
await callback_query.message.delete()
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=invite_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
try:
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
chat_id=chat_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_invite.jpg"),
|
||||
caption=invite_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=invite_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
except Exception as e:
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=f"❗️ Не удалось отправить сообщение. Техническая ошибка: {e}",
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
|
||||
|
||||
@router.callback_query(F.data == "view_profile")
|
||||
async def view_profile_handler(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await process_callback_view_profile(callback_query, state)
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ annotated-types==0.7.0
|
||||
async-timeout==4.0.3
|
||||
asyncpg==0.30.0
|
||||
attrs==24.2.0
|
||||
certifi==2024.8.30
|
||||
certifi
|
||||
charset-normalizer==3.4.0
|
||||
Deprecated==1.2.14
|
||||
distro==1.9.0
|
||||
@@ -17,8 +17,8 @@ magic-filter==1.0.12
|
||||
multidict==6.1.0
|
||||
netaddr==1.3.0
|
||||
propcache==0.2.0
|
||||
pydantic==2.9.2
|
||||
pydantic_core==2.23.4
|
||||
pydantic
|
||||
pydantic_core
|
||||
requests==2.32.3
|
||||
typing_extensions==4.12.2
|
||||
urllib3==2.2.3
|
||||
|
||||
Reference in New Issue
Block a user