Files
DepressedCat 5f2aad00f4
Check extension and helper / check (push) Canceled after 0s
Publish YouTubeDL 1.4.5 and website on Gitea
2026-10-10 23:53:17 +03:00

101 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
let toastHost, toastRoot, tabId = null;
const dismissed = new Set();
const toastTimers = new Map();
const knownJobs = new Map();
const stages = { preparing: "Подготовка", downloading: "Скачивание", merging: "Объединение дорожек", remuxing: "Подготовка файла", finalizing: "Финализация файла", encoding: "Перекодирование", audio: "Обработка аудио", saving: "Сохранение файла", installing: "Установка", updating: "Обновление компонентов", complete: "Файл сохранён", error: "Ошибка", cancelled: "Отменено", cancelling: "Останавливаю обработку" };
const isDark = () => document.documentElement.hasAttribute("dark") || document.documentElement.dataset.theme === "dark";
function updateText(node, value) {
if (node.textContent === value) return;
if (node.childNodes.length === 1 && node.firstChild.nodeType === 3) node.firstChild.data = value;
else node.textContent = value;
}
function downloadSizeText(job) {
const megabytes = value => `${(value / 1048576).toLocaleString("ru-RU", { minimumFractionDigits: 1, maximumFractionDigits: 1 })} МБ`;
if (job.status === "complete" && Number.isFinite(job.result?.size) && job.result.size >= 0) return `Размер файла: ${megabytes(job.result.size)}`;
const value = job.stage === "saving" ? job.copied : job.downloaded;
if (!["downloading", "saving"].includes(job.stage) || !Number.isFinite(value) || value < 0) return "";
const downloaded = `${job.stage === "saving" ? "Сохранено" : "Скачано"} ${megabytes(value)}`;
return Number.isFinite(job.total) && job.total > 0 ? `${downloaded} из ${megabytes(job.total)}` : downloaded;
}
function processingText(job) {
if (!["merging", "remuxing", "finalizing", "encoding", "audio"].includes(job.stage)) return "";
const duration = value => {
const seconds = Math.floor(value);
return seconds >= 3600 ? `${Math.floor(seconds / 3600)}:${String(Math.floor(seconds / 60) % 60).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}` : `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
};
const parts = [];
if (Number.isFinite(job.processedSeconds) && job.processedSeconds >= 0) parts.push(`Обработано ${duration(job.processedSeconds)}${job.durationSeconds > 0 ? ` из ${duration(job.durationSeconds)}` : ""}`);
if (Number.isFinite(job.written) && job.written >= 0) parts.push(`Записано ${(job.written / 1048576).toLocaleString("ru-RU", { minimumFractionDigits: 1, maximumFractionDigits: 1 })} МБ`);
return parts.join(" · ");
}
const commonStyle = ` :host{font-family:Roboto,"Segoe UI",sans-serif;color-scheme:light;--bg:#fff;--fg:#152334;--muted:#6a7787;--line:#e1e7ee;--track:#edf2f7;--accent:#0788bc}:host([dark]){color-scheme:dark;--bg:#1b2029;--fg:#eef2fa;--muted:#a1adbd;--line:#333e4e;--track:#2b3543;--accent:#59d0f4}*{box-sizing:border-box}button{font:inherit;cursor:pointer}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important}}`;
function applyTheme() { toastHost?.toggleAttribute("dark", isDark()); }
function ensureToasts() {
if (toastHost?.isConnected) return;
toastHost = document.createElement("div"); toastHost.id = "ytdl-notifications";
toastHost.style.cssText = "position:fixed;right:20px;bottom:20px;width:min(365px,calc(100vw - 32px));z-index:2147483647;pointer-events:none";
toastRoot = toastHost.attachShadow({ mode: "open" });
toastRoot.innerHTML = `<style>${commonStyle}.transfer{font-size:11px;color:var(--muted);margin-top:8px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.toast{background:var(--bg);color:var(--fg);border:1px solid var(--line);border-radius:16px;padding:15px;margin-top:9px;box-shadow:0 12px 38px #0003;pointer-events:auto;animation:toast-in .32s cubic-bezier(.2,.8,.2,1) both;position:relative}.top{display:flex;gap:11px;align-items:center}.icon{width:35px;height:35px;display:grid;place-items:center;flex-shrink:0;border-radius:11px;background:color-mix(in srgb,var(--accent) 13%,var(--bg));color:var(--accent);font-size:24px}.copy{min-width:0;flex:1}.title{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.stage{font-size:11px;color:var(--muted);margin-top:4px}.dismiss{border:0;background:transparent;color:var(--muted);font-size:20px;align-self:flex-start;line-height:16px;padding:0 0 0 3px}.track{height:5px;background:var(--track);border-radius:5px;margin-top:13px;overflow:hidden}.fill{height:100%;border-radius:5px;background:linear-gradient(90deg,#149bce,#59ded5);transition:width .3s ease;position:relative;overflow:hidden}.toast[data-status=running] .fill:not(.indeterminate):after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent,#fff6,transparent);animation:shine 1.6s linear infinite}.fill.indeterminate{width:35%!important;transition:none;animation:slide 1.7s infinite ease-in-out}.bottom{display:flex;justify-content:space-between;align-items:center;margin-top:9px;font-size:10px;color:var(--muted)}.action{border:0;background:none;color:var(--accent);font-size:11px;padding:0}.complete .icon{background:#35c99820;color:#22b98a}.error .icon{background:#ff656520;color:#e67171}.toast.exit{animation:toast-out .18s ease-in forwards}.encoding .icon{animation:pulse 1.8s infinite}.label{font-variant-numeric:tabular-nums;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;max-width:235px}@keyframes toast-in{from{opacity:0;transform:translateY(18px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes toast-out{to{opacity:0;transform:translateX(30px)}}@keyframes shine{from{transform:translateX(-100%)}to{transform:translateX(100%)}}@keyframes slide{0%,100%{transform:translateX(0)}50%{transform:translateX(calc(100% / .35 - 100%))}}@keyframes pulse{50%{box-shadow:0 0 0 5px #31bee912}}@media(max-width:450px){.toast{padding:12px}} </style>`;
document.documentElement.append(toastHost); applyTheme();
}
function dismiss(id) {
dismissed.add(id); clearTimeout(toastTimers.get(id));
const toast = toastRoot?.querySelector(`[data-id="${CSS.escape(id)}"]`);
toast?.classList.add("exit"); setTimeout(() => toast?.remove(), 180);
}
function showJob(job) {
knownJobs.set(job.id, job);
if (dismissed.has(job.id)) return;
ensureToasts();
let toast = toastRoot.querySelector(`[data-id="${CSS.escape(job.id)}"]`);
if (!toast) {
toast = document.createElement("article"); toast.className = "toast"; toast.dataset.id = job.id;
toast.setAttribute("role", "status"); toast.setAttribute("aria-live", "polite");
toast.innerHTML = '<div class="top"><div class="icon">↓</div><div class="copy"><div class="title"></div><div class="stage"></div></div><button class="dismiss" aria-label="Скрыть уведомление">×</button></div><div class="track"><div class="fill"></div></div><div class="transfer" hidden></div><div class="bottom"><span class="label"></span><button class="action"></button></div>';
toast.querySelector(".dismiss").addEventListener("click", () => dismiss(job.id));
toast.querySelector(".action").addEventListener("click", () => {
const current = knownJobs.get(job.id);
chrome.runtime.sendMessage({ type: current.status === "complete" ? "REVEAL_JOB" : "CANCEL_JOB", jobId: job.id }).catch(() => {});
});
toastRoot.append(toast);
}
const className = `toast ${job.stage}`;
if (toast.className !== className) toast.className = className;
if (toast.dataset.status !== job.status) toast.dataset.status = job.status;
updateText(toast.querySelector(".title"), job.title || "YouTubeDL");
const suffix = job.stage === "downloading" ? ` · ${job.track === "audio" ? "аудио" : "видео"}` : ["encoding", "audio"].includes(job.stage) && job.engine ? ` · ${{ nvenc: "NVIDIA NVENC", amf: "AMD AMF", cpu: "CPU", copy: "без перекодирования" }[job.engine] || job.engine}` : ["merging", "remuxing"].includes(job.stage) && !job.message ? " · без перекодирования" : "";
updateText(toast.querySelector(".stage"), (job.stage === "complete" && job.kind === "update" ? job.result?.title || "Обновление завершено" : ["updating", "merging", "remuxing", "finalizing", "encoding", "audio"].includes(job.stage) && job.message ? job.message : job.stage === "complete" && job.kind === "dependency" ? "Установка завершена" : stages[job.stage] || "Обработка") + suffix);
updateText(toast.querySelector(".icon"), { complete: "✓", error: "!", cancelled: "×", encoding: "✦", merging: "⇄", remuxing: "⇄", audio: "♫" }[job.stage] || "↓");
const fill = toast.querySelector(".fill");
const width = `${Number.isFinite(job.percent) ? Math.max(0, Math.min(100, job.percent)) : 0}%`;
if (fill.style.width !== width) fill.style.width = width;
fill.classList.toggle("indeterminate", job.status === "running" && !Number.isFinite(job.percent));
toast.querySelector(".track").hidden = ["error", "cancelled"].includes(job.status);
const transfer = toast.querySelector(".transfer");
updateText(transfer, downloadSizeText(job) || processingText(job)); transfer.hidden = !transfer.textContent;
let label = Number.isFinite(job.percent) ? `${Math.round(job.percent)}%` : "";
if (job.stage === "downloading" && Number.isFinite(job.speed) && job.speed >= 0) label += `${label ? " · " : ""}${(job.speed / 1048576).toFixed(1)} МБ/с`;
if (["merging", "remuxing", "encoding", "audio"].includes(job.stage) && job.speed) label += `${label ? " · " : ""}${job.speed}`;
if (["merging", "remuxing", "encoding", "audio"].includes(job.stage) && Number.isFinite(job.eta) && job.eta > 0) label += `${label ? " · " : ""}осталось ≈ ${Math.ceil(job.eta)} с`;
if (job.status === "complete") label = job.result?.filename || job.result?.title || "Готово";
if (job.status === "error") label = job.error || "Ошибка";
const readout = toast.querySelector(".label");
updateText(readout, label);
const tooltip = job.error || job.result?.path || label;
if (readout.title !== tooltip) readout.title = tooltip;
const action = toast.querySelector(".action");
updateText(action, job.status === "complete" ? "Открыть папку ↗" : "Отменить");
action.hidden = !["running", "complete"].includes(job.status) || job.canCancel === false || (["dependency", "update"].includes(job.kind) && job.status === "complete");
if (job.status !== "running" && !toastTimers.has(job.id)) toastTimers.set(job.id, setTimeout(() => dismiss(job.id), job.status === "error" ? 60000 : 20000));
}
chrome.runtime.onMessage.addListener(message => {
if (message.type === "JOB_PROGRESS") showJob(message.job);
if (message.type === "REMOVE_JOB") dismiss(message.jobId);
});
new MutationObserver(applyTheme).observe(document.documentElement, { attributes: true, attributeFilter: ["dark", "data-theme"] });
chrome.runtime.sendMessage({ type: "GET_CONTEXT" }).then(context => {
tabId = context?.tabId; for (const job of context?.jobs || []) if (job.status === "running") showJob(job);
}).catch(() => {});
})();