Zeigt €/m², färbt Anzeigen dynamisch, und ermöglicht Seitennavigation mit 'a'/'d'. Vereinfachte Paginierungslogik.
当前为
// ==UserScript==
// @name Kleinanzeigen m² Preis Rechner, Farbkodierer & Seitennavigation V4.2
// @namespace http://tampermonkey.net/
// @version 1.5.2
// @description Zeigt €/m², färbt Anzeigen dynamisch, und ermöglicht Seitennavigation mit 'a'/'d'. Vereinfachte Paginierungslogik.
// @author Dein Name
// @license MIT
// @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
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// CSS (unverändert)
GM_addStyle(`
.sqm-price-display { font-size: 1.4em; color: #20242C; font-weight: bold; margin-left: 10px; }
.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 parsePrice(priceString) { // (unverändert)
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) { // (unverändert)
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) { // (unverändert)
element.classList.remove('sqm-price-low', 'sqm-price-medium', 'sqm-price-high', 'sqm-price-uniform');
}
function processAdItems() { // (unverändert, Logik zur Preisberechnung und Farbkodierung)
const items = document.querySelectorAll('article.aditem');
const itemsData = [];
items.forEach(item => {
removeColorClasses(item);
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;
itemsData.push({ element: item, pricePerSqm: 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 (itemsData.length < 1) return;
const allPricesPerSqm = itemsData.map(d => d.pricePerSqm);
const minPrice = Math.min(...allPricesPerSqm);
const maxPrice = Math.max(...allPricesPerSqm);
const range = maxPrice - minPrice;
if (range === 0) {
itemsData.forEach(data => data.element.classList.add('sqm-price-uniform'));
return;
}
const lowerBound = minPrice + range / 3;
const upperBound = minPrice + (2 * range) / 3;
itemsData.forEach(data => {
const itemElement = data.element;
const pricePerSqmVal = data.pricePerSqm;
if (pricePerSqmVal <= lowerBound) itemElement.classList.add('sqm-price-low');
else if (pricePerSqmVal <= upperBound) itemElement.classList.add('sqm-price-medium');
else itemElement.classList.add('sqm-price-high');
});
}
processAdItems(); // Initialer Aufruf
// MutationObserver (unverändert)
const observerTargetNode = document.getElementById('srchrslt-adtable') || document.body;
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 für Seitennavigation (VEREINFACHT) ---
document.addEventListener('keydown', function(event) {
const targetTagName = event.target.tagName.toLowerCase();
if (targetTagName === 'input' || targetTagName === 'textarea' || event.target.isContentEditable) {
return;
}
const key = event.key.toLowerCase();
if (key === 'a') { // 'a' für Zurück (Previous)
// Direkter Klick auf das Element mit der Klasse .pagination-prev
const prevButton = document.querySelector('.pagination-prev');
if (prevButton) {
prevButton.click();
}
} else if (key === 'd') { // 'd' für Nächste (Next)
// Direkter Klick auf das Element mit der Klasse .pagination-next
const nextButton = document.querySelector('.pagination-next');
if (nextButton) {
nextButton.click();
}
}
});
})();