mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-27 14:51:19 +00:00
- Add pyproject.toml with uv and ruff configuration - Pin Python version to 3.13 via .python-version - Add Makefile commands: lint, format, fix - Apply ruff formatting to entire codebase - Remove unused imports (base64 in yookassa/simple_subscription) - Update .gitignore for new config files
210 lines
6.7 KiB
Python
210 lines
6.7 KiB
Python
import logging
|
||
from datetime import datetime
|
||
|
||
from aiogram import Dispatcher, F, types
|
||
|
||
from app.config import settings
|
||
from app.database.models import User
|
||
from app.keyboards.inline import get_server_status_keyboard
|
||
from app.localization.texts import get_texts
|
||
from app.services.server_status_service import (
|
||
ServerStatusEntry,
|
||
ServerStatusError,
|
||
ServerStatusService,
|
||
)
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
_status_service = ServerStatusService()
|
||
|
||
|
||
async def show_server_status(callback: types.CallbackQuery, db_user: User) -> None:
|
||
await _render_server_status(callback, db_user, page=1)
|
||
|
||
|
||
async def change_server_status_page(callback: types.CallbackQuery, db_user: User) -> None:
|
||
try:
|
||
_, page_str = callback.data.split(':', 1)
|
||
page = int(page_str)
|
||
except (ValueError, AttributeError, IndexError):
|
||
page = 1
|
||
|
||
await _render_server_status(callback, db_user, page=page)
|
||
|
||
|
||
async def _render_server_status(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
page: int = 1,
|
||
) -> None:
|
||
texts = get_texts(db_user.language)
|
||
|
||
if settings.get_server_status_mode() != 'xray':
|
||
await callback.answer(texts.t('SERVER_STATUS_NOT_CONFIGURED', 'Функция недоступна.'), show_alert=True)
|
||
return
|
||
|
||
try:
|
||
servers = await _status_service.get_servers()
|
||
except ServerStatusError as error:
|
||
logger.warning('Server status error: %s', error)
|
||
await callback.answer(
|
||
texts.t('SERVER_STATUS_ERROR_SHORT', 'Не удалось получить данные'),
|
||
show_alert=True,
|
||
)
|
||
return
|
||
except Exception as error: # pragma: no cover - defensive logging
|
||
logger.error('Unexpected server status error: %s', error)
|
||
await callback.answer(
|
||
texts.t('SERVER_STATUS_ERROR_SHORT', 'Не удалось получить данные'),
|
||
show_alert=True,
|
||
)
|
||
return
|
||
|
||
message, total_pages, current_page = _build_status_message(servers, texts, page)
|
||
keyboard = get_server_status_keyboard(db_user.language, current_page, total_pages)
|
||
|
||
await callback.message.edit_text(
|
||
message,
|
||
reply_markup=keyboard,
|
||
disable_web_page_preview=True,
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
def _build_status_message(
|
||
servers: list[ServerStatusEntry],
|
||
texts,
|
||
page: int,
|
||
) -> tuple[str, int, int]:
|
||
total_servers = len(servers)
|
||
online_servers = [server for server in servers if server.is_online]
|
||
offline_servers = [server for server in servers if not server.is_online]
|
||
|
||
items_per_page = settings.get_server_status_items_per_page()
|
||
pages = _split_into_pages(online_servers, offline_servers, items_per_page)
|
||
|
||
total_pages = max(1, len(pages))
|
||
current_index = min(max(page - 1, 0), total_pages - 1)
|
||
|
||
current_online, current_offline = pages[current_index] if pages else ([], [])
|
||
|
||
lines: list[str] = [texts.t('SERVER_STATUS_TITLE', '📊 <b>Статус серверов</b>')]
|
||
|
||
if total_servers == 0:
|
||
lines.append('')
|
||
lines.append(texts.t('SERVER_STATUS_NO_SERVERS', 'Нет данных о серверах.'))
|
||
message = '\n'.join(lines).strip()
|
||
return message, 1, 1
|
||
|
||
summary = texts.t(
|
||
'SERVER_STATUS_SUMMARY',
|
||
'Всего серверов: {total} (в сети: {online}, вне сети: {offline})',
|
||
).format(
|
||
total=total_servers,
|
||
online=len(online_servers),
|
||
offline=len(offline_servers),
|
||
)
|
||
|
||
updated_at = datetime.now().strftime('%H:%M:%S')
|
||
|
||
lines.extend(
|
||
[
|
||
'',
|
||
summary,
|
||
texts.t('SERVER_STATUS_UPDATED_AT', '⏱ Обновлено: {time}').format(time=updated_at),
|
||
'',
|
||
]
|
||
)
|
||
|
||
if current_online:
|
||
lines.append(texts.t('SERVER_STATUS_AVAILABLE', '✅ <b>Доступны</b>'))
|
||
lines.extend(_format_server_lines(current_online, texts, online=True))
|
||
lines.append('')
|
||
|
||
if current_offline:
|
||
lines.append(texts.t('SERVER_STATUS_UNAVAILABLE', '❌ <b>Недоступны</b>'))
|
||
lines.extend(_format_server_lines(current_offline, texts, online=False))
|
||
lines.append('')
|
||
|
||
if total_pages > 1:
|
||
lines.append(
|
||
texts.t('SERVER_STATUS_PAGINATION', 'Страница {current} из {total}').format(
|
||
current=current_index + 1,
|
||
total=total_pages,
|
||
)
|
||
)
|
||
|
||
message = '\n'.join(line for line in lines if line is not None)
|
||
message = message.strip()
|
||
return message, total_pages, current_index + 1
|
||
|
||
|
||
def _split_into_pages(
|
||
online: list[ServerStatusEntry],
|
||
offline: list[ServerStatusEntry],
|
||
items_per_page: int,
|
||
) -> list[tuple[list[ServerStatusEntry], list[ServerStatusEntry]]]:
|
||
if not online and not offline:
|
||
return [([], [])]
|
||
|
||
pages: list[tuple[list[ServerStatusEntry], list[ServerStatusEntry]]] = []
|
||
online_index = 0
|
||
offline_index = 0
|
||
|
||
while online_index < len(online) or offline_index < len(offline):
|
||
current_online: list[ServerStatusEntry] = []
|
||
current_offline: list[ServerStatusEntry] = []
|
||
remaining = max(1, items_per_page)
|
||
|
||
while remaining > 0 and online_index < len(online):
|
||
current_online.append(online[online_index])
|
||
online_index += 1
|
||
remaining -= 1
|
||
|
||
while remaining > 0 and offline_index < len(offline):
|
||
current_offline.append(offline[offline_index])
|
||
offline_index += 1
|
||
remaining -= 1
|
||
|
||
pages.append((current_online, current_offline))
|
||
|
||
return pages if pages else [([], [])]
|
||
|
||
|
||
def _format_server_lines(
|
||
servers: list[ServerStatusEntry],
|
||
texts,
|
||
*,
|
||
online: bool,
|
||
) -> list[str]:
|
||
lines: list[str] = []
|
||
for server in servers:
|
||
latency_text: str
|
||
if online:
|
||
if server.latency_ms and server.latency_ms > 0:
|
||
latency_text = texts.t('SERVER_STATUS_LATENCY', '{latency} мс').format(latency=server.latency_ms)
|
||
else:
|
||
latency_text = texts.t('SERVER_STATUS_LATENCY_UNKNOWN', 'нет данных')
|
||
else:
|
||
latency_text = texts.t('SERVER_STATUS_OFFLINE', 'нет ответа')
|
||
|
||
name = server.display_name or server.name
|
||
flag_prefix = f'{server.flag} ' if server.flag else ''
|
||
server_line = f'{flag_prefix}{name} — {latency_text}'
|
||
lines.append(f'<blockquote>{server_line}</blockquote>')
|
||
|
||
return lines
|
||
|
||
|
||
def register_handlers(dp: Dispatcher) -> None:
|
||
dp.callback_query.register(
|
||
show_server_status,
|
||
F.data == 'menu_server_status',
|
||
)
|
||
|
||
dp.callback_query.register(
|
||
change_server_status_page,
|
||
F.data.startswith('server_status_page:'),
|
||
)
|