您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Zeigt €/m², färbt Anzeigen dynamisch (basierend auf regional gespeicherten Preisen nach 3-stelligem PLZ-Präfix), Seitennavigation mit 'a'/'d'. Mit Löschbestätigung.
// ==UserScript== // @name Der intelligente Flächen-Preisrechner für Kleinanzeigen 🏡 // @namespace http://tampermonkey.net/ // @version 2.0 // @description Zeigt €/m², färbt Anzeigen dynamisch (basierend auf regional gespeicherten Preisen nach 3-stelligem PLZ-Präfix), Seitennavigation mit 'a'/'d'. Mit Löschbestätigung. // @author Dein Name // @license MIT // @icon https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://kleinanzeigen.de&size=64 // @match https://www.kleinanzeigen.de/s-wohnung-mieten/* // @match https://www.kleinanzeigen.de/s-haus-mieten/* // @match https://www.kleinanzeigen.de/s-wohnung-kaufen/* // @match https://www.kleinanzeigen.de/s-haus-kaufen/* // @match https://www.kleinanzeigen.de/s-wg-zimmer/* // @match https://www.kleinanzeigen.de/s-immobilien/* // @match https://www.kleinanzeigen.de/s-grundstuecke-gaerten/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const REGIONAL_STORAGE_KEY = 'kleinanzeigenRegionalSqmPrices_3digit'; const PLZ_PREFIX_LENGTH = 3; let globalSqmPricesByRegion = loadRegionalSqmPrices(); GM_addStyle(` .sqm-price-display { font-size: 1.4em; color: #20242C; font-weight: bold; margin-left: 10px; } /* ... Rest des CSS unverändert ... */ .aditem-main--middle--price-shipping { display: flex; align-items: baseline; flex-wrap: wrap; } .sqm-price-container { margin-left: auto; padding-left: 5px; } .sqm-price-low { background-color: rgba(144, 238, 144, 0.4) !important; } .sqm-price-medium { background-color: rgba(255, 210, 100, 0.4) !important; } .sqm-price-high { background-color: rgba(255, 127, 127, 0.4) !important; } .sqm-price-uniform { background-color: rgba(200, 200, 200, 0.3) !important; } `); function loadRegionalSqmPrices() { return GM_getValue(REGIONAL_STORAGE_KEY, {}); } function saveRegionalSqmPrices() { GM_setValue(REGIONAL_STORAGE_KEY, globalSqmPricesByRegion); } function calculateStorageInfo(data) { const jsonString = JSON.stringify(data); const sizeBytes = new TextEncoder().encode(jsonString).length; const sizeMegabytes = (sizeBytes / (1024 * 1024)).toFixed(4); // Etwas mehr Genauigkeit für kleine MB-Werte let totalAdEntries = 0; for (const region in data) { if (data.hasOwnProperty(region)) { totalAdEntries += Object.keys(data[region]).length; } } return { sizeMegabytes, totalAdEntries }; } function confirmAndClearAllRegionalSqmPrices() { const currentData = GM_getValue(REGIONAL_STORAGE_KEY, {}); // Aktuelle Daten für Info holen const { sizeMegabytes, totalAdEntries } = calculateStorageInfo(currentData); const confirmationMessage = `Möchten Sie wirklich alle gespeicherten regionalen m² Preisdaten löschen?\n\n` + `Anzahl der erfassten Anzeigen: ${totalAdEntries}\n` + `Geschätzte Speichergröße: ${sizeMegabytes} MB\n\n` + `Diese Aktion kann nicht rückgängig gemacht werden.`; if (confirm(confirmationMessage)) { // confirm() zeigt OK/Abbrechen Dialog GM_deleteValue(REGIONAL_STORAGE_KEY); globalSqmPricesByRegion = {}; // Internen Speicher auch leeren alert('Alle gespeicherten regionalen m² Preise (3-stellig PLZ) wurden gelöscht. Die Farbanzeige wird zurückgesetzt.'); processAdItems(); // Neu einfärben (jetzt nur basierend auf aktueller Seite oder leer) } else { alert('Löschvorgang abgebrochen.'); } } GM_registerMenuCommand("Alle regionalen m² Preise (3-stellig PLZ) löschen...", confirmAndClearAllRegionalSqmPrices, "L"); // Der Titel des Menübefehls hat jetzt "..." um anzudeuten, dass eine weitere Interaktion folgt. function parsePrice(priceString) { /* ... unverändert ... */ } function parseArea(areaString) { /* ... unverändert ... */ } function removeColorClasses(element) { /* ... unverändert ... */ } function getPlzPrefixFromItem(item) { /* ... unverändert ... */ } // Die Funktionen parsePrice, parseArea, removeColorClasses, getPlzPrefixFromItem hier einfügen // (aus Platzgründen gekürzt, sind im vorherigen Skript vorhanden) function parsePrice(priceString) { if (!priceString) return null; const cleanedPrice = priceString.replace(/\s*VB\s*/i, '').replace(/\s*€\s*/g, '').replace(/\./g, '').replace(/,/g, '.').trim(); if (cleanedPrice.toLowerCase() === 'zu verschenken') return 0; if (cleanedPrice.toLowerCase() === 'auf anfrage' || cleanedPrice.toLowerCase() === 'anfrage') return null; const price = parseFloat(cleanedPrice); return isNaN(price) ? null : price; } function parseArea(areaString) { if (!areaString) return null; const match = areaString.match(/([\d\.,]+)\s*m²/i); if (match && match[1]) { const cleanedArea = match[1].replace(/\./g, '').replace(/,/g, '.'); const area = parseFloat(cleanedArea); return isNaN(area) || area === 0 ? null : area; } return null; } function removeColorClasses(element) { element.classList.remove('sqm-price-low', 'sqm-price-medium', 'sqm-price-high', 'sqm-price-uniform'); } function getPlzPrefixFromItem(item) { const locationElement = item.querySelector('.aditem-main--top--left'); if (locationElement) { const locationText = locationElement.textContent; const plzMatch = locationText.match(/\b(\d{5})\b/); if (plzMatch && plzMatch[1]) { return plzMatch[1].substring(0, PLZ_PREFIX_LENGTH); } } return null; } function processAdItems() { /* ... Logik bleibt weitgehend gleich, lädt und speichert globalSqmPricesByRegion ... */ globalSqmPricesByRegion = loadRegionalSqmPrices(); const items = document.querySelectorAll('article.aditem'); let mainStorageHasChanged = false; const itemsByPlzOnPage = {}; items.forEach(item => { removeColorClasses(item); const adId = item.dataset.adid; const plzPrefix = getPlzPrefixFromItem(item); if (!adId || !plzPrefix) { return; } if (!globalSqmPricesByRegion[plzPrefix]) { globalSqmPricesByRegion[plzPrefix] = {}; } if (!itemsByPlzOnPage[plzPrefix]) { itemsByPlzOnPage[plzPrefix] = []; } const priceShippingContainer = item.querySelector('.aditem-main--middle--price-shipping'); if (!priceShippingContainer) return; const existingSqmTextContainer = priceShippingContainer.querySelector('.sqm-price-container'); if (existingSqmTextContainer) existingSqmTextContainer.remove(); const priceElement = priceShippingContainer.querySelector('.aditem-main--middle--price-shipping--price'); const priceText = priceElement ? priceElement.textContent : null; const tagsContainer = item.querySelector('.aditem-main--bottom p'); let areaText = null; if (tagsContainer) { const simpleTags = tagsContainer.querySelectorAll('span.simpletag'); simpleTags.forEach(tag => { if (tag.textContent && tag.textContent.includes('m²')) areaText = tag.textContent; }); } const price = parsePrice(priceText); const area = parseArea(areaText); if (price !== null && area !== null && area > 0) { const pricePerSqm = price / area; const roundedPricePerSqm = Math.round(pricePerSqm); if (globalSqmPricesByRegion[plzPrefix][adId] !== roundedPricePerSqm) { globalSqmPricesByRegion[plzPrefix][adId] = roundedPricePerSqm; mainStorageHasChanged = true; } itemsByPlzOnPage[plzPrefix].push({ element: item, adId: adId, precisePricePerSqm: pricePerSqm }); const sqmPriceOuterContainer = document.createElement('div'); sqmPriceOuterContainer.classList.add('sqm-price-container'); const sqmPriceElement = document.createElement('span'); sqmPriceElement.classList.add('sqm-price-display'); sqmPriceElement.textContent = `${pricePerSqm.toFixed(2).replace('.', ',')} €/m²`; sqmPriceOuterContainer.appendChild(sqmPriceElement); priceShippingContainer.appendChild(sqmPriceOuterContainer); } }); if (mainStorageHasChanged) { saveRegionalSqmPrices(); } for (const plzKey in itemsByPlzOnPage) { const itemsInThisRegionOnPage = itemsByPlzOnPage[plzKey]; const allStoredPricesInThisRegion = Object.values(globalSqmPricesByRegion[plzKey] || {}).filter(p => typeof p === 'number'); if (allStoredPricesInThisRegion.length < 1) continue; const minRegionalPrice = Math.min(...allStoredPricesInThisRegion); const maxRegionalPrice = Math.max(...allStoredPricesInThisRegion); const regionalRange = maxRegionalPrice - minRegionalPrice; let lowerBound, upperBound; if (regionalRange === 0) { itemsInThisRegionOnPage.forEach(data => data.element.classList.add('sqm-price-uniform')); continue; } lowerBound = minRegionalPrice + regionalRange / 3; upperBound = minRegionalPrice + (regionalRange * 2) / 3; itemsInThisRegionOnPage.forEach(data => { const itemElement = data.element; const roundedPriceForThisAd = globalSqmPricesByRegion[plzKey][data.adId]; if (typeof roundedPriceForThisAd !== 'number') return; if (roundedPriceForThisAd <= lowerBound) { itemElement.classList.add('sqm-price-low'); } else if (roundedPriceForThisAd <= upperBound) { itemElement.classList.add('sqm-price-medium'); } else { itemElement.classList.add('sqm-price-high'); } }); } } processAdItems(); // MutationObserver (unverändert) const observerTargetNode = document.getElementById('srchrslt-adtable') || document.body; // ... (vollständiger MutationObserver Code hier einfügen) const observer = new MutationObserver(function(mutationsList) { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { let hasNewAdItems = false; for (const addedNode of mutation.addedNodes) { if (addedNode.nodeType === Node.ELEMENT_NODE && (addedNode.matches && (addedNode.matches('.aditem') || addedNode.querySelector('.aditem')))) { hasNewAdItems = true; break; } } if (hasNewAdItems) { setTimeout(processAdItems, 350); return; } } } }); observer.observe(observerTargetNode, { childList: true, subtree: true }); // Tastaturkürzel (unverändert) document.addEventListener('keydown', function(event) { // ... (vollständiger Tastaturkürzel Code hier einfügen) const targetTagName = event.target.tagName.toLowerCase(); if (targetTagName === 'input' || targetTagName === 'textarea' || event.target.isContentEditable) { return; } const key = event.key.toLowerCase(); if (key === 'a') { const prevButton = document.querySelector('.pagination-prev'); if (prevButton) prevButton.click(); } else if (key === 'd') { const nextButton = document.querySelector('.pagination-next'); if (nextButton) nextButton.click(); } }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址