+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()}"
|
||||
)
|
||||
|
||||
+898
-230
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
@@ -18,44 +20,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):
|
||||
async def show_coupon_list(callback_query: types.CallbackQuery, session: Any):
|
||||
try:
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
coupons = await get_all_coupons()
|
||||
coupons = await get_all_coupons(session)
|
||||
|
||||
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 +48,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,85 +62,60 @@ 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())
|
||||
async def handle_delete_coupon(callback_query: types.CallbackQuery):
|
||||
async def handle_delete_coupon(callback_query: types.CallbackQuery, session: Any):
|
||||
coupon_code = callback_query.data[len("delete_coupon_") :]
|
||||
|
||||
try:
|
||||
result = await delete_coupon_from_db(coupon_code)
|
||||
result = await delete_coupon_from_db(coupon_code, session)
|
||||
|
||||
if result:
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
await show_coupon_list(callback_query)
|
||||
await show_coupon_list(callback_query, session)
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
f"❌ Купон с кодом <b>{coupon_code}</b> не найден.",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await show_coupon_list(callback_query)
|
||||
await show_coupon_list(callback_query, session)
|
||||
|
||||
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())
|
||||
async def process_coupon_data(message: types.Message, state: FSMContext):
|
||||
async def process_coupon_data(message: types.Message, state: FSMContext, session: Any):
|
||||
text = message.text.strip()
|
||||
|
||||
parts = text.split()
|
||||
|
||||
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,41 +125,26 @@ 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
|
||||
|
||||
try:
|
||||
await create_coupon(coupon_code, coupon_amount, usage_limit)
|
||||
await create_coupon(coupon_code, coupon_amount, usage_limit, session)
|
||||
|
||||
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())
|
||||
|
||||
+284
-406
@@ -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,137 +29,126 @@ 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())
|
||||
async def handle_restore_trial(callback_query: types.CallbackQuery):
|
||||
async def handle_restore_trial(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = int(callback_query.data.split("_")[2])
|
||||
|
||||
await restore_trial(tg_id)
|
||||
await restore_trial(tg_id, session)
|
||||
|
||||
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"🔑 Ключ: <code>{key_details['key']}</code>\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"🔑 Ключ: <code>{key_details['key']}</code>\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.chat.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(
|
||||
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):
|
||||
tg_id = callback_query.from_user.id
|
||||
async def process_connect_pc(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.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())
|
||||
|
||||
+69
-158
@@ -1,27 +1,25 @@
|
||||
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 aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
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,
|
||||
check_connection_exists,
|
||||
get_balance,
|
||||
get_trial,
|
||||
store_key,
|
||||
update_balance,
|
||||
use_trial,
|
||||
)
|
||||
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,96 +34,57 @@ 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()
|
||||
|
||||
trial_status = existing_connection["trial"] if existing_connection else 0
|
||||
|
||||
async def select_server(callback_query: CallbackQuery, state: FSMContext, session: Any):
|
||||
trial_status = await get_trial(callback_query.message.chat.id, session)
|
||||
if trial_status == 1:
|
||||
await bot.send_message(
|
||||
chat_id=callback_query.from_user.id,
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="✅ Да, подключить новое устройство", callback_data="confirm_create_new_key")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=KEY,
|
||||
parse_mode="HTML",
|
||||
reply_markup=InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text="✅ Да, подключить новое устройство",
|
||||
callback_data="confirm_create_new_key",
|
||||
)
|
||||
],
|
||||
[InlineKeyboardButton(text="↩️ Назад", callback_data="cancel_create_key")],
|
||||
]
|
||||
),
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
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
|
||||
tg_id = callback_query.message.chat.id
|
||||
|
||||
logger.info(f"User {tg_id} confirmed creation of a new key.")
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < RENEWAL_PLANS["1"]["price"]:
|
||||
replenish_button = InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
||||
await callback_query.message.edit_text(NULL_BALANCE, reply_markup=keyboard)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
await callback_query.message.answer(NULL_BALANCE, reply_markup=builder.as_markup())
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
logger.info(f"Balance for user {tg_id} is sufficient. Asking for device name.")
|
||||
|
||||
await callback_query.message.edit_text("🔑 Пожалуйста, введите имя подключаемого устройства:")
|
||||
await callback_query.message.answer("🔑 Пожалуйста, введите имя подключаемого устройства:")
|
||||
await state.set_state(Form.waiting_for_key_name)
|
||||
logger.info(f"State set to waiting_for_key_name for user {tg_id}")
|
||||
await state.update_data(creating_new_key=True)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@dp.callback_query(F.data == "cancel_create_key")
|
||||
async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext, 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):
|
||||
tg_id = message.from_user.id
|
||||
async def handle_key_name_input(message: Message, state: FSMContext, session: Any):
|
||||
tg_id = message.chat.id
|
||||
key_name = sanitize_key_name(message.text)
|
||||
|
||||
logger.info(f"User {tg_id} is attempting to create a key with the name: {key_name}")
|
||||
@@ -135,38 +94,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()
|
||||
|
||||
trial_status = existing_connection["trial"] if existing_connection else 0
|
||||
logger.info(f"Checking trial status for user {tg_id}.")
|
||||
trial_status = await get_trial(message.chat.id, session)
|
||||
|
||||
if trial_status == 0:
|
||||
expiry_time = current_time + timedelta(days=1, hours=3)
|
||||
@@ -174,12 +122,11 @@ 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")
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
||||
await message.bot.send_message(
|
||||
tg_id,
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
await message.answer(
|
||||
"💳 Недостаточно средств для создания подписки на новое устройство. Пополните баланс в личном кабинете.",
|
||||
reply_markup=keyboard,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
logger.warning(f"User {tg_id} has insufficient funds for key creation.")
|
||||
await state.clear()
|
||||
@@ -194,29 +141,17 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
logger.info(f"Generated public link for the key: {public_link}")
|
||||
|
||||
button_support = InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL)
|
||||
|
||||
button_profile = InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
|
||||
button_iphone = InlineKeyboardButton(text="🍏 Подключить", url=f"{CONNECT_IOS}{public_link}")
|
||||
button_android = InlineKeyboardButton(
|
||||
text="🤖 Подключить",
|
||||
url=f"{CONNECT_ANDROID}{public_link}",
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
|
||||
)
|
||||
|
||||
button_download_ios = InlineKeyboardButton(text="🍏 Скачать", url=DOWNLOAD_IOS)
|
||||
button_download_android = InlineKeyboardButton(
|
||||
text="🤖 Скачать",
|
||||
url=DOWNLOAD_ANDROID,
|
||||
)
|
||||
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[button_support],
|
||||
[button_download_ios, button_download_android],
|
||||
[button_iphone, button_android],
|
||||
[button_profile],
|
||||
]
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{public_link}"),
|
||||
InlineKeyboardButton(text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{public_link}"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
remaining_time = expiry_time - current_time
|
||||
days = remaining_time.days
|
||||
@@ -224,7 +159,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=builder.as_markup())
|
||||
|
||||
try:
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
@@ -244,40 +179,16 @@ 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.")
|
||||
connection_exists = await check_connection_exists(message.chat.id)
|
||||
if connection_exists:
|
||||
await use_trial(message.chat.id, session)
|
||||
else:
|
||||
await add_connection(tg_id=tg_id, balance=0, trial=1, session=session)
|
||||
|
||||
logger.info(f"Storing key for user {tg_id} in the database.")
|
||||
await store_key(
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
public_link,
|
||||
least_loaded_cluster,
|
||||
)
|
||||
await store_key(tg_id, client_id, email, expiry_timestamp, public_link, least_loaded_cluster, session)
|
||||
|
||||
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):
|
||||
|
||||
+310
-494
@@ -2,22 +2,13 @@ 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 aiogram.types import BufferedInputFile, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
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 +34,147 @@ router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "view_keys")
|
||||
async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
async def process_callback_view_keys(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.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:
|
||||
builder = InlineKeyboardBuilder()
|
||||
for record in records:
|
||||
key_name = record["email"]
|
||||
builder.row(InlineKeyboardButton(text=f"🔑 {key_name}", callback_data=f"view_key|{key_name}"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
inline_keyboard = builder.as_markup()
|
||||
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")
|
||||
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,
|
||||
)
|
||||
|
||||
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>"
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
text=response_message,
|
||||
reply_markup=inline_keyboard,
|
||||
)
|
||||
|
||||
image_path = os.path.join("img", "pic_keys.jpg")
|
||||
else:
|
||||
response_message = NO_KEYS
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="➕ Создать подписку", callback_data="create_key"))
|
||||
builder.row(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 = builder.as_markup()
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
chat_id=tg_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"),
|
||||
caption=response_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=inline_keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text=response_message,
|
||||
reply_markup=inline_keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
image_path = os.path.join("img", "pic_keys.jpg")
|
||||
|
||||
else:
|
||||
response_message = NO_KEYS
|
||||
create_key_button = types.InlineKeyboardButton(text="➕ Создать подписку", callback_data="create_key")
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад", callback_data="view_profile")
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button], [back_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}")
|
||||
|
||||
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):
|
||||
tg_id = callback_query.from_user.id
|
||||
async def process_callback_view_key(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.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)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
|
||||
)
|
||||
|
||||
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
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{key}"),
|
||||
InlineKeyboardButton(text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{key}"),
|
||||
)
|
||||
|
||||
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>"
|
||||
builder.row(InlineKeyboardButton(text="💻 Windows/Linux", callback_data=f"connect_pc|{key_name}"))
|
||||
|
||||
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
|
||||
response_message = key_message(key, formatted_expiry_date, days_left_message, server_name)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}"),
|
||||
InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete_key|{key_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):
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔄 Обновить подписку", callback_data=f"update_subscription|{key_name}")
|
||||
)
|
||||
|
||||
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")
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
inline_keyboard = [
|
||||
[download_iphone_button, download_android_button],
|
||||
[connect_iphone_button, connect_android_button],
|
||||
[connect_pc_button],
|
||||
[renew_button, delete_button],
|
||||
]
|
||||
keyboard = builder.as_markup()
|
||||
|
||||
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 +182,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,
|
||||
):
|
||||
tg_id = callback_query.from_user.id
|
||||
async def process_callback_update_subscription(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.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} обновлена!"
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
response_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
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,177 +273,124 @@ 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):
|
||||
tg_id = callback_query.from_user.id
|
||||
async def process_callback_renew_key(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.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"]
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 1 месяц ({RENEWAL_PLANS["1"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|1|{client_id}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 3 месяца ({RENEWAL_PLANS["3"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|3|{client_id}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 6 месяцев ({RENEWAL_PLANS["6"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|6|{client_id}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 12 месяцев ({RENEWAL_PLANS["12"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|12|{client_id}",
|
||||
)
|
||||
)
|
||||
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=builder.as_markup(),
|
||||
)
|
||||
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):
|
||||
tg_id = callback_query.from_user.id
|
||||
async def process_callback_renew_plan(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
plan, client_id = (
|
||||
callback_query.data.split("|")[1],
|
||||
callback_query.data.split("|")[2],
|
||||
@@ -565,83 +401,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:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="Пополнить баланс", callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
INSUFFICIENT_FUNDS_MSG,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
return
|
||||
|
||||
response_message = SUCCESS_RENEWAL_MSG.format(months=RENEWAL_PLANS[plan]["months"])
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
|
||||
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)
|
||||
|
||||
+33
-87
@@ -1,107 +1,53 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
import uuid
|
||||
|
||||
import asyncpg
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import add_client
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, PUBLIC_LINK, TRIAL_TIME
|
||||
from database import store_key
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, PUBLIC_LINK, TRIAL_TIME
|
||||
from database import store_key, use_trial
|
||||
from handlers.texts import INSTRUCTIONS
|
||||
from handlers.utils import generate_random_email, get_least_loaded_cluster
|
||||
|
||||
|
||||
async def create_trial_key(tg_id: int):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
client_id = str(uuid.uuid4())
|
||||
email = generate_random_email()
|
||||
async def create_trial_key(tg_id: int, session: Any):
|
||||
client_id = str(uuid.uuid4())
|
||||
email = generate_random_email()
|
||||
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
|
||||
instructions = INSTRUCTIONS
|
||||
result = {"key": public_link, "instructions": instructions}
|
||||
current_time = datetime.utcnow()
|
||||
expiry_time = current_time + timedelta(days=TRIAL_TIME, hours=3)
|
||||
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
||||
|
||||
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
|
||||
instructions = INSTRUCTIONS
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
for server_id, server in CLUSTERS[least_loaded_cluster].items():
|
||||
xui = AsyncApi(
|
||||
CLUSTERS[least_loaded_cluster][server_id]["API_URL"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
result = {"key": public_link, "instructions": instructions}
|
||||
|
||||
asyncio.create_task(generate_and_store_keys(tg_id, client_id, email, public_link))
|
||||
|
||||
return result
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
|
||||
async def generate_and_store_keys(tg_id: int, client_id: str, email: str, public_link: str):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
current_time = datetime.utcnow()
|
||||
expiry_time = current_time + timedelta(days=TRIAL_TIME, hours=3)
|
||||
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
||||
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
|
||||
tasks = []
|
||||
for server_id, server in CLUSTERS[least_loaded_cluster].items():
|
||||
task = create_key_on_server(
|
||||
least_loaded_cluster,
|
||||
server_id,
|
||||
client_id,
|
||||
email,
|
||||
tg_id,
|
||||
expiry_timestamp,
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await store_key(
|
||||
tg_id,
|
||||
await add_client(
|
||||
xui,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
public_link,
|
||||
server_id=least_loaded_cluster,
|
||||
)
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
INSERT INTO connections (tg_id, trial)
|
||||
VALUES ($1, 1)
|
||||
ON CONFLICT (tg_id)
|
||||
DO UPDATE SET trial = 1
|
||||
""",
|
||||
tg_id,
|
||||
limit_ip=1,
|
||||
total_gb=0,
|
||||
expiry_time=expiry_timestamp,
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
|
||||
async def create_key_on_server(
|
||||
cluster_id: str,
|
||||
server_id: str,
|
||||
client_id: str,
|
||||
email: str,
|
||||
tg_id: int,
|
||||
expiry_timestamp: int,
|
||||
):
|
||||
"""Создает ключ на сервере в указанном кластере и возвращает результат."""
|
||||
|
||||
xui = AsyncApi(
|
||||
CLUSTERS[cluster_id][server_id]["API_URL"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
response = await add_client(
|
||||
xui,
|
||||
await store_key(
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
tg_id,
|
||||
limit_ip=1,
|
||||
total_gb=0,
|
||||
expiry_time=expiry_timestamp,
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
expiry_timestamp,
|
||||
public_link,
|
||||
server_id=least_loaded_cluster,
|
||||
)
|
||||
|
||||
return response
|
||||
await use_trial(tg_id, session)
|
||||
return result
|
||||
|
||||
+91
-46
@@ -2,12 +2,20 @@ import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from aiogram import Bot, Router, types
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
import asyncpg
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import delete_client
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, TOTAL_GB
|
||||
from database import delete_key, get_balance, update_balance, update_key_expiry
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, TOTAL_GB, TRIAL_TIME
|
||||
from database import (
|
||||
add_notification,
|
||||
check_notification_time,
|
||||
delete_key,
|
||||
get_balance,
|
||||
update_balance,
|
||||
update_key_expiry,
|
||||
)
|
||||
from handlers.keys.key_utils import renew_key_in_cluster
|
||||
from handlers.texts import KEY_EXPIRY_10H, KEY_EXPIRY_24H, KEY_RENEWED, RENEWAL_PLANS
|
||||
from logger import logger
|
||||
@@ -27,6 +35,8 @@ async def notify_expiring_keys(bot: Bot):
|
||||
|
||||
logger.info("Начало обработки уведомлений.")
|
||||
|
||||
await notify_inactive_trial_users(bot, conn)
|
||||
await asyncio.sleep(1)
|
||||
await notify_10h_keys(bot, conn, current_time, threshold_time_10h)
|
||||
await asyncio.sleep(1)
|
||||
await notify_24h_keys(bot, conn, current_time, threshold_time_24h)
|
||||
@@ -95,28 +105,12 @@ async def notify_10h_keys(
|
||||
|
||||
if not await is_bot_blocked(bot, tg_id):
|
||||
try:
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="🔄 Продлить VPN",
|
||||
callback_data=f'renew_key|{record["client_id"]}',
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="💳 Пополнить баланс",
|
||||
callback_data="pay",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет",
|
||||
callback_data="view_profile",
|
||||
)
|
||||
],
|
||||
]
|
||||
)
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.button(text="🔄 Продлить VPN", callback_data=f'renew_key|{record["client_id"]}')
|
||||
keyboard.button(text="💳 Пополнить баланс", callback_data="pay")
|
||||
keyboard.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
keyboard.adjust(1)
|
||||
keyboard = keyboard.as_markup()
|
||||
await bot.send_message(tg_id, message, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
|
||||
except Exception as e:
|
||||
@@ -175,28 +169,26 @@ async def notify_24h_keys(
|
||||
|
||||
if not await is_bot_blocked(bot, tg_id):
|
||||
try:
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="🔄 Продлить VPN",
|
||||
callback_data=f'renew_key|{record["client_id"]}',
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="💳 Пополнить баланс",
|
||||
callback_data="pay",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет",
|
||||
callback_data="view_profile",
|
||||
)
|
||||
],
|
||||
]
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="🔄 Продлить VPN",
|
||||
callback_data=f'renew_key|{record["client_id"]}',
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="💳 Пополнить баланс",
|
||||
callback_data="pay",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет",
|
||||
callback_data="profile",
|
||||
)
|
||||
)
|
||||
keyboard = builder.as_markup()
|
||||
await bot.send_message(tg_id, message_24h, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление за 24 часа отправлено пользователю {tg_id}.")
|
||||
except Exception as e:
|
||||
@@ -212,6 +204,59 @@ async def notify_24h_keys(
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
logger.info("Проверка пользователей, не активировавших пробный период...")
|
||||
|
||||
inactive_trial_users = await conn.fetch(
|
||||
"""
|
||||
SELECT tg_id, username FROM users
|
||||
WHERE tg_id IN (
|
||||
SELECT tg_id FROM connections
|
||||
WHERE trial = 0
|
||||
) AND tg_id NOT IN (
|
||||
SELECT DISTINCT tg_id FROM keys
|
||||
)
|
||||
"""
|
||||
)
|
||||
logger.info(f"Найдено {len(inactive_trial_users)} неактивных пользователей.")
|
||||
|
||||
for user in inactive_trial_users:
|
||||
tg_id = user['tg_id']
|
||||
username = user.get('username', 'Пользователь')
|
||||
|
||||
try:
|
||||
# Проверяем, можно ли отправить уведомление
|
||||
can_notify = await check_notification_time(
|
||||
tg_id, 'inactive_trial', hours=24, session=conn # Уведомление не чаще, чем раз в 24 часа
|
||||
)
|
||||
|
||||
if can_notify and not await is_bot_blocked(bot, tg_id):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(text="🚀 Активировать пробный период", callback_data="create_key")
|
||||
)
|
||||
builder.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
keyboard = builder.as_markup()
|
||||
|
||||
message = (
|
||||
f"👋 Привет, {username}!\n\n"
|
||||
f"🎉 У тебя есть бесплатный пробный период на {TRIAL_TIME} дней!\n"
|
||||
"🕒 Не упусти возможность попробовать наш VPN прямо сейчас.\n\n"
|
||||
"💡 Нажми на кнопку ниже, чтобы активировать пробный доступ."
|
||||
)
|
||||
|
||||
await bot.send_message(tg_id, message, reply_markup=keyboard)
|
||||
logger.info(f"Отправлено уведомление неактивному пользователю {tg_id}.")
|
||||
|
||||
# Добавляем запись о notification
|
||||
await add_notification(tg_id, 'inactive_trial', session=conn)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления неактивному пользователю {tg_id}: {e}")
|
||||
|
||||
await asyncio.sleep(1) # Небольшая задержка между отправками
|
||||
|
||||
|
||||
async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: float):
|
||||
logger.info("Проверка истекших ключей...")
|
||||
|
||||
@@ -245,7 +290,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()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
from aiocryptopay import AioCryptoPay, Networks
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
@@ -6,9 +8,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 +26,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
|
||||
|
||||
async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for i in range(0, len(PAYMENT_OPTIONS), 2):
|
||||
if i + 1 < len(PAYMENT_OPTIONS):
|
||||
builder.row(
|
||||
@@ -74,28 +54,19 @@ async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, st
|
||||
callback_data="enter_custom_amount_crypto",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_profile"))
|
||||
|
||||
key_count = await get_key_count(tg_id)
|
||||
|
||||
if key_count == 0:
|
||||
exists = await check_connection_exists(tg_id)
|
||||
exists = await check_connection_exists(callback_query.message.chat.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(tg_id=callback_query.message.chat.id, balance=0.0, trial=0, session=session)
|
||||
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 +74,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 +81,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,24 +88,18 @@ 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)),
|
||||
description=f"Пополнения баланса на {amount} руб",
|
||||
payload=f"{callback_query.from_user.id}:{int(amount)}",
|
||||
payload=f"{callback_query.message.chat.id}:{int(amount)}",
|
||||
)
|
||||
|
||||
if hasattr(invoice, "bot_invoice_url"):
|
||||
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 +107,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 +143,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)
|
||||
@@ -224,7 +162,7 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
asset="USDT",
|
||||
amount=str(int(amount // RUB_TO_USDT)),
|
||||
description=f"Пополнения баланса на {amount} руб",
|
||||
payload=f"{message.from_user.id}:{amount}",
|
||||
payload=f"{message.chat.id}:{amount}",
|
||||
)
|
||||
|
||||
if hasattr(invoice, "bot_invoice_url"):
|
||||
@@ -233,18 +171,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,12 +127,12 @@ 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"
|
||||
user_email = f"{callback_query.message.chat.id}@solo.net"
|
||||
user_ip = callback_query.message.chat.id
|
||||
payment_url = await create_payment(callback_query.from_user.id, amount, user_email, user_ip)
|
||||
payment_url = await create_payment(callback_query.message.chat.id, amount, user_email, user_ip)
|
||||
|
||||
if payment_url:
|
||||
confirm_keyboard = InlineKeyboardMarkup(
|
||||
@@ -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 callback_query.message.answer(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_freekassa)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.entering_custom_amount_freekassa)
|
||||
@@ -187,9 +166,9 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
await message.answer("Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:")
|
||||
return
|
||||
|
||||
user_email = f"{message.from_user.id}@solo.net"
|
||||
user_email = f"{message.chat.id}@solo.net"
|
||||
user_ip = message.chat.id
|
||||
payment_url = await create_payment(message.from_user.id, amount, user_email, user_ip)
|
||||
payment_url = await create_payment(message.chat.id, amount, user_email, user_ip)
|
||||
|
||||
if payment_url:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton("Оплатить", url=payment_url)]])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import hashlib
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
@@ -8,9 +9,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,30 +48,9 @@ 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
|
||||
async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
logger.info(f"User {tg_id} initiated Robokassa payment.")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -106,23 +86,15 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st
|
||||
if key_count == 0:
|
||||
exists = await check_connection_exists(tg_id)
|
||||
if not exists:
|
||||
await add_connection(tg_id, balance=0.0, trial=0)
|
||||
await add_connection(tg_id, balance=0.0, trial=0, session=session)
|
||||
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 +104,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,22 +114,17 @@ 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)
|
||||
logger.info(f"User {callback_query.from_user.id} selected amount: {amount}.")
|
||||
logger.info(f"User {callback_query.message.chat.id} selected amount: {amount}.")
|
||||
inv_id = 0
|
||||
|
||||
tg_id = callback_query.from_user.id
|
||||
tg_id = callback_query.message.chat.id
|
||||
payment_url = generate_payment_link(amount, inv_id, "Пополнение баланса", tg_id)
|
||||
|
||||
logger.info(f"Payment URL for user {callback_query.from_user.id}: {payment_url}")
|
||||
logger.info(f"Payment URL for user {callback_query.message.chat.id}: {payment_url}")
|
||||
|
||||
confirm_keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
@@ -171,12 +133,11 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
]
|
||||
)
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
await callback_query.message.answer(
|
||||
text=f"Вы выбрали пополнение на {amount} рублей. Для оплаты перейдите по ссылке ниже:",
|
||||
reply_markup=confirm_keyboard,
|
||||
)
|
||||
logger.info(f"Payment link sent to user {callback_query.from_user.id}.")
|
||||
await callback_query.answer()
|
||||
logger.info(f"Payment link sent to user {callback_query.message.chat.id}.")
|
||||
|
||||
|
||||
async def robokassa_webhook(request):
|
||||
@@ -238,31 +199,18 @@ 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
|
||||
tg_id = callback_query.message.chat.id
|
||||
logger.info(f"User {tg_id} chose to enter a custom amount.")
|
||||
|
||||
await callback_query.message.edit_text(text="Пожалуйста, введите сумму пополнения в рублях (например, 150):")
|
||||
await callback_query.message.answer(text="Пожалуйста, введите сумму пополнения в рублях (например, 150):")
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
async def handle_custom_amount_input(message: types.Message, state: FSMContext):
|
||||
tg_id = message.from_user.id
|
||||
tg_id = message.chat.id
|
||||
logger.info(f"User {tg_id} entered custom amount: {message.text}")
|
||||
inv_id = 0
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
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, 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,28 +21,9 @@ 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
|
||||
async def process_callback_pay_stars(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"))
|
||||
@@ -77,21 +60,19 @@ async def process_callback_pay_stars(callback_query: types.CallbackQuery, state:
|
||||
if key_count == 0:
|
||||
exists = await check_connection_exists(tg_id)
|
||||
if not exists:
|
||||
await add_connection(tg_id, balance=0.0, trial=0)
|
||||
await add_connection(tg_id, balance=0.0, trial=0, session=session)
|
||||
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
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 +127,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 callback_query.message.answer(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_stars)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.entering_custom_amount_stars)
|
||||
@@ -213,7 +178,7 @@ async def on_successful_payment(
|
||||
message: types.Message,
|
||||
):
|
||||
try:
|
||||
user_id = int(message.from_user.id)
|
||||
user_id = int(message.chat.id)
|
||||
amount = float(message.successful_payment.invoice_payload.split("_")[0])
|
||||
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
|
||||
await add_payment(int(user_id), float(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}")
|
||||
@@ -1,3 +1,4 @@
|
||||
from typing import Any
|
||||
import uuid
|
||||
|
||||
from aiogram import F, Router, types
|
||||
@@ -8,9 +9,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,28 +30,9 @@ 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
|
||||
async def process_callback_pay_yookassa(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
@@ -79,28 +61,21 @@ 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)
|
||||
|
||||
if key_count == 0:
|
||||
exists = await check_connection_exists(tg_id)
|
||||
if not exists:
|
||||
await add_connection(tg_id, balance=0.0, trial=0)
|
||||
await add_connection(tg_id, balance=0.0, trial=0, session=session)
|
||||
|
||||
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 +83,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)
|
||||
@@ -133,7 +96,7 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
|
||||
# state_data = await state.get_data()
|
||||
customer_name = callback_query.from_user.full_name
|
||||
customer_id = callback_query.from_user.id
|
||||
customer_id = callback_query.message.chat.id
|
||||
|
||||
customer_email = f"{customer_id}@solo.net"
|
||||
|
||||
@@ -176,32 +139,12 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
]
|
||||
)
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
await callback_query.message.answer(
|
||||
text=f"Вы выбрали пополнение на {amount} рублей.",
|
||||
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 +168,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)
|
||||
@@ -254,7 +196,7 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
"receipt": {
|
||||
"customer": {
|
||||
"full_name": message.from_user.full_name,
|
||||
"email": f"{message.from_user.id}@solo.net",
|
||||
"email": f"{message.chat.id}@solo.net",
|
||||
"phone": "79000000000",
|
||||
},
|
||||
"items": [
|
||||
@@ -269,7 +211,7 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
}
|
||||
],
|
||||
},
|
||||
"metadata": {"user_id": message.from_user.id},
|
||||
"metadata": {"user_id": message.chat.id},
|
||||
},
|
||||
uuid.uuid4(),
|
||||
)
|
||||
|
||||
+49
-100
@@ -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
|
||||
chat_id = callback_query.message.chat.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,15 +78,13 @@ 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")
|
||||
async def invite_handler(callback_query: types.CallbackQuery):
|
||||
chat_id = callback_query.from_user.id
|
||||
chat_id = callback_query.message.chat.id
|
||||
referral_link = get_referral_link(chat_id)
|
||||
|
||||
referral_stats = await get_referral_stats(chat_id)
|
||||
@@ -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)
|
||||
|
||||
+42
-99
@@ -1,134 +1,86 @@
|
||||
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, use_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, session: Any, admin: bool):
|
||||
await start_command(callback_query.message, state, session, admin)
|
||||
|
||||
|
||||
@router.message(Command("start"))
|
||||
async def start_command(message: Message, state: FSMContext, session: Any, admin: bool):
|
||||
if message.text:
|
||||
try:
|
||||
referrer_tg_id = int(message.text.split("referral_")[1])
|
||||
await add_referral(message.chat.id, referrer_tg_id, session)
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
connection_exists = await check_connection_exists(message.chat.id)
|
||||
if not connection_exists:
|
||||
await add_connection(tg_id=message.chat.id, session=session)
|
||||
trial_status = await get_trial(message.chat.id, session)
|
||||
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()
|
||||
user_id = callback_query.from_user.id
|
||||
async def handle_connect_vpn(callback_query: CallbackQuery, session: Any):
|
||||
user_id = callback_query.message.chat.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 use_trial(user_id, session)
|
||||
|
||||
key_message = (
|
||||
f"🔑 <b>Ваш персональный ключ доступа:</b>\n"
|
||||
f"<pre>{trial_key_info['key']}</pre>\n\n"
|
||||
f"<code>{trial_key_info['key']}</code>\n\n"
|
||||
f"📋 <b>Быстрая инструкция по подключению:</b>\n{INSTRUCTIONS_TRIAL}"
|
||||
)
|
||||
|
||||
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())
|
||||
|
||||
@@ -55,23 +55,25 @@ async def on_shutdown(app):
|
||||
try:
|
||||
await asyncio.gather(*asyncio.all_tasks(), return_exceptions=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Error during shutdown: {e}")
|
||||
logger.error(f"Ошибка при завершении работы: {e}")
|
||||
|
||||
|
||||
async def shutdown_site(site):
|
||||
logger.info("Остановка сайта...")
|
||||
logger.info("Остановка сайт...")
|
||||
await site.stop()
|
||||
logger.info("Сервер остановлен.")
|
||||
logger.info("Сервер сайт.")
|
||||
|
||||
|
||||
async def main():
|
||||
dp.include_router(router)
|
||||
|
||||
if DEV_MODE:
|
||||
logger.info("Запуск в режиме разработки...")
|
||||
await bot.delete_webhook()
|
||||
await init_db()
|
||||
await dp.start_polling(bot)
|
||||
else:
|
||||
logger.info("Запуск в production режиме...")
|
||||
app = web.Application()
|
||||
app.on_startup.append(on_startup)
|
||||
app.on_shutdown.append(on_shutdown)
|
||||
@@ -96,7 +98,7 @@ async def main():
|
||||
site = web.TCPSite(runner, host=WEBAPP_HOST, port=WEBAPP_PORT)
|
||||
await site.start()
|
||||
|
||||
logger.info(f"Webhook URL: {WEBHOOK_URL}")
|
||||
logger.info(f"URL вебхука: {WEBHOOK_URL}")
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
for sig in (signal.SIGINT, signal.SIGTERM):
|
||||
@@ -107,7 +109,10 @@ async def main():
|
||||
finally:
|
||||
pending = asyncio.all_tasks()
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
try:
|
||||
task.cancel()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
await asyncio.gather(*pending, return_exceptions=True)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,10 +14,9 @@ class DatabaseMiddleware(BaseMiddleware):
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
async with await asyncpg.create_pool(DATABASE_URL) as pool:
|
||||
async with pool.acquire() as session:
|
||||
data["session"] = session
|
||||
try:
|
||||
return await handler(event, data)
|
||||
finally:
|
||||
await pool.release(session)
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
data["session"] = conn
|
||||
return await handler(event, data)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
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):
|
||||
try:
|
||||
await event.bot.delete_message(event.chat.id, event.message_id - 1)
|
||||
except Exception:
|
||||
pass
|
||||
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