// ==UserScript== // @name MyDealz JSONL-Kommentar Extraktor für LLM mit Markdown 🧙♂️ // @namespace violentmonkey // @version 3.8 // @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/assets/img/emojis/pirate_1b64c.svg // @grant none // @downloadURL none // ==/UserScript== (function () { 'use strict'; // --- Konfiguration --- const SCRIPT_VERSION = '3.8'; // KI-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/' } ]; // Selektoren & Konstanten 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_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 }; const BTN_COLORS = { NORMAL: '#2c7ff3', ERROR: '#e53935', SUCCESS: '#4caf50', INFO: '#607d8b' }; // INFO für Logs // Neue Konstanten für Textmeldungen 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', POPUP_LOADING: 'Generiere Daten...' }; // Prompt-Level Definitionen const PROMPT_LEVELS = { SHORT: { label: 'Kurz', id: 'promptShortBtn', // Hinzugefügt für einfacheren Zugriff 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', // Hinzugefügt 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".* ### 💬 Wichtige Zitate > **"Ein besonders positives oder repräsentatives Zitat..."** > > **"Ein repräsentatives kritisches Zitat oder eine wichtige Beobachtung..."** ### 🏁 Fazit *(Eine abschließende, neutrale Zusammenfassung der Diskussion in 2-3 prägnanten Sätzen.)* --- ## Daten ` }, DETAILED: { label: 'Detailliert', id: 'promptDetailedBtn', // Hinzugefügt 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 scriptStart = Date.now(); let totalExpectedComments = 0; // Speichert die erwartete Anzahl Kommentare let currentPromptLevel = 'MEDIUM'; // Speichert den aktuell aktiven Prompt-Level // Array zum Speichern von Konsolenmeldungen für Debug-Zwecke im Popup const consoleLogs = []; const originalConsoleLog = console.log; const originalConsoleError = console.error; const originalConsoleWarn = console.warn; // Überschreiben von console.log, console.error und console.warn, um Meldungen abzufangen 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; // Warte 3 Iterationen ohne Änderung } else { stableCount = 0; } // Reset, wenn sich Anzahl ändert lastCount = btns.length; btns.forEach(btn => btn.click()); await sleep(INTERVAL.REPLY_WAIT); } } 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; } // Destrukturierung wie vorgeschlagen 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 || ''); } // Angepasste Funktion zum Erstellen des Intro-Textes mit Level-Parameter 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 = ` `; w.document.body.innerHTML = `