您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Dashboard mit präzisem Anker, robustere Parser, €/m², Werbeblocker #komplett refaktoriert
当前为
// ==UserScript== // @name Kleinanzeigen Mietdashboard V8.2 (Final Anchor Fix) // @namespace http://tampermonkey.net/ // @version 8.2 // @license MIT // @description Dashboard mit präzisem Anker, robustere Parser, €/m², Werbeblocker #komplett refaktoriert // @author Deine KI-Assistenz // @match https://www.kleinanzeigen.de/s-wohnung-mieten/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const CONFIG = { DASHBOARD_ANCHOR_SELECTOR: '.srp-header.l-container-row', // ❗ NEU: Dein exakter Ankerpunkt! AD_ITEM_SELECTOR: '.ad-listitem, article.aditem[data-adid]', STORAGE_KEY_PREFIX: 'ka_RegionalSqmPrices', PLZ_PREFIX_LENGTH: 3, DEBOUNCE_DELAY: 450, AD_SCRIPT_PATTERNS: [ /ads\.js/i, /advertisement\.js/i, /adservice/i, /googlesyndication\.com/i, /liberty.*\.js/i, /fbevent\.js/i, /teads.*\.js/i, /taboola.*\.js/i, /criteo.*\.js/i, /bat\.bing\.com/i, /hotjar.*\.js/i ], AD_ELEMENT_SELECTORS: [ '.site-base--left-banner', '.site-base--right-banner', '#banner-skyscraper', '.sticky-advertisement', 'div[id^="google_ads_iframe_"]', 'iframe[aria-label*="ad"]', '[data-liberty-position-name*="banner"]', '[aria-label*="Advertisement"]', '[aria-label*="Werbung"]', 'div[aria-label*="Gesponsert"]' ] }; const KleinanzeigenOptimizer = { state: { lastUrl: location.href, regionalPrices: {}, plzLength: 3 }, init() { this.state.plzLength = GM_getValue('plz_length', CONFIG.PLZ_PREFIX_LENGTH); this.injectStyles(); this.blockScripts(); this.registerMenuCommands(); setTimeout(() => this.run(), 250); // Leicht erhöhter Start-Delay this.observeSPA(); window.addEventListener('error', e => console.error('[KA-SCRIPT] Globaler JS-Fehler:', e.error, e)); }, run() { console.log('[KA-SCRIPT] Starte Analyse für:', location.href); try { this.removeAdElements(); this.processAdItems(); this.setupImgZoom(); } catch(e) { console.error('[KA-SCRIPT] Ein schwerwiegender Fehler ist im run() aufgetreten:', e); } }, injectStyles() { GM_addStyle(` :root { --ka-color-low: #38cb7f; --ka-color-low-bg: #ecfaed; --ka-color-mid: #ffd264; --ka-color-mid-bg: #fff8d2; --ka-color-high: #ff6363; --ka-color-high-bg: #fff1ef; --ka-price-color: #2342b2; --ka-border-color: #e3e9f1; } #ka-main-dashboard { margin: 0 0 16px 0; display:flex; gap:1.3em; background:#f4f7fb; border:2px solid var(--ka-border-color); border-radius:13px; box-shadow:0 2px 18px #2222; padding:1.2em 1.6em; align-items:center; flex-wrap:wrap; } .ka-dash-card { flex:1 1 0; text-align:center; border-radius:10px; background:#fff; margin:0 0.2em; padding:.9em .4em; box-shadow:0 1px 8px #2221; min-width:120px; } .ka-dash-card.ka-low { border-left:7px solid var(--ka-color-low); } .ka-dash-card.ka-mid { border-left:7px solid var(--ka-color-mid); } .ka-dash-card.ka-high { border-left:7px solid var(--ka-color-high); } .ka-dash-title { font-weight:700; font-size:1.13em; margin-bottom:6px; } .ka-dash-value { font-size:1.77em; margin:0 0 .3em 0; display:block; font-weight:bold; } .ka-dash-sub { color:#777;font-size:.97em;line-height:1.12 } #ka-dash-meta { font-size:.94em; color:#444; margin-top:6px; flex-basis:100%; text-align:center; } #ka-dash-clear-btn { margin-left:.7em;font-size:.98em;padding:.07em .55em; cursor:pointer; } .ka-sqm-wrap { text-align:right; } .ka-sqm-price-display { font-size:1.65em; color: var(--ka-price-color); font-weight:800; margin-left:.6em; background:#eef4fc; padding:2px 16px 2px 13px; border-radius:5px; float:none; display:inline-block; } article.aditem.ka-price-low, .ad-listitem.ka-price-low { background: var(--ka-color-low-bg) !important; } article.aditem.ka-price-mid, .ad-listitem.ka-price-mid { background: var(--ka-color-mid-bg) !important; } article.aditem.ka-price-high, .ad-listitem.ka-price-high { background: var(--ka-color-high-bg) !important; } article.aditem.ka-price-uniform, .ad-listitem.ka-price-uniform { background:#eee !important; } .ka-overlay-img { position:fixed; left:50%; top:50%; max-width:94vw; max-height:94vh; transform:translate(-50%,-50%); z-index:29999; border-radius:12px; box-shadow:0 8px 40px 0 rgba(0,0,0,0.72); background:#222; opacity:0; pointer-events:none; display:block; object-fit:contain; transition:opacity 0.16s cubic-bezier(.19,1,.22,1);} `); }, injectDashboard(stats) { console.log('[KA-SCRIPT] Injiziere Dashboard mit folgenden Daten:', stats); document.getElementById('ka-main-dashboard')?.remove(); // ❗ NEU: Direkter Anker, wie von dir vorgegeben, mit einem sicheren Fallback. const anchor = document.querySelector(CONFIG.DASHBOARD_ANCHOR_SELECTOR); if (!anchor) { console.error(`[KA-SCRIPT] Dashboard-Anker "${CONFIG.DASHBOARD_ANCHOR_SELECTOR}" nicht gefunden! Nutze Body als Fallback.`); document.body.prepend(this.createDashboardPanel(stats)); return; } console.log('[KA-SCRIPT] Dashboard-Anker gefunden:', anchor); const panel = this.createDashboardPanel(stats); anchor.parentNode.insertBefore(panel, anchor.nextSibling); }, createDashboardPanel(stats) { const panel = document.createElement('div'); panel.id = 'ka-main-dashboard'; const createCard = (className, title, value, subtext) => { const card = document.createElement('div'); card.className = `ka-dash-card ${className}`; card.innerHTML = `<div class="ka-dash-title">${title}</div><span class="ka-dash-value">${value}</span><div class="ka-dash-sub">${subtext}</div>`; return card; }; panel.append( createCard('ka-low', 'Günstig', stats.low.count, `${stats.low.min}–${stats.low.max} €/m²`), createCard('ka-mid', 'Mittel', stats.mid.count, `${stats.mid.min + 1}–${stats.mid.max} €/m²`), createCard('ka-high', 'Teuer', stats.high.count, `${stats.high.min + 1}–${stats.high.max} €/m²`) ); const metaDiv = document.createElement('div'); metaDiv.id = 'ka-dash-meta'; metaDiv.innerHTML = `Analysiert: <b>${stats.adsOnPage}</b> · Ø-Preis: <b>${stats.avg} €/m²</b>`; const clearBtn = document.createElement('button'); clearBtn.id = 'ka-dash-clear-btn'; clearBtn.textContent = 'Preis-Cache löschen'; clearBtn.onclick = () => this.storage.clear(); metaDiv.appendChild(clearBtn); panel.appendChild(metaDiv); return panel; }, blockScripts() { const observer = new MutationObserver(mutations => { mutations.forEach(m => m.addedNodes.forEach(node => { if (node.nodeType === 1 && node.tagName === 'SCRIPT' && node.src && CONFIG.AD_SCRIPT_PATTERNS.some(rx => rx.test(node.src))) { console.warn('[KA-SCRIPT] Blockiere Werbe-Script:', node.src); node.remove(); } })); }); observer.observe(document.documentElement, { childList: true, subtree: true }); }, removeAdElements() { document.querySelectorAll(CONFIG.AD_ELEMENT_SELECTORS.join(', ')).forEach(el => el.remove()); document.querySelectorAll(CONFIG.AD_ITEM_SELECTOR).forEach(ad => { if (/top anzeige|gesponsert|sponsored/i.test(ad.textContent)) ad.remove(); }); }, processAdItems() { this.state.regionalPrices = this.storage.load(); let storageChanged = false; const adElements = document.querySelectorAll(CONFIG.AD_ITEM_SELECTOR); console.log(`[KA-SCRIPT] ${adElements.length} Anzeigenelemente mit Selektor "${CONFIG.AD_ITEM_SELECTOR}" gefunden.`); if (adElements.length === 0) return; const adData = Array.from(adElements).map(article => { article.classList.remove('ka-price-low', 'ka-price-mid', 'ka-price-high', 'ka-price-uniform'); article.querySelector('.ka-sqm-wrap')?.remove(); const data = this.parser.parseArticle(article, this.state.plzLength); if (!data) return null; const roundedPrice = Math.round(data.pricePerSqm); const plzPrefix = data.plzPrefix; if (!this.state.regionalPrices[plzPrefix]) this.state.regionalPrices[plzPrefix] = {}; if (this.state.regionalPrices[plzPrefix][data.adId] !== roundedPrice) { this.state.regionalPrices[plzPrefix][data.adId] = roundedPrice; storageChanged = true; } const pricebox = article.querySelector('.aditem-main--middle--price-shipping'); if (pricebox) { const wrap = document.createElement('div'); wrap.className = 'ka-sqm-wrap'; wrap.innerHTML = `<span class="ka-sqm-price-display">${data.pricePerSqm.toFixed(2).replace('.', ',')} €/m²</span>`; pricebox.appendChild(wrap); } return { article, ...data }; }).filter(Boolean); console.log(`[KA-SCRIPT] ${adData.length} davon konnten erfolgreich verarbeitet werden.`); if (storageChanged) this.storage.save(this.state.regionalPrices); if (!adData.length) { document.getElementById('ka-main-dashboard')?.remove(); return; } const prices = adData.map(d => d.pricePerSqm); const minP = Math.min(...prices), maxP = Math.max(...prices), range = maxP - minP; const lower = minP + range / 3, upper = minP + 2 * range / 3; let low = 0, mid = 0, high = 0; adData.forEach(({ article, pricePerSqm }) => { if (range < 0.01) { article.classList.add('ka-price-uniform'); return; } if (pricePerSqm <= lower) { article.classList.add('ka-price-low'); low++; } else if (pricePerSqm <= upper) { article.classList.add('ka-price-mid'); mid++; } else { article.classList.add('ka-price-high'); high++; } }); this.injectDashboard({ low: { count: low, min: Math.round(minP), max: Math.floor(lower) }, mid: { count: mid, min: Math.floor(lower), max: Math.floor(upper) }, high: { count: high, min: Math.floor(upper), max: Math.round(maxP) }, adsOnPage: adData.length, avg: (prices.reduce((s, x) => s + x, 0) / prices.length).toFixed(2), }); }, parser: { // ❗ VERBESSERT: Robusterer Parser mit Fallbacks parseArticle(article, plzLength) { const data = { plzPrefix: this.getPLZPrefix(article, plzLength), area: this.getArea(article), price: this.getPrice(article), adId: this.getAdId(article) }; if (!data.plzPrefix || !data.area || !data.price || !data.adId) return null; data.pricePerSqm = data.price / data.area; return data; }, getPLZPrefix: (article, plzLength) => (article.textContent.match(/\b(\d{5})\b/) || [])[1]?.substring(0, plzLength) || null, getArea(article) { const tags = article.querySelector('.aditem-main--middle--tags')?.textContent; let match = tags ? tags.match(/([0-9.,]+)\s*m²/) : null; if (match) return parseFloat(match[1].replace(',', '.')); match = article.textContent.match(/\b([\d.,]+)\s*m²\b/); // Fallback auf gesamten Text return match ? parseFloat(match[1].replace(',', '.')) : null; }, getPrice(article) { const priceEl = article.querySelector('.aditem-main--middle--price-shipping--price'); if (priceEl) { const cleaned = priceEl.textContent.replace(/\s*€\s*|VB/gi, '').replace(/\./g, '').replace(',', '.'); const value = parseFloat(cleaned); if (!isNaN(value)) return value; } const match = article.textContent.match(/(\d{1,3}(?:\.\d{3})*,\d{2}|\d+)\s*€/); // Fallback auf gesamten Text if(match) return parseFloat(match[1].replace(/\./g, '').replace(',', '.')); return null; }, getAdId: (article) => article.dataset.adid || 'ad_' + btoa(article.textContent.substring(0, 32)).replace(/[^a-zA-Z0-9]/g, '').substring(0, 10) }, storage: { /* ... keine Änderung ... */ getStorageKey: () => `${CONFIG.STORAGE_KEY_PREFIX}_${KleinanzeigenOptimizer.state.plzLength}`, load: () => GM_getValue(KleinanzeigenOptimizer.storage.getStorageKey(), {}), save: (data) => GM_setValue(KleinanzeigenOptimizer.storage.getStorageKey(), data), clear: () => { if (confirm('Alle regionalen m²-Preis-Daten für die aktuelle PLZ-Länge löschen?')) { GM_deleteValue(KleinanzeigenOptimizer.storage.getStorageKey()); KleinanzeigenOptimizer.state.regionalPrices = {}; KleinanzeigenOptimizer.run(); } } }, setupImgZoom() { /* ... keine Änderung ... */ const list = document.querySelector('#srchrslt-adtable, #srchrslt-gallery'); if (!list || list.dataset.kaZoomBound) return; list.dataset.kaZoomBound = 'y'; let overlayImg = document.querySelector('.ka-overlay-img'); if (!overlayImg) { overlayImg = document.createElement('img'); overlayImg.className = 'ka-overlay-img'; document.body.appendChild(overlayImg); overlayImg.onclick = () => { overlayImg.style.opacity = '0'; overlayImg.style.pointerEvents = 'none'; }; } list.addEventListener('mouseover', e => { const img = e.target.closest('.imagebox img, .aditem-image img'); if (img) { img.style.cursor = 'zoom-in'; overlayImg.src = img.src; overlayImg.style.opacity = '1'; overlayImg.style.pointerEvents = 'auto'; } }); list.addEventListener('mouseout', e => { if (e.target.closest('.imagebox img, .aditem-image img')) { overlayImg.style.opacity = '0'; } }); }, observeSPA() { /* ... keine Änderung ... */ const debouncedRun = this.utils.debounce(() => this.run(), CONFIG.DEBOUNCE_DELAY); const observer = new MutationObserver(() => { if (location.href !== this.state.lastUrl) { this.state.lastUrl = location.href; debouncedRun(); } }); observer.observe(document.body, { childList: true, subtree: true }); }, registerMenuCommands() { /* ... keine Änderung ... */ GM_registerMenuCommand('Miet-Dashboard: PLZ-Genauigkeit einstellen', () => { const newLength = prompt('Postleitzahlen-Genauigkeit (2-5 Ziffern):', this.state.plzLength); const val = parseInt(newLength); if (val >= 2 && val <= 5) { GM_setValue('plz_length', val); alert('Einstellung gespeichert. Die Seite wird neu geladen.'); location.reload(); } else if (newLength !== null) { alert('Ungültige Eingabe. Bitte eine Zahl zwischen 2 und 5 eingeben.'); } }); }, utils: { debounce(func, delay) { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), delay); }; } } }; KleinanzeigenOptimizer.init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址