Release RedGifs Video Downloader 0.6.21
This commit is contained in:
commit
3ffa17b6c4
23 files changed
+5398
No files matched your search
@@ -0,0 +1,600 @@
|
||||
"use strict";
|
||||
|
||||
const urlInput = document.getElementById("urlInput");
|
||||
const resolveButton = document.getElementById("resolveButton");
|
||||
const settingsButton = document.getElementById("settingsButton");
|
||||
const settingsPanel = document.getElementById("settingsPanel");
|
||||
const baseFolderInput = document.getElementById("baseFolder");
|
||||
const groupByProfileInput = document.getElementById("groupByProfile");
|
||||
const saveAsInput = document.getElementById("saveAs");
|
||||
const settingsPathPreview = document.getElementById("settingsPathPreview");
|
||||
const settingsSaveButton = document.getElementById("settingsSaveButton");
|
||||
const settingsResetButton = document.getElementById("settingsResetButton");
|
||||
const settingsStatus = document.getElementById("settingsStatus");
|
||||
const alternativeToggle = document.getElementById("alternativeToggle");
|
||||
const alternativePanel = document.getElementById("alternativePanel");
|
||||
const diagnosticButton = document.getElementById("diagnosticButton");
|
||||
const diagnosticStatus = document.getElementById("diagnosticStatus");
|
||||
const saveDestination = document.getElementById("saveDestination");
|
||||
const statusBox = document.getElementById("status");
|
||||
const collectionBox = document.getElementById("collection");
|
||||
const collectionTitle = document.getElementById("collectionTitle");
|
||||
const collectionCount = document.getElementById("collectionCount");
|
||||
const collectionItems = document.getElementById("collectionItems");
|
||||
const resultBox = document.getElementById("result");
|
||||
const thumbnail = document.getElementById("thumbnail");
|
||||
const mediaTitle = document.getElementById("mediaTitle");
|
||||
const formatsBox = document.getElementById("formats");
|
||||
const progressCard = document.getElementById("progressCard");
|
||||
const progressLabel = document.getElementById("progressLabel");
|
||||
const progressValue = document.getElementById("progressValue");
|
||||
const progressBar = document.getElementById("progressBar");
|
||||
|
||||
let currentMedia = null;
|
||||
let pollingTimer = null;
|
||||
let activeTabId = null;
|
||||
let selectedProfileSlug = null;
|
||||
let activeTabUrl = "";
|
||||
let alternativeScanned = false;
|
||||
const DEFAULT_DOWNLOAD_SETTINGS = Object.freeze({
|
||||
baseFolder: "RedGifs",
|
||||
groupByProfile: true,
|
||||
saveAs: false
|
||||
});
|
||||
let downloadSettings = Object.assign({}, DEFAULT_DOWNLOAD_SETTINGS);
|
||||
let settingsStatusTimer = null;
|
||||
let popupBlurTimer = null;
|
||||
const panelAnimations = new WeakMap();
|
||||
|
||||
function panelIsExpanded(element) {
|
||||
if (element.dataset.popupExpanded === "true") return true;
|
||||
if (element.dataset.popupExpanded === "false") return false;
|
||||
return !element.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function clearPanelMotionStyles(element) {
|
||||
element.style.removeProperty("overflow");
|
||||
element.style.removeProperty("will-change");
|
||||
element.style.removeProperty("transform-origin");
|
||||
}
|
||||
|
||||
function setPanelExpanded(element, expanded, options) {
|
||||
const config = Object.assign({ duration: 260, offset: 8, scale: 0.985 }, options || {});
|
||||
const previous = panelAnimations.get(element);
|
||||
if (previous) previous.cancel();
|
||||
clearPanelMotionStyles(element);
|
||||
element.dataset.popupExpanded = String(expanded);
|
||||
|
||||
const reduceMotion = typeof window.matchMedia === "function"
|
||||
&& window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
if (reduceMotion || typeof element.animate !== "function") {
|
||||
element.classList.toggle("hidden", !expanded);
|
||||
panelAnimations.delete(element);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (expanded) element.classList.remove("hidden");
|
||||
if (!expanded && element.classList.contains("hidden")) return Promise.resolve();
|
||||
|
||||
const fullHeight = Math.max(1, Math.ceil(element.getBoundingClientRect().height));
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
const expandedBox = {
|
||||
marginTop: computedStyle.marginTop,
|
||||
marginBottom: computedStyle.marginBottom,
|
||||
paddingTop: computedStyle.paddingTop,
|
||||
paddingBottom: computedStyle.paddingBottom,
|
||||
borderTopWidth: computedStyle.borderTopWidth,
|
||||
borderBottomWidth: computedStyle.borderBottomWidth
|
||||
};
|
||||
element.style.overflow = "hidden";
|
||||
element.style.willChange = "height, margin, padding, border-width, opacity, transform";
|
||||
element.style.transformOrigin = "top center";
|
||||
|
||||
const collapsedFrame = {
|
||||
height: "0px",
|
||||
marginTop: "0px",
|
||||
marginBottom: "0px",
|
||||
paddingTop: "0px",
|
||||
paddingBottom: "0px",
|
||||
borderTopWidth: "0px",
|
||||
borderBottomWidth: "0px",
|
||||
opacity: 0,
|
||||
transform: `translateY(-${config.offset}px) scale(${config.scale})`
|
||||
};
|
||||
const expandedFrame = {
|
||||
height: fullHeight + "px",
|
||||
...expandedBox,
|
||||
opacity: 1,
|
||||
transform: "translateY(0) scale(1)"
|
||||
};
|
||||
const animation = element.animate(
|
||||
expanded ? [collapsedFrame, expandedFrame] : [expandedFrame, collapsedFrame],
|
||||
{
|
||||
duration: config.duration,
|
||||
easing: expanded ? "cubic-bezier(.22, 1, .36, 1)" : "cubic-bezier(.4, 0, .2, 1)"
|
||||
}
|
||||
);
|
||||
panelAnimations.set(element, animation);
|
||||
|
||||
return animation.finished.catch(() => {}).then(() => {
|
||||
if (panelAnimations.get(element) !== animation) return;
|
||||
if (!expanded) element.classList.add("hidden");
|
||||
clearPanelMotionStyles(element);
|
||||
panelAnimations.delete(element);
|
||||
});
|
||||
}
|
||||
|
||||
function reportPopupDiagnostic(level, event, details) {
|
||||
try {
|
||||
const pending = chrome.runtime.sendMessage({
|
||||
action: "diagnosticEvent",
|
||||
level,
|
||||
area: "popup",
|
||||
event,
|
||||
details: details || {}
|
||||
});
|
||||
if (pending && typeof pending.catch === "function") pending.catch(() => {});
|
||||
} catch {
|
||||
// A failed logger must not replace the original UI error.
|
||||
}
|
||||
}
|
||||
|
||||
function cleanFolderPath(value) {
|
||||
return String(value || "")
|
||||
.replace(/\\/g, "/")
|
||||
.split("/")
|
||||
.map((part) => part.replace(/[<>:"|?*\u0000-\u001f]/g, "_").replace(/[. ]+$/g, "").trim())
|
||||
.filter((part) => part && part !== "." && part !== "..")
|
||||
.slice(0, 8)
|
||||
.join("/");
|
||||
}
|
||||
|
||||
function profileFromUrl(value) {
|
||||
try {
|
||||
const match = new URL(value).pathname.match(/\/users\/([^/?#]+)/i);
|
||||
return match && match[1] ? decodeURIComponent(match[1]) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function diagnosticPageUrl(value) {
|
||||
try {
|
||||
const parsed = new URL(value);
|
||||
parsed.username = "";
|
||||
parsed.password = "";
|
||||
parsed.search = "";
|
||||
parsed.hash = "";
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return String(value || "").slice(0, 300);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSaveDestination() {
|
||||
saveDestination.textContent = "";
|
||||
if (downloadSettings.saveAs) {
|
||||
saveDestination.textContent = "Для каждого видео Chrome спросит место сохранения.";
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = ["Загрузки"];
|
||||
const baseFolder = cleanFolderPath(downloadSettings.baseFolder);
|
||||
if (baseFolder) parts.push(baseFolder);
|
||||
const profile = profileFromUrl(activeTabUrl);
|
||||
if (downloadSettings.groupByProfile && profile) parts.push(cleanFolderPath(profile));
|
||||
|
||||
saveDestination.appendChild(document.createTextNode("Видео сохраняются в: "));
|
||||
const strong = document.createElement("strong");
|
||||
strong.textContent = parts.join("/");
|
||||
saveDestination.appendChild(strong);
|
||||
}
|
||||
|
||||
function readSettingsForm() {
|
||||
return {
|
||||
baseFolder: cleanFolderPath(baseFolderInput.value),
|
||||
groupByProfile: groupByProfileInput.checked,
|
||||
saveAs: saveAsInput.checked
|
||||
};
|
||||
}
|
||||
|
||||
function renderSettingsPathPreview() {
|
||||
const settings = readSettingsForm();
|
||||
if (settings.saveAs) {
|
||||
settingsPathPreview.textContent = "Chrome спросит место сохранения";
|
||||
return;
|
||||
}
|
||||
const parts = ["Загрузки"];
|
||||
if (settings.baseFolder) parts.push(settings.baseFolder);
|
||||
if (settings.groupByProfile) parts.push(profileFromUrl(activeTabUrl) || "profile");
|
||||
parts.push("video.mp4");
|
||||
settingsPathPreview.textContent = parts.join("/");
|
||||
}
|
||||
|
||||
function writeSettingsForm(settings) {
|
||||
baseFolderInput.value = settings.baseFolder || "";
|
||||
groupByProfileInput.checked = settings.groupByProfile !== false;
|
||||
saveAsInput.checked = Boolean(settings.saveAs);
|
||||
renderSettingsPathPreview();
|
||||
}
|
||||
|
||||
function showSettingsStatus(text, kind) {
|
||||
if (settingsStatusTimer) clearTimeout(settingsStatusTimer);
|
||||
settingsStatusTimer = null;
|
||||
settingsStatus.textContent = text || "";
|
||||
settingsStatus.className = "settings-status" + (kind ? " " + kind : "");
|
||||
if (text) {
|
||||
settingsStatusTimer = setTimeout(() => {
|
||||
settingsStatus.textContent = "";
|
||||
settingsStatusTimer = null;
|
||||
}, 2600);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDownloadSettings() {
|
||||
const settings = readSettingsForm();
|
||||
settingsSaveButton.disabled = true;
|
||||
try {
|
||||
await chrome.storage.local.set({ downloadSettings: settings });
|
||||
downloadSettings = Object.assign({}, DEFAULT_DOWNLOAD_SETTINGS, settings);
|
||||
writeSettingsForm(downloadSettings);
|
||||
renderSaveDestination();
|
||||
showSettingsStatus("Настройки сохранены.");
|
||||
} catch (error) {
|
||||
reportPopupDiagnostic("error", "settings-save-failed", {
|
||||
name: error && error.name,
|
||||
message: error && error.message
|
||||
});
|
||||
showSettingsStatus(error && error.message ? error.message : "Не удалось сохранить настройки.", "error");
|
||||
} finally {
|
||||
settingsSaveButton.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function setStatus(text, kind) {
|
||||
statusBox.textContent = text;
|
||||
statusBox.className = "status" + (kind ? " " + kind : "");
|
||||
}
|
||||
|
||||
function setBusy(busy) {
|
||||
resolveButton.disabled = busy;
|
||||
resolveButton.textContent = busy ? "Сканирую…" : "Сканировать";
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (pollingTimer) {
|
||||
clearTimeout(pollingTimer);
|
||||
pollingTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function renderMedia(media) {
|
||||
setPanelExpanded(collectionBox, false, { duration: 210, offset: 5 });
|
||||
currentMedia = media;
|
||||
if (media.profileSlug) selectedProfileSlug = media.profileSlug;
|
||||
renderSaveDestination();
|
||||
mediaTitle.textContent = media.title || media.id || "RedGifs video";
|
||||
formatsBox.textContent = "";
|
||||
|
||||
if (media.thumbnail) {
|
||||
thumbnail.src = media.thumbnail;
|
||||
thumbnail.classList.remove("hidden");
|
||||
resultBox.classList.remove("no-image");
|
||||
} else {
|
||||
thumbnail.removeAttribute("src");
|
||||
thumbnail.classList.add("hidden");
|
||||
resultBox.classList.add("no-image");
|
||||
}
|
||||
|
||||
media.formats.forEach((format, index) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "format-button";
|
||||
button.textContent = "Скачать · " + format.label;
|
||||
button.addEventListener("click", () => downloadFormat(index, button));
|
||||
formatsBox.appendChild(button);
|
||||
});
|
||||
setPanelExpanded(resultBox, true, { duration: 240, offset: 6 });
|
||||
}
|
||||
|
||||
function renderCollection(collection) {
|
||||
currentMedia = null;
|
||||
setPanelExpanded(resultBox, false, { duration: 210, offset: 5 });
|
||||
collectionItems.textContent = "";
|
||||
collectionTitle.textContent = collection.title || "Видимые ролики";
|
||||
collectionCount.textContent = collection.items.length + " шт.";
|
||||
|
||||
collection.items.forEach((item) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "collection-item";
|
||||
|
||||
if (item.thumbnail) {
|
||||
const image = document.createElement("img");
|
||||
image.className = "collection-thumb";
|
||||
image.src = item.thumbnail;
|
||||
image.alt = "";
|
||||
button.appendChild(image);
|
||||
} else {
|
||||
button.classList.add("no-image");
|
||||
}
|
||||
|
||||
const copy = document.createElement("span");
|
||||
copy.className = "collection-copy";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = item.title || item.id || "RedGifs video";
|
||||
const id = document.createElement("small");
|
||||
id.textContent = item.id ? "ID: " + item.id : "Прямой источник";
|
||||
copy.appendChild(title);
|
||||
copy.appendChild(id);
|
||||
button.appendChild(copy);
|
||||
button.addEventListener("click", async () => {
|
||||
const target = item.watchUrl || item.directVideoUrl;
|
||||
if (!target) return;
|
||||
selectedProfileSlug = item.profileSlug || null;
|
||||
urlInput.value = target;
|
||||
await resolveCurrentUrl();
|
||||
});
|
||||
collectionItems.appendChild(button);
|
||||
});
|
||||
setPanelExpanded(collectionBox, true, { duration: 240, offset: 6 });
|
||||
}
|
||||
|
||||
async function resolveCurrentUrl() {
|
||||
const value = urlInput.value.trim();
|
||||
if (!value) {
|
||||
setStatus("Вставьте ссылку на страницу ролика.", "error");
|
||||
return;
|
||||
}
|
||||
stopPolling();
|
||||
setPanelExpanded(progressCard, false, { duration: 190, offset: 4 });
|
||||
setPanelExpanded(resultBox, false, { duration: 210, offset: 5 });
|
||||
setPanelExpanded(collectionBox, false, { duration: 210, offset: 5 });
|
||||
currentMedia = null;
|
||||
setBusy(true);
|
||||
setStatus("Получаю временный токен и адреса видео…");
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({ action: "resolve", url: value, tabId: activeTabId });
|
||||
if (!response || !response.ok) {
|
||||
throw new Error(response && response.error ? response.error : "Нет ответа от расширения.");
|
||||
}
|
||||
if (response.media.type === "collection") {
|
||||
renderCollection(response.media);
|
||||
setStatus("Найдены видимые ролики. Выберите нужный.", "success");
|
||||
} else {
|
||||
renderMedia(response.media);
|
||||
setStatus("Найдено форматов: " + response.media.formats.length, "success");
|
||||
}
|
||||
} catch (error) {
|
||||
reportPopupDiagnostic("error", "resolve-failed", {
|
||||
url: value,
|
||||
name: error && error.name,
|
||||
message: error && error.message
|
||||
});
|
||||
setStatus(error && error.message ? error.message : String(error), "error");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFormat(index, button) {
|
||||
if (!currentMedia || !currentMedia.formats[index]) return;
|
||||
const format = currentMedia.formats[index];
|
||||
button.disabled = true;
|
||||
setStatus(format.kind === "hls" ? "Запускаю локальную сборку HLS…" : "Запускаю локальную загрузку…");
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
action: "download",
|
||||
format,
|
||||
title: currentMedia.title,
|
||||
id: currentMedia.id,
|
||||
sourceUrl: currentMedia.sourceUrl,
|
||||
profileSlug: selectedProfileSlug,
|
||||
tabId: activeTabId
|
||||
});
|
||||
if (!response || !response.ok) {
|
||||
throw new Error(response && response.error ? response.error : "Загрузка не запущена.");
|
||||
}
|
||||
if (response.result.jobId) {
|
||||
setPanelExpanded(progressCard, true, { duration: 220, offset: 5 });
|
||||
setStatus(response.result.mode === "hls" ? "HLS загружается и собирается локально." : "Видео загружается локально.", "success");
|
||||
pollJob(response.result.jobId);
|
||||
} else {
|
||||
setStatus("Загрузка передана Chrome: " + response.result.filename, "success");
|
||||
}
|
||||
} catch (error) {
|
||||
reportPopupDiagnostic("error", "download-start-failed", {
|
||||
mediaId: currentMedia && currentMedia.id,
|
||||
format: format && format.label,
|
||||
name: error && error.name,
|
||||
message: error && error.message
|
||||
});
|
||||
setStatus(error && error.message ? error.message : String(error), "error");
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function pollJob(jobId) {
|
||||
stopPolling();
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({ action: "getJob", jobId });
|
||||
if (!response || !response.ok || !response.job) {
|
||||
throw new Error("Состояние HLS-задачи потеряно.");
|
||||
}
|
||||
const job = response.job;
|
||||
const progress = Math.max(0, Math.min(100, Math.round(Number(job.progress) || 0)));
|
||||
progressLabel.textContent = job.status || "Обработка HLS…";
|
||||
progressValue.textContent = progress + "%";
|
||||
progressBar.style.width = progress + "%";
|
||||
if (job.state === "complete") {
|
||||
setStatus("Готово: " + (job.filename || "файл передан Chrome"), "success");
|
||||
return;
|
||||
}
|
||||
if (job.state === "error") throw new Error(job.error || "Ошибка обработки HLS.");
|
||||
pollingTimer = setTimeout(() => pollJob(jobId), 500);
|
||||
} catch (error) {
|
||||
reportPopupDiagnostic("error", "job-poll-failed", {
|
||||
jobId,
|
||||
name: error && error.name,
|
||||
message: error && error.message
|
||||
});
|
||||
setStatus(error && error.message ? error.message : String(error), "error");
|
||||
}
|
||||
}
|
||||
|
||||
resolveButton.addEventListener("click", () => {
|
||||
selectedProfileSlug = null;
|
||||
resolveCurrentUrl();
|
||||
});
|
||||
urlInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
selectedProfileSlug = null;
|
||||
resolveCurrentUrl();
|
||||
}
|
||||
});
|
||||
settingsButton.addEventListener("click", () => {
|
||||
const opening = !panelIsExpanded(settingsPanel);
|
||||
setPanelExpanded(settingsPanel, opening, { duration: 300, offset: 10, scale: 0.98 });
|
||||
settingsButton.setAttribute("aria-expanded", String(opening));
|
||||
settingsButton.setAttribute("aria-label", opening ? "Скрыть настройки" : "Открыть настройки");
|
||||
settingsButton.title = opening ? "Скрыть настройки" : "Настройки";
|
||||
if (opening) renderSettingsPathPreview();
|
||||
});
|
||||
window.addEventListener("focus", () => {
|
||||
if (popupBlurTimer) clearTimeout(popupBlurTimer);
|
||||
popupBlurTimer = null;
|
||||
});
|
||||
window.addEventListener("blur", () => {
|
||||
if (popupBlurTimer) clearTimeout(popupBlurTimer);
|
||||
popupBlurTimer = setTimeout(() => {
|
||||
popupBlurTimer = null;
|
||||
if (!document.hasFocus()) window.close();
|
||||
}, 140);
|
||||
});
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
window.focus();
|
||||
} catch {
|
||||
// Browser-native popup focus remains the fallback.
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") window.close();
|
||||
});
|
||||
baseFolderInput.addEventListener("input", renderSettingsPathPreview);
|
||||
groupByProfileInput.addEventListener("change", renderSettingsPathPreview);
|
||||
saveAsInput.addEventListener("change", renderSettingsPathPreview);
|
||||
settingsSaveButton.addEventListener("click", saveDownloadSettings);
|
||||
settingsResetButton.addEventListener("click", () => {
|
||||
writeSettingsForm(DEFAULT_DOWNLOAD_SETTINGS);
|
||||
showSettingsStatus("Значения сброшены — нажмите «Сохранить».");
|
||||
});
|
||||
diagnosticButton.addEventListener("click", async () => {
|
||||
diagnosticButton.disabled = true;
|
||||
diagnosticStatus.className = "diagnostic-status";
|
||||
diagnosticStatus.textContent = "Собираю DOM, события и сетевые ошибки…";
|
||||
try {
|
||||
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
const tab = tabs && tabs[0];
|
||||
if (!tab || !/^https:\/\/(?:[^/]+\.)?redgifs\.com\//i.test(tab.url || "")) {
|
||||
throw new Error("Откройте вкладку RedGifs и повторите.");
|
||||
}
|
||||
let pageDiagnostics = null;
|
||||
let pageError = null;
|
||||
let runtimeDiagnostics = null;
|
||||
let runtimeError = null;
|
||||
try {
|
||||
const response = await chrome.tabs.sendMessage(tab.id, { action: "captureExtensionDiagnostics" });
|
||||
if (!response || !response.ok || !response.report) {
|
||||
throw new Error(response && response.error ? response.error : "Снимок страницы не получен.");
|
||||
}
|
||||
pageDiagnostics = response.report;
|
||||
} catch (error) {
|
||||
pageError = error && error.message ? error.message : String(error);
|
||||
}
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({ action: "getDiagnosticLog" });
|
||||
if (!response || !response.ok || !response.diagnostics) {
|
||||
throw new Error(response && response.error ? response.error : "Журнал фонового процесса не получен.");
|
||||
}
|
||||
runtimeDiagnostics = response.diagnostics;
|
||||
} catch (error) {
|
||||
runtimeError = error && error.message ? error.message : String(error);
|
||||
}
|
||||
if (!pageDiagnostics && !runtimeDiagnostics) {
|
||||
throw new Error("Диагностический лог не получен. Обновите вкладку RedGifs.");
|
||||
}
|
||||
const report = {
|
||||
schemaVersion: 2,
|
||||
generatedAt: new Date().toISOString(),
|
||||
extensionVersion: chrome.runtime.getManifest ? chrome.runtime.getManifest().version : null,
|
||||
activeTab: { id: tab.id, url: diagnosticPageUrl(tab.url) },
|
||||
collectionErrors: { page: pageError, runtime: runtimeError },
|
||||
pageDiagnostics,
|
||||
runtimeDiagnostics
|
||||
};
|
||||
const saveResponse = await chrome.runtime.sendMessage({
|
||||
action: "downloadDiagnosticReport",
|
||||
report
|
||||
});
|
||||
if (!saveResponse || !saveResponse.ok || !saveResponse.result) {
|
||||
throw new Error(saveResponse && saveResponse.error
|
||||
? saveResponse.error
|
||||
: "Фоновый процесс не смог сохранить диагностический лог.");
|
||||
}
|
||||
diagnosticStatus.className = "diagnostic-status success";
|
||||
const runtimeEvents = runtimeDiagnostics && Array.isArray(runtimeDiagnostics.events)
|
||||
? runtimeDiagnostics.events
|
||||
: [];
|
||||
const errorCount = runtimeEvents.filter((entry) => entry.level === "error").length;
|
||||
diagnosticStatus.textContent = "Лог сохранён: событий — " + runtimeEvents.length + ", ошибок — " + errorCount + ".";
|
||||
} catch (error) {
|
||||
diagnosticStatus.className = "diagnostic-status error";
|
||||
diagnosticStatus.textContent = error && error.message ? error.message : String(error);
|
||||
} finally {
|
||||
diagnosticButton.disabled = false;
|
||||
}
|
||||
});
|
||||
alternativeToggle.addEventListener("click", async () => {
|
||||
const opening = !panelIsExpanded(alternativePanel);
|
||||
alternativeToggle.setAttribute("aria-expanded", String(opening));
|
||||
if (!opening) {
|
||||
stopPolling();
|
||||
await setPanelExpanded(alternativePanel, false, { duration: 260, offset: 8, scale: 0.985 });
|
||||
return;
|
||||
}
|
||||
await setPanelExpanded(alternativePanel, true, { duration: 300, offset: 10, scale: 0.98 });
|
||||
if (!alternativeScanned && urlInput.value.trim()) {
|
||||
alternativeScanned = true;
|
||||
await resolveCurrentUrl();
|
||||
}
|
||||
});
|
||||
|
||||
(async () => {
|
||||
writeSettingsForm(downloadSettings);
|
||||
try {
|
||||
const stored = await chrome.storage.local.get("downloadSettings");
|
||||
downloadSettings = Object.assign({}, DEFAULT_DOWNLOAD_SETTINGS, stored.downloadSettings || {});
|
||||
writeSettingsForm(downloadSettings);
|
||||
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
const tab = tabs && tabs[0];
|
||||
if (tab && /^https:\/\/(?:[^/]+\.)?redgifs\.com\//i.test(tab.url || "")) {
|
||||
activeTabId = tab.id;
|
||||
activeTabUrl = tab.url;
|
||||
urlInput.value = tab.url;
|
||||
}
|
||||
renderSaveDestination();
|
||||
renderSettingsPathPreview();
|
||||
} catch {
|
||||
writeSettingsForm(downloadSettings);
|
||||
renderSaveDestination();
|
||||
}
|
||||
})();
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, areaName) => {
|
||||
if (areaName !== "local" || !changes.downloadSettings) return;
|
||||
downloadSettings = Object.assign({}, DEFAULT_DOWNLOAD_SETTINGS, changes.downloadSettings.newValue || {});
|
||||
writeSettingsForm(downloadSettings);
|
||||
renderSaveDestination();
|
||||
});
|
||||
Reference in new issue
Block a user