// ==UserScript== // @name Rezka UX Cleaner Safe // @namespace local.rezka.ux-cleaner-safe // @version 0.8.0 // @description Убирает branded-пустоту, VK/соцвиджеты, Premium-мусор и добавляет безопасное скрытие комментариев. // @match https://rezka-ua.tv/* // @match https://*.rezka-ua.tv/* // @run-at document-idle // @grant none // ==/UserScript== (function () { "use strict"; const PANEL_ID = "rezka-cleaner-panel"; const STYLE_ID = "rezka-cleaner-style"; const HIDDEN_CLASS = "rezka-cleaner-hidden"; const BODY_CLASS_ENABLED = "rezka-cleaner-enabled"; const BODY_CLASS_COMMENTS_HIDDEN = "rezka-cleaner-comments-hidden"; const ATTR_MUTED_HREF = "data-rezka-cleaner-muted-href"; const AUTO_SCROLL_BUTTON_ID = "rezka-cleaner-autoscroll-toggle"; const STORAGE = { commentsHidden: "rezkaCleaner.commentsHidden", cleanerEnabled: "rezkaCleaner.enabled", autoOriginalSeenByTitle: "rezkaCleaner.autoOriginalSeenByTitle", autoScrollToPlayer: "rezkaCleaner.autoScrollToPlayer", }; const PAGE_CLASSES_TO_REMOVE = [ "has-brand", "active-brand", "pp", ]; const VK_LINK_SELECTOR = [ "a[href*='vk.com']", "a[href*='vk.ru']", "a[href*='vk.cc']", "a[href*='vkontakte.ru']", ].join(","); const VK_WIDGET_SELECTOR = [ "#vk_groups", "#vk_like", "#vk_comments", ".vk_groups", ".vk-like", ".vk-widget", ".vk-widget-wrapper", "[id*='vk_groups']", "[id*='vk_like']", "[id*='vk_comments']", "iframe[src*='vk.com']", "iframe[src*='vk.ru']", "iframe[src*='vkontakte.ru']", ].join(","); const SOCIAL_LINK_SELECTOR = [ "a[href*='vk.com/share']", "a[href*='connect.ok.ru/offer']", "a[href*='facebook.com/sharer']", "a[href*='x.com/share']", "a[href*='twitter.com/share']", "a[href*='wa.me']", "a[href*='t.me/share']", "a[href^='viber://']", ].join(","); const PREMIUM_LINK_SELECTOR = [ "a[href='/payments']", "a[href='/payments/']", "a[href*='/payments/']", "a[href*='/payments?']", "a[href*='rezka-ua.tv/payments']", ].join(","); const SAFE_COMMENT_SELECTORS = [ "#addcomment-title", "#comments-form", "#comments-list-button", "#hd-comments-list", "#hd-comments-navigation", ".b-sideactions__reviews", ".comments-form", "#comments", "#comments-list", ".comments", ".comments-list", ".b-comments", ".b-comments__list", ".b-post__comments", ].join(","); const SAFE_TOP_BRAND_SELECTORS = [ ".b-brand", ".b-brand__wrapper", ".b-brand__content", ".b-branding", ".b-header-brand", ".b-top-brand", ".top-brand", ".brand-wrapper", ".branding", "#brand", "#branding", ].join(","); const TOP_SUBSCRIBE_SELECTOR = [ ".b-tophead__subscribe-dropdown", ".b-tophead__subscribe-dropdown-inner", ".b-tophead__subscribe-dropdown-list", ].join(","); let cleanTimer = null; let heavyCleanupTimer = null; let bootDone = false; let autoOriginalApplied = false; let playerAutoScrollApplied = false; boot(); function boot() { if (bootDone) return; if (!document.head || !document.body) { window.setTimeout(boot, 50); return; } bootDone = true; injectCss(); applyEnabledState(); runSafeCleanup(); createPanel(); applyAutoOriginalOnce(); scrollToPlayerOnOpen(); observeChanges(); window.addEventListener("resize", () => { window.setTimeout(updateTopMenuLayout, 60); }); } function injectCss() { if (document.getElementById(STYLE_ID)) return; const css = ` body.${BODY_CLASS_ENABLED} { --rezka-cleaner-top-head-height: 40px; --rezka-cleaner-top-nav-height: 66px; --rezka-cleaner-top-offset: calc( var(--rezka-cleaner-top-head-height) + var(--rezka-cleaner-top-nav-height) ); margin-top: 0 !important; padding-top: 0 !important; } body.${BODY_CLASS_ENABLED} #top-head { position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; width: 100% !important; z-index: 2147481100 !important; } body.${BODY_CLASS_ENABLED} #top-nav { position: fixed !important; top: var(--rezka-cleaner-top-head-height) !important; left: 0 !important; right: 0 !important; width: 100% !important; z-index: 2147481099 !important; } body.${BODY_CLASS_ENABLED} #top-nav-wrapper { display: block !important; height: var(--rezka-cleaner-top-offset) !important; } .${HIDDEN_CLASS} { display: none !important; } body.${BODY_CLASS_ENABLED} ${SAFE_TOP_BRAND_SELECTORS}, body.${BODY_CLASS_ENABLED} ${TOP_SUBSCRIBE_SELECTOR}, body.${BODY_CLASS_ENABLED} ${VK_WIDGET_SELECTOR} { display: none !important; } body.${BODY_CLASS_ENABLED} ${SAFE_TOP_BRAND_SELECTORS} { height: 0 !important; min-height: 0 !important; max-height: 0 !important; margin: 0 !important; padding: 0 !important; overflow: hidden !important; } body.${BODY_CLASS_COMMENTS_HIDDEN} #comments, body.${BODY_CLASS_COMMENTS_HIDDEN} #comments-list, body.${BODY_CLASS_COMMENTS_HIDDEN} .comments, body.${BODY_CLASS_COMMENTS_HIDDEN} .comments-list, body.${BODY_CLASS_COMMENTS_HIDDEN} .b-comments, body.${BODY_CLASS_COMMENTS_HIDDEN} .b-comments__list, body.${BODY_CLASS_COMMENTS_HIDDEN} .b-post__comments { display: none !important; } body.${BODY_CLASS_ENABLED}.b-theme__template__night .b-post__schedule_table .current-episode { background: none !important; } #${PANEL_ID} { position: fixed; right: 14px; bottom: 14px; z-index: 2147483647; width: 215px; box-sizing: border-box; padding: 10px; border-radius: 12px; background: rgba(18, 18, 18, 0.92); color: #fff; font: 13px/1.35 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } #${PANEL_ID} strong { display: block; margin-bottom: 7px; } #${PANEL_ID} button { display: block; width: 100%; margin: 6px 0 0; padding: 7px 8px; border: 0; border-radius: 8px; cursor: pointer; font: inherit; color: #111; background: #f2f2f2; } #${PANEL_ID} button:hover { filter: brightness(0.94); } #${PANEL_ID} button[data-active="true"] { background: #bff8c7; } #rezka-cleaner-original-button[data-selected="true"] { background: #bff8c7; color: #0d3b16; } #${PANEL_ID} .rezka-cleaner-status { margin-top: 7px; font-size: 11px; opacity: 0.8; } `; const style = document.createElement("style"); style.id = STYLE_ID; style.textContent = css; document.head.appendChild(style); } function runSafeCleanup(options) { const { skipHeavyBlocks = false } = options || {}; try { if (!document.body) return; applyEnabledState(); if (!isCleanerEnabled()) { unhideManagedElements(); restoreMutedLinks(); updatePanelState(); return; } removeTopBrandClasses(); updateTopMenuLayout(); hideTopBrandElements(); hideVkLinksAndWidgets(); hideSocialLinks(); hidePremiumLinksAndSmallBlocks(); if (!skipHeavyBlocks) { collapseLargeEmptyBlocks(); } applyCommentsState(); updatePanelState(); } catch (error) { console.warn("[Rezka UX Cleaner Safe]", error); } } function isCleanerEnabled() { return getStoredBoolean(STORAGE.cleanerEnabled, true); } function applyEnabledState() { if (!document.body) return; const enabled = isCleanerEnabled(); document.body.classList.toggle(BODY_CLASS_ENABLED, enabled); if (!enabled) { document.body.classList.remove(BODY_CLASS_COMMENTS_HIDDEN); unhideManagedElements(); restoreMutedLinks(); clearTopMenuLayout(); } } function toggleCleanerEnabled() { const current = isCleanerEnabled(); setStoredBoolean(STORAGE.cleanerEnabled, !current); applyEnabledState(); runSafeCleanup(); } function isAutoScrollToPlayerEnabled() { return getStoredBoolean(STORAGE.autoScrollToPlayer, true); } function toggleAutoScrollToPlayer() { const current = isAutoScrollToPlayerEnabled(); setStoredBoolean(STORAGE.autoScrollToPlayer, !current); updatePanelState(); } function updateTopMenuLayout() { if (!isCleanerEnabled()) return; const root = document.documentElement; const topHead = document.getElementById("top-head"); const topNav = document.getElementById("top-nav"); if (!root || !topHead || !topNav) return; const topHeadHeight = Math.max( 36, Math.round(topHead.getBoundingClientRect().height || 0) ); const topNavHeight = Math.max( 48, Math.round(topNav.getBoundingClientRect().height || 0) ); root.style.setProperty( "--rezka-cleaner-top-head-height", `${topHeadHeight}px` ); root.style.setProperty( "--rezka-cleaner-top-nav-height", `${topNavHeight}px` ); } function clearTopMenuLayout() { const root = document.documentElement; if (!root) return; root.style.removeProperty("--rezka-cleaner-top-head-height"); root.style.removeProperty("--rezka-cleaner-top-nav-height"); } function removeTopBrandClasses() { const roots = [document.documentElement, document.body].filter(Boolean); for (const root of roots) { for (const className of PAGE_CLASSES_TO_REMOVE) { root.classList.remove(className); } } } function hideTopBrandElements() { document.querySelectorAll(SAFE_TOP_BRAND_SELECTORS).forEach(hideElement); const layoutSelectors = [ "body", "#wrapper", ".wrapper", ".b-wrapper", ".l-page", ".b-content", ".b-layout", ].join(","); document.querySelectorAll(layoutSelectors).forEach((el) => { el.style.setProperty("margin-top", "0", "important"); el.style.setProperty("padding-top", "0", "important"); }); } function hideVkLinksAndWidgets() { document.querySelectorAll(VK_LINK_SELECTOR).forEach((link) => { markLinkAsMuted(link); link.setAttribute("aria-hidden", "true"); hideElement(link); hideNearbyVkSubscribeButton(link); }); document.querySelectorAll(VK_WIDGET_SELECTOR).forEach((el) => { hideElement(el); hideCompactContainer(el, { maxTextLength: 220, maxLinks: 4, maxDepth: 4, requiredTextParts: ["vk"], }); }); document.querySelectorAll("div, section, aside, td").forEach((el) => { if (el.closest(`#${PANEL_ID}`)) return; if (el.closest("#top-head, #top-nav, .b-tophead_wrapper, .b-topnav_wrapper")) return; if (el.children.length > 10) return; const text = normalizeText(el.textContent); if (text.length === 0 || text.length > 500) return; if ( text.includes("follow on vk") || (text.includes("подпишись") && text.includes("vk")) || (text.includes("вконтакте") && text.length < 300) ) { hideCompactContainer(el, { maxTextLength: 320, maxLinks: 6, maxDepth: 2, requiredTextParts: [], }); } }); } function hideNearbyVkSubscribeButton(link) { const parent = link.parentElement; if (!parent) return; parent.querySelectorAll("button").forEach((button) => { const text = normalizeText(button.textContent); if ( text.includes("подпишись") || text.includes("подписаться") || text.includes("вконтакте") ) { hideElement(button); } }); } function hideSocialLinks() { document.querySelectorAll(SOCIAL_LINK_SELECTOR).forEach((link) => { markLinkAsMuted(link); link.setAttribute("aria-hidden", "true"); hideElement(link); }); document.querySelectorAll("p, span, div, td").forEach((el) => { if (el.closest(`#${PANEL_ID}`)) return; if (el.children.length > 3) return; const text = normalizeText(el.textContent); if (text === "расскажи друзьям о своих впечатлениях:") { hideElement(el); } }); } function hidePremiumLinksAndSmallBlocks() { document.querySelectorAll(".b-post__lastepisodeout").forEach((el) => { hideElement(el); }); hideMobilePlayerInfoHint(); document.querySelectorAll(PREMIUM_LINK_SELECTOR).forEach((link) => { markLinkAsMuted(link); link.setAttribute("aria-hidden", "true"); hideElement(link); hideCompactContainer(link, { maxTextLength: 240, maxLinks: 3, maxDepth: 4, requiredTextParts: ["premium"], }); }); document.querySelectorAll("div, p, span, td").forEach((el) => { if (el.closest(`#${PANEL_ID}`)) return; if (el.children.length > 8) return; const text = normalizeText(el.textContent); if (text.length === 0 || text.length > 500) return; const isPremiumNoise = text.includes("перейти на premium") || text.includes("доступен только для premium") || text.includes("только для premium") || text.includes("доступний тільки для premium") || text.includes("перейти на premium і дивитися"); if (isPremiumNoise && text.length < 300) { hideElement(el); } }); } function hideMobilePlayerInfoHint() { document.querySelectorAll(".b-post__mixedtext").forEach((el) => { if (el.closest(`#${PANEL_ID}`)) return; const text = normalizeText(el.textContent || ""); const isMobilePlayerHint = text.includes("плеер также доступен на телефоне и планшете") && text.includes("iphone") && text.includes("ipad"); if (isMobilePlayerHint) { hideElement(el); } }); } function collapseLargeEmptyBlocks() { const main = document.querySelector(".b-content__main"); if (!main) return; collapsePrioritySpacerBlocks(main); main.querySelectorAll("div, section, aside").forEach((el) => { if (!(el instanceof HTMLElement)) return; if (el.closest(`#${PANEL_ID}`)) return; if (isProtectedMainContentBlock(el, main)) return; if (!isElementVisibleForCleanup(el)) return; const rect = el.getBoundingClientRect(); if (rect.height < 120) return; if (hasRichMediaContent(el)) return; const text = normalizeText(el.textContent || ""); const linksCount = el.querySelectorAll("a").length; const nodesCount = el.querySelectorAll("*").length; const styleAttr = (el.getAttribute("style") || "").toLowerCase(); const hasHeightStyle = styleAttr.includes("height"); const hasHiddenOverflowStyle = styleAttr.includes("overflow") && styleAttr.includes("hidden"); const hasSemanticText = Boolean(el.querySelector("h1, h2, h3, h4, strong, b, p, ul, ol")); const looksEmpty = text.length <= 28 && linksCount <= 1 && nodesCount <= 20 && !hasSemanticText; const fixedSpacerCandidate = hasHeightStyle && (hasHiddenOverflowStyle || rect.height >= 220) && text.length <= 120 && linksCount <= 3 && !hasSemanticText; if (looksEmpty || fixedSpacerCandidate) { hideElement(el); } }); } function collapsePrioritySpacerBlocks(main) { const mixedBlocks = Array.from(main.querySelectorAll(".b-post__mixedtext")); mixedBlocks.forEach((mixed) => { let sibling = mixed.nextElementSibling; while (sibling && !sibling.matches(".b-post__lastepisodeout")) { const current = sibling; sibling = sibling.nextElementSibling; if (!(current instanceof HTMLElement)) continue; if (!isElementVisibleForCleanup(current)) continue; if (isProtectedMainContentBlock(current, main)) continue; const styleAttr = (current.getAttribute("style") || "").toLowerCase(); const hasFixedHeightStyle = styleAttr.includes("height") && styleAttr.includes("overflow"); const computed = window.getComputedStyle(current); const hasHiddenOverflow = computed.overflow === "hidden" || computed.overflowY === "hidden" || computed.overflowX === "hidden"; const rect = current.getBoundingClientRect(); if (hasFixedHeightStyle && hasHiddenOverflow && rect.height >= 120) { hideElement(current); } } }); } function isProtectedMainContentBlock(el, main) { if (el === main) return true; if (el.matches("#player, #cdnplayer-container, #cdnplayer")) return true; if (el.matches("#translators-list, .b-post__lastepisodeout, .b-post__schedule")) return true; if (el.matches(".b-post__rating_table, .b-post__actions, .b-post__social_holder_wrapper")) return true; if (el.matches(".b-post__infotable, .b-post__description, .b-post__mixedtext")) return true; if (el.matches(".b-post__title, .b-post__origtitle, .b-content__htools")) return true; if (el.matches(".b-post__status_wrapper, .b-post__schedule_dates, .b-post__schedule_table")) return true; if (el.querySelector("#player, #cdnplayer-container, #cdnplayer")) return true; if (el.querySelector("#translators-list, .b-post__lastepisodeout, .b-post__schedule")) return true; if (el.querySelector(".b-post__rating_table, .b-post__actions, .b-post__social_holder_wrapper")) return true; if (el.querySelector(".b-post__infotable, .b-post__description, .b-post__mixedtext")) return true; if (el.querySelector(".b-post__title, .b-post__origtitle, .b-content__htools")) return true; if (el.querySelector(".b-post__status_wrapper, .b-post__schedule_dates, .b-post__schedule_table")) return true; return false; } function hasRichMediaContent(el) { return Boolean( el.querySelector( "#player, #cdnplayer, #cdnplayer-container, iframe, img, video, table" ) ); } function isElementVisibleForCleanup(el) { const computed = window.getComputedStyle(el); if (computed.display === "none") return false; if (computed.visibility === "hidden") return false; if (Number(computed.opacity || "1") === 0) return false; const rect = el.getBoundingClientRect(); if (rect.width < 2 || rect.height < 2) return false; return true; } function hideCompactContainer(startEl, options) { const { maxTextLength = 240, maxLinks = 4, maxDepth = 3, requiredTextParts = [], } = options || {}; let el = startEl; for ( let depth = 0; depth <= maxDepth && el && el !== document.body; depth += 1 ) { if (el.closest?.(`#${PANEL_ID}`)) return; if (el.matches?.("#top-head, #top-nav, .b-tophead_wrapper, .b-topnav_wrapper")) { return; } const text = normalizeText(el.textContent); const linkCount = el.querySelectorAll?.("a").length || 0; const childCount = el.querySelectorAll?.("*").length || 0; const hasRequiredText = requiredTextParts.every((part) => text.includes(part) ); const isCompact = text.length > 0 && text.length <= maxTextLength && linkCount <= maxLinks && childCount <= 40; if (isCompact && hasRequiredText) { hideElement(el); return; } el = el.parentElement; } hideElement(startEl); } function markLinkAsMuted(link) { if (!link || !(link instanceof HTMLAnchorElement)) return; if (!link.hasAttribute("href")) return; if (link.hasAttribute(ATTR_MUTED_HREF)) return; link.setAttribute(ATTR_MUTED_HREF, link.getAttribute("href") || ""); } function restoreMutedLinks() { document.querySelectorAll(`a[${ATTR_MUTED_HREF}]`).forEach((link) => { const href = link.getAttribute(ATTR_MUTED_HREF); if (href) { link.setAttribute("href", href); } else { link.removeAttribute("href"); } link.removeAttribute(ATTR_MUTED_HREF); link.removeAttribute("aria-hidden"); }); } function unhideManagedElements() { document.querySelectorAll(`.${HIDDEN_CLASS}`).forEach((el) => { el.classList.remove(HIDDEN_CLASS); el.style.removeProperty("display"); }); } function applyCommentsState() { if (!document.body) return; if (!isCleanerEnabled()) { document.body.classList.remove(BODY_CLASS_COMMENTS_HIDDEN); document.querySelectorAll(SAFE_COMMENT_SELECTORS).forEach((el) => { el.classList.remove(HIDDEN_CLASS); }); return; } const hidden = getStoredBoolean(STORAGE.commentsHidden, true); document.body.classList.toggle(BODY_CLASS_COMMENTS_HIDDEN, hidden); document.querySelectorAll(SAFE_COMMENT_SELECTORS).forEach((el) => { if (el.closest(`#${PANEL_ID}`)) return; el.classList.toggle(HIDDEN_CLASS, hidden); }); } function toggleComments() { if (!isCleanerEnabled()) return; const current = getStoredBoolean(STORAGE.commentsHidden, true); setStoredBoolean(STORAGE.commentsHidden, !current); applyCommentsState(); updatePanelState(); } function isOriginalSubtitlesLabel(value) { const text = normalizeText(value); return ( text.includes("оригинал") || text.includes("субтитры") || text.includes("original") ); } function findOriginalTranslatorItem() { return document.querySelector("#translators-list [data-translator_id='238']"); } function isCurrentOriginalSubtitlesPage() { const activeTranslator = document.querySelector( "#translators-list .b-translator__item.active, #translators-list .active[data-translator_id]" ); if (activeTranslator?.getAttribute("data-translator_id") === "238") { return true; } const currentPath = window.location.pathname || ""; const currentHash = window.location.hash || ""; return ( currentPath.includes("/238-subtitles") || currentHash.includes("t:238") ); } function findOriginalSubtitlesLink() { const translatorLinks = Array.from( document.querySelectorAll("#translators-list a[href]") ); const directTranslatorLink = translatorLinks.find((link) => { return isOriginalSubtitlesLabel(link.textContent); }); if (directTranslatorLink) return directTranslatorLink; const links = Array.from(document.querySelectorAll("a[href]")); return links.find((link) => { const text = normalizeText(link.textContent); const href = link.href || ""; return ( isOriginalSubtitlesLabel(text) || href.includes("subtitles") || href.includes("translator=original") ); }); } function goToOriginalSubtitles() { return goToOriginalSubtitlesWithOptions({ silent: false }); } function goToOriginalSubtitlesWithOptions(options) { const { silent = false } = options || {}; const link = findOriginalSubtitlesLink(); if (link?.href) { const targetUrl = new URL(link.href, window.location.href); const currentUrl = new URL(window.location.href); const samePathAndQuery = targetUrl.pathname === currentUrl.pathname && targetUrl.search === currentUrl.search; if (samePathAndQuery && (!targetUrl.hash || targetUrl.hash === currentUrl.hash)) { updatePanelState(); return true; } if (samePathAndQuery && targetUrl.hash && targetUrl.hash !== currentUrl.hash) { window.location.hash = targetUrl.hash; updatePanelState(); return true; } window.location.href = link.href; return true; } const translatorItem = findOriginalTranslatorItem(); if (translatorItem) { translatorItem.dispatchEvent( new MouseEvent("click", { bubbles: true, cancelable: true, view: window, }) ); window.setTimeout(() => { runSafeCleanup(); }, 350); return true; } if (!silent) { alert("Не нашёл переключатель на оригинал/субтитры на этой странице."); } return false; } function applyAutoOriginalOnce() { if (autoOriginalApplied) return; autoOriginalApplied = true; if (!isCleanerEnabled()) return; const titleKey = getCurrentTitleKey(); if (titleKey && isAutoOriginalSeenForTitle(titleKey)) { updatePanelState(); return; } if (isCurrentOriginalSubtitlesPage()) { if (titleKey) { setAutoOriginalSeenForTitle(titleKey, true); } updatePanelState(); return; } const switched = goToOriginalSubtitlesWithOptions({ silent: true }); if (switched && titleKey) { setAutoOriginalSeenForTitle(titleKey, true); } } function pageMentionsPremiumOnly() { const premiumSelectors = [ "#b-post__prem_holder", ".b-post__prem_content", ".b-post__prem_content-text", "#prem-link-head", "#prem-link-foot", ].join(","); const isPremiumText = (text) => text.includes("только для premium") || text.includes("перевод доступен только для premium") || text.includes("переклад доступний тільки для premium"); const hasVisiblePremiumBlock = Array.from( document.querySelectorAll(premiumSelectors) ).some((el) => { const style = window.getComputedStyle(el); if (style.display === "none" || style.visibility === "hidden") return false; if (Number(style.opacity || "1") === 0) return false; const rect = el.getBoundingClientRect(); if (rect.width < 2 || rect.height < 2) return false; return isPremiumText(normalizeText(el.textContent || "")); }); return hasVisiblePremiumBlock; } function createPanel() { if (!document.body) return; if (document.getElementById(PANEL_ID)) return; const panel = document.createElement("div"); panel.id = PANEL_ID; const title = document.createElement("strong"); title.textContent = "Rezka Cleaner"; const enableButton = document.createElement("button"); enableButton.type = "button"; enableButton.id = "rezka-cleaner-enable-toggle"; enableButton.addEventListener("click", toggleCleanerEnabled); const commentsButton = document.createElement("button"); commentsButton.type = "button"; commentsButton.id = "rezka-cleaner-comments-toggle"; commentsButton.addEventListener("click", toggleComments); const originalButton = document.createElement("button"); originalButton.type = "button"; originalButton.id = "rezka-cleaner-original-button"; originalButton.textContent = "Оригинал + субтитры"; originalButton.addEventListener("click", goToOriginalSubtitles); const autoScrollButton = document.createElement("button"); autoScrollButton.type = "button"; autoScrollButton.id = AUTO_SCROLL_BUTTON_ID; autoScrollButton.addEventListener("click", toggleAutoScrollToPlayer); const status = document.createElement("div"); status.className = "rezka-cleaner-status"; status.id = "rezka-cleaner-status"; panel.append( title, enableButton, commentsButton, originalButton, autoScrollButton, status ); document.body.appendChild(panel); updatePanelState(); } function updatePanelState() { const enableButton = document.getElementById("rezka-cleaner-enable-toggle"); const commentsButton = document.getElementById( "rezka-cleaner-comments-toggle" ); const originalButton = document.getElementById( "rezka-cleaner-original-button" ); const autoScrollButton = document.getElementById(AUTO_SCROLL_BUTTON_ID); const status = document.getElementById("rezka-cleaner-status"); const enabled = isCleanerEnabled(); if (enableButton) { enableButton.textContent = enabled ? "Очистка: включена" : "Очистка: выключена"; enableButton.setAttribute("data-active", enabled ? "true" : "false"); } if (commentsButton) { const commentsHidden = getStoredBoolean(STORAGE.commentsHidden, true); commentsButton.disabled = !enabled; commentsButton.textContent = !enabled ? "Комментарии (недоступно)" : commentsHidden ? "Показать комментарии" : "Скрыть комментарии"; } if (originalButton) { originalButton.setAttribute( "data-selected", isCurrentOriginalSubtitlesPage() ? "true" : "false" ); } if (autoScrollButton) { const autoScrollEnabled = isAutoScrollToPlayerEnabled(); autoScrollButton.disabled = !enabled; autoScrollButton.textContent = !enabled ? "Автопереход к плееру (недоступно)" : autoScrollEnabled ? "Автопереход к плееру: вкл" : "Автопереход к плееру: выкл"; autoScrollButton.setAttribute( "data-active", autoScrollEnabled && enabled ? "true" : "false" ); } if (status) { if (!enabled) { status.textContent = "Очистка выключена."; return; } const originalLink = findOriginalSubtitlesLink(); const originalTranslatorItem = findOriginalTranslatorItem(); const premiumOnly = pageMentionsPremiumOnly(); if ((originalLink || originalTranslatorItem) && premiumOnly) { status.textContent = "Оригинал найден, но сайт пишет Premium."; } else if (originalLink) { status.textContent = "Оригинал/субтитры найдены."; } else if (originalTranslatorItem) { status.textContent = "Оригинал доступен через переключатель озвучки."; } else { status.textContent = "Оригинал/субтитры не найдены."; } } } function observeChanges() { if (!document.body) return; const observer = new MutationObserver((records) => { const hasRelevantMutation = records.some((record) => { if (!record.target) return false; if (record.target instanceof Element && record.target.closest?.(`#${PANEL_ID}`)) { return false; } return record.addedNodes.length > 0 || record.removedNodes.length > 0; }); if (!hasRelevantMutation) return; if (cleanTimer) return; cleanTimer = window.setTimeout(() => { cleanTimer = null; applyEnabledState(); runSafeCleanup({ skipHeavyBlocks: true }); if (heavyCleanupTimer) return; heavyCleanupTimer = window.setTimeout(() => { heavyCleanupTimer = null; runSafeCleanup(); }, 900); }, 400); }); observer.observe(document.body, { childList: true, subtree: true, }); } function scrollToPlayerOnOpen() { if (playerAutoScrollApplied) return; playerAutoScrollApplied = true; if (!isCleanerEnabled()) return; if (!isAutoScrollToPlayerEnabled()) return; if (shouldSkipAutoScrollForHash()) return; const maxAttempts = 20; const retryDelayMs = 140; window.setTimeout(() => { scrollToPreferredAnchorWithRetry(0, maxAttempts, retryDelayMs); }, 120); } function findTranslatorAnchor() { const translatorsTitle = document.querySelector( ".b-translators__block .b-translators__title" ); if (translatorsTitle) return translatorsTitle; return document.querySelector("#translators-list"); } function findPlayerFallbackAnchor() { return ( document.querySelector("#player") || document.querySelector("#cdnplayer-container") || document.querySelector("#cdnplayer") ); } function scrollToPreferredAnchorWithRetry(attempt, maxAttempts, retryDelayMs) { const translatorAnchor = findTranslatorAnchor(); if (translatorAnchor) { scrollToAnchorWithTopOffset(translatorAnchor); return; } if (attempt >= maxAttempts) { const fallbackAnchor = findPlayerFallbackAnchor(); if (fallbackAnchor) { scrollToAnchorWithTopOffset(fallbackAnchor); } return; } window.setTimeout(() => { scrollToPreferredAnchorWithRetry(attempt + 1, maxAttempts, retryDelayMs); }, retryDelayMs); } function scrollToAnchorWithTopOffset(anchor) { if (!anchor) return; const stickyOffset = getStickyTopOffset(); const topPadding = 10; const top = Math.max( 0, window.scrollY + anchor.getBoundingClientRect().top - stickyOffset - topPadding ); window.scrollTo({ top, behavior: "smooth", }); } function getStickyTopOffset() { const root = document.documentElement; const style = root ? window.getComputedStyle(root) : null; const cssValue = style ? parseFloat( String(style.getPropertyValue("--rezka-cleaner-top-offset") || "").replace( "px", "" ) ) : Number.NaN; if (Number.isFinite(cssValue) && cssValue > 0) { return cssValue; } const topHead = document.getElementById("top-head"); const topNav = document.getElementById("top-nav"); const headHeight = topHead ? topHead.getBoundingClientRect().height : 0; const navHeight = topNav ? topNav.getBoundingClientRect().height : 0; const fallbackOffset = Math.max(0, headHeight + navHeight); return fallbackOffset; } function shouldSkipAutoScrollForHash() { const hash = String(window.location.hash || "").toLowerCase(); if (!hash) return false; if (hash.startsWith("#t:")) return false; if (hash.startsWith("#comment")) return true; return true; } function hideElement(el) { if (!el) return; if (!isCleanerEnabled()) return; if (el === document.body) return; if (el === document.documentElement) return; if (el.closest?.(`#${PANEL_ID}`)) return; if (el.matches?.("#top-head, #top-nav, .b-tophead_wrapper, .b-topnav_wrapper")) return; el.classList.add(HIDDEN_CLASS); } function normalizeText(value) { return String(value || "") .replace(/\s+/g, " ") .trim() .toLowerCase(); } function getStoredBoolean(key, fallback) { const value = safeStorageGet(key); if (value === "true") return true; if (value === "false") return false; return fallback; } function setStoredBoolean(key, value) { safeStorageSet(key, value ? "true" : "false"); } function safeStorageGet(key) { try { return localStorage.getItem(key); } catch { return null; } } function safeStorageSet(key, value) { try { localStorage.setItem(key, value); } catch { // ignore storage write errors (private mode / restricted context) } } function getCurrentTitleKey() { let path = String(window.location.pathname || "").trim().toLowerCase(); if (!path) return "/"; path = path.replace(/\/+$/, ""); path = path.replace(/\/\d+-subtitles(?=\/|\.html$)/i, ""); path = path.replace(/\/\d+-season\/\d+-episode\.html$/i, ""); path = path.replace(/\/\d+-season$/i, ""); path = path.replace(/\/\d+-episode\.html$/i, ""); path = path.replace(/\.html$/i, ""); return path || "/"; } function getAutoOriginalSeenMap() { const raw = safeStorageGet(STORAGE.autoOriginalSeenByTitle); if (!raw) return {}; try { const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { return {}; } return parsed; } catch { return {}; } } function isAutoOriginalSeenForTitle(titleKey) { if (!titleKey) return false; const map = getAutoOriginalSeenMap(); return Boolean(map[titleKey]); } function setAutoOriginalSeenForTitle(titleKey, value) { if (!titleKey) return; const map = getAutoOriginalSeenMap(); if (value) { map[titleKey] = true; } else { delete map[titleKey]; } try { safeStorageSet(STORAGE.autoOriginalSeenByTitle, JSON.stringify(map)); } catch { // ignore storage write errors } } })();