Merge pull request #98 from izzzzzi/main

fix ref/serach from username and new stats in admin
This commit is contained in:
Vladislav Lisitsyn
2024-11-23 08:18:10 +03:00
committed by GitHub
31 changed files with 746 additions and 987 deletions
+3 -3
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
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
+4 -1
View File
@@ -1,2 +1,5 @@
formatting:
@black . && isort . && flake8
@echo "Running black..." && black .
@echo "Running isort..." && isort .
@echo "Running flake8..." && flake8 --config .flake8
@echo "Running pylint..." && pylint .
+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}")
+48 -32
View File
@@ -9,6 +9,21 @@ from logger import logger
async def init_db():
conn = await asyncpg.connect(DATABASE_URL)
# Таблица для хранения информации о платежах
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS payments (
id SERIAL PRIMARY KEY,
tg_id BIGINT NOT NULL,
amount REAL NOT NULL,
payment_system TEXT NOT NULL,
status TEXT DEFAULT 'success',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tg_id) REFERENCES users(tg_id)
)
"""
)
# Таблица для хранения основной информации о пользователях из Telegram
await conn.execute(
"""
@@ -108,7 +123,7 @@ async def create_coupon(coupon_code: str, amount: float, usage_limit: int):
usage_limit,
)
except Exception as e:
print(f"Ошибка при создании купона: {e}")
logger.error(f"Ошибка при создании купона: {e}")
raise
finally:
await conn.close()
@@ -126,7 +141,7 @@ async def get_all_coupons():
)
return coupons
except Exception as e:
print(f"Ошибка при получении купонов: {e}")
logger.error(f"Ошибка при получении купонов: {e}")
return []
finally:
await conn.close()
@@ -157,7 +172,7 @@ async def delete_coupon_from_db(coupon_code: str):
return True
except Exception as e:
print(f"Ошибка при удалении купона: {e}")
logger.error(f"Ошибка при удалении купона: {e}")
return False
finally:
await conn.close()
@@ -201,7 +216,12 @@ async def check_connection_exists(tg_id: int):
async def store_key(
tg_id: int, client_id: str, email: str, expiry_time: int, key: str, server_id: str
tg_id: int,
client_id: str,
email: str,
expiry_time: int,
key: str,
server_id: str,
):
conn = await asyncpg.connect(DATABASE_URL)
await conn.execute(
@@ -258,9 +278,7 @@ async def has_active_key(tg_id: int) -> bool:
async def get_balance(tg_id: int) -> float:
conn = await asyncpg.connect(DATABASE_URL)
balance = await conn.fetchval(
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
)
balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
await conn.close()
return balance if balance is not None else 0.0
@@ -363,7 +381,10 @@ async def get_referral_stats(referrer_tg_id: int):
await conn.close()
return {"total_referrals": total_referrals, "active_referrals": active_referrals}
return {
"total_referrals": total_referrals,
"active_referrals": active_referrals,
}
async def update_key_expiry(client_id: str, new_expiry_time: int):
@@ -430,9 +451,7 @@ async def get_client_id_by_email(email: str):
async def get_tg_id_by_client_id(client_id: str):
conn = await asyncpg.connect(DATABASE_URL)
try:
result = await conn.fetchrow(
"SELECT tg_id FROM keys WHERE client_id = $1", client_id
)
result = await conn.fetchrow("SELECT tg_id FROM keys WHERE client_id = $1", client_id)
return result["tg_id"] if result else None
finally:
await conn.close()
@@ -446,17 +465,6 @@ async def upsert_user(
language_code: str = None,
is_bot: bool = False,
):
"""
Создает или обновляет информацию о пользователе в базе данных.
Args:
tg_id (int): Уникальный идентификатор пользователя в Telegram
username (str, optional): Никнейм пользователя
first_name (str, optional): Имя пользователя
last_name (str, optional): Фамилия пользователя
language_code (str, optional): Код языка пользователя
is_bot (bool, optional): Флаг, указывающий является ли пользователь ботом
"""
conn = await asyncpg.connect(DATABASE_URL)
try:
await conn.execute(
@@ -479,15 +487,23 @@ async def upsert_user(
language_code,
is_bot,
)
# Создаем запись в connections, если ее еще нет
await conn.execute(
"""
INSERT INTO connections (tg_id, balance, trial)
VALUES ($1, 0.0, 0)
ON CONFLICT (tg_id) DO NOTHING
""",
tg_id,
)
finally:
await conn.close()
async def add_payment(tg_id: int, amount: float, payment_system: str):
conn = await asyncpg.connect(DATABASE_URL)
try:
await conn.execute(
"""
INSERT INTO payments (tg_id, amount, payment_system, status)
VALUES ($1, $2, $3, 'success')
""",
tg_id,
amount,
payment_system,
)
except Exception as e:
logger.error(f"Ошибка при добавлении платежа: {e}")
finally:
await conn.close()
+19 -23
View File
@@ -1,8 +1,10 @@
import asyncpg
from aiogram import Router, types
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
import asyncpg
from bot import bot
from config import DATABASE_URL
@@ -32,9 +34,7 @@ async def cmd_add_balance(message: types.Message):
return
await add_balance_to_client(int(client_id), amount)
await message.reply(
f"✅ Баланс клиента {client_id} успешно пополнен на {amount}"
)
await message.reply(f"✅ Баланс клиента {client_id} успешно пополнен на {amount}")
except ValueError:
await message.reply(
"❓ Неверный формат команды!\n"
@@ -51,9 +51,7 @@ async def backup_command(message: types.Message):
await message.answer("🔄 Инициализация резервного копирования базы данных...")
await backup_database()
await message.answer(
"✅ Бэкап базы данных успешно завершен и отправлен администратору."
)
await message.answer("✅ Бэкап базы данных успешно завершен и отправлен администратору.")
@router.message(Command("send_trial"), IsAdminFilter())
@@ -81,14 +79,10 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
except Exception as e:
if "Forbidden: bot was blocked by the user" in str(e):
blocked_count += 1
logger.info(
f"🚫 Бот заблокирован пользователем с tg_id: {tg_id}"
)
logger.info(f"🚫 Бот заблокирован пользователем с tg_id: {tg_id}")
else:
error_count += 1
logger.error(
f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}"
)
logger.error(f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}")
await message.answer(
f"📊 Результаты рассылки пробных периодов:\n"
@@ -97,9 +91,7 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
f"❌ Ошибок: {error_count}"
)
else:
await message.answer(
"📭 Нет пользователей с неиспользованными пробными ключами."
)
await message.answer("📭 Нет пользователей с неиспользованными пробными ключами.")
finally:
await conn.close()
@@ -109,12 +101,18 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
@router.message(Command("send_to_all"), IsAdminFilter())
async def send_message_to_all_clients(
message: types.Message, state: FSMContext, from_panel=False
):
async def send_message_to_all_clients(message: types.Message, state: FSMContext, from_panel=False):
try:
await message.delete()
except Exception:
pass
if from_panel:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
await message.answer(
"✍️ Введите текст сообщения, который вы хотите отправить всем клиентам:"
"✍️ Введите текст сообщения, который вы хотите отправить всем клиентам:",
reply_markup=builder.as_markup(),
)
await state.set_state(Form.waiting_for_message)
@@ -141,9 +139,7 @@ async def process_message_to_all(
success_count += 1
except Exception as e:
error_count += 1
logger.error(
f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}"
)
logger.error(f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}")
await message.answer(
f"📤 Рассылка завершена:\n"
+16 -36
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,18 +26,12 @@ async def show_coupon_management_menu(
await state.clear()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text=" Создать купон", callback_data="create_coupon")
)
builder.row(InlineKeyboardButton(text=" Создать купон", callback_data="create_coupon"))
builder.row(InlineKeyboardButton(text="Купоны", callback_data="coupons"))
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_admin_menu")
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
markup = builder.as_markup()
await callback_query.message.answer(
"🛠 Меню управления купонами:", reply_markup=markup
)
await callback_query.message.answer("🛠 Меню управления купонами:", reply_markup=markup)
await callback_query.answer()
@@ -55,14 +47,11 @@ async def show_coupon_list(callback_query: types.CallbackQuery):
if not coupons:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
markup = builder.as_markup()
await callback_query.message.answer(
"❌ На данный момент нет доступных купонов.\n"
"Вы можете вернуться в меню управления.",
"❌ На данный момент нет доступных купонов.\n" "Вы можете вернуться в меню управления.",
parse_mode="HTML",
reply_markup=markup,
)
@@ -87,19 +76,16 @@ async def show_coupon_list(callback_query: types.CallbackQuery):
)
)
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
markup = builder.as_markup()
await callback_query.message.answer(
coupon_list, parse_mode="HTML", reply_markup=markup
)
await callback_query.message.answer(coupon_list, parse_mode="HTML", reply_markup=markup)
except Exception as e:
logger.error(f"Ошибка при получении списка купонов: {e}")
await callback_query.message.answer(
f"❌ Произошла ошибка при получении списка купонов: {e}", parse_mode="HTML"
f"❌ Произошла ошибка при получении списка купонов: {e}",
parse_mode="HTML",
)
await callback_query.answer()
@@ -120,15 +106,14 @@ async def handle_delete_coupon(callback_query: types.CallbackQuery):
await show_coupon_list(callback_query)
else:
await callback_query.message.answer(
f"❌ Купон с кодом <b>{coupon_code}</b> не найден.", parse_mode="HTML"
f"❌ Купон с кодом <b>{coupon_code}</b> не найден.",
parse_mode="HTML",
)
await show_coupon_list(callback_query)
except Exception as e:
logger.error(f"Ошибка при удалении купона: {e}")
await callback_query.message.answer(
f"❌ Произошла ошибка при удалении купона: {e}", parse_mode="HTML"
)
await callback_query.message.answer(f"❌ Произошла ошибка при удалении купона: {e}", parse_mode="HTML")
await callback_query.answer()
@@ -180,8 +165,7 @@ async def process_coupon_data(message: types.Message, state: FSMContext):
usage_limit = int(parts[2])
except ValueError:
await message.answer(
"<b>⚠️ Проверьте правильность введенных данных.</b>\n"
"Сумма должна быть числом, а лимит — целым числом.",
"<b>⚠️ Проверьте правильность введенных данных.</b>\n" "Сумма должна быть числом, а лимит — целым числом.",
parse_mode="HTML",
reply_markup=markup,
)
@@ -197,9 +181,7 @@ async def process_coupon_data(message: types.Message, state: FSMContext):
)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
markup = builder.as_markup()
try:
@@ -213,9 +195,7 @@ async def process_coupon_data(message: types.Message, state: FSMContext):
except Exception as e:
logger.error(f"Ошибка при создании купона: {e}")
await message.answer(
f"<b>❌ Ошибка при создании купона:</b> {e}", parse_mode="HTML"
)
await message.answer(f"<b>❌ Ошибка при создании купона:</b> {e}", parse_mode="HTML")
@router.callback_query(F.data == "back_to_coupons_menu")
+88 -110
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,45 +21,35 @@ router = Router()
class UserEditorState(StatesGroup):
waiting_for_tg_id = State()
displaying_user_info = State()
waiting_for_restart_confirmation = State()
@router.callback_query(F.data == "admin", IsAdminFilter())
async def handle_admin_callback_query(callback_query: CallbackQuery):
await handle_admin_message(callback_query.message)
async def handle_admin_callback_query(callback_query: CallbackQuery, state: FSMContext):
await handle_admin_message(callback_query.message, state)
@router.message(Command("admin"), F.data == "admin", IsAdminFilter())
async def handle_admin_message(message: types.Message):
async def handle_admin_message(message: types.Message, state: FSMContext):
await state.clear()
try:
await message.delete()
except Exception:
pass
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="📊 Статистика пользователей", callback_data="user_stats"
)
)
builder.row(
InlineKeyboardButton(
text="👥 Управление пользователями", callback_data="user_editor"
)
)
builder.row(
InlineKeyboardButton(
text="🎟️ Управление купонами", callback_data="coupons_editor"
)
)
builder.row(
InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to_alls")
)
builder.row(
InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups")
)
builder.row(
InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot")
)
builder.row(
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile")
)
builder.row(InlineKeyboardButton(text="📊 Статистика пользователей", callback_data="user_stats"))
builder.row(InlineKeyboardButton(text="👥 Управление пользователями", callback_data="user_editor"))
builder.row(InlineKeyboardButton(text="🎟️ Управление купонами", callback_data="coupons_editor"))
builder.row(InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to_alls"))
builder.row(InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups"))
builder.row(InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot"))
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
await bot.send_message(
message.chat.id, "🤖 Панель администратора", reply_markup=builder.as_markup()
message.chat.id,
"🤖 Панель администратора",
reply_markup=builder.as_markup(),
)
@@ -71,6 +61,14 @@ async def user_stats_menu(callback_query: CallbackQuery):
total_keys = await conn.fetchval("SELECT COUNT(*) FROM keys")
total_referrals = await conn.fetchval("SELECT COUNT(*) FROM referrals")
total_payments_today = await conn.fetchval(
"SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= CURRENT_DATE"
)
total_payments_week = await conn.fetchval(
"SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= date_trunc('week', CURRENT_DATE)"
)
total_payments_all_time = await conn.fetchval("SELECT COALESCE(SUM(amount), 0) FROM payments")
active_keys = await conn.fetchval(
"SELECT COUNT(*) FROM keys WHERE expiry_time > $1",
int(datetime.utcnow().timestamp() * 1000),
@@ -78,27 +76,27 @@ async def user_stats_menu(callback_query: CallbackQuery):
expired_keys = total_keys - active_keys
stats_message = (
f"📈 <b>Подробная статистика проекта:</b>\n\n"
f"👤 Зарегистрированных пользователей: <b>{total_users}</b>\n"
f"🔑 Всего сгенерированных ключей: <b>{total_keys}</b>\n"
f"🤝 Привлеченных рефералов: <b>{total_referrals}</b>\n"
f"✅ Действующих ключей: <b>{active_keys}</b>\n"
f"❌ Просроченных ключей: <b>{expired_keys}</b>"
f"📊 <b>Подробная статистика проекта:</b>\n\n"
f"👥 Пользователи:\n"
f" 🌐 Зарегистрировано: <b>{total_users}</b>\n"
f" 🤝 Привлеченных рефералов: <b>{total_referrals}</b>\n\n"
f"🔑 Ключи:\n"
f" 🌈 Всего сгенерировано: <b>{total_keys}</b>\n"
f" ✅ Действующих: <b>{active_keys}</b>\n"
f" ❌ Просроченных: <b>{expired_keys}</b>\n\n"
f"💰 Финансовая статистика:\n"
f" 📅 За день: <b>{total_payments_today} ₽</b>\n"
f" 📆 За неделю: <b>{total_payments_week} ₽</b>\n"
f" 🏦 За все время: <b>{total_payments_all_time} ₽</b>\n"
)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="🔙 Вернуться в меню", callback_data="back_to_admin_menu"
)
)
builder.row(InlineKeyboardButton(text="🔄 Обновить", callback_data="user_stats"))
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
await callback_query.message.edit_text(
stats_message, reply_markup=builder.as_markup(), parse_mode="HTML"
)
await callback_query.message.edit_text(stats_message, reply_markup=builder.as_markup(), parse_mode="HTML")
finally:
await conn.close()
await callback_query.answer()
@router.callback_query(F.data == "send_to_alls", IsAdminFilter())
@@ -111,25 +109,50 @@ async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext):
async def handle_backup(message: Message):
await message.answer("💾 Инициализация резервного копирования базы данных...")
await backup_database()
await message.answer(
"✅ Резервная копия успешно создана и отправлена администратору."
)
await message.answer("✅ Резервная копия успешно создана и отправлена администратору.")
@router.callback_query(F.data == "restart_bot", IsAdminFilter())
async def handle_restart(callback_query: CallbackQuery):
async def handle_restart(callback_query: CallbackQuery, state: FSMContext):
await state.set_state(UserEditorState.waiting_for_restart_confirmation)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="✅ Да, перезапустить", callback_data="confirm_restart"),
InlineKeyboardButton(text="❌ Нет, отмена", callback_data="admin"),
)
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
await callback_query.message.edit_text(
"🤔 Вы уверены, что хотите перезапустить бота?",
reply_markup=builder.as_markup(),
)
@router.callback_query(
F.data == "confirm_restart",
UserEditorState.waiting_for_restart_confirmation,
IsAdminFilter(),
)
async def confirm_restart_bot(callback_query: CallbackQuery, state: FSMContext):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
try:
subprocess.run(
["sudo", "systemctl", "restart", "bot.service"],
["systemctl", "restart", "bot.service"],
check=True,
capture_output=True,
text=True,
)
await callback_query.message.answer("🔄 Бот успешно перезапущен.")
except subprocess.CalledProcessError as e:
await callback_query.message.answer(
f"⚠️ Перезагрузка бота будет выполнена через 30 секунд. Детали: {e.stderr}"
await state.clear()
await callback_query.message.edit_text("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
except subprocess.CalledProcessError:
await callback_query.message.edit_text("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
except Exception as e:
await callback_query.message.edit_text(
f"⚠️ Ошибка при перезагрузке бота: {e.stderr}",
reply_markup=builder.as_markup(),
)
finally:
await callback_query.answer()
@router.callback_query(F.data == "user_editor", IsAdminFilter())
@@ -137,63 +160,18 @@ async def user_editor_menu(callback_query: CallbackQuery):
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="🔍 Поиск по названию ключа", callback_data="search_by_key_name"
)
)
builder.row(
InlineKeyboardButton(
text="🆔 Поиск по Telegram ID", callback_data="search_by_tg_id"
)
)
builder.row(
InlineKeyboardButton(
text="🔙 Вернуться назад", callback_data="back_to_admin_menu"
text="🔍 Поиск по названию ключа",
callback_data="search_by_key_name",
)
)
builder.row(InlineKeyboardButton(text="🆔 Поиск по Telegram ID", callback_data="search_by_tg_id"))
builder.row(InlineKeyboardButton(text="🌐 Поиск по Username", callback_data="search_by_username"))
builder.row(InlineKeyboardButton(text="🔙 Вернуться назад", callback_data="admin"))
await callback_query.message.edit_text(
"👇 Выберите способ поиска пользователя:", reply_markup=builder.as_markup()
)
@router.callback_query(F.data == "back_to_admin_menu", IsAdminFilter())
async def back_to_admin_menu(callback_query: CallbackQuery):
try:
await callback_query.message.delete()
except Exception:
pass
tg_id = callback_query.from_user.id
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="📊 Статистика пользователей", callback_data="user_stats"
)
)
builder.row(
InlineKeyboardButton(
text="👥 Управление пользователями", callback_data="user_editor"
)
)
builder.row(
InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to_alls")
)
builder.row(
InlineKeyboardButton(
text="🎟️ Управление купонами", callback_data="coupons_editor"
)
)
builder.row(
InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups")
)
builder.row(
InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot")
)
await bot.send_message(
tg_id, "🤖 Панель администратора", reply_markup=builder.as_markup()
"👇 Выберите способ поиска пользователя:",
reply_markup=builder.as_markup(),
)
async def handle_error(tg_id, callback_query, message):
await bot.edit_message_text(
message, chat_id=tg_id, message_id=callback_query.message.message_id
)
await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id)
+117 -96
View File
@@ -1,18 +1,17 @@
import asyncio
from datetime import datetime
import asyncpg
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
import asyncpg
from bot import bot
from config import CLUSTERS, DATABASE_URL, TOTAL_GB
from database import get_client_id_by_email, restore_trial, update_key_expiry
from filters.admin import IsAdminFilter
from handlers.admin.admin_panel import back_to_admin_menu
from handlers.keys.key_utils import delete_key_from_cluster, renew_key_in_cluster
from handlers.utils import sanitize_key_name
from logger import logger
@@ -22,6 +21,7 @@ router = Router()
class UserEditorState(StatesGroup):
waiting_for_tg_id = State()
waiting_for_username = State()
displaying_user_info = State()
waiting_for_new_balance = State()
waiting_for_key_name = State()
@@ -34,19 +34,29 @@ async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext):
await state.set_state(UserEditorState.waiting_for_tg_id)
@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(), IsAdminFilter())
async def handle_tg_id_input(message: types.Message, state: FSMContext):
tg_id = int(message.text)
@router.callback_query(F.data == "search_by_username", IsAdminFilter())
async def prompt_username(callback_query: CallbackQuery, state: FSMContext):
await callback_query.message.edit_text("🔍 Введите Username клиента:")
await state.set_state(UserEditorState.waiting_for_username)
@router.message(UserEditorState.waiting_for_username, IsAdminFilter())
async def handle_username_input(message: types.Message, state: FSMContext):
username = message.text.strip()
conn = await asyncpg.connect(DATABASE_URL)
try:
balance = await conn.fetchval(
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
)
user_record = await conn.fetchrow("SELECT tg_id FROM users WHERE username = $1", username)
if not user_record:
await message.reply("🔍 Пользователь с указанным username не найден. 🚫")
await state.clear()
return
tg_id = user_record["tg_id"]
username = await conn.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id)
balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
key_records = await conn.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
referral_count = await conn.fetchval(
"SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id
)
referral_count = await conn.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
if balance is None:
await message.reply("Пользователь с указанным tg_id не найден.")
@@ -56,37 +66,85 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext):
builder = InlineKeyboardBuilder()
for (email,) in key_records:
builder.row(
InlineKeyboardButton(
text=f"🔑 {email}", callback_data=f"edit_key_{email}"
)
)
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
builder.row(
InlineKeyboardButton(
text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}"
text="📝 Изменить баланс",
callback_data=f"change_balance_{tg_id}",
)
)
builder.row(
InlineKeyboardButton(
text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}"
text="🔄 Восстановить пробник",
callback_data=f"restore_trial_{tg_id}",
)
)
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_user_editor")
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
user_info = (
f"📊 Информация о пользователе:\n"
f"📊 Информация о пользователе:\n\n"
f"🆔 ID пользователя: <b>{tg_id}</b>\n"
f"👤 Логин пользователя: <b>@{username}</b>\n"
f"💰 Баланс: <b>{balance}</b>\n"
f"👥 Количество рефералов: <b>{referral_count}</b>\n"
f"🔑 Ключи (для редактирования нажмите на ключ):"
)
await message.reply(
user_info, reply_markup=builder.as_markup(), parse_mode="HTML"
await message.reply(user_info, reply_markup=builder.as_markup(), parse_mode="HTML")
await state.set_state(UserEditorState.displaying_user_info)
finally:
await conn.close()
@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(), IsAdminFilter())
async def handle_tg_id_input(message: types.Message, state: FSMContext):
tg_id = int(message.text)
conn = await asyncpg.connect(DATABASE_URL)
try:
username = await conn.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id)
balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
key_records = await conn.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
referral_count = await conn.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
if balance is None:
await message.reply("❌ Пользователь с указанным tg_id не найден. 🔍")
await state.clear()
return
builder = InlineKeyboardBuilder()
for (email,) in key_records:
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
builder.row(
InlineKeyboardButton(
text="📝 Изменить баланс",
callback_data=f"change_balance_{tg_id}",
)
)
builder.row(
InlineKeyboardButton(
text="🔄 Восстановить пробник",
callback_data=f"restore_trial_{tg_id}",
)
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
user_info = (
f"📊 Информация о пользователе:\n\n"
f"🆔 ID пользователя: <b>{tg_id}</b>\n"
f"👤 Логин пользователя: <b>@{username}</b>\n"
f"💰 Баланс: <b>{balance}</b>\n"
f"👥 Количество рефералов: <b>{referral_count}</b>\n"
f"🔑 Ключи (для редактирования нажмите на ключ):"
)
await message.reply(user_info, reply_markup=builder.as_markup(), parse_mode="HTML")
await state.set_state(UserEditorState.displaying_user_info)
finally:
@@ -100,15 +158,9 @@ async def handle_restore_trial(callback_query: types.CallbackQuery):
await restore_trial(tg_id)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="🔙 Назад в меню администратора", callback_data="back_to_user_editor"
)
)
builder.row(InlineKeyboardButton(text="🔙 Назад в меню администратора", callback_data="admin"))
await callback_query.message.edit_text(
"✅ Триал успешно восстановлен.", reply_markup=builder.as_markup()
)
await callback_query.message.edit_text("✅ Триал успешно восстановлен.", reply_markup=builder.as_markup())
@router.callback_query(F.data.startswith("change_balance_"), IsAdminFilter())
@@ -124,9 +176,7 @@ async def process_balance_change(callback_query: CallbackQuery, state: FSMContex
@router.message(UserEditorState.waiting_for_new_balance, IsAdminFilter())
async def handle_new_balance_input(message: types.Message, state: FSMContext):
if not message.text.isdigit() or int(message.text) < 0:
await message.reply(
"❌ Пожалуйста, введите корректную сумму для изменения баланса."
)
await message.reply("❌ Пожалуйста, введите корректную сумму для изменения баланса.")
return
new_balance = int(message.text)
@@ -136,7 +186,9 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext):
conn = await asyncpg.connect(DATABASE_URL)
try:
await conn.execute(
"UPDATE connections SET balance = $1 WHERE tg_id = $2", new_balance, tg_id
"UPDATE connections SET balance = $1 WHERE tg_id = $2",
new_balance,
tg_id,
)
response_message = f"✅ Баланс успешно изменен на <b>{new_balance}</b>."
@@ -145,11 +197,13 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext):
builder.row(
InlineKeyboardButton(
text="🔙 Назад в меню администратора",
callback_data="back_to_user_editor",
callback_data="admin",
)
)
await message.reply(
response_message, reply_markup=builder.as_markup(), parse_mode="HTML"
response_message,
reply_markup=builder.as_markup(),
parse_mode="HTML",
)
finally:
@@ -182,9 +236,7 @@ async def process_key_edit(callback_query: CallbackQuery):
for cluster in CLUSTERS.values():
if server_id in cluster:
server_name = cluster[server_id].get(
"name", "Неизвестный сервер"
)
server_name = cluster[server_id].get("name", "Неизвестный сервер")
break
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
@@ -219,20 +271,14 @@ async def process_key_edit(callback_query: CallbackQuery):
callback_data=f"delete_key_admin|{email}",
),
)
builder.row(
InlineKeyboardButton(
text="🔙 Назад", callback_data="back_to_user_editor"
)
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
await callback_query.message.edit_text(
response_message,
reply_markup=builder.as_markup(),
parse_mode="HTML",
)
else:
await callback_query.message.edit_text(
"<b>Информация о ключе не найдена.</b>", parse_mode="HTML"
)
await callback_query.message.edit_text("<b>Информация о ключе не найдена.</b>", parse_mode="HTML")
finally:
await conn.close()
@@ -269,7 +315,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
builder.row(
InlineKeyboardButton(
text="🔙 Назад в меню администратора",
callback_data="back_to_user_editor",
callback_data="admin",
)
)
@@ -296,9 +342,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
server_name = cluster[server_id].get("name", "Неизвестный сервер")
break
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime(
"%d %B %Y"
)
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%d %B %Y")
response_messages.append(
f"🔑 Ключ: <pre>{key}</pre>\n"
@@ -315,13 +359,12 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
)
key_buttons.row(
InlineKeyboardButton(
text="❌ Удалить ключ", callback_data=f"delete_key_admin|{email}"
text="❌ Удалить ключ",
callback_data=f"delete_key_admin|{email}",
)
)
key_buttons.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_user_editor")
)
key_buttons.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
await message.reply(
"\n".join(response_messages),
@@ -358,9 +401,7 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
try:
expiry_time_str = message.text
expiry_time = int(
datetime.strptime(expiry_time_str, "%Y-%m-%d %H:%M:%S").timestamp() * 1000
)
expiry_time = int(datetime.strptime(expiry_time_str, "%Y-%m-%d %H:%M:%S").timestamp() * 1000)
client_id = await get_client_id_by_email(email)
if client_id is None:
@@ -370,9 +411,7 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
conn = await asyncpg.connect(DATABASE_URL)
try:
record = await conn.fetchrow(
"SELECT server_id FROM keys WHERE client_id = $1", client_id
)
record = await conn.fetchrow("SELECT server_id FROM keys WHERE client_id = $1", client_id)
if not record:
await message.reply("Клиент не найден в базе данных.")
await state.clear()
@@ -398,16 +437,16 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
await update_key_expiry(client_id, expiry_time)
response_message = f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
response_message = (
f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="🔙 Назад", callback_data="back_to_user_editor"
)
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
await message.reply(
response_message, reply_markup=builder.as_markup(), parse_mode="HTML"
response_message,
reply_markup=builder.as_markup(),
parse_mode="HTML",
)
finally:
@@ -428,9 +467,7 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
conn = await asyncpg.connect(DATABASE_URL)
try:
client_id = await conn.fetchval(
"SELECT client_id FROM keys WHERE email = $1", email
)
client_id = await conn.fetchval("SELECT client_id FROM keys WHERE email = $1", email)
if client_id is None:
await bot.edit_message_text(
@@ -443,14 +480,11 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
builder = InlineKeyboardBuilder()
builder.row(
types.InlineKeyboardButton(
text="✅ Да, удалить", callback_data=f"confirm_delete_admin|{client_id}"
)
)
builder.row(
types.InlineKeyboardButton(
text="❌ Нет, отменить", callback_data="view_keys"
text="✅ Да, удалить",
callback_data=f"confirm_delete_admin|{client_id}",
)
)
builder.row(types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="view_keys"))
await bot.edit_message_text(
"<b>❓ Вы уверены, что хотите удалить ключ?</b>",
chat_id=tg_id,
@@ -472,24 +506,18 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
try:
conn = await asyncpg.connect(DATABASE_URL)
try:
record = await conn.fetchrow(
"SELECT email FROM keys WHERE client_id = $1", client_id
)
record = await conn.fetchrow("SELECT email FROM keys WHERE client_id = $1", client_id)
if record:
email = record["email"]
response_message = "✅ Ключ успешно удален."
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys")
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
async def delete_key_from_servers(email, client_id):
tasks = []
for cluster_id in CLUSTERS:
tasks.append(
delete_key_from_cluster(cluster_id, email, client_id)
)
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
await asyncio.gather(*tasks)
await delete_key_from_servers(email, client_id)
@@ -504,9 +532,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
else:
response_message = "🚫 Ключ не найден или уже удален."
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys")
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
await bot.edit_message_text(
response_message,
chat_id=tg_id,
@@ -536,8 +562,3 @@ async def delete_key_from_db(client_id):
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
finally:
await conn.close()
@router.callback_query(F.data == "back_to_user_editor")
async def back_to_user_editor(callback_query: CallbackQuery):
await back_to_admin_menu(callback_query)
+11 -21
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,22 +20,18 @@ router = Router()
@router.callback_query(F.data == "activate_coupon")
async def handle_activate_coupon(
callback_query: types.CallbackQuery, state: FSMContext
):
async def handle_activate_coupon(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile")
)
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
await callback_query.message.answer(
"<b>Введите код купона:</b>\n\n"
"Пожалуйста, введите действующий код купона, который вы хотите активировать.",
"<b>🎫 Введите код купона:</b>\n\n"
"📝 Пожалуйста, введите действующий код купона, который вы хотите активировать. 🔑",
parse_mode="HTML",
reply_markup=builder.as_markup(),
)
@@ -54,13 +50,9 @@ async def process_coupon_code(message: types.Message, state: FSMContext):
activation_result = await activate_coupon(message.from_user.id, coupon_code)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile"))
markup = builder.as_markup()
await message.answer(activation_result, reply_markup=markup, parse_mode="HTML")
await message.answer(activation_result, reply_markup=builder.as_markup(), parse_mode="HTML")
await state.clear()
@@ -79,7 +71,7 @@ async def activate_coupon(user_id: int, coupon_code: str):
)
if not coupon_record:
return "<b>❌ Купон не найден</b> или его использование ограничено. Пожалуйста, проверьте код и попробуйте снова."
return "<b>❌ Купон не найден</b> 🚫 или его использование ограничено. 🔒 Пожалуйста, проверьте код и попробуйте снова. 🔍"
usage_exists = await conn.fetchrow(
"""
@@ -90,7 +82,7 @@ async def activate_coupon(user_id: int, coupon_code: str):
)
if usage_exists:
return "<b>❌ Вы уже активировали этот купон.</b> Купоны могут быть активированы только один раз."
return "<b>❌ Вы уже активировали этот купон.</b> 🚫 Купоны могут быть активированы только один раз. 🔒"
coupon_amount = coupon_record["amount"]
@@ -116,13 +108,11 @@ async def activate_coupon(user_id: int, coupon_code: str):
)
await update_balance(user_id, coupon_amount)
return f"<b>✅ Купон успешно активирован!</b>\n\nНа ваш баланс добавлено <b>{coupon_amount} рублей</b>."
return f"<b>✅ Купон успешно активирован! 🎉</b>\n\nНа ваш баланс добавлено <b>{coupon_amount} рублей</b> 💰."
except Exception as e:
logger.error(f"Ошибка при активации купона: {e}")
return (
"<b>⚠️ Произошла ошибка при активации купона.</b>\nПопробуйте ещё раз позже."
)
return "<b>⚠️ Произошла ошибка при активации купона! 🔧</b>\nПопробуйте ещё раз позже. 🕒"
finally:
await conn.close()
+14 -24
View File
@@ -22,28 +22,26 @@ router = Router()
async def process_donate(callback_query: types.CallbackQuery, state: FSMContext):
try:
await state.clear()
await callback_query.message.delete()
except Exception as e:
logger.error(f"Не удалось удалить сообщение: {e}")
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"))
builder.row(
InlineKeyboardButton(
text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"
)
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести сумму доната", callback_data="enter_custom_donate_amount"
text="💰 Ввести сумму доната",
callback_data="enter_custom_donate_amount",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_profile"))
await bot.send_message(
chat_id=callback_query.from_user.id,
text="🌟 Поддержите наш проект!\n\n"
"Каждый донат помогает развивать и улучшать сервис. "
"Мы ценим вашу поддержку и работаем над тем, чтобы сделать наш продукт еще лучше. 💡",
text="🌟 Поддержите наш проект! 💪\n\n"
"💖 Каждый донат помогает развивать и улучшать сервис. "
"🤝 Мы ценим вашу поддержку и работаем над тем, чтобы сделать наш продукт еще лучше. 🚀💡",
reply_markup=builder.as_markup(),
)
@@ -51,10 +49,10 @@ async def process_donate(callback_query: types.CallbackQuery, state: FSMContext)
@router.callback_query(F.data == "enter_custom_donate_amount")
async def process_enter_donate_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
await callback_query.message.edit_text(f"💸 Введите сумму доната в рублях:")
async def process_enter_donate_amount(callback_query: types.CallbackQuery, state: FSMContext):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="donate"))
await callback_query.message.edit_text(f"💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup())
await state.set_state(DonateState.entering_donate_amount)
await callback_query.answer()
@@ -70,9 +68,7 @@ async def process_donate_amount_input(message: types.Message, state: FSMContext)
if message.text.isdigit():
amount = int(message.text)
if amount // RUB_TO_XTR <= 0:
await message.answer(
f"Сумма доната должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:"
)
await message.answer(f"Сумма доната должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:")
return
await state.update_data(amount=amount)
@@ -117,18 +113,12 @@ async def on_successful_donate(message: types.Message, state: FSMContext):
if previous_message_id:
try:
await bot.delete_message(
chat_id=user_id, message_id=previous_message_id
)
await bot.delete_message(chat_id=user_id, message_id=previous_message_id)
except Exception as e:
logger.error(f"Не удалось удалить сообщение: {e}")
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="Вернуться в профиль", callback_data="view_profile"
)
)
builder.row(InlineKeyboardButton(text="Вернуться в профиль", callback_data="view_profile"))
sent_message = await bot.send_message(
chat_id=user_id,
+15 -16
View File
@@ -1,8 +1,9 @@
import os
import asyncpg
from aiogram import F, Router, types
from aiogram.types import BufferedInputFile, InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
import asyncpg
from bot import bot
from config import CONNECT_WINDOWS, DATABASE_URL, SUPPORT_CHAT_URL
@@ -24,17 +25,18 @@ async def send_instructions(callback_query: types.CallbackQuery):
await callback_query.answer()
return
back_button = InlineKeyboardButton(
text="⬅️ Вернуться в профиль", callback_data="view_profile"
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
builder.row(
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"),
)
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
with open(image_path, "rb") as image_from_buffer:
await callback_query.message.answer_photo(
BufferedInputFile(image_from_buffer.read(), filename="instructions.jpg"),
caption=instructions_message,
parse_mode="Markdown",
reply_markup=keyboard,
reply_markup=builder.as_markup(),
)
await callback_query.answer()
@@ -46,9 +48,7 @@ async def process_connect_pc(callback_query: types.CallbackQuery):
key_name = callback_query.data.split("|")[1]
try:
await bot.delete_message(
chat_id=tg_id, message_id=callback_query.message.message_id
)
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
@@ -82,13 +82,9 @@ async def process_connect_pc(callback_query: types.CallbackQuery):
text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}"
)
support_button = types.InlineKeyboardButton(
text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}"
)
support_button = types.InlineKeyboardButton(text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}")
back_button = types.InlineKeyboardButton(
text="🔙 Назад в профиль", callback_data="view_profile"
)
back_button = types.InlineKeyboardButton(text="🔙 Назад в профиль", callback_data="view_profile")
inline_keyboard = [
[connect_windows_button],
@@ -98,7 +94,10 @@ async def process_connect_pc(callback_query: types.CallbackQuery):
keyboard = types.InlineKeyboardMarkup(inline_keyboard=inline_keyboard)
await bot.send_message(
tg_id, instruction_message, reply_markup=keyboard, parse_mode="HTML"
tg_id,
instruction_message,
reply_markup=keyboard,
parse_mode="HTML",
)
finally:
+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
+108 -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(
@@ -483,10 +471,9 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
parse_mode="HTML",
)
else:
# Если ключ не найден
response_message = "<b>Ключ не найден.</b>"
await bot.send_message(
chat_id=tg_id, text=response_message, parse_mode="HTML"
)
await bot.send_message(chat_id=tg_id, text=response_message, parse_mode="HTML")
finally:
await conn.close()
@@ -509,16 +496,12 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
try:
conn = await asyncpg.connect(DATABASE_URL)
try:
record = await conn.fetchrow(
"SELECT client_id FROM keys WHERE email = $1", email
)
record = await conn.fetchrow("SELECT client_id FROM keys WHERE email = $1", email)
if record:
client_id = record["client_id"]
response_message = "Ключ успешно удален."
back_button = types.InlineKeyboardButton(
text="Назад", callback_data="view_keys"
)
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
await delete_key(client_id)
@@ -533,9 +516,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
try:
tasks = []
for cluster_id, cluster in CLUSTERS.items():
tasks.append(
delete_key_from_cluster(cluster_id, email, client_id)
)
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
await asyncio.gather(*tasks)
@@ -548,9 +529,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
else:
response_message = "Ключ не найден или уже удален."
back_button = types.InlineKeyboardButton(
text="Назад", callback_data="view_keys"
)
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
await bot.edit_message_text(
@@ -587,16 +566,15 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
try:
try:
await bot.delete_message(
chat_id=tg_id, message_id=callback_query.message.message_id
)
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
conn = await asyncpg.connect(DATABASE_URL)
try:
record = await conn.fetchrow(
"SELECT email, expiry_time FROM keys WHERE client_id = $1", client_id
"SELECT email, expiry_time FROM keys WHERE client_id = $1",
client_id,
)
if record:
@@ -605,29 +583,17 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
current_time = datetime.utcnow().timestamp() * 1000
if expiry_time <= current_time:
new_expiry_time = int(
current_time
+ timedelta(days=days_to_extend).total_seconds() * 1000
)
new_expiry_time = int(current_time + timedelta(days=days_to_extend).total_seconds() * 1000)
else:
new_expiry_time = int(
expiry_time
+ timedelta(days=days_to_extend).total_seconds() * 1000
)
new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000)
cost = RENEWAL_PLANS[plan]["price"]
balance = await get_balance(tg_id)
if balance < cost:
replenish_button = types.InlineKeyboardButton(
text="Пополнить баланс", callback_data="pay"
)
view_profile = types.InlineKeyboardButton(
text="👤 Личный кабинет", callback_data="view_profile"
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[[replenish_button], [view_profile]]
)
replenish_button = types.InlineKeyboardButton(text="Пополнить баланс", callback_data="pay")
view_profile = types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [view_profile]])
await bot.send_message(
tg_id,
@@ -637,16 +603,15 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
)
return
response_message = SUCCESS_RENEWAL_MSG.format(
months=RENEWAL_PLANS[plan]["months"]
)
back_button = types.InlineKeyboardButton(
text="Назад", callback_data="view_profile"
)
response_message = SUCCESS_RENEWAL_MSG.format(months=RENEWAL_PLANS[plan]["months"])
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_profile")
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
await bot.send_message(
tg_id, response_message, reply_markup=keyboard, parse_mode="HTML"
tg_id,
response_message,
reply_markup=keyboard,
parse_mode="HTML",
)
async def renew_key_on_servers():
@@ -654,7 +619,11 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
for cluster_id in CLUSTERS:
task = asyncio.create_task(
renew_key_in_cluster(
cluster_id, email, client_id, new_expiry_time, total_gb
cluster_id,
email,
client_id,
new_expiry_time,
total_gb,
)
)
tasks.append(task)
@@ -673,8 +642,6 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
await conn.close()
except Exception as e:
await bot.send_message(
tg_id, f"Ошибка при продлении ключа: {e}", parse_mode="HTML"
)
await bot.send_message(tg_id, f"Ошибка при продлении ключа: {e}", parse_mode="HTML")
await callback_query.answer()
+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()
+29 -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(
"""
@@ -93,6 +90,7 @@ async def notify_10h_keys(
email=email,
expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"),
days_left_message=days_left_message,
price=RENEWAL_PLANS["1"]["price"],
)
if not await is_bot_blocked(bot, tg_id):
@@ -113,7 +111,8 @@ async def notify_10h_keys(
],
[
types.InlineKeyboardButton(
text="👤 Личный кабинет", callback_data="view_profile"
text="👤 Личный кабинет",
callback_data="view_profile",
)
],
]
@@ -121,9 +120,7 @@ async def notify_10h_keys(
await bot.send_message(tg_id, message, reply_markup=keyboard)
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
except Exception as e:
logger.error(
f"Ошибка при отправке уведомления пользователю {tg_id}: {e}"
)
logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")
continue
await conn.execute(
@@ -136,7 +133,10 @@ async def notify_10h_keys(
async def notify_24h_keys(
bot: Bot, conn: asyncpg.Connection, current_time: float, threshold_time_24h: float
bot: Bot,
conn: asyncpg.Connection,
current_time: float,
threshold_time_24h: float,
):
logger.info("Проверка истекших ключей...")
@@ -191,7 +191,8 @@ async def notify_24h_keys(
],
[
types.InlineKeyboardButton(
text="👤 Личный кабинет", callback_data="view_profile"
text="👤 Личный кабинет",
callback_data="view_profile",
)
],
]
@@ -199,18 +200,14 @@ async def notify_24h_keys(
await bot.send_message(tg_id, message_24h, reply_markup=keyboard)
logger.info(f"Уведомление за 24 часа отправлено пользователю {tg_id}.")
except Exception as e:
logger.error(
f"Ошибка при отправке уведомления за 24 часа пользователю {tg_id}: {e}"
)
logger.error(f"Ошибка при отправке уведомления за 24 часа пользователю {tg_id}: {e}")
continue
await conn.execute(
"UPDATE keys SET notified_24h = TRUE WHERE client_id = $1",
record["client_id"],
)
logger.info(
f"Обновлено поле notified_24h для клиента {record['client_id']}."
)
logger.info(f"Обновлено поле notified_24h для клиента {record['client_id']}.")
await asyncio.sleep(1)
@@ -248,30 +245,18 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
"💡 Не откладывайте подключение VPN!"
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text="👤 Личный кабинет", callback_data="view_profile"
)
]
]
inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")]]
)
try:
if balance >= RENEWAL_PLANS["1"]["price"]:
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
new_expiry_time = int(
(datetime.utcnow() + timedelta(days=30)).timestamp() * 1000
)
new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000)
await update_key_expiry(client_id, new_expiry_time)
for cluster_id in CLUSTERS:
await renew_key_in_cluster(
cluster_id, email, client_id, new_expiry_time, TOTAL_GB
)
logger.info(
f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}."
)
await renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, TOTAL_GB)
logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.")
await conn.execute(
"""
@@ -281,25 +266,15 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
""",
client_id,
)
logger.info(
f"Флаги notified и notified_24 сброшены для клиента с ID {client_id}."
)
logger.info(f"Флаги notified и notified_24 сброшены для клиента с ID {client_id}.")
try:
await bot.send_message(
tg_id, text=KEY_RENEWED, reply_markup=keyboard
)
logger.info(
f"Уведомление об успешном продлении отправлено клиенту {tg_id}."
)
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
except Exception as e:
logger.error(
f"Ошибка при отправке уведомления клиенту {tg_id}: {e}"
)
logger.error(f"Ошибка при отправке уведомления клиенту {tg_id}: {e}")
else:
await safe_send_message(
bot, tg_id, message_expired, reply_markup=keyboard
)
await safe_send_message(bot, tg_id, message_expired, reply_markup=keyboard)
await delete_key(client_id)
for cluster_id, cluster in CLUSTERS.items():
+2 -8
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"
+20 -42
View File
@@ -8,7 +8,7 @@ from aiohttp import web
from bot import bot
from config import CRYPTO_BOT_ENABLE, CRYPTO_BOT_TOKEN, RUB_TO_USDT
from database import add_connection, check_connection_exists, get_key_count, update_balance
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
@@ -24,22 +24,16 @@ class ReplenishBalanceState(StatesGroup):
entering_custom_amount_crypto = State()
async def send_message_with_deletion(
chat_id, text, reply_markup=None, state=None, message_key="last_message_id"
):
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key="last_message_id"):
if state:
try:
state_data = await state.get_data()
previous_message_id = state_data.get(message_key)
if previous_message_id:
await bot.delete_message(
chat_id=chat_id, message_id=previous_message_id
)
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
sent_message = await bot.send_message(
chat_id=chat_id, text=text, reply_markup=reply_markup
)
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
await state.update_data({message_key: sent_message.message_id})
except Exception as e:
@@ -50,9 +44,7 @@ async def send_message_with_deletion(
@router.callback_query(F.data == "pay_cryptobot")
async def process_callback_pay_cryptobot(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
builder = InlineKeyboardBuilder()
@@ -78,7 +70,8 @@ async def process_callback_pay_cryptobot(
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_crypto"
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_crypto",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile"))
@@ -91,9 +84,7 @@ async def process_callback_pay_cryptobot(
await add_connection(tg_id, balance=0.0, trial=0)
try:
await bot.delete_message(
chat_id=tg_id, message_id=callback_query.message.message_id
)
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
except Exception as e:
logger.error(f"Не удалось удалить сообщение: {e}")
@@ -108,9 +99,7 @@ async def process_callback_pay_cryptobot(
@router.callback_query(F.data.startswith("crypto_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
data = callback_query.data.split("|", 1)
if len(data) != 2:
@@ -152,9 +141,7 @@ async def process_amount_selection(
if hasattr(invoice, "bot_invoice_url"):
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url)
)
builder.row(InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url))
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
await bot.send_message(
chat_id=callback_query.from_user.id,
@@ -173,9 +160,7 @@ async def process_amount_selection(
async def send_payment_success_notification(user_id: int, amount: float):
try:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
)
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
await bot.send_message(
chat_id=user_id,
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
@@ -193,9 +178,7 @@ async def cryptobot_webhook(request):
await process_crypto_payment(data["payload"])
return web.Response(status=200)
else:
logger.warning(
f"Неподдерживаемый тип обновления: {data.get('update_type')}"
)
logger.warning(f"Неподдерживаемый тип обновления: {data.get('update_type')}")
return web.Response(status=400)
except Exception as e:
logger.error(f"Ошибка обработки вебхука: {e}")
@@ -209,6 +192,7 @@ async def process_crypto_payment(payload):
try:
user_id = int(user_id_str)
amount = int(amount_str)
await add_payment(int(user_id), float(amount), "cryptobot")
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
await update_balance(user_id, amount)
await send_payment_success_notification(user_id, amount)
@@ -219,9 +203,7 @@ async def process_crypto_payment(payload):
@router.callback_query(F.data == "enter_custom_amount_crypto")
async def process_enter_custom_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
await callback_query.message.edit_text(text="Введите сумму пополнения:")
await state.set_state(ReplenishBalanceState.entering_custom_amount_crypto)
await callback_query.answer()
@@ -232,15 +214,11 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
if message.text.isdigit():
amount = int(message.text)
if amount // RUB_TO_USDT <= 0:
await message.answer(
f"Сумма должна быть больше {RUB_TO_USDT}. Пожалуйста, введите сумму еще раз:"
)
await message.answer(f"Сумма должна быть больше {RUB_TO_USDT}. Пожалуйста, введите сумму еще раз:")
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_crypto
)
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_crypto)
try:
invoice = await crypto.create_invoice(
asset="USDT",
@@ -251,9 +229,7 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
if hasattr(invoice, "bot_invoice_url"):
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url)
)
builder.row(InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url))
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"),
)
@@ -263,7 +239,9 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
)
else:
await send_message_with_deletion(
message.from_user.id, "Ошибка при создании платежа.", state=state
message.from_user.id,
"Ошибка при создании платежа.",
state=state,
)
except Exception as e:
logger.error(f"Ошибка при создании платежа: {e}")
+15 -35
View File
@@ -4,17 +4,17 @@ import logging
import time
import uuid
import requests
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
import requests
from bot import bot
from config import FREEKASSA_API_KEY, FREEKASSA_SHOP_ID
from database import update_balance
from database import add_payment, update_balance
from handlers.texts import PAYMENT_OPTIONS
router = Router()
@@ -49,9 +49,7 @@ async def create_payment(user_id, amount, email, ip):
params["signature"] = generate_signature(params, FREEKASSA_API_KEY)
try:
response = requests.post(
"https://api.freekassa.com/v1/orders/create", json=params
)
response = requests.post("https://api.freekassa.com/v1/orders/create", json=params)
response_data = response.json()
logging.debug(f"Ответ от FreeKassa при создании платежа: {response_data}")
@@ -86,6 +84,7 @@ async def freekassa_webhook(request):
if data["status"] == "completed":
user_id = data["metadata"]["user_id"]
amount = float(data["amount"])
await add_payment(int(user_id), float(amount), "freekassa")
await update_balance(user_id, amount)
await send_payment_success_notification(user_id, amount)
@@ -94,9 +93,7 @@ async def freekassa_webhook(request):
@router.callback_query(lambda c: c.data == "pay_freekassa")
async def process_callback_pay_freekassa(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_callback_pay_freekassa(callback_query: types.CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
builder = InlineKeyboardBuilder()
@@ -121,14 +118,13 @@ async def process_callback_pay_freekassa(
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_freekassa"
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_freekassa",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile"))
await bot.delete_message(
chat_id=tg_id, message_id=callback_query.message.message_id
)
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
await bot.send_message(
chat_id=tg_id,
@@ -141,9 +137,7 @@ async def process_callback_pay_freekassa(
@router.callback_query(F.data.startswith("freekassa_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
data = callback_query.data.split("|", 1)
amount_str = data[1]
try:
@@ -154,18 +148,12 @@ async def process_amount_selection(
user_email = f"{callback_query.from_user.id}@solo.net"
user_ip = callback_query.message.chat.id
payment_url = await create_payment(
callback_query.from_user.id, amount, user_email, user_ip
)
payment_url = await create_payment(callback_query.from_user.id, amount, user_email, user_ip)
if payment_url:
confirm_keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=f"Оплатить {amount} рублей", url=payment_url
)
],
[InlineKeyboardButton(text=f"Оплатить {amount} рублей", url=payment_url)],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")],
]
)
@@ -185,9 +173,7 @@ async def process_amount_selection(
@router.callback_query(F.data == "enter_custom_amount_freekassa")
async def process_enter_custom_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
await callback_query.message.edit_text(text="Введите сумму пополнения:")
await state.set_state(ReplenishBalanceState.entering_custom_amount_freekassa)
await callback_query.answer()
@@ -198,21 +184,15 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
if message.text.isdigit():
amount = int(message.text)
if amount <= 0:
await message.answer(
"Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
)
await message.answer("Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:")
return
user_email = f"{message.from_user.id}@solo.net"
user_ip = message.chat.id
payment_url = await create_payment(
message.from_user.id, amount, user_email, user_ip
)
payment_url = await create_payment(message.from_user.id, amount, user_email, user_ip)
if payment_url:
keyboard = InlineKeyboardMarkup(
inline_keyboard=[[InlineKeyboardButton("Оплатить", url=payment_url)]]
)
keyboard = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton("Оплатить", url=payment_url)]])
await message.answer(
f"Вы выбрали оплату на {amount} рублей. Перейдите по ссылке для завершения оплаты:",
reply_markup=keyboard,
+25 -49
View File
@@ -6,13 +6,13 @@ from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
from loguru import logger
from robokassa import HashAlgorithm, Robokassa
from bot import bot
from config import ROBOKASSA_ENABLE, ROBOKASSA_LOGIN, ROBOKASSA_PASSWORD1, ROBOKASSA_PASSWORD2, ROBOKASSA_TEST_MODE
from database import add_connection, check_connection_exists, get_key_count, update_balance
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
router = Router()
@@ -36,35 +36,28 @@ if ROBOKASSA_ENABLE:
def generate_payment_link(amount, inv_id, description, tg_id):
"""Генерация ссылки на оплату."""
logger.debug(
f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}"
)
logger.debug(f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}")
payment_link = robokassa._payment.link.generate_by_script(
out_sum=amount, inv_id=inv_id, description="пополнение баланса", id=f"{tg_id}"
out_sum=amount,
inv_id=inv_id,
description="пополнение баланса",
id=f"{tg_id}",
)
logger.info(f"Generated payment link: {payment_link}")
return payment_link
async def send_message_with_deletion(
chat_id, text, reply_markup=None, state=None, message_key="last_message_id"
):
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key="last_message_id"):
if state:
try:
state_data = await state.get_data()
previous_message_id = state_data.get(message_key)
if previous_message_id:
logger.debug(
f"Deleting previous message with ID: {previous_message_id}"
)
await bot.delete_message(
chat_id=chat_id, message_id=previous_message_id
)
logger.debug(f"Deleting previous message with ID: {previous_message_id}")
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
sent_message = await bot.send_message(
chat_id=chat_id, text=text, reply_markup=reply_markup
)
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
await state.update_data({message_key: sent_message.message_id})
logger.debug(f"Sent new message with ID: {sent_message.message_id}")
@@ -76,9 +69,7 @@ async def send_message_with_deletion(
@router.callback_query(F.data == "pay_robokassa")
async def process_callback_pay_robokassa(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
logger.info(f"User {tg_id} initiated Robokassa payment.")
@@ -104,7 +95,8 @@ async def process_callback_pay_robokassa(
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_robokassa"
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_robokassa",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile"))
@@ -118,9 +110,7 @@ async def process_callback_pay_robokassa(
logger.info(f"Created new connection for user {tg_id} with balance 0.0.")
try:
await bot.delete_message(
chat_id=tg_id, message_id=callback_query.message.message_id
)
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
logger.debug(f"Deleted message with ID: {callback_query.message.message_id}")
except Exception as e:
logger.error(f"Не удалось удалить сообщение: {e}")
@@ -136,9 +126,7 @@ async def process_callback_pay_robokassa(
@router.callback_query(F.data.startswith("robokassa_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
logger.info(f"Получены данные callback_data: {callback_query.data}")
data = callback_query.data.split("|")
@@ -203,9 +191,7 @@ async def robokassa_webhook(request):
shp_id = params.get("shp_id")
signature_value = params.get("SignatureValue")
logger.info(
f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}"
)
logger.info(f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}")
if not check_payment_signature(params):
logger.error("Неверная подпись или данные запроса.")
@@ -222,6 +208,8 @@ async def robokassa_webhook(request):
await update_balance(int(tg_id), float(amount))
await send_payment_success_notification(tg_id, float(amount))
await add_payment(int(tg_id), float(amount), "robokassa")
logger.info(f"Payment successful. Balance updated for user {tg_id}.")
return web.Response(text=f"OK{inv_id}")
@@ -242,9 +230,7 @@ def check_payment_signature(params):
logger.info(f"Signature string before hashing: {signature_string}")
expected_signature = (
hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
)
expected_signature = hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
logger.info(f"Expected signature: {expected_signature}")
logger.info(f"Received signature: {signature_value}")
@@ -254,9 +240,7 @@ def check_payment_signature(params):
async def send_payment_success_notification(user_id: int, amount: float):
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
)
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
await bot.send_message(
chat_id=user_id,
@@ -267,18 +251,12 @@ async def send_payment_success_notification(user_id: int, amount: float):
@router.callback_query(F.data == "enter_custom_amount_robokassa")
async def process_custom_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_custom_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
logger.info(f"User {tg_id} chose to enter a custom amount.")
await callback_query.message.edit_text(
text="Пожалуйста, введите сумму пополнения в рублях (например, 150):"
)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_robokassa
)
await callback_query.message.edit_text(text="Пожалуйста, введите сумму пополнения в рублях (например, 150):")
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
await callback_query.answer()
@@ -313,6 +291,4 @@ async def handle_custom_amount_input(message: types.Message, state: FSMContext):
await state.clear()
except ValueError as e:
logger.error(f"Некорректная сумма от пользователя {tg_id}: {e}")
await message.answer(
text="Введите корректную сумму в рублях (целое положительное число)."
)
await message.answer(text="Введите корректную сумму в рублях (целое положительное число).")
+14 -34
View File
@@ -6,7 +6,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from bot import bot
from config import RUB_TO_XTR
from database import add_connection, check_connection_exists, get_key_count, update_balance
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
@@ -19,22 +19,16 @@ class ReplenishBalanceState(StatesGroup):
entering_custom_amount_stars = State()
async def send_message_with_deletion(
chat_id, text, reply_markup=None, state=None, message_key="last_message_id"
):
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key="last_message_id"):
if state:
try:
state_data = await state.get_data()
previous_message_id = state_data.get(message_key)
if previous_message_id:
await bot.delete_message(
chat_id=chat_id, message_id=previous_message_id
)
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
sent_message = await bot.send_message(
chat_id=chat_id, text=text, reply_markup=reply_markup
)
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
await state.update_data({message_key: sent_message.message_id})
except Exception as e:
@@ -45,17 +39,11 @@ async def send_message_with_deletion(
@router.callback_query(F.data == "pay_stars")
async def process_callback_pay_stars(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_callback_pay_stars(callback_query: types.CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"
)
)
builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"))
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
@@ -78,7 +66,8 @@ async def process_callback_pay_stars(
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_stars"
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_stars",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
@@ -106,9 +95,7 @@ async def process_callback_pay_stars(
@router.callback_query(F.data.startswith("stars_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
data = callback_query.data.split("|", 1)
if len(data) != 2:
@@ -165,9 +152,7 @@ async def process_amount_selection(
async def send_payment_success_notification(user_id: int, amount: float):
try:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
)
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
await bot.send_message(
chat_id=user_id,
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
@@ -178,9 +163,7 @@ async def send_payment_success_notification(user_id: int, amount: float):
@router.callback_query(F.data == "enter_custom_amount_stars")
async def process_enter_custom_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
await callback_query.message.edit_text(text="Введите сумму пополнения:")
await state.set_state(ReplenishBalanceState.entering_custom_amount_stars)
await callback_query.answer()
@@ -191,15 +174,11 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
if message.text.isdigit():
amount = int(message.text)
if amount // RUB_TO_XTR <= 0:
await message.answer(
f"Сумма должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:"
)
await message.answer(f"Сумма должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:")
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_stars
)
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_stars)
try:
builder = InlineKeyboardBuilder()
builder.row(
@@ -237,6 +216,7 @@ async def on_successful_payment(
user_id = int(message.from_user.id)
amount = float(message.successful_payment.invoice_payload.split("_")[0])
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
await add_payment(int(user_id), float(amount), "stars")
await update_balance(user_id, amount)
await send_payment_success_notification(user_id, amount)
except ValueError as e:
+26 -38
View File
@@ -10,7 +10,7 @@ from yookassa import Configuration, Payment
from bot import bot
from config import YOOKASSA_ENABLE, YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID
from database import add_connection, check_connection_exists, get_key_count, update_balance
from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
@@ -29,22 +29,16 @@ class ReplenishBalanceState(StatesGroup):
entering_custom_amount_yookassa = State()
async def send_message_with_deletion(
chat_id, text, reply_markup=None, state=None, message_key="last_message_id"
):
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key="last_message_id"):
if state:
try:
state_data = await state.get_data()
previous_message_id = state_data.get(message_key)
if previous_message_id:
await bot.delete_message(
chat_id=chat_id, message_id=previous_message_id
)
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
sent_message = await bot.send_message(
chat_id=chat_id, text=text, reply_markup=reply_markup
)
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
await state.update_data({message_key: sent_message.message_id})
except Exception as e:
@@ -55,9 +49,7 @@ async def send_message_with_deletion(
@router.callback_query(F.data == "pay_yookassa")
async def process_callback_pay_yookassa(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_callback_pay_yookassa(callback_query: types.CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
builder = InlineKeyboardBuilder()
@@ -83,7 +75,8 @@ async def process_callback_pay_yookassa(
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_yookassa"
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_yookassa",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_profile"))
@@ -96,9 +89,7 @@ async def process_callback_pay_yookassa(
await add_connection(tg_id, balance=0.0, trial=0)
try:
await bot.delete_message(
chat_id=tg_id, message_id=callback_query.message.message_id
)
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
except Exception as e:
logger.error(f"Не удалось удалить сообщение: {e}")
@@ -113,9 +104,7 @@ async def process_callback_pay_yookassa(
@router.callback_query(F.data.startswith("yookassa_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
data = callback_query.data.split("|", 1)
if len(data) != 2:
@@ -140,9 +129,7 @@ async def process_amount_selection(
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_yookassa
)
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_yookassa)
# state_data = await state.get_data()
customer_name = callback_query.from_user.full_name
@@ -153,7 +140,10 @@ async def process_amount_selection(
payment = Payment.create(
{
"amount": {"value": str(amount), "currency": "RUB"},
"confirmation": {"type": "redirect", "return_url": "https://pocomacho.ru/"},
"confirmation": {
"type": "redirect",
"return_url": "https://pocomacho.ru/",
},
"capture": True,
"description": "Пополнение баланса",
"receipt": {
@@ -192,7 +182,9 @@ async def process_amount_selection(
)
else:
await send_message_with_deletion(
callback_query.from_user.id, "Ошибка при создании платежа.", state=state
callback_query.from_user.id,
"Ошибка при создании платежа.",
state=state,
)
await callback_query.answer()
@@ -201,9 +193,7 @@ async def process_amount_selection(
async def send_payment_success_notification(user_id: int, amount: float):
try:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
)
builder.row(InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile"))
await bot.send_message(
chat_id=user_id,
@@ -224,6 +214,7 @@ async def yookassa_webhook(request):
user_id = int(user_id_str)
amount = float(amount_str)
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
await add_payment(int(user_id), float(amount), "yookassa")
await update_balance(user_id, amount)
await send_payment_success_notification(user_id, amount)
except ValueError as e:
@@ -233,9 +224,7 @@ async def yookassa_webhook(request):
@router.callback_query(F.data == "enter_custom_amount_yookassa")
async def process_enter_custom_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
await callback_query.message.edit_text(text="Введите сумму пополнения:")
await state.set_state(ReplenishBalanceState.entering_custom_amount_yookassa)
await callback_query.answer()
@@ -246,15 +235,11 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
if message.text.isdigit():
amount = int(message.text)
if amount <= 0:
await message.answer(
"Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
)
await message.answer("Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:")
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_yookassa
)
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_yookassa)
try:
payment = Payment.create(
@@ -276,7 +261,10 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
{
"description": "Пополнение баланса",
"quantity": "1.00",
"amount": {"value": str(amount), "currency": "RUB"},
"amount": {
"value": str(amount),
"currency": "RUB",
},
"vat_code": 6,
}
],
+34 -23
View File
@@ -8,15 +8,13 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from bot import bot
from config import CHANNEL_URL
from database import get_balance, get_key_count, get_referral_stats
from handlers.texts import get_referral_link, invite_message_send, profile_message_send
from handlers.texts import RENEWAL_PLANS, get_referral_link, invite_message_send, profile_message_send
from logger import logger
router = Router()
async def process_callback_view_profile(
callback_query: types.CallbackQuery, state: FSMContext, admin: bool
):
async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext, admin: bool):
chat_id = callback_query.from_user.id
username = callback_query.from_user.full_name
@@ -35,6 +33,7 @@ async def process_callback_view_profile(
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="📢 Наш канал", url=CHANNEL_URL))
builder.row(InlineKeyboardButton(text="💡 Тарифы", callback_data="view_tariffs"))
builder.row(
InlineKeyboardButton(text=" Устройство", callback_data="create_key"),
InlineKeyboardButton(text="📱 Мои устройства", callback_data="view_keys"),
@@ -49,16 +48,10 @@ async def process_callback_view_profile(
InlineKeyboardButton(text="👥 Пригласить друзей", callback_data="invite"),
InlineKeyboardButton(text="📘 Инструкции", callback_data="instructions"),
)
builder.row(
InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
)
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
if admin:
builder.row(
InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")
)
builder.row(
InlineKeyboardButton(text="⬅️ Главное меню", callback_data="back_to_menu")
)
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
builder.row(InlineKeyboardButton(text="⬅️ Главное меню", callback_data="back_to_menu"))
try:
await callback_query.message.delete()
@@ -84,9 +77,33 @@ async def process_callback_view_profile(
except Exception as e:
await bot.send_message(
chat_id, f"❗️ Не удалось загрузить профиль. Техническая ошибка: {e}"
chat_id,
f"❗️ Не удалось загрузить профиль. Техническая ошибка: {e}",
)
@router.callback_query(F.data == "view_tariffs")
async def view_tariffs_handler(callback_query: types.CallbackQuery):
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
await callback_query.message.answer(
"<b>🚀 Доступные тарифы VPN:</b>\n\n"
+ "\n".join(
[
f"{months} {'месяц' if months == '1' else 'месяца' if int(months) in [2, 3, 4] else 'месяцев'}: "
f"{RENEWAL_PLANS[months]['price']} "
f"{'💳' if months == '1' else '🌟' if months == '3' else '🔥' if months == '6' else '🚀'} рублей"
for months in sorted(RENEWAL_PLANS.keys(), key=int)
]
),
parse_mode="HTML",
reply_markup=builder.as_markup(),
)
await callback_query.answer()
@@ -102,9 +119,7 @@ async def invite_handler(callback_query: types.CallbackQuery):
image_path = os.path.join("img", "pic_invite.jpg")
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile")
)
builder.row(InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile"))
try:
await callback_query.message.delete()
@@ -116,9 +131,7 @@ async def invite_handler(callback_query: types.CallbackQuery):
with open(image_path, "rb") as image_file:
await bot.send_photo(
chat_id=chat_id,
photo=BufferedInputFile(
image_file.read(), filename="pic_invite.jpg"
),
photo=BufferedInputFile(image_file.read(), filename="pic_invite.jpg"),
caption=invite_message,
parse_mode="HTML",
reply_markup=builder.as_markup(),
@@ -142,8 +155,6 @@ async def invite_handler(callback_query: types.CallbackQuery):
@router.callback_query(F.data == "view_profile")
async def view_profile_handler(
callback_query: types.CallbackQuery, state: FSMContext, admin: bool = False
):
async def view_profile_handler(callback_query: types.CallbackQuery, state: FSMContext, admin: bool = False):
await state.clear()
await process_callback_view_profile(callback_query, state, admin)
+29 -60
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("Обнаружен реферальный код.")
@@ -69,49 +69,27 @@ async def start_command(message: Message, admin: bool = False):
logger.info(f"ID пригласившего пользователя: {referrer_tg_id}")
except ValueError:
logger.error("Ошибка парсинга реферального ID.")
await message.answer("Некорректный реферальный код.")
return
connection_exists = await check_connection_exists(message.from_user.id)
logger.info(
f"Результат проверки подключения для user_id {message.from_user.id}: {connection_exists}"
)
logger.info(f"Результат проверки подключения для user_id {message.from_user.id}: {connection_exists}")
if not connection_exists:
logger.info(
f"Добавляем подключение для пользователя: {message.from_user.id}"
)
logger.info(f"Добавляем подключение для пользователя: {message.from_user.id}")
await add_connection(message.from_user.id)
logger.info(
f"Добавляем реферал для пользователя {message.from_user.id}, приглашённым {referrer_tg_id}"
)
logger.info(f"Добавляем реферал для пользователя {message.from_user.id}, приглашённым {referrer_tg_id}")
await add_referral(message.from_user.id, referrer_tg_id)
await message.answer("Вас пригласил друг, добро пожаловать!")
else:
logger.warning(
f"Пользователь {message.from_user.id} уже зарегистрирован."
)
await message.answer("Вы уже зарегистрированы в системе!")
logger.info(f"Пользователь {message.from_user.id} уже зарегистрирован.")
logger.info(
f"Проверяем статус пробного периода для user_id {message.from_user.id}"
)
logger.info(f"Проверяем статус пробного периода для user_id {message.from_user.id}")
trial_status = await get_trial(message.from_user.id)
logger.info(
f"Статус пробного периода для user_id {message.from_user.id}: {trial_status}"
)
logger.info(f"Статус пробного периода для user_id {message.from_user.id}: {trial_status}")
logger.info(
f"Отправка приветственного сообщения для user_id {message.from_user.id}"
)
logger.info(f"Отправка приветственного сообщения для user_id {message.from_user.id}")
await send_welcome_message(message.chat.id, trial_status, admin)
except Exception as e:
logger.exception(
f"Ошибка в обработке команды /start для user_id {message.from_user.id}: {e}"
)
logger.error(f"Ошибка в обработке команды /start для user_id {message.from_user.id}: {e}")
await message.answer("Произошла ошибка. Пожалуйста, попробуйте позже.")
@@ -141,9 +119,7 @@ async def handle_connect_vpn(callback_query: CallbackQuery):
except Exception as e:
logger.error(f"Ошибка при обновлении trial: {e}")
await callback_query.message.answer(
"Произошла ошибка при обновлении статуса."
)
await callback_query.message.answer("Произошла ошибка при обновлении статуса.")
key_message = (
f"🔑 <b>Ваш персональный ключ доступа:</b>\n"
@@ -152,16 +128,15 @@ async def handle_connect_vpn(callback_query: CallbackQuery):
)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile"))
builder.row(
InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS),
InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
)
builder.row(
InlineKeyboardButton(
text="🍏 Подключить на iOS", url=f'{CONNECT_IOS}{trial_key_info["key"]}'
text="🍏 Подключить на iOS",
url=f'{CONNECT_IOS}{trial_key_info["key"]}',
),
InlineKeyboardButton(
text="🤖 Подключить на Android",
@@ -169,9 +144,7 @@ async def handle_connect_vpn(callback_query: CallbackQuery):
),
)
await callback_query.message.answer(
key_message, parse_mode="HTML", reply_markup=builder.as_markup()
)
await callback_query.message.answer(key_message, parse_mode="HTML", reply_markup=builder.as_markup())
await callback_query.answer()
@@ -183,14 +156,10 @@ async def handle_about_vpn(callback_query: CallbackQuery):
about_vpn_message = get_about_vpn("3.1.1_Stable")
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
)
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu"))
await callback_query.message.answer(
about_vpn_message, parse_mode="HTML", reply_markup=builder.as_markup()
)
await callback_query.message.answer(about_vpn_message, parse_mode="HTML", reply_markup=builder.as_markup())
await callback_query.answer()
+4 -14
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
+5 -1
View File
@@ -29,4 +29,8 @@ loguru
aiocryptopay
py3xui
sqlalchemy
robokassa
robokassa
flake8
black
isort
pylint