Update config

This commit is contained in:
Zakhar Izmaylov
2024-11-22 21:24:55 +03:00
parent 034c1246bd
commit d1b7a01b4f
30 changed files with 556 additions and 891 deletions
+2 -2
View File
@@ -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,config.py
-3
View File
@@ -1,3 +0,0 @@
[settings]
profile=black
line_length = 250
+3 -1
View File
@@ -1,2 +1,4 @@
formatting:
@black . && isort . && flake8
@echo "Running black..." && black .
@echo "Running isort..." && isort .
@echo "Running flake8..." && flake8 --config .flake8
+14 -5
View File
@@ -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:
+3 -9
View File
@@ -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}")
+12 -8
View File
@@ -216,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(
@@ -273,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
@@ -378,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):
@@ -445,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()
+8 -22
View File
@@ -1,10 +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
@@ -34,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"
@@ -53,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())
@@ -83,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"
@@ -99,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()
@@ -111,9 +101,7 @@ 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:
@@ -151,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"
+15 -33
View File
@@ -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,16 +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="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()
@@ -53,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,
)
@@ -85,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()
@@ -118,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()
@@ -178,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,
)
@@ -195,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:
@@ -211,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")
+69 -68
View File
@@ -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,15 +21,17 @@ 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()
@@ -37,35 +39,17 @@ async def handle_admin_message(message: types.Message):
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(),
)
@@ -83,9 +67,7 @@ async def user_stats_menu(callback_query: CallbackQuery):
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"
)
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",
@@ -109,19 +91,12 @@ async def user_stats_menu(callback_query: CallbackQuery):
)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="🔄 Обновить", callback_data="user_stats")
)
builder.row(
InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin")
)
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())
@@ -134,52 +109,78 @@ 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):
try:
# Перезапуск службы бота
subprocess.run(
["sudo", "systemctl", "restart", "bot.service"],
check=True,
capture_output=True,
text=True,
)
await callback_query.message.answer("🔄 Бот успешно перезапущен.")
# Очистка состояния
await state.clear()
# Создание клавиатуры для возврата в меню администратора
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
# Уведомление о успешной перезагрузке
await callback_query.message.edit_text("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
except subprocess.CalledProcessError as e:
await callback_query.message.answer(
f"⚠️ Перезагрузка бота будет выполнена через 30 секунд. Детали: {e.stderr}"
# Обработка ошибок при перезагрузке
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
await callback_query.message.edit_text(
f"⚠️ Ошибка при перезагрузке бота: {e.stderr}",
reply_markup=builder.as_markup(),
)
await callback_query.answer()
@router.callback_query(F.data == "user_editor", IsAdminFilter())
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="🌐 Поиск по Username", callback_data="search_by_username"
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()
"👇 Выберите способ поиска пользователя:",
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)
+57 -93
View File
@@ -1,12 +1,12 @@
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
@@ -45,9 +45,7 @@ async def handle_username_input(message: types.Message, state: FSMContext):
username = message.text.strip()
conn = await asyncpg.connect(DATABASE_URL)
try:
user_record = await conn.fetchrow(
"SELECT tg_id FROM users WHERE username = $1", username
)
user_record = await conn.fetchrow("SELECT tg_id FROM users WHERE username = $1", username)
if not user_record:
await message.reply("🔍 Пользователь с указанным username не найден. 🚫")
@@ -55,13 +53,10 @@ async def handle_username_input(message: types.Message, state: FSMContext):
return
tg_id = user_record["tg_id"]
balance = await conn.fetchval(
"SELECT balance FROM connections WHERE tg_id = $1", 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 не найден.")
@@ -71,35 +66,33 @@ async def handle_username_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="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:
@@ -112,13 +105,10 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext):
conn = await asyncpg.connect(DATABASE_URL)
try:
balance = await conn.fetchval(
"SELECT balance FROM connections WHERE tg_id = $1", 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 не найден. 🔍")
@@ -128,35 +118,33 @@ 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="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:
@@ -170,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="admin"
)
)
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())
@@ -194,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)
@@ -206,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>."
@@ -219,7 +201,9 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext):
)
)
await message.reply(
response_message, reply_markup=builder.as_markup(), parse_mode="HTML"
response_message,
reply_markup=builder.as_markup(),
parse_mode="HTML",
)
finally:
@@ -252,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)
@@ -289,18 +271,14 @@ async def process_key_edit(callback_query: CallbackQuery):
callback_data=f"delete_key_admin|{email}",
),
)
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="admin")
)
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()
@@ -364,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"
@@ -383,7 +359,8 @@ 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}",
)
)
@@ -424,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:
@@ -436,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()
@@ -464,12 +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="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:
@@ -490,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(
@@ -505,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,
@@ -534,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)
@@ -566,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,
+5 -13
View File
@@ -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,18 +20,14 @@ 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"
@@ -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"))
await message.answer(
activation_result, reply_markup=builder.as_markup(), parse_mode="HTML"
)
await message.answer(activation_result, reply_markup=builder.as_markup(), parse_mode="HTML")
await state.clear()
+8 -23
View File
@@ -28,14 +28,11 @@ async def process_donate(callback_query: types.CallbackQuery, state: FSMContext)
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"))
@@ -52,14 +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
):
async def process_enter_donate_amount(callback_query: types.CallbackQuery, state: FSMContext):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="donate"))
await callback_query.message.edit_text(
f"💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup()
)
await callback_query.message.edit_text(f"💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup())
await state.set_state(DonateState.entering_donate_amount)
await callback_query.answer()
@@ -75,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)
@@ -122,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,
+9 -14
View File
@@ -1,9 +1,9 @@
import os
import asyncpg
from aiogram import F, Router, types
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
import asyncpg
from bot import bot
from config import CONNECT_WINDOWS, DATABASE_URL, SUPPORT_CHAT_URL
@@ -28,9 +28,7 @@ async def send_instructions(callback_query: types.CallbackQuery):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
builder.row(
InlineKeyboardButton(
text="⬅️ Вернуться в профиль", callback_data="view_profile"
),
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"),
)
with open(image_path, "rb") as image_from_buffer:
@@ -50,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}")
@@ -86,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],
@@ -102,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:
+35 -52
View File
@@ -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()
+6 -18
View File
@@ -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
+107 -141
View File
@@ -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(
@@ -485,9 +473,7 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
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()
@@ -510,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)
@@ -534,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)
@@ -549,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(
@@ -588,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:
@@ -606,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,
@@ -638,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():
@@ -655,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)
@@ -674,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()
+11 -31
View File
@@ -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",
+3 -7
View File
@@ -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()
+28 -54
View File
@@ -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(
"""
@@ -114,7 +111,8 @@ async def notify_10h_keys(
],
[
types.InlineKeyboardButton(
text="👤 Личный кабинет", callback_data="view_profile"
text="👤 Личный кабинет",
callback_data="view_profile",
)
],
]
@@ -122,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(
@@ -137,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("Проверка истекших ключей...")
@@ -192,7 +191,8 @@ async def notify_24h_keys(
],
[
types.InlineKeyboardButton(
text="👤 Личный кабинет", callback_data="view_profile"
text="👤 Личный кабинет",
callback_data="view_profile",
)
],
]
@@ -200,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)
@@ -249,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(
"""
@@ -282,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
View File
@@ -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"
+18 -41
View File
@@ -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}")
@@ -220,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()
@@ -233,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",
@@ -252,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"),
)
@@ -264,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}")
+13 -34
View File
@@ -4,13 +4,13 @@ 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
@@ -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}")
@@ -95,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()
@@ -122,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,
@@ -142,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:
@@ -155,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")],
]
)
@@ -186,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()
@@ -199,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,
+21 -47
View File
@@ -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("Неверная подпись или данные запроса.")
@@ -244,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}")
@@ -256,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,
@@ -269,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()
@@ -315,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="Введите корректную сумму в рублях (целое положительное число).")
+12 -33
View File
@@ -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(
+24 -37
View File
@@ -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,
@@ -234,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()
@@ -247,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(
@@ -277,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,
}
],
+11 -28
View File
@@ -14,9 +14,7 @@ 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,9 +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="view_tariffs"))
builder.row(
InlineKeyboardButton(text=" Устройство", callback_data="create_key"),
InlineKeyboardButton(text="📱 Мои устройства", callback_data="view_keys"),
@@ -52,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()
@@ -87,7 +77,8 @@ async def process_callback_view_profile(
except Exception as e:
await bot.send_message(
chat_id, f"❗️ Не удалось загрузить профиль. Техническая ошибка: {e}"
chat_id,
f"❗️ Не удалось загрузить профиль. Техническая ошибка: {e}",
)
@@ -98,9 +89,7 @@ async def view_tariffs_handler(callback_query: types.CallbackQuery):
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>🚀 Доступные тарифы VPN:</b>\n\n"
@@ -130,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()
@@ -144,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(),
@@ -170,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)
+28 -51
View File
@@ -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("Обнаружен реферальный код.")
@@ -72,38 +72,24 @@ async def start_command(message: Message, admin: bool = False):
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)
else:
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.error(
f"Ошибка в обработке команды /start для user_id {message.from_user.id}: {e}"
)
logger.error(f"Ошибка в обработке команды /start для user_id {message.from_user.id}: {e}")
await message.answer("Произошла ошибка. Пожалуйста, попробуйте позже.")
@@ -133,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"
@@ -144,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",
@@ -161,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()
@@ -175,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
View File
@@ -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 -1
View File
@@ -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
+27
View File
@@ -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