Merge branch 'main' of https://github.com/izzzzzi/Solo_bot
This commit is contained in:
@@ -4,7 +4,7 @@ from datetime import datetime
|
||||
|
||||
from aiogram.types import BufferedInputFile
|
||||
|
||||
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER
|
||||
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER, PG_HOST, PG_PORT
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ async def backup_database():
|
||||
|
||||
def _create_database_backup():
|
||||
USER = DB_USER
|
||||
HOST = "localhost"
|
||||
HOST = PG_HOST
|
||||
PORT = PG_PORT
|
||||
BACKUP_DIR = BACK_DIR
|
||||
DATE = datetime.now().strftime("%Y-%m-%d-%H%M%S")
|
||||
BACKUP_FILE = f"{BACKUP_DIR}/{DB_NAME}-backup-{DATE}.sql"
|
||||
@@ -36,6 +37,8 @@ def _create_database_backup():
|
||||
USER,
|
||||
"-h",
|
||||
HOST,
|
||||
"-p",
|
||||
PORT,
|
||||
"-F",
|
||||
"c",
|
||||
"-f",
|
||||
|
||||
@@ -318,6 +318,7 @@ async def handle_new_balance_input(
|
||||
|
||||
|
||||
async def get_key_details(email, session):
|
||||
|
||||
record = await session.fetchrow(
|
||||
"""
|
||||
SELECT k.key, k.expiry_time, k.server_id, c.tg_id, c.balance
|
||||
@@ -331,18 +332,11 @@ async def get_key_details(email, session):
|
||||
if not record:
|
||||
return None
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
cluster_name = record["server_id"]
|
||||
|
||||
cluster_name = "Неизвестный кластер"
|
||||
for cluster_name, cluster_servers in servers.items():
|
||||
if any(
|
||||
server["inbound_id"] == record["server_id"] for server in cluster_servers
|
||||
):
|
||||
cluster_name = cluster_name
|
||||
break
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(record["expiry_time"] / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
expiry_date = datetime.fromtimestamp(record["expiry_time"] / 1000, tz=moscow_tz)
|
||||
current_date = datetime.now(moscow_tz)
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
if time_left.total_seconds() <= 0:
|
||||
@@ -355,7 +349,7 @@ async def get_key_details(email, session):
|
||||
|
||||
return {
|
||||
"key": record["key"],
|
||||
"expiry_date": expiry_date.strftime("%d %B %Y года"),
|
||||
"expiry_date": expiry_date.strftime("%d %B %Y года %H:%M"),
|
||||
"days_left_message": days_left_message,
|
||||
"server_name": cluster_name,
|
||||
"balance": record["balance"],
|
||||
|
||||
@@ -212,6 +212,7 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(inbound_id),
|
||||
sub_id=email
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,16 @@ from config import DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, SUPERNODE, TRANSITIO
|
||||
from database import get_servers_from_db
|
||||
from logger import logger
|
||||
|
||||
# Глобальная переменная для пула соединений
|
||||
db_pool = None
|
||||
|
||||
async def init_db_pool():
|
||||
"""
|
||||
Инициализация пула соединений, если он ещё не создан.
|
||||
"""
|
||||
global db_pool
|
||||
if not db_pool:
|
||||
db_pool = await asyncpg.create_pool(dsn=DATABASE_URL, min_size=5, max_size=20)
|
||||
|
||||
async def fetch_url_content(url, tg_id):
|
||||
try:
|
||||
@@ -33,44 +43,42 @@ async def fetch_url_content(url, tg_id):
|
||||
logger.error(f"Ошибка при получении {url} для tg_id: {tg_id}: {e}")
|
||||
return []
|
||||
|
||||
|
||||
async def combine_unique_lines(urls, tg_id, query_string):
|
||||
if SUPERNODE:
|
||||
logger.info(f"Режим SUPERNODE активен. Возвращаем первую ссылку для tg_id: {tg_id}")
|
||||
urls_with_query = [f"{urls[0]}?{query_string}"] if urls else []
|
||||
return await fetch_url_content(urls_with_query[0], tg_id) if urls_with_query else []
|
||||
if not urls:
|
||||
return []
|
||||
url_with_query = f"{urls[0]}?{query_string}" if query_string else urls[0]
|
||||
return await fetch_url_content(url_with_query, tg_id)
|
||||
|
||||
all_lines = []
|
||||
logger.info(
|
||||
f"Начинаем объединение подписок для tg_id: {tg_id}, запрос: {query_string}"
|
||||
)
|
||||
|
||||
urls_with_query = [f"{url}?{query_string}" for url in urls]
|
||||
urls_with_query = [f"{url}?{query_string}" if query_string else url 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)
|
||||
tasks = [fetch_url_content(url, tg_id) for url in urls_with_query]
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
all_lines = set()
|
||||
for lines in results:
|
||||
all_lines.update(filter(None, lines))
|
||||
|
||||
all_lines = list(set(filter(None, all_lines)))
|
||||
logger.info(
|
||||
f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}"
|
||||
)
|
||||
|
||||
return all_lines
|
||||
|
||||
return list(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")
|
||||
|
||||
@@ -83,8 +91,10 @@ async def handle_old_subscription(request):
|
||||
|
||||
logger.info(f"Обработка запроса для старого клиента с email: {email}")
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
# Инициализируем пул соединений
|
||||
await init_db_pool()
|
||||
|
||||
async with db_pool.acquire() as conn:
|
||||
key_info = await conn.fetchrow(
|
||||
"SELECT created_at, server_id FROM keys WHERE email = $1", email
|
||||
)
|
||||
@@ -121,35 +131,31 @@ async def handle_old_subscription(request):
|
||||
status=400,
|
||||
)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
cluster_servers = servers.get(cluster_name, [])
|
||||
logger.info(f"Сервера в кластере: {cluster_servers}")
|
||||
servers = await get_servers_from_db()
|
||||
cluster_servers = servers.get(cluster_name, [])
|
||||
logger.info(f"Сервера в кластере: {cluster_servers}")
|
||||
|
||||
urls = []
|
||||
for server in cluster_servers:
|
||||
server_subscription_url = f"{server['subscription_url']}/{email}"
|
||||
urls.append(server_subscription_url)
|
||||
urls = [
|
||||
f"{server['subscription_url']}/{email}" for server in cluster_servers
|
||||
]
|
||||
|
||||
combined_subscriptions = await combine_unique_lines(urls, email, "")
|
||||
combined_subscriptions = await combine_unique_lines(urls, email, "")
|
||||
|
||||
base64_encoded = base64.b64encode(
|
||||
"\n".join(combined_subscriptions).encode("utf-8")
|
||||
).decode("utf-8")
|
||||
base64_encoded = base64.b64encode(
|
||||
"\n".join(combined_subscriptions).encode("utf-8")
|
||||
).decode("utf-8")
|
||||
|
||||
encoded_project_name = f"{PROJECT_NAME} - {SUB_MESSAGE}"
|
||||
headers = {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Content-Disposition": "inline",
|
||||
"profile-update-interval": "7",
|
||||
"profile-title": "base64:"
|
||||
+ base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"),
|
||||
}
|
||||
encoded_project_name = f"{PROJECT_NAME} - {SUB_MESSAGE}"
|
||||
headers = {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Content-Disposition": "inline",
|
||||
"profile-update-interval": "7",
|
||||
"profile-title": "base64:"
|
||||
+ base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"),
|
||||
}
|
||||
|
||||
logger.info(f"Возвращаем объединенные подписки для email: {email}")
|
||||
return web.Response(text=base64_encoded, headers=headers)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
logger.info(f"Возвращаем объединенные подписки для email: {email}")
|
||||
return web.Response(text=base64_encoded, headers=headers)
|
||||
|
||||
|
||||
async def handle_new_subscription(request):
|
||||
@@ -165,9 +171,10 @@ async def handle_new_subscription(request):
|
||||
|
||||
logger.info(f"Обработка запроса для нового клиента: email={email}, tg_id={tg_id}")
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
# Инициализируем пул соединений
|
||||
await init_db_pool()
|
||||
|
||||
async with db_pool.acquire() as conn:
|
||||
client_data = await conn.fetchrow(
|
||||
"SELECT tg_id, server_id FROM keys WHERE email = $1", email
|
||||
)
|
||||
@@ -189,16 +196,12 @@ async def handle_new_subscription(request):
|
||||
status=403,
|
||||
)
|
||||
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
cluster_servers = servers.get(cluster_name, [])
|
||||
|
||||
urls = []
|
||||
for server in cluster_servers:
|
||||
server_subscription_url = f"{server['subscription_url']}/{email}"
|
||||
urls.append(server_subscription_url)
|
||||
urls = [
|
||||
f"{server['subscription_url']}/{email}" for server in cluster_servers
|
||||
]
|
||||
|
||||
query_string = request.query_string
|
||||
logger.info(f"Извлечен query string: {query_string}")
|
||||
|
||||
@@ -43,9 +43,11 @@ async def create_trial_key(tg_id: int, session: Any):
|
||||
servers_in_cluster = clusters[least_loaded_cluster]
|
||||
tasks = []
|
||||
|
||||
for index, server_info in enumerate(servers_in_cluster):
|
||||
for server_info in servers_in_cluster:
|
||||
server_name = server_info.get("server_name", "unknown")
|
||||
|
||||
if SUPERNODE:
|
||||
email = f"{base_email}{index}"
|
||||
email = f"{base_email}_{server_name.lower()}"
|
||||
else:
|
||||
email = base_email
|
||||
|
||||
|
||||
+64
-29
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
|
||||
import asyncpg
|
||||
from aiogram import Bot, Router, types
|
||||
@@ -17,6 +18,7 @@ from config import (
|
||||
RENEWAL_PLANS,
|
||||
TOTAL_GB,
|
||||
TRIAL_TIME,
|
||||
EXPIRED_KEYS_CHECK_INTERVAL
|
||||
)
|
||||
from database import (
|
||||
add_blocked_user,
|
||||
@@ -56,6 +58,24 @@ async def check_users_and_update_blocked(bot: Bot):
|
||||
await conn.close()
|
||||
|
||||
|
||||
async def periodic_expired_keys_check(bot: Bot):
|
||||
"""Периодическая проверка истекших ключей с кастомным интервалом."""
|
||||
while True:
|
||||
conn = None
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
current_time = int(datetime.utcnow().timestamp() * 1000)
|
||||
await handle_expired_keys(bot, conn, current_time)
|
||||
logger.info("✅ Проверка истекших ключей выполнена.")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка в periodic_expired_keys_check: {e}")
|
||||
finally:
|
||||
if conn:
|
||||
await conn.close()
|
||||
|
||||
await asyncio.sleep(EXPIRED_KEYS_CHECK_INTERVAL)
|
||||
|
||||
|
||||
|
||||
async def notify_expiring_keys(bot: Bot):
|
||||
conn = None
|
||||
@@ -64,25 +84,19 @@ async def notify_expiring_keys(bot: Bot):
|
||||
logger.info("Подключение к базе данных успешно.")
|
||||
|
||||
current_time = int(datetime.utcnow().timestamp() * 1000)
|
||||
threshold_time_10h = int(
|
||||
(datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000
|
||||
)
|
||||
threshold_time_24h = int(
|
||||
(datetime.utcnow() + timedelta(days=1)).timestamp() * 1000
|
||||
)
|
||||
threshold_time_10h = int((datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000)
|
||||
threshold_time_24h = int((datetime.utcnow() + timedelta(days=1)).timestamp() * 1000)
|
||||
|
||||
logger.info("Начало обработки уведомлений.")
|
||||
|
||||
await notify_inactive_trial_users(bot, conn)
|
||||
await asyncio.sleep(1)
|
||||
await asyncio.sleep(0.5)
|
||||
await check_online_users()
|
||||
await asyncio.sleep(1)
|
||||
await asyncio.sleep(0.5)
|
||||
await notify_10h_keys(bot, conn, current_time, threshold_time_10h)
|
||||
await asyncio.sleep(1)
|
||||
await asyncio.sleep(0.5)
|
||||
await notify_24h_keys(bot, conn, current_time, threshold_time_24h)
|
||||
await asyncio.sleep(1)
|
||||
await handle_expired_keys(bot, conn, current_time)
|
||||
await asyncio.sleep(1)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомлений: {e}")
|
||||
@@ -92,6 +106,7 @@ async def notify_expiring_keys(bot: Bot):
|
||||
logger.info("Соединение с базой данных закрыто.")
|
||||
|
||||
|
||||
|
||||
async def is_bot_blocked(bot: Bot, chat_id: int) -> bool:
|
||||
if DEV_MODE:
|
||||
return False
|
||||
@@ -137,8 +152,10 @@ async def process_10h_record(record, bot, conn):
|
||||
email = record["email"]
|
||||
expiry_time = record["expiry_time"]
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
expiry_date = datetime.fromtimestamp(expiry_time / 1000, tz=moscow_tz)
|
||||
current_date = datetime.now(moscow_tz)
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
days_left_message = (
|
||||
@@ -211,8 +228,10 @@ async def process_24h_record(record, bot, conn):
|
||||
email = record["email"]
|
||||
expiry_time = record["expiry_time"]
|
||||
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
expiry_date = datetime.fromtimestamp(expiry_time / 1000, tz=moscow_tz)
|
||||
current_date = datetime.now(moscow_tz)
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
days_left_message = (
|
||||
@@ -368,13 +387,15 @@ async def process_key(record, bot, conn):
|
||||
email = record["email"]
|
||||
balance = await get_balance(tg_id)
|
||||
expiry_time = record["expiry_time"]
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
expiry_date = datetime.fromtimestamp(expiry_time / 1000, tz=moscow_tz)
|
||||
current_date = datetime.now(moscow_tz)
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
logger.info(
|
||||
f"Время истечения ключа: {expiry_time} (UTC: {expiry_date}), "
|
||||
f"Текущее время (UTC): {current_date}, "
|
||||
f"Время истечения ключа: {expiry_time} (МСК: {expiry_date}), "
|
||||
f"Текущее время (МСК): {current_date}, "
|
||||
f"Оставшееся время: {time_left}"
|
||||
)
|
||||
|
||||
@@ -391,7 +412,8 @@ async def process_key(record, bot, conn):
|
||||
try:
|
||||
if AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
|
||||
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
|
||||
new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000)
|
||||
|
||||
new_expiry_time = int((datetime.now(moscow_tz) + timedelta(days=30)).timestamp() * 1000)
|
||||
await update_key_expiry(client_id, new_expiry_time)
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
@@ -410,23 +432,35 @@ async def process_key(record, bot, conn):
|
||||
)
|
||||
logger.info(f"Флаги notified сброшены для клиента {client_id}.")
|
||||
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
|
||||
try:
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось отправить уведомление о продлении клиенту {tg_id}: {e}")
|
||||
|
||||
else:
|
||||
message_expired = "Ваша подписка истекла. Пополните баланс для продления."
|
||||
await bot.send_message(tg_id, text=message_expired, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об истечении подписки отправлено пользователю {tg_id}.")
|
||||
try:
|
||||
await bot.send_message(tg_id, text=message_expired, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об истечении подписки отправлено пользователю {tg_id}.")
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось отправить уведомление об истечении клиенту {tg_id}: {e}")
|
||||
|
||||
if AUTO_DELETE_EXPIRED_KEYS:
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
for cluster_id in servers:
|
||||
await delete_key_from_cluster(cluster_id, email, client_id)
|
||||
logger.info(f"Клиент {client_id} удален из кластера {cluster_id}.")
|
||||
try:
|
||||
await delete_key_from_cluster(cluster_id, email, client_id)
|
||||
logger.info(f"Клиент {client_id} удален из кластера {cluster_id}.")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении клиента {client_id} из кластера {cluster_id}: {e}")
|
||||
|
||||
await delete_key(client_id)
|
||||
logger.info(f"Ключ {client_id} удалён из базы данных.")
|
||||
try:
|
||||
await delete_key(client_id)
|
||||
logger.info(f"Ключ {client_id} удалён из базы данных.")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
|
||||
else:
|
||||
logger.info(f"Ключ {client_id} НЕ был удалён (AUTO_DELETE_EXPIRED_KEYS=False).")
|
||||
|
||||
@@ -434,6 +468,7 @@ async def process_key(record, bot, conn):
|
||||
logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}")
|
||||
|
||||
|
||||
|
||||
async def check_online_users():
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user