Refactoring
This commit is contained in:
+2
-1
@@ -51,4 +51,5 @@ Thumbs.db
|
||||
|
||||
nginx.conf
|
||||
scripts
|
||||
models.py
|
||||
models.py
|
||||
Dockerfile
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
FROM python:3.10-slim
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y postgresql-client locales && \
|
||||
pip install --upgrade pip && pip install -r requirements.txt && \
|
||||
sed -i '/ru_RU.UTF-8/s/^# //g' /etc/locale.gen && \
|
||||
locale-gen ru_RU.UTF-8
|
||||
|
||||
ENV LANG=ru_RU.UTF-8
|
||||
ENV LANGUAGE=ru_RU:ru
|
||||
ENV LC_ALL=ru_RU.UTF-8
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN sed -i "s|DATABASE_URL = .*|DATABASE_URL = '${DATABASE_URL}'|" config.py
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
@@ -2,4 +2,4 @@ formatting:
|
||||
@echo "Running black..." && black .
|
||||
@echo "Running isort..." && isort .
|
||||
@echo "Running flake8..." && flake8 --config .flake8
|
||||
@echo "Running pylint..." && pylint .
|
||||
# @echo "Running pylint..." && pylint .
|
||||
|
||||
@@ -62,9 +62,10 @@ async def _send_backup_to_admin(bot, backup_file_path):
|
||||
if isinstance(admin_ids, list):
|
||||
for id in admin_ids:
|
||||
await bot.send_document(id, backup_input_file)
|
||||
logger.info(f"Бэкап базы данных отправлен админу: {id}")
|
||||
else:
|
||||
await bot.send_document(admin_ids, backup_input_file)
|
||||
logger.info(f"Бэкап базы данных отправлен админу: {ADMIN_ID}")
|
||||
logger.info(f"Бэкап базы данных отправлен админу: {ADMIN_ID}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке бэкапа в Telegram: {e}")
|
||||
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import traceback
|
||||
|
||||
from aiogram import Bot, Dispatcher, Router
|
||||
from aiogram.client.default import DefaultBotProperties
|
||||
from aiogram.enums import ParseMode
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
from aiogram.types import ErrorEvent
|
||||
|
||||
from config import API_TOKEN, CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, ROBOKASSA_ENABLE, STARS_ENABLE, YOOKASSA_ENABLE
|
||||
from logger import logger
|
||||
from middlewares.admin import AdminMiddleware
|
||||
from middlewares.database import DatabaseMiddleware
|
||||
from middlewares.delete import DeleteMessageMiddleware
|
||||
from middlewares.logging import LoggingMiddleware
|
||||
from middlewares.user import UserMiddleware
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot=bot, storage=storage)
|
||||
router = Router()
|
||||
|
||||
from handlers import commands, coupons, donate, notifications, pay, profile, start
|
||||
from handlers.admin import admin_commands, admin_coupons, admin_panel, admin_user_editor
|
||||
from handlers import coupons, donate, notifications, pay, profile, start
|
||||
from handlers.admin import admin_coupons, admin_panel, admin_user_editor
|
||||
from handlers.instructions import instructions
|
||||
from handlers.keys import key_management, keys
|
||||
from handlers.payments import cryprobot_pay, freekassa_pay, robokassa_pay, stars_pay, yookassa_pay
|
||||
|
||||
dp.include_router(admin_commands.router)
|
||||
dp.include_router(admin_coupons.router)
|
||||
dp.include_router(admin_panel.router)
|
||||
dp.include_router(admin_user_editor.router)
|
||||
dp.include_router(commands.router)
|
||||
dp.include_router(coupons.router)
|
||||
dp.include_router(start.router)
|
||||
dp.include_router(profile.router)
|
||||
@@ -54,3 +59,17 @@ dp.callback_query.middleware(UserMiddleware())
|
||||
|
||||
dp.message.middleware(DatabaseMiddleware())
|
||||
dp.callback_query.middleware(DatabaseMiddleware())
|
||||
|
||||
dp.message.outer_middleware(DeleteMessageMiddleware())
|
||||
dp.callback_query.outer_middleware(DeleteMessageMiddleware())
|
||||
|
||||
|
||||
@dp.error()
|
||||
async def error_handler(event: ErrorEvent):
|
||||
logger.error(
|
||||
"Ошибка в боте:\n"
|
||||
f"Исключение: {event.exception}\n"
|
||||
f"Тип: {type(event.exception)}\n"
|
||||
f"Update: {event.update}\n"
|
||||
f"Трассировка:\n{traceback.format_exc()}"
|
||||
)
|
||||
|
||||
+15
-17
@@ -8,22 +8,6 @@ from logger import logger
|
||||
|
||||
async def init_db():
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
|
||||
# Таблица для хранения информации о платежах
|
||||
await conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS payments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tg_id BIGINT NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
payment_system TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'success',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (tg_id) REFERENCES users(tg_id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# Таблица для хранения основной информации о пользователях из Telegram
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -40,6 +24,21 @@ async def init_db():
|
||||
"""
|
||||
)
|
||||
|
||||
# Таблица для хранения информации о платежах
|
||||
await conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS payments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tg_id BIGINT NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
payment_system TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'success',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (tg_id) REFERENCES users(tg_id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# Таблица для хранения информации о пользователях
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -183,7 +182,6 @@ async def restore_trial(tg_id: int):
|
||||
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()
|
||||
|
||||
@@ -10,11 +10,8 @@ class IsAdminFilter(BaseFilter):
|
||||
async def __call__(self, message: Message) -> bool:
|
||||
try:
|
||||
admin_ids: Union[int, list[int]] = ADMIN_ID
|
||||
|
||||
if isinstance(admin_ids, list):
|
||||
return message.from_user.id in admin_ids
|
||||
|
||||
return message.from_user.id == admin_ids
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
from aiogram import Router, types
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
import asyncpg
|
||||
|
||||
from bot import bot
|
||||
from config import DATABASE_URL
|
||||
from database import add_balance_to_client, check_connection_exists
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.texts import TRIAL
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
class Form(StatesGroup):
|
||||
waiting_for_server_selection = State()
|
||||
waiting_for_key_name = State()
|
||||
viewing_profile = State()
|
||||
waiting_for_message = State()
|
||||
|
||||
|
||||
@router.message(Command("add_balance"), IsAdminFilter())
|
||||
async def cmd_add_balance(message: types.Message):
|
||||
try:
|
||||
_, client_id, amount = message.text.split()
|
||||
amount = float(amount)
|
||||
|
||||
if not await check_connection_exists(int(client_id)):
|
||||
await message.reply(f"❌ Клиент с ID {client_id} не найден в базе данных.")
|
||||
return
|
||||
|
||||
await add_balance_to_client(int(client_id), amount)
|
||||
await message.reply(f"✅ Баланс клиента {client_id} успешно пополнен на {amount}")
|
||||
except ValueError:
|
||||
await message.reply(
|
||||
"❓ Неверный формат команды!\n"
|
||||
"Пожалуйста, используйте следующий шаблон:\n"
|
||||
"/add_balance <ID клиента> <сумма пополнения>"
|
||||
)
|
||||
except Exception as e:
|
||||
await message.reply(f"🚨 Произошла непредвиденная ошибка: {e}")
|
||||
|
||||
|
||||
@router.message(Command("backup"), IsAdminFilter())
|
||||
async def backup_command(message: types.Message):
|
||||
from backup import backup_database
|
||||
|
||||
await message.answer("🔄 Инициализация резервного копирования базы данных...")
|
||||
await backup_database()
|
||||
await message.answer("✅ Бэкап базы данных успешно завершен и отправлен администратору.")
|
||||
|
||||
|
||||
@router.message(Command("send_trial"), IsAdminFilter())
|
||||
async def handle_send_trial_command(message: types.Message, state: FSMContext):
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
records = await conn.fetch(
|
||||
"""
|
||||
SELECT tg_id FROM connections WHERE trial = 0
|
||||
"""
|
||||
)
|
||||
|
||||
if records:
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
blocked_count = 0
|
||||
|
||||
for record in records:
|
||||
tg_id = record["tg_id"]
|
||||
trial_message = TRIAL
|
||||
try:
|
||||
await bot.send_message(chat_id=tg_id, text=trial_message)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
if "Forbidden: bot was blocked by the user" in str(e):
|
||||
blocked_count += 1
|
||||
logger.info(f"🚫 Бот заблокирован пользователем с tg_id: {tg_id}")
|
||||
else:
|
||||
error_count += 1
|
||||
logger.error(f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}")
|
||||
|
||||
await message.answer(
|
||||
f"📊 Результаты рассылки пробных периодов:\n"
|
||||
f"✅ Успешно отправлено: {success_count}\n"
|
||||
f"🚫 Заблокировано: {blocked_count}\n"
|
||||
f"❌ Ошибок: {error_count}"
|
||||
)
|
||||
else:
|
||||
await message.answer("📭 Нет пользователей с неиспользованными пробными ключами.")
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
await message.answer(f"❗ Ошибка при отправке сообщений: {e}")
|
||||
|
||||
|
||||
@router.message(Command("send_to_all"), IsAdminFilter())
|
||||
async def send_message_to_all_clients(message: types.Message, state: FSMContext, from_panel=False):
|
||||
try:
|
||||
await message.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if from_panel:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
await message.answer(
|
||||
"✍️ Введите текст сообщения, который вы хотите отправить всем клиентам:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await state.set_state(Form.waiting_for_message)
|
||||
|
||||
|
||||
@router.message(Form.waiting_for_message, IsAdminFilter())
|
||||
async def process_message_to_all(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
text_message = message.text
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
tg_ids = await conn.fetch("SELECT tg_id FROM connections")
|
||||
|
||||
total_users = len(tg_ids)
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
|
||||
for record in tg_ids:
|
||||
tg_id = record["tg_id"]
|
||||
try:
|
||||
await bot.send_message(chat_id=tg_id, text=text_message)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
logger.error(f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}")
|
||||
|
||||
await message.answer(
|
||||
f"📤 Рассылка завершена:\n"
|
||||
f"👥 Всего пользователей: {total_users}\n"
|
||||
f"✅ Успешно отправлено: {success_count}\n"
|
||||
f"❌ Не доставлено: {error_count}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"❗ Ошибка при подключении к базе данных: {e}")
|
||||
await message.answer("❌ Произошла ошибка при отправке сообщения.")
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
await state.clear()
|
||||
@@ -18,44 +18,27 @@ router = Router()
|
||||
|
||||
@router.callback_query(F.data == "coupons_editor", IsAdminFilter())
|
||||
async def show_coupon_management_menu(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
finally:
|
||||
await state.clear()
|
||||
|
||||
await state.clear()
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="➕ Создать купон", callback_data="create_coupon"))
|
||||
builder.row(InlineKeyboardButton(text="Купоны", callback_data="coupons"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
|
||||
markup = builder.as_markup()
|
||||
await callback_query.message.answer("🛠 Меню управления купонами:", reply_markup=markup)
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("🛠 Меню управления купонами:", reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "coupons", IsAdminFilter())
|
||||
async def show_coupon_list(callback_query: types.CallbackQuery):
|
||||
try:
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
coupons = await get_all_coupons()
|
||||
|
||||
if not coupons:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
|
||||
markup = builder.as_markup()
|
||||
|
||||
await callback_query.message.answer(
|
||||
"❌ На данный момент нет доступных купонов.\n" "Вы можете вернуться в меню управления.",
|
||||
parse_mode="HTML",
|
||||
reply_markup=markup,
|
||||
"❌ На данный момент нет доступных купонов. 🚫\nВы можете вернуться в меню управления. 🔙",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await callback_query.answer()
|
||||
return
|
||||
|
||||
coupon_list = "📜 Список всех купонов:\n\n"
|
||||
@@ -63,10 +46,10 @@ async def show_coupon_list(callback_query: types.CallbackQuery):
|
||||
|
||||
for coupon in coupons:
|
||||
coupon_list += (
|
||||
f"<b>Код:</b> {coupon['code']}\n"
|
||||
f"<b>Сумма:</b> {coupon['amount']} рублей\n"
|
||||
f"<b>Лимит использования:</b> {coupon['usage_limit']} раз\n"
|
||||
f"<b>Использовано:</b> {coupon['usage_count']} раз\n\n"
|
||||
f"🏷️ <b>Код:</b> {coupon['code']}\n"
|
||||
f"💰 <b>Сумма:</b> {coupon['amount']} рублей\n"
|
||||
f"🔢 <b>Лимит использования:</b> {coupon['usage_limit']} раз\n"
|
||||
f"✅ <b>Использовано:</b> {coupon['usage_count']} раз\n\n"
|
||||
)
|
||||
|
||||
builder.row(
|
||||
@@ -77,17 +60,10 @@ async def show_coupon_list(callback_query: types.CallbackQuery):
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
|
||||
|
||||
markup = builder.as_markup()
|
||||
await callback_query.message.answer(coupon_list, parse_mode="HTML", reply_markup=markup)
|
||||
await callback_query.message.answer(coupon_list, reply_markup=builder.as_markup())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка купонов: {e}")
|
||||
await callback_query.message.answer(
|
||||
f"❌ Произошла ошибка при получении списка купонов: {e}",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("delete_coupon_"), IsAdminFilter())
|
||||
@@ -98,45 +74,29 @@ async def handle_delete_coupon(callback_query: types.CallbackQuery):
|
||||
result = await delete_coupon_from_db(coupon_code)
|
||||
|
||||
if result:
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
await show_coupon_list(callback_query)
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
f"❌ Купон с кодом <b>{coupon_code}</b> не найден.",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await show_coupon_list(callback_query)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении купона: {e}")
|
||||
await callback_query.message.answer(f"❌ Произошла ошибка при удалении купона: {e}", parse_mode="HTML")
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "create_coupon", IsAdminFilter())
|
||||
async def handle_create_coupon(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
|
||||
markup = builder.as_markup()
|
||||
|
||||
await callback_query.message.answer(
|
||||
"<b>Введите данные для создания купона в формате:</b>\n\n"
|
||||
"<i>код</i> <i>сумма</i> <i>лимит</i>\n\n"
|
||||
"Пример: <b>'COUPON1 50 5'</b>\n\n",
|
||||
parse_mode="HTML",
|
||||
reply_markup=markup,
|
||||
"🎫 <b>Введите данные для создания купона в формате:</b>\n\n"
|
||||
"📝 <i>код</i> 💰 <i>сумма</i> 🔢 <i>лимит</i>\n\n"
|
||||
"Пример: <b>'COUPON1 50 5'</b> 👈\n\n",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await state.set_state(AdminCouponsState.waiting_for_coupon_data)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(AdminCouponsState.waiting_for_coupon_data, IsAdminFilter())
|
||||
@@ -147,15 +107,13 @@ async def process_coupon_data(message: types.Message, state: FSMContext):
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
|
||||
markup = builder.as_markup()
|
||||
|
||||
if len(parts) != 3:
|
||||
await message.answer(
|
||||
"<b>❌ Некорректный формат!</b> Пожалуйста, введите данные в формате:\n"
|
||||
"<b>код</b> <b>сумма</b> <b>лимит</b>\n"
|
||||
"Пример: <b>'COUPON1 50 5'</b>",
|
||||
parse_mode="HTML",
|
||||
reply_markup=markup,
|
||||
"❌ <b>Некорректный формат!</b> 📝 Пожалуйста, введите данные в формате:\n"
|
||||
"🏷️ <b>код</b> 💰 <b>сумма</b> 🔢 <b>лимит</b>\n"
|
||||
"Пример: <b>'COUPON1 50 5'</b> 👈",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -165,9 +123,9 @@ async def process_coupon_data(message: types.Message, state: FSMContext):
|
||||
usage_limit = int(parts[2])
|
||||
except ValueError:
|
||||
await message.answer(
|
||||
"<b>⚠️ Проверьте правильность введенных данных.</b>\n" "Сумма должна быть числом, а лимит — целым числом.",
|
||||
parse_mode="HTML",
|
||||
reply_markup=markup,
|
||||
"⚠️ <b>Проверьте правильность введенных данных!</b>\n"
|
||||
"💱 Сумма должна быть числом, 🔢 а лимит — целым числом.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -175,31 +133,16 @@ async def process_coupon_data(message: types.Message, state: FSMContext):
|
||||
await create_coupon(coupon_code, coupon_amount, usage_limit)
|
||||
|
||||
result_message = (
|
||||
f"✅ Купон с кодом <b>{coupon_code}</b> успешно создан!\n"
|
||||
f"Сумма: <b>{coupon_amount} рублей</b>\n"
|
||||
f"Лимит использования: <b>{usage_limit} раз</b>."
|
||||
f"✅ Купон с кодом <b>{coupon_code}</b> успешно создан! 🎉\n"
|
||||
f"Сумма: <b>{coupon_amount} рублей</b> 💰\n"
|
||||
f"Лимит использования: <b>{usage_limit} раз</b> 🔢."
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
|
||||
markup = builder.as_markup()
|
||||
|
||||
try:
|
||||
await message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
await message.answer(result_message, parse_mode="HTML", reply_markup=markup)
|
||||
|
||||
await message.answer(result_message, reply_markup=builder.as_markup())
|
||||
await state.clear()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании купона: {e}")
|
||||
await message.answer(f"<b>❌ Ошибка при создании купона:</b> {e}", parse_mode="HTML")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "back_to_coupons_menu")
|
||||
async def back_to_coupons_menu(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
"""Возвращаем пользователя в меню управления купонами"""
|
||||
await state.clear()
|
||||
await show_coupon_management_menu(callback_query)
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
from datetime import datetime
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
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.types import CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
import asyncpg
|
||||
|
||||
from backup import backup_database
|
||||
from bot import bot
|
||||
from config import DATABASE_URL
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.admin.admin_commands import send_message_to_all_clients
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -22,6 +21,7 @@ class UserEditorState(StatesGroup):
|
||||
waiting_for_tg_id = State()
|
||||
displaying_user_info = State()
|
||||
waiting_for_restart_confirmation = State()
|
||||
waiting_for_message = State()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "admin", IsAdminFilter())
|
||||
@@ -33,11 +33,6 @@ async def handle_admin_callback_query(callback_query: CallbackQuery, state: FSMC
|
||||
async def handle_admin_message(message: types.Message, state: FSMContext):
|
||||
await state.clear()
|
||||
|
||||
try:
|
||||
await message.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="📊 Статистика пользователей", callback_data="user_stats"))
|
||||
builder.row(InlineKeyboardButton(text="👥 Управление пользователями", callback_data="user_editor"))
|
||||
@@ -45,31 +40,26 @@ async def handle_admin_message(message: types.Message, state: FSMContext):
|
||||
builder.row(InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to_alls"))
|
||||
builder.row(InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups"))
|
||||
builder.row(InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
|
||||
await bot.send_message(
|
||||
message.chat.id,
|
||||
"🤖 Панель администратора",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="profile"))
|
||||
await message.answer("🤖 Панель администратора", reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "user_stats", IsAdminFilter())
|
||||
async def user_stats_menu(callback_query: CallbackQuery):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
async def user_stats_menu(callback_query: CallbackQuery, session: Any):
|
||||
try:
|
||||
total_users = await conn.fetchval("SELECT COUNT(*) FROM connections")
|
||||
total_keys = await conn.fetchval("SELECT COUNT(*) FROM keys")
|
||||
total_referrals = await conn.fetchval("SELECT COUNT(*) FROM referrals")
|
||||
total_users = await session.fetchval("SELECT COUNT(*) FROM connections")
|
||||
total_keys = await session.fetchval("SELECT COUNT(*) FROM keys")
|
||||
total_referrals = await session.fetchval("SELECT COUNT(*) FROM referrals")
|
||||
|
||||
total_payments_today = await conn.fetchval(
|
||||
total_payments_today = await session.fetchval(
|
||||
"SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= CURRENT_DATE"
|
||||
)
|
||||
total_payments_week = await conn.fetchval(
|
||||
total_payments_week = await session.fetchval(
|
||||
"SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= date_trunc('week', CURRENT_DATE)"
|
||||
)
|
||||
total_payments_all_time = await conn.fetchval("SELECT COALESCE(SUM(amount), 0) FROM payments")
|
||||
total_payments_all_time = await session.fetchval("SELECT COALESCE(SUM(amount), 0) FROM payments")
|
||||
|
||||
active_keys = await conn.fetchval(
|
||||
active_keys = await session.fetchval(
|
||||
"SELECT COUNT(*) FROM keys WHERE expiry_time > $1",
|
||||
int(datetime.utcnow().timestamp() * 1000),
|
||||
)
|
||||
@@ -94,22 +84,59 @@ async def user_stats_menu(callback_query: CallbackQuery):
|
||||
builder.row(InlineKeyboardButton(text="🔄 Обновить", callback_data="user_stats"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
|
||||
|
||||
await callback_query.message.edit_text(stats_message, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||
finally:
|
||||
await conn.close()
|
||||
await callback_query.message.answer(stats_message, reply_markup=builder.as_markup())
|
||||
except Exception as e:
|
||||
logger.error(f"Error in user_stats_menu: {e}")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "send_to_alls", IsAdminFilter())
|
||||
async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext):
|
||||
await send_message_to_all_clients(callback_query.message, state, from_panel=True)
|
||||
await callback_query.answer()
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
await callback_query.message.answer(
|
||||
"✍️ Введите текст сообщения, который вы хотите отправить всем клиентам 📢🌐:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await state.set_state(UserEditorState.waiting_for_message)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_message, IsAdminFilter())
|
||||
async def process_message_to_all(message: types.Message, state: FSMContext, session: Any):
|
||||
text_message = message.text
|
||||
|
||||
try:
|
||||
tg_ids = await session.fetch("SELECT tg_id FROM connections")
|
||||
|
||||
total_users = len(tg_ids)
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
|
||||
for record in tg_ids:
|
||||
tg_id = record["tg_id"]
|
||||
try:
|
||||
await bot.send_message(chat_id=tg_id, text=text_message)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
logger.error(f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}")
|
||||
|
||||
await message.answer(
|
||||
f"📤 Рассылка завершена:\n"
|
||||
f"👥 Всего пользователей: {total_users}\n"
|
||||
f"✅ Успешно отправлено: {success_count}\n"
|
||||
f"❌ Не доставлено: {error_count}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"❗ Ошибка при подключении к базе данных: {e}")
|
||||
|
||||
await state.clear()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "backups", IsAdminFilter())
|
||||
async def handle_backup(message: Message):
|
||||
await message.answer("💾 Инициализация резервного копирования базы данных...")
|
||||
async def handle_backup(callback_query: CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.answer("💾 Инициализация резервного копирования базы данных...")
|
||||
await backup_database()
|
||||
await message.answer("✅ Резервная копия успешно создана и отправлена администратору.")
|
||||
await callback_query.message.answer("✅ Резервная копия успешно создана и отправлена администратору.")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "restart_bot", IsAdminFilter())
|
||||
@@ -121,7 +148,7 @@ async def handle_restart(callback_query: CallbackQuery, state: FSMContext):
|
||||
InlineKeyboardButton(text="❌ Нет, отмена", callback_data="admin"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
|
||||
await callback_query.message.edit_text(
|
||||
await callback_query.message.answer(
|
||||
"🤔 Вы уверены, что хотите перезапустить бота?",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
@@ -143,16 +170,13 @@ async def confirm_restart_bot(callback_query: CallbackQuery, state: FSMContext):
|
||||
text=True,
|
||||
)
|
||||
await state.clear()
|
||||
await callback_query.message.edit_text("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
|
||||
await callback_query.message.answer("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
|
||||
except subprocess.CalledProcessError:
|
||||
await callback_query.message.edit_text("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
|
||||
await callback_query.message.answer("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
|
||||
except Exception as e:
|
||||
await callback_query.message.edit_text(
|
||||
f"⚠️ Ошибка при перезагрузке бота: {e.stderr}",
|
||||
reply_markup=builder.as_markup(),
|
||||
await callback_query.message.answer(
|
||||
f"⚠️ Ошибка при перезагрузке бота: {e.stderr}", reply_markup=builder.as_markup()
|
||||
)
|
||||
finally:
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "user_editor", IsAdminFilter())
|
||||
@@ -167,11 +191,4 @@ async def user_editor_menu(callback_query: CallbackQuery):
|
||||
builder.row(InlineKeyboardButton(text="🆔 Поиск по Telegram ID", callback_data="search_by_tg_id"))
|
||||
builder.row(InlineKeyboardButton(text="🌐 Поиск по Username", callback_data="search_by_username"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Вернуться назад", callback_data="admin"))
|
||||
await callback_query.message.edit_text(
|
||||
"👇 Выберите способ поиска пользователя:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
|
||||
async def handle_error(tg_id, callback_query, message):
|
||||
await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
await callback_query.message.answer("👇 Выберите способ поиска пользователя:", reply_markup=builder.as_markup())
|
||||
|
||||
+282
-404
@@ -1,18 +1,17 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
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
|
||||
import asyncpg
|
||||
|
||||
from bot import bot
|
||||
from config import CLUSTERS, DATABASE_URL, TOTAL_GB
|
||||
from config import CLUSTERS, TOTAL_GB
|
||||
from database import get_client_id_by_email, restore_trial, update_key_expiry
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import delete_key_from_cluster, renew_key_in_cluster
|
||||
from handlers.keys.key_utils import delete_key_from_cluster, delete_key_from_db, renew_key_in_cluster
|
||||
from handlers.utils import sanitize_key_name
|
||||
from logger import logger
|
||||
|
||||
@@ -30,125 +29,114 @@ class UserEditorState(StatesGroup):
|
||||
|
||||
@router.callback_query(F.data == "search_by_tg_id", IsAdminFilter())
|
||||
async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text("🔍 Введите Telegram ID клиента:")
|
||||
await callback_query.message.answer("🔍 Введите Telegram ID клиента:")
|
||||
await state.set_state(UserEditorState.waiting_for_tg_id)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "search_by_username", IsAdminFilter())
|
||||
async def prompt_username(callback_query: CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text("🔍 Введите Username клиента:")
|
||||
await callback_query.message.answer("🔍 Введите Username клиента:")
|
||||
await state.set_state(UserEditorState.waiting_for_username)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_username, IsAdminFilter())
|
||||
async def handle_username_input(message: types.Message, state: FSMContext):
|
||||
async def handle_username_input(message: types.Message, state: FSMContext, session: Any):
|
||||
username = message.text.strip()
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
user_record = await conn.fetchrow("SELECT tg_id FROM users WHERE username = $1", username)
|
||||
user_record = await session.fetchrow("SELECT tg_id FROM users WHERE username = $1", username)
|
||||
|
||||
if not user_record:
|
||||
await message.reply("🔍 Пользователь с указанным username не найден. 🚫")
|
||||
await state.clear()
|
||||
return
|
||||
if not user_record:
|
||||
await message.answer("🔍 Пользователь с указанным username не найден. 🚫")
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
tg_id = user_record["tg_id"]
|
||||
username = await conn.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id)
|
||||
balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
||||
key_records = await conn.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
|
||||
referral_count = await conn.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
|
||||
tg_id = user_record["tg_id"]
|
||||
username = await session.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id)
|
||||
balance = await session.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
||||
key_records = await session.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
|
||||
referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
|
||||
|
||||
if balance is None:
|
||||
await message.reply("Пользователь с указанным tg_id не найден.")
|
||||
await state.clear()
|
||||
return
|
||||
if balance is None:
|
||||
await message.answer("🚫 Пользователь с указанным tg_id не найден. 🔍")
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for (email,) in key_records:
|
||||
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
|
||||
for (email,) in key_records:
|
||||
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📝 Изменить баланс",
|
||||
callback_data=f"change_balance_{tg_id}",
|
||||
)
|
||||
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=f"restore_trial_{tg_id}",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
|
||||
user_info = (
|
||||
f"📊 Информация о пользователе:\n\n"
|
||||
f"🆔 ID пользователя: <b>{tg_id}</b>\n"
|
||||
f"👤 Логин пользователя: <b>@{username}</b>\n"
|
||||
f"💰 Баланс: <b>{balance}</b>\n"
|
||||
f"👥 Количество рефералов: <b>{referral_count}</b>\n"
|
||||
f"🔑 Ключи (для редактирования нажмите на ключ):"
|
||||
)
|
||||
await message.reply(user_info, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||
await state.set_state(UserEditorState.displaying_user_info)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
user_info = (
|
||||
f"📊 Информация о пользователе:\n\n"
|
||||
f"🆔 ID пользователя: <b>{tg_id}</b>\n"
|
||||
f"👤 Логин пользователя: <b>@{username}</b>\n"
|
||||
f"💰 Баланс: <b>{balance}</b>\n"
|
||||
f"👥 Количество рефералов: <b>{referral_count}</b>\n"
|
||||
f"🔑 Ключи (для редактирования нажмите на ключ):"
|
||||
)
|
||||
await message.answer(user_info, reply_markup=builder.as_markup())
|
||||
await state.set_state(UserEditorState.displaying_user_info)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(), IsAdminFilter())
|
||||
async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
||||
async def handle_tg_id_input(message: types.Message, state: FSMContext, session: Any):
|
||||
tg_id = int(message.text)
|
||||
username = await session.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id)
|
||||
balance = await session.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
||||
key_records = await session.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
|
||||
referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
username = await conn.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id)
|
||||
balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
||||
key_records = await conn.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
|
||||
referral_count = await conn.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
|
||||
if balance is None:
|
||||
await message.answer("❌ Пользователь с указанным tg_id не найден. 🔍")
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
if balance is None:
|
||||
await message.reply("❌ Пользователь с указанным tg_id не найден. 🔍")
|
||||
await state.clear()
|
||||
return
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
for (email,) in key_records:
|
||||
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
|
||||
|
||||
for (email,) in key_records:
|
||||
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📝 Изменить баланс",
|
||||
callback_data=f"change_balance_{tg_id}",
|
||||
)
|
||||
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=f"restore_trial_{tg_id}",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
|
||||
user_info = (
|
||||
f"📊 Информация о пользователе:\n\n"
|
||||
f"🆔 ID пользователя: <b>{tg_id}</b>\n"
|
||||
f"👤 Логин пользователя: <b>@{username}</b>\n"
|
||||
f"💰 Баланс: <b>{balance}</b>\n"
|
||||
f"👥 Количество рефералов: <b>{referral_count}</b>\n"
|
||||
f"🔑 Ключи (для редактирования нажмите на ключ):"
|
||||
)
|
||||
await message.reply(user_info, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||
await state.set_state(UserEditorState.displaying_user_info)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
user_info = (
|
||||
f"📊 Информация о пользователе:\n\n"
|
||||
f"🆔 ID пользователя: <b>{tg_id}</b>\n"
|
||||
f"👤 Логин пользователя: <b>@{username}</b>\n"
|
||||
f"💰 Баланс: <b>{balance}</b>\n"
|
||||
f"👥 Количество рефералов: <b>{referral_count}</b>\n"
|
||||
f"🔑 Ключи (для редактирования нажмите на ключ):"
|
||||
)
|
||||
await message.answer(user_info, reply_markup=builder.as_markup())
|
||||
await state.set_state(UserEditorState.displaying_user_info)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("restore_trial_"), IsAdminFilter())
|
||||
@@ -160,7 +148,7 @@ async def handle_restore_trial(callback_query: types.CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад в меню администратора", callback_data="admin"))
|
||||
|
||||
await callback_query.message.edit_text("✅ Триал успешно восстановлен.", reply_markup=builder.as_markup())
|
||||
await callback_query.message.answer("✅ Триал успешно восстановлен.", reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("change_balance_"), IsAdminFilter())
|
||||
@@ -168,31 +156,129 @@ async def process_balance_change(callback_query: CallbackQuery, state: FSMContex
|
||||
tg_id = int(callback_query.data.split("_")[2])
|
||||
await state.update_data(tg_id=tg_id)
|
||||
|
||||
await callback_query.message.edit_text("💸 Введите новую сумму баланса:")
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("💸 Введите новую сумму баланса:")
|
||||
await state.set_state(UserEditorState.waiting_for_new_balance)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_new_balance, IsAdminFilter())
|
||||
async def handle_new_balance_input(message: types.Message, state: FSMContext):
|
||||
async def handle_new_balance_input(message: types.Message, state: FSMContext, session: Any):
|
||||
if not message.text.isdigit() or int(message.text) < 0:
|
||||
await message.reply("❌ Пожалуйста, введите корректную сумму для изменения баланса.")
|
||||
await message.answer("❌ Пожалуйста, введите корректную сумму для изменения баланса.")
|
||||
return
|
||||
|
||||
new_balance = int(message.text)
|
||||
user_data = await state.get_data()
|
||||
tg_id = user_data.get("tg_id")
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
await conn.execute(
|
||||
"UPDATE connections SET balance = $1 WHERE tg_id = $2",
|
||||
new_balance,
|
||||
tg_id,
|
||||
await session.execute(
|
||||
"UPDATE connections SET balance = $1 WHERE tg_id = $2",
|
||||
new_balance,
|
||||
tg_id,
|
||||
)
|
||||
|
||||
response_message = f"✅ Баланс успешно изменен на <b>{new_balance}</b>."
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад в меню администратора",
|
||||
callback_data="admin",
|
||||
)
|
||||
)
|
||||
await message.answer(response_message, reply_markup=builder.as_markup())
|
||||
await state.clear()
|
||||
|
||||
response_message = f"✅ Баланс успешно изменен на <b>{new_balance}</b>."
|
||||
|
||||
async def get_key_details(email, session):
|
||||
record = await session.fetchrow(
|
||||
"""
|
||||
SELECT k.key, k.expiry_time, k.server_id, c.tg_id, c.balance
|
||||
FROM keys k
|
||||
JOIN connections c ON k.tg_id = c.tg_id
|
||||
WHERE k.email = $1
|
||||
""",
|
||||
email,
|
||||
)
|
||||
|
||||
if not record:
|
||||
return None
|
||||
|
||||
# Определение сервера
|
||||
server_name = "Неизвестный сервер"
|
||||
for cluster in CLUSTERS.values():
|
||||
if record['server_id'] in cluster:
|
||||
server_name = cluster[record['server_id']].get("name", "Неизвестный сервер")
|
||||
break
|
||||
|
||||
# Расчет времени до истечения
|
||||
expiry_date = datetime.utcfromtimestamp(record['expiry_time'] / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
if time_left.total_seconds() <= 0:
|
||||
days_left_message = "<b>Ключ истек.</b>"
|
||||
elif time_left.days > 0:
|
||||
days_left_message = f"Осталось дней: <b>{time_left.days}</b>"
|
||||
else:
|
||||
hours_left = time_left.seconds // 3600
|
||||
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
||||
|
||||
return {
|
||||
'key': record['key'],
|
||||
'expiry_date': expiry_date.strftime("%d %B %Y года"),
|
||||
'days_left_message': days_left_message,
|
||||
'server_name': server_name,
|
||||
'balance': record['balance'],
|
||||
'tg_id': record['tg_id'],
|
||||
}
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("edit_key_"), IsAdminFilter())
|
||||
async def process_key_edit(callback_query: CallbackQuery, session: Any):
|
||||
email = callback_query.data.split("_", 2)[2]
|
||||
key_details = await get_key_details(email, session)
|
||||
|
||||
if not key_details:
|
||||
await callback_query.message.answer("🔍 <b>Информация о ключе не найдена.</b> 🚫")
|
||||
return
|
||||
|
||||
response_message = (
|
||||
f"🔑 Ключ: <pre>{key_details['key']}</pre>\n"
|
||||
f"⏰ Дата истечения: <b>{key_details['expiry_date']}</b>\n"
|
||||
f"💰 Баланс пользователя: <b>{key_details['balance']}</b>\n"
|
||||
f"🌐 Сервер: <b>{key_details['server_name']}</b>"
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="⏳ Изменить время истечения",
|
||||
callback_data=f"change_expiry|{email}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить ключ",
|
||||
callback_data=f"delete_key_admin|{email}",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "search_by_key_name", IsAdminFilter())
|
||||
async def prompt_key_name(callback_query: CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.answer("🔑 Введите имя ключа:")
|
||||
await state.set_state(UserEditorState.waiting_for_key_name)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_key_name, IsAdminFilter())
|
||||
async def handle_key_name_input(message: types.Message, state: FSMContext, session: Any):
|
||||
key_name = sanitize_key_name(message.text)
|
||||
key_details = await get_key_details(key_name, session)
|
||||
|
||||
if not key_details:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
@@ -200,202 +286,57 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext):
|
||||
callback_data="admin",
|
||||
)
|
||||
)
|
||||
await message.reply(
|
||||
response_message,
|
||||
|
||||
await message.answer(
|
||||
"🚫 Пользователь с указанным именем ключа не найден.",
|
||||
reply_markup=builder.as_markup(),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
response_message = (
|
||||
f"🔑 Ключ: <pre>{key_details['key']}</pre>\n"
|
||||
f"⏰ Дата истечения: <b>{key_details['expiry_date']}</b>\n"
|
||||
f"💰 Баланс пользователя: <b>{key_details['balance']}</b>\n"
|
||||
f"🌐 Сервер: <b>{key_details['server_name']}</b>"
|
||||
)
|
||||
|
||||
await state.clear()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("edit_key_"), IsAdminFilter())
|
||||
async def process_key_edit(callback_query: CallbackQuery):
|
||||
email = callback_query.data.split("_", 2)[2]
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow(
|
||||
"""
|
||||
SELECT k.key, k.expiry_time, k.server_id
|
||||
FROM keys k
|
||||
WHERE k.email = $1
|
||||
""",
|
||||
email,
|
||||
)
|
||||
|
||||
if record:
|
||||
key = record["key"]
|
||||
expiry_time = record["expiry_time"]
|
||||
server_id = record["server_id"]
|
||||
server_name = "Неизвестный сервер"
|
||||
|
||||
for cluster in CLUSTERS.values():
|
||||
if server_id in cluster:
|
||||
server_name = cluster[server_id].get("name", "Неизвестный сервер")
|
||||
break
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
if time_left.total_seconds() <= 0:
|
||||
days_left_message = "<b>Ключ истек.</b>"
|
||||
elif time_left.days > 0:
|
||||
days_left_message = f"Осталось дней: <b>{time_left.days}</b>"
|
||||
else:
|
||||
hours_left = time_left.seconds // 3600
|
||||
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
||||
|
||||
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
|
||||
|
||||
response_message = (
|
||||
f"Ключ: <pre>{key}</pre>\n"
|
||||
f"Дата истечения: <b>{formatted_expiry_date}</b>\n"
|
||||
f"{days_left_message}\n"
|
||||
f"Сервер: <b>{server_name}</b>"
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="⏳ Изменить время истечения",
|
||||
callback_data=f"change_expiry|{email}",
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить ключ",
|
||||
callback_data=f"delete_key_admin|{email}",
|
||||
),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
await callback_query.message.edit_text(
|
||||
response_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
else:
|
||||
await callback_query.message.edit_text("<b>Информация о ключе не найдена.</b>", parse_mode="HTML")
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении информации о ключе: {e}")
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "search_by_key_name", IsAdminFilter())
|
||||
async def prompt_key_name(callback_query: CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text("🔑 Введите имя ключа:")
|
||||
await state.set_state(UserEditorState.waiting_for_key_name)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_key_name, IsAdminFilter())
|
||||
async def handle_key_name_input(message: types.Message, state: FSMContext):
|
||||
key_name = sanitize_key_name(message.text)
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
user_records = await conn.fetch(
|
||||
"""
|
||||
SELECT c.tg_id, c.balance, k.email, k.key, k.expiry_time, k.server_id
|
||||
FROM connections c
|
||||
JOIN keys k ON c.tg_id = k.tg_id
|
||||
WHERE k.email = $1
|
||||
""",
|
||||
key_name,
|
||||
key_buttons = InlineKeyboardBuilder()
|
||||
key_buttons.row(
|
||||
InlineKeyboardButton(
|
||||
text="⏳ Изменить время истечения",
|
||||
callback_data=f"change_expiry|{key_name}",
|
||||
)
|
||||
|
||||
if not user_records:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад в меню администратора",
|
||||
callback_data="admin",
|
||||
)
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
"🚫 Пользователь с указанным именем ключа не найден.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
response_messages = []
|
||||
key_buttons = InlineKeyboardBuilder()
|
||||
|
||||
for record in user_records:
|
||||
balance = record["balance"]
|
||||
email = record["email"]
|
||||
key = record["key"]
|
||||
expiry_time = record["expiry_time"]
|
||||
server_id = record["server_id"]
|
||||
server_name = "Неизвестный сервер"
|
||||
|
||||
for cluster in CLUSTERS.values():
|
||||
if server_id in cluster:
|
||||
server_name = cluster[server_id].get("name", "Неизвестный сервер")
|
||||
break
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%d %B %Y")
|
||||
|
||||
response_messages.append(
|
||||
f"🔑 Ключ: <pre>{key}</pre>\n"
|
||||
f"⏰ Дата истечения: <b>{expiry_date}</b>\n"
|
||||
f"💰 Баланс пользователя: <b>{balance}</b>\n"
|
||||
f"🌐 Сервер: <b>{server_name}</b>"
|
||||
)
|
||||
|
||||
key_buttons.row(
|
||||
InlineKeyboardButton(
|
||||
text="⏳ Изменить время истечения",
|
||||
callback_data=f"change_expiry|{email}",
|
||||
)
|
||||
)
|
||||
key_buttons.row(
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить ключ",
|
||||
callback_data=f"delete_key_admin|{email}",
|
||||
)
|
||||
)
|
||||
|
||||
key_buttons.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
|
||||
await message.reply(
|
||||
"\n".join(response_messages),
|
||||
reply_markup=key_buttons.as_markup(),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
key_buttons.row(
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить ключ",
|
||||
callback_data=f"delete_key_admin|{key_name}",
|
||||
)
|
||||
)
|
||||
key_buttons.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
await message.answer(response_message, reply_markup=key_buttons.as_markup())
|
||||
await state.clear()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("change_expiry|"), IsAdminFilter())
|
||||
async def prompt_expiry_change(callback_query: CallbackQuery, state: FSMContext):
|
||||
email = callback_query.data.split("|")[1]
|
||||
await callback_query.message.edit_text(
|
||||
f"⏳ Введите новое время истечения для ключа <b>{email}</b> в формате <code>YYYY-MM-DD HH:MM:SS</code>:",
|
||||
parse_mode="HTML",
|
||||
await callback_query.message.answer(
|
||||
f"⏳ Введите новое время истечения для ключа <b>{email}</b> в формате <code>YYYY-MM-DD HH:MM:SS</code>:"
|
||||
)
|
||||
await state.update_data(email=email)
|
||||
await state.set_state(UserEditorState.waiting_for_expiry_time)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_expiry_time, IsAdminFilter())
|
||||
async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
||||
async def handle_expiry_time_input(message: types.Message, state: FSMContext, session: Any):
|
||||
user_data = await state.get_data()
|
||||
email = user_data.get("email")
|
||||
|
||||
if not email:
|
||||
await message.reply("Email не найден в состоянии.")
|
||||
await message.answer("📧 Email не найден в состоянии. 🚫")
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
@@ -405,160 +346,97 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
||||
|
||||
client_id = await get_client_id_by_email(email)
|
||||
if client_id is None:
|
||||
await message.reply(f"Клиент с email {email} не найден.")
|
||||
await message.answer(f"🚫 Клиент с email {email} не найден. 🔍")
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow("SELECT server_id FROM keys WHERE client_id = $1", client_id)
|
||||
if not record:
|
||||
await message.reply("Клиент не найден в базе данных.")
|
||||
await state.clear()
|
||||
return
|
||||
record = await session.fetchrow("SELECT server_id FROM keys WHERE client_id = $1", client_id)
|
||||
if not record:
|
||||
await message.answer("🚫 Клиент не найден в базе данных. 🔍")
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
async def update_key_on_all_servers():
|
||||
tasks = []
|
||||
for cluster_id in CLUSTERS:
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
client_id,
|
||||
expiry_time,
|
||||
total_gb=TOTAL_GB,
|
||||
)
|
||||
async def update_key_on_all_servers():
|
||||
tasks = []
|
||||
for cluster_id in CLUSTERS:
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
client_id,
|
||||
expiry_time,
|
||||
total_gb=TOTAL_GB,
|
||||
)
|
||||
)
|
||||
await asyncio.gather(*tasks)
|
||||
)
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await update_key_on_all_servers()
|
||||
await update_key_on_all_servers()
|
||||
|
||||
await update_key_expiry(client_id, expiry_time)
|
||||
await update_key_expiry(client_id, expiry_time)
|
||||
|
||||
response_message = (
|
||||
f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
await message.reply(
|
||||
response_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
response_message = (
|
||||
f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
await message.answer(response_message, reply_markup=builder.as_markup())
|
||||
except ValueError:
|
||||
await message.reply("❌ Пожалуйста, используйте формат: YYYY-MM-DD HH:MM:SS.")
|
||||
await message.answer("❌ Пожалуйста, используйте формат: YYYY-MM-DD HH:MM:SS.")
|
||||
except Exception as e:
|
||||
await message.reply(f"Произошла ошибка: {e}")
|
||||
|
||||
logger.error(e)
|
||||
await state.clear()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("delete_key_admin|"), IsAdminFilter())
|
||||
async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
async def process_callback_delete_key(callback_query: types.CallbackQuery, session: Any):
|
||||
email = callback_query.data.split("|")[1]
|
||||
client_id = await session.fetchval("SELECT client_id FROM keys WHERE email = $1", email)
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
client_id = await conn.fetchval("SELECT client_id FROM keys WHERE email = $1", email)
|
||||
|
||||
if client_id is None:
|
||||
await bot.edit_message_text(
|
||||
"Ключ не найден.",
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
)
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="✅ Да, удалить",
|
||||
callback_data=f"confirm_delete_admin|{client_id}",
|
||||
)
|
||||
if client_id is None:
|
||||
await callback_query.message.answer(
|
||||
"🔍 Ключ не найден. 🚫",
|
||||
)
|
||||
builder.row(types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="view_keys"))
|
||||
await bot.edit_message_text(
|
||||
"<b>❓ Вы уверены, что хотите удалить ключ?</b>",
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
reply_markup=builder.as_markup(),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
finally:
|
||||
await conn.close()
|
||||
return
|
||||
|
||||
await callback_query.answer()
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="✅ Да, удалить",
|
||||
callback_data=f"confirm_delete_admin|{client_id}",
|
||||
)
|
||||
)
|
||||
builder.row(types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="view_keys"))
|
||||
await callback_query.message.answer(
|
||||
"<b>❓ Вы уверены, что хотите удалить ключ?</b>",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("confirm_delete_admin|"), IsAdminFilter())
|
||||
async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
async def process_callback_confirm_delete(callback_query: types.CallbackQuery, session: Any):
|
||||
client_id = callback_query.data.split("|")[1]
|
||||
record = await session.fetchrow("SELECT email FROM keys WHERE client_id = $1", client_id)
|
||||
if record:
|
||||
email = record["email"]
|
||||
response_message = "✅ Ключ успешно удален."
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow("SELECT email FROM keys WHERE client_id = $1", client_id)
|
||||
async def delete_key_from_servers(email, client_id):
|
||||
tasks = []
|
||||
for cluster_id in CLUSTERS:
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
if record:
|
||||
email = record["email"]
|
||||
response_message = "✅ Ключ успешно удален."
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
await delete_key_from_servers(email, client_id)
|
||||
await delete_key_from_db(client_id, session)
|
||||
|
||||
async def delete_key_from_servers(email, client_id):
|
||||
tasks = []
|
||||
for cluster_id in CLUSTERS:
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await delete_key_from_servers(email, client_id)
|
||||
await delete_key_from_db(client_id)
|
||||
|
||||
await bot.edit_message_text(
|
||||
response_message,
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
response_message = "🚫 Ключ не найден или уже удален."
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
await bot.edit_message_text(
|
||||
response_message,
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
await bot.edit_message_text(
|
||||
f"Ошибка при удалении ключа: {e}",
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
async def delete_key_from_db(client_id):
|
||||
"""Удаление ключа из базы данных"""
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
await conn.execute("DELETE FROM keys WHERE client_id = $1", client_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
|
||||
finally:
|
||||
await conn.close()
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
else:
|
||||
response_message = "🚫 Ключ не найден или уже удален."
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
from aiogram import Router, types
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
from handlers.start import start_command
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.message(Command("start"))
|
||||
async def handle_start(message: types.Message, state: FSMContext, admin: bool = False):
|
||||
await start_command(message, admin)
|
||||
|
||||
|
||||
@router.message(Command("menu"))
|
||||
async def handle_menu(message: types.Message, state: FSMContext, admin: bool = False):
|
||||
await start_command(message, admin)
|
||||
+43
-68
@@ -1,15 +1,13 @@
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
import asyncpg
|
||||
|
||||
from config import DATABASE_URL
|
||||
from database import update_balance
|
||||
from logger import logger
|
||||
|
||||
|
||||
class CouponActivationState(StatesGroup):
|
||||
@@ -21,98 +19,75 @@ router = Router()
|
||||
|
||||
@router.callback_query(F.data == "activate_coupon")
|
||||
async def handle_activate_coupon(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"<b>🎫 Введите код купона:</b>\n\n"
|
||||
"📝 Пожалуйста, введите действующий код купона, который вы хотите активировать. 🔑",
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await state.set_state(CouponActivationState.waiting_for_coupon_code)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(CouponActivationState.waiting_for_coupon_code)
|
||||
async def process_coupon_code(message: types.Message, state: FSMContext):
|
||||
try:
|
||||
await message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
async def process_coupon_code(message: types.Message, state: FSMContext, session: Any):
|
||||
coupon_code = message.text.strip()
|
||||
activation_result = await activate_coupon(message.from_user.id, coupon_code)
|
||||
activation_result = await activate_coupon(message.from_user.id, coupon_code, session)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await message.answer(activation_result, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||
await state.clear()
|
||||
|
||||
|
||||
async def activate_coupon(user_id: int, coupon_code: str):
|
||||
"""Функция для активации купона"""
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
async def activate_coupon(user_id: int, coupon_code: str, session: Any):
|
||||
coupon_record = await session.fetchrow(
|
||||
"""
|
||||
SELECT id, usage_limit, usage_count, is_used, amount
|
||||
FROM coupons
|
||||
WHERE code = $1 AND (usage_count < usage_limit OR usage_limit = 0) AND is_used = FALSE
|
||||
""",
|
||||
coupon_code,
|
||||
)
|
||||
|
||||
try:
|
||||
coupon_record = await conn.fetchrow(
|
||||
if not coupon_record:
|
||||
return "<b>❌ Купон не найден</b> 🚫 или его использование ограничено. 🔒 Пожалуйста, проверьте код и попробуйте снова. 🔍"
|
||||
|
||||
usage_exists = await session.fetchrow(
|
||||
"""
|
||||
SELECT 1 FROM coupon_usages WHERE coupon_id = $1 AND user_id = $2
|
||||
""",
|
||||
coupon_record["id"],
|
||||
user_id,
|
||||
)
|
||||
|
||||
if usage_exists:
|
||||
return "<b>❌ Вы уже активировали этот купон.</b> 🚫 Купоны могут быть активированы только один раз. 🔒"
|
||||
|
||||
coupon_amount = coupon_record["amount"]
|
||||
|
||||
async with session.transaction():
|
||||
await session.execute(
|
||||
"""
|
||||
SELECT id, usage_limit, usage_count, is_used, amount
|
||||
FROM coupons
|
||||
WHERE code = $1 AND (usage_count < usage_limit OR usage_limit = 0) AND is_used = FALSE
|
||||
UPDATE coupons
|
||||
SET usage_count = usage_count + 1,
|
||||
is_used = CASE WHEN usage_count + 1 >= usage_limit AND usage_limit > 0 THEN TRUE ELSE FALSE END
|
||||
WHERE id = $1
|
||||
""",
|
||||
coupon_code,
|
||||
coupon_record["id"],
|
||||
)
|
||||
|
||||
if not coupon_record:
|
||||
return "<b>❌ Купон не найден</b> 🚫 или его использование ограничено. 🔒 Пожалуйста, проверьте код и попробуйте снова. 🔍"
|
||||
|
||||
usage_exists = await conn.fetchrow(
|
||||
await session.execute(
|
||||
"""
|
||||
SELECT 1 FROM coupon_usages WHERE coupon_id = $1 AND user_id = $2
|
||||
INSERT INTO coupon_usages (coupon_id, user_id, used_at)
|
||||
VALUES ($1, $2, $3)
|
||||
""",
|
||||
coupon_record["id"],
|
||||
user_id,
|
||||
datetime.utcnow(),
|
||||
)
|
||||
|
||||
if usage_exists:
|
||||
return "<b>❌ Вы уже активировали этот купон.</b> 🚫 Купоны могут быть активированы только один раз. 🔒"
|
||||
|
||||
coupon_amount = coupon_record["amount"]
|
||||
|
||||
async with conn.transaction():
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE coupons
|
||||
SET usage_count = usage_count + 1,
|
||||
is_used = CASE WHEN usage_count + 1 >= usage_limit AND usage_limit > 0 THEN TRUE ELSE FALSE END
|
||||
WHERE id = $1
|
||||
""",
|
||||
coupon_record["id"],
|
||||
)
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
INSERT INTO coupon_usages (coupon_id, user_id, used_at)
|
||||
VALUES ($1, $2, $3)
|
||||
""",
|
||||
coupon_record["id"],
|
||||
user_id,
|
||||
datetime.utcnow(),
|
||||
)
|
||||
|
||||
await update_balance(user_id, coupon_amount)
|
||||
return f"<b>✅ Купон успешно активирован! 🎉</b>\n\nНа ваш баланс добавлено <b>{coupon_amount} рублей</b> 💰."
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при активации купона: {e}")
|
||||
return "<b>⚠️ Произошла ошибка при активации купона! 🔧</b>\nПопробуйте ещё раз позже. 🕒"
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
await update_balance(user_id, coupon_amount)
|
||||
return f"<b>✅ Купон успешно активирован! 🎉</b>\n\nНа ваш баланс добавлено <b>{coupon_amount} рублей</b> 💰."
|
||||
|
||||
+6
-40
@@ -4,7 +4,6 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import RUB_TO_XTR
|
||||
from logger import logger
|
||||
|
||||
@@ -20,12 +19,7 @@ router = Router()
|
||||
|
||||
@router.callback_query(F.data == "donate")
|
||||
async def process_donate(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
|
||||
try:
|
||||
await state.clear()
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить сообщение: {e}")
|
||||
await state.clear()
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"))
|
||||
@@ -35,36 +29,26 @@ async def process_donate(callback_query: types.CallbackQuery, state: FSMContext)
|
||||
callback_data="enter_custom_donate_amount",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_profile"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="profile"))
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=callback_query.from_user.id,
|
||||
await callback_query.message.answer(
|
||||
text="🌟 Поддержите наш проект! 💪\n\n"
|
||||
"💖 Каждый донат помогает развивать и улучшать сервис. "
|
||||
"🤝 Мы ценим вашу поддержку и работаем над тем, чтобы сделать наш продукт еще лучше. 🚀💡",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_donate_amount")
|
||||
async def process_enter_donate_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="donate"))
|
||||
await callback_query.message.edit_text(f"💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup())
|
||||
await callback_query.message.answer(f"💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup())
|
||||
await state.set_state(DonateState.entering_donate_amount)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(DonateState.entering_donate_amount)
|
||||
async def process_donate_amount_input(message: types.Message, state: FSMContext):
|
||||
|
||||
try:
|
||||
await message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить сообщение: {e}")
|
||||
|
||||
if message.text.isdigit():
|
||||
amount = int(message.text)
|
||||
if amount // RUB_TO_XTR <= 0:
|
||||
@@ -91,7 +75,6 @@ async def process_donate_amount_input(message: types.Message, state: FSMContext)
|
||||
await state.set_state(DonateState.waiting_for_donate_payment)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании доната: {e}")
|
||||
await message.answer("Произошла ошибка при создании доната.")
|
||||
else:
|
||||
await message.answer("Некорректная сумма. Пожалуйста, введите сумму еще раз:")
|
||||
|
||||
@@ -104,31 +87,14 @@ async def on_pre_checkout_query(pre_checkout_query: PreCheckoutQuery):
|
||||
@router.message(F.successful_payment, DonateState.waiting_for_donate_payment)
|
||||
async def on_successful_donate(message: types.Message, state: FSMContext):
|
||||
try:
|
||||
user_id = int(message.from_user.id)
|
||||
amount = float(message.successful_payment.invoice_payload.split("_")[0])
|
||||
logger.debug(f"Donate succeeded for user_id: {user_id}, amount: {amount}")
|
||||
|
||||
state_data = await state.get_data()
|
||||
previous_message_id = state_data.get("last_message_id")
|
||||
|
||||
if previous_message_id:
|
||||
try:
|
||||
await bot.delete_message(chat_id=user_id, message_id=previous_message_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить сообщение: {e}")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Вернуться в профиль", callback_data="view_profile"))
|
||||
|
||||
sent_message = await bot.send_message(
|
||||
chat_id=user_id,
|
||||
builder.row(InlineKeyboardButton(text="Вернуться в профиль", callback_data="profile"))
|
||||
await message.answer(
|
||||
text=f"🙏 Спасибо за донат {amount} рублей! Ваша поддержка очень важна для нас. 💖",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await state.update_data(last_message_id=sent_message.message_id)
|
||||
await state.clear()
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"Ошибка конвертации user_id или amount: {e}")
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,114 +1,64 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
import asyncpg
|
||||
|
||||
from bot import bot
|
||||
from config import CONNECT_WINDOWS, DATABASE_URL, SUPPORT_CHAT_URL
|
||||
from config import CONNECT_WINDOWS, SUPPORT_CHAT_URL
|
||||
from handlers.texts import INSTRUCTION_PC, INSTRUCTIONS, KEY_MESSAGE
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "instructions")
|
||||
async def send_instructions(callback_query: types.CallbackQuery):
|
||||
await callback_query.message.delete()
|
||||
|
||||
instructions_message = INSTRUCTIONS
|
||||
|
||||
image_path = os.path.join("img", "instructions.jpg")
|
||||
|
||||
if not os.path.isfile(image_path):
|
||||
await callback_query.message.answer("Файл изображения не найден.")
|
||||
await callback_query.answer()
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"),
|
||||
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="profile"),
|
||||
)
|
||||
|
||||
with open(image_path, "rb") as image_from_buffer:
|
||||
await callback_query.message.answer_photo(
|
||||
await callback_query.answer_photo(
|
||||
BufferedInputFile(image_from_buffer.read(), filename="instructions.jpg"),
|
||||
caption=instructions_message,
|
||||
parse_mode="Markdown",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("connect_pc|"))
|
||||
async def process_connect_pc(callback_query: types.CallbackQuery):
|
||||
async def process_connect_pc(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.from_user.id
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
try:
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
record = await session.fetchrow(
|
||||
"""
|
||||
SELECT k.key
|
||||
FROM keys k
|
||||
WHERE k.tg_id = $1 AND k.email = $2
|
||||
""",
|
||||
tg_id,
|
||||
key_name,
|
||||
)
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
# Поиск ключа по имени ключа
|
||||
record = await conn.fetchrow(
|
||||
"""
|
||||
SELECT k.key
|
||||
FROM keys k
|
||||
WHERE k.tg_id = $1 AND k.email = $2
|
||||
""",
|
||||
tg_id,
|
||||
key_name,
|
||||
)
|
||||
if not record:
|
||||
await callback_query.message.answer("❌ <b>Ключ не найден. Проверьте имя ключа.</b> 🔍")
|
||||
return
|
||||
|
||||
if not record:
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text="<b>Ключ не найден. Проверьте имя ключа.</b>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
return
|
||||
key = record["key"]
|
||||
key_message = KEY_MESSAGE.format(key)
|
||||
instruction_message = f"{key_message}{INSTRUCTION_PC}"
|
||||
|
||||
key = record["key"]
|
||||
key_message = KEY_MESSAGE.format(key)
|
||||
instruction_message = f"{key_message}{INSTRUCTION_PC}"
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}"))
|
||||
builder.row(InlineKeyboardButton(text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад в профиль", callback_data="profile"))
|
||||
|
||||
connect_windows_button = types.InlineKeyboardButton(
|
||||
text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}"
|
||||
)
|
||||
|
||||
support_button = types.InlineKeyboardButton(text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}")
|
||||
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад в профиль", callback_data="view_profile")
|
||||
|
||||
inline_keyboard = [
|
||||
[connect_windows_button],
|
||||
[support_button],
|
||||
[back_button],
|
||||
]
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=inline_keyboard)
|
||||
|
||||
await bot.send_message(
|
||||
tg_id,
|
||||
instruction_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении ключа: {e}")
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text="<b>Произошла ошибка. Пожалуйста, повторите попытку позже.</b>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer(instruction_message, reply_markup=builder.as_markup())
|
||||
|
||||
+40
-107
@@ -1,27 +1,16 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
import uuid
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message
|
||||
import asyncpg
|
||||
|
||||
from bot import bot, dp
|
||||
from config import (
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DATABASE_URL,
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
PUBLIC_LINK,
|
||||
SUPPORT_CHAT_URL,
|
||||
)
|
||||
from config import CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, SUPPORT_CHAT_URL
|
||||
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_cluster
|
||||
from handlers.profile import process_callback_view_profile
|
||||
from handlers.texts import KEY, KEY_TRIAL, NULL_BALANCE, RENEWAL_PLANS, key_message_success
|
||||
from handlers.utils import get_least_loaded_cluster, sanitize_key_name
|
||||
from logger import logger
|
||||
@@ -36,39 +25,24 @@ class Form(StatesGroup):
|
||||
waiting_for_message = State()
|
||||
|
||||
|
||||
@dp.callback_query(F.data == "create_key")
|
||||
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
try:
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@router.callback_query(F.data == "create_key")
|
||||
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext, session: Any):
|
||||
server_id = "все сервера"
|
||||
await state.update_data(selected_server_id=server_id)
|
||||
await select_server(callback_query, state)
|
||||
await callback_query.answer()
|
||||
await select_server(callback_query, state, session)
|
||||
|
||||
|
||||
async def select_server(callback_query: CallbackQuery, state: FSMContext):
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
existing_connection = await conn.fetchrow(
|
||||
"SELECT trial FROM connections WHERE tg_id = $1",
|
||||
callback_query.from_user.id,
|
||||
)
|
||||
finally:
|
||||
await conn.close()
|
||||
async def select_server(callback_query: CallbackQuery, state: FSMContext, session: Any):
|
||||
existing_connection = await session.fetchrow(
|
||||
"SELECT trial FROM connections WHERE tg_id = $1",
|
||||
callback_query.from_user.id,
|
||||
)
|
||||
|
||||
trial_status = existing_connection["trial"] if existing_connection else 0
|
||||
|
||||
if trial_status == 1:
|
||||
await bot.send_message(
|
||||
chat_id=callback_query.from_user.id,
|
||||
await callback_query.message.answer(
|
||||
text=KEY,
|
||||
parse_mode="HTML",
|
||||
reply_markup=InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
@@ -77,23 +51,17 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext):
|
||||
callback_data="confirm_create_new_key",
|
||||
)
|
||||
],
|
||||
[InlineKeyboardButton(text="↩️ Назад", callback_data="cancel_create_key")],
|
||||
[InlineKeyboardButton(text="↩️ Назад", callback_data="profile")],
|
||||
]
|
||||
),
|
||||
)
|
||||
await state.update_data(creating_new_key=True)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=callback_query.from_user.id,
|
||||
text=KEY_TRIAL,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await callback_query.message.answer(KEY_TRIAL)
|
||||
await state.set_state(Form.waiting_for_key_name)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@dp.callback_query(F.data == "confirm_create_new_key")
|
||||
@router.callback_query(F.data == "confirm_create_new_key")
|
||||
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
@@ -101,7 +69,7 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < RENEWAL_PLANS["1"]["price"]:
|
||||
replenish_button = InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||
replenish_button = InlineKeyboardButton(text="Перейти в профиль", callback_data="profile")
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
||||
await callback_query.message.edit_text(NULL_BALANCE, reply_markup=keyboard)
|
||||
await state.clear()
|
||||
@@ -114,17 +82,9 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex
|
||||
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, admin: bool):
|
||||
await process_callback_view_profile(callback_query, state, admin)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(Form.waiting_for_key_name)
|
||||
async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
async def handle_key_name_input(message: Message, state: FSMContext, session: Any):
|
||||
tg_id = message.from_user.id
|
||||
key_name = sanitize_key_name(message.text)
|
||||
|
||||
@@ -135,36 +95,27 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
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 for user {tg_id} in the database.")
|
||||
existing_key = await conn.fetchrow(
|
||||
"SELECT * FROM keys WHERE email = $1 AND tg_id = $2",
|
||||
key_name.lower(),
|
||||
tg_id,
|
||||
logger.info(f"Checking if key name '{key_name}' already exists for user {tg_id} in the database.")
|
||||
existing_key = await session.fetchrow(
|
||||
"SELECT * FROM keys WHERE email = $1 AND tg_id = $2",
|
||||
key_name.lower(),
|
||||
tg_id,
|
||||
)
|
||||
if existing_key:
|
||||
await message.answer(
|
||||
"❌ Упс! Это имя уже используется. Выберите другое уникальное название для ключа.",
|
||||
)
|
||||
if existing_key:
|
||||
await message.bot.send_message(
|
||||
tg_id,
|
||||
"❌ Упс! Это имя уже используется. Выберите другое уникальное название для ключа.",
|
||||
)
|
||||
logger.warning(f"Key name '{key_name}' already exists for user {tg_id}.")
|
||||
await state.set_state(Form.waiting_for_key_name)
|
||||
return
|
||||
finally:
|
||||
await conn.close()
|
||||
logger.warning(f"Key name '{key_name}' already exists for user {tg_id}.")
|
||||
await state.set_state(Form.waiting_for_key_name)
|
||||
return
|
||||
|
||||
client_id = str(uuid.uuid4())
|
||||
email = key_name.lower()
|
||||
current_time = datetime.utcnow()
|
||||
expiry_time = None
|
||||
|
||||
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)
|
||||
finally:
|
||||
await conn.close()
|
||||
logger.info(f"Checking trial status for user {tg_id}.")
|
||||
existing_connection = await session.fetchrow("SELECT trial FROM connections WHERE tg_id = $1", tg_id)
|
||||
|
||||
trial_status = existing_connection["trial"] if existing_connection else 0
|
||||
|
||||
@@ -174,10 +125,9 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
else:
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < RENEWAL_PLANS["1"]["price"]:
|
||||
replenish_button = InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||
replenish_button = InlineKeyboardButton(text="Перейти в профиль", callback_data="profile")
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
||||
await message.bot.send_message(
|
||||
tg_id,
|
||||
await message.answer(
|
||||
"💳 Недостаточно средств для создания подписки на новое устройство. Пополните баланс в личном кабинете.",
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
@@ -196,7 +146,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
button_support = InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL)
|
||||
|
||||
button_profile = InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
|
||||
button_profile = InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
|
||||
button_iphone = InlineKeyboardButton(text="🍏 Подключить", url=f"{CONNECT_IOS}{public_link}")
|
||||
button_android = InlineKeyboardButton(
|
||||
text="🤖 Подключить",
|
||||
@@ -224,7 +174,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
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)
|
||||
await message.answer(key_message, reply_markup=keyboard)
|
||||
|
||||
try:
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
@@ -244,16 +194,12 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
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)
|
||||
if existing_connection:
|
||||
await conn.execute("UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id)
|
||||
else:
|
||||
await add_connection(tg_id, 0, 1)
|
||||
finally:
|
||||
await conn.close()
|
||||
logger.info(f"Updating trial status for user {tg_id} in the database.")
|
||||
existing_connection = await session.fetchrow("SELECT * FROM connections WHERE tg_id = $1", tg_id)
|
||||
if existing_connection:
|
||||
await session.execute("UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id)
|
||||
else:
|
||||
await add_connection(tg_id, 0, 1)
|
||||
|
||||
logger.info(f"Storing key for user {tg_id} in the database.")
|
||||
await store_key(
|
||||
@@ -267,17 +213,4 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@dp.callback_query(F.data == "back_to_main")
|
||||
async def handle_back_to_main(callback_query: CallbackQuery, state: FSMContext, admin: bool):
|
||||
await process_callback_view_profile(callback_query, state, admin)
|
||||
await callback_query.answer()
|
||||
|
||||
@@ -79,15 +79,11 @@ async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, to
|
||||
raise e
|
||||
|
||||
|
||||
async def delete_key_from_db(client_id):
|
||||
"""Удаление ключа из базы данных"""
|
||||
async def delete_key_from_db(client_id, session):
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
await conn.execute("DELETE FROM keys WHERE client_id = $1", client_id)
|
||||
await session.execute("DELETE FROM keys WHERE client_id = $1", client_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
|
||||
async def delete_key_from_cluster(cluster_id, email, client_id):
|
||||
|
||||
+315
-485
@@ -2,22 +2,12 @@ import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile
|
||||
import asyncpg
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CLUSTERS,
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DATABASE_URL,
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
PUBLIC_LINK,
|
||||
TOTAL_GB,
|
||||
)
|
||||
from config import CLUSTERS, CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, TOTAL_GB
|
||||
from database import delete_key, get_balance, store_key, update_balance, update_key_expiry
|
||||
from handlers.keys.key_utils import (
|
||||
delete_key_from_cluster,
|
||||
@@ -43,207 +33,157 @@ router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "view_keys")
|
||||
async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
async def process_callback_view_keys(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
records = await conn.fetch(
|
||||
"""
|
||||
SELECT email, client_id FROM keys WHERE tg_id = $1
|
||||
""",
|
||||
tg_id,
|
||||
)
|
||||
records = await session.fetch(
|
||||
"""
|
||||
SELECT email, client_id FROM keys WHERE tg_id = $1
|
||||
""",
|
||||
tg_id,
|
||||
)
|
||||
|
||||
if records:
|
||||
buttons = []
|
||||
for record in records:
|
||||
key_name = record["email"]
|
||||
button = types.InlineKeyboardButton(
|
||||
text=f"🔑 {key_name}",
|
||||
callback_data=f"view_key|{key_name}",
|
||||
)
|
||||
|
||||
buttons.append([button])
|
||||
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад", callback_data="view_profile")
|
||||
buttons.append([back_button])
|
||||
|
||||
inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||
response_message = (
|
||||
"<b>🔑 Список ваших устройств</b>\n\n" "<i>👇 Выберите устройство для управления подпиской:</i>"
|
||||
if records:
|
||||
buttons = []
|
||||
for record in records:
|
||||
key_name = record["email"]
|
||||
button = types.InlineKeyboardButton(
|
||||
text=f"🔑 {key_name}",
|
||||
callback_data=f"view_key|{key_name}",
|
||||
)
|
||||
|
||||
image_path = os.path.join("img", "pic_keys.jpg")
|
||||
buttons.append([button])
|
||||
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад", callback_data="profile")
|
||||
buttons.append([back_button])
|
||||
|
||||
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,
|
||||
inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||
response_message = (
|
||||
"<b>🔑 Список ваших устройств</b>\n\n" "<i>👇 Выберите устройство для управления подпиской:</i>"
|
||||
)
|
||||
|
||||
image_path = os.path.join("img", "pic_keys.jpg")
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await callback_query.message.answer_photo(
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"),
|
||||
caption=response_message,
|
||||
reply_markup=inline_keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
else:
|
||||
response_message = NO_KEYS
|
||||
create_key_button = types.InlineKeyboardButton(text="➕ Создать подписку", callback_data="create_key")
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад", callback_data="view_profile")
|
||||
await callback_query.message.answer(
|
||||
text=response_message,
|
||||
reply_markup=inline_keyboard,
|
||||
)
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button], [back_button]])
|
||||
else:
|
||||
response_message = NO_KEYS
|
||||
create_key_button = types.InlineKeyboardButton(text="➕ Создать подписку", callback_data="create_key")
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад", callback_data="profile")
|
||||
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button], [back_button]])
|
||||
|
||||
image_path = os.path.join("img", "pic_keys.jpg")
|
||||
image_path = os.path.join("img", "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,
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await callback_query.message.answer_photo(
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"),
|
||||
caption=response_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
text=response_message,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
except Exception as e:
|
||||
await handle_error(tg_id, callback_query, f"Ошибка при получении ключей: {e}")
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("view_key|"))
|
||||
async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
async def process_callback_view_key(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.from_user.id
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
try:
|
||||
try:
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception:
|
||||
pass
|
||||
record = await session.fetchrow(
|
||||
"""
|
||||
SELECT k.expiry_time, k.server_id, k.key
|
||||
FROM keys k
|
||||
WHERE k.tg_id = $1 AND k.email = $2
|
||||
""",
|
||||
tg_id,
|
||||
key_name,
|
||||
)
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow(
|
||||
"""
|
||||
SELECT k.expiry_time, k.server_id, k.key
|
||||
FROM keys k
|
||||
WHERE k.tg_id = $1 AND k.email = $2
|
||||
""",
|
||||
tg_id,
|
||||
key_name,
|
||||
if record:
|
||||
key = record["key"]
|
||||
expiry_time = record["expiry_time"]
|
||||
server_name = record["server_id"]
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
if time_left.total_seconds() <= 0:
|
||||
days_left_message = "<b>🕒 Статус подписки:</b>\n🔴 Истекла\nОсталось часов: 0"
|
||||
elif time_left.days > 0:
|
||||
days_left_message = f"Осталось дней: <b>{time_left.days}</b>"
|
||||
else:
|
||||
hours_left = time_left.seconds // 3600
|
||||
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
||||
|
||||
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
|
||||
response_message = key_message(key, formatted_expiry_date, days_left_message, server_name)
|
||||
|
||||
download_android_button = types.InlineKeyboardButton(text="🤖 Скачать", url=DOWNLOAD_ANDROID)
|
||||
download_iphone_button = types.InlineKeyboardButton(text="🍏 Скачать", url=DOWNLOAD_IOS)
|
||||
|
||||
connect_iphone_button = types.InlineKeyboardButton(text="🍏 Подключить", url=f"{CONNECT_IOS}{key}")
|
||||
connect_android_button = types.InlineKeyboardButton(text="🤖 Подключить", url=f"{CONNECT_ANDROID}{key}")
|
||||
|
||||
connect_pc_button = types.InlineKeyboardButton(
|
||||
text="💻 Windows/Linux",
|
||||
callback_data=f"connect_pc|{key_name}",
|
||||
)
|
||||
|
||||
if record:
|
||||
key = record["key"]
|
||||
expiry_time = record["expiry_time"]
|
||||
server_name = record["server_id"]
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
renew_button = types.InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}")
|
||||
delete_button = types.InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete_key|{key_name}")
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад в профиль", callback_data="profile")
|
||||
|
||||
if time_left.total_seconds() <= 0:
|
||||
days_left_message = "<b>🕒 Статус подписки:</b>\n🔴 Истекла\nОсталось часов: 0"
|
||||
elif time_left.days > 0:
|
||||
days_left_message = f"Осталось дней: <b>{time_left.days}</b>"
|
||||
else:
|
||||
hours_left = time_left.seconds // 3600
|
||||
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
||||
inline_keyboard = [
|
||||
[download_iphone_button, download_android_button],
|
||||
[connect_iphone_button, connect_android_button],
|
||||
[connect_pc_button],
|
||||
[renew_button, delete_button],
|
||||
]
|
||||
|
||||
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
|
||||
response_message = key_message(key, formatted_expiry_date, days_left_message, server_name)
|
||||
|
||||
download_android_button = types.InlineKeyboardButton(text="🤖 Скачать", url=DOWNLOAD_ANDROID)
|
||||
download_iphone_button = types.InlineKeyboardButton(text="🍏 Скачать", url=DOWNLOAD_IOS)
|
||||
|
||||
connect_iphone_button = types.InlineKeyboardButton(text="🍏 Подключить", url=f"{CONNECT_IOS}{key}")
|
||||
connect_android_button = types.InlineKeyboardButton(text="🤖 Подключить", url=f"{CONNECT_ANDROID}{key}")
|
||||
|
||||
connect_pc_button = types.InlineKeyboardButton(
|
||||
text="💻 Windows/Linux",
|
||||
callback_data=f"connect_pc|{key_name}",
|
||||
if not key.startswith(PUBLIC_LINK):
|
||||
update_subscription_button = types.InlineKeyboardButton(
|
||||
text="🔄 Обновить подписку",
|
||||
callback_data=f"update_subscription|{key_name}",
|
||||
)
|
||||
inline_keyboard.append([update_subscription_button])
|
||||
|
||||
renew_button = types.InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}")
|
||||
delete_button = types.InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete_key|{key_name}")
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад в профиль", callback_data="view_profile")
|
||||
inline_keyboard.append([back_button])
|
||||
|
||||
inline_keyboard = [
|
||||
[download_iphone_button, download_android_button],
|
||||
[connect_iphone_button, connect_android_button],
|
||||
[connect_pc_button],
|
||||
[renew_button, delete_button],
|
||||
]
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=inline_keyboard)
|
||||
|
||||
if not key.startswith(PUBLIC_LINK):
|
||||
update_subscription_button = types.InlineKeyboardButton(
|
||||
text="🔄 Обновить подписку",
|
||||
callback_data=f"update_subscription|{key_name}",
|
||||
)
|
||||
inline_keyboard.append([update_subscription_button])
|
||||
image_path = os.path.join("img", "pic_view.jpg")
|
||||
|
||||
inline_keyboard.append([back_button])
|
||||
if not os.path.isfile(image_path):
|
||||
await callback_query.message.answer("Файл изображения не найден.")
|
||||
return
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=inline_keyboard)
|
||||
|
||||
image_path = os.path.join("img", "pic_view.jpg")
|
||||
|
||||
if not os.path.isfile(image_path):
|
||||
await bot.send_message(tg_id, "Файл изображения не найден.")
|
||||
return
|
||||
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
chat_id=tg_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_view.jpg"),
|
||||
caption=response_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text="<b>Информация о подписке не найдена.</b>",
|
||||
parse_mode="HTML",
|
||||
with open(image_path, "rb") as image_file:
|
||||
await callback_query.message.answer_photo(
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_view.jpg"),
|
||||
caption=response_message,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
text="<b>Информация о подписке не найдена.</b>",
|
||||
)
|
||||
except Exception as e:
|
||||
await handle_error(
|
||||
tg_id,
|
||||
@@ -251,127 +191,85 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
f"Ошибка при получении информации о ключе: {e}",
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("update_subscription|"))
|
||||
async def process_callback_update_subscription(
|
||||
callback_query: types.CallbackQuery,
|
||||
):
|
||||
async def process_callback_update_subscription(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.from_user.id
|
||||
email = callback_query.data.split("|")[1]
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.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,
|
||||
)
|
||||
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}"
|
||||
if record:
|
||||
expiry_time = record["expiry_time"]
|
||||
client_id = record["client_id"]
|
||||
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
|
||||
|
||||
try:
|
||||
await conn.execute(
|
||||
"""
|
||||
DELETE FROM keys
|
||||
WHERE tg_id = $1 AND email = $2
|
||||
""",
|
||||
tg_id,
|
||||
email,
|
||||
)
|
||||
except Exception as delete_error:
|
||||
await bot.send_message(
|
||||
tg_id,
|
||||
f"Ошибка при удалении старой подписки: {delete_error}",
|
||||
)
|
||||
return
|
||||
|
||||
least_loaded_cluster_id = await get_least_loaded_cluster()
|
||||
|
||||
tasks = []
|
||||
tasks.append(
|
||||
update_key_on_cluster(
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_time,
|
||||
least_loaded_cluster_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
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
least_loaded_cluster_id = await get_least_loaded_cluster()
|
||||
|
||||
await store_key(
|
||||
tasks = []
|
||||
tasks.append(
|
||||
update_key_on_cluster(
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_time,
|
||||
public_link,
|
||||
server_id=least_loaded_cluster_id,
|
||||
least_loaded_cluster_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 asyncio.gather(*tasks)
|
||||
|
||||
response_message = f"Ваша подписка {email} обновлена!"
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад в профиль", callback_data="view_profile")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await bot.send_message(
|
||||
tg_id,
|
||||
response_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
else:
|
||||
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(
|
||||
tg_id,
|
||||
"<b>Ключ не найден в базе данных.</b>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
await store_key(
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_time,
|
||||
public_link,
|
||||
server_id=least_loaded_cluster_id,
|
||||
)
|
||||
response_message = f"Ваша подписка {email} обновлена!"
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад в профиль", callback_data="profile")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await callback_query.message.answer(
|
||||
response_message,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
"<b>Ключ не найден в базе данных.</b>",
|
||||
)
|
||||
except Exception as e:
|
||||
await handle_error(tg_id, callback_query, f"Ошибка при обновлении подписки: {e}")
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("delete_key|"))
|
||||
async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
client_id = callback_query.data.split("|")[1]
|
||||
|
||||
try:
|
||||
try:
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
confirmation_keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
@@ -384,176 +282,128 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
]
|
||||
)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
await callback_query.message.answer(
|
||||
text="<b>Вы уверены, что хотите удалить ключ?</b>",
|
||||
reply_markup=confirmation_keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text=f"<b>Ошибка при удалении ключа:</b> {e}",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
logger.error(e)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("renew_key|"))
|
||||
async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||
async def process_callback_renew_key(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.from_user.id
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
try:
|
||||
try:
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow(
|
||||
"""
|
||||
SELECT client_id, expiry_time
|
||||
FROM keys
|
||||
WHERE email = $1
|
||||
""",
|
||||
key_name,
|
||||
)
|
||||
|
||||
if record:
|
||||
client_id = record["client_id"]
|
||||
expiry_time = record["expiry_time"]
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text=f'📅 1 месяц ({RENEWAL_PLANS["1"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|1|{client_id}",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text=f'📅 3 месяца ({RENEWAL_PLANS["3"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|3|{client_id}",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text=f'📅 6 месяцев ({RENEWAL_PLANS["6"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|6|{client_id}",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text=f'📅 12 месяцев ({RENEWAL_PLANS["12"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|12|{client_id}",
|
||||
)
|
||||
],
|
||||
[types.InlineKeyboardButton(text="🔙 Назад", callback_data="view_profile")],
|
||||
]
|
||||
)
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
response_message = PLAN_SELECTION_MSG.format(
|
||||
balance=balance,
|
||||
expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text=response_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
else:
|
||||
# Если ключ не найден
|
||||
response_message = "<b>Ключ не найден.</b>"
|
||||
await bot.send_message(chat_id=tg_id, text=response_message, parse_mode="HTML")
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text=f"<b>Ошибка при выборе плана:</b> {e}",
|
||||
parse_mode="HTML",
|
||||
record = await session.fetchrow(
|
||||
"""
|
||||
SELECT client_id, expiry_time
|
||||
FROM keys
|
||||
WHERE email = $1
|
||||
""",
|
||||
key_name,
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
if record:
|
||||
client_id = record["client_id"]
|
||||
expiry_time = record["expiry_time"]
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text=f'📅 1 месяц ({RENEWAL_PLANS["1"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|1|{client_id}",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text=f'📅 3 месяца ({RENEWAL_PLANS["3"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|3|{client_id}",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text=f'📅 6 месяцев ({RENEWAL_PLANS["6"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|6|{client_id}",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text=f'📅 12 месяцев ({RENEWAL_PLANS["12"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|12|{client_id}",
|
||||
)
|
||||
],
|
||||
[types.InlineKeyboardButton(text="🔙 Назад", callback_data="profile")],
|
||||
]
|
||||
)
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
response_message = PLAN_SELECTION_MSG.format(
|
||||
balance=balance,
|
||||
expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=response_message,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await callback_query.message.answer("<b>Ключ не найден.</b>")
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("confirm_delete|"))
|
||||
async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
async def process_callback_confirm_delete(callback_query: types.CallbackQuery, session: Any):
|
||||
email = callback_query.data.split("|")[1]
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow("SELECT client_id FROM keys WHERE email = $1", email)
|
||||
record = await session.fetchrow("SELECT client_id FROM keys WHERE email = $1", email)
|
||||
|
||||
if record:
|
||||
client_id = record["client_id"]
|
||||
response_message = "Ключ успешно удален."
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
if record:
|
||||
client_id = record["client_id"]
|
||||
response_message = "Ключ успешно удален."
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await delete_key(client_id)
|
||||
await bot.edit_message_text(
|
||||
response_message,
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
await delete_key(client_id)
|
||||
await callback_query.message.answer(
|
||||
response_message,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
|
||||
async def delete_key_from_servers():
|
||||
try:
|
||||
tasks = []
|
||||
for cluster_id, cluster in CLUSTERS.items():
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
async def delete_key_from_servers():
|
||||
try:
|
||||
tasks = []
|
||||
for cluster_id, cluster in CLUSTERS.items():
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении ключа {client_id}: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении ключа {client_id}: {e}")
|
||||
|
||||
asyncio.create_task(delete_key_from_servers())
|
||||
asyncio.create_task(delete_key_from_servers())
|
||||
|
||||
await delete_key_from_db(client_id)
|
||||
await delete_key_from_db(client_id, session)
|
||||
|
||||
else:
|
||||
response_message = "Ключ не найден или уже удален."
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await bot.edit_message_text(
|
||||
response_message,
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
else:
|
||||
response_message = "Ключ не найден или уже удален."
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await callback_query.message.answer(
|
||||
response_message,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
except Exception as e:
|
||||
await bot.edit_message_text(
|
||||
f"Ошибка при удалении ключа: {e}",
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
logger.error(e)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("renew_plan|"))
|
||||
async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
async def process_callback_renew_plan(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.from_user.id
|
||||
plan, client_id = (
|
||||
callback_query.data.split("|")[1],
|
||||
@@ -565,83 +415,63 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
total_gb = TOTAL_GB * gb_multiplier.get(plan, 1) if TOTAL_GB > 0 else 0
|
||||
|
||||
try:
|
||||
try:
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
record = await session.fetchrow(
|
||||
"SELECT email, expiry_time FROM keys WHERE client_id = $1",
|
||||
client_id,
|
||||
)
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow(
|
||||
"SELECT email, expiry_time FROM keys WHERE client_id = $1",
|
||||
client_id,
|
||||
)
|
||||
|
||||
if record:
|
||||
email = record["email"]
|
||||
expiry_time = record["expiry_time"]
|
||||
current_time = datetime.utcnow().timestamp() * 1000
|
||||
|
||||
if expiry_time <= current_time:
|
||||
new_expiry_time = int(current_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
||||
else:
|
||||
new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
||||
|
||||
cost = RENEWAL_PLANS[plan]["price"]
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < cost:
|
||||
replenish_button = types.InlineKeyboardButton(text="Пополнить баланс", callback_data="pay")
|
||||
view_profile = types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [view_profile]])
|
||||
|
||||
await bot.send_message(
|
||||
tg_id,
|
||||
INSUFFICIENT_FUNDS_MSG,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
return
|
||||
|
||||
response_message = SUCCESS_RENEWAL_MSG.format(months=RENEWAL_PLANS[plan]["months"])
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_profile")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await bot.send_message(
|
||||
tg_id,
|
||||
response_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
async def renew_key_on_servers():
|
||||
tasks = []
|
||||
for cluster_id in CLUSTERS:
|
||||
task = asyncio.create_task(
|
||||
renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
client_id,
|
||||
new_expiry_time,
|
||||
total_gb,
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await update_balance(tg_id, -cost)
|
||||
await update_key_expiry(client_id, new_expiry_time)
|
||||
|
||||
await renew_key_on_servers()
|
||||
if record:
|
||||
email = record["email"]
|
||||
expiry_time = record["expiry_time"]
|
||||
current_time = datetime.utcnow().timestamp() * 1000
|
||||
|
||||
if expiry_time <= current_time:
|
||||
new_expiry_time = int(current_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
||||
else:
|
||||
await bot.send_message(tg_id, KEY_NOT_FOUND_MSG, parse_mode="HTML")
|
||||
new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
cost = RENEWAL_PLANS[plan]["price"]
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < cost:
|
||||
replenish_button = types.InlineKeyboardButton(text="Пополнить баланс", callback_data="pay")
|
||||
view_profile = types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [view_profile]])
|
||||
|
||||
await callback_query.message.answer(
|
||||
INSUFFICIENT_FUNDS_MSG,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
return
|
||||
|
||||
response_message = SUCCESS_RENEWAL_MSG.format(months=RENEWAL_PLANS[plan]["months"])
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="profile")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await callback_query.message.answer(response_message, reply_markup=keyboard)
|
||||
|
||||
async def renew_key_on_servers():
|
||||
tasks = []
|
||||
for cluster_id in CLUSTERS:
|
||||
task = asyncio.create_task(
|
||||
renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
client_id,
|
||||
new_expiry_time,
|
||||
total_gb,
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await update_balance(tg_id, -cost)
|
||||
await update_key_expiry(client_id, new_expiry_time)
|
||||
|
||||
await renew_key_on_servers()
|
||||
|
||||
else:
|
||||
await callback_query.message.answer(KEY_NOT_FOUND_MSG)
|
||||
except Exception as e:
|
||||
await bot.send_message(tg_id, f"Ошибка при продлении ключа: {e}", parse_mode="HTML")
|
||||
|
||||
await callback_query.answer()
|
||||
logger.error(e)
|
||||
|
||||
@@ -112,7 +112,7 @@ async def notify_10h_keys(
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет",
|
||||
callback_data="view_profile",
|
||||
callback_data="profile",
|
||||
)
|
||||
],
|
||||
]
|
||||
@@ -192,7 +192,7 @@ async def notify_24h_keys(
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет",
|
||||
callback_data="view_profile",
|
||||
callback_data="profile",
|
||||
)
|
||||
],
|
||||
]
|
||||
@@ -245,7 +245,7 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
"💡 Не откладывайте подключение VPN!"
|
||||
)
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")]]
|
||||
inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]]
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
+1
-15
@@ -3,15 +3,12 @@ from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, ROBOKASSA_ENABLE, STARS_ENABLE, YOOKASSA_ENABLE
|
||||
from database import get_trial
|
||||
from handlers.start import send_welcome_message
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay")
|
||||
async def handle_pay(callback_query: CallbackQuery):
|
||||
await callback_query.message.delete()
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if YOOKASSA_ENABLE:
|
||||
@@ -51,23 +48,12 @@ async def handle_pay(callback_query: CallbackQuery):
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🎟️ Активировать купон", callback_data="activate_coupon"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"💸 <b>Выберите удобный способ пополнения баланса:</b>\n\n"
|
||||
"• Быстро и безопасно\n"
|
||||
"• Поддержка разных платежных систем\n"
|
||||
"• Моментальное зачисление средств 🚀",
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "back_to_menu")
|
||||
async def handle_back_to_menu(callback_query: CallbackQuery, admin: bool = False):
|
||||
await callback_query.message.delete()
|
||||
trial_status = await get_trial(callback_query.from_user.id)
|
||||
await send_welcome_message(callback_query.from_user.id, trial_status, admin)
|
||||
await callback_query.answer()
|
||||
|
||||
@@ -6,9 +6,9 @@ from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiohttp import web
|
||||
|
||||
from bot import bot
|
||||
from config import CRYPTO_BOT_ENABLE, CRYPTO_BOT_TOKEN, RUB_TO_USDT
|
||||
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from logger import logger
|
||||
|
||||
@@ -24,31 +24,9 @@ class ReplenishBalanceState(StatesGroup):
|
||||
entering_custom_amount_crypto = State()
|
||||
|
||||
|
||||
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key="last_message_id"):
|
||||
if state:
|
||||
try:
|
||||
state_data = await state.get_data()
|
||||
previous_message_id = state_data.get(message_key)
|
||||
|
||||
if previous_message_id:
|
||||
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
|
||||
|
||||
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
|
||||
await state.update_data({message_key: sent_message.message_id})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении/отправке сообщения: {e}")
|
||||
return None
|
||||
|
||||
return sent_message
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_cryptobot")
|
||||
async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for i in range(0, len(PAYMENT_OPTIONS), 2):
|
||||
if i + 1 < len(PAYMENT_OPTIONS):
|
||||
builder.row(
|
||||
@@ -75,27 +53,16 @@ async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, st
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile"))
|
||||
|
||||
key_count = await get_key_count(tg_id)
|
||||
|
||||
key_count = await get_key_count(callback_query.from_user.id)
|
||||
if key_count == 0:
|
||||
exists = await check_connection_exists(tg_id)
|
||||
exists = await check_connection_exists(callback_query.from_user.id)
|
||||
if not exists:
|
||||
await add_connection(tg_id, balance=0.0, trial=0)
|
||||
|
||||
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="Выберите сумму пополнения:",
|
||||
await add_connection(callback_query.from_user.id, balance=0.0, trial=0)
|
||||
await callback_query.message.answer(
|
||||
"Выберите сумму пополнения:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await state.set_state(ReplenishBalanceState.choosing_amount_crypto)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("crypto_amount|"))
|
||||
@@ -103,11 +70,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
data = callback_query.data.split("|", 1)
|
||||
|
||||
if len(data) != 2:
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
await callback_query.message.answer("Неверные данные для выбора суммы.")
|
||||
return
|
||||
|
||||
@@ -115,11 +77,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
try:
|
||||
amount = int(amount_str)
|
||||
except ValueError:
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
await callback_query.message.answer("Некорректная сумма.")
|
||||
return
|
||||
|
||||
@@ -127,11 +84,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_crypto)
|
||||
|
||||
try:
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
invoice = await crypto.create_invoice(
|
||||
asset="USDT",
|
||||
amount=str(int(amount // RUB_TO_USDT)),
|
||||
@@ -143,8 +95,7 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
|
||||
await bot.send_message(
|
||||
chat_id=callback_query.from_user.id,
|
||||
await callback_query.message.answer(
|
||||
text=f"Вы выбрали пополнение на {amount} рублей.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
@@ -152,22 +103,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
await callback_query.message.answer("Ошибка при создании платежа.")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании платежа: {e}")
|
||||
await callback_query.message.answer("Произошла ошибка при создании платежа.")
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
async def send_payment_success_notification(user_id: int, amount: float):
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
|
||||
|
||||
|
||||
async def cryptobot_webhook(request):
|
||||
@@ -204,9 +139,8 @@ async def process_crypto_payment(payload):
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_crypto")
|
||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||
await callback_query.message.answer(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_crypto)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.entering_custom_amount_crypto)
|
||||
@@ -233,18 +167,11 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"),
|
||||
)
|
||||
await message.message.edit_text(
|
||||
await message.answer(
|
||||
text=f"Вы выбрали пополнение на {amount} рублей.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await send_message_with_deletion(
|
||||
message.from_user.id,
|
||||
"Ошибка при создании платежа.",
|
||||
state=state,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании платежа: {e}")
|
||||
await message.answer("Произошла ошибка при создании платежа.")
|
||||
else:
|
||||
await message.answer("Некорректная сумма. Пожалуйста, введите сумму еще раз:")
|
||||
|
||||
@@ -12,9 +12,9 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiohttp import web
|
||||
import requests
|
||||
|
||||
from bot import bot
|
||||
from config import FREEKASSA_API_KEY, FREEKASSA_SHOP_ID
|
||||
from database import add_payment, update_balance
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
|
||||
router = Router()
|
||||
@@ -65,16 +65,6 @@ async def create_payment(user_id, amount, email, ip):
|
||||
return None
|
||||
|
||||
|
||||
async def send_payment_success_notification(user_id, amount):
|
||||
try:
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
|
||||
|
||||
|
||||
async def freekassa_webhook(request):
|
||||
data = await request.json()
|
||||
logging.debug(f"Получен вебхук от FreeKassa: {data}")
|
||||
@@ -94,8 +84,6 @@ async def freekassa_webhook(request):
|
||||
|
||||
@router.callback_query(lambda c: c.data == "pay_freekassa")
|
||||
async def process_callback_pay_freekassa(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
for i in range(0, len(PAYMENT_OPTIONS), 2):
|
||||
if i + 1 < len(PAYMENT_OPTIONS):
|
||||
@@ -124,16 +112,12 @@ async def process_callback_pay_freekassa(callback_query: types.CallbackQuery, st
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile"))
|
||||
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
await callback_query.message.answer(
|
||||
text="Выберите сумму пополнения через FreeKassa:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await state.set_state(ReplenishBalanceState.choosing_amount_freekassa)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("freekassa_amount|"))
|
||||
@@ -143,7 +127,7 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
try:
|
||||
amount = int(amount_str)
|
||||
except ValueError:
|
||||
await bot.send_message(callback_query.from_user.id, "Некорректная сумма.")
|
||||
await callback_query.message.answer("Некорректная сумма.")
|
||||
return
|
||||
|
||||
user_email = f"{callback_query.from_user.id}@solo.net"
|
||||
@@ -158,25 +142,20 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
]
|
||||
)
|
||||
|
||||
await bot.send_message(
|
||||
callback_query.from_user.id,
|
||||
await callback_query.message.answer(
|
||||
f"Вы выбрали оплату на {amount} рублей. Перейдите по ссылке для завершения оплаты:",
|
||||
reply_markup=confirm_keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
callback_query.from_user.id,
|
||||
await callback_query.message.answer(
|
||||
"Ошибка при создании платежа. Попробуйте позже.",
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_freekassa")
|
||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_freekassa)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.entering_custom_amount_freekassa)
|
||||
|
||||
@@ -8,9 +8,9 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiohttp import web
|
||||
from robokassa import HashAlgorithm, Robokassa
|
||||
|
||||
from bot import bot
|
||||
from config import ROBOKASSA_ENABLE, ROBOKASSA_LOGIN, ROBOKASSA_PASSWORD1, ROBOKASSA_PASSWORD2, ROBOKASSA_TEST_MODE
|
||||
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from logger import logger
|
||||
|
||||
@@ -47,27 +47,6 @@ def generate_payment_link(amount, inv_id, description, tg_id):
|
||||
return payment_link
|
||||
|
||||
|
||||
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key="last_message_id"):
|
||||
if state:
|
||||
try:
|
||||
state_data = await state.get_data()
|
||||
previous_message_id = state_data.get(message_key)
|
||||
|
||||
if previous_message_id:
|
||||
logger.debug(f"Deleting previous message with ID: {previous_message_id}")
|
||||
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
|
||||
|
||||
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
|
||||
await state.update_data({message_key: sent_message.message_id})
|
||||
|
||||
logger.debug(f"Sent new message with ID: {sent_message.message_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении/отправке сообщения: {e}")
|
||||
return None
|
||||
|
||||
return sent_message
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_robokassa")
|
||||
async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
@@ -109,20 +88,12 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st
|
||||
await add_connection(tg_id, balance=0.0, trial=0)
|
||||
logger.info(f"Created new connection for user {tg_id} with balance 0.0.")
|
||||
|
||||
try:
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
logger.debug(f"Deleted message with ID: {callback_query.message.message_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить сообщение: {e}")
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
await callback_query.message.answer(
|
||||
text="Выберите сумму пополнения:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await state.set_state(ReplenishBalanceState.choosing_amount_robokassa)
|
||||
logger.info(f"Displayed amount selection for user {tg_id}.")
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("robokassa_amount|"))
|
||||
@@ -132,12 +103,7 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
data = callback_query.data.split("|")
|
||||
if len(data) != 3 or data[1] != "amount":
|
||||
logger.error("Ошибка: callback_data не соответствует формату.")
|
||||
await send_message_with_deletion(
|
||||
chat_id=callback_query.from_user.id,
|
||||
text="Неверные данные для выбора суммы.",
|
||||
state=state,
|
||||
)
|
||||
await callback_query.answer("Ошибка: данные повреждены.")
|
||||
await callback_query.message.answer("Ошибка: данные повреждены.")
|
||||
return
|
||||
|
||||
amount_str = data[2]
|
||||
@@ -147,12 +113,7 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
raise ValueError("Сумма должна быть положительным числом.")
|
||||
except ValueError as e:
|
||||
logger.error(f"Некорректное значение суммы: {amount_str}. Ошибка: {e}")
|
||||
await send_message_with_deletion(
|
||||
chat_id=callback_query.from_user.id,
|
||||
text="Некорректная сумма. Попробуйте снова.",
|
||||
state=state,
|
||||
)
|
||||
await callback_query.answer("Некорректная сумма.")
|
||||
await callback_query.message.answer("Некорректная сумма.")
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
@@ -176,7 +137,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
reply_markup=confirm_keyboard,
|
||||
)
|
||||
logger.info(f"Payment link sent to user {callback_query.from_user.id}.")
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
async def robokassa_webhook(request):
|
||||
@@ -238,18 +198,6 @@ def check_payment_signature(params):
|
||||
return signature_value.upper() == expected_signature.upper()
|
||||
|
||||
|
||||
async def send_payment_success_notification(user_id: int, amount: float):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
logger.info(f"Sent payment success notification to user {user_id}.")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_robokassa")
|
||||
async def process_custom_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
@@ -257,7 +205,6 @@ async def process_custom_amount_selection(callback_query: types.CallbackQuery, s
|
||||
|
||||
await callback_query.message.edit_text(text="Пожалуйста, введите сумму пополнения в рублях (например, 150):")
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
|
||||
@@ -4,9 +4,9 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import RUB_TO_XTR
|
||||
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from logger import logger
|
||||
|
||||
@@ -19,25 +19,6 @@ class ReplenishBalanceState(StatesGroup):
|
||||
entering_custom_amount_stars = State()
|
||||
|
||||
|
||||
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key="last_message_id"):
|
||||
if state:
|
||||
try:
|
||||
state_data = await state.get_data()
|
||||
previous_message_id = state_data.get(message_key)
|
||||
|
||||
if previous_message_id:
|
||||
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
|
||||
|
||||
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
|
||||
await state.update_data({message_key: sent_message.message_id})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении/отправке сообщения: {e}")
|
||||
return None
|
||||
|
||||
return sent_message
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_stars")
|
||||
async def process_callback_pay_stars(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
@@ -84,14 +65,12 @@ async def process_callback_pay_stars(callback_query: types.CallbackQuery, state:
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить сообщение: {e}")
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
await callback_query.message.answer(
|
||||
text="Выберите сумму пополнения:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await state.set_state(ReplenishBalanceState.choosing_amount_stars)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("stars_amount|"))
|
||||
@@ -146,27 +125,11 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
logger.error(f"Ошибка при создании платежа: {e}")
|
||||
await callback_query.message.answer("Произошла ошибка при создании платежа.")
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
async def send_payment_success_notification(user_id: int, amount: float):
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_stars")
|
||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_stars)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.entering_custom_amount_stars)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from logger import logger
|
||||
|
||||
|
||||
async def send_payment_success_notification(user_id: int, amount: float):
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="profile"))
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
|
||||
@@ -8,9 +8,9 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiohttp import web
|
||||
from yookassa import Configuration, Payment
|
||||
|
||||
from bot import bot
|
||||
from config import YOOKASSA_ENABLE, YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID
|
||||
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from logger import logger
|
||||
|
||||
@@ -29,25 +29,6 @@ class ReplenishBalanceState(StatesGroup):
|
||||
entering_custom_amount_yookassa = State()
|
||||
|
||||
|
||||
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key="last_message_id"):
|
||||
if state:
|
||||
try:
|
||||
state_data = await state.get_data()
|
||||
previous_message_id = state_data.get(message_key)
|
||||
|
||||
if previous_message_id:
|
||||
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
|
||||
|
||||
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
|
||||
await state.update_data({message_key: sent_message.message_id})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении/отправке сообщения: {e}")
|
||||
return None
|
||||
|
||||
return sent_message
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_yookassa")
|
||||
async def process_callback_pay_yookassa(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
@@ -79,7 +60,7 @@ async def process_callback_pay_yookassa(callback_query: types.CallbackQuery, sta
|
||||
callback_data="enter_custom_amount_yookassa",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_profile"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="profile"))
|
||||
|
||||
key_count = await get_key_count(tg_id)
|
||||
|
||||
@@ -88,19 +69,12 @@ async def process_callback_pay_yookassa(callback_query: types.CallbackQuery, sta
|
||||
if not exists:
|
||||
await add_connection(tg_id, balance=0.0, trial=0)
|
||||
|
||||
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,
|
||||
await callback_query.message.answer(
|
||||
text="Выберите сумму пополнения:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await state.set_state(ReplenishBalanceState.choosing_amount_yookassa)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("yookassa_amount|"))
|
||||
@@ -108,24 +82,12 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
data = callback_query.data.split("|", 1)
|
||||
|
||||
if len(data) != 2:
|
||||
await send_message_with_deletion(
|
||||
callback_query.from_user.id,
|
||||
"Неверные данные для выбора суммы.",
|
||||
state=state,
|
||||
message_key="amount_error_message_id",
|
||||
)
|
||||
return
|
||||
|
||||
amount_str = data[1]
|
||||
try:
|
||||
amount = int(amount_str)
|
||||
except ValueError:
|
||||
await send_message_with_deletion(
|
||||
callback_query.from_user.id,
|
||||
"Некорректная сумма.",
|
||||
state=state,
|
||||
message_key="amount_error_message_id",
|
||||
)
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
@@ -181,27 +143,7 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
reply_markup=confirm_keyboard,
|
||||
)
|
||||
else:
|
||||
await send_message_with_deletion(
|
||||
callback_query.from_user.id,
|
||||
"Ошибка при создании платежа.",
|
||||
state=state,
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
async def send_payment_success_notification(user_id: int, amount: float):
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
|
||||
await callback_query.message.answer("Ошибка при создании платежа.")
|
||||
|
||||
|
||||
async def yookassa_webhook(request):
|
||||
@@ -225,9 +167,8 @@ async def yookassa_webhook(request):
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_yookassa")
|
||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||
await callback_query.message.answer(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_yookassa)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.entering_custom_amount_yookassa)
|
||||
|
||||
+47
-98
@@ -5,91 +5,68 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import CHANNEL_URL
|
||||
from database import get_balance, get_key_count, get_referral_stats
|
||||
from handlers.texts import RENEWAL_PLANS, get_referral_link, invite_message_send, profile_message_send
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "profile")
|
||||
async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext, admin: bool):
|
||||
chat_id = callback_query.from_user.id
|
||||
username = callback_query.from_user.full_name
|
||||
|
||||
image_path = os.path.join("img", "pic.jpg")
|
||||
key_count = await get_key_count(chat_id)
|
||||
balance = await get_balance(chat_id)
|
||||
if balance is None:
|
||||
balance = 0
|
||||
|
||||
try:
|
||||
key_count = await get_key_count(chat_id)
|
||||
balance = await get_balance(chat_id)
|
||||
if balance is None:
|
||||
balance = 0
|
||||
profile_message = profile_message_send(username, chat_id, balance, key_count)
|
||||
|
||||
profile_message = profile_message_send(username, chat_id, balance, key_count)
|
||||
if key_count == 0:
|
||||
profile_message += "\n🔧 <i>Нажмите кнопку ➕ Устройство, чтобы настроить VPN-подключение</i>"
|
||||
|
||||
if key_count == 0:
|
||||
profile_message += "\n🔧 <i>Нажмите кнопку ➕ Устройство, чтобы настроить VPN-подключение</i>"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="📢 Наш канал", url=CHANNEL_URL))
|
||||
builder.row(InlineKeyboardButton(text="💡 Тарифы", callback_data="view_tariffs"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="➕ Устройство", callback_data="create_key"),
|
||||
InlineKeyboardButton(text="📱 Мои устройства", callback_data="view_keys"),
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="📢 Наш канал", url=CHANNEL_URL))
|
||||
builder.row(InlineKeyboardButton(text="💡 Тарифы", callback_data="view_tariffs"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="➕ Устройство", callback_data="create_key"),
|
||||
InlineKeyboardButton(text="📱 Мои устройства", callback_data="view_keys"),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💳 Пополнить баланс",
|
||||
callback_data="pay",
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💳 Пополнить баланс",
|
||||
callback_data="pay",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👥 Пригласить друзей", callback_data="invite"),
|
||||
InlineKeyboardButton(text="📘 Инструкции", callback_data="instructions"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
|
||||
if admin:
|
||||
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Главное меню", callback_data="back_to_menu"))
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👥 Пригласить друзей", callback_data="invite"),
|
||||
InlineKeyboardButton(text="📘 Инструкции", callback_data="instructions"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
|
||||
if admin:
|
||||
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Главное меню", callback_data="start"))
|
||||
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
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=chat_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic.jpg"),
|
||||
caption=profile_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=profile_message,
|
||||
parse_mode="HTML",
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await callback_query.message.answer_photo(
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic.jpg"),
|
||||
caption=profile_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
await bot.send_message(
|
||||
chat_id,
|
||||
f"❗️ Не удалось загрузить профиль. Техническая ошибка: {e}",
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
text=profile_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "view_tariffs")
|
||||
async def view_tariffs_handler(callback_query: types.CallbackQuery):
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"<b>🚀 Доступные тарифы VPN:</b>\n\n"
|
||||
@@ -101,10 +78,8 @@ async def view_tariffs_handler(callback_query: types.CallbackQuery):
|
||||
for months in sorted(RENEWAL_PLANS.keys(), key=int)
|
||||
]
|
||||
),
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "invite")
|
||||
@@ -119,42 +94,16 @@ async def invite_handler(callback_query: types.CallbackQuery):
|
||||
image_path = os.path.join("img", "pic_invite.jpg")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
|
||||
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
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",
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="profile"))
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await callback_query.message.answer_photo(
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_invite.jpg"),
|
||||
caption=invite_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
except Exception as e:
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=f"❗️ Не удалось отправить сообщение. Техническая ошибка: {e}",
|
||||
parse_mode="HTML",
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
text=invite_message,
|
||||
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, admin: bool = False):
|
||||
await state.clear()
|
||||
await process_callback_view_profile(callback_query, state, admin)
|
||||
|
||||
+40
-97
@@ -1,125 +1,77 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
import asyncpg
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CHANNEL_URL,
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DATABASE_URL,
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
SUPPORT_CHAT_URL,
|
||||
)
|
||||
from database import add_connection, add_referral, check_connection_exists, get_trial
|
||||
from config import CHANNEL_URL, CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, SUPPORT_CHAT_URL
|
||||
from database import add_connection, add_referral, check_connection_exists, get_trial, restore_trial
|
||||
from handlers.keys.trial_key import create_trial_key
|
||||
from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
async def send_welcome_message(chat_id: int, trial_status: int, admin: bool):
|
||||
@router.callback_query(F.data == "start")
|
||||
async def handle_start_callback_query(callback_query: CallbackQuery, state: FSMContext):
|
||||
await start_command(callback_query.message, state)
|
||||
|
||||
|
||||
@router.message(Command("start"))
|
||||
async def start_command(message: Message, admin: bool):
|
||||
if message.text:
|
||||
try:
|
||||
referrer_tg_id = int(message.text.split("referral_")[1])
|
||||
await add_referral(message.from_user.id, referrer_tg_id)
|
||||
except (ValueError,IndexError):
|
||||
pass
|
||||
connection_exists = await check_connection_exists(message.from_user.id)
|
||||
if not connection_exists:
|
||||
await add_connection(message.from_user.id)
|
||||
trial_status = await get_trial(message.from_user.id)
|
||||
image_path = os.path.join("img", "pic.jpg")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
if trial_status == 0:
|
||||
builder.row(InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile"))
|
||||
if admin:
|
||||
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📢 Официальный канал", url=CHANNEL_URL),
|
||||
)
|
||||
if admin:
|
||||
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
|
||||
builder.row(InlineKeyboardButton(text="🌐 О нашем VPN", callback_data="about_vpn"))
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_from_buffer:
|
||||
await bot.send_photo(
|
||||
chat_id=chat_id,
|
||||
await message.answer_photo(
|
||||
photo=BufferedInputFile(image_from_buffer.read(), filename="pic.jpg"),
|
||||
caption=WELCOME_TEXT,
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
await message.answer(
|
||||
text=WELCOME_TEXT,
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
|
||||
async def start_command(message: Message, admin: bool = False):
|
||||
try:
|
||||
logger.info(f"Получена команда /start. Текст сообщения: {message.text}, user_id: {message.from_user.id}")
|
||||
|
||||
if "referral_" in message.text:
|
||||
logger.info("Обнаружен реферальный код.")
|
||||
try:
|
||||
referrer_tg_id = int(message.text.split("referral_")[1])
|
||||
logger.info(f"ID пригласившего пользователя: {referrer_tg_id}")
|
||||
except ValueError:
|
||||
logger.error("Ошибка парсинга реферального ID.")
|
||||
return
|
||||
|
||||
connection_exists = await check_connection_exists(message.from_user.id)
|
||||
logger.info(f"Результат проверки подключения для user_id {message.from_user.id}: {connection_exists}")
|
||||
if not connection_exists:
|
||||
logger.info(f"Добавляем подключение для пользователя: {message.from_user.id}")
|
||||
await add_connection(message.from_user.id)
|
||||
logger.info(f"Добавляем реферал для пользователя {message.from_user.id}, приглашённым {referrer_tg_id}")
|
||||
await add_referral(message.from_user.id, referrer_tg_id)
|
||||
else:
|
||||
logger.info(f"Пользователь {message.from_user.id} уже зарегистрирован.")
|
||||
|
||||
logger.info(f"Проверяем статус пробного периода для user_id {message.from_user.id}")
|
||||
trial_status = await get_trial(message.from_user.id)
|
||||
logger.info(f"Статус пробного периода для user_id {message.from_user.id}: {trial_status}")
|
||||
|
||||
logger.info(f"Отправка приветственного сообщения для user_id {message.from_user.id}")
|
||||
await send_welcome_message(message.chat.id, trial_status, admin)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в обработке команды /start для user_id {message.from_user.id}: {e}")
|
||||
await message.answer("Произошла ошибка. Пожалуйста, попробуйте позже.")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "connect_vpn")
|
||||
async def handle_connect_vpn(callback_query: CallbackQuery):
|
||||
await callback_query.message.delete()
|
||||
async def handle_connect_vpn(callback_query: CallbackQuery, session: Any):
|
||||
user_id = callback_query.from_user.id
|
||||
|
||||
trial_key_info = await create_trial_key(user_id)
|
||||
trial_key_info = await create_trial_key(user_id, session)
|
||||
|
||||
if "error" in trial_key_info:
|
||||
await callback_query.message.answer(trial_key_info["error"])
|
||||
else:
|
||||
try:
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
|
||||
result = await conn.execute(
|
||||
"""
|
||||
UPDATE connections SET trial = 1 WHERE tg_id = $1
|
||||
""",
|
||||
user_id,
|
||||
)
|
||||
logger.info(f"Rows updated: {result}")
|
||||
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обновлении trial: {e}")
|
||||
await callback_query.message.answer("Произошла ошибка при обновлении статуса.")
|
||||
await restore_trial(user_id)
|
||||
|
||||
key_message = (
|
||||
f"🔑 <b>Ваш персональный ключ доступа:</b>\n"
|
||||
@@ -128,7 +80,7 @@ async def handle_connect_vpn(callback_query: CallbackQuery):
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
|
||||
@@ -144,28 +96,19 @@ async def handle_connect_vpn(callback_query: CallbackQuery):
|
||||
),
|
||||
)
|
||||
|
||||
await callback_query.message.answer(key_message, parse_mode="HTML", reply_markup=builder.as_markup())
|
||||
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer(key_message, reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "about_vpn")
|
||||
async def handle_about_vpn(callback_query: CallbackQuery):
|
||||
await callback_query.message.delete()
|
||||
|
||||
about_vpn_message = get_about_vpn("3.1.1_Stable")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📢 Официальный канал", url=CHANNEL_URL),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="start"))
|
||||
|
||||
await callback_query.message.answer(about_vpn_message, parse_mode="HTML", reply_markup=builder.as_markup())
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "back_to_menu")
|
||||
async def handle_back_to_menu(callback_query: CallbackQuery, admin: bool = False):
|
||||
await callback_query.message.delete()
|
||||
trial_status = await get_trial(callback_query.from_user.id)
|
||||
await send_welcome_message(callback_query.from_user.id, trial_status, admin)
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer(get_about_vpn("3.1.1_Stable"), reply_markup=builder.as_markup())
|
||||
|
||||
@@ -4,7 +4,6 @@ from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import ADMIN_ID
|
||||
from logger import logger
|
||||
|
||||
|
||||
class AdminMiddleware(BaseMiddleware):
|
||||
@@ -20,10 +19,8 @@ class AdminMiddleware(BaseMiddleware):
|
||||
def _check_admin_access(self, event: TelegramObject) -> bool:
|
||||
try:
|
||||
admin_ids: Union[int, list[int]] = ADMIN_ID
|
||||
|
||||
if isinstance(admin_ids, list):
|
||||
return event.from_user.id in admin_ids
|
||||
return event.from_user.id == admin_ids
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка проверки администратора: {e}")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
from typing import Any, Awaitable, Callable, Dict
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import CallbackQuery, Message, TelegramObject
|
||||
|
||||
|
||||
class DeleteMessageMiddleware(BaseMiddleware):
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
if isinstance(event, (Message, CallbackQuery)):
|
||||
if isinstance(event, Message):
|
||||
await event.delete()
|
||||
elif isinstance(event, CallbackQuery):
|
||||
await event.answer()
|
||||
await event.message.delete()
|
||||
return await handler(event, data)
|
||||
Reference in New Issue
Block a user