Merge pull request #98 from izzzzzi/main
fix ref/serach from username and new stats in admin
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[flake8]
|
||||
max-line-length = 250
|
||||
max-line-length = 120
|
||||
ignore = E203, E266, E501, W503, F541, E704, W293, W291, E126, E121, E123, E128, E302, E131, E231, W292, E402, E261, E305, E701
|
||||
max-complexity = 25
|
||||
max-complexity = 15
|
||||
select = B, C, E, F, W, T4, B9
|
||||
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,.txt
|
||||
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,.txt,config.py
|
||||
@@ -1,3 +0,0 @@
|
||||
[settings]
|
||||
profile=black
|
||||
line_length = 250
|
||||
@@ -1,2 +1,5 @@
|
||||
formatting:
|
||||
@black . && isort . && flake8
|
||||
@echo "Running black..." && black .
|
||||
@echo "Running isort..." && isort .
|
||||
@echo "Running flake8..." && flake8 --config .flake8
|
||||
@echo "Running pylint..." && pylint .
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
from aiogram.types import BufferedInputFile
|
||||
@@ -31,7 +31,18 @@ def _create_database_backup():
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
["pg_dump", "-U", USER, "-h", HOST, "-F", "c", "-f", BACKUP_FILE, DB_NAME],
|
||||
[
|
||||
"pg_dump",
|
||||
"-U",
|
||||
USER,
|
||||
"-h",
|
||||
HOST,
|
||||
"-F",
|
||||
"c",
|
||||
"-f",
|
||||
BACKUP_FILE,
|
||||
DB_NAME,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
logger.info(f"Бэкап базы данных создан: {BACKUP_FILE}")
|
||||
@@ -46,9 +57,7 @@ def _create_database_backup():
|
||||
async def _send_backup_to_admin(bot, backup_file_path):
|
||||
try:
|
||||
with open(backup_file_path, "rb") as backup_file:
|
||||
backup_input_file = BufferedInputFile(
|
||||
backup_file.read(), filename=os.path.basename(backup_file_path)
|
||||
)
|
||||
backup_input_file = BufferedInputFile(backup_file.read(), filename=os.path.basename(backup_file_path))
|
||||
admin_ids: Union[int, list[int]] = ADMIN_ID
|
||||
if isinstance(admin_ids, list):
|
||||
for id in admin_ids:
|
||||
|
||||
@@ -44,9 +44,7 @@ async def add_client(
|
||||
return {"status": "failed", "error": str(e)}
|
||||
|
||||
|
||||
async def extend_client_key(
|
||||
xui, email: str, new_expiry_time: int, client_id: str, total_gb: int
|
||||
):
|
||||
async def extend_client_key(xui, email: str, new_expiry_time: int, client_id: str, total_gb: int):
|
||||
"""
|
||||
Функция для обновления срока действия ключа клиента по email.
|
||||
"""
|
||||
@@ -62,9 +60,7 @@ async def extend_client_key(
|
||||
logger.warning(f"Ошибка: клиент {email} не имеет действительного ID.")
|
||||
return
|
||||
|
||||
logger.info(
|
||||
f"Обновление ключа клиента {client.email} с ID {client.id} до нового времени: {new_expiry_time}"
|
||||
)
|
||||
logger.info(f"Обновление ключа клиента {client.email} с ID {client.id} до нового времени: {new_expiry_time}")
|
||||
|
||||
client.id = client_id
|
||||
client.expiry_time = new_expiry_time
|
||||
@@ -75,9 +71,7 @@ async def extend_client_key(
|
||||
client.limit_ip = 1
|
||||
|
||||
await xui.client.update(client.id, client)
|
||||
logger.info(
|
||||
f"Ключ клиента {client.email} успешно продлён до {new_expiry_time}."
|
||||
)
|
||||
logger.info(f"Ключ клиента {client.email} успешно продлён до {new_expiry_time}.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обновлении клиента с email {email}: {e}")
|
||||
|
||||
+48
-32
@@ -9,6 +9,21 @@ from logger import logger
|
||||
async def init_db():
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
|
||||
# Таблица для хранения информации о платежах
|
||||
await conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS payments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tg_id BIGINT NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
payment_system TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'success',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (tg_id) REFERENCES users(tg_id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# Таблица для хранения основной информации о пользователях из Telegram
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -108,7 +123,7 @@ async def create_coupon(coupon_code: str, amount: float, usage_limit: int):
|
||||
usage_limit,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Ошибка при создании купона: {e}")
|
||||
logger.error(f"Ошибка при создании купона: {e}")
|
||||
raise
|
||||
finally:
|
||||
await conn.close()
|
||||
@@ -126,7 +141,7 @@ async def get_all_coupons():
|
||||
)
|
||||
return coupons
|
||||
except Exception as e:
|
||||
print(f"Ошибка при получении купонов: {e}")
|
||||
logger.error(f"Ошибка при получении купонов: {e}")
|
||||
return []
|
||||
finally:
|
||||
await conn.close()
|
||||
@@ -157,7 +172,7 @@ async def delete_coupon_from_db(coupon_code: str):
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка при удалении купона: {e}")
|
||||
logger.error(f"Ошибка при удалении купона: {e}")
|
||||
return False
|
||||
finally:
|
||||
await conn.close()
|
||||
@@ -201,7 +216,12 @@ async def check_connection_exists(tg_id: int):
|
||||
|
||||
|
||||
async def store_key(
|
||||
tg_id: int, client_id: str, email: str, expiry_time: int, key: str, server_id: str
|
||||
tg_id: int,
|
||||
client_id: str,
|
||||
email: str,
|
||||
expiry_time: int,
|
||||
key: str,
|
||||
server_id: str,
|
||||
):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
await conn.execute(
|
||||
@@ -258,9 +278,7 @@ async def has_active_key(tg_id: int) -> bool:
|
||||
|
||||
async def get_balance(tg_id: int) -> float:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
balance = await conn.fetchval(
|
||||
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
||||
await conn.close()
|
||||
return balance if balance is not None else 0.0
|
||||
|
||||
@@ -363,7 +381,10 @@ async def get_referral_stats(referrer_tg_id: int):
|
||||
|
||||
await conn.close()
|
||||
|
||||
return {"total_referrals": total_referrals, "active_referrals": active_referrals}
|
||||
return {
|
||||
"total_referrals": total_referrals,
|
||||
"active_referrals": active_referrals,
|
||||
}
|
||||
|
||||
|
||||
async def update_key_expiry(client_id: str, new_expiry_time: int):
|
||||
@@ -430,9 +451,7 @@ async def get_client_id_by_email(email: str):
|
||||
async def get_tg_id_by_client_id(client_id: str):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
result = await conn.fetchrow(
|
||||
"SELECT tg_id FROM keys WHERE client_id = $1", client_id
|
||||
)
|
||||
result = await conn.fetchrow("SELECT tg_id FROM keys WHERE client_id = $1", client_id)
|
||||
return result["tg_id"] if result else None
|
||||
finally:
|
||||
await conn.close()
|
||||
@@ -446,17 +465,6 @@ async def upsert_user(
|
||||
language_code: str = None,
|
||||
is_bot: bool = False,
|
||||
):
|
||||
"""
|
||||
Создает или обновляет информацию о пользователе в базе данных.
|
||||
|
||||
Args:
|
||||
tg_id (int): Уникальный идентификатор пользователя в Telegram
|
||||
username (str, optional): Никнейм пользователя
|
||||
first_name (str, optional): Имя пользователя
|
||||
last_name (str, optional): Фамилия пользователя
|
||||
language_code (str, optional): Код языка пользователя
|
||||
is_bot (bool, optional): Флаг, указывающий является ли пользователь ботом
|
||||
"""
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
await conn.execute(
|
||||
@@ -479,15 +487,23 @@ async def upsert_user(
|
||||
language_code,
|
||||
is_bot,
|
||||
)
|
||||
|
||||
# Создаем запись в connections, если ее еще нет
|
||||
await conn.execute(
|
||||
"""
|
||||
INSERT INTO connections (tg_id, balance, trial)
|
||||
VALUES ($1, 0.0, 0)
|
||||
ON CONFLICT (tg_id) DO NOTHING
|
||||
""",
|
||||
tg_id,
|
||||
)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
|
||||
async def add_payment(tg_id: int, amount: float, payment_system: str):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
await conn.execute(
|
||||
"""
|
||||
INSERT INTO payments (tg_id, amount, payment_system, status)
|
||||
VALUES ($1, $2, $3, 'success')
|
||||
""",
|
||||
tg_id,
|
||||
amount,
|
||||
payment_system,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при добавлении платежа: {e}")
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import asyncpg
|
||||
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
|
||||
@@ -32,9 +34,7 @@ async def cmd_add_balance(message: types.Message):
|
||||
return
|
||||
|
||||
await add_balance_to_client(int(client_id), amount)
|
||||
await message.reply(
|
||||
f"✅ Баланс клиента {client_id} успешно пополнен на {amount}"
|
||||
)
|
||||
await message.reply(f"✅ Баланс клиента {client_id} успешно пополнен на {amount}")
|
||||
except ValueError:
|
||||
await message.reply(
|
||||
"❓ Неверный формат команды!\n"
|
||||
@@ -51,9 +51,7 @@ async def backup_command(message: types.Message):
|
||||
|
||||
await message.answer("🔄 Инициализация резервного копирования базы данных...")
|
||||
await backup_database()
|
||||
await message.answer(
|
||||
"✅ Бэкап базы данных успешно завершен и отправлен администратору."
|
||||
)
|
||||
await message.answer("✅ Бэкап базы данных успешно завершен и отправлен администратору.")
|
||||
|
||||
|
||||
@router.message(Command("send_trial"), IsAdminFilter())
|
||||
@@ -81,14 +79,10 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
|
||||
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}"
|
||||
)
|
||||
logger.info(f"🚫 Бот заблокирован пользователем с tg_id: {tg_id}")
|
||||
else:
|
||||
error_count += 1
|
||||
logger.error(
|
||||
f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}")
|
||||
|
||||
await message.answer(
|
||||
f"📊 Результаты рассылки пробных периодов:\n"
|
||||
@@ -97,9 +91,7 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
|
||||
f"❌ Ошибок: {error_count}"
|
||||
)
|
||||
else:
|
||||
await message.answer(
|
||||
"📭 Нет пользователей с неиспользованными пробными ключами."
|
||||
)
|
||||
await message.answer("📭 Нет пользователей с неиспользованными пробными ключами.")
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
@@ -109,12 +101,18 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
|
||||
|
||||
|
||||
@router.message(Command("send_to_all"), IsAdminFilter())
|
||||
async def send_message_to_all_clients(
|
||||
message: types.Message, state: FSMContext, from_panel=False
|
||||
):
|
||||
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)
|
||||
|
||||
@@ -141,9 +139,7 @@ async def process_message_to_all(
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
logger.error(
|
||||
f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}")
|
||||
|
||||
await message.answer(
|
||||
f"📤 Рассылка завершена:\n"
|
||||
|
||||
@@ -17,9 +17,7 @@ router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "coupons_editor", IsAdminFilter())
|
||||
async def show_coupon_management_menu(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def show_coupon_management_menu(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception as e:
|
||||
@@ -28,18 +26,12 @@ async def show_coupon_management_menu(
|
||||
await state.clear()
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="➕ Создать купон", callback_data="create_coupon")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="➕ Создать купон", callback_data="create_coupon"))
|
||||
builder.row(InlineKeyboardButton(text="Купоны", callback_data="coupons"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_admin_menu")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
|
||||
markup = builder.as_markup()
|
||||
await callback_query.message.answer(
|
||||
"🛠 Меню управления купонами:", reply_markup=markup
|
||||
)
|
||||
await callback_query.message.answer("🛠 Меню управления купонами:", reply_markup=markup)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@@ -55,14 +47,11 @@ async def show_coupon_list(callback_query: types.CallbackQuery):
|
||||
|
||||
if not coupons:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
|
||||
markup = builder.as_markup()
|
||||
|
||||
await callback_query.message.answer(
|
||||
"❌ На данный момент нет доступных купонов.\n"
|
||||
"Вы можете вернуться в меню управления.",
|
||||
"❌ На данный момент нет доступных купонов.\n" "Вы можете вернуться в меню управления.",
|
||||
parse_mode="HTML",
|
||||
reply_markup=markup,
|
||||
)
|
||||
@@ -87,19 +76,16 @@ async def show_coupon_list(callback_query: types.CallbackQuery):
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
|
||||
)
|
||||
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, parse_mode="HTML", reply_markup=markup)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка купонов: {e}")
|
||||
await callback_query.message.answer(
|
||||
f"❌ Произошла ошибка при получении списка купонов: {e}", parse_mode="HTML"
|
||||
f"❌ Произошла ошибка при получении списка купонов: {e}",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await callback_query.answer()
|
||||
|
||||
@@ -120,15 +106,14 @@ async def handle_delete_coupon(callback_query: types.CallbackQuery):
|
||||
await show_coupon_list(callback_query)
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
f"❌ Купон с кодом <b>{coupon_code}</b> не найден.", parse_mode="HTML"
|
||||
f"❌ Купон с кодом <b>{coupon_code}</b> не найден.",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await show_coupon_list(callback_query)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении купона: {e}")
|
||||
await callback_query.message.answer(
|
||||
f"❌ Произошла ошибка при удалении купона: {e}", parse_mode="HTML"
|
||||
)
|
||||
await callback_query.message.answer(f"❌ Произошла ошибка при удалении купона: {e}", parse_mode="HTML")
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@@ -180,8 +165,7 @@ async def process_coupon_data(message: types.Message, state: FSMContext):
|
||||
usage_limit = int(parts[2])
|
||||
except ValueError:
|
||||
await message.answer(
|
||||
"<b>⚠️ Проверьте правильность введенных данных.</b>\n"
|
||||
"Сумма должна быть числом, а лимит — целым числом.",
|
||||
"<b>⚠️ Проверьте правильность введенных данных.</b>\n" "Сумма должна быть числом, а лимит — целым числом.",
|
||||
parse_mode="HTML",
|
||||
reply_markup=markup,
|
||||
)
|
||||
@@ -197,9 +181,7 @@ async def process_coupon_data(message: types.Message, state: FSMContext):
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
|
||||
markup = builder.as_markup()
|
||||
|
||||
try:
|
||||
@@ -213,9 +195,7 @@ async def process_coupon_data(message: types.Message, state: FSMContext):
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании купона: {e}")
|
||||
await message.answer(
|
||||
f"<b>❌ Ошибка при создании купона:</b> {e}", parse_mode="HTML"
|
||||
)
|
||||
await message.answer(f"<b>❌ Ошибка при создании купона:</b> {e}", parse_mode="HTML")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "back_to_coupons_menu")
|
||||
|
||||
+88
-110
@@ -1,13 +1,13 @@
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
import subprocess
|
||||
|
||||
import asyncpg
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
import asyncpg
|
||||
|
||||
from backup import backup_database
|
||||
from bot import bot
|
||||
@@ -21,45 +21,35 @@ router = Router()
|
||||
class UserEditorState(StatesGroup):
|
||||
waiting_for_tg_id = State()
|
||||
displaying_user_info = State()
|
||||
waiting_for_restart_confirmation = State()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "admin", IsAdminFilter())
|
||||
async def handle_admin_callback_query(callback_query: CallbackQuery):
|
||||
await handle_admin_message(callback_query.message)
|
||||
async def handle_admin_callback_query(callback_query: CallbackQuery, state: FSMContext):
|
||||
await handle_admin_message(callback_query.message, state)
|
||||
|
||||
|
||||
@router.message(Command("admin"), F.data == "admin", IsAdminFilter())
|
||||
async def handle_admin_message(message: types.Message):
|
||||
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"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🎟️ Управление купонами", callback_data="coupons_editor"
|
||||
)
|
||||
)
|
||||
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")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📊 Статистика пользователей", callback_data="user_stats"))
|
||||
builder.row(InlineKeyboardButton(text="👥 Управление пользователями", callback_data="user_editor"))
|
||||
builder.row(InlineKeyboardButton(text="🎟️ Управление купонами", callback_data="coupons_editor"))
|
||||
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()
|
||||
message.chat.id,
|
||||
"🤖 Панель администратора",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
|
||||
@@ -71,6 +61,14 @@ async def user_stats_menu(callback_query: CallbackQuery):
|
||||
total_keys = await conn.fetchval("SELECT COUNT(*) FROM keys")
|
||||
total_referrals = await conn.fetchval("SELECT COUNT(*) FROM referrals")
|
||||
|
||||
total_payments_today = await conn.fetchval(
|
||||
"SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= CURRENT_DATE"
|
||||
)
|
||||
total_payments_week = await conn.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")
|
||||
|
||||
active_keys = await conn.fetchval(
|
||||
"SELECT COUNT(*) FROM keys WHERE expiry_time > $1",
|
||||
int(datetime.utcnow().timestamp() * 1000),
|
||||
@@ -78,27 +76,27 @@ async def user_stats_menu(callback_query: CallbackQuery):
|
||||
expired_keys = total_keys - active_keys
|
||||
|
||||
stats_message = (
|
||||
f"📈 <b>Подробная статистика проекта:</b>\n\n"
|
||||
f"👤 Зарегистрированных пользователей: <b>{total_users}</b>\n"
|
||||
f"🔑 Всего сгенерированных ключей: <b>{total_keys}</b>\n"
|
||||
f"🤝 Привлеченных рефералов: <b>{total_referrals}</b>\n"
|
||||
f"✅ Действующих ключей: <b>{active_keys}</b>\n"
|
||||
f"❌ Просроченных ключей: <b>{expired_keys}</b>"
|
||||
f"📊 <b>Подробная статистика проекта:</b>\n\n"
|
||||
f"👥 Пользователи:\n"
|
||||
f" 🌐 Зарегистрировано: <b>{total_users}</b>\n"
|
||||
f" 🤝 Привлеченных рефералов: <b>{total_referrals}</b>\n\n"
|
||||
f"🔑 Ключи:\n"
|
||||
f" 🌈 Всего сгенерировано: <b>{total_keys}</b>\n"
|
||||
f" ✅ Действующих: <b>{active_keys}</b>\n"
|
||||
f" ❌ Просроченных: <b>{expired_keys}</b>\n\n"
|
||||
f"💰 Финансовая статистика:\n"
|
||||
f" 📅 За день: <b>{total_payments_today} ₽</b>\n"
|
||||
f" 📆 За неделю: <b>{total_payments_week} ₽</b>\n"
|
||||
f" 🏦 За все время: <b>{total_payments_all_time} ₽</b>\n"
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Вернуться в меню", callback_data="back_to_admin_menu"
|
||||
)
|
||||
)
|
||||
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"
|
||||
)
|
||||
await callback_query.message.edit_text(stats_message, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||
finally:
|
||||
await conn.close()
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "send_to_alls", IsAdminFilter())
|
||||
@@ -111,25 +109,50 @@ async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext):
|
||||
async def handle_backup(message: Message):
|
||||
await message.answer("💾 Инициализация резервного копирования базы данных...")
|
||||
await backup_database()
|
||||
await message.answer(
|
||||
"✅ Резервная копия успешно создана и отправлена администратору."
|
||||
)
|
||||
await message.answer("✅ Резервная копия успешно создана и отправлена администратору.")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "restart_bot", IsAdminFilter())
|
||||
async def handle_restart(callback_query: CallbackQuery):
|
||||
async def handle_restart(callback_query: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(UserEditorState.waiting_for_restart_confirmation)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="✅ Да, перезапустить", callback_data="confirm_restart"),
|
||||
InlineKeyboardButton(text="❌ Нет, отмена", callback_data="admin"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
|
||||
await callback_query.message.edit_text(
|
||||
"🤔 Вы уверены, что хотите перезапустить бота?",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
F.data == "confirm_restart",
|
||||
UserEditorState.waiting_for_restart_confirmation,
|
||||
IsAdminFilter(),
|
||||
)
|
||||
async def confirm_restart_bot(callback_query: CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
|
||||
try:
|
||||
subprocess.run(
|
||||
["sudo", "systemctl", "restart", "bot.service"],
|
||||
["systemctl", "restart", "bot.service"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
await callback_query.message.answer("🔄 Бот успешно перезапущен.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
await callback_query.message.answer(
|
||||
f"⚠️ Перезагрузка бота будет выполнена через 30 секунд. Детали: {e.stderr}"
|
||||
await state.clear()
|
||||
await callback_query.message.edit_text("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
|
||||
except subprocess.CalledProcessError:
|
||||
await callback_query.message.edit_text("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
|
||||
except Exception as e:
|
||||
await callback_query.message.edit_text(
|
||||
f"⚠️ Ошибка при перезагрузке бота: {e.stderr}",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
finally:
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "user_editor", IsAdminFilter())
|
||||
@@ -137,63 +160,18 @@ async def user_editor_menu(callback_query: CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔍 Поиск по названию ключа", callback_data="search_by_key_name"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🆔 Поиск по Telegram ID", callback_data="search_by_tg_id"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Вернуться назад", callback_data="back_to_admin_menu"
|
||||
text="🔍 Поиск по названию ключа",
|
||||
callback_data="search_by_key_name",
|
||||
)
|
||||
)
|
||||
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()
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "back_to_admin_menu", IsAdminFilter())
|
||||
async def back_to_admin_menu(callback_query: CallbackQuery):
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
tg_id = callback_query.from_user.id
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📊 Статистика пользователей", callback_data="user_stats"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="👥 Управление пользователями", callback_data="user_editor"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to_alls")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🎟️ Управление купонами", callback_data="coupons_editor"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot")
|
||||
)
|
||||
await bot.send_message(
|
||||
tg_id, "🤖 Панель администратора", reply_markup=builder.as_markup()
|
||||
"👇 Выберите способ поиска пользователя:",
|
||||
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 bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
import asyncpg
|
||||
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 database import get_client_id_by_email, restore_trial, update_key_expiry
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.admin.admin_panel import back_to_admin_menu
|
||||
from handlers.keys.key_utils import delete_key_from_cluster, renew_key_in_cluster
|
||||
from handlers.utils import sanitize_key_name
|
||||
from logger import logger
|
||||
@@ -22,6 +21,7 @@ router = Router()
|
||||
|
||||
class UserEditorState(StatesGroup):
|
||||
waiting_for_tg_id = State()
|
||||
waiting_for_username = State()
|
||||
displaying_user_info = State()
|
||||
waiting_for_new_balance = State()
|
||||
waiting_for_key_name = State()
|
||||
@@ -34,19 +34,29 @@ async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(UserEditorState.waiting_for_tg_id)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(), IsAdminFilter())
|
||||
async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
||||
tg_id = int(message.text)
|
||||
@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 state.set_state(UserEditorState.waiting_for_username)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_username, IsAdminFilter())
|
||||
async def handle_username_input(message: types.Message, state: FSMContext):
|
||||
username = message.text.strip()
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
balance = await conn.fetchval(
|
||||
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
user_record = await conn.fetchrow("SELECT tg_id FROM users WHERE username = $1", username)
|
||||
|
||||
if not user_record:
|
||||
await message.reply("🔍 Пользователь с указанным 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
|
||||
)
|
||||
referral_count = await conn.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
|
||||
|
||||
if balance is None:
|
||||
await message.reply("Пользователь с указанным tg_id не найден.")
|
||||
@@ -56,37 +66,85 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for (email,) in key_records:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f"🔑 {email}", callback_data=f"edit_key_{email}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}"
|
||||
text="📝 Изменить баланс",
|
||||
callback_data=f"change_balance_{tg_id}",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}"
|
||||
text="🔄 Восстановить пробник",
|
||||
callback_data=f"restore_trial_{tg_id}",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_user_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
|
||||
user_info = (
|
||||
f"📊 Информация о пользователе:\n"
|
||||
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 message.reply(user_info, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||
await state.set_state(UserEditorState.displaying_user_info)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(), IsAdminFilter())
|
||||
async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
||||
tg_id = int(message.text)
|
||||
|
||||
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.reply("❌ Пользователь с указанным tg_id не найден. 🔍")
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
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"restore_trial_{tg_id}",
|
||||
)
|
||||
)
|
||||
|
||||
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:
|
||||
@@ -100,15 +158,9 @@ async def handle_restore_trial(callback_query: types.CallbackQuery):
|
||||
await restore_trial(tg_id)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад в меню администратора", callback_data="back_to_user_editor"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад в меню администратора", callback_data="admin"))
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
"✅ Триал успешно восстановлен.", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.edit_text("✅ Триал успешно восстановлен.", reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("change_balance_"), IsAdminFilter())
|
||||
@@ -124,9 +176,7 @@ async def process_balance_change(callback_query: CallbackQuery, state: FSMContex
|
||||
@router.message(UserEditorState.waiting_for_new_balance, IsAdminFilter())
|
||||
async def handle_new_balance_input(message: types.Message, state: FSMContext):
|
||||
if not message.text.isdigit() or int(message.text) < 0:
|
||||
await message.reply(
|
||||
"❌ Пожалуйста, введите корректную сумму для изменения баланса."
|
||||
)
|
||||
await message.reply("❌ Пожалуйста, введите корректную сумму для изменения баланса.")
|
||||
return
|
||||
|
||||
new_balance = int(message.text)
|
||||
@@ -136,7 +186,9 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
await conn.execute(
|
||||
"UPDATE connections SET balance = $1 WHERE tg_id = $2", new_balance, tg_id
|
||||
"UPDATE connections SET balance = $1 WHERE tg_id = $2",
|
||||
new_balance,
|
||||
tg_id,
|
||||
)
|
||||
|
||||
response_message = f"✅ Баланс успешно изменен на <b>{new_balance}</b>."
|
||||
@@ -145,11 +197,13 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext):
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад в меню администратора",
|
||||
callback_data="back_to_user_editor",
|
||||
callback_data="admin",
|
||||
)
|
||||
)
|
||||
await message.reply(
|
||||
response_message, reply_markup=builder.as_markup(), parse_mode="HTML"
|
||||
response_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
finally:
|
||||
@@ -182,9 +236,7 @@ async def process_key_edit(callback_query: CallbackQuery):
|
||||
|
||||
for cluster in CLUSTERS.values():
|
||||
if server_id in cluster:
|
||||
server_name = cluster[server_id].get(
|
||||
"name", "Неизвестный сервер"
|
||||
)
|
||||
server_name = cluster[server_id].get("name", "Неизвестный сервер")
|
||||
break
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
@@ -219,20 +271,14 @@ async def process_key_edit(callback_query: CallbackQuery):
|
||||
callback_data=f"delete_key_admin|{email}",
|
||||
),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data="back_to_user_editor"
|
||||
)
|
||||
)
|
||||
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"
|
||||
)
|
||||
await callback_query.message.edit_text("<b>Информация о ключе не найдена.</b>", parse_mode="HTML")
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
@@ -269,7 +315,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад в меню администратора",
|
||||
callback_data="back_to_user_editor",
|
||||
callback_data="admin",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -296,9 +342,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
|
||||
server_name = cluster[server_id].get("name", "Неизвестный сервер")
|
||||
break
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime(
|
||||
"%d %B %Y"
|
||||
)
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%d %B %Y")
|
||||
|
||||
response_messages.append(
|
||||
f"🔑 Ключ: <pre>{key}</pre>\n"
|
||||
@@ -315,13 +359,12 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
|
||||
)
|
||||
key_buttons.row(
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить ключ", callback_data=f"delete_key_admin|{email}"
|
||||
text="❌ Удалить ключ",
|
||||
callback_data=f"delete_key_admin|{email}",
|
||||
)
|
||||
)
|
||||
|
||||
key_buttons.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_user_editor")
|
||||
)
|
||||
key_buttons.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
|
||||
await message.reply(
|
||||
"\n".join(response_messages),
|
||||
@@ -358,9 +401,7 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
||||
|
||||
try:
|
||||
expiry_time_str = message.text
|
||||
expiry_time = int(
|
||||
datetime.strptime(expiry_time_str, "%Y-%m-%d %H:%M:%S").timestamp() * 1000
|
||||
)
|
||||
expiry_time = int(datetime.strptime(expiry_time_str, "%Y-%m-%d %H:%M:%S").timestamp() * 1000)
|
||||
|
||||
client_id = await get_client_id_by_email(email)
|
||||
if client_id is None:
|
||||
@@ -370,9 +411,7 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow(
|
||||
"SELECT server_id FROM keys WHERE client_id = $1", client_id
|
||||
)
|
||||
record = await conn.fetchrow("SELECT server_id FROM keys WHERE client_id = $1", client_id)
|
||||
if not record:
|
||||
await message.reply("Клиент не найден в базе данных.")
|
||||
await state.clear()
|
||||
@@ -398,16 +437,16 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
||||
|
||||
await update_key_expiry(client_id, expiry_time)
|
||||
|
||||
response_message = f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
|
||||
response_message = (
|
||||
f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data="back_to_user_editor"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
await message.reply(
|
||||
response_message, reply_markup=builder.as_markup(), parse_mode="HTML"
|
||||
response_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
finally:
|
||||
@@ -428,9 +467,7 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
client_id = await conn.fetchval(
|
||||
"SELECT client_id FROM keys WHERE email = $1", email
|
||||
)
|
||||
client_id = await conn.fetchval("SELECT client_id FROM keys WHERE email = $1", email)
|
||||
|
||||
if client_id is None:
|
||||
await bot.edit_message_text(
|
||||
@@ -443,14 +480,11 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="✅ Да, удалить", callback_data=f"confirm_delete_admin|{client_id}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="❌ Нет, отменить", callback_data="view_keys"
|
||||
text="✅ Да, удалить",
|
||||
callback_data=f"confirm_delete_admin|{client_id}",
|
||||
)
|
||||
)
|
||||
builder.row(types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="view_keys"))
|
||||
await bot.edit_message_text(
|
||||
"<b>❓ Вы уверены, что хотите удалить ключ?</b>",
|
||||
chat_id=tg_id,
|
||||
@@ -472,24 +506,18 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow(
|
||||
"SELECT email FROM keys WHERE client_id = $1", client_id
|
||||
)
|
||||
record = await conn.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")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
|
||||
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)
|
||||
)
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await delete_key_from_servers(email, client_id)
|
||||
@@ -504,9 +532,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||
else:
|
||||
response_message = "🚫 Ключ не найден или уже удален."
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
await bot.edit_message_text(
|
||||
response_message,
|
||||
chat_id=tg_id,
|
||||
@@ -536,8 +562,3 @@ async def delete_key_from_db(client_id):
|
||||
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "back_to_user_editor")
|
||||
async def back_to_user_editor(callback_query: CallbackQuery):
|
||||
await back_to_admin_menu(callback_query)
|
||||
|
||||
+11
-21
@@ -1,11 +1,11 @@
|
||||
from datetime import datetime
|
||||
|
||||
import asyncpg
|
||||
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
|
||||
@@ -20,22 +20,18 @@ router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "activate_coupon")
|
||||
async def handle_activate_coupon(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
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="view_profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"<b>Введите код купона:</b>\n\n"
|
||||
"Пожалуйста, введите действующий код купона, который вы хотите активировать.",
|
||||
"<b>🎫 Введите код купона:</b>\n\n"
|
||||
"📝 Пожалуйста, введите действующий код купона, который вы хотите активировать. 🔑",
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
@@ -54,13 +50,9 @@ async def process_coupon_code(message: types.Message, state: FSMContext):
|
||||
activation_result = await activate_coupon(message.from_user.id, coupon_code)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile"))
|
||||
|
||||
markup = builder.as_markup()
|
||||
|
||||
await message.answer(activation_result, reply_markup=markup, parse_mode="HTML")
|
||||
await message.answer(activation_result, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||
await state.clear()
|
||||
|
||||
|
||||
@@ -79,7 +71,7 @@ async def activate_coupon(user_id: int, coupon_code: str):
|
||||
)
|
||||
|
||||
if not coupon_record:
|
||||
return "<b>❌ Купон не найден</b> или его использование ограничено. Пожалуйста, проверьте код и попробуйте снова."
|
||||
return "<b>❌ Купон не найден</b> 🚫 или его использование ограничено. 🔒 Пожалуйста, проверьте код и попробуйте снова. 🔍"
|
||||
|
||||
usage_exists = await conn.fetchrow(
|
||||
"""
|
||||
@@ -90,7 +82,7 @@ async def activate_coupon(user_id: int, coupon_code: str):
|
||||
)
|
||||
|
||||
if usage_exists:
|
||||
return "<b>❌ Вы уже активировали этот купон.</b> Купоны могут быть активированы только один раз."
|
||||
return "<b>❌ Вы уже активировали этот купон.</b> 🚫 Купоны могут быть активированы только один раз. 🔒"
|
||||
|
||||
coupon_amount = coupon_record["amount"]
|
||||
|
||||
@@ -116,13 +108,11 @@ async def activate_coupon(user_id: int, coupon_code: str):
|
||||
)
|
||||
|
||||
await update_balance(user_id, coupon_amount)
|
||||
return f"<b>✅ Купон успешно активирован!</b>\n\nНа ваш баланс добавлено <b>{coupon_amount} рублей</b>."
|
||||
return f"<b>✅ Купон успешно активирован! 🎉</b>\n\nНа ваш баланс добавлено <b>{coupon_amount} рублей</b> 💰."
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при активации купона: {e}")
|
||||
return (
|
||||
"<b>⚠️ Произошла ошибка при активации купона.</b>\nПопробуйте ещё раз позже."
|
||||
)
|
||||
return "<b>⚠️ Произошла ошибка при активации купона! 🔧</b>\nПопробуйте ещё раз позже. 🕒"
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
+14
-24
@@ -22,28 +22,26 @@ router = Router()
|
||||
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}")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💰 Ввести сумму доната", callback_data="enter_custom_donate_amount"
|
||||
text="💰 Ввести сумму доната",
|
||||
callback_data="enter_custom_donate_amount",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_profile"))
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=callback_query.from_user.id,
|
||||
text="🌟 Поддержите наш проект!\n\n"
|
||||
"Каждый донат помогает развивать и улучшать сервис. "
|
||||
"Мы ценим вашу поддержку и работаем над тем, чтобы сделать наш продукт еще лучше. 💡",
|
||||
text="🌟 Поддержите наш проект! 💪\n\n"
|
||||
"💖 Каждый донат помогает развивать и улучшать сервис. "
|
||||
"🤝 Мы ценим вашу поддержку и работаем над тем, чтобы сделать наш продукт еще лучше. 🚀💡",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
@@ -51,10 +49,10 @@ async def process_donate(callback_query: types.CallbackQuery, state: FSMContext)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_donate_amount")
|
||||
async def process_enter_donate_amount(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
await callback_query.message.edit_text(f"💸 Введите сумму доната в рублях:")
|
||||
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 state.set_state(DonateState.entering_donate_amount)
|
||||
await callback_query.answer()
|
||||
|
||||
@@ -70,9 +68,7 @@ async def process_donate_amount_input(message: types.Message, state: FSMContext)
|
||||
if message.text.isdigit():
|
||||
amount = int(message.text)
|
||||
if amount // RUB_TO_XTR <= 0:
|
||||
await message.answer(
|
||||
f"Сумма доната должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:"
|
||||
)
|
||||
await message.answer(f"Сумма доната должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:")
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
@@ -117,18 +113,12 @@ async def on_successful_donate(message: types.Message, state: FSMContext):
|
||||
|
||||
if previous_message_id:
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=user_id, message_id=previous_message_id
|
||||
)
|
||||
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"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="Вернуться в профиль", callback_data="view_profile"))
|
||||
|
||||
sent_message = await bot.send_message(
|
||||
chat_id=user_id,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import os
|
||||
|
||||
import asyncpg
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
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
|
||||
@@ -24,17 +25,18 @@ async def send_instructions(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
return
|
||||
|
||||
back_button = InlineKeyboardButton(
|
||||
text="⬅️ Вернуться в профиль", callback_data="view_profile"
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"),
|
||||
)
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
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=keyboard,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
@@ -46,9 +48,7 @@ async def process_connect_pc(callback_query: types.CallbackQuery):
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
@@ -82,13 +82,9 @@ async def process_connect_pc(callback_query: types.CallbackQuery):
|
||||
text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}"
|
||||
)
|
||||
|
||||
support_button = types.InlineKeyboardButton(
|
||||
text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}"
|
||||
)
|
||||
support_button = types.InlineKeyboardButton(text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}")
|
||||
|
||||
back_button = types.InlineKeyboardButton(
|
||||
text="🔙 Назад в профиль", callback_data="view_profile"
|
||||
)
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад в профиль", callback_data="view_profile")
|
||||
|
||||
inline_keyboard = [
|
||||
[connect_windows_button],
|
||||
@@ -98,7 +94,10 @@ async def process_connect_pc(callback_query: types.CallbackQuery):
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=inline_keyboard)
|
||||
|
||||
await bot.send_message(
|
||||
tg_id, instruction_message, reply_markup=keyboard, parse_mode="HTML"
|
||||
tg_id,
|
||||
instruction_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
finally:
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
import uuid
|
||||
|
||||
import asyncpg
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message
|
||||
import asyncpg
|
||||
|
||||
from bot import bot, dp
|
||||
from config import CONNECT_ANDROID, CONNECT_IOS, DATABASE_URL, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, SUPPORT_CHAT_URL
|
||||
from config import (
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DATABASE_URL,
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
PUBLIC_LINK,
|
||||
SUPPORT_CHAT_URL,
|
||||
)
|
||||
from database import add_connection, get_balance, store_key, update_balance
|
||||
from handlers.instructions.instructions import send_instructions
|
||||
from handlers.keys.key_utils import create_key_on_cluster
|
||||
@@ -33,9 +41,7 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -71,18 +77,16 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext):
|
||||
callback_data="confirm_create_new_key",
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text="↩️ Назад", callback_data="cancel_create_key"
|
||||
)
|
||||
],
|
||||
[InlineKeyboardButton(text="↩️ Назад", callback_data="cancel_create_key")],
|
||||
]
|
||||
),
|
||||
)
|
||||
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"
|
||||
chat_id=callback_query.from_user.id,
|
||||
text=KEY_TRIAL,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await state.set_state(Form.waiting_for_key_name)
|
||||
|
||||
@@ -97,9 +101,7 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < RENEWAL_PLANS["1"]["price"]:
|
||||
replenish_button = InlineKeyboardButton(
|
||||
text="Перейти в профиль", callback_data="view_profile"
|
||||
)
|
||||
replenish_button = InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
||||
await callback_query.message.edit_text(NULL_BALANCE, reply_markup=keyboard)
|
||||
await state.clear()
|
||||
@@ -107,9 +109,7 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex
|
||||
|
||||
logger.info(f"Balance for user {tg_id} is sufficient. Asking for device name.")
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
"🔑 Пожалуйста, введите имя подключаемого устройства:"
|
||||
)
|
||||
await callback_query.message.edit_text("🔑 Пожалуйста, введите имя подключаемого устройства:")
|
||||
await state.set_state(Form.waiting_for_key_name)
|
||||
logger.info(f"State set to waiting_for_key_name for user {tg_id}")
|
||||
await state.update_data(creating_new_key=True)
|
||||
@@ -118,9 +118,7 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex
|
||||
|
||||
|
||||
@dp.callback_query(F.data == "cancel_create_key")
|
||||
async def cancel_create_key(
|
||||
callback_query: CallbackQuery, state: FSMContext, admin: bool
|
||||
):
|
||||
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()
|
||||
|
||||
@@ -133,17 +131,13 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
logger.info(f"User {tg_id} is attempting to create a key with the name: {key_name}")
|
||||
|
||||
if not key_name:
|
||||
await message.bot.send_message(
|
||||
tg_id, "📝 Пожалуйста, назовите устройство на английском языке."
|
||||
)
|
||||
await message.bot.send_message(tg_id, "📝 Пожалуйста, назовите устройство на английском языке.")
|
||||
logger.warning(f"User {tg_id} entered an invalid key name: {key_name}")
|
||||
return
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
logger.info(
|
||||
f"Checking if key name '{key_name}' already exists for user {tg_id} in the database."
|
||||
)
|
||||
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(),
|
||||
@@ -168,9 +162,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
logger.info(f"Checking trial status for user {tg_id}.")
|
||||
existing_connection = await conn.fetchrow(
|
||||
"SELECT trial FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
existing_connection = await conn.fetchrow("SELECT trial FROM connections WHERE tg_id = $1", tg_id)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
@@ -182,9 +174,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
else:
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < RENEWAL_PLANS["1"]["price"]:
|
||||
replenish_button = InlineKeyboardButton(
|
||||
text="Перейти в профиль", callback_data="view_profile"
|
||||
)
|
||||
replenish_button = InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
||||
await message.bot.send_message(
|
||||
tg_id,
|
||||
@@ -206,12 +196,8 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
button_support = InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL)
|
||||
|
||||
button_profile = InlineKeyboardButton(
|
||||
text="👤 Личный кабинет", callback_data="view_profile"
|
||||
)
|
||||
button_iphone = InlineKeyboardButton(
|
||||
text="🍏 Подключить", url=f"{CONNECT_IOS}{public_link}"
|
||||
)
|
||||
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}",
|
||||
@@ -238,9 +224,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.bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard)
|
||||
|
||||
try:
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
@@ -263,13 +247,9 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
logger.info(f"Updating trial status for user {tg_id} in the database.")
|
||||
existing_connection = await conn.fetchrow(
|
||||
"SELECT * FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
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
|
||||
)
|
||||
await conn.execute("UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id)
|
||||
else:
|
||||
await add_connection(tg_id, 0, 1)
|
||||
finally:
|
||||
@@ -277,7 +257,12 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
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
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
public_link,
|
||||
least_loaded_cluster,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
@@ -293,8 +278,6 @@ async def handle_instructions(callback_query: CallbackQuery):
|
||||
|
||||
|
||||
@dp.callback_query(F.data == "back_to_main")
|
||||
async def handle_back_to_main(
|
||||
callback_query: CallbackQuery, state: FSMContext, admin: bool
|
||||
):
|
||||
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()
|
||||
|
||||
@@ -24,9 +24,7 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
|
||||
)
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
existing_key = await conn.fetchrow(
|
||||
"SELECT 1 FROM keys WHERE email = $1", email
|
||||
)
|
||||
existing_key = await conn.fetchrow("SELECT 1 FROM keys WHERE email = $1", email)
|
||||
|
||||
if existing_key:
|
||||
raise ValueError(f"Email {email} уже существует в базе данных.")
|
||||
@@ -72,16 +70,12 @@ async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, to
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
tasks.append(
|
||||
extend_client_key(xui, email, new_expiry_time, client_id, total_gb)
|
||||
)
|
||||
tasks.append(extend_client_key(xui, email, new_expiry_time, client_id, total_gb))
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Не удалось продлить ключ {client_id} в кластере {cluster_id}: {e}"
|
||||
)
|
||||
logger.error(f"Не удалось продлить ключ {client_id} в кластере {cluster_id}: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
@@ -117,9 +111,7 @@ async def delete_key_from_cluster(cluster_id, email, client_id):
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Не удалось удалить ключ {client_id} в кластере {cluster_id}: {e}"
|
||||
)
|
||||
logger.error(f"Не удалось удалить ключ {client_id} в кластере {cluster_id}: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
@@ -154,12 +146,8 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
logger.info(
|
||||
f"Ключ успешно обновлен для {client_id} на всех серверах в кластере {cluster_id}"
|
||||
)
|
||||
logger.info(f"Ключ успешно обновлен для {client_id} на всех серверах в кластере {cluster_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при обновлении ключа на серверах кластера {cluster_id} для {client_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при обновлении ключа на серверах кластера {cluster_id} для {client_id}: {e}")
|
||||
raise e
|
||||
|
||||
+108
-141
@@ -1,17 +1,39 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import asyncpg
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile
|
||||
import asyncpg
|
||||
|
||||
from bot import bot
|
||||
from config import CLUSTERS, CONNECT_ANDROID, CONNECT_IOS, DATABASE_URL, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, TOTAL_GB
|
||||
from config import (
|
||||
CLUSTERS,
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DATABASE_URL,
|
||||
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, delete_key_from_db, renew_key_in_cluster, update_key_on_cluster
|
||||
from handlers.texts import INSUFFICIENT_FUNDS_MSG, KEY_NOT_FOUND_MSG, NO_KEYS, PLAN_SELECTION_MSG, RENEWAL_PLANS, SUCCESS_RENEWAL_MSG, key_message
|
||||
from handlers.keys.key_utils import (
|
||||
delete_key_from_cluster,
|
||||
delete_key_from_db,
|
||||
renew_key_in_cluster,
|
||||
update_key_on_cluster,
|
||||
)
|
||||
from handlers.texts import (
|
||||
INSUFFICIENT_FUNDS_MSG,
|
||||
KEY_NOT_FOUND_MSG,
|
||||
NO_KEYS,
|
||||
PLAN_SELECTION_MSG,
|
||||
RENEWAL_PLANS,
|
||||
SUCCESS_RENEWAL_MSG,
|
||||
key_message,
|
||||
)
|
||||
from handlers.utils import get_least_loaded_cluster, handle_error
|
||||
from logger import logger
|
||||
|
||||
@@ -45,22 +67,20 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
|
||||
buttons.append([button])
|
||||
|
||||
back_button = types.InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data="view_profile"
|
||||
)
|
||||
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>"
|
||||
"<b>🔑 Список ваших устройств</b>\n\n" "<i>👇 Выберите устройство для управления подпиской:</i>"
|
||||
)
|
||||
|
||||
image_path = os.path.join("img", "pic_keys.jpg")
|
||||
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
@@ -69,9 +89,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
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"
|
||||
),
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"),
|
||||
caption=response_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=inline_keyboard,
|
||||
@@ -86,20 +104,15 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
|
||||
else:
|
||||
response_message = NO_KEYS
|
||||
create_key_button = types.InlineKeyboardButton(
|
||||
text="➕ Создать подписку", callback_data="create_key"
|
||||
)
|
||||
back_button = types.InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data="view_profile"
|
||||
)
|
||||
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]]
|
||||
)
|
||||
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
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
@@ -110,9 +123,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
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"
|
||||
),
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"),
|
||||
caption=response_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=keyboard,
|
||||
@@ -141,9 +152,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
|
||||
try:
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -168,9 +177,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
if time_left.total_seconds() <= 0:
|
||||
days_left_message = (
|
||||
"<b>🕒 Статус подписки:</b>\n🔴 Истекла\nОсталось часов: 0"
|
||||
)
|
||||
days_left_message = "<b>🕒 Статус подписки:</b>\n🔴 Истекла\nОсталось часов: 0"
|
||||
elif time_left.days > 0:
|
||||
days_left_message = f"Осталось дней: <b>{time_left.days}</b>"
|
||||
else:
|
||||
@@ -178,37 +185,22 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
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
|
||||
)
|
||||
response_message = key_message(key, formatted_expiry_date, days_left_message, server_name)
|
||||
|
||||
download_android_button = types.InlineKeyboardButton(
|
||||
text="🤖 Скачать", url=DOWNLOAD_ANDROID
|
||||
)
|
||||
download_iphone_button = types.InlineKeyboardButton(
|
||||
text="🍏 Скачать", url=DOWNLOAD_IOS
|
||||
)
|
||||
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_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}"
|
||||
text="💻 Windows/Linux",
|
||||
callback_data=f"connect_pc|{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"
|
||||
)
|
||||
renew_button = types.InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}")
|
||||
delete_button = types.InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete_key|{key_name}")
|
||||
back_button = types.InlineKeyboardButton(text="🔙 Назад в профиль", callback_data="view_profile")
|
||||
|
||||
inline_keyboard = [
|
||||
[download_iphone_button, download_android_button],
|
||||
@@ -237,9 +229,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
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"
|
||||
),
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_view.jpg"),
|
||||
caption=response_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
@@ -256,14 +246,18 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
|
||||
except Exception as e:
|
||||
await handle_error(
|
||||
tg_id, callback_query, f"Ошибка при получении информации о ключе: {e}"
|
||||
tg_id,
|
||||
callback_query,
|
||||
f"Ошибка при получении информации о ключе: {e}",
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("update_subscription|"))
|
||||
async def process_callback_update_subscription(callback_query: types.CallbackQuery):
|
||||
async def process_callback_update_subscription(
|
||||
callback_query: types.CallbackQuery,
|
||||
):
|
||||
tg_id = callback_query.from_user.id
|
||||
email = callback_query.data.split("|")[1]
|
||||
|
||||
@@ -296,7 +290,8 @@ async def process_callback_update_subscription(callback_query: types.CallbackQue
|
||||
)
|
||||
except Exception as delete_error:
|
||||
await bot.send_message(
|
||||
tg_id, f"Ошибка при удалении старой подписки: {delete_error}"
|
||||
tg_id,
|
||||
f"Ошибка при удалении старой подписки: {delete_error}",
|
||||
)
|
||||
return
|
||||
|
||||
@@ -305,7 +300,11 @@ async def process_callback_update_subscription(callback_query: types.CallbackQue
|
||||
tasks = []
|
||||
tasks.append(
|
||||
update_key_on_cluster(
|
||||
tg_id, client_id, email, expiry_time, least_loaded_cluster_id
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_time,
|
||||
least_loaded_cluster_id,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -322,39 +321,42 @@ async def process_callback_update_subscription(callback_query: types.CallbackQue
|
||||
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
chat_id=tg_id,
|
||||
message_id=callback_query.message.message_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
response_message = f"Ваша подписка {email} обновлена!"
|
||||
back_button = types.InlineKeyboardButton(
|
||||
text="🔙 Назад в профиль", callback_data="view_profile"
|
||||
)
|
||||
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"
|
||||
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
|
||||
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"
|
||||
tg_id,
|
||||
"<b>Ключ не найден в базе данных.</b>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
await handle_error(
|
||||
tg_id, callback_query, f"Ошибка при обновлении подписки: {e}"
|
||||
)
|
||||
await handle_error(tg_id, callback_query, f"Ошибка при обновлении подписки: {e}")
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
@@ -366,9 +368,7 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
|
||||
try:
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -380,11 +380,7 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
callback_data=f"confirm_delete|{client_id}",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="❌ Нет, отменить", callback_data="view_keys"
|
||||
)
|
||||
],
|
||||
[types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="view_keys")],
|
||||
]
|
||||
)
|
||||
|
||||
@@ -412,9 +408,7 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||
|
||||
try:
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -459,11 +453,7 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||
callback_data=f"renew_plan|12|{client_id}",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data="view_profile"
|
||||
)
|
||||
],
|
||||
[types.InlineKeyboardButton(text="🔙 Назад", callback_data="view_profile")],
|
||||
]
|
||||
)
|
||||
|
||||
@@ -471,9 +461,7 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||
|
||||
response_message = PLAN_SELECTION_MSG.format(
|
||||
balance=balance,
|
||||
expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
),
|
||||
expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
|
||||
await bot.send_message(
|
||||
@@ -483,10 +471,9 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||
parse_mode="HTML",
|
||||
)
|
||||
else:
|
||||
# Если ключ не найден
|
||||
response_message = "<b>Ключ не найден.</b>"
|
||||
await bot.send_message(
|
||||
chat_id=tg_id, text=response_message, parse_mode="HTML"
|
||||
)
|
||||
await bot.send_message(chat_id=tg_id, text=response_message, parse_mode="HTML")
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
@@ -509,16 +496,12 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow(
|
||||
"SELECT client_id FROM keys WHERE email = $1", email
|
||||
)
|
||||
record = await conn.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"
|
||||
)
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await delete_key(client_id)
|
||||
@@ -533,9 +516,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||
try:
|
||||
tasks = []
|
||||
for cluster_id, cluster in CLUSTERS.items():
|
||||
tasks.append(
|
||||
delete_key_from_cluster(cluster_id, email, client_id)
|
||||
)
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
@@ -548,9 +529,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||
|
||||
else:
|
||||
response_message = "Ключ не найден или уже удален."
|
||||
back_button = types.InlineKeyboardButton(
|
||||
text="Назад", callback_data="view_keys"
|
||||
)
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await bot.edit_message_text(
|
||||
@@ -587,16 +566,15 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
|
||||
try:
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow(
|
||||
"SELECT email, expiry_time FROM keys WHERE client_id = $1", client_id
|
||||
"SELECT email, expiry_time FROM keys WHERE client_id = $1",
|
||||
client_id,
|
||||
)
|
||||
|
||||
if record:
|
||||
@@ -605,29 +583,17 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
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]]
|
||||
)
|
||||
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,
|
||||
@@ -637,16 +603,15 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
)
|
||||
return
|
||||
|
||||
response_message = SUCCESS_RENEWAL_MSG.format(
|
||||
months=RENEWAL_PLANS[plan]["months"]
|
||||
)
|
||||
back_button = types.InlineKeyboardButton(
|
||||
text="Назад", callback_data="view_profile"
|
||||
)
|
||||
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"
|
||||
tg_id,
|
||||
response_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
async def renew_key_on_servers():
|
||||
@@ -654,7 +619,11 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
for cluster_id in CLUSTERS:
|
||||
task = asyncio.create_task(
|
||||
renew_key_in_cluster(
|
||||
cluster_id, email, client_id, new_expiry_time, total_gb
|
||||
cluster_id,
|
||||
email,
|
||||
client_id,
|
||||
new_expiry_time,
|
||||
total_gb,
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
@@ -673,8 +642,6 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
await bot.send_message(
|
||||
tg_id, f"Ошибка при продлении ключа: {e}", parse_mode="HTML"
|
||||
)
|
||||
await bot.send_message(tg_id, f"Ошибка при продлении ключа: {e}", parse_mode="HTML")
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
@@ -2,8 +2,8 @@ import base64
|
||||
from datetime import datetime
|
||||
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
from aiohttp import web
|
||||
import asyncpg
|
||||
|
||||
from config import CLUSTERS, DATABASE_URL, TRANSITION_DATE_STR
|
||||
from logger import logger
|
||||
@@ -19,9 +19,7 @@ async def fetch_url_content(url, tg_id):
|
||||
logger.info(f"Успешно получен контент с {url} для tg_id: {tg_id}")
|
||||
return base64.b64decode(content).decode("utf-8").split("\n")
|
||||
else:
|
||||
logger.error(
|
||||
f"Не удалось получить {url} для tg_id: {tg_id}, статус: {response.status}"
|
||||
)
|
||||
logger.error(f"Не удалось получить {url} для tg_id: {tg_id}, статус: {response.status}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении {url} для tg_id: {tg_id}: {e}")
|
||||
@@ -30,9 +28,7 @@ async def fetch_url_content(url, tg_id):
|
||||
|
||||
async def combine_unique_lines(urls, tg_id, query_string):
|
||||
all_lines = []
|
||||
logger.info(
|
||||
f"Начинаем объединение подписок для tg_id: {tg_id}, запрос: {query_string}"
|
||||
)
|
||||
logger.info(f"Начинаем объединение подписок для tg_id: {tg_id}, запрос: {query_string}")
|
||||
|
||||
urls_with_query = [f"{url}?{query_string}" for url in urls]
|
||||
logger.info(f"Составлены URL-адреса: {urls_with_query}")
|
||||
@@ -42,9 +38,7 @@ async def combine_unique_lines(urls, tg_id, query_string):
|
||||
all_lines.extend(lines)
|
||||
|
||||
all_lines = list(set(filter(None, all_lines)))
|
||||
logger.info(
|
||||
f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}"
|
||||
)
|
||||
logger.info(f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}")
|
||||
|
||||
return all_lines
|
||||
|
||||
@@ -55,9 +49,7 @@ transition_timestamp_ms = int(transition_date.timestamp() * 1000)
|
||||
|
||||
transition_timestamp_ms_adjusted = transition_timestamp_ms - (3 * 60 * 60 * 1000)
|
||||
|
||||
logger.info(
|
||||
f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}"
|
||||
)
|
||||
logger.info(f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}")
|
||||
|
||||
|
||||
async def handle_old_subscription(request):
|
||||
@@ -74,9 +66,7 @@ async def handle_old_subscription(request):
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
key_info = await conn.fetchrow(
|
||||
"SELECT created_at FROM keys WHERE email = $1", email
|
||||
)
|
||||
key_info = await conn.fetchrow("SELECT created_at FROM keys WHERE email = $1", email)
|
||||
|
||||
if not key_info:
|
||||
logger.warning(f"Клиент с email {email} не найден в базе.")
|
||||
@@ -89,13 +79,9 @@ async def handle_old_subscription(request):
|
||||
logger.info(f"Значение created_at для клиента с email {email}: {created_at_ms}")
|
||||
|
||||
created_at_datetime = datetime.utcfromtimestamp(created_at_ms / 1000)
|
||||
logger.info(
|
||||
f"Время создания клиента в формате datetime (UTC): {created_at_datetime}"
|
||||
)
|
||||
logger.info(f"Время создания клиента в формате datetime (UTC): {created_at_datetime}")
|
||||
|
||||
logger.info(
|
||||
f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}"
|
||||
)
|
||||
logger.info(f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}")
|
||||
|
||||
if created_at_ms >= transition_timestamp_ms_adjusted:
|
||||
logger.info(f"Клиент с email {email} является новым.")
|
||||
@@ -112,9 +98,7 @@ async def handle_old_subscription(request):
|
||||
|
||||
combined_subscriptions = await combine_unique_lines(urls, email, "")
|
||||
|
||||
base64_encoded = base64.b64encode(
|
||||
"\n".join(combined_subscriptions).encode("utf-8")
|
||||
).decode("utf-8")
|
||||
base64_encoded = base64.b64encode("\n".join(combined_subscriptions).encode("utf-8")).decode("utf-8")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
@@ -145,9 +129,7 @@ async def handle_new_subscription(request):
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
client_data = await conn.fetchrow(
|
||||
"SELECT tg_id FROM keys WHERE email = $1", email
|
||||
)
|
||||
client_data = await conn.fetchrow("SELECT tg_id FROM keys WHERE email = $1", email)
|
||||
|
||||
if not client_data:
|
||||
logger.warning(f"Клиент с email {email} не найден в базе.")
|
||||
@@ -178,9 +160,7 @@ async def handle_new_subscription(request):
|
||||
|
||||
combined_subscriptions = await combine_unique_lines(urls, tg_id, query_string)
|
||||
|
||||
base64_encoded = base64.b64encode(
|
||||
"\n".join(combined_subscriptions).encode("utf-8")
|
||||
).decode("utf-8")
|
||||
base64_encoded = base64.b64encode("\n".join(combined_subscriptions).encode("utf-8")).decode("utf-8")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
import uuid
|
||||
|
||||
import asyncpg
|
||||
from py3xui import AsyncApi
|
||||
@@ -23,9 +23,7 @@ async def create_trial_key(tg_id: int):
|
||||
|
||||
result = {"key": public_link, "instructions": instructions}
|
||||
|
||||
asyncio.create_task(
|
||||
generate_and_store_keys(tg_id, client_id, email, public_link)
|
||||
)
|
||||
asyncio.create_task(generate_and_store_keys(tg_id, client_id, email, public_link))
|
||||
|
||||
return result
|
||||
|
||||
@@ -33,9 +31,7 @@ async def create_trial_key(tg_id: int):
|
||||
await conn.close()
|
||||
|
||||
|
||||
async def generate_and_store_keys(
|
||||
tg_id: int, client_id: str, email: str, public_link: str
|
||||
):
|
||||
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()
|
||||
|
||||
+29
-54
@@ -1,8 +1,8 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import asyncpg
|
||||
from aiogram import Bot, Router, types
|
||||
import asyncpg
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import delete_client
|
||||
@@ -22,9 +22,7 @@ async def notify_expiring_keys(bot: Bot):
|
||||
logger.info("Подключение к базе данных успешно.")
|
||||
|
||||
current_time = datetime.utcnow().timestamp() * 1000
|
||||
threshold_time_10h = (
|
||||
datetime.utcnow() + timedelta(hours=10)
|
||||
).timestamp() * 1000
|
||||
threshold_time_10h = (datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000
|
||||
threshold_time_24h = (datetime.utcnow() + timedelta(days=1)).timestamp() * 1000
|
||||
|
||||
logger.info("Начало обработки уведомлений.")
|
||||
@@ -48,19 +46,18 @@ async def is_bot_blocked(bot: Bot, chat_id: int) -> bool:
|
||||
try:
|
||||
member = await bot.get_chat_member(chat_id, bot.id)
|
||||
blocked = member.status == "left"
|
||||
logger.info(
|
||||
f"Статус бота для пользователя {chat_id}: {'заблокирован' if blocked else 'активен'}"
|
||||
)
|
||||
logger.info(f"Статус бота для пользователя {chat_id}: {'заблокирован' if blocked else 'активен'}")
|
||||
return blocked
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Не удалось проверить статус бота для пользователя {chat_id}: {e}"
|
||||
)
|
||||
logger.warning(f"Не удалось проверить статус бота для пользователя {chat_id}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def notify_10h_keys(
|
||||
bot: Bot, conn: asyncpg.Connection, current_time: float, threshold_time_10h: float
|
||||
bot: Bot,
|
||||
conn: asyncpg.Connection,
|
||||
current_time: float,
|
||||
threshold_time_10h: float,
|
||||
):
|
||||
records = await conn.fetch(
|
||||
"""
|
||||
@@ -93,6 +90,7 @@ async def notify_10h_keys(
|
||||
email=email,
|
||||
expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
days_left_message=days_left_message,
|
||||
price=RENEWAL_PLANS["1"]["price"],
|
||||
)
|
||||
|
||||
if not await is_bot_blocked(bot, tg_id):
|
||||
@@ -113,7 +111,8 @@ async def notify_10h_keys(
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет", callback_data="view_profile"
|
||||
text="👤 Личный кабинет",
|
||||
callback_data="view_profile",
|
||||
)
|
||||
],
|
||||
]
|
||||
@@ -121,9 +120,7 @@ async def notify_10h_keys(
|
||||
await bot.send_message(tg_id, message, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при отправке уведомления пользователю {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")
|
||||
continue
|
||||
|
||||
await conn.execute(
|
||||
@@ -136,7 +133,10 @@ async def notify_10h_keys(
|
||||
|
||||
|
||||
async def notify_24h_keys(
|
||||
bot: Bot, conn: asyncpg.Connection, current_time: float, threshold_time_24h: float
|
||||
bot: Bot,
|
||||
conn: asyncpg.Connection,
|
||||
current_time: float,
|
||||
threshold_time_24h: float,
|
||||
):
|
||||
logger.info("Проверка истекших ключей...")
|
||||
|
||||
@@ -191,7 +191,8 @@ async def notify_24h_keys(
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет", callback_data="view_profile"
|
||||
text="👤 Личный кабинет",
|
||||
callback_data="view_profile",
|
||||
)
|
||||
],
|
||||
]
|
||||
@@ -199,18 +200,14 @@ async def notify_24h_keys(
|
||||
await bot.send_message(tg_id, message_24h, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление за 24 часа отправлено пользователю {tg_id}.")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при отправке уведомления за 24 часа пользователю {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при отправке уведомления за 24 часа пользователю {tg_id}: {e}")
|
||||
continue
|
||||
|
||||
await conn.execute(
|
||||
"UPDATE keys SET notified_24h = TRUE WHERE client_id = $1",
|
||||
record["client_id"],
|
||||
)
|
||||
logger.info(
|
||||
f"Обновлено поле notified_24h для клиента {record['client_id']}."
|
||||
)
|
||||
logger.info(f"Обновлено поле notified_24h для клиента {record['client_id']}.")
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
@@ -248,30 +245,18 @@ 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="view_profile")]]
|
||||
)
|
||||
|
||||
try:
|
||||
if balance >= RENEWAL_PLANS["1"]["price"]:
|
||||
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
|
||||
new_expiry_time = int(
|
||||
(datetime.utcnow() + timedelta(days=30)).timestamp() * 1000
|
||||
)
|
||||
new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000)
|
||||
await update_key_expiry(client_id, new_expiry_time)
|
||||
|
||||
for cluster_id in CLUSTERS:
|
||||
await renew_key_in_cluster(
|
||||
cluster_id, email, client_id, new_expiry_time, TOTAL_GB
|
||||
)
|
||||
logger.info(
|
||||
f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}."
|
||||
)
|
||||
await renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, TOTAL_GB)
|
||||
logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.")
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -281,25 +266,15 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
""",
|
||||
client_id,
|
||||
)
|
||||
logger.info(
|
||||
f"Флаги notified и notified_24 сброшены для клиента с ID {client_id}."
|
||||
)
|
||||
logger.info(f"Флаги notified и notified_24 сброшены для клиента с ID {client_id}.")
|
||||
try:
|
||||
await bot.send_message(
|
||||
tg_id, text=KEY_RENEWED, reply_markup=keyboard
|
||||
)
|
||||
logger.info(
|
||||
f"Уведомление об успешном продлении отправлено клиенту {tg_id}."
|
||||
)
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при отправке уведомления клиенту {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при отправке уведомления клиенту {tg_id}: {e}")
|
||||
|
||||
else:
|
||||
await safe_send_message(
|
||||
bot, tg_id, message_expired, reply_markup=keyboard
|
||||
)
|
||||
await safe_send_message(bot, tg_id, message_expired, reply_markup=keyboard)
|
||||
await delete_key(client_id)
|
||||
|
||||
for cluster_id, cluster in CLUSTERS.items():
|
||||
|
||||
+2
-8
@@ -49,15 +49,9 @@ async def handle_pay(callback_query: CallbackQuery):
|
||||
callback_data="pay_robokassa",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🎟️ Активировать купон", callback_data="activate_coupon"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🎟️ Активировать купон", callback_data="activate_coupon"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"💸 <b>Выберите удобный способ пополнения баланса:</b>\n\n"
|
||||
|
||||
@@ -8,7 +8,7 @@ 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, check_connection_exists, get_key_count, update_balance
|
||||
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from logger import logger
|
||||
|
||||
@@ -24,22 +24,16 @@ 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"
|
||||
):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
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:
|
||||
@@ -50,9 +44,7 @@ async def send_message_with_deletion(
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_cryptobot")
|
||||
async def process_callback_pay_cryptobot(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -78,7 +70,8 @@ async def process_callback_pay_cryptobot(
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_crypto"
|
||||
text="💰 Ввести свою сумму",
|
||||
callback_data="enter_custom_amount_crypto",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile"))
|
||||
@@ -91,9 +84,7 @@ async def process_callback_pay_cryptobot(
|
||||
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
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить сообщение: {e}")
|
||||
|
||||
@@ -108,9 +99,7 @@ async def process_callback_pay_cryptobot(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("crypto_amount|"))
|
||||
async def process_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
data = callback_query.data.split("|", 1)
|
||||
|
||||
if len(data) != 2:
|
||||
@@ -152,9 +141,7 @@ async def process_amount_selection(
|
||||
|
||||
if hasattr(invoice, "bot_invoice_url"):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url)
|
||||
)
|
||||
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,
|
||||
@@ -173,9 +160,7 @@ async def process_amount_selection(
|
||||
async def send_payment_success_notification(user_id: int, amount: float):
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||
@@ -193,9 +178,7 @@ async def cryptobot_webhook(request):
|
||||
await process_crypto_payment(data["payload"])
|
||||
return web.Response(status=200)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Неподдерживаемый тип обновления: {data.get('update_type')}"
|
||||
)
|
||||
logger.warning(f"Неподдерживаемый тип обновления: {data.get('update_type')}")
|
||||
return web.Response(status=400)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка обработки вебхука: {e}")
|
||||
@@ -209,6 +192,7 @@ async def process_crypto_payment(payload):
|
||||
try:
|
||||
user_id = int(user_id_str)
|
||||
amount = int(amount_str)
|
||||
await add_payment(int(user_id), float(amount), "cryptobot")
|
||||
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
|
||||
await update_balance(user_id, amount)
|
||||
await send_payment_success_notification(user_id, amount)
|
||||
@@ -219,9 +203,7 @@ 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
|
||||
):
|
||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_crypto)
|
||||
await callback_query.answer()
|
||||
@@ -232,15 +214,11 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
if message.text.isdigit():
|
||||
amount = int(message.text)
|
||||
if amount // RUB_TO_USDT <= 0:
|
||||
await message.answer(
|
||||
f"Сумма должна быть больше {RUB_TO_USDT}. Пожалуйста, введите сумму еще раз:"
|
||||
)
|
||||
await message.answer(f"Сумма должна быть больше {RUB_TO_USDT}. Пожалуйста, введите сумму еще раз:")
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
await state.set_state(
|
||||
ReplenishBalanceState.waiting_for_payment_confirmation_crypto
|
||||
)
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_crypto)
|
||||
try:
|
||||
invoice = await crypto.create_invoice(
|
||||
asset="USDT",
|
||||
@@ -251,9 +229,7 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
|
||||
if hasattr(invoice, "bot_invoice_url"):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"),
|
||||
)
|
||||
@@ -263,7 +239,9 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
)
|
||||
else:
|
||||
await send_message_with_deletion(
|
||||
message.from_user.id, "Ошибка при создании платежа.", state=state
|
||||
message.from_user.id,
|
||||
"Ошибка при создании платежа.",
|
||||
state=state,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании платежа: {e}")
|
||||
|
||||
@@ -4,17 +4,17 @@ import logging
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
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 update_balance
|
||||
from database import add_payment, update_balance
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
|
||||
router = Router()
|
||||
@@ -49,9 +49,7 @@ async def create_payment(user_id, amount, email, ip):
|
||||
params["signature"] = generate_signature(params, FREEKASSA_API_KEY)
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
"https://api.freekassa.com/v1/orders/create", json=params
|
||||
)
|
||||
response = requests.post("https://api.freekassa.com/v1/orders/create", json=params)
|
||||
response_data = response.json()
|
||||
|
||||
logging.debug(f"Ответ от FreeKassa при создании платежа: {response_data}")
|
||||
@@ -86,6 +84,7 @@ async def freekassa_webhook(request):
|
||||
if data["status"] == "completed":
|
||||
user_id = data["metadata"]["user_id"]
|
||||
amount = float(data["amount"])
|
||||
await add_payment(int(user_id), float(amount), "freekassa")
|
||||
|
||||
await update_balance(user_id, amount)
|
||||
await send_payment_success_notification(user_id, amount)
|
||||
@@ -94,9 +93,7 @@ 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
|
||||
):
|
||||
async def process_callback_pay_freekassa(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -121,14 +118,13 @@ async def process_callback_pay_freekassa(
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_freekassa"
|
||||
text="💰 Ввести свою сумму",
|
||||
callback_data="enter_custom_amount_freekassa",
|
||||
)
|
||||
)
|
||||
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.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
@@ -141,9 +137,7 @@ async def process_callback_pay_freekassa(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("freekassa_amount|"))
|
||||
async def process_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
data = callback_query.data.split("|", 1)
|
||||
amount_str = data[1]
|
||||
try:
|
||||
@@ -154,18 +148,12 @@ async def process_amount_selection(
|
||||
|
||||
user_email = f"{callback_query.from_user.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.from_user.id, amount, user_email, user_ip)
|
||||
|
||||
if payment_url:
|
||||
confirm_keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=f"Оплатить {amount} рублей", url=payment_url
|
||||
)
|
||||
],
|
||||
[InlineKeyboardButton(text=f"Оплатить {amount} рублей", url=payment_url)],
|
||||
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")],
|
||||
]
|
||||
)
|
||||
@@ -185,9 +173,7 @@ async def process_amount_selection(
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_freekassa")
|
||||
async def process_enter_custom_amount(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_freekassa)
|
||||
await callback_query.answer()
|
||||
@@ -198,21 +184,15 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
if message.text.isdigit():
|
||||
amount = int(message.text)
|
||||
if amount <= 0:
|
||||
await message.answer(
|
||||
"Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
|
||||
)
|
||||
await message.answer("Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:")
|
||||
return
|
||||
|
||||
user_email = f"{message.from_user.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.from_user.id, amount, user_email, user_ip)
|
||||
|
||||
if payment_url:
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[[InlineKeyboardButton("Оплатить", url=payment_url)]]
|
||||
)
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton("Оплатить", url=payment_url)]])
|
||||
await message.answer(
|
||||
f"Вы выбрали оплату на {amount} рублей. Перейдите по ссылке для завершения оплаты:",
|
||||
reply_markup=keyboard,
|
||||
|
||||
@@ -6,13 +6,13 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiohttp import web
|
||||
from loguru import logger
|
||||
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, check_connection_exists, get_key_count, update_balance
|
||||
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -36,35 +36,28 @@ if ROBOKASSA_ENABLE:
|
||||
|
||||
def generate_payment_link(amount, inv_id, description, tg_id):
|
||||
"""Генерация ссылки на оплату."""
|
||||
logger.debug(
|
||||
f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}"
|
||||
)
|
||||
logger.debug(f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}")
|
||||
payment_link = robokassa._payment.link.generate_by_script(
|
||||
out_sum=amount, inv_id=inv_id, description="пополнение баланса", id=f"{tg_id}"
|
||||
out_sum=amount,
|
||||
inv_id=inv_id,
|
||||
description="пополнение баланса",
|
||||
id=f"{tg_id}",
|
||||
)
|
||||
logger.info(f"Generated payment link: {payment_link}")
|
||||
return payment_link
|
||||
|
||||
|
||||
async def send_message_with_deletion(
|
||||
chat_id, text, reply_markup=None, state=None, message_key="last_message_id"
|
||||
):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
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}")
|
||||
@@ -76,9 +69,7 @@ async def send_message_with_deletion(
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_robokassa")
|
||||
async def process_callback_pay_robokassa(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
logger.info(f"User {tg_id} initiated Robokassa payment.")
|
||||
|
||||
@@ -104,7 +95,8 @@ async def process_callback_pay_robokassa(
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_robokassa"
|
||||
text="💰 Ввести свою сумму",
|
||||
callback_data="enter_custom_amount_robokassa",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile"))
|
||||
@@ -118,9 +110,7 @@ async def process_callback_pay_robokassa(
|
||||
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
|
||||
)
|
||||
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}")
|
||||
@@ -136,9 +126,7 @@ async def process_callback_pay_robokassa(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("robokassa_amount|"))
|
||||
async def process_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
logger.info(f"Получены данные callback_data: {callback_query.data}")
|
||||
|
||||
data = callback_query.data.split("|")
|
||||
@@ -203,9 +191,7 @@ async def robokassa_webhook(request):
|
||||
shp_id = params.get("shp_id")
|
||||
signature_value = params.get("SignatureValue")
|
||||
|
||||
logger.info(
|
||||
f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}"
|
||||
)
|
||||
logger.info(f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}")
|
||||
|
||||
if not check_payment_signature(params):
|
||||
logger.error("Неверная подпись или данные запроса.")
|
||||
@@ -222,6 +208,8 @@ async def robokassa_webhook(request):
|
||||
await update_balance(int(tg_id), float(amount))
|
||||
await send_payment_success_notification(tg_id, float(amount))
|
||||
|
||||
await add_payment(int(tg_id), float(amount), "robokassa")
|
||||
|
||||
logger.info(f"Payment successful. Balance updated for user {tg_id}.")
|
||||
|
||||
return web.Response(text=f"OK{inv_id}")
|
||||
@@ -242,9 +230,7 @@ def check_payment_signature(params):
|
||||
|
||||
logger.info(f"Signature string before hashing: {signature_string}")
|
||||
|
||||
expected_signature = (
|
||||
hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
|
||||
)
|
||||
expected_signature = hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
|
||||
|
||||
logger.info(f"Expected signature: {expected_signature}")
|
||||
logger.info(f"Received signature: {signature_value}")
|
||||
@@ -254,9 +240,7 @@ def check_payment_signature(params):
|
||||
|
||||
async def send_payment_success_notification(user_id: int, amount: float):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
@@ -267,18 +251,12 @@ async def send_payment_success_notification(user_id: int, amount: float):
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_robokassa")
|
||||
async def process_custom_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_custom_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
logger.info(f"User {tg_id} chose to enter a custom amount.")
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text="Пожалуйста, введите сумму пополнения в рублях (например, 150):"
|
||||
)
|
||||
await state.set_state(
|
||||
ReplenishBalanceState.waiting_for_payment_confirmation_robokassa
|
||||
)
|
||||
await callback_query.message.edit_text(text="Пожалуйста, введите сумму пополнения в рублях (например, 150):")
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@@ -313,6 +291,4 @@ async def handle_custom_amount_input(message: types.Message, state: FSMContext):
|
||||
await state.clear()
|
||||
except ValueError as e:
|
||||
logger.error(f"Некорректная сумма от пользователя {tg_id}: {e}")
|
||||
await message.answer(
|
||||
text="Введите корректную сумму в рублях (целое положительное число)."
|
||||
)
|
||||
await message.answer(text="Введите корректную сумму в рублях (целое положительное число).")
|
||||
|
||||
@@ -6,7 +6,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import RUB_TO_XTR
|
||||
from database import add_connection, check_connection_exists, get_key_count, update_balance
|
||||
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from logger import logger
|
||||
|
||||
@@ -19,22 +19,16 @@ 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"
|
||||
):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
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:
|
||||
@@ -45,17 +39,11 @@ async def send_message_with_deletion(
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_stars")
|
||||
async def process_callback_pay_stars(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_callback_pay_stars(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"))
|
||||
|
||||
for i in range(0, len(PAYMENT_OPTIONS), 2):
|
||||
if i + 1 < len(PAYMENT_OPTIONS):
|
||||
@@ -78,7 +66,8 @@ async def process_callback_pay_stars(
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_stars"
|
||||
text="💰 Ввести свою сумму",
|
||||
callback_data="enter_custom_amount_stars",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
|
||||
@@ -106,9 +95,7 @@ async def process_callback_pay_stars(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("stars_amount|"))
|
||||
async def process_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
data = callback_query.data.split("|", 1)
|
||||
|
||||
if len(data) != 2:
|
||||
@@ -165,9 +152,7 @@ async def process_amount_selection(
|
||||
async def send_payment_success_notification(user_id: int, amount: float):
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||
@@ -178,9 +163,7 @@ async def send_payment_success_notification(user_id: int, amount: float):
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_stars")
|
||||
async def process_enter_custom_amount(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_stars)
|
||||
await callback_query.answer()
|
||||
@@ -191,15 +174,11 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
if message.text.isdigit():
|
||||
amount = int(message.text)
|
||||
if amount // RUB_TO_XTR <= 0:
|
||||
await message.answer(
|
||||
f"Сумма должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:"
|
||||
)
|
||||
await message.answer(f"Сумма должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:")
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
await state.set_state(
|
||||
ReplenishBalanceState.waiting_for_payment_confirmation_stars
|
||||
)
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_stars)
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
@@ -237,6 +216,7 @@ async def on_successful_payment(
|
||||
user_id = int(message.from_user.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")
|
||||
await update_balance(user_id, amount)
|
||||
await send_payment_success_notification(user_id, amount)
|
||||
except ValueError as e:
|
||||
|
||||
@@ -10,7 +10,7 @@ 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, check_connection_exists, get_key_count, update_balance
|
||||
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
|
||||
from handlers.texts import PAYMENT_OPTIONS
|
||||
from logger import logger
|
||||
|
||||
@@ -29,22 +29,16 @@ 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"
|
||||
):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
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:
|
||||
@@ -55,9 +49,7 @@ async def send_message_with_deletion(
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_yookassa")
|
||||
async def process_callback_pay_yookassa(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_callback_pay_yookassa(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -83,7 +75,8 @@ async def process_callback_pay_yookassa(
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_yookassa"
|
||||
text="💰 Ввести свою сумму",
|
||||
callback_data="enter_custom_amount_yookassa",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_profile"))
|
||||
@@ -96,9 +89,7 @@ async def process_callback_pay_yookassa(
|
||||
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
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить сообщение: {e}")
|
||||
|
||||
@@ -113,9 +104,7 @@ async def process_callback_pay_yookassa(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("yookassa_amount|"))
|
||||
async def process_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
data = callback_query.data.split("|", 1)
|
||||
|
||||
if len(data) != 2:
|
||||
@@ -140,9 +129,7 @@ async def process_amount_selection(
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
await state.set_state(
|
||||
ReplenishBalanceState.waiting_for_payment_confirmation_yookassa
|
||||
)
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_yookassa)
|
||||
|
||||
# state_data = await state.get_data()
|
||||
customer_name = callback_query.from_user.full_name
|
||||
@@ -153,7 +140,10 @@ async def process_amount_selection(
|
||||
payment = Payment.create(
|
||||
{
|
||||
"amount": {"value": str(amount), "currency": "RUB"},
|
||||
"confirmation": {"type": "redirect", "return_url": "https://pocomacho.ru/"},
|
||||
"confirmation": {
|
||||
"type": "redirect",
|
||||
"return_url": "https://pocomacho.ru/",
|
||||
},
|
||||
"capture": True,
|
||||
"description": "Пополнение баланса",
|
||||
"receipt": {
|
||||
@@ -192,7 +182,9 @@ async def process_amount_selection(
|
||||
)
|
||||
else:
|
||||
await send_message_with_deletion(
|
||||
callback_query.from_user.id, "Ошибка при создании платежа.", state=state
|
||||
callback_query.from_user.id,
|
||||
"Ошибка при создании платежа.",
|
||||
state=state,
|
||||
)
|
||||
|
||||
await callback_query.answer()
|
||||
@@ -201,9 +193,7 @@ async def process_amount_selection(
|
||||
async def send_payment_success_notification(user_id: int, amount: float):
|
||||
try:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
@@ -224,6 +214,7 @@ async def yookassa_webhook(request):
|
||||
user_id = int(user_id_str)
|
||||
amount = float(amount_str)
|
||||
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
|
||||
await add_payment(int(user_id), float(amount), "yookassa")
|
||||
await update_balance(user_id, amount)
|
||||
await send_payment_success_notification(user_id, amount)
|
||||
except ValueError as e:
|
||||
@@ -233,9 +224,7 @@ 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
|
||||
):
|
||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||
await state.set_state(ReplenishBalanceState.entering_custom_amount_yookassa)
|
||||
await callback_query.answer()
|
||||
@@ -246,15 +235,11 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
if message.text.isdigit():
|
||||
amount = int(message.text)
|
||||
if amount <= 0:
|
||||
await message.answer(
|
||||
"Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
|
||||
)
|
||||
await message.answer("Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:")
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
await state.set_state(
|
||||
ReplenishBalanceState.waiting_for_payment_confirmation_yookassa
|
||||
)
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_yookassa)
|
||||
|
||||
try:
|
||||
payment = Payment.create(
|
||||
@@ -276,7 +261,10 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
|
||||
{
|
||||
"description": "Пополнение баланса",
|
||||
"quantity": "1.00",
|
||||
"amount": {"value": str(amount), "currency": "RUB"},
|
||||
"amount": {
|
||||
"value": str(amount),
|
||||
"currency": "RUB",
|
||||
},
|
||||
"vat_code": 6,
|
||||
}
|
||||
],
|
||||
|
||||
+34
-23
@@ -8,15 +8,13 @@ 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 get_referral_link, invite_message_send, profile_message_send
|
||||
from handlers.texts import RENEWAL_PLANS, get_referral_link, invite_message_send, profile_message_send
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
async def process_callback_view_profile(
|
||||
callback_query: types.CallbackQuery, state: FSMContext, admin: bool
|
||||
):
|
||||
async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext, admin: bool):
|
||||
chat_id = callback_query.from_user.id
|
||||
username = callback_query.from_user.full_name
|
||||
|
||||
@@ -35,6 +33,7 @@ async def process_callback_view_profile(
|
||||
|
||||
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"),
|
||||
@@ -49,16 +48,10 @@ async def process_callback_view_profile(
|
||||
InlineKeyboardButton(text="👥 Пригласить друзей", callback_data="invite"),
|
||||
InlineKeyboardButton(text="📘 Инструкции", callback_data="instructions"),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
|
||||
)
|
||||
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="admin"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Главное меню", callback_data="back_to_menu"))
|
||||
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
@@ -84,9 +77,33 @@ async def process_callback_view_profile(
|
||||
|
||||
except Exception as e:
|
||||
await bot.send_message(
|
||||
chat_id, f"❗️ Не удалось загрузить профиль. Техническая ошибка: {e}"
|
||||
chat_id,
|
||||
f"❗️ Не удалось загрузить профиль. Техническая ошибка: {e}",
|
||||
)
|
||||
|
||||
|
||||
@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"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"<b>🚀 Доступные тарифы VPN:</b>\n\n"
|
||||
+ "\n".join(
|
||||
[
|
||||
f"{months} {'месяц' if months == '1' else 'месяца' if int(months) in [2, 3, 4] else 'месяцев'}: "
|
||||
f"{RENEWAL_PLANS[months]['price']} "
|
||||
f"{'💳' if months == '1' else '🌟' if months == '3' else '🔥' if months == '6' else '🚀'} рублей"
|
||||
for months in sorted(RENEWAL_PLANS.keys(), key=int)
|
||||
]
|
||||
),
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@@ -102,9 +119,7 @@ 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")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
|
||||
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
@@ -116,9 +131,7 @@ async def invite_handler(callback_query: types.CallbackQuery):
|
||||
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"
|
||||
),
|
||||
photo=BufferedInputFile(image_file.read(), filename="pic_invite.jpg"),
|
||||
caption=invite_message,
|
||||
parse_mode="HTML",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -142,8 +155,6 @@ async def invite_handler(callback_query: types.CallbackQuery):
|
||||
|
||||
|
||||
@router.callback_query(F.data == "view_profile")
|
||||
async def view_profile_handler(
|
||||
callback_query: types.CallbackQuery, state: FSMContext, admin: bool = False
|
||||
):
|
||||
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)
|
||||
|
||||
+29
-60
@@ -1,12 +1,20 @@
|
||||
import os
|
||||
|
||||
import asyncpg
|
||||
from aiogram import F, Router
|
||||
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 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 handlers.keys.trial_key import create_trial_key
|
||||
from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn
|
||||
@@ -20,16 +28,10 @@ async def send_welcome_message(chat_id: int, trial_status: int, admin: bool):
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
if trial_status == 0:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
|
||||
)
|
||||
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="admin"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL),
|
||||
)
|
||||
@@ -58,9 +60,7 @@ async def send_welcome_message(chat_id: int, trial_status: int, admin: bool):
|
||||
|
||||
async def start_command(message: Message, admin: bool = False):
|
||||
try:
|
||||
logger.info(
|
||||
f"Получена команда /start. Текст сообщения: {message.text}, user_id: {message.from_user.id}"
|
||||
)
|
||||
logger.info(f"Получена команда /start. Текст сообщения: {message.text}, user_id: {message.from_user.id}")
|
||||
|
||||
if "referral_" in message.text:
|
||||
logger.info("Обнаружен реферальный код.")
|
||||
@@ -69,49 +69,27 @@ async def start_command(message: Message, admin: bool = False):
|
||||
logger.info(f"ID пригласившего пользователя: {referrer_tg_id}")
|
||||
except ValueError:
|
||||
logger.error("Ошибка парсинга реферального ID.")
|
||||
await message.answer("Некорректный реферальный код.")
|
||||
return
|
||||
|
||||
connection_exists = await check_connection_exists(message.from_user.id)
|
||||
logger.info(
|
||||
f"Результат проверки подключения для user_id {message.from_user.id}: {connection_exists}"
|
||||
)
|
||||
|
||||
logger.info(f"Результат проверки подключения для user_id {message.from_user.id}: {connection_exists}")
|
||||
if not connection_exists:
|
||||
logger.info(
|
||||
f"Добавляем подключение для пользователя: {message.from_user.id}"
|
||||
)
|
||||
logger.info(f"Добавляем подключение для пользователя: {message.from_user.id}")
|
||||
await add_connection(message.from_user.id)
|
||||
|
||||
logger.info(
|
||||
f"Добавляем реферал для пользователя {message.from_user.id}, приглашённым {referrer_tg_id}"
|
||||
)
|
||||
logger.info(f"Добавляем реферал для пользователя {message.from_user.id}, приглашённым {referrer_tg_id}")
|
||||
await add_referral(message.from_user.id, referrer_tg_id)
|
||||
|
||||
await message.answer("Вас пригласил друг, добро пожаловать!")
|
||||
else:
|
||||
logger.warning(
|
||||
f"Пользователь {message.from_user.id} уже зарегистрирован."
|
||||
)
|
||||
await message.answer("Вы уже зарегистрированы в системе!")
|
||||
logger.info(f"Пользователь {message.from_user.id} уже зарегистрирован.")
|
||||
|
||||
logger.info(
|
||||
f"Проверяем статус пробного периода для user_id {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}: {trial_status}")
|
||||
|
||||
logger.info(
|
||||
f"Отправка приветственного сообщения для user_id {message.from_user.id}"
|
||||
)
|
||||
logger.info(f"Отправка приветственного сообщения для user_id {message.from_user.id}")
|
||||
await send_welcome_message(message.chat.id, trial_status, admin)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"Ошибка в обработке команды /start для user_id {message.from_user.id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка в обработке команды /start для user_id {message.from_user.id}: {e}")
|
||||
await message.answer("Произошла ошибка. Пожалуйста, попробуйте позже.")
|
||||
|
||||
|
||||
@@ -141,9 +119,7 @@ async def handle_connect_vpn(callback_query: CallbackQuery):
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обновлении trial: {e}")
|
||||
await callback_query.message.answer(
|
||||
"Произошла ошибка при обновлении статуса."
|
||||
)
|
||||
await callback_query.message.answer("Произошла ошибка при обновлении статуса.")
|
||||
|
||||
key_message = (
|
||||
f"🔑 <b>Ваш персональный ключ доступа:</b>\n"
|
||||
@@ -152,16 +128,15 @@ async def handle_connect_vpn(callback_query: CallbackQuery):
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🍏 Подключить на iOS", url=f'{CONNECT_IOS}{trial_key_info["key"]}'
|
||||
text="🍏 Подключить на iOS",
|
||||
url=f'{CONNECT_IOS}{trial_key_info["key"]}',
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="🤖 Подключить на Android",
|
||||
@@ -169,9 +144,7 @@ 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.message.answer(key_message, parse_mode="HTML", reply_markup=builder.as_markup())
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
@@ -183,14 +156,10 @@ async def handle_about_vpn(callback_query: CallbackQuery):
|
||||
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="donate"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
about_vpn_message, parse_mode="HTML", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer(about_vpn_message, parse_mode="HTML", reply_markup=builder.as_markup())
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
|
||||
+4
-14
@@ -56,11 +56,7 @@ async def get_least_loaded_cluster() -> str:
|
||||
logger.info(f"Cluster loads: {cluster_loads}")
|
||||
|
||||
if not cluster_loads:
|
||||
available_clusters = [
|
||||
cluster_id
|
||||
for cluster_id in CLUSTERS.keys()
|
||||
if re.match(r"^cluster\d+$", cluster_id)
|
||||
]
|
||||
available_clusters = [cluster_id for cluster_id in CLUSTERS.keys() if re.match(r"^cluster\d+$", cluster_id)]
|
||||
|
||||
logger.info(f"Available clusters from config: {available_clusters}")
|
||||
|
||||
@@ -72,18 +68,14 @@ async def get_least_loaded_cluster() -> str:
|
||||
logger.warning("No valid clusters found in config, returning 'cluster1'.")
|
||||
return "cluster1"
|
||||
|
||||
least_loaded_cluster = min(
|
||||
cluster_loads, key=lambda k: (cluster_loads.get(k, 0), k)
|
||||
)
|
||||
least_loaded_cluster = min(cluster_loads, key=lambda k: (cluster_loads.get(k, 0), k))
|
||||
|
||||
logger.info(f"Least loaded cluster selected: {least_loaded_cluster}")
|
||||
|
||||
return least_loaded_cluster
|
||||
|
||||
|
||||
async def handle_error(
|
||||
tg_id: int, callback_query: Optional[object] = None, message: str = ""
|
||||
) -> None:
|
||||
async def handle_error(tg_id: int, callback_query: Optional[object] = None, message: str = "") -> None:
|
||||
"""
|
||||
Обрабатывает ошибку, отправляя сообщение пользователю.
|
||||
|
||||
@@ -95,9 +87,7 @@ async def handle_error(
|
||||
try:
|
||||
if callback_query and hasattr(callback_query, "message"):
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception as delete_error:
|
||||
logger.warning(f"Не удалось удалить сообщение: {delete_error}")
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Any, Awaitable, Callable, Dict
|
||||
|
||||
import asyncpg
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
import asyncpg
|
||||
|
||||
from config import DATABASE_URL
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
[tool.black]
|
||||
line-length = 120 # Совпадает с flake8
|
||||
target-version = ['py39','py310','py311'] # Укажите версию Python вашего проекта
|
||||
skip-string-normalization = true # Отключает нормализацию кавычек
|
||||
include = '\.pyi?$' # Включает Python-файлы
|
||||
exclude = '''
|
||||
/(
|
||||
\.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| _build
|
||||
| buck-out
|
||||
| build
|
||||
| dist
|
||||
)/
|
||||
''' # Исключает системные папки
|
||||
|
||||
[tool.isort]
|
||||
profile = "black" # Устанавливает совместимость с black
|
||||
line_length = 120
|
||||
multi_line_output = 3 # Формат многострочных импортов
|
||||
include_trailing_comma = true # Совместимость с black
|
||||
force_sort_within_sections = true # Сортировка внутри секций
|
||||
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
|
||||
skip_gitignore = true # Учитывать .gitignore
|
||||
+5
-1
@@ -29,4 +29,8 @@ loguru
|
||||
aiocryptopay
|
||||
py3xui
|
||||
sqlalchemy
|
||||
robokassa
|
||||
robokassa
|
||||
flake8
|
||||
black
|
||||
isort
|
||||
pylint
|
||||
Reference in New Issue
Block a user