чистота кода
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
from config import SERVERS # Импортируем SERVERS из config.py
|
||||
from config import SERVERS
|
||||
|
||||
session = None
|
||||
|
||||
def login_with_credentials(server_id: str, username: str, password: str):
|
||||
global session
|
||||
session = requests.Session()
|
||||
api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера
|
||||
api_url = SERVERS[server_id]['API_URL']
|
||||
auth_url = f"{api_url}/login/"
|
||||
data = {
|
||||
"username": username,
|
||||
@@ -22,10 +23,10 @@ def login_with_credentials(server_id: str, username: str, password: str):
|
||||
raise Exception(f"Ошибка авторизации: {response.status_code}, {response.text}")
|
||||
|
||||
def get_clients(session, server_id):
|
||||
api_url = SERVERS[server_id]['API_URL'] # Получаем GET_INBOUNDS_URL для выбранного сервера
|
||||
api_url = SERVERS[server_id]['API_URL']
|
||||
response = session.get(f'{api_url}/panel/api/inbounds/list/')
|
||||
if response.status_code == 200:
|
||||
return response.json() # Возвращает данные по инбаундам и клиентам
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Ошибка при получении клиентов: {response.status_code}, {response.text}")
|
||||
|
||||
@@ -45,12 +46,10 @@ def link(session, server_id: str, client_id: str, email: str):
|
||||
inbounds = response['obj'][0]
|
||||
settings = json.loads(inbounds['settings'])
|
||||
|
||||
# Найти клиентский ID в настройках
|
||||
stream_settings = json.loads(inbounds['streamSettings'])
|
||||
tcp = stream_settings.get('network', 'tcp')
|
||||
reality = stream_settings.get('security', 'reality')
|
||||
flow = stream_settings.get('flow', 'xtls-rprx-vision')
|
||||
|
||||
# Создание ссылки для подключения VLESS
|
||||
val = f"vless://{client_id}@{SERVERS[server_id]['DOMEN']}?type={tcp}&security={reality}&pbk={SERVERS[server_id]['PBK']}&fp=chrome&sni={SERVERS[server_id]['SNI']}&sid={SERVERS[server_id]['SID']}=%2F&flow={flow}#{SERVERS[server_id]['PREFIX']}-{email}"
|
||||
return val
|
||||
|
||||
@@ -10,7 +10,6 @@ router = Router()
|
||||
|
||||
from handlers import key_management, keys, notifications, pay, profile, start
|
||||
|
||||
# Регистрация обработчиков
|
||||
dp.include_router(start.router)
|
||||
dp.include_router(profile.router)
|
||||
dp.include_router(keys.router)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import json
|
||||
from config import SERVERS # Импортируем SERVERS из config.py
|
||||
|
||||
from config import SERVERS
|
||||
|
||||
|
||||
def add_client(session, server_id: str, client_id: str, email: str, tg_id: str, limit_ip: int, total_gb: int, expiry_time: int, enable: bool, flow: str):
|
||||
api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера
|
||||
api_url = SERVERS[server_id]['API_URL']
|
||||
url = f'{api_url}/panel/api/inbounds/addClient'
|
||||
|
||||
email = email.lower()
|
||||
@@ -44,7 +46,7 @@ def add_client(session, server_id: str, client_id: str, email: str, tg_id: str,
|
||||
print(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}")
|
||||
|
||||
def extend_client_key(session, server_id: str, tg_id, client_id, email: str, new_expiry_time: int) -> bool:
|
||||
api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера
|
||||
api_url = SERVERS[server_id]['API_URL']
|
||||
response = session.get(f"{api_url}/panel/api/inbounds/getClientTraffics/{email}")
|
||||
print(f"GET {response.url} Status: {response.status_code}")
|
||||
print(f"GET Response: {response.text}")
|
||||
@@ -108,7 +110,7 @@ def extend_client_key(session, server_id: str, tg_id, client_id, email: str, new
|
||||
return False
|
||||
|
||||
def delete_client(session, server_id: str, client_id: str) -> bool:
|
||||
api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера
|
||||
api_url = SERVERS[server_id]['API_URL']
|
||||
url = f"{api_url}/panel/api/inbounds/1/delClient/{client_id}"
|
||||
headers = {
|
||||
'Accept': 'application/json'
|
||||
|
||||
+1
-6
@@ -4,10 +4,10 @@ import asyncpg
|
||||
|
||||
from config import DATABASE_URL
|
||||
|
||||
|
||||
async def init_db():
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
|
||||
# Создаем таблицу connections, если она не существует
|
||||
await conn.execute('''
|
||||
CREATE TABLE IF NOT EXISTS connections (
|
||||
tg_id BIGINT PRIMARY KEY NOT NULL,
|
||||
@@ -16,7 +16,6 @@ async def init_db():
|
||||
)
|
||||
''')
|
||||
|
||||
# Создаем таблицу keys, если она не существует
|
||||
await conn.execute('''
|
||||
CREATE TABLE IF NOT EXISTS keys (
|
||||
tg_id BIGINT NOT NULL,
|
||||
@@ -31,24 +30,20 @@ async def init_db():
|
||||
)
|
||||
''')
|
||||
|
||||
# Добавляем поле server_id в таблицу keys, если его нет
|
||||
try:
|
||||
await conn.execute('''
|
||||
ALTER TABLE keys
|
||||
ADD COLUMN server_id TEXT NOT NULL DEFAULT 'server1'
|
||||
''')
|
||||
except asyncpg.exceptions.DuplicateColumnError:
|
||||
# Если поле уже существует, ничего не делаем
|
||||
pass
|
||||
|
||||
# Добавляем поле notified в таблицу keys, если его нет
|
||||
try:
|
||||
await conn.execute('''
|
||||
ALTER TABLE keys
|
||||
ADD COLUMN notified BOOLEAN NOT NULL DEFAULT FALSE
|
||||
''')
|
||||
except asyncpg.exceptions.DuplicateColumnError:
|
||||
# Если поле уже существует, ничего не делаем
|
||||
pass
|
||||
|
||||
await conn.close()
|
||||
|
||||
@@ -12,18 +12,15 @@ from aiogram.types import (CallbackQuery, InlineKeyboardButton,
|
||||
from auth import link, login_with_credentials
|
||||
from bot import dp
|
||||
from client import add_client
|
||||
from config import (ADMIN_PASSWORD, ADMIN_USERNAME,
|
||||
DATABASE_URL, SERVERS)
|
||||
from database import (add_connection, get_balance, store_key,
|
||||
update_balance)
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS
|
||||
from database import add_connection, get_balance, store_key, update_balance
|
||||
from handlers.instructions import send_instructions
|
||||
from handlers.notifications import send_message_to_all_clients
|
||||
from handlers.profile import process_callback_view_profile
|
||||
from handlers.start import start_command
|
||||
from handlers.notifications import send_message_to_all_clients
|
||||
|
||||
router = Router()
|
||||
|
||||
# Удаляем специальные символы из имени ключа
|
||||
def sanitize_key_name(key_name: str) -> str:
|
||||
return re.sub(r'[^a-z0-9@._-]', '', key_name.lower())
|
||||
|
||||
@@ -36,14 +33,12 @@ class Form(StatesGroup):
|
||||
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
# Получаем количество подключений для каждого сервера
|
||||
server_buttons = []
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
for server_id, server in SERVERS.items():
|
||||
# Получаем количество ключей на сервере
|
||||
count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE server_id = $1', server_id)
|
||||
percent_full = (count / 100) * 100 # Заполнение в процентах
|
||||
percent_full = (count / 100) * 100
|
||||
server_name = f"{server['name']} ({percent_full:.1f}%)"
|
||||
server_buttons.append([InlineKeyboardButton(text=server_name, callback_data=f'select_server|{server_id}')])
|
||||
finally:
|
||||
@@ -64,7 +59,6 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext):
|
||||
server_id = callback_query.data.split('|')[1]
|
||||
await state.update_data(selected_server_id=server_id)
|
||||
|
||||
# Получаем данные о trial из базы данных
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
existing_connection = await conn.fetchrow('SELECT trial FROM connections WHERE tg_id = $1', callback_query.from_user.id)
|
||||
@@ -78,7 +72,7 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext):
|
||||
"<b>⚠️ У вас уже был пробный ключ.</b>\n\n"
|
||||
"Новый ключ будет выдан на <b>один месяц</b> и стоит <b>100 рублей</b>.\n\n"
|
||||
"<i>Хотите продолжить?</i>",
|
||||
parse_mode="HTML", # Добавляем параметр parse_mode
|
||||
parse_mode="HTML",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text='✅ Да, создать новый ключ', callback_data='confirm_create_new_key')],
|
||||
[InlineKeyboardButton(text='↩️ Назад', callback_data='cancel_create_key')]
|
||||
@@ -101,7 +95,6 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex
|
||||
data = await state.get_data()
|
||||
server_id = data.get('selected_server_id')
|
||||
|
||||
# Проверяем баланс перед созданием нового ключа
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < 100:
|
||||
replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')
|
||||
@@ -125,7 +118,6 @@ async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext):
|
||||
await process_callback_view_profile(callback_query, state)
|
||||
await callback_query.answer()
|
||||
|
||||
# Обработка текстовых сообщений
|
||||
@dp.message()
|
||||
async def handle_text(message: Message, state: FSMContext):
|
||||
current_state = await state.get_state()
|
||||
@@ -170,7 +162,6 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
current_time = datetime.utcnow()
|
||||
expiry_time = None
|
||||
|
||||
# Получаем статус пробного ключа из базы данных
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
existing_connection = await conn.fetchrow('SELECT trial FROM connections WHERE tg_id = $1', tg_id)
|
||||
@@ -180,10 +171,8 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
trial_status = existing_connection['trial'] if existing_connection else 0
|
||||
|
||||
if trial_status == 0:
|
||||
# Создаем пробный ключ на 1 день
|
||||
expiry_time = current_time + timedelta(days=1, hours=3)
|
||||
else:
|
||||
# Проверяем баланс перед созданием нового ключа
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < 100:
|
||||
replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')
|
||||
@@ -198,7 +187,6 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
||||
|
||||
try:
|
||||
# Попробуем добавить клиента
|
||||
response = add_client(session, server_id, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_timestamp, enable=True, flow="xtls-rprx-vision")
|
||||
|
||||
if not response.get("success", True):
|
||||
@@ -225,13 +213,11 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
await store_key(tg_id, client_id, email, expiry_timestamp, connection_link, server_id)
|
||||
|
||||
# Рассчитываем оставшееся время до окончания действия ключа
|
||||
remaining_time = expiry_time - current_time
|
||||
days = remaining_time.days
|
||||
hours, remainder = divmod(remaining_time.seconds, 3600)
|
||||
minutes, _ = divmod(remainder, 60)
|
||||
|
||||
# Формируем сообщение с информацией о ключе
|
||||
remaining_time_message = (
|
||||
f"Оставшееся время ключа: {days} день"
|
||||
)
|
||||
|
||||
+19
-56
@@ -3,15 +3,14 @@ from datetime import datetime, timedelta
|
||||
import asyncpg
|
||||
from aiogram import Router, types
|
||||
|
||||
from auth import login_with_credentials, link
|
||||
from auth import link, login_with_credentials
|
||||
from bot import bot
|
||||
from client import delete_client, extend_client_key, add_client
|
||||
from client import add_client, delete_client, extend_client_key
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS
|
||||
from database import get_balance, update_balance
|
||||
|
||||
router = Router()
|
||||
|
||||
# Обработка запроса на просмотр ключей
|
||||
@router.callback_query(lambda c: c.data == 'view_keys')
|
||||
async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
@@ -24,26 +23,19 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
''', tg_id)
|
||||
|
||||
if records:
|
||||
# Создаем кнопки для каждого ключа
|
||||
buttons = []
|
||||
for record in records:
|
||||
key_name = record['email']
|
||||
client_id = record['client_id']
|
||||
# Заменяем подчеркивание на вертикальную черту в callback_data
|
||||
button = types.InlineKeyboardButton(text=f"🔑 {key_name}", callback_data=f'view_key|{key_name}|{client_id}')
|
||||
buttons.append([button])
|
||||
|
||||
# Создаем клавиатуру с кнопками
|
||||
inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||
response_message = "<b>Выберите устройство:</b>"
|
||||
|
||||
# Редактируем сообщение с клавиатурой
|
||||
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=inline_keyboard, parse_mode="HTML")
|
||||
else:
|
||||
# Если нет ключей, добавляем кнопку "Создать ключ" и "Назад"
|
||||
response_message = "<b>У вас нет ключей.</b>"
|
||||
|
||||
# Кнопка "Создать ключ"
|
||||
create_key_button = types.InlineKeyboardButton(text='➕ Создать ключ', callback_data='create_key')
|
||||
back_button = types.InlineKeyboardButton(text='🔙 Назад', callback_data='view_profile') # Измените на правильное значение для кнопки "Назад"
|
||||
|
||||
@@ -62,7 +54,6 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||
@router.callback_query(lambda c: c.data.startswith('view_key|'))
|
||||
async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
# Разделяем данные по вертикальной черте
|
||||
key_name, client_id = callback_query.data.split('|')[1], callback_query.data.split('|')[2]
|
||||
|
||||
try:
|
||||
@@ -79,7 +70,6 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
expiry_time = record['expiry_time']
|
||||
server_id = record['server_id']
|
||||
|
||||
# Получаем название сервера по server_id
|
||||
server_name = SERVERS.get(server_id, {}).get('name', 'Неизвестный сервер')
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
@@ -99,7 +89,6 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
f"{days_left_message}\n"
|
||||
f"🌍 <b>Сервер:</b> {server_name}")
|
||||
|
||||
# Кнопки для продления, инструкций и удаления
|
||||
renew_button = types.InlineKeyboardButton(text='⏳ Продлить ключ', callback_data=f'renew_key|{client_id}')
|
||||
instructions_button = types.InlineKeyboardButton(text='📘 Инструкции', callback_data='instructions')
|
||||
delete_button = types.InlineKeyboardButton(text='❌ Удалить ключ', callback_data=f'delete_key|{client_id}')
|
||||
@@ -108,10 +97,10 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[instructions_button], # Инструкции отдельной строкой
|
||||
[renew_button, delete_button], # Продлить и Удалить в одном ряду
|
||||
[change_location_button], # Сменить локацию отдельной строкой
|
||||
[back_button] # Назад отдельной строкой
|
||||
[instructions_button],
|
||||
[renew_button, delete_button],
|
||||
[change_location_button],
|
||||
[back_button]
|
||||
]
|
||||
)
|
||||
|
||||
@@ -127,12 +116,10 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
# Обработка запроса на удаление ключа
|
||||
@router.callback_query(lambda c: c.data.startswith('delete_key|'))
|
||||
async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
client_id = callback_query.data.split('|')[1] # Используем разделитель вертикальная черта
|
||||
client_id = callback_query.data.split('|')[1]
|
||||
|
||||
confirmation_keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(text='✅ Да, удалить', callback_data=f'confirm_delete|{client_id}')],
|
||||
@@ -142,12 +129,10 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
await bot.edit_message_text("<b>Вы уверены, что хотите удалить ключ?</b>", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=confirmation_keyboard, parse_mode="HTML")
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
# Обработка выбора плана продления
|
||||
@router.callback_query(lambda c: c.data.startswith('renew_key|'))
|
||||
async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
client_id = callback_query.data.split('|')[1] # Используем разделитель вертикальная черта
|
||||
client_id = callback_query.data.split('|')[1]
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
@@ -157,9 +142,7 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||
if record:
|
||||
email = record['email']
|
||||
expiry_time = record['expiry_time']
|
||||
current_time = datetime.utcnow().timestamp() * 1000 # Получаем текущее время в миллисекундах
|
||||
|
||||
# Добавляем новые планы продления (6 месяцев и год)
|
||||
current_time = datetime.utcnow().timestamp() * 1000
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(text='📅 1 месяц (100 руб.)', callback_data=f'renew_plan|1|{client_id}')],
|
||||
[types.InlineKeyboardButton(text='📅 3 месяца (285 руб.)', callback_data=f'renew_plan|3|{client_id}')],
|
||||
@@ -186,19 +169,16 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||
@router.callback_query(lambda c: c.data.startswith('confirm_delete|'))
|
||||
async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
client_id = callback_query.data.split('|')[1] # Используем разделитель вертикальная черта
|
||||
client_id = callback_query.data.split('|')[1]
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
# Извлекаем server_id и email из базы данных
|
||||
record = await conn.fetchrow('SELECT email, server_id FROM keys WHERE client_id = $1', client_id)
|
||||
|
||||
if record:
|
||||
email = record['email']
|
||||
server_id = record['server_id'] # Извлекаем server_id из записи
|
||||
|
||||
# Используем server_id для авторизации
|
||||
server_id = record['server_id']
|
||||
session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
success = delete_client(session, server_id, client_id)
|
||||
|
||||
@@ -227,36 +207,33 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||
@router.callback_query(lambda c: c.data.startswith('renew_plan|'))
|
||||
async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
plan, client_id = callback_query.data.split('|')[1], callback_query.data.split('|')[2] # '1', '3', '6' или '12' и client_id
|
||||
days_to_extend = 30 * int(plan) # Умножаем на количество месяцев (1 месяц = 30 дней)
|
||||
plan, client_id = callback_query.data.split('|')[1], callback_query.data.split('|')[2]
|
||||
days_to_extend = 30 * int(plan)
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
# Извлекаем email, expiry_time и server_id из базы данных
|
||||
record = await conn.fetchrow('SELECT email, expiry_time, server_id FROM keys WHERE client_id = $1', client_id)
|
||||
|
||||
if record:
|
||||
email = record['email']
|
||||
expiry_time = record['expiry_time']
|
||||
server_id = record['server_id'] # Извлекаем server_id из записи
|
||||
current_time = datetime.utcnow().timestamp() * 1000 # Текущее время в миллисекундах
|
||||
server_id = record['server_id']
|
||||
current_time = datetime.utcnow().timestamp() * 1000
|
||||
|
||||
# Проверяем, если ключ истек, то продлеваем от текущей даты, иначе продлеваем от текущей даты истечения
|
||||
if expiry_time <= current_time:
|
||||
new_expiry_time = int(current_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
||||
else:
|
||||
new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
||||
|
||||
# Стоимость продления в зависимости от выбранного плана
|
||||
if plan == '1':
|
||||
cost = 100
|
||||
elif plan == '3':
|
||||
cost = 285
|
||||
elif plan == '6':
|
||||
cost = 540 # Стоимость для 6 месяцев
|
||||
cost = 540
|
||||
elif plan == '12':
|
||||
cost = 1000 # Стоимость для 12 месяцев
|
||||
cost = 1000
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
if balance < cost:
|
||||
@@ -267,12 +244,10 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
await bot.edit_message_text("Недостаточно средств для продления ключа.", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard)
|
||||
return
|
||||
|
||||
# Продлеваем ключ через API, используя server_id
|
||||
session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
success = extend_client_key(session, server_id, tg_id, client_id, email, new_expiry_time)
|
||||
|
||||
if success:
|
||||
# Обновляем баланс и ключ в базе данных
|
||||
await update_balance(tg_id, -cost)
|
||||
await conn.execute('UPDATE keys SET expiry_time = $1 WHERE client_id = $2', new_expiry_time, client_id)
|
||||
response_message = f"Ваш ключ был успешно продлен на {days_to_extend // 30} месяц(-а)."
|
||||
@@ -298,16 +273,13 @@ async def handle_error(tg_id, callback_query, message):
|
||||
@router.callback_query(lambda c: c.data.startswith('change_location|'))
|
||||
async def process_callback_change_location(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
client_id = callback_query.data.split('|')[1] # Используем разделитель вертикальная черта
|
||||
|
||||
# Получаем количество подключений для каждого сервера
|
||||
client_id = callback_query.data.split('|')[1]
|
||||
server_buttons = []
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
for server_id, server in SERVERS.items():
|
||||
# Получаем количество ключей на сервере
|
||||
count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE server_id = $1', server_id)
|
||||
percent_full = (count / 100) * 100 # Заполнение в процентах
|
||||
percent_full = (count / 100) * 100
|
||||
server_name = f"{server['name']} ({percent_full:.1f}%)"
|
||||
server_buttons.append([types.InlineKeyboardButton(text=server_name, callback_data=f'select_server&{server_id}&{client_id}')])
|
||||
finally:
|
||||
@@ -327,32 +299,23 @@ async def process_callback_select_server(callback_query: types.CallbackQuery):
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
# Извлекаем email и expiry_time из базы данных
|
||||
record = await conn.fetchrow('SELECT email, expiry_time, server_id FROM keys WHERE client_id = $1', client_id)
|
||||
|
||||
if record:
|
||||
email = record['email']
|
||||
expiry_time = record['expiry_time']
|
||||
current_server_id = record['server_id']
|
||||
|
||||
# Создаем сессию для нового сервера
|
||||
session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
|
||||
# Рассчитываем новое время окончания ключа
|
||||
new_expiry_time = int(datetime.utcnow().timestamp() * 1000) + (expiry_time - datetime.utcnow().timestamp() * 1000)
|
||||
|
||||
# Создаем нового клиента на новом сервере
|
||||
new_client_data = add_client(session, server_id, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=new_expiry_time, enable=True, flow="xtls-rprx-vision")
|
||||
|
||||
if new_client_data:
|
||||
# Генерируем новый ключ
|
||||
new_key = link(session, server_id, client_id, email)
|
||||
|
||||
# Обновляем запись в базе данных, только ключ и сервер
|
||||
await conn.execute('UPDATE keys SET server_id = $1, key = $2 WHERE client_id = $3',
|
||||
server_id, new_key, client_id)
|
||||
|
||||
# Удаляем клиента с текущего сервера
|
||||
session = login_with_credentials(current_server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
success_delete = delete_client(session, current_server_id, client_id)
|
||||
if success_delete:
|
||||
|
||||
+28
-28
@@ -2,15 +2,15 @@ from datetime import datetime, timedelta
|
||||
|
||||
import asyncpg
|
||||
from aiogram import Bot, Router, types
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import ( # Импортируем необходимые классы
|
||||
InlineKeyboardButton, InlineKeyboardMarkup)
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
|
||||
from bot import bot
|
||||
from config import DATABASE_URL, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_ID
|
||||
from client import delete_client
|
||||
from auth import login_with_credentials
|
||||
from bot import bot
|
||||
from client import delete_client
|
||||
from config import ADMIN_ID, ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||
from database import get_balance
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -21,8 +21,7 @@ async def notify_expiring_keys(bot: Bot):
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
# Получаем все ключи, которые истекают в течение следующих 10 часов и еще не были уведомлены
|
||||
threshold_time = (datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000 # В миллисекундах
|
||||
threshold_time = (datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000
|
||||
records = await conn.fetch('''
|
||||
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
|
||||
WHERE expiry_time <= $1 AND expiry_time > $2 AND notified = FALSE
|
||||
@@ -32,32 +31,40 @@ async def notify_expiring_keys(bot: Bot):
|
||||
tg_id = record['tg_id']
|
||||
email = record['email']
|
||||
expiry_time = record['expiry_time']
|
||||
server_id = record['server_id'] # Получаем server_id из записи
|
||||
server_id = record['server_id']
|
||||
|
||||
# Рассчитываем оставшееся время и уменьшаем его на 3 часа
|
||||
time_left = (expiry_time / 1000) - datetime.utcnow().timestamp()
|
||||
hours_left = max(0, int(time_left // 3600) - 3)
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
# Создаем клавиатуру с выбором тарифов
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text='1 месяц (100 руб.)', callback_data=f'renew_plan|1|{record["client_id"]}')],
|
||||
[InlineKeyboardButton(text='3 месяца (285 руб.)', callback_data=f'renew_plan|3|{record["client_id"]}')],
|
||||
[InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')]
|
||||
[
|
||||
InlineKeyboardButton(text='1 месяц (100 руб.)', callback_data=f'renew_plan|1|{record["client_id"]}'),
|
||||
InlineKeyboardButton(text='3 месяца (285 руб.)', callback_data=f'renew_plan|3|{record["client_id"]}')
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text='6 месяцев (540 руб.)', callback_data=f'renew_plan|6|{record["client_id"]}'),
|
||||
InlineKeyboardButton(text='1 год (1080 руб.)', callback_data=f'renew_plan|12|{record["client_id"]}')
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance'),
|
||||
InlineKeyboardButton(text='Назад', callback_data='back_to_main')
|
||||
]
|
||||
])
|
||||
|
||||
message = f"Ваш ключ <b>{email}</b> истечет и будет удален через <b>{hours_left} часов</b> (<b>{expiry_date}</b>). Пожалуйста, продлите его."
|
||||
message = (f"Ваш ключ <b>{email}</b> истечет и будет удален через <b>{hours_left} часов</b> "
|
||||
f"(<b>{expiry_date}</b>). Пожалуйста, продлите его.\n"
|
||||
f"Ваш текущий баланс: <b>{balance:.2f} руб.</b>")
|
||||
|
||||
try:
|
||||
await bot.send_message(chat_id=tg_id, text=message, parse_mode='HTML', reply_markup=keyboard)
|
||||
|
||||
# Обновляем статус уведомления в базе данных
|
||||
await conn.execute('UPDATE keys SET notified = TRUE WHERE client_id = $1', record['client_id'])
|
||||
except Exception as e:
|
||||
print(f"Ошибка при отправке сообщения пользователю {tg_id}: {e}. Пропускаем этого пользователя.")
|
||||
|
||||
# Обрабатываем истекшие ключи
|
||||
expired_records = await conn.fetch('''
|
||||
SELECT tg_id, email, client_id, server_id FROM keys
|
||||
WHERE expiry_time <= $1
|
||||
@@ -67,18 +74,13 @@ async def notify_expiring_keys(bot: Bot):
|
||||
tg_id = record['tg_id']
|
||||
email = record['email']
|
||||
client_id = record['client_id']
|
||||
server_id = record['server_id'] # Получаем server_id из записи
|
||||
server_id = record['server_id']
|
||||
|
||||
# Удаляем ключ из базы данных
|
||||
await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id)
|
||||
|
||||
# Создаем сессию с использованием учетных данных
|
||||
session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
|
||||
# Удаляем клиента из панели
|
||||
delete_client(session, server_id, client_id)
|
||||
|
||||
# Создаем клавиатуру с кнопкой "В профиль"
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text='В профиль', callback_data='view_profile')]
|
||||
])
|
||||
@@ -95,14 +97,13 @@ async def notify_expiring_keys(bot: Bot):
|
||||
except Exception as e:
|
||||
print(f"Ошибка при отправке уведомлений: {e}")
|
||||
|
||||
|
||||
@router.message(Command('send_to_all'))
|
||||
async def send_message_to_all_clients(message: types.Message):
|
||||
# Проверяем, является ли отправитель администратором
|
||||
if message.from_user.id != ADMIN_ID: # Замените ADMIN_ID на ID вашего администратора
|
||||
if message.from_user.id != ADMIN_ID:
|
||||
await message.answer("У вас нет прав для выполнения этой команды.")
|
||||
return
|
||||
|
||||
# Получаем текст сообщения
|
||||
text = message.get_args()
|
||||
if not text:
|
||||
await message.answer("Пожалуйста, введите текст сообщения после команды.")
|
||||
@@ -110,7 +111,6 @@ async def send_message_to_all_clients(message: types.Message):
|
||||
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
# Получаем все tg_id клиентов
|
||||
tg_ids = await conn.fetch('SELECT tg_id FROM keys')
|
||||
|
||||
for record in tg_ids:
|
||||
@@ -125,4 +125,4 @@ async def send_message_to_all_clients(message: types.Message):
|
||||
print(f"Ошибка при подключении к базе данных: {e}")
|
||||
await message.answer("Произошла ошибка при отправке сообщения.")
|
||||
finally:
|
||||
await conn.close()
|
||||
await conn.close()
|
||||
|
||||
+2
-8
@@ -6,7 +6,7 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiohttp import web
|
||||
from yookassa import Configuration, Payment # Импортируем ЮKассу
|
||||
from yookassa import Configuration, Payment
|
||||
|
||||
from bot import bot
|
||||
from config import YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID
|
||||
@@ -18,7 +18,6 @@ router = Router()
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
# Настройка конфигурации ЮKассы
|
||||
Configuration.account_id = YOOKASSA_SHOP_ID
|
||||
Configuration.secret_key = YOOKASSA_SECRET_KEY
|
||||
|
||||
@@ -51,17 +50,13 @@ async def send_message_with_deletion(chat_id, text, reply_markup=None, state=Non
|
||||
async def process_callback_replenish_balance(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
# Проверяем, есть ли у пользователя ключи
|
||||
key_count = await get_key_count(tg_id)
|
||||
|
||||
# Если ключей нет, проверяем, существует ли запись с таким tg_id
|
||||
if key_count == 0:
|
||||
exists = await check_connection_exists(tg_id)
|
||||
# Если записи нет, создаем нового клиента в базе данных
|
||||
if not exists:
|
||||
await add_connection(tg_id, balance=0.0, trial=0)
|
||||
|
||||
# Переходим сразу к выбору суммы пополнения
|
||||
amount_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text='100 RUB', callback_data='amount_100'), InlineKeyboardButton(text='300 RUB', callback_data='amount_300')],
|
||||
[InlineKeyboardButton(text='600 RUB', callback_data='amount_600'), InlineKeyboardButton(text='1000 RUB', callback_data='amount_1000')],
|
||||
@@ -101,7 +96,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
customer_name = callback_query.from_user.full_name
|
||||
customer_id = callback_query.from_user.id
|
||||
|
||||
# Создаем платеж с чеком для самозанятых
|
||||
payment = Payment.create({
|
||||
"amount": {
|
||||
"value": str(amount),
|
||||
@@ -109,7 +103,7 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
||||
},
|
||||
"confirmation": {
|
||||
"type": "redirect",
|
||||
"return_url": "https://pocomacho.ru/" # Укажите реальный URL для возврата
|
||||
"return_url": "https://pocomacho.ru/"
|
||||
},
|
||||
"capture": True,
|
||||
"description": "Пополнение баланса",
|
||||
|
||||
+5
-11
@@ -3,7 +3,7 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
|
||||
from bot import bot # Импортируем объект bot
|
||||
from bot import bot
|
||||
from database import get_balance, get_key_count
|
||||
|
||||
router = Router()
|
||||
@@ -14,25 +14,21 @@ class ReplenishBalanceState(StatesGroup):
|
||||
|
||||
async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
username = callback_query.from_user.full_name # Получаем имя пользователя
|
||||
username = callback_query.from_user.full_name
|
||||
|
||||
try:
|
||||
# Получаем количество ключей
|
||||
key_count = await get_key_count(tg_id)
|
||||
|
||||
# Получаем баланс
|
||||
balance = await get_balance(tg_id)
|
||||
if balance is None:
|
||||
balance = 0 # По умолчанию 0, если баланс неизвестен
|
||||
balance = 0
|
||||
|
||||
profile_message = (
|
||||
f"<b>Профиль: {username}</b>\n\n"
|
||||
f"🔹 <b>ID:</b> {tg_id}\n"
|
||||
f"🔹 <b>Баланс:</b> {balance} RUB\n"
|
||||
f"🔹 <b>К-во устройств:</b> {key_count}\n" # Изменили текст на "К-во устройств"
|
||||
f"🔹 <b>К-во устройств:</b> {key_count}\n"
|
||||
)
|
||||
|
||||
# Кнопки для действий в профиле с добавленными смайликами
|
||||
button_create_key = InlineKeyboardButton(text='➕ Устройство', callback_data='create_key')
|
||||
button_view_keys = InlineKeyboardButton(text='📱 Мои устройства', callback_data='view_keys')
|
||||
button_replenish_balance = InlineKeyboardButton(text='💳 Пополнить баланс', callback_data='replenish_balance')
|
||||
@@ -49,12 +45,10 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta
|
||||
profile_message = f"❗️ Ошибка при получении данных профиля: {e}"
|
||||
keyboard = None
|
||||
|
||||
# Удаляем текущее сообщение
|
||||
await callback_query.message.delete()
|
||||
|
||||
# Отправляем новое сообщение с профилем через bot
|
||||
await bot.send_message(
|
||||
chat_id=tg_id, # ID чата, куда отправляем сообщение
|
||||
chat_id=tg_id,
|
||||
text=profile_message,
|
||||
parse_mode='HTML',
|
||||
reply_markup=keyboard
|
||||
|
||||
+4
-13
@@ -7,8 +7,7 @@ from aiogram.types import (BufferedInputFile, CallbackQuery,
|
||||
InlineKeyboardButton, InlineKeyboardMarkup, Message)
|
||||
|
||||
from bot import bot
|
||||
from config import (CHANNEL_URL,
|
||||
SUPPORT_CHAT_URL)
|
||||
from config import CHANNEL_URL, SUPPORT_CHAT_URL
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -16,22 +15,18 @@ class FeedbackState(StatesGroup):
|
||||
waiting_for_feedback = State()
|
||||
|
||||
async def send_welcome_message(chat_id: int):
|
||||
# Новый текст приветствия
|
||||
welcome_text = (
|
||||
"*SoloNet — ваш доступ в свободный интернет! 🌐✨*\n\n"
|
||||
"Используйте надежный и быстрый VPN, который гарантирует вашу безопасность даже в самых строгих условиях. 🔒🚀\n\n"
|
||||
"*ver. 1.0*"
|
||||
)
|
||||
|
||||
# Путь к изображению
|
||||
image_path = os.path.join(os.path.dirname(__file__), 'pic.jpg')
|
||||
|
||||
# Проверка существования файла
|
||||
if not os.path.isfile(image_path):
|
||||
await bot.send_message(chat_id, "Файл изображения не найден.")
|
||||
return
|
||||
|
||||
# Создаем inline-клавиатуру
|
||||
inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')],
|
||||
[InlineKeyboardButton(text='🔒 О VPN', callback_data='about_vpn')],
|
||||
@@ -39,14 +34,13 @@ async def send_welcome_message(chat_id: int):
|
||||
[InlineKeyboardButton(text='📢 Наш канал', url=CHANNEL_URL)]
|
||||
])
|
||||
|
||||
# Отправляем изображение с инлайн-клавиатурой
|
||||
with open(image_path, 'rb') as image_from_buffer:
|
||||
await bot.send_photo(
|
||||
chat_id,
|
||||
BufferedInputFile(image_from_buffer.read(), filename="pic.jpg"),
|
||||
caption=welcome_text,
|
||||
parse_mode='Markdown',
|
||||
reply_markup=inline_keyboard # Inline-клавиатура
|
||||
reply_markup=inline_keyboard
|
||||
)
|
||||
|
||||
@router.message(Command('start'))
|
||||
@@ -55,9 +49,8 @@ async def start_command(message: Message):
|
||||
|
||||
@router.callback_query(lambda c: c.data == 'about_vpn')
|
||||
async def handle_about_vpn(callback_query: CallbackQuery):
|
||||
# Удаляем сообщение главного меню
|
||||
await callback_query.message.delete()
|
||||
|
||||
await callback_query.message.delete()
|
||||
info_message = (
|
||||
"*О VPN*\n\n"
|
||||
"Мы используем высокоскоростные серверы в разных локациях и выдаём ключ каждому индивидуально. "
|
||||
@@ -78,9 +71,7 @@ async def handle_about_vpn(callback_query: CallbackQuery):
|
||||
|
||||
@router.callback_query(lambda c: c.data == 'back_to_menu')
|
||||
async def handle_back_to_menu(callback_query: CallbackQuery):
|
||||
# Удаляем текущее сообщение
|
||||
|
||||
await callback_query.message.delete()
|
||||
|
||||
# Отправляем приветственное сообщение
|
||||
await send_welcome_message(callback_query.from_user.id)
|
||||
await callback_query.answer()
|
||||
|
||||
Reference in New Issue
Block a user