@@ -1,15 +1,15 @@
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
from config import API_URL, GET_INBOUNDS_URL
|
||||
from config import SERVERS # Импортируем SERVERS из config.py
|
||||
|
||||
session = None
|
||||
|
||||
def login_with_credentials(username, password):
|
||||
def login_with_credentials(server_id: str, username: str, password: str):
|
||||
global session
|
||||
session = requests.Session()
|
||||
auth_url = f"{API_URL}/login/"
|
||||
api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера
|
||||
auth_url = f"{api_url}/login/"
|
||||
data = {
|
||||
"username": username,
|
||||
"password": password
|
||||
@@ -21,22 +21,23 @@ def login_with_credentials(username, password):
|
||||
else:
|
||||
raise Exception(f"Ошибка авторизации: {response.status_code}, {response.text}")
|
||||
|
||||
|
||||
def get_clients(session):
|
||||
response = session.get(GET_INBOUNDS_URL)
|
||||
def get_clients(session, server_id):
|
||||
api_url = SERVERS[server_id]['API_URL'] # Получаем GET_INBOUNDS_URL для выбранного сервера
|
||||
response = session.get(f'{api_url}/panel/api/inbounds/list/')
|
||||
if response.status_code == 200:
|
||||
return response.json() # Возвращает данные по инбаундам и клиентам
|
||||
else:
|
||||
raise Exception(f"Ошибка при получении клиентов: {response.status_code}, {response.text}")
|
||||
|
||||
def link(session, client_id: str, email: str):
|
||||
def link(session, server_id: str, client_id: str, email: str):
|
||||
"""
|
||||
Получение ссылки для подключения по ID клиента.
|
||||
:param session: requests.Session - авторизованная сессия
|
||||
:param server_id: str - идентификатор сервера
|
||||
:param client_id: str - идентификатор клиента
|
||||
:param email: str - электронная почта клиента
|
||||
:return: str - ссылка для подключения
|
||||
"""
|
||||
response = get_clients(session)
|
||||
response = get_clients(session, server_id)
|
||||
|
||||
if 'obj' not in response or len(response['obj']) == 0:
|
||||
raise Exception("Не удалось получить данные клиентов.")
|
||||
@@ -51,6 +52,5 @@ def link(session, client_id: str, email: str):
|
||||
flow = stream_settings.get('flow', 'xtls-rprx-vision')
|
||||
|
||||
# Создание ссылки для подключения VLESS
|
||||
val = f"vless://{client_id}@solonet.pocomacho.ru:443?type={tcp}&security={reality}&pbk=Ik3cfHdM5RXYCuzTxhaFfo58Fzwjvw11AtKsz5-izxA&fp=chrome&sni=discord.com&sid=6fc9&spx=%2F&flow={flow}#SoloNet_DE-{email}"
|
||||
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
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import json
|
||||
from config import SERVERS # Импортируем SERVERS из config.py
|
||||
|
||||
from config import API_URL
|
||||
|
||||
|
||||
def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, total_gb: int, expiry_time: int, enable: bool, flow: str):
|
||||
url = f'{API_URL}/panel/api/inbounds/addClient'
|
||||
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 для выбранного сервера
|
||||
url = f'{api_url}/panel/api/inbounds/addClient'
|
||||
|
||||
email = email.lower()
|
||||
|
||||
@@ -44,8 +43,9 @@ def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, t
|
||||
else:
|
||||
print(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}")
|
||||
|
||||
def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: int) -> bool:
|
||||
response = session.get(f"{API_URL}/panel/api/inbounds/getClientTraffics/{email}")
|
||||
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 для выбранного сервера
|
||||
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}")
|
||||
|
||||
@@ -93,7 +93,7 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in
|
||||
}
|
||||
|
||||
try:
|
||||
response = session.post(f"{API_URL}/panel/api/inbounds/updateClient/{client_id}", json=payload, headers=headers)
|
||||
response = session.post(f"{api_url}/panel/api/inbounds/updateClient/{client_id}", json=payload, headers=headers)
|
||||
print(f"POST {response.url} Status: {response.status_code}")
|
||||
print(f"POST Request Data: {json.dumps(payload, indent=2)}")
|
||||
print(f"POST Response: {response.text}")
|
||||
@@ -107,8 +107,9 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in
|
||||
print(f"Ошибка запроса: {e}")
|
||||
return False
|
||||
|
||||
def delete_client(session, client_id: str) -> bool:
|
||||
url = f"{API_URL}/panel/api/inbounds/1/delClient/{client_id}"
|
||||
def delete_client(session, server_id: str, client_id: str) -> bool:
|
||||
api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера
|
||||
url = f"{api_url}/panel/api/inbounds/1/delClient/{client_id}"
|
||||
headers = {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
@@ -121,4 +122,4 @@ def delete_client(session, client_id: str) -> bool:
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Ошибка запроса: {e}")
|
||||
return False
|
||||
return False
|
||||
|
||||
+27
-5
@@ -4,9 +4,9 @@ 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,
|
||||
@@ -14,6 +14,7 @@ async def init_db():
|
||||
trial INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
''')
|
||||
# Создаем таблицу keys, если она не существует
|
||||
await conn.execute('''
|
||||
CREATE TABLE IF NOT EXISTS keys (
|
||||
tg_id BIGINT NOT NULL,
|
||||
@@ -22,11 +23,22 @@ async def init_db():
|
||||
created_at BIGINT NOT NULL,
|
||||
expiry_time BIGINT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
server_id TEXT NOT NULL DEFAULT 'server1', -- новое поле для идентификатора сервера
|
||||
PRIMARY KEY (tg_id, client_id)
|
||||
)
|
||||
''')
|
||||
# Добавляем поле server_id в таблицу keys, если его нет
|
||||
try:
|
||||
await conn.execute('''
|
||||
ALTER TABLE keys
|
||||
ADD COLUMN server_id TEXT NOT NULL DEFAULT 'server1'
|
||||
''')
|
||||
except asyncpg.exceptions.DuplicateColumnError:
|
||||
# Если поле уже существует, ничего не делаем
|
||||
pass
|
||||
await conn.close()
|
||||
|
||||
|
||||
async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
await conn.execute('''
|
||||
@@ -43,12 +55,12 @@ async def check_connection_exists(tg_id: int):
|
||||
await conn.close()
|
||||
return exists
|
||||
|
||||
async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str):
|
||||
async def store_key(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('''
|
||||
INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
''', tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), expiry_time, key)
|
||||
INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key, server_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
''', tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), expiry_time, key, server_id)
|
||||
await conn.close()
|
||||
|
||||
async def get_keys(tg_id: int):
|
||||
@@ -61,6 +73,16 @@ async def get_keys(tg_id: int):
|
||||
await conn.close()
|
||||
return records
|
||||
|
||||
async def get_keys_by_server(tg_id: int, server_id: str):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
records = await conn.fetch('''
|
||||
SELECT client_id, email, created_at, key
|
||||
FROM keys
|
||||
WHERE tg_id = $1 AND server_id = $2
|
||||
''', tg_id, server_id)
|
||||
await conn.close()
|
||||
return records
|
||||
|
||||
async def has_active_key(tg_id: int) -> bool:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
count = await conn.fetchval("SELECT COUNT(*) FROM keys WHERE tg_id = $1", tg_id)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+42
-10
@@ -10,11 +10,11 @@ from aiogram.types import (CallbackQuery, InlineKeyboardButton,
|
||||
InlineKeyboardMarkup, Message)
|
||||
|
||||
from auth import link, login_with_credentials
|
||||
from bot import bot, dp
|
||||
from bot import dp
|
||||
from client import add_client
|
||||
from config import (ADMIN_ID, ADMIN_PASSWORD, ADMIN_USERNAME, API_TOKEN,
|
||||
DATABASE_URL)
|
||||
from database import (add_connection, get_balance, has_active_key, store_key,
|
||||
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.profile import process_callback_view_profile
|
||||
@@ -28,6 +28,7 @@ def sanitize_key_name(key_name: str) -> str:
|
||||
return re.sub(r'[^a-z0-9@._-]', '', key_name.lower())
|
||||
|
||||
class Form(StatesGroup):
|
||||
waiting_for_server_selection = State()
|
||||
waiting_for_key_name = State()
|
||||
viewing_profile = State()
|
||||
|
||||
@@ -35,10 +36,38 @@ 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 # Заполнение в процентах
|
||||
server_name = f"{server['name']} ({percent_full:.1f}%)"
|
||||
server_buttons.append([InlineKeyboardButton(text=server_name, callback_data=f'select_server|{server_id}')])
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
"<b>⚙️ Выберите сервер для создания ключа:</b>",
|
||||
parse_mode="HTML",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=server_buttons)
|
||||
)
|
||||
|
||||
await state.set_state(Form.waiting_for_server_selection)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
@dp.callback_query(F.data.startswith('select_server|'))
|
||||
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', tg_id)
|
||||
existing_connection = await conn.fetchrow('SELECT trial FROM connections WHERE tg_id = $1', callback_query.from_user.id)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
@@ -69,6 +98,8 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC
|
||||
@dp.callback_query(F.data == 'confirm_create_new_key')
|
||||
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.from_user.id
|
||||
data = await state.get_data()
|
||||
server_id = data.get('selected_server_id')
|
||||
|
||||
# Проверяем баланс перед созданием нового ключа
|
||||
balance = await get_balance(tg_id)
|
||||
@@ -131,8 +162,9 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
data = await state.get_data()
|
||||
creating_new_key = data.get('creating_new_key', False)
|
||||
server_id = data.get('selected_server_id')
|
||||
|
||||
session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
client_id = str(uuid.uuid4())
|
||||
email = key_name.lower()
|
||||
current_time = datetime.utcnow()
|
||||
@@ -167,7 +199,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
|
||||
try:
|
||||
# Попробуем добавить клиента
|
||||
response = add_client(session, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_timestamp, enable=True, flow="xtls-rprx-vision")
|
||||
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):
|
||||
error_msg = response.get("msg", "Неизвестная ошибка.")
|
||||
@@ -178,7 +210,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
else:
|
||||
raise Exception(error_msg)
|
||||
|
||||
connection_link = link(session, client_id, email)
|
||||
connection_link = link(session, server_id, client_id, email)
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
@@ -191,7 +223,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
await store_key(tg_id, client_id, email, expiry_timestamp, connection_link)
|
||||
await store_key(tg_id, client_id, email, expiry_timestamp, connection_link, server_id)
|
||||
|
||||
# Рассчитываем оставшееся время до окончания действия ключа
|
||||
remaining_time = expiry_time - current_time
|
||||
@@ -230,4 +262,4 @@ 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):
|
||||
await process_callback_view_profile(callback_query, state)
|
||||
await callback_query.answer()
|
||||
await callback_query.answer()
|
||||
|
||||
+119
-16
@@ -3,10 +3,10 @@ from datetime import datetime, timedelta
|
||||
import asyncpg
|
||||
from aiogram import Router, types
|
||||
|
||||
from auth import login_with_credentials
|
||||
from auth import login_with_credentials, link
|
||||
from bot import bot
|
||||
from client import delete_client, extend_client_key
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||
from client import delete_client, extend_client_key, add_client
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS
|
||||
from database import get_balance, update_balance
|
||||
|
||||
router = Router()
|
||||
@@ -69,7 +69,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow('''
|
||||
SELECT k.key, k.expiry_time
|
||||
SELECT k.key, k.expiry_time, k.server_id
|
||||
FROM keys k
|
||||
WHERE k.tg_id = $1 AND k.email = $2
|
||||
''', tg_id, key_name)
|
||||
@@ -77,6 +77,11 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
if record:
|
||||
key = record['key']
|
||||
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)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
@@ -91,14 +96,24 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||
|
||||
response_message = (f"🔑 <b>Ваш ключ:</b>\n<pre>{key}</pre>\n"
|
||||
f"📅 <b>Дата окончания:</b> {expiry_date.strftime('%Y-%m-%d %H:%M:%S')}\n"
|
||||
f"{days_left_message}")
|
||||
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}')
|
||||
change_location_button = types.InlineKeyboardButton(text='🌍 Сменить локацию', callback_data=f'change_location|{client_id}')
|
||||
back_button = types.InlineKeyboardButton(text='🔙 Назад в профиль', callback_data='view_profile')
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [delete_button], [back_button]])
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[instructions_button], # Инструкции отдельной строкой
|
||||
[renew_button, delete_button], # Продлить и Удалить в одном ряду
|
||||
[change_location_button], # Сменить локацию отдельной строкой
|
||||
[back_button] # Назад отдельной строкой
|
||||
]
|
||||
)
|
||||
|
||||
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML")
|
||||
else:
|
||||
@@ -112,6 +127,7 @@ 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):
|
||||
@@ -167,7 +183,6 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
@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
|
||||
@@ -176,13 +191,16 @@ 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)
|
||||
# Извлекаем 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 из записи
|
||||
|
||||
session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
success = delete_client(session, client_id)
|
||||
# Используем server_id для авторизации
|
||||
session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
success = delete_client(session, server_id, client_id)
|
||||
|
||||
if success:
|
||||
await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id)
|
||||
@@ -215,11 +233,13 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
record = await conn.fetchrow('SELECT email, expiry_time FROM keys WHERE client_id = $1', client_id)
|
||||
# Извлекаем 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 # Текущее время в миллисекундах
|
||||
|
||||
# Проверяем, если ключ истек, то продлеваем от текущей даты, иначе продлеваем от текущей даты истечения
|
||||
@@ -247,9 +267,9 @@ 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
|
||||
session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
success = extend_client_key(session, tg_id, client_id, email, new_expiry_time)
|
||||
# Продлеваем ключ через 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:
|
||||
# Обновляем баланс и ключ в базе данных
|
||||
@@ -272,6 +292,89 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@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] # Используем разделитель вертикальная черта
|
||||
|
||||
# Получаем количество подключений для каждого сервера
|
||||
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 # Заполнение в процентах
|
||||
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:
|
||||
await conn.close()
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=server_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=keyboard, parse_mode="HTML")
|
||||
await callback_query.answer()
|
||||
|
||||
@router.callback_query(lambda c: c.data.startswith('select_server&'))
|
||||
async def process_callback_select_server(callback_query: types.CallbackQuery):
|
||||
tg_id = callback_query.from_user.id
|
||||
server_id, client_id = callback_query.data.split('&')[1], callback_query.data.split('&')[2]
|
||||
|
||||
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:
|
||||
response_message = ("Ключ успешно перемещен на новый сервер.\n\n"
|
||||
"<b>Не забудьте удалить старый ключ из вашего приложения и установить новый.<b>")
|
||||
else:
|
||||
response_message = "Ошибка при удалении ключа с текущего сервера."
|
||||
else:
|
||||
response_message = "Ошибка при создании клиента на новом сервере."
|
||||
|
||||
else:
|
||||
response_message = "Ключ не найден или уже удален."
|
||||
|
||||
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys')
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
await bot.edit_message_text(f"Ошибка при смене локации: {e}", chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
@@ -24,7 +24,7 @@ async def notify_expiring_keys(bot: Bot):
|
||||
# Получаем все ключи, которые истекают в течение следующих 10 часов
|
||||
threshold_time = (datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000 # В миллисекундах
|
||||
records = await conn.fetch('''
|
||||
SELECT tg_id, email, expiry_time, client_id FROM keys
|
||||
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
|
||||
WHERE expiry_time <= $1 AND expiry_time > $2
|
||||
''', threshold_time, datetime.utcnow().timestamp() * 1000)
|
||||
|
||||
@@ -32,6 +32,7 @@ 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 из записи
|
||||
|
||||
# Рассчитываем оставшееся время и уменьшаем его на 3 часа
|
||||
time_left = (expiry_time / 1000) - datetime.utcnow().timestamp()
|
||||
@@ -55,7 +56,7 @@ async def notify_expiring_keys(bot: Bot):
|
||||
|
||||
# Обрабатываем истекшие ключи
|
||||
expired_records = await conn.fetch('''
|
||||
SELECT tg_id, email, client_id FROM keys
|
||||
SELECT tg_id, email, client_id, server_id FROM keys
|
||||
WHERE expiry_time <= $1
|
||||
''', datetime.utcnow().timestamp() * 1000)
|
||||
|
||||
@@ -63,15 +64,16 @@ 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 из записи
|
||||
|
||||
# Удаляем ключ из базы данных
|
||||
await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id)
|
||||
|
||||
# Создаем сессию с использованием учетных данных
|
||||
session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||
|
||||
# Удаляем клиента из панели
|
||||
delete_client(session, client_id)
|
||||
delete_client(session, server_id, client_id)
|
||||
|
||||
# Создаем клавиатуру с кнопкой "В профиль"
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
@@ -90,6 +92,7 @@ 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):
|
||||
# Проверяем, является ли отправитель администратором
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
import asyncpg
|
||||
from aiogram import Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ from aiogram.types import (BufferedInputFile, CallbackQuery,
|
||||
InlineKeyboardButton, InlineKeyboardMarkup, Message)
|
||||
|
||||
from bot import bot
|
||||
from config import (ADMIN_ID, CHANNEL_URL,
|
||||
from config import (CHANNEL_URL,
|
||||
SUPPORT_CHAT_URL)
|
||||
|
||||
router = Router()
|
||||
|
||||
Reference in New Issue
Block a user