您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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"> </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或关注我们的公众号极客氢云获取最新地址