7c883b07f9
This reverts commitfa681fac92, reversing changes made tob31c9d2e10.
173 lines
7.5 KiB
Python
173 lines
7.5 KiB
Python
import asyncio
|
||
from datetime import datetime, timedelta
|
||
import re
|
||
|
||
from aiogram.types import InlineKeyboardButton
|
||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||
import asyncpg
|
||
from ping3 import ping
|
||
|
||
from bot import bot
|
||
from config import ADMIN_ID, DATABASE_URL
|
||
from database import get_servers_from_db
|
||
from logger import logger
|
||
|
||
try:
|
||
from config import CLUSTERS
|
||
except ImportError:
|
||
CLUSTERS = None
|
||
logger.warning("Переменная CLUSTERS не найдена в конфигурации. Добавьте сервера через админ-панель!")
|
||
|
||
|
||
async def sync_servers_with_db():
|
||
"""
|
||
Синхронизирует сервера из конфигурации CLUSTERS с базой данных.
|
||
Если CLUSTERS не найден, синхронизация не будет выполнена.
|
||
"""
|
||
if CLUSTERS is None:
|
||
logger.info("Конфигурация CLUSTERS не найдена. Синхронизация не будет выполнена.")
|
||
return
|
||
|
||
try:
|
||
conn = await asyncpg.connect(DATABASE_URL)
|
||
logger.info("Подключение к базе данных для синхронизации серверов успешно.")
|
||
|
||
for cluster_name, servers in CLUSTERS.items():
|
||
for server_key, server_info in servers.items():
|
||
exists = await conn.fetchval(
|
||
"""
|
||
SELECT 1 FROM servers
|
||
WHERE cluster_name = $1 AND server_name = $2
|
||
""",
|
||
cluster_name,
|
||
server_info["name"],
|
||
)
|
||
|
||
if not exists:
|
||
await conn.execute(
|
||
"""
|
||
INSERT INTO servers (cluster_name, server_name, api_url, subscription_url, inbound_id)
|
||
VALUES ($1, $2, $3, $4, $5)
|
||
""",
|
||
cluster_name,
|
||
server_info["name"],
|
||
server_info["API_URL"],
|
||
server_info["SUBSCRIPTION"],
|
||
server_info["INBOUND_ID"],
|
||
)
|
||
logger.info(f"Сервер {server_info['name']} из кластера {cluster_name} добавлен в базу данных.")
|
||
else:
|
||
logger.info(f"Сервер {server_info['name']} из кластера {cluster_name} уже существует.")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при синхронизации серверов: {e}")
|
||
finally:
|
||
if 'conn' in locals():
|
||
await conn.close()
|
||
|
||
|
||
last_ping_times = {}
|
||
last_notification_times = {}
|
||
|
||
|
||
async def ping_server(server_ip: str) -> bool:
|
||
"""
|
||
Функция пинга сервера.
|
||
Возвращает True, если сервер доступен, иначе False.
|
||
"""
|
||
try:
|
||
logger.debug(f"Пингуем сервер {server_ip}...")
|
||
response = ping(server_ip, timeout=3)
|
||
if response is False:
|
||
logger.warning(f"Сервер {server_ip} не отвечает.")
|
||
return False
|
||
return True
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при пинге сервера {server_ip}: {e}")
|
||
return False
|
||
|
||
|
||
async def notify_admin(server_name: str):
|
||
"""
|
||
Отправляет уведомление всем администраторам о недоступности сервера.
|
||
Уведомления отправляются не чаще чем раз в 3 минуты.
|
||
"""
|
||
try:
|
||
current_time = datetime.now()
|
||
last_notification_time = last_notification_times.get(server_name)
|
||
|
||
if last_notification_time and current_time - last_notification_time < timedelta(minutes=3):
|
||
logger.info(f"Не отправляем уведомление для сервера {server_name}, так как прошло менее 3 минут.")
|
||
return
|
||
|
||
logger.info(f"Отправка уведомлений администратору о недоступности сервера {server_name}...")
|
||
|
||
builder = InlineKeyboardBuilder()
|
||
builder.row(InlineKeyboardButton(text="Управление сервером", callback_data=f"manage_server|{server_name}"))
|
||
|
||
for admin_id in ADMIN_ID:
|
||
await bot.send_message(
|
||
admin_id,
|
||
(
|
||
f"❌ <b>Сервер '{server_name}'</b> не отвечает более 3 минут.\n\n"
|
||
"Проверьте соединение к серверу, подключение к панели или удалите его из таблицы серверов в боте, "
|
||
"чтобы не выдать подписку к неработающему серверу."
|
||
),
|
||
parse_mode="HTML",
|
||
reply_markup=builder.as_markup(),
|
||
)
|
||
logger.info(f"Уведомление отправлено администратору с ID {admin_id} о сервере {server_name}.")
|
||
|
||
last_notification_times[server_name] = current_time
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при отправке уведомления администраторам: {e}")
|
||
|
||
|
||
async def check_servers():
|
||
"""
|
||
Периодическая проверка серверов с учетом извлечения хоста из `api_url`.
|
||
"""
|
||
while True:
|
||
servers = await get_servers_from_db()
|
||
current_time = datetime.now()
|
||
|
||
logger.info(f"Начинаю проверку серверов: {current_time}")
|
||
|
||
for cluster_name, cluster_servers in servers.items():
|
||
logger.debug(f"Проверка кластеров: {cluster_name}")
|
||
for server in cluster_servers:
|
||
original_api_url = server["api_url"]
|
||
server_name = server["server_name"]
|
||
|
||
server_host = extract_host(original_api_url)
|
||
logger.debug(f"Проверка доступности сервера '{server_name}' с хостом {server_host}")
|
||
|
||
is_online = await ping_server(server_host)
|
||
|
||
if is_online:
|
||
last_ping_times[server_name] = current_time
|
||
else:
|
||
last_ping_time = last_ping_times.get(server_name)
|
||
if last_ping_time and current_time - last_ping_time > timedelta(minutes=3):
|
||
logger.warning(f"Сервер {server_name} не отвечает более 3 минут. Отправляю уведомление.")
|
||
await notify_admin(server_name)
|
||
elif not last_ping_time:
|
||
last_ping_times[server_name] = current_time
|
||
logger.info(f"Сервер {server_name} не отвечал ранее, но теперь зарегистрирован.")
|
||
|
||
logger.info("Завершена проверка всех серверов.")
|
||
await asyncio.sleep(30)
|
||
|
||
|
||
def extract_host(api_url: str) -> str:
|
||
"""
|
||
Извлекает только хост из `api_url` (без путей, портов и параметров).
|
||
"""
|
||
match = re.match(r"(https?://)?([^:/]+)", api_url)
|
||
if match:
|
||
host = match.group(2)
|
||
logger.debug(f"Извлечён хост: {host} из URL: {api_url}")
|
||
return host
|
||
logger.error(f"Не удалось извлечь хост из URL: {api_url}")
|
||
return api_url
|