diff --git a/src/archiver.mjs b/src/archiver.mjs index 7c25f04..4586b0a 100644 --- a/src/archiver.mjs +++ b/src/archiver.mjs @@ -64,6 +64,27 @@ const COMMON_ANNOYANCE_ROOT_CLASSES = [ "iubenda-cs-visible" ]; +const COMMON_ANNOYANCE_TRIGGER_SELECTORS = [ + "a[href*=\"getadmiral.com\" i]", + "a[href*=\"%67e%74%61%64mi%72%61l.com\" i]", + "a[href*=\"admiraladblock\" i]", + "button", + "[role=\"dialog\"]", + "[aria-modal=\"true\"]" +]; + +const COMMON_ANNOYANCE_TEXT_PATTERNS = [ + "ad blocker detected", + "allow ads", + "allowlist", + "continue using your ad blocker", + "disable your ad blocker", + "support us by disabling", + "support the verge by allowing ads", + "turn off your ad blocker", + "you are using an ad blocker" +]; + const COMMON_ANNOYANCE_CSS = ` ${COMMON_ANNOYANCE_SELECTORS.join(",\n")} { display: none !important; @@ -1253,7 +1274,57 @@ async function injectCosmeticFilters(page, hostname) { async function removeCommonAnnoyances(page) { try { - await page.evaluate(({ selectors, rootClasses }) => { + await page.evaluate(({ selectors, rootClasses, triggerSelectors, textPatterns }) => { + const annoyanceText = textPatterns.map((pattern) => pattern.toLowerCase()); + const textFor = (element) => (element.innerText || element.textContent || "") + .replace(/\s+/g, " ") + .trim() + .toLowerCase(); + const isTextTrigger = (element) => { + const text = textFor(element); + if (!text) return false; + if (annoyanceText.some((pattern) => text.includes(pattern))) return true; + return text.includes("ad blocker") && /allow|disable|turn off|subscribe|support/.test(text); + }; + const isLinkTrigger = (element) => { + const href = `${element.getAttribute("href") || ""} ${element.href || ""}`.toLowerCase(); + return href.includes("getadmiral.com") || + href.includes("%67e%74%61%64mi%72%61l.com") || + href.includes("admiraladblock"); + }; + const findModalRoot = (element) => { + let node = element; + let best = null; + while (node && node !== document.body && node !== document.documentElement) { + const style = window.getComputedStyle(node); + const rect = node.getBoundingClientRect(); + const zIndex = Number.parseInt(style.zIndex, 10); + const positioned = /fixed|absolute|sticky/i.test(style.position); + const wide = rect.width >= window.innerWidth * 0.5; + const tall = rect.height >= window.innerHeight * 0.4; + const fullScreen = rect.width >= window.innerWidth * 0.9 && rect.height >= window.innerHeight * 0.9; + if ((positioned && (wide || tall || zIndex >= 1000)) || fullScreen) { + best = node; + } + node = node.parentElement; + } + return best || element.closest("[role=\"dialog\"], [aria-modal=\"true\"]") || element; + }; + + for (const selector of triggerSelectors) { + try { + document.querySelectorAll(selector).forEach((element) => { + if (!isLinkTrigger(element) && !isTextTrigger(element)) return; + const root = findModalRoot(element); + if (root && root !== document.body && root !== document.documentElement) { + root.remove(); + } + }); + } catch { + // Ignore selectors unsupported by the current browser. + } + } + for (const selector of selectors) { try { document.querySelectorAll(selector).forEach((element) => element.remove()); @@ -1280,7 +1351,9 @@ async function removeCommonAnnoyances(page) { } }, { selectors: COMMON_ANNOYANCE_SELECTORS, - rootClasses: COMMON_ANNOYANCE_ROOT_CLASSES + rootClasses: COMMON_ANNOYANCE_ROOT_CLASSES, + triggerSelectors: COMMON_ANNOYANCE_TRIGGER_SELECTORS, + textPatterns: COMMON_ANNOYANCE_TEXT_PATTERNS }); } catch { // Ignore cleanup failures; the archive is still useful. diff --git a/test/archiver.test.mjs b/test/archiver.test.mjs index 7c3d059..1989221 100644 --- a/test/archiver.test.mjs +++ b/test/archiver.test.mjs @@ -100,6 +100,34 @@ test("renderPage removes common cookie consent overlays before snapshot", async assert.doesNotMatch(rendered, /
]*class="[^"]*didomi-popup-open/i); }); +test("renderPage removes Admiral adblock walls before snapshot", async () => { + const html = ` + + +Or subscribe to continue using your ad blocker uninterrupted.
+ + Powered By +