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

Exportiert Kommentare als LLM- und Markdown-optimiertes JSONL. Version 3.8: Fixes für aktiven Button-Status und Button-Padding.

// ==UserScript==
// @name          MyDealz JSONL-Kommentar Extraktor für LLM mit Markdown 🧙‍♂️
// @namespace     violentmonkey
// @version       4.0
// @description   Exportiert Kommentare als LLM- und Markdown-optimiertes JSONL. Version 3.8: Fixes für aktiven Button-Status und Button-Padding.
// @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.0';
    
    // 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"]',
        COMMENT_COUNT_HEADER: 'h2 span.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');
            const text = bodyNode ? 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 {
            title = document.title.replace(/\|.*$/, '').trim();
        }
        let url = window.location.origin + window.location.pathname;
        return { title, url };
    }
    
    function getMainDescription() {
        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);
    }
})();

QingJ © 2025

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