Move keys utils into utils folder
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
import asyncio
|
||||
|
||||
import asyncpg
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from utils.client import add_client, delete_client, extend_client_key
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, TOTAL_GB
|
||||
from utils.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 = []
|
||||
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:
|
||||
xui = AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
if not inbound_id:
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_info.get('server_name', 'unknown')}. Пропуск."
|
||||
)
|
||||
continue
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
existing_key = await conn.fetchrow("SELECT 1 FROM keys WHERE email = $1", email)
|
||||
|
||||
if existing_key:
|
||||
raise ValueError(f"Email {email} уже существует в базе данных.")
|
||||
|
||||
tasks.append(
|
||||
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 conn.close()
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании ключа: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, total_gb):
|
||||
try:
|
||||
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:
|
||||
xui = AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
if not inbound_id:
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_info.get('server_name', 'unknown')}. Пропуск."
|
||||
)
|
||||
continue
|
||||
|
||||
tasks.append(
|
||||
extend_client_key(
|
||||
xui,
|
||||
int(inbound_id),
|
||||
email,
|
||||
new_expiry_time,
|
||||
client_id,
|
||||
total_gb,
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось продлить ключ {client_id} в кластере {cluster_id}: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
async def delete_key_from_db(client_id, session):
|
||||
try:
|
||||
await session.execute("DELETE FROM keys WHERE client_id = $1", client_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
|
||||
|
||||
|
||||
async def delete_key_from_cluster(cluster_id, email, client_id):
|
||||
"""Удаление ключа с серверов в кластере"""
|
||||
try:
|
||||
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:
|
||||
xui = AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
if not inbound_id:
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_info.get('server_name', 'unknown')}. Пропуск."
|
||||
)
|
||||
continue
|
||||
|
||||
tasks.append(
|
||||
delete_client(
|
||||
xui,
|
||||
int(inbound_id),
|
||||
email,
|
||||
client_id,
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить ключ {client_id} в кластере {cluster_id}: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id):
|
||||
try:
|
||||
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:
|
||||
xui = AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
if not inbound_id:
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_info.get('server_name', 'unknown')}. Пропуск."
|
||||
)
|
||||
continue
|
||||
|
||||
tasks.append(
|
||||
add_client(
|
||||
xui,
|
||||
client_id,
|
||||
email,
|
||||
tg_id,
|
||||
limit_ip=1,
|
||||
total_gb=TOTAL_GB,
|
||||
expiry_time=expiry_time,
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(inbound_id),
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
logger.info(f"Ключ успешно обновлен для {client_id} на всех серверах в кластере {cluster_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обновлении ключа на серверах кластера {cluster_id} для {client_id}: {e}")
|
||||
raise e
|
||||
@@ -0,0 +1,176 @@
|
||||
import base64
|
||||
from datetime import datetime
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import web
|
||||
import asyncpg
|
||||
|
||||
from config import DATABASE_URL, TRANSITION_DATE_STR
|
||||
from utils.database import get_servers_from_db
|
||||
from logger import logger
|
||||
|
||||
|
||||
async def fetch_url_content(url, tg_id):
|
||||
try:
|
||||
logger.info(f"Получение URL: {url} для tg_id: {tg_id}")
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, ssl=False) as response:
|
||||
if response.status == 200:
|
||||
content = await response.text()
|
||||
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}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении {url} для tg_id: {tg_id}: {e}")
|
||||
return []
|
||||
|
||||
|
||||
async def combine_unique_lines(urls, tg_id, query_string):
|
||||
all_lines = []
|
||||
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}")
|
||||
|
||||
for url in urls_with_query:
|
||||
lines = await fetch_url_content(url, tg_id)
|
||||
all_lines.extend(lines)
|
||||
|
||||
all_lines = list(set(filter(None, all_lines)))
|
||||
logger.info(f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}")
|
||||
|
||||
return all_lines
|
||||
|
||||
|
||||
transition_date = datetime.strptime(TRANSITION_DATE_STR, "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
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}")
|
||||
|
||||
|
||||
async def handle_old_subscription(request):
|
||||
email = request.match_info.get("email")
|
||||
|
||||
if not email:
|
||||
logger.warning("Получен запрос без email")
|
||||
return web.Response(
|
||||
text="❌ Неверные параметры запроса. Требуется email.",
|
||||
status=400,
|
||||
)
|
||||
|
||||
logger.info(f"Обработка запроса для старого клиента с email: {email}")
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
key_info = await conn.fetchrow("SELECT created_at FROM keys WHERE email = $1", email)
|
||||
|
||||
if not key_info:
|
||||
logger.warning(f"Клиент с email {email} не найден в базе.")
|
||||
return web.Response(
|
||||
text="❌ Клиент с таким email не найден.",
|
||||
status=404,
|
||||
)
|
||||
|
||||
created_at_ms = key_info["created_at"]
|
||||
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}")
|
||||
|
||||
if created_at_ms >= transition_timestamp_ms_adjusted:
|
||||
logger.info(f"Клиент с email {email} является новым.")
|
||||
return web.Response(
|
||||
text="❌ Эта ссылка устарела. Пожалуйста, обновите ссылку.",
|
||||
status=400,
|
||||
)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
urls = []
|
||||
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, "")
|
||||
|
||||
base64_encoded = base64.b64encode("\n".join(combined_subscriptions).encode("utf-8")).decode("utf-8")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Content-Disposition": "inline",
|
||||
"profile-update-interval": "7",
|
||||
"profile-title": email,
|
||||
}
|
||||
|
||||
logger.info(f"Возвращаем объединенные подписки для email: {email}")
|
||||
return web.Response(text=base64_encoded, headers=headers)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
|
||||
async def handle_new_subscription(request):
|
||||
email = request.match_info.get("email")
|
||||
tg_id = request.match_info.get("tg_id")
|
||||
|
||||
if not email or not tg_id:
|
||||
logger.warning("Получен запрос с отсутствующими параметрами email или tg_id")
|
||||
return web.Response(
|
||||
text="❌ Неверные параметры запроса. Требуются email и tg_id.",
|
||||
status=400,
|
||||
)
|
||||
|
||||
logger.info(f"Обработка запроса для нового клиента: email={email}, tg_id={tg_id}")
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
client_data = await conn.fetchrow("SELECT tg_id FROM keys WHERE email = $1", email)
|
||||
|
||||
if not client_data:
|
||||
logger.warning(f"Клиент с email {email} не найден в базе.")
|
||||
return web.Response(
|
||||
text="❌ Клиент с таким email не найден.",
|
||||
status=404,
|
||||
)
|
||||
|
||||
stored_tg_id = client_data["tg_id"]
|
||||
|
||||
if str(tg_id) != str(stored_tg_id):
|
||||
logger.warning(f"Неверный tg_id для клиента с email {email}.")
|
||||
return web.Response(
|
||||
text="❌ Неверные данные. Получите свой ключ в боте.",
|
||||
status=403,
|
||||
)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
urls = []
|
||||
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
|
||||
logger.info(f"Извлечен query string: {query_string}")
|
||||
|
||||
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")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Content-Disposition": "inline",
|
||||
"profile-update-interval": "7",
|
||||
"profile-title": email,
|
||||
}
|
||||
|
||||
logger.info(f"Возвращаем объединенные подписки для email: {email}")
|
||||
return web.Response(text=base64_encoded, headers=headers)
|
||||
@@ -0,0 +1,68 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
import uuid
|
||||
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from utils.client import add_client
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, PUBLIC_LINK, TOTAL_GB, TRIAL_TIME
|
||||
from utils.database import get_servers_from_db, store_key, use_trial
|
||||
from handlers.texts import INSTRUCTIONS
|
||||
from utils.utils import generate_random_email, get_least_loaded_cluster
|
||||
|
||||
|
||||
async def create_trial_key(tg_id: int, session: Any):
|
||||
client_id = str(uuid.uuid4())
|
||||
email = generate_random_email()
|
||||
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
|
||||
instructions = INSTRUCTIONS
|
||||
result = {"key": public_link, "instructions": instructions, "email": email}
|
||||
current_time = datetime.utcnow()
|
||||
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()
|
||||
|
||||
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"]),
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await store_key(
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
public_link,
|
||||
server_id=least_loaded_cluster,
|
||||
session=session,
|
||||
)
|
||||
await use_trial(tg_id, session)
|
||||
return result
|
||||
Reference in New Issue
Block a user