servers in database
This commit is contained in:
+47
-1
@@ -118,6 +118,20 @@ async def init_db():
|
||||
"""
|
||||
)
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS servers (
|
||||
id SERIAL PRIMARY KEY,
|
||||
cluster_name TEXT NOT NULL,
|
||||
server_name TEXT NOT NULL,
|
||||
api_url TEXT NOT NULL,
|
||||
subscription_url TEXT NOT NULL,
|
||||
inbound_id TEXT NOT NULL,
|
||||
UNIQUE(cluster_name, server_name) -- Уникальность по названию кластера и сервера
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
await conn.close()
|
||||
|
||||
|
||||
@@ -1163,8 +1177,40 @@ async def check_notification_time(tg_id: int, notification_type: str, hours: int
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при проверке времени уведомления для пользователя {tg_id}: {e}")
|
||||
return False
|
||||
return False
|
||||
|
||||
finally:
|
||||
if conn is not None and session is None:
|
||||
await conn.close()
|
||||
|
||||
|
||||
async def get_servers_from_db():
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
|
||||
# Запрос к базе данных для получения всех серверов
|
||||
result = await conn.fetch(
|
||||
"""
|
||||
SELECT cluster_name, server_name, api_url, subscription_url, inbound_id
|
||||
FROM servers
|
||||
"""
|
||||
)
|
||||
|
||||
await conn.close()
|
||||
|
||||
# Преобразуем результат в удобный формат
|
||||
servers = {}
|
||||
for row in result:
|
||||
cluster_name = row['cluster_name']
|
||||
if cluster_name not in servers:
|
||||
servers[cluster_name] = []
|
||||
|
||||
servers[cluster_name].append(
|
||||
{
|
||||
'server_name': row['server_name'],
|
||||
'api_url': row['api_url'],
|
||||
'subscription_url': row['subscription_url'],
|
||||
'inbound_id': row['inbound_id'],
|
||||
}
|
||||
)
|
||||
|
||||
return servers
|
||||
|
||||
@@ -29,7 +29,7 @@ from database import (
|
||||
use_trial,
|
||||
)
|
||||
from handlers.keys.key_utils import create_key_on_cluster
|
||||
from handlers.texts import KEY, KEY_TRIAL, NULL_BALANCE, key_message_success
|
||||
from handlers.texts import KEY, NULL_BALANCE, key_message_success
|
||||
from handlers.utils import generate_random_email, get_least_loaded_cluster
|
||||
from logger import logger
|
||||
|
||||
@@ -53,7 +53,6 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC
|
||||
async def select_server(callback_query: CallbackQuery, state: FSMContext, session: Any):
|
||||
trial_status = await get_trial(callback_query.message.chat.id, session)
|
||||
if trial_status == 1:
|
||||
# Если триал есть, бот предлагает подключить новое устройство
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="✅ Да, подключить новое устройство", callback_data="confirm_create_new_key")
|
||||
|
||||
+34
-25
@@ -4,28 +4,32 @@ import asyncpg
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import add_client, delete_client, extend_client_key
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, TOTAL_GB
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, TOTAL_GB
|
||||
from database import get_servers_from_db
|
||||
from logger import logger
|
||||
|
||||
|
||||
async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_timestamp):
|
||||
try:
|
||||
tasks = []
|
||||
cluster = CLUSTERS.get(cluster_id)
|
||||
servers = await get_servers_from_db()
|
||||
cluster = servers.get(cluster_id)
|
||||
|
||||
if not cluster:
|
||||
raise ValueError(f"Кластер с ID {cluster_id} не найден.")
|
||||
|
||||
for server_info in cluster.values():
|
||||
for server_info in cluster:
|
||||
xui = AsyncApi(
|
||||
server_info["API_URL"],
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("INBOUND_ID")
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
if not inbound_id:
|
||||
logger.warning(f"INBOUND_ID отсутствует для сервера {server_info.get('name', 'unknown')}. Пропуск.")
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_info.get('server_name', 'unknown')}. Пропуск."
|
||||
)
|
||||
continue
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
@@ -58,27 +62,26 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
|
||||
|
||||
|
||||
async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, total_gb):
|
||||
"""
|
||||
Функция для продления срока действия ключа на всех серверах в кластере
|
||||
и обновления объема трафика.
|
||||
"""
|
||||
try:
|
||||
cluster = CLUSTERS.get(cluster_id)
|
||||
servers = await get_servers_from_db()
|
||||
cluster = servers.get(cluster_id)
|
||||
|
||||
if not cluster:
|
||||
raise ValueError(f"Кластер с ID {cluster_id} не найден.")
|
||||
|
||||
tasks = []
|
||||
for server_info in cluster.values():
|
||||
for server_info in cluster:
|
||||
xui = AsyncApi(
|
||||
server_info["API_URL"],
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("INBOUND_ID")
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
if not inbound_id:
|
||||
logger.warning(f"INBOUND_ID отсутствует для сервера {server_info.get('name', 'unknown')}. Пропуск.")
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_info.get('server_name', 'unknown')}. Пропуск."
|
||||
)
|
||||
continue
|
||||
|
||||
tasks.append(
|
||||
@@ -109,22 +112,25 @@ async def delete_key_from_db(client_id, session):
|
||||
async def delete_key_from_cluster(cluster_id, email, client_id):
|
||||
"""Удаление ключа с серверов в кластере"""
|
||||
try:
|
||||
cluster = CLUSTERS.get(cluster_id)
|
||||
servers = await get_servers_from_db()
|
||||
cluster = servers.get(cluster_id)
|
||||
|
||||
if not cluster:
|
||||
raise ValueError(f"Кластер с ID {cluster_id} не найден.")
|
||||
|
||||
tasks = []
|
||||
for server_info in cluster.values():
|
||||
for server_info in cluster:
|
||||
xui = AsyncApi(
|
||||
server_info["API_URL"],
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("INBOUND_ID")
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
if not inbound_id:
|
||||
logger.warning(f"INBOUND_ID отсутствует для сервера {server_info.get('name', 'unknown')}. Пропуск.")
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_info.get('server_name', 'unknown')}. Пропуск."
|
||||
)
|
||||
continue
|
||||
|
||||
tasks.append(
|
||||
@@ -145,22 +151,25 @@ async def delete_key_from_cluster(cluster_id, email, client_id):
|
||||
|
||||
async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id):
|
||||
try:
|
||||
cluster = CLUSTERS.get(cluster_id)
|
||||
servers = await get_servers_from_db()
|
||||
cluster = servers.get(cluster_id)
|
||||
|
||||
if not cluster:
|
||||
raise ValueError(f"Кластер с ID {cluster_id} не найден.")
|
||||
|
||||
tasks = []
|
||||
for server_info in cluster.values():
|
||||
for server_info in cluster:
|
||||
xui = AsyncApi(
|
||||
server_info["API_URL"],
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("INBOUND_ID")
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
if not inbound_id:
|
||||
logger.warning(f"INBOUND_ID отсутствует для сервера {server_info.get('name', 'unknown')}. Пропуск.")
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_info.get('server_name', 'unknown')}. Пропуск."
|
||||
)
|
||||
continue
|
||||
|
||||
tasks.append(
|
||||
|
||||
+8
-13
@@ -8,17 +8,8 @@ from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import (
|
||||
CLUSTERS,
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
PUBLIC_LINK,
|
||||
RENEWAL_PLANS,
|
||||
TOTAL_GB,
|
||||
)
|
||||
from database import delete_key, get_balance, store_key, update_balance, update_key_expiry
|
||||
from config import CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, RENEWAL_PLANS, TOTAL_GB
|
||||
from database import delete_key, get_balance, get_servers_from_db, store_key, update_balance, update_key_expiry
|
||||
from handlers.keys.key_utils import (
|
||||
delete_key_from_cluster,
|
||||
delete_key_from_db,
|
||||
@@ -362,10 +353,12 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery, s
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
async def delete_key_from_servers():
|
||||
try:
|
||||
tasks = []
|
||||
for cluster_id, cluster in CLUSTERS.items():
|
||||
for cluster_id, cluster in servers.items():
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
@@ -438,9 +431,11 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery, sessi
|
||||
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
async def renew_key_on_servers():
|
||||
tasks = []
|
||||
for cluster_id in CLUSTERS:
|
||||
for cluster_id in servers:
|
||||
task = asyncio.create_task(
|
||||
renew_key_in_cluster(
|
||||
cluster_id,
|
||||
|
||||
@@ -5,7 +5,8 @@ import aiohttp
|
||||
from aiohttp import web
|
||||
import asyncpg
|
||||
|
||||
from config import CLUSTERS, DATABASE_URL, TRANSITION_DATE_STR
|
||||
from config import DATABASE_URL, TRANSITION_DATE_STR
|
||||
from database import get_servers_from_db
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -81,8 +82,6 @@ async def handle_old_subscription(request):
|
||||
created_at_datetime = datetime.utcfromtimestamp(created_at_ms / 1000)
|
||||
logger.info(f"Время создания клиента в формате datetime (UTC): {created_at_datetime}")
|
||||
|
||||
logger.info(f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}")
|
||||
|
||||
if created_at_ms >= transition_timestamp_ms_adjusted:
|
||||
logger.info(f"Клиент с email {email} является новым.")
|
||||
return web.Response(
|
||||
@@ -90,10 +89,12 @@ async def handle_old_subscription(request):
|
||||
status=400,
|
||||
)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
urls = []
|
||||
for cluster in CLUSTERS.values():
|
||||
for server in cluster.values():
|
||||
server_subscription_url = f"{server['SUBSCRIPTION']}/{email}"
|
||||
for cluster_name, cluster_servers in servers.items():
|
||||
for server in cluster_servers:
|
||||
server_subscription_url = f"{server['subscription_url']}/{email}"
|
||||
urls.append(server_subscription_url)
|
||||
|
||||
combined_subscriptions = await combine_unique_lines(urls, email, "")
|
||||
@@ -149,10 +150,12 @@ async def handle_new_subscription(request):
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
urls = []
|
||||
for cluster in CLUSTERS.values():
|
||||
for server in cluster.values():
|
||||
server_subscription_url = f"{server['SUBSCRIPTION']}/{email}"
|
||||
for cluster_name, cluster_servers in servers.items():
|
||||
for server in cluster_servers:
|
||||
server_subscription_url = f"{server['subscription_url']}/{email}"
|
||||
urls.append(server_subscription_url)
|
||||
|
||||
query_string = request.query_string
|
||||
|
||||
+31
-23
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
import uuid
|
||||
@@ -5,8 +6,8 @@ import uuid
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import add_client
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, PUBLIC_LINK, TOTAL_GB, TRIAL_TIME
|
||||
from database import store_key, use_trial
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, PUBLIC_LINK, TOTAL_GB, TRIAL_TIME
|
||||
from database import get_servers_from_db, store_key, use_trial
|
||||
from handlers.texts import INSTRUCTIONS
|
||||
from handlers.utils import generate_random_email, get_least_loaded_cluster
|
||||
|
||||
@@ -21,31 +22,38 @@ async def create_trial_key(tg_id: int, session: Any):
|
||||
expiry_time = current_time + timedelta(days=TRIAL_TIME)
|
||||
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
||||
|
||||
clusters = await get_servers_from_db()
|
||||
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
|
||||
for server_id, server_info in CLUSTERS[least_loaded_cluster].items():
|
||||
xui = AsyncApi(
|
||||
server_info["API_URL"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
if least_loaded_cluster not in clusters:
|
||||
raise ValueError(f"Кластер {least_loaded_cluster} не найден в базе данных.")
|
||||
|
||||
servers_in_cluster = clusters[least_loaded_cluster]
|
||||
|
||||
tasks = []
|
||||
|
||||
for server_info in servers_in_cluster:
|
||||
tasks.append(
|
||||
add_client(
|
||||
AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
),
|
||||
client_id,
|
||||
email,
|
||||
tg_id,
|
||||
limit_ip=1,
|
||||
total_gb=TOTAL_GB,
|
||||
expiry_time=expiry_timestamp,
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(server_info["inbound_id"]),
|
||||
)
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("INBOUND_ID")
|
||||
if not inbound_id:
|
||||
raise ValueError(f"INBOUND_ID отсутствует для сервера {server_info.get('name', 'unknown')}")
|
||||
|
||||
await add_client(
|
||||
xui,
|
||||
client_id,
|
||||
email,
|
||||
tg_id,
|
||||
limit_ip=1,
|
||||
total_gb=TOTAL_GB,
|
||||
expiry_time=expiry_timestamp,
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(inbound_id),
|
||||
)
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await store_key(
|
||||
tg_id,
|
||||
|
||||
+15
-11
@@ -6,12 +6,13 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
import asyncpg
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, DEV_MODE, RENEWAL_PLANS, TOTAL_GB, TRIAL_TIME
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, DEV_MODE, RENEWAL_PLANS, TOTAL_GB, TRIAL_TIME
|
||||
from database import (
|
||||
add_notification,
|
||||
check_notification_time,
|
||||
delete_key,
|
||||
get_balance,
|
||||
get_servers_from_db,
|
||||
update_balance,
|
||||
update_key_expiry,
|
||||
)
|
||||
@@ -228,9 +229,7 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
username = user.get('username', 'Пользователь')
|
||||
|
||||
try:
|
||||
can_notify = await check_notification_time(
|
||||
tg_id, 'inactive_trial', hours=24, session=conn
|
||||
)
|
||||
can_notify = await check_notification_time(tg_id, 'inactive_trial', hours=24, session=conn)
|
||||
|
||||
if can_notify and not await is_bot_blocked(bot, tg_id):
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -296,7 +295,9 @@ async def process_key(record, bot, conn):
|
||||
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:
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
for cluster_id in servers:
|
||||
await renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, TOTAL_GB)
|
||||
logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.")
|
||||
|
||||
@@ -324,9 +325,10 @@ async def process_key(record, bot, conn):
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления об истечении подписки пользователю {tg_id}: {e}")
|
||||
|
||||
for cluster_id in CLUSTERS:
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
for cluster_id in servers:
|
||||
await delete_key_from_cluster(cluster_id, email, client_id)
|
||||
# await xui.client.delete_depleted(-1)
|
||||
logger.info(f"Клиент {client_id} удален из кластера {cluster_id}.")
|
||||
|
||||
await delete_key(client_id)
|
||||
@@ -337,14 +339,16 @@ async def process_key(record, bot, conn):
|
||||
|
||||
|
||||
async def check_online_users():
|
||||
for cluster_id, cluster in CLUSTERS.items():
|
||||
for server_id, server in cluster.items():
|
||||
xui = AsyncApi(server["API_URL"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD)
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
for cluster_id, cluster in servers.items():
|
||||
for server_id, server in enumerate(cluster):
|
||||
xui = AsyncApi(server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD)
|
||||
await xui.login()
|
||||
try:
|
||||
online_users = len(await xui.client.online())
|
||||
logger.info(
|
||||
f"Сервер '{server['name']}' доступен, текущее количество активных пользователей: {online_users}."
|
||||
f"Сервер '{server['server_name']}' доступен, текущее количество активных пользователей: {online_users}."
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось проверить пользователей на сервере {server_id}: {e}")
|
||||
|
||||
@@ -184,4 +184,4 @@ async def on_successful_payment(
|
||||
await update_balance(user_id, amount)
|
||||
await send_payment_success_notification(user_id, amount)
|
||||
except ValueError as e:
|
||||
logger.error(f"Ошибка конвертации user_id или amount: {e}")
|
||||
logger.error(f"Ошибка конвертации user_id или amount: {e}")
|
||||
|
||||
Reference in New Issue
Block a user