MyDealz JSONL-Kommentar Extraktor für LLM mit Markdown 🧙‍♂️

Exportiert Kommentare als LLM- und Markdown-optimiertes JSONL. Version 4.1: Selektor für Debug-Modus korrigiert und Parser robuster gemacht.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         MyDealz JSONL-Kommentar Extraktor für LLM mit Markdown 🧙‍♂️
// @namespace    violentmonkey
// @version      4.1
// @description  Exportiert Kommentare als LLM- und Markdown-optimiertes JSONL. Version 4.1: Selektor für Debug-Modus korrigiert und Parser robuster gemacht.
// @match        https://www.mydealz.de/diskussion/*
// @match        https://www.mydealz.de/deals/*
// @icon         https://www.mydealz.de/favicon.svg?v=2
// @grant        none
// ==/UserScript==
(function() {
    'use strict';
    
    // --- Configuration ---
    const SCRIPT_VERSION = '4.1';
    
    // AI Links
    const KI_LINKS = [
        { id: 'chatgptBtn', label: 'ChatGPT', url: 'https://chatgpt.com/' },
        { id: 'perplexityBtn', label: 'Perplexity', url: 'https://www.perplexity.ai/' },
        { id: 'claudeBtn', label: 'Claude', url: 'https://claude.ai/' },
        { id: 'geminiBtn', label: 'Gemini', url: 'https://gemini.google.com/' },
        { id: 'mistralBtn', label: 'Mistral', url: 'https://chat.mistral.ai/chat' },
        { id: 'grokBtn', label: 'Grok', url: 'https://grok.com/' }
    ];
    
    // Selectors & Constants
    const SELECTORS = {
        REPLY_BTN: 'button[data-t="moreReplies"]:not([disabled])',
        NEXT_PAGE: 'button[aria-label="Nächste Seite"]:not([disabled])',
        FIRST_PAGE: 'button[aria-label="Erste Seite"]:not([disabled])',
        CURRENT_PAGE: 'button[aria-label="Aktuelle Seite"]',
        COMMENT_LINK: 'a.button--type-text[href*="#comments"]',
        // *** KORRIGIERT (V4.1) ***
        // Dieser Selektor (nur für Debug-Modus) war falsch.
        // Er basiert jetzt auf den Daten aus deinem Helper (Element 5).
        COMMENT_COUNT_HEADER: 'span.size--all-l.size--fromW3-xl.text--b',
        COMMENT_ARTICLE: 'article.comment',
        THREAD_TITLE: '.thread-title .text--b.size--all-xl.size--fromW3-xxl'
    };
    
    const INTERVAL = { 
        PAGE: 2000, 
        REPLY_WAIT: 800, 
        UI_RENDER: 50,
        REPLY_CHECK: 500,
        CLICK_COOLDOWN: 200
    };
    
    const BTN_COLORS = { 
        NORMAL: '#2c7ff3', 
        ERROR: '#e53935', 
        SUCCESS: '#4caf50', 
        INFO: '#607d8b',
        EXPAND_NORMAL: '#2C7CBD',
        EXPAND_PROCESSING: '#FFA500',
        EXPAND_SUCCESS: '#4CAF50'
    };
    
    const MESSAGES = {
        POPUP_BLOCKED: 'Popup blockiert! Bitte Popups für diese Seite erlauben.',
        SCRIPT_RELOAD_PAGE_1: 'Skript setzt Seite auf 1 zurück für vollständigen Export...',
        START_EXPORT: 'Starte Export...',
        GOTO_PAGE_1: 'Gehe zu Seite 1...',
        EXPANDING_REPLIES: (page, current, total) => `Seite ${page}: Erweitere Antworten (${current}/${total} gesammelt)...`,
        COLLECTING_COMMENTS: (page, current, total) => `Seite ${page}: Sammle Kommentare (${current}/${total} gesammelt)...`,
        LOADING_PAGE: (page, current, total) => `Lade Seite ${page} (${current}/${total} gesammelt)...`,
        PREPARING_EXPORT: 'Bereite Export vor...',
        EXPORT_COMPLETE: 'Fertig!',
        NO_COMMENTS_FOUND: 'Keine Kommentare gefunden!',
        EXPORT_WITH_DISCREPANCY: (found, expected) => `Achtung: ${found} von ${expected} Kommentaren erfasst!`,
        ERROR_GENERIC: 'Fehler! (Siehe Konsole)',
        COPIED: 'Copied!',
        NOTHING_TO_COPY: 'Nichts zu kopieren!',
        NO_ELEMENT_FOUND: (selector) => `Fehler: Element mit Selektor "${selector}" nicht gefunden.`,
        EXPORT_BUTTON_LABEL: 'Kommentare als JSONL exportieren',
        EXPAND_BUTTON_LABEL: 'Alle Antworten erweitern',
        POPUP_LOADING: 'Generiere Daten...',
        COPY_TO_CLIPBOARD: 'Copy to Clipboard'
    };
    
    // Prompt-Level Definitions
    const PROMPT_LEVELS = {
        NONE: {
            label: 'Ohne Prompt',
            id: 'promptNoneBtn',
            intro: (title, url, commentCountNote) => '' // Kein Prompt-Header
        },
        SHORT: {
            label: 'Kurz',
            id: 'promptShortBtn',
            intro: (title, url, commentCountNote) =>
`# Zusammenfassung der Kommentare zur MyDealz-Diskussion [${title}](${url})

## 🤖 Anweisung an die KI

**Deine Rolle:** Du bist ein hilfsbereiter Community-Analyst. Analysiere die Kommentare und fasse sie prägnant zusammen.

**Dein Ziel:** Erstelle eine kurze Zusammenfassung (max. 1-2 Sätze pro Punkt) mit den wichtigsten Meinungen (Pro, Contra, Neutral).

---

## 📋 Gewünschtes Ausgabeformat (Markdown)

### Allgemeine Stimmung
### ✅ Positive Aspekte (Pros)
### ❌ Negative Aspekte (Kritik & Nachteile)
### 💡 Neutrale Beobachtungen & Fragen
### 🏁 Fazit

---

## Daten
`
        },
        MEDIUM: {
            label: 'Durchschnittlich',
            id: 'promptMediumBtn',
            intro: (title, url, commentCountNote) =>
`# Analyse der ${commentCountNote} zur MyDealz-Diskussion [${title}](${url})

## 🤖 Anweisung an die KI

**Deine Rolle:** Du bist ein hilfsbereiter Community-Analyst. Deine Aufgabe ist es, die bereitgestellten Benutzerkommentare zu analysieren und eine strukturierte, leicht verständliche Zusammenfassung zu erstellen.

**Dein Input:** Die Daten liegen im JSONL-Format vor (jedes Objekt eine neue Zeile):
- \`{"Metadaten": {...}}\`: Enthält Kontext. **Ignoriere diesen Block in deiner finalen Ausgabe.**
- \`{"maindescription": "..."}\`: Die ursprüngliche Deal-Beschreibung als Kontext.
- \`{"text": "...", "like": ...}\`: Die einzelnen Benutzerkommentare mit Bewertungen.

**Dein Ziel:** Erstelle eine Zusammenfassung, die die wichtigsten Meinungen, Pro- & Contra-Punkte sowie wiederkehrende Themen beleuchtet.

---

## 📋 Gewünschtes Ausgabeformat (Markdown)

Bitte halte dich exakt an die folgende Gliederung und nutze passende Emojis:

### Allgemeine Stimmung
*(Ein kurzer Absatz über den allgemeinen Ton der Diskussion. Z.B. "Überwiegend positiv", "gemischte Gefühle", "hitzige Debatte".)*

### ✅ Positive Aspekte (Pros)
- *Liste der positiv bewerteten Punkte, die von Nutzern genannt wurden.*
- *Zum Beispiel: "Gute Qualität", "Schnelle Lieferung", "Preis-Leistungs-Verhältnis top".*

### ❌ Negative Aspekte (Kritik & Nachteile)
- *Liste der Kritikpunkte und genannten Nachteile.*
- *Zum Beispiel: "Hält nicht lange", "Schlechter Kundenservice", "Zu teuer".*

### 💡 Neutrale Beobachtungen & Fragen
- *Liste der wiederkehrenden Fragen oder neutralen Anmerkungen.*
- *Zum Beispiel: "Frage nach der Kompatibilität mit X", "Vergleich mit Produkt Y".*

### 🏁 Fazit
*(Eine abschließende, neutrale Zusammenfassung der Diskussion in 2-3 prägnanten Sätzen.)*

---

## Daten
`
        },
        DETAILED: {
            label: 'Detailliert',
            id: 'promptDetailedBtn',
            intro: (title, url, commentCountNote) =>
`# Ausführliche Analyse der ${commentCountNote} zur MyDealz-Diskussion [${title}](${url})

## 🤖 Anweisung an die KI

**Deine Rolle:** Du bist ein sehr detailorientierter Community-Analyst. Deine Aufgabe ist es, die bereitgestellten Benutzerkommentare tiefgehend zu analysieren und eine umfassende, gut strukturierte und leicht verständliche Zusammenfassung zu erstellen.

**Dein Input:** Die Daten liegen im JSONL-Format vor (jedes Objekt eine neue Zeile):
- \`{"Metadaten": {...}}\`: Enthält Kontext. **Ignoriere diesen Block in deiner finalen Ausgabe.**
- \`{"maindescription": "..."}\`: Die ursprüngliche Deal-Beschreibung als Kontext.
- \`{"text": "...", "like": ...}\`: Die einzelnen Benutzerkommentare mit Bewertungen.

**Dein Ziel:** Erstelle eine ausführliche und nuancierte Zusammenfassung, die alle wichtigen Meinungen, detaillierte Pro- & Contra-Punkte (ggf. mit Sub-Punkten), wiederkehrende Themen, häufig gestellte Fragen und bemerkenswerte Beobachtungen beleuchtet. Gehe auch auf die Stärke der Meinungen ein (z.B. "starke Zustimmung", "vereinzelt kritisiert").

---

## 📋 Gewünschtes Ausgabeformat (Markdown)

Bitte halte dich exakt an die folgende Gliederung und nutze passende Emojis:

### Allgemeine Stimmung
*(Ein ausführlicher Absatz über den allgemeinen Ton der Diskussion, die vorherrschenden Emotionen und die Dynamik der Debatte.)*

### ✅ Positive Aspekte (Pros)
- *Detaillierte Liste der positiv bewerteten Punkte, die von Nutzern genannt wurden. Füge, wenn möglich, Beispiele oder Kontext hinzu.*
  - *Unterpunkte für spezifische Details oder Nuancen.*

### ❌ Negative Aspekte (Kritik & Nachteile)
- *Detaillierte Liste der Kritikpunkte und genannten Nachteile. Beschreibe die Art der Kritik und ihre Häufigkeit.*
  - *Unterpunkte für spezifische Probleme oder wiederkehrende Beschwerden.*

### 💡 Neutrale Beobachtungen & Häufig gestellte Fragen
- *Ausführliche Liste der wiederkehrenden Fragen, neutralen Anmerkungen oder Informationen, die keine klare positive oder negative Wertung haben.*
  - *Unterpunkte für spezifische Fragen oder Vergleiche.*

### 📈 Auffällige Trends & Muster
*(Ein Absatz über besondere Muster in den Kommentaren, z.B. bestimmte Argumentationsketten, Vergleiche mit anderen Produkten oder die Reaktion auf bestimmte Aspekte des Deals.)*

### 💬 Wichtige Zitate
> **"Ein besonders prägnantes oder repräsentatives positives Zitat, das die Stimmung gut einfängt."**
>
> **"Ein repräsentatives kritisches Zitat, das ein Hauptproblem oder eine starke Meinungsverschiedenheit verdeutlicht."**
>
> **"Ein neutrales, aufschlussreiches Zitat, das eine wichtige Beobachtung oder Frage hervorhebt."**

### 🏁 Fazit
*(Eine abschließende, ausgewogene und detaillierte Zusammenfassung der gesamten Diskussion in 3-5 prägnanten Sätzen, die die Essenz der Kommentare einfängt.)*

---

## Daten
`
        }
    };
    
    let collectedComments = [];
    let exportBtn = null;
    let expandBtn = null;
    let scriptStart = Date.now();
    let totalExpectedComments = 0;
    let currentPromptLevel = 'MEDIUM';
    
    // Array for console logs
    const consoleLogs = [];
    const originalConsoleLog = console.log;
    const originalConsoleError = console.error;
    const originalConsoleWarn = console.warn;
    
    console.log = function (...args) {
        consoleLogs.push({ type: 'log', message: args.map(a => String(a)).join(' ') });
        originalConsoleLog.apply(console, args);
    };
    console.error = function (...args) {
        consoleLogs.push({ type: 'error', message: args.map(a => String(a)).join(' ') });
        originalConsoleError.apply(console, args);
    };
    console.warn = function (...args) {
        consoleLogs.push({ type: 'warn', message: args.map(a => String(a)).join(' ') });
        originalConsoleWarn.apply(console, args);
    };
    
    const sleep = ms => new Promise(r => setTimeout(r, ms));
    
    function showNotification(message, type = 'info') {
        const notification = document.createElement('div');
        const bgColor = type === 'error' ? BTN_COLORS.ERROR : BTN_COLORS.NORMAL;
        Object.assign(notification.style, {
            position: 'fixed', top: '80px', right: '20px', padding: '12px 20px', background: bgColor, color: '#fff',
            border: 'none', borderRadius: '4px', fontSize: '14px', zIndex: 10000, boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
            opacity: '0', transition: 'opacity 0.3s ease-in-out'
        });
        notification.textContent = message;
        document.body.appendChild(notification);
        setTimeout(() => notification.style.opacity = '1', 10);
        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => document.body.removeChild(notification), 300);
        }, 5000);
    }
    
    function getTotalCommentsFromLink() {
        const link = document.querySelector(SELECTORS.COMMENT_LINK);
        if (!link) {
            console.warn(MESSAGES.NO_ELEMENT_FOUND(SELECTORS.COMMENT_LINK));
            return 0;
        }
        const m = link.textContent.match(/\d+/);
        return m ? parseInt(m[0], 10) : 0;
    }
    
    async function expandAllRepliesRobust() {
        let lastCount = -1;
        let stableCount = 0;
        while (true) {
            const btns = Array.from(document.querySelectorAll(SELECTORS.REPLY_BTN)).filter(btn => btn.offsetParent !== null);
            if (btns.length === 0) break;
            if (btns.length === lastCount) {
                stableCount++;
                if (stableCount >= 3) break;
            } else { 
                stableCount = 0; 
            }
            lastCount = btns.length;
            btns.forEach(btn => btn.click());
            await sleep(INTERVAL.REPLY_WAIT);
        }
    }
    
    // Standalone expand replies function with click counter
    async function expandAllRepliesWithCounter(button) {
        return new Promise((resolve) => {
            let totalClicks = 0;
            let consecutiveChecksWithoutAction = 0;
            const MAX_CHECKS = 5;
            
            if (button) {
                button.textContent = 'Erweitere...';
                button.style.background = BTN_COLORS.EXPAND_PROCESSING;
                button.disabled = true;
            }
            
            const checkAndClick = async () => {
                const buttons = Array.from(document.querySelectorAll(SELECTORS.REPLY_BTN))
                    .filter(btn => btn.offsetParent !== null);
                
                if (buttons.length > 0) {
                    consecutiveChecksWithoutAction = 0;
                    const buttonToClick = buttons[0];
                    
                    console.log(`Klicke Button: "${buttonToClick.textContent.trim()}"`);
                    try {
                        buttonToClick.click();
                        totalClicks++;
                        await sleep(INTERVAL.CLICK_COOLDOWN);
                        setTimeout(checkAndClick, 0);
                    } catch (e) {
                        console.error('Fehler beim Klicken:', e, buttonToClick);
                        await sleep(INTERVAL.REPLY_CHECK);
                        setTimeout(checkAndClick, 0);
                    }
                } else {
                    consecutiveChecksWithoutAction++;
                    console.log(`Keine klickbaren Buttons gefunden. Check ${consecutiveChecksWithoutAction}/${MAX_CHECKS}. Warte ${INTERVAL.REPLY_CHECK}ms...`);
                    if (consecutiveChecksWithoutAction < MAX_CHECKS) {
                        setTimeout(checkAndClick, INTERVAL.REPLY_CHECK);
                    } else {
                        console.log(`Erweiterung abgeschlossen. Gesamt Klicks: ${totalClicks}.`);
                        const remainingButtons = document.querySelectorAll(SELECTORS.REPLY_BTN);
                        if (remainingButtons.length > 0) {
                            console.warn(`Warnung: ${remainingButtons.length} Buttons sind noch vorhanden. Sie könnten versteckt sein.`);
                        }
                        if (button) {
                            button.textContent = `Fertig (${totalClicks} Klicks)`;
                            button.style.background = BTN_COLORS.EXPAND_SUCCESS;
                            button.disabled = true;
                            
                            // Reset button after 3 seconds
                            setTimeout(() => {
                                button.textContent = MESSAGES.EXPAND_BUTTON_LABEL;
                                button.style.background = BTN_COLORS.EXPAND_NORMAL;
                                button.disabled = false;
                            }, 3000);
                        }
                        resolve();
                    }
                }
            };
            
            checkAndClick();
        });
    }
    
    function cleanText(text) {
        return text.replace(/[\n\r\t]+/g, ' ').replace(/\s\s+/g, ' ').trim();
    }
    
    function collectCommentsOnPage() {
        const articles = document.querySelectorAll(SELECTORS.COMMENT_ARTICLE);
        for (const article of articles) {
            const bodyNode = article.querySelector('.comment-body .userHtml-content');
            
            // *** VERBESSERT (V4.1) ***
            // Überspringe "article"-Elemente, die keinen Text-Body haben,
            // um leere Kommentare im Export zu vermeiden.
            if (!bodyNode) {
                console.warn('Kommentar-Text-Body nicht gefunden in:', article);
                continue;
            }
            
            const text = cleanText(bodyNode.textContent);
            const reactionsBtn = article.querySelector('button.comment-reactions');
            
            let like = 0, helpful = 0, funny = 0;
            if (reactionsBtn) {
                const likeSpan = reactionsBtn.querySelector('.comment-like');
                const helpfulSpan = reactionsBtn.querySelector('.comment-helpful');
                const funnySpan = reactionsBtn.querySelector('.comment-funny');
                like = likeSpan ? parseInt(likeSpan.textContent.trim(), 10) || 0 : 0;
                helpful = helpfulSpan ? parseInt(helpfulSpan.textContent.trim(), 10) || 0 : 0;
                funny = funnySpan ? parseInt(funnySpan.textContent.trim(), 10) || 0 : 0;
            }
            
            const obj = { text, ...(like > 0 && { like }), ...(helpful > 0 && { helpful }), ...(funny > 0 && { funny }) };
            collectedComments.push(obj);
        }
    }
    
    function getThreadTitleAndUrl() {
        let title = '';
        const el = document.querySelector(SELECTORS.THREAD_TITLE);
        if (el) {
            title = el.textContent.trim();
        } else {
            // Fallback, falls der Selektor fehlschlägt
            title = document.title.replace(/\|.*$/, '').trim();
        }
        let url = window.location.origin + window.location.pathname;
        return { title, url };
    }
    
    function getMainDescription() {
        // Fallback-Kette für die Hauptbeschreibung
        let descEl = document.querySelector('.picker-highlight') ||
                     document.querySelector('.userHtml-content:not(.comment-body .userHtml-content)') ||
                     document.querySelector('.thread--content');
        if (!descEl) {
            console.warn(MESSAGES.NO_ELEMENT_FOUND('.picker-highlight, .userHtml-content:not(.comment-body .userHtml-content), .thread--content'));
            return '';
        }
        return cleanText(descEl.innerText || descEl.textContent || '');
    }
    
    function buildIntroText(commentCount, actualCommentCount, level = 'MEDIUM') {
        const { title, url } = getThreadTitleAndUrl();
        let commentCountNote = `${commentCount} Kommentare`;
        if (actualCommentCount < commentCount) {
            commentCountNote = `${actualCommentCount} von ${commentCount} Kommentaren (potenziell unvollständig)`;
        } else if (actualCommentCount > commentCount) {
            commentCountNote = `${actualCommentCount} Kommentare (mehr als erwartet, alle erfasst)`;
        }
        
        const promptTemplate = PROMPT_LEVELS[level.toUpperCase()]?.intro;
        if (!promptTemplate) {
            console.error(`Unbekanntes Prompt-Level: ${level}. Nutze Standard 'MEDIUM'.`);
            return PROMPT_LEVELS.MEDIUM.intro(title, url, commentCountNote);
        }
        return promptTemplate(title, url, commentCountNote);
    }
    
    function openExportWindowWithLLM(jsonlData, initialHeader, filename) {
        const w = window.open('', 'blank', 'width=950,height=800,resizable=yes,scrollbars=yes');
        if (!w) {
            showNotification(MESSAGES.POPUP_BLOCKED, 'error');
            throw new Error('Popup wurde blockiert.');
        }
        
        w.document.title = 'MyDealz Kommentar-Export';
        w.document.head.innerHTML = `
            <style>
                html, body { height: 100%; margin: 0; padding: 0; box-sizing: border-box; overflow: hidden; }
                body { font-family: sans-serif; margin: 20px; min-width: 600px; min-height: 400px; box-sizing: border-box; display: flex; flex-direction: column; align-items: stretch; height: calc(100vh - 40px); }
                .button-row { display: flex; align-items: center; margin-bottom: 12px; flex-wrap: wrap; gap: 8px; padding: 5px; }
                .button-row-left { display: flex; align-items: center; gap: 8px; }
                .button-row-right { display: flex; align-items: center; gap: 8px; margin-left: auto; }
                .ki-btn-row { display: flex; align-items: center; margin-bottom: 12px; flex-wrap: wrap; gap: 8px; padding: 5px; }
                
                button {
                    display: inline-flex;
                    align-items: center;
                    padding: 10px 16px;
                    border: none;
                    border-radius: 4px;
                    font-size: 16px;
                    cursor: pointer;
                    transition: background-color 0.2s, transform 0.1s;
                }
                button:hover:not(:disabled) {
                    transform: translateY(-1px);
                    filter: brightness(1.1);
                }
                button:active:not(:disabled) {
                    transform: translateY(0);
                    filter: brightness(0.9);
                }
                
                #copyBtn { background: #d32f2f; color: #fff; }
                #saveBtn { background: #007bff; color: #fff; }
                .prompt-level-btn { background: #6c757d; color: #fff; }
                .prompt-level-btn.active { background: #28a745; }
                #showLogsBtn { background: ${BTN_COLORS.INFO}; color: #fff; }
                #copiedMsg { min-width: 60px; display: inline-block; color: #4caf50; font-weight: bold; opacity: 0; transition: opacity .3s; }
                .ki-btn { background: #eee; color: #333; }
                .ki-btn:hover { background: #e0e0e0; }
                
                .main-content { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
                pre { flex: 1 1 auto; min-height: 200px; max-height: 70vh; white-space: pre-wrap; word-wrap: break-word; border: 1px solid #ccc; padding: 12px; border-radius: 4px; overflow: auto; margin: 0; background: #fafafa; }
                #loadingOverlay {
                    position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255,255,255,0.9);
                    display: flex; justify-content: center; align-items: center; z-index: 10001; font-size: 24px; font-weight: bold;
                }
                #errorMsg { color: #e53935; font-weight: bold; margin-left: 10px; }
                .log-container {
                    flex: 1 1 auto; min-height: 200px; max-height: 70vh; white-space: pre-wrap; word-wrap: break-word; border: 1px solid #ccc; padding: 12px; border-radius: 4px; overflow: auto; margin: 0; background: #fafafa;
                    display: none;
                }
                .log-message { font-family: monospace; font-size: 13px; margin: 0; padding: 2px 0; }
                .log-message.error { color: #e53935; }
                .log-message.warn { color: #ff9800; }
                .log-message.log { color: #333; }
            </style>
        `;
        w.document.body.innerHTML = `
            <div id="loadingOverlay">${MESSAGES.POPUP_LOADING}</div>
            <div class="button-row">
                <div class="button-row-left">
                    <button id="copyBtn">${MESSAGES.COPY_TO_CLIPBOARD}</button>
                    <button id="saveBtn">Save .jsonl</button>
                </div>
                <div class="button-row-right">
                    <span id="copiedMsg">&nbsp;</span>
                    <span id="errorMsg"></span>
                    <button id="showLogsBtn">Error Logs</button>
                </div>
            </div>
            <div class="button-row">
                <button id="${PROMPT_LEVELS.NONE.id}" class="prompt-level-btn">${PROMPT_LEVELS.NONE.label}</button>
                <button id="${PROMPT_LEVELS.SHORT.id}" class="prompt-level-btn">${PROMPT_LEVELS.SHORT.label}</button>
                <button id="${PROMPT_LEVELS.MEDIUM.id}" class="prompt-level-btn">${PROMPT_LEVELS.MEDIUM.label}</button>
                <button id="${PROMPT_LEVELS.DETAILED.id}" class="prompt-level-btn">${PROMPT_LEVELS.DETAILED.label}</button>
            </div>
            <div class="ki-btn-row">
                ${KI_LINKS.map(btn => `<button class="ki-btn" id="${btn.id}">${btn.label}</button>`).join('')}
            </div>
            <div class="main-content">
                <pre id="exportText"></pre>
                <div id="logContainer" class="log-container"></div>
            </div>
        `;
        
        const loadingOverlay = w.document.getElementById('loadingOverlay');
        loadingOverlay.style.display = 'flex';
        
        setTimeout(() => {
            const exportTextPre = w.document.getElementById('exportText');
            exportTextPre.textContent = initialHeader + jsonlData;
            loadingOverlay.style.display = 'none';
            
            const btnC = w.document.getElementById('copyBtn');
            const btnS = w.document.getElementById('saveBtn');
            const btnLogs = w.document.getElementById('showLogsBtn');
            const msg = w.document.getElementById('copiedMsg');
            const errorMsgSpan = w.document.getElementById('errorMsg');
            const logContainerDiv = w.document.getElementById('logContainer');
            
            const promptNoneBtn = w.document.getElementById(PROMPT_LEVELS.NONE.id);
            const promptShortBtn = w.document.getElementById(PROMPT_LEVELS.SHORT.id);
            const promptMediumBtn = w.document.getElementById(PROMPT_LEVELS.MEDIUM.id);
            const promptDetailedBtn = w.document.getElementById(PROMPT_LEVELS.DETAILED.id);
            const promptLevelBtns = [promptNoneBtn, promptShortBtn, promptMediumBtn, promptDetailedBtn];
            
            w.document.getElementById(PROMPT_LEVELS[currentPromptLevel].id).classList.add('active');
            
            const updatePromptLevel = (level) => {
                currentPromptLevel = level;
                
                const { title, url } = getThreadTitleAndUrl();
                const maindescription = getMainDescription();
                
                const commentsJsonl = collectedComments.map(obj => JSON.stringify(obj)).join('\n');
                
                const metaObj = {
                    'Metadaten': {
                        'Titel': title,
                        'URL': url,
                        'Kommentare (gefunden)': collectedComments.length,
                        'Kommentare (erwartet)': totalExpectedComments,
                        'Export-Zeitpunkt': new Date().toISOString(),
                        'Laufzeit des Skripts': Math.round((Date.now() - scriptStart) / 1000) + 's',
                        'Skript-Version': SCRIPT_VERSION
                    }
                };
                
                const newHeader = buildIntroText(totalExpectedComments, collectedComments.length, level);
                exportTextPre.textContent = newHeader + JSON.stringify(metaObj) + '\n' + JSON.stringify({ 'maindescription': maindescription }) + '\n' + commentsJsonl;
                
                promptLevelBtns.forEach(btn => btn.classList.remove('active'));
                w.document.getElementById(PROMPT_LEVELS[level].id).classList.add('active');
            };
            
            promptNoneBtn.onclick = () => updatePromptLevel('NONE');
            promptShortBtn.onclick = () => updatePromptLevel('SHORT');
            promptMediumBtn.onclick = () => updatePromptLevel('MEDIUM');
            promptDetailedBtn.onclick = () => updatePromptLevel('DETAILED');
            
            btnC.onclick = () => {
                if (!exportTextPre.textContent) {
                    errorMsgSpan.textContent = MESSAGES.NOTHING_TO_COPY;
                    setTimeout(() => errorMsgSpan.textContent = '', 3000);
                    return;
                }
                w.navigator.clipboard.writeText(exportTextPre.textContent).then(() => {
                    msg.textContent = MESSAGES.COPIED;
                    msg.style.opacity = 1;
                    errorMsgSpan.textContent = '';
                    setTimeout(() => { msg.style.opacity = 0; msg.textContent = '\xa0'; }, 2000);
                }).catch(err => {
                    errorMsgSpan.textContent = `Copy-Fehler: ${err.message}`;
                    console.error('Fehler beim Kopieren:', err);
                });
            };
            
            btnS.onclick = () => {
                try {
                    const blob = new Blob([exportTextPre.textContent], { type: 'application/jsonl;charset=utf-8' });
                    const url = w.URL.createObjectURL(blob);
                    const a = w.document.createElement('a');
                    a.href = url;
                    a.download = filename + '.jsonl';
                    w.document.body.appendChild(a);
                    a.click();
                    w.URL.revokeObjectURL(url);
                    w.document.body.removeChild(a);
                    errorMsgSpan.textContent = '';
                } catch (err) {
                    errorMsgSpan.textContent = `Speicher-Fehler: ${err.message}`;
                    console.error('Fehler beim Speichern:', err);
                }
            };
            
            btnLogs.onclick = () => {
                if (logContainerDiv.style.display === 'none') {
                    logContainerDiv.innerHTML = '';
                    consoleLogs.forEach(entry => {
                        const p = w.document.createElement('p');
                        p.className = `log-message ${entry.type}`;
                        p.textContent = `[${entry.type.toUpperCase()}] ${entry.message}`;
                        logContainerDiv.appendChild(p);
                    });
                    logContainerDiv.style.display = 'block';
                    exportTextPre.style.display = 'none';
                    btnLogs.textContent = 'Hide Logs';
                } else {
                    logContainerDiv.style.display = 'none';
                    exportTextPre.style.display = 'block';
                    btnLogs.textContent = 'Error Logs';
                }
            };
            
            KI_LINKS.forEach(btn => {
                const el = w.document.getElementById(btn.id);
                if (el) el.onclick = () => window.open(btn.url, '_blank');
            });
        }, 50);
    }
    
    async function runExport() {
        try {
            exportBtn.disabled = true;
            exportBtn.style.background = BTN_COLORS.NORMAL;
            scriptStart = Date.now();
            collectedComments = [];
            consoleLogs.length = 0;
            
            exportBtn.textContent = MESSAGES.START_EXPORT;
            await sleep(INTERVAL.UI_RENDER);
            
            totalExpectedComments = getTotalCommentsFromLink();
            
            const first = document.querySelector(SELECTORS.FIRST_PAGE);
            if (first) {
                first.click();
                exportBtn.textContent = MESSAGES.GOTO_PAGE_1;
                await sleep(INTERVAL.PAGE);
            }
            
            let pageCount = 1;
            while (true) {
                const currentPageStr = document.querySelector(SELECTORS.CURRENT_PAGE)?.textContent.trim() || `~${pageCount}`;
                const currentCommentsCollected = collectedComments.length;
                
                exportBtn.textContent = MESSAGES.EXPANDING_REPLIES(currentPageStr, currentCommentsCollected, totalExpectedComments);
                await sleep(INTERVAL.UI_RENDER);
                await expandAllRepliesRobust();
                
                exportBtn.textContent = MESSAGES.COLLECTING_COMMENTS(currentPageStr, currentCommentsCollected, totalExpectedComments);
                await sleep(INTERVAL.UI_RENDER);
                collectCommentsOnPage();
                
                const btn = document.querySelector(SELECTORS.NEXT_PAGE);
                if (!btn) break;
                
                pageCount++;
                btn.click();
                exportBtn.textContent = MESSAGES.LOADING_PAGE(pageCount, collectedComments.length, totalExpectedComments);
                await sleep(INTERVAL.PAGE);
            }
            
            exportBtn.textContent = MESSAGES.PREPARING_EXPORT;
            await sleep(INTERVAL.UI_RENDER);
            
            const ist = collectedComments.length;
            const duration = Math.round((Date.now() - scriptStart) / 1000);
            const { title, url } = getThreadTitleAndUrl();
            const maindescription = getMainDescription();
            
            const metaObj = {
                'Metadaten': {
                    'Titel': title,
                    'URL': url,
                    'Kommentare (gefunden)': ist,
                    'Kommentare (erwartet)': totalExpectedComments,
                    'Export-Zeitpunkt': new Date().toISOString(),
                    'Laufzeit des Skripts': duration + 's',
                    'Skript-Version': SCRIPT_VERSION
                }
            };
            
            if (ist === 0) {
                exportBtn.textContent = MESSAGES.NO_COMMENTS_FOUND;
                exportBtn.style.background = BTN_COLORS.ERROR;
                showNotification(MESSAGES.NO_COMMENTS_FOUND, 'error');
            } else if (ist < totalExpectedComments) {
                exportBtn.textContent = MESSAGES.EXPORT_WITH_DISCREPANCY(ist, totalExpectedComments);
                exportBtn.style.background = BTN_COLORS.ERROR;
                showNotification(MESSAGES.EXPORT_WITH_DISCREPANCY(ist, totalExpectedComments), 'error');
            } else {
                exportBtn.textContent = MESSAGES.EXPORT_COMPLETE;
                exportBtn.style.background = BTN_COLORS.SUCCESS;
            }
            
            const jsonlData = JSON.stringify(metaObj) + '\n' + JSON.stringify({ 'maindescription': maindescription }) + '\n' + collectedComments.map(obj => JSON.stringify(obj)).join('\n');
            
            const initialHeader = buildIntroText(totalExpectedComments, ist, currentPromptLevel);
            
            openExportWindowWithLLM(jsonlData, initialHeader, title.replace(/[\\/:*?"<>|]/g, '') || 'mydealz-comments');
            
            setTimeout(() => {
                exportBtn.textContent = MESSAGES.EXPORT_BUTTON_LABEL;
                exportBtn.style.background = BTN_COLORS.NORMAL;
                exportBtn.disabled = false;
            }, 4000);
            
        } catch (error) {
            console.error('Fehler beim Kommentar-Export:', error);
            if (error.message !== 'Popup wurde blockiert.') {
                exportBtn.textContent = MESSAGES.ERROR_GENERIC;
                exportBtn.style.background = BTN_COLORS.ERROR;
                showNotification(MESSAGES.ERROR_GENERIC, 'error');
            }
            setTimeout(() => {
                exportBtn.textContent = MESSAGES.EXPORT_BUTTON_LABEL;
                exportBtn.style.background = BTN_COLORS.NORMAL;
                exportBtn.disabled = false;
            }, 5000);
        }
    }
    
    async function handleExpandReplies() {
        await expandAllRepliesWithCounter(expandBtn);
    }
    
    function injectButtons() {
        // Export button
        exportBtn = document.createElement('button');
        exportBtn.textContent = MESSAGES.EXPORT_BUTTON_LABEL;
        Object.assign(exportBtn.style, {
            position: 'fixed', top: '20px', right: '20px', padding: '10px 16px', background: BTN_COLORS.NORMAL,
            color: '#fff', border: 'none', borderRadius: '4px', fontSize: '14px', cursor: 'pointer', zIndex: 9999,
            transition: 'background-color 0.3s'
        });
        
        // Long-click detection for debug mode
        let longClickTimer = null;
        let countdownInterval = null;
        let isLongClickActive = false;
        let debugModeTriggered = false;
        const originalButtonText = MESSAGES.EXPORT_BUTTON_LABEL;
        
        exportBtn.addEventListener('mousedown', () => {
            isLongClickActive = false;
            debugModeTriggered = false;
            let countdown = 4;
            
            longClickTimer = setTimeout(() => {
                isLongClickActive = true;
                exportBtn.textContent = `Debug-Modus in ${countdown}s`;
                exportBtn.style.background = BTN_COLORS.INFO;
                
                countdownInterval = setInterval(() => {
                    countdown--;
                    if (countdown > 0) {
                        exportBtn.textContent = `Debug-Modus in ${countdown}s`;
                    } else {
                        clearInterval(countdownInterval);
                        exportBtn.textContent = originalButtonText;
                        exportBtn.style.background = BTN_COLORS.NORMAL;
                        debugModeTriggered = true;
                        activateDebugMode();
                    }
                }, 1000);
            }, 1000); // Start countdown after 1 second hold
        });
        
        exportBtn.addEventListener('mouseup', () => {
            if (longClickTimer) {
                clearTimeout(longClickTimer);
                longClickTimer = null;
            }
            if (countdownInterval) {
                clearInterval(countdownInterval);
                countdownInterval = null;
            }
            
            // Reset button appearance if countdown was active
            if (isLongClickActive && !debugModeTriggered) {
                exportBtn.textContent = originalButtonText;
                exportBtn.style.background = BTN_COLORS.NORMAL;
            }
            
            // Reset flags after a short delay to prevent click
            setTimeout(() => {
                isLongClickActive = false;
                debugModeTriggered = false;
            }, 100);
        });
        
        exportBtn.addEventListener('mouseleave', () => {
            if (longClickTimer) {
                clearTimeout(longClickTimer);
                longClickTimer = null;
            }
            if (countdownInterval) {
                clearInterval(countdownInterval);
                countdownInterval = null;
            }
            
            // Reset button appearance
            if (isLongClickActive) {
                exportBtn.textContent = originalButtonText;
                exportBtn.style.background = BTN_COLORS.NORMAL;
            }
            
            isLongClickActive = false;
            debugModeTriggered = false;
        });
        
        exportBtn.onclick = (e) => {
            // Prevent normal click if long-click was active
            if (isLongClickActive || debugModeTriggered) {
                e.preventDefault();
                e.stopPropagation();
                return;
            }
            runExport();
        };
        
        document.body.appendChild(exportBtn);
        
        // Expand replies button
        expandBtn = document.createElement('button');
        expandBtn.textContent = MESSAGES.EXPAND_BUTTON_LABEL;
        Object.assign(expandBtn.style, {
            position: 'fixed', top: '60px', right: '20px', padding: '10px 16px', background: BTN_COLORS.EXPAND_NORMAL,
            color: '#fff', border: 'none', borderRadius: '4px', fontSize: '14px', cursor: 'pointer', zIndex: 9999,
            transition: 'background-color 0.3s'
        });
        expandBtn.onclick = handleExpandReplies;
        document.body.appendChild(expandBtn);
    }
    
    function activateDebugMode() {
        console.log('🔍 Debug-Modus aktiviert - Erweitere zuerst alle Antworten...');
        
        // First expand all replies, then highlight
        expandAllRepliesWithCounter(null).then(() => {
            console.log('🔍 Alle Antworten erweitert - Markiere jetzt alle extrahierten Elemente...');
            
            // Create info overlay
            const infoOverlay = document.createElement('div');
            Object.assign(infoOverlay.style, {
                position: 'fixed',
                top: '120px',
                right: '20px',
                background: 'rgba(0, 0, 0, 0.9)',
                color: '#fff',
                padding: '20px',
                borderRadius: '8px',
                fontSize: '14px',
                zIndex: 10001,
                maxWidth: '400px',
                lineHeight: '1.6'
            });
            
            const closeBtn = document.createElement('button');
            closeBtn.textContent = '✕ Debug-Modus beenden';
            Object.assign(closeBtn.style, {
                background: '#e53935',
                color: '#fff',
                border: 'none',
                padding: '8px 12px',
                borderRadius: '4px',
                cursor: 'pointer',
                marginTop: '10px',
                width: '100%'
            });
            
            let infoHTML = '<strong>🔍 Debug-Modus - Extrahierte Elemente:</strong><br><br>';
            
            // Highlight and count thread title
            const titleEl = document.querySelector(SELECTORS.THREAD_TITLE);
            if (titleEl) {
                titleEl.style.boxShadow = '0 0 0 3px rgba(255, 0, 0, 0.4)';
                titleEl.style.background = 'rgba(255, 0, 0, 0.1)';
                titleEl.setAttribute('data-debug-highlight', 'true');
                infoHTML += `✅ <strong>Thread-Titel:</strong> "${titleEl.textContent.trim().substring(0, 50)}..."<br>`;
            } else {
                infoHTML += '❌ <strong>Thread-Titel:</strong> Nicht gefunden<br>';
            }
            
            // Highlight main description
            const descEl = document.querySelector('.picker-highlight') ||
                           document.querySelector('.userHtml-content:not(.comment-body .userHtml-content)') ||
                           document.querySelector('.thread--content');
            if (descEl) {
                descEl.style.boxShadow = '0 0 0 3px rgba(255, 100, 0, 0.4)';
                descEl.style.background = 'rgba(255, 100, 0, 0.1)';
                descEl.setAttribute('data-debug-highlight', 'true');
                const descText = cleanText(descEl.innerText || descEl.textContent || '');
                infoHTML += `✅ <strong>Hauptbeschreibung:</strong> ${descText.length} Zeichen<br>`;
            } else {
                infoHTML += '❌ <strong>Hauptbeschreibung:</strong> Nicht gefunden<br>';
            }
            
            // Highlight both comment count elements
            const commentLink = document.querySelector(SELECTORS.COMMENT_LINK);
            const commentCountHeader = document.querySelector(SELECTORS.COMMENT_COUNT_HEADER);
            let commentCountFound = 0;
            
            if (commentLink) {
                commentLink.style.boxShadow = '0 0 0 3px rgba(0, 150, 255, 0.4)';
                commentLink.style.background = 'rgba(0, 150, 255, 0.1)';
                commentLink.setAttribute('data-debug-highlight', 'true');
                commentCountFound++;
            }
            
            if (commentCountHeader) {
                commentCountHeader.style.boxShadow = '0 0 0 3px rgba(0, 150, 255, 0.4)';
                commentCountHeader.style.background = 'rgba(0, 150, 255, 0.1)';
                commentCountHeader.setAttribute('data-debug-highlight', 'true');
                commentCountFound++;
            }
            
            if (commentCountFound > 0) {
                const totalComments = getTotalCommentsFromLink();
                infoHTML += `✅ <strong>Kommentar-Anzahl:</strong> ${totalComments} (${commentCountFound} Stellen gefunden)<br>`;
            } else {
                infoHTML += '❌ <strong>Kommentar-Anzahl:</strong> Nicht gefunden<br>';
            }
            
            infoHTML += '<br><strong>📝 Kommentare auf dieser Seite:</strong><br>';
            
            // Highlight all comment articles
            const articles = document.querySelectorAll(SELECTORS.COMMENT_ARTICLE);
            infoHTML += `✅ <strong>Anzahl Kommentare:</strong> ${articles.length}<br><br>`;
            
            let commentCount = 0;
            articles.forEach((article, index) => {
                // Don't highlight the entire article, only the text content
                const bodyNode = article.querySelector('.comment-body .userHtml-content');
                if (bodyNode) {
                    bodyNode.style.boxShadow = '0 0 0 2px rgba(0, 255, 0, 0.5)';
                    bodyNode.style.background = 'rgba(0, 255, 0, 0.1)';
                    bodyNode.setAttribute('data-debug-highlight', 'true');
                }
                
                const reactionsBtn = article.querySelector('button.comment-reactions');
                if (reactionsBtn) {
                    reactionsBtn.style.boxShadow = '0 0 0 2px rgba(255, 200, 0, 0.5)';
                    reactionsBtn.style.background = 'rgba(255, 200, 0, 0.1)';
                    reactionsBtn.setAttribute('data-debug-highlight', 'true');
                    
                    const likeSpan = reactionsBtn.querySelector('.comment-like');
                    const helpfulSpan = reactionsBtn.querySelector('.comment-helpful');
                    const funnySpan = reactionsBtn.querySelector('.comment-funny');
                    
                    if (likeSpan || helpfulSpan || funnySpan) {
                        if (commentCount < 3) { // Show details for first 3 comments
                            const like = likeSpan ? parseInt(likeSpan.textContent.trim(), 10) || 0 : 0;
                            const helpful = helpfulSpan ? parseInt(helpfulSpan.textContent.trim(), 10) || 0 : 0;
                            const funny = funnySpan ? parseInt(funnySpan.textContent.trim(), 10) || 0 : 0;
                            infoHTML += `<small>Kommentar ${index + 1}: `;
                            if (like > 0) infoHTML += `👍 ${like} `;
                            if (helpful > 0) infoHTML += `💡 ${helpful} `;
                            if (funny > 0) infoHTML += `😄 ${funny} `;
                            infoHTML += '</small><br>';
                        }
                    }
                }
                commentCount++;
            });
            
            if (commentCount > 3) {
                infoHTML += `<small>... und ${commentCount - 3} weitere Kommentare</small><br>`;
            }
            
            infoHTML += '<br><strong>🎨 Legende:</strong><br>';
            infoHTML += '<small>🔴 Rot = Thread-Titel<br>';
            infoHTML += '🟠 Orange = Hauptbeschreibung<br>';
            infoHTML += '🔵 Blau = Kommentar-Anzahl<br>';
            infoHTML += '🟢 Grün = Kommentare<br>';
            infoHTML += '🟡 Gelb = Reaktionen (Likes, etc.)</small>';
            
            infoOverlay.innerHTML = infoHTML;
            infoOverlay.appendChild(closeBtn);
            document.body.appendChild(infoOverlay);
            
            closeBtn.onclick = () => {
                // Remove all highlights
                document.querySelectorAll('[data-debug-highlight]').forEach(el => {
                    el.style.boxShadow = '';
                    el.style.background = '';
                    el.removeAttribute('data-debug-highlight');
                });
                document.body.removeChild(infoOverlay);
                console.log('🔍 Debug-Modus beendet');
            };
        });
    }
    
    function ensureStartOnPageOne() {
        const url = new URL(window.location.href);
        const isCommentPage = url.hash.includes('comments');
        const pageParam = url.searchParams.get('page');
        if (pageParam && pageParam !== '1' && isCommentPage) {
            showNotification(MESSAGES.SCRIPT_RELOAD_PAGE_1);
            url.searchParams.set('page', '1');
            url.hash = 'comments';
            window.location.href = url.toString();
        } else {
            injectButtons();
        }
    }
    
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        ensureStartOnPageOne();
    } else {
        window.addEventListener('load', ensureStartOnPageOne);
    }
})();