148 lines
4.5 KiB
Python
148 lines
4.5 KiB
Python
import contextlib
|
|
from collections.abc import Awaitable, Callable
|
|
|
|
from aiogram.exceptions import TelegramBadRequest
|
|
from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
|
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
|
|
|
from healthbot.komodo import KomodoError, Update, clean_output, komodo
|
|
from healthbot.ui import edit, esc, pre, show
|
|
from utils.format import clock
|
|
|
|
RUN_TIMEOUT = 30 * 60
|
|
OUTPUT_LIMIT = 3000
|
|
QUICK_SECONDS = 10
|
|
NOISE = ("Action completed successfully",)
|
|
ACTIVE: set[str] = set()
|
|
|
|
Back = list[tuple[str, str]]
|
|
|
|
|
|
def _stage_text(output: str) -> str:
|
|
lines = [
|
|
line
|
|
for line in clean_output(output).splitlines()
|
|
if not any(mark in line for mark in NOISE)
|
|
]
|
|
return "\n".join(lines).strip()
|
|
|
|
|
|
def output_of(update: Update, *, only_failed: bool) -> str:
|
|
parts = []
|
|
for stage in update.stages:
|
|
if "config" in stage.name.lower():
|
|
if not stage.success:
|
|
parts.append(f"{stage.name}: вывод скрыт, в нём бывают секреты")
|
|
continue
|
|
if only_failed and stage.success:
|
|
continue
|
|
text = _stage_text(stage.output)
|
|
if not text:
|
|
continue
|
|
parts.append(f"{stage.name}:\n{text}" if len(update.stages) > 1 else text)
|
|
return "\n\n".join(parts)
|
|
|
|
|
|
def result_text(title: str, update: Update, *, verbose: bool, extra: str = "") -> str:
|
|
icon = "✅" if update.success else "🔴"
|
|
state = "" if update.success else " не удалось"
|
|
lines = [f"{icon} <b>{esc(title)}</b>{state} · {clock(update.seconds)}"]
|
|
if extra:
|
|
lines.append(extra)
|
|
if verbose or not update.success:
|
|
output = output_of(update, only_failed=not verbose)
|
|
if output:
|
|
lines.append(pre(output, OUTPUT_LIMIT))
|
|
return "\n".join(lines)
|
|
|
|
|
|
def result_kb(update: Update | None, back: Back) -> InlineKeyboardMarkup:
|
|
kb = InlineKeyboardBuilder()
|
|
for text, data in back:
|
|
kb.button(text=text, callback_data=data)
|
|
rows = [len(back)] if back else []
|
|
if update is not None and len(output_of(update, only_failed=False)) > OUTPUT_LIMIT:
|
|
kb.button(text="Весь вывод файлом", callback_data=f"run:file:{update.id}")
|
|
rows.append(1)
|
|
kb.adjust(*rows or [1])
|
|
return kb.as_markup()
|
|
|
|
|
|
async def track( # noqa: PLR0913
|
|
origin: Message | CallbackQuery,
|
|
title: str,
|
|
starter: Callable[[], Awaitable[Update]],
|
|
*,
|
|
back: Back,
|
|
verbose: bool,
|
|
extra: str = "",
|
|
new: bool = False,
|
|
) -> None:
|
|
source = origin.message if isinstance(origin, CallbackQuery) else origin
|
|
key = f"{source.message_id if source else 0}:{title}"
|
|
if key in ACTIVE:
|
|
return
|
|
ACTIVE.add(key)
|
|
try:
|
|
await _track(
|
|
origin, title, starter, back, verbose=verbose, extra=extra, new=new
|
|
)
|
|
finally:
|
|
ACTIVE.discard(key)
|
|
|
|
|
|
async def _track( # noqa: PLR0913
|
|
origin: Message | CallbackQuery,
|
|
title: str,
|
|
starter: Callable[[], Awaitable[Update]],
|
|
back: Back,
|
|
*,
|
|
verbose: bool,
|
|
extra: str,
|
|
new: bool,
|
|
) -> None:
|
|
progress = await show(origin, f"⏳ <b>{esc(title)}</b> выполняется", new=new)
|
|
if progress is None:
|
|
return
|
|
try:
|
|
started = await starter()
|
|
except KomodoError as exc:
|
|
await edit(
|
|
progress,
|
|
f"🔴 <b>{esc(title)}</b> не запустилось\n{esc(exc)}",
|
|
result_kb(None, back),
|
|
)
|
|
return
|
|
|
|
if not started.id:
|
|
await edit(progress, f"✅ <b>{esc(title)}</b> запущено", result_kb(None, back))
|
|
return
|
|
|
|
check = InlineKeyboardBuilder()
|
|
check.button(text="Проверить", callback_data=f"run:st:{started.id}")
|
|
try:
|
|
update = await komodo.wait(started.id, RUN_TIMEOUT)
|
|
except KomodoError as exc:
|
|
await edit(
|
|
progress,
|
|
f"⏳ <b>{esc(title)}</b> запущено, результат узнать не удалось\n{esc(exc)}",
|
|
check.as_markup(),
|
|
)
|
|
return
|
|
if update is None:
|
|
await edit(
|
|
progress,
|
|
f"⏳ <b>{esc(title)}</b> идёт дольше {RUN_TIMEOUT // 60} минут",
|
|
check.as_markup(),
|
|
)
|
|
return
|
|
|
|
text = result_text(title, update, verbose=verbose, extra=extra)
|
|
markup = result_kb(update, back)
|
|
if update.seconds < QUICK_SECONDS:
|
|
await edit(progress, text, markup)
|
|
return
|
|
await show(progress, text, markup)
|
|
with contextlib.suppress(TelegramBadRequest):
|
|
await progress.delete()
|