Kleinanzeigen Komponenten-Analysator (Final)

Finaler Analysator mit hochkomprimierter Ausgabe für LLMs, perfektem Drag&Drop und Multi-Pick.

目前為 2025-06-07 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Kleinanzeigen Komponenten-Analysator (Final)
// @namespace    http://tampermonkey.net/
// @version      12.0
// @description  Finaler Analysator mit hochkomprimierter Ausgabe für LLMs, perfektem Drag&Drop und Multi-Pick.
// @author       Assistant & Dein Name
// @license MIT
// @match        https://www.kleinanzeigen.de/*
// @grant        GM_addStyle
// @grant        GM_setClipboard
// ==/UserScript==

(function() {
    'use strict';

    // Globale Variablen & Cache für Performance
    let pickerMode = false, pickerButton = null, lastHoveredElement = null, selectedElements = [];
    let isDragging = false, wasDragging = false, offsetX, offsetY;
    const defaultStylesCache = new Map();

    // CSS-Stile für das Skript
    GM_addStyle(`
        /* ... (CSS unverändert) ... */
        #element-picker-btn { position: fixed; right: 20px; top: 50%; transform: translateY(-50%); width: 50px; height: 50px; background: #007185; border: none; border-radius: 50%; cursor: grab; z-index: 9999; box-shadow: 0 2px 10px rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; transition: background-color 0.3s ease; }
        #element-picker-btn:hover { background-color: #005a6b; }
        body.picker-dragging, body.picker-dragging * { cursor: grabbing !important; }
        #element-picker-btn.active { background: #dc3545; animation: pulse 1s infinite; }
        @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(220, 53, 69, 0); } 100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); } }
        .picker-cursor, .picker-cursor * { cursor: crosshair !important; }
        .picker-highlight { outline: 2px solid red !important; outline-offset: 1px !important; background-color: rgba(255, 0, 0, 0.1) !important; }
        .picker-selected { outline: 2px solid #007bff !important; outline-offset: 1px !important; background-color: rgba(0, 123, 255, 0.15) !important; }
        .picker-notification { position: fixed; top: 20px; right: 20px; background: #28a745; color: white; padding: 10px 20px; border-radius: 5px; z-index: 10000; font-family: Arial, sans-serif; font-size: 14px; animation: slideIn 0.3s ease-out; }
        @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
        .picker-status { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: #dc3545; color: white; padding: 8px 16px; border-radius: 5px; z-index: 10000; font-family: Arial, sans-serif; font-size: 12px; font-weight: bold; }
    `);

    // --- DATENEXTRAKTION (HOCHOPTIMIERT FÜR LLM) ---
    function getCompressedCss(element) {
        // Temporäres Entfernen der Skript-Klassen für saubere Analyse
        const hadHighlight = element.classList.contains('picker-highlight');
        if (hadHighlight) element.classList.remove('picker-highlight');

        const tagName = element.tagName;
        if (!defaultStylesCache.has(tagName)) {
            const dummy = document.createElement(tagName);
            document.body.appendChild(dummy);
            defaultStylesCache.set(tagName, window.getComputedStyle(dummy));
            document.body.removeChild(dummy);
        }

        const styles = window.getComputedStyle(element);
        const defaultStyles = defaultStylesCache.get(tagName);
        const cssProps = [];
        // Stark reduzierte Liste der absolut wichtigsten Layout-Eigenschaften
        const relevantProperties = [ 'display', 'position', 'flex-direction', 'justify-content', 'align-items', 'width', 'height', 'margin', 'padding', 'border', 'border-radius', 'font-weight', 'color' ];

        for (const prop of relevantProperties) {
            const value = styles.getPropertyValue(prop);
            // Nur hinzufügen, wenn der Wert vom Browser-Standard abweicht
            if (value !== defaultStyles.getPropertyValue(prop)) {
                cssProps.push(`${prop}: ${value}`);
            }
        }

        // Ursprüngliche Klasse wiederherstellen
        if (hadHighlight) element.classList.add('picker-highlight');
        return cssProps.length > 0 ? `{ ${cssProps.join('; ')} }` : '';
    }

    // Rekursive Funktion zum Aufbau des komprimierten Kind-Element-Baums
    function buildChildTree(element, level = 0) {
        let treeText = '';
        const indentation = '  '.repeat(level);

        for (const child of element.children) {
            if (child.tagName === 'SCRIPT') continue;

            const childCss = getCompressedCss(child);
            const classAttr = child.className ? ` class="${child.className}"` : '';
            
            treeText += `${indentation}└─ <${child.tagName.toLowerCase()}${classAttr}> ${childCss}\n`;

            if (child.children.length > 0) {
                treeText += buildChildTree(child, level + 1);
            }
        }
        return treeText;
    }

    function extractElementData(adListItem) {
        const adItem = adListItem.querySelector('article.aditem');
        const targetElement = adItem || adListItem;
        const data = { tagName: targetElement.tagName.toLowerCase(), className: targetElement.className || '', dataAdId: targetElement.getAttribute('data-adid') || '', dataHref: targetElement.getAttribute('data-href') || '', title: '', description: '', price: '', location: '', date: '', outerHTML: compactHTML(adListItem.outerHTML) };
        const titleElement = adListItem.querySelector('h2.text-module-begin a.ellipsis'); if (titleElement) data.title = titleElement.textContent.trim();
        const descElement = adListItem.querySelector('.aditem-main--middle--description'); if (descElement) data.description = descElement.textContent.trim();
        const priceElement = adListItem.querySelector('.aditem-main--middle--price-shipping--price'); if (priceElement) data.price = priceElement.textContent.trim();
        const locationElement = adListItem.querySelector('.aditem-main--top--left'); if (locationElement) data.location = locationElement.textContent.trim();
        const dateElement = adListItem.querySelector('.aditem-main--top--right'); if (dateElement) data.date = dateElement.textContent.trim();
        
        const childTree = buildChildTree(adListItem);

        let output = '----------------COMPONENT BLUEPRINT----------------\n';
        output += `SOURCE: ${window.location.href}\n`;
        output += `CONTAINER: <${adListItem.tagName.toLowerCase()} class="${adListItem.className}">\n\n`;
        output += '---KEY DATA---\n';
        output += `Title: ${data.title}\n`;
        output += `Price: ${data.price}\n`;
        output += `Location: ${data.location}\n`;
        output += `Date: ${data.date}\n`;
        output += `Description: ${data.description.substring(0, 100)}...\n\n`;
        output += '---STRUCTURE & STYLES---\n';
        output += childTree || '(No child elements found)\n\n';
        output += '---RAW HTML---\n';
        output += data.outerHTML;
        output += '\n----------------END----------------';
        return output;
    }
    
    // --- RESTLICHE FUNKTIONEN (UNVERÄNDERT) ---
    function makeButtonDraggable(button) { button.addEventListener('mousedown', (e) => { if (e.button !== 0 || pickerMode) return; isDragging = true; wasDragging = false; document.body.classList.add('picker-dragging'); button.style.cursor = 'grabbing'; button.style.transition = 'none'; const rect = button.getBoundingClientRect(); button.style.left = `${rect.left}px`; button.style.top = `${rect.top}px`; button.style.right = 'auto'; button.style.transform = 'none'; offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; document.addEventListener('mousemove', onDragMove); document.addEventListener('mouseup', onDragEnd); }); function onDragMove(e) { if (!isDragging) return; e.preventDefault(); wasDragging = true; let newX = e.clientX - offsetX; let newY = e.clientY - offsetY; newX = Math.max(0, Math.min(newX, window.innerWidth - button.offsetWidth)); newY = Math.max(0, Math.min(newY, window.innerHeight - button.offsetHeight)); button.style.left = `${newX}px`; button.style.top = `${newY}px`; } function onDragEnd() { if (!isDragging) return; isDragging = false; document.body.classList.remove('picker-dragging'); button.style.cursor = 'grab'; button.style.transition = 'background-color 0.3s ease'; document.removeEventListener('mousemove', onDragMove); document.removeEventListener('mouseup', onDragEnd); if (wasDragging) { const position = { x: button.style.left, y: button.style.top }; localStorage.setItem('pickerButtonPosition', JSON.stringify(position)); } } }
    function togglePickerMode() { if (wasDragging) { wasDragging = false; return; } pickerMode = !pickerMode; if (pickerMode) { activatePickerMode(); } else { deactivatePickerMode(); } }
    function activatePickerMode() { pickerButton.classList.add('active'); document.body.classList.add('picker-cursor'); showStatus('Picker aktiv. STRG+Klick für Mehrfachauswahl. ESC zum Beenden.'); document.addEventListener('mouseover', onMouseOver, true); document.addEventListener('mouseout', onMouseOut, true); document.addEventListener('click', onElementClick, true); document.addEventListener('keydown', onKeyDown, true); }
    function deactivatePickerMode() { pickerMode = false; pickerButton.classList.remove('active'); document.body.classList.remove('picker-cursor'); if (lastHoveredElement) { lastHoveredElement.classList.remove('picker-highlight'); } selectedElements.forEach(el => el.classList.remove('picker-selected')); selectedElements = []; lastHoveredElement = null; removeStatus(); document.removeEventListener('mouseover', onMouseOver, true); document.removeEventListener('mouseout', onMouseOut, true); document.removeEventListener('click', onElementClick, true); document.removeEventListener('keydown', onKeyDown, true); }
    function onMouseOver(e) { if (!pickerMode || e.target.closest('#element-picker-btn')) return; const adListItem = findAdListItem(e.target); if (!adListItem) return; if (lastHoveredElement && lastHoveredElement !== adListItem) { lastHoveredElement.classList.remove('picker-highlight'); } if (lastHoveredElement !== adListItem) { lastHoveredElement = adListItem; lastHoveredElement.classList.add('picker-highlight'); } e.stopPropagation(); }
    function onMouseOut(e) { if (pickerMode) e.stopPropagation(); }
    function onElementClick(e) { if (!pickerMode || e.target.closest('#element-picker-btn')) return; e.preventDefault(); e.stopPropagation(); const adListItem = findAdListItem(e.target); if (!adListItem) { showNotification('Kein ad-listitem Element gefunden!'); return; } if (e.ctrlKey) { const index = selectedElements.indexOf(adListItem); if (index > -1) { adListItem.classList.remove('picker-selected'); selectedElements.splice(index, 1); } else { adListItem.classList.add('picker-selected'); selectedElements.push(adListItem); } showStatus(`${selectedElements.length} Element(e) ausgewählt. Klick ohne STRG zum Kopieren.`); } else { let elementsToCopy = []; if (selectedElements.length > 0 && !selectedElements.includes(adListItem)) { selectedElements.push(adListItem); elementsToCopy = selectedElements; } else if (selectedElements.length > 0) { elementsToCopy = selectedElements; } else { elementsToCopy = [adListItem]; } if (elementsToCopy.length > 0) { let combinedText = ""; elementsToCopy.forEach((element, i) => { combinedText += extractElementData(element); if (i < elementsToCopy.length - 1) { combinedText += '\n\n\n'; } }); GM_setClipboard(combinedText, 'text'); showNotification(`${elementsToCopy.length} Komponenten-Blaupause(n) kopiert!`); } deactivatePickerMode(); } }
    function onKeyDown(e) { if (pickerMode && e.key === 'Escape') { e.preventDefault(); deactivatePickerMode(); } }
    function createPipetteIcon() { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("width", "24"); svg.setAttribute("height", "24"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("fill", "white"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", "M20.71 5.63l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-3.12 3.12-1.93-1.91-1.41 1.41 1.42 1.42L3 16.25V21h4.75l8.92-8.92 1.42 1.42 1.41-1.41-1.91-1.93 3.12-3.12c.4-.4.4-1.03 0-1.41zM6.92 19L5 17.08l8.06-8.06 1.92 1.92L6.92 19z"); svg.appendChild(path); return svg; }
    function createPickerButton() { const button = document.createElement('button'); button.id = 'element-picker-btn'; button.title = 'Komponenten-Analysator starten'; button.appendChild(createPipetteIcon()); button.addEventListener('click', togglePickerMode); document.body.appendChild(button); return button; }
    function findAdListItem(element) { return element.closest('.ad-listitem'); }
    function loadButtonPosition() { const savedPosition = localStorage.getItem('pickerButtonPosition'); if (savedPosition) { const pos = JSON.parse(savedPosition); pickerButton.style.top = pos.y; pickerButton.style.left = pos.x; pickerButton.style.right = 'auto'; pickerButton.style.transform = 'none'; } }
    function compactHTML(html) { return html.replace(/\s+/g, ' ').replace(/\s*<\s*/g, '<').replace(/\s*>\s*/g, '>').trim(); }
    function showNotification(message) { const n=document.createElement('div');n.className='picker-notification';n.textContent=message;document.body.appendChild(n);setTimeout(()=>{if(n.parentNode)n.parentNode.removeChild(n)},3000); }
    function showStatus(message) { removeStatus();const s=document.createElement('div');s.id='picker-status';s.className='picker-status';s.textContent=message;document.body.appendChild(s); }
    function removeStatus() { const e=document.getElementById('picker-status');if(e)e.parentNode.removeChild(e); }
    function init() { pickerButton = createPickerButton(); makeButtonDraggable(pickerButton); loadButtonPosition(); console.log('Komponenten-Analysator (Final) wurde geladen.'); }
    if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址