您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Highlights, hides, and cleans sponsored eBay listings
// ==UserScript== // @name Spotless for eBay // @namespace https://github.com/OsborneLabs // @version 1.5.3 // @description Highlights, hides, and cleans sponsored eBay listings // @author Osborne Labs // @license GPL-3.0 // @homepageURL https://github.com/OsborneLabs/Spotless // @supportURL https://github.com/OsborneLabs/Spotless/issues // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDcwLjYgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4KICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxLjQwNjU5MzQwNjU5MzQwMTYgMS40MDY1OTM0MDY1OTM0MDE2KSBzY2FsZSgyLjgxIDIuODEpIj4KICAgIDxwYXRoIGZpbGw9IiM4NDg0ODQiIGQ9Ik0xMjcuOCw0Ni4xSDM4LjdWNDJjMC0yMy40LDE5LjEtNDIuNSw0Mi41LTQyLjVoNC4xYzIzLjQsMCw0Mi41LDE5LjEsNDIuNSw0Mi41VjQ2LjF6IE00Mi43LDQyaDgxVjQyIGMwLTIxLjItMTcuMi0zOC40LTM4LjQtMzguNGgtNC4xQzYwLDMuNSw0Mi43LDIwLjgsNDIuNyw0Mkw0Mi43LDQyeiI+PC9wYXRoPgogICAgPHBhdGggZmlsbD0iI0U1MzIzOCIgZD0iTTM1LjEsMTgxLjdoLTkuNmMtMTQuMywwLTI1LjktMTEuNi0yNS45LTI1LjlWNDkuNGMwLTUuMiw0LjMtOS41LDkuNS05LjVoMjYuMUwzNS4xLDE4MS43TDM1LjEsMTgxLjd6Ij48L3BhdGg+CiAgICA8cmVjdCB4PSIzNS4xIiB5PSIzOS45IiBmaWxsPSIjMDA2NEQyIiB3aWR0aD0iNDguMSIgaGVpZ2h0PSIxNDEuOCI+PC9yZWN0PgogICAgPHJlY3QgeD0iODMuMiIgeT0iMzkuOSIgZmlsbD0iI0Y1QUYwMiIgd2lkdGg9IjQ4LjEiIGhlaWdodD0iMTQxLjgiPjwvcmVjdD4KICAgIDxwYXRoIGZpbGw9IiM4NkI4MTciIGQ9Ik0xNDEsMTgxLjdoLTkuNlYzOS45aDI2LjFjNS4yLDAsOS41LDQuMyw5LjUsOS41djEwNi40QzE2NywxNzAuMSwxNTUuMywxODEuNywxNDEsMTgxLjd6Ij48L3BhdGg+CiAgPC9nPgo8L3N2Zz4K // @match https://www.ebay.com/* // @match https://www.ebay.at/* // @match https://www.ebay.ca/* // @match https://www.ebay.ch/* // @match https://www.ebay.com.au/* // @match https://www.ebay.com.hk/* // @match https://www.ebay.com.my/* // @match https://www.ebay.com.sg/* // @match https://www.ebay.co.uk/* // @match https://www.ebay.de/* // @match https://www.ebay.es/* // @match https://www.ebay.fr/* // @match https://www.ebay.ie/* // @match https://www.ebay.it/* // @match https://www.ebay.nl/* // @match https://www.ebay.pl/* // @run-at document-start // @grant none // ==/UserScript== /* jshint esversion: 11 */ (function() { "use strict"; const APP_NAME = "Spotless"; const APP_NAME_DEBUG = "SPOTLESS FOR EBAY"; const APP_KEY_SPONSORED_CONTENT = "hideSponsoredContent"; let hidingEnabled = localStorage.getItem(APP_KEY_SPONSORED_CONTENT); hidingEnabled = hidingEnabled !== "false"; let highlightedSponsoredContent = []; let isProcessing = false; let updateScheduled = false; let observerInitialized = false; const APP_ICONS = { locked: ` <svg class="lock-icon lock-icon-animation" id="lockedIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M12 2C9.79 2 8 3.79 8 6v4H7c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2v-8c0-1.1-.9-2-2-2h-1V6c0-2.21-1.79-4-4-4zm-2 8V6c0-1.1.9-2 2-2s2 .9 2 2v4h-4z"/> </svg>`, unlocked: ` <svg class="lock-icon lock-icon-animation" id="unlockedIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M17 8V6c0-2.76-2.24-5-5-5S7 3.24 7 6h2c0-1.66 1.34-3 3-3s3 1.34 3 3v2H7c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2h-1z"/> </svg>`, arrow: ` <svg id="arrowIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M7 10l5 5 5-5z"/> </svg>`, heart: ` <svg class="heart-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41 0.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/> </svg>` }; const APP_BLOCK_PARAMETERS = ["campaign", "promoted_items", "source", "sr"]; async function init() { observeURLMutation(); createStyles(); buildPanel(); hideShowPanel(); await processSponsoredContent(); } function createStyles() { const style = document.createElement("style"); style.textContent = ` :root { --size-font-title: 18px; --size-font-body: 14px; --size-font-body-error: 16px; --size-font-footer: 12px; --color-font-text: white; --color-font-link-hover: lightblue; --color-font-link-visited: lightblue; --color-panel: rgba(34, 50, 70, 0.85); --color-panel-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); --color-row: rgba(20, 30, 45, 0.5); --color-divider-border: rgba(255, 255, 255, 0.1); --color-bubble: #e74c3c; --color-highlight-background: #ffe6e6; --color-highlight-border: red; --color-svg-fill: white; --color-svg-fill-heart-hover: red; --color-switch-knob: white; --color-switch-on: #2AA866; --color-switch-off: #ccc; --thickness-highlight-border: 2px; } #panelWrapper, #panelBox, .lock-icon-animation, .lock-icon-animation.active { box-sizing: border-box; } #panelWrapper { position: fixed; bottom: 10px; right: 5px; z-index: 2147483647; width: 100%; max-width: 320px; padding: 0 16px; font-family: "Segoe UI", sans-serif; } @media (max-width: 768px) { #panelWrapper { bottom: 5px; left: 50%; transform: translateX(-50%); width: 90% !important; right: unset; padding: 0 16px; } } #panelBox { display: none; } #panelBox.show { display: flex; flex-direction: column; gap: 0px; background: var(--color-panel); backdrop-filter: blur(10px); color: var(--color-font-text); padding: 16px; border-radius: 12px; width: 100%; box-shadow: var(--color-panel-shadow); transition: transform 0.2s ease; } #panelBox:hover { transform: translateY(-2px); } #panelBox.minimized #arrowIcon { transform: rotate(180deg); } #panelBox.minimized { padding: 12px; overflow: hidden; } #panelHeader { display: flex; align-items: center; gap: 8px; } #panelHeader h2.panel-title { font-size: var(--size-font-title); font-weight: 600; margin: 0; color: var(--color-font-text); } .panel-body-row { margin: 0; font-size: var(--size-font-body); display: flex; align-items: center; justify-content: space-between; background: var(--color-row); backdrop-filter: blur(12px); padding: 12px 16px; border-radius: 8px; } .panel-body-row + .panel-body-row { margin-top: 5px; } .panel-footer { display: flex; align-items: center; justify-content: flex-end; gap: 6px; font-size: var(--size-font-footer); color: var(--color-font-text); } .panel-page-container { position: relative; width: 100%; } hr.section-divider { flex-grow: 1; border: none; border-top: 1px solid var(--color-divider-border); margin: 12px 0; } #minimizePanelButton { width: 28px; height: 28px; margin-left: auto; padding: 2px; border: none; background: none; display: flex; align-items: center; justify-content: center; cursor: pointer; box-sizing: content-box; } .lock-icon { width: 28px; height: 28px; padding: 4px; border-radius: 50%; fill: var(--color-svg-fill); } .lock-icon-animation { position: absolute; top: 0; left: 0; width: 28px; height: 28px; opacity: 0; transition: opacity 0.4s ease, transform 0.4s ease; transform: rotate(0deg); } .lock-icon-animation.active { opacity: 1; transform: rotate(360deg); } #lockIconContainer { position: relative; width: 28px; height: 28px; } #arrowIcon { width: 28px; height: 28px; fill: var(--color-svg-fill); transition: transform 0.3s ease; } .heart-icon { width: 10px; height: 10px; vertical-align: middle; fill: var(--color-svg-fill); } .heart-icon:hover { fill: var(--color-svg-fill-heart-hover); } #countBubble { background-color: var(--color-bubble); color: var(--color-font-text); font-size: 12px; font-weight: bold; padding: 3px 8px; border-radius: 999px; min-width: 20px; text-align: center; } .switch { position: relative; display: inline-block; width: 42px; height: 22px; } .switch input { opacity: 0; width: 0; height: 0; } .switch-label { margin-right: 10px; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--color-switch-off); transition: 0.3s; border-radius: 34px; } .slider:before { position: absolute; content: ""; height: 18px; width: 18px; top: 2px; left: 2px; background-color: var(--color-switch-knob); transition: 0.3s; border-radius: 50%; } input:checked + .slider { background-color: var(--color-switch-on); } input:checked + .slider:before { transform: translateX(20px); } #creatorPage { color: var(--color-font-text); transition: color 0.3s ease; } #creatorPage:hover, .outbound-status-page:hover, .outbound-update-page:hover{ color: var(--color-font-link-hover); } .error-page { text-align: center; font-size: var(--size-font-body-error); } .outbound-status-page, .outbound-update-page { text-decoration: underline; color: var(--color-font-text); } .outbound-status-page:visited, .outbound-update-page:visited { color: var(--color-font-link-visited); } .sponsored-highlight { border: var(--thickness-highlight-border) solid var(--color-highlight-border) !important; background-color: var(--color-highlight-background); } .sponsored-hidden { display: none !important; } .sponsored-hidden-banner { display: none !important; } `; document.head.appendChild(style); } function initializeObserver() { if (observerInitialized) return; observer.observe(document.body, { childList: true, subtree: true, }); observerInitialized = true; } function validateCurrentURL() { const url = new URL(location.href); const params = url.searchParams; const isSearchPage = /^https:\/\/www\.ebay\.[a-z.]+\/sch\//i.test(url.href); const isAdvancedSearchPage = url.href.includes("ebayadvsearch"); const isSellerPage = params.has("_ssn"); const isSoldPage = params.get("LH_Sold") === "1"; return isSearchPage && !isAdvancedSearchPage && !isSellerPage && !isSoldPage; } function observeURLMutation() { let lastUrl = location.href; const observeURL = () => { const currentUrl = location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; hideShowPanel(); scheduleHighlightUpdate(); } }; const pushState = history.pushState; history.pushState = function() { pushState.apply(history, arguments); observeURL(); }; const replaceState = history.replaceState; history.replaceState = function() { replaceState.apply(history, arguments); observeURL(); }; window.addEventListener("popstate", observeURL); window.addEventListener("hashchange", observeURL); setInterval(observeURL, 1000); } async function buildPanel() { const wrapper = document.createElement("div"); wrapper.id = "panelWrapper"; const box = document.createElement("div"); box.id = "panelBox"; const header = buildPanelHeader(); const sponsoredCount = await processSponsoredContent(); const body = determinePanelState(sponsoredCount, hidingEnabled); const footer = buildPanelFooter(); const topDivider = document.createElement("hr"); topDivider.className = "section-divider"; const bottomDivider = document.createElement("hr"); bottomDivider.className = "section-divider"; box.appendChild(header); box.appendChild(topDivider); box.appendChild(body); box.appendChild(bottomDivider); box.appendChild(footer); wrapper.appendChild(box); document.body.appendChild(wrapper); const minimizePanelButton = document.getElementById("minimizePanelButton"); minimizePanelButton.addEventListener("click", () => { const panelBox = document.getElementById("panelBox"); const isCurrentlyMinimized = panelBox.classList.contains("minimized"); const newState = !isCurrentlyMinimized; localStorage.setItem("panelMinimized", newState); minimizePanel(newState); }); const isPanelMinimized = localStorage.getItem("panelMinimized") === "true"; minimizePanel(isPanelMinimized); const toggleSponsoredContentSwitchInput = document.getElementById("toggleSponsoredContentSwitch"); if (!toggleSponsoredContentSwitchInput) { updateLockIcon(); return; } toggleSponsoredContentSwitchInput.addEventListener("change", (e) => { hidingEnabled = e.target.checked; localStorage.setItem(APP_KEY_SPONSORED_CONTENT, hidingEnabled); updateLockIcon(); scheduleHighlightUpdate(); }); updateLockIcon(); } function buildPanelHeader() { const header = document.createElement("div"); header.id = "panelHeader"; header.innerHTML = ` <div id="lockIconContainer"> ${APP_ICONS.locked} ${APP_ICONS.unlocked} </div> <h2 class="panel-title" aria-level="1">${APP_NAME}</h2> <button id="minimizePanelButton" aria-label="Expands or minimizes the panel"> ${APP_ICONS.arrow} </button> `; return header; } function buildPanelFooter() { const footer = document.createElement("div"); footer.className = "panel-footer"; const creatorPage = document.createElement("a"); creatorPage.href = "https://github.com/OsborneLabs/Spotless"; creatorPage.target = "_blank"; creatorPage.rel = "noopener noreferrer"; creatorPage.style.textDecoration = "none"; creatorPage.textContent = "Osborne"; creatorPage.id = "creatorPage"; const separator = document.createElement("span"); separator.textContent = " · "; const donatePage = document.createElement("a"); donatePage.href = "https://ko-fi.com/osbornelabs"; donatePage.target = "_blank"; donatePage.rel = "noopener noreferrer"; donatePage.innerHTML = APP_ICONS.heart; donatePage.style.display = "inline-flex"; donatePage.style.alignItems = "center"; donatePage.style.justifyContent = "center"; footer.appendChild(creatorPage); footer.appendChild(separator); footer.appendChild(donatePage); return footer; } function buildPanelRow(innerHTML = "") { const row = document.createElement("div"); row.className = "panel-body-row"; row.innerHTML = innerHTML; return row; } function buildCountSponsoredContentRow() { const row = buildPanelRow(` <span>Content found</span> <span id="countBubble">0</span> `); row.id = "countSponsoredContentRow"; return row; } function buildToggleSponsoredContentRow() { const row = buildPanelRow(` <span class="switch-label">Hide sponsored content</span> <label class="switch" aria-label="Toggles the visibility of sponsored content"> <input type="checkbox" id="toggleSponsoredContentSwitch" ${hidingEnabled ? "checked" : ""}> <span class="slider"></span> </label> `); row.id = "toggleSponsoredContentRow"; return row; } function buildPanelHomePage() { const pageContainer = document.createElement("div"); pageContainer.id = "panelPagecontainer"; pageContainer.classList.add("panel-page-container"); const homePage = document.createElement("div"); homePage.id = "homePage"; homePage.className = "panel-page"; homePage.style.display = "block"; const countSponsoredContentRow = buildCountSponsoredContentRow(); const toggleSponsoredContentRow = buildToggleSponsoredContentRow(); homePage.appendChild(countSponsoredContentRow); homePage.appendChild(toggleSponsoredContentRow); pageContainer.appendChild(homePage); return pageContainer; } function buildPanelErrorPage() { const errorPage = document.createElement("div"); errorPage.classList.add("error-page", "panel-page"); const errorMessage = document.createElement("p"); errorMessage.textContent = "No sponsored content found"; errorMessage.appendChild(document.createElement("br")); const outboundUpdatePage = document.createElement("a"); outboundUpdatePage.textContent = "Update"; outboundUpdatePage.href = "https://greasyfork.org/en/scripts/541981"; outboundUpdatePage.target = "_blank"; outboundUpdatePage.rel = "noopener noreferrer"; outboundUpdatePage.classList.add("outbound-update-page"); const outboundStatusPage = document.createElement("a"); outboundStatusPage.textContent = "check status"; outboundStatusPage.href = "https://github.com/OsborneLabs/Spotless"; outboundStatusPage.target = "_blank"; outboundStatusPage.rel = "noopener noreferrer"; outboundStatusPage.classList.add("outbound-status-page"); errorMessage.appendChild(outboundUpdatePage); errorMessage.appendChild(document.createTextNode(" or ")); errorMessage.appendChild(outboundStatusPage); errorPage.appendChild(errorMessage); return errorPage; } function minimizePanel(minimized) { const panelBox = document.getElementById("panelBox"); if (!panelBox) return; const panelPage = panelBox.querySelector(".panel-page"); const sectionDivider = panelBox.querySelectorAll(".section-divider"); const panelFooter = panelBox.querySelector(".panel-footer"); panelBox.classList.toggle("minimized", minimized); if (panelPage) panelPage.style.display = minimized ? "none" : "block"; sectionDivider.forEach(el => { el.style.display = minimized ? "none" : ""; }); if (panelFooter) panelFooter.style.display = minimized ? "none" : ""; } function hideShowPanel() { const panelBox = document.getElementById("panelBox"); if (!panelBox) return; const isSearchPage = validateCurrentURL(); if (isSearchPage) { panelBox.classList.add("show"); } else { panelBox.classList.remove("show"); } } function determinePanelState(sponsoredCount) { if (sponsoredCount < 2 || sponsoredCount > 20) { return buildPanelErrorPage(); } return buildPanelHomePage(); } function updateLockIcon() { const locked = document.getElementById("lockedIcon"); const unlocked = document.getElementById("unlockedIcon"); locked.classList.toggle("active", hidingEnabled); unlocked.classList.toggle("active", !hidingEnabled); } function detectSponsoredListingBySVG(batchSize = 5) { return new Promise((resolve) => { const listings = getListingElements(); const sponsoredElements = []; let index = 0; function processBatch() { const end = Math.min(index + batchSize, listings.length); const batch = listings.slice(index, end); let processedInBatch = 0; if (batch.length === 0) { resolve(sponsoredElements); return; } batch.forEach((listing) => { const svgImage = listing.querySelector(".s-item__sep span[aria-hidden='true']"); if (!svgImage) return done(); const backgroundImage = getComputedStyle(svgImage.parentElement).backgroundImage; const match = backgroundImage.match(/url\("data:image\/svg\+xml;base64,([^"]+)"\)/); if (!match || !match[1]) return done(); const base64 = match[1]; const svgString = atob(base64); const img = new Image(); const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); img.src = "data:image/svg+xml;base64," + btoa(svgString); img.onload = () => { canvas.width = img.naturalWidth || 20; canvas.height = img.naturalHeight || 20; ctx.drawImage(img, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; const colors = new Set(); for (let i = 0; i < imageData.length; i += 4) { const r = imageData[i]; const g = imageData[i + 1]; const b = imageData[i + 2]; const a = imageData[i + 3]; if (a > 0) { colors.add(`${r},${g},${b}`); } } if (colors.size > 1) { sponsoredElements.push(listing); } done(); }; img.onerror = () => { done(); }; function done() { processedInBatch++; if (processedInBatch === batch.length) { index += batchSize; setTimeout(processBatch, 0); } } }); if (batch.length === 0) resolve(sponsoredElements); } if (listings.length === 0) { resolve([]); } else { processBatch(); } }); } function detectSponsoredListingBySeparatorSize() { const listings = getListingElements(); const sponsoredListings = []; listings.forEach(listing => { const separatorSpan = listing.querySelector('span.s-item__sep'); if (!separatorSpan) return; const innerSpan = separatorSpan.querySelector('span'); const width = innerSpan?.offsetWidth || 0; const height = innerSpan?.offsetHeight || 0; const isSponsored = width > 0 && height > 0; if (isSponsored) { sponsoredListings.push(listing); } }); return sponsoredListings; } function detectSponsoredListingByInvertStyle() { const invertStyleMatch = /div\.([a-zA-Z0-9_-]+)(?:\s+div)?\s*\{[^}]*color:\s*(black|white);[^}]*filter:\s*invert\(([-\d.]+)\)/g; const sponsoredGroups = {}; const classToInvertMap = {}; const styleTags = Array.from(document.querySelectorAll("style")); styleTags.forEach(styleTag => { const css = styleTag.textContent; let match; while ((match = invertStyleMatch.exec(css)) !== null) { const [_, className, color, invertValue] = match; if (!classToInvertMap[className]) { classToInvertMap[className] = []; } classToInvertMap[className].push({ color, invert: parseFloat(invertValue) }); } }); const containers = Array.from(document.querySelectorAll('div[role="text"]')).filter(container => { return container.querySelector('div[aria-hidden="true"]'); }); containers.forEach(container => { const targetDiv = container.querySelector('div[aria-hidden="true"]'); if (!targetDiv) return; const ancestorDiv = container.closest("div[class*='_']"); if (!ancestorDiv) return; const classList = Array.from(ancestorDiv.classList); const dynamicClass = classList.find(cls => classToInvertMap[cls]); if (!dynamicClass) return; const candidates = classToInvertMap[dynamicClass]; const invertEntry = candidates?.[0]; if (!invertEntry) return; const key = invertEntry.invert; if (!sponsoredGroups[key]) { sponsoredGroups[key] = []; } sponsoredGroups[key].push(container); }); const groupEntries = Object.entries(sponsoredGroups); if (groupEntries.length === 0) { return { allGroups: [] }; } const sortedGroups = groupEntries.sort((a, b) => a[1].length - b[1].length); const [sponsoredInvert, sponsoredList] = sortedGroups[0]; return { invert: parseFloat(sponsoredInvert), elements: sponsoredList, allGroups: groupEntries }; } function detectSponsoredBanner() { return new Promise((resolve) => { setTimeout(() => { const banners = Array.from( document.querySelectorAll(".s-answer-region-center-top.s-answer-region > div") ).filter((el) => el.offsetHeight >= 140); banners.forEach(banner => { banner.classList.add("sponsored-hidden-banner"); }); resolve(banners); }, 525); }); } async function processSponsoredContent() { if (isProcessing) return 0; isProcessing = true; try { observer.disconnect(); resetDesignateSponsoredContent(); const detectedSponsoredElements = new Set(); const base64Results = await detectSponsoredListingBySVG(); base64Results.forEach(el => { const li = el.closest("li"); if (li) detectedSponsoredElements.add(li); }); if (detectedSponsoredElements.size === 0) { const dimensionBasedResults = detectSponsoredListingBySeparatorSize(); dimensionBasedResults.forEach(li => detectedSponsoredElements.add(li)); } if (detectedSponsoredElements.size === 0) { const invertMethod = detectSponsoredListingByInvertStyle(); invertMethod.elements?.forEach(container => { const li = container.closest("li"); if (li) detectedSponsoredElements.add(li); }); } requestAnimationFrame(() => { for (const el of detectedSponsoredElements) { if (!el.hasAttribute("data-sponsored-processed")) { designateSponsoredContent(el); highlightSponsoredContent(el); hideShowSponsoredContent(el, hidingEnabled); } } cleanListingURLS(); cleanGeneralURLs(); hideShowPanel(); const count = detectedSponsoredElements.size; countSponsoredContent(count); initializeObserver(); isProcessing = false; }); return detectedSponsoredElements.size; } catch (err) { console.error(`${APP_NAME_DEBUG}: UNABLE TO PROCESS SPONSORED CONTENT, SEE CONSOLE ERROR\n`, err); isProcessing = false; initializeObserver(); return 0; } } function designateSponsoredContent(el) { el.setAttribute("data-sponsored", "true"); el.setAttribute("data-sponsored-processed", "true"); highlightedSponsoredContent.push(el); } function resetDesignateSponsoredContent() { highlightedSponsoredContent.forEach(el => { el.classList.remove("sponsored-hidden"); el.removeAttribute("data-sponsored"); el.removeAttribute("data-sponsored-processed"); el.style.border = ""; el.style.backgroundColor = ""; }); highlightedSponsoredContent.length = 0; } function highlightSponsoredContent(element) { element.setAttribute("data-sponsored", "true"); element.classList.add("sponsored-highlight"); } function hideShowSponsoredContent(element, hide) { element.classList.toggle("sponsored-hidden", hide); } function countSponsoredContent(count) { const countBubble = document.getElementById("countBubble"); if (countBubble) countBubble.textContent = count; } function getListingElements() { return Array.from(document.querySelectorAll("li[class*='s-']")).filter( (el) => el.className.split(/\s+/).some((cls) => /^s-[\w-]+$/.test(cls)) ); } function cleanListingURLS() { const listingURLMatch = /^https:\/\/www\.ebay\.([a-z.]+)\/itm\/(\d+)(?:[/?#].*)?/; const links = document.querySelectorAll("a[href*='/itm/']"); links.forEach((link) => { const match = link.href.match(listingURLMatch); if (match) { const tld = match[1]; const itemId = match[2]; const cleanUrl = `https://www.ebay.${tld}/itm/${itemId}`; if (link.href !== cleanUrl) { link.href = cleanUrl; } } }); } function cleanGeneralURLs() { const links = document.querySelectorAll("a[href*='ebay.']"); links.forEach((link) => { try { const url = new URL(link.href); const tldMatch = url.hostname.match(/(?:^|\.)ebay\.([a-z.]+)$/); if (!tldMatch) return; const params = new URLSearchParams(url.search); APP_BLOCK_PARAMETERS.forEach(param => { if (params.has(param)) { params.delete(param); } }); for (const key of [...params.keys()]) { if (key.startsWith("utm_") || key.startsWith("_trk")) { params.delete(key); } } const cleanUrl = `${url.origin}${url.pathname}${params.toString() ? '?' + params.toString() : ''}${url.hash}`; if (link.href !== cleanUrl) { link.href = cleanUrl; } } catch (e) {} }); } function scheduleHighlightUpdate() { if (updateScheduled || isProcessing) return; updateScheduled = true; requestAnimationFrame(() => { processSponsoredContent().finally(() => { updateScheduled = false; }); }); } function cleanListingObserver() { const urlCleanListingObserver = new MutationObserver(() => { cleanListingURLS(); }); urlCleanListingObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['href'], }); } const observer = new MutationObserver(() => { hideShowPanel(); scheduleHighlightUpdate(); }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', cleanListingObserver); } else { cleanListingObserver(); } (async function() { const delayedInit = async () => { await new Promise(r => setTimeout(r, 200)); init(); detectSponsoredBanner(); }; if (document.readyState === "complete" || document.readyState === "interactive") { await delayedInit(); } else { window.addEventListener("DOMContentLoaded", delayedInit); } })(); window.addEventListener("storage", (event) => { if (event.key === APP_KEY_SPONSORED_CONTENT) { const newValue = event.newValue === "true"; if (newValue !== hidingEnabled) { hidingEnabled = newValue; const toggleSponsoredContentSwitchInput = document.getElementById("toggleSponsoredContentSwitch"); if (toggleSponsoredContentSwitchInput) toggleSponsoredContentSwitchInput.checked = hidingEnabled; updateLockIcon(); scheduleHighlightUpdate(); } } else if (event.key === "panelMinimized") { minimizePanel(event.newValue === "true"); } }); })();