// ==UserScript== // @name MyDealz JSONL-Kommentar Extraktor für LLM mit Markdown 🧙‍♂️ // @namespace violentmonkey // @version 3.0 // @description Exportiert Kommentare als LLM- und Markdown-optimiertes JSONL (ein Objekt pro Zeile) mit präzisem Prompt, Metadatenobjekt, maindescription, LLM-Buttons, robustem Expandieren, UTF-8 Versionsänderung: JSON ---> JSONL (einzeilig, besser für LLM) // @match https://www.mydealz.de/diskussion/* // @match https://www.mydealz.de/deals/* // @grant none // @downloadURL none // ==/UserScript== (function () { 'use strict'; // 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 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])', LAST_PAGE: 'button[aria-label="Letzte Seite"]', 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 = { CHECK: 500, CLICK: 200, PAGE: 2000, REPLY_WAIT: 800 }; let collectedComments = []; let exportBtn = null; let scriptStart = Date.now(); const sleep = ms => new Promise(r => setTimeout(r, ms)); function getTotalCommentsFromLink() { const link = document.querySelector(SELECTORS.COMMENT_LINK); if (!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); } } 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 }; if (like > 0) obj.like = like; if (helpful > 0) obj.helpful = helpful; if (funny > 0) obj.funny = 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(); } // Nur der Basis-Deal-Link ohne Parameter und Hash let url = window.location.origin + window.location.pathname; return { title, url }; } // Hauptbeschreibung extrahieren (Dealtext, ohne Kommentare) function getMainDescription() { let descEl = document.querySelector('.picker-highlight') || document.querySelector('.userHtml-content:not(.comment-body .userHtml-content)') || document.querySelector('.thread--content'); if (!descEl) return ''; return cleanText(descEl.innerText || descEl.textContent || ''); } function buildIntroText(commentCount) { const { title, url } = getThreadTitleAndUrl(); return ( `# Zusammenfassung der ${commentCount} Kommentare zur MyDealz-Diskussion [${title}](${url}) ## Anweisung Fasse die bereitgestellten Benutzerkommentare zu der Diskussion zusammen. Die Daten liegen im JSONL-Format vor (jedes Objekt in einer neuen Zeile): - **Metadaten:** {"Metadaten": {...}} - **Hauptbeschreibung:** {"maindescription": "..."} - **Kommentare:** {"text": "...", "like": ##, "helpful": ##, "funny": ##} (Die Felder "like", "helpful", "funny" sind nur vorhanden, wenn sie > 0 sind.) **Formatierungshinweise für die Zusammenfassung:** - Nutze Überschriften (z.B. ## Hauptthemen, ## Kritikpunkte). - Verwende Listen (- oder *) für Aufzählungen. - Baue passende Emojis zur Auflockerung des Textflusses ein (z.B. ✅😅👍). - Hebe besonders relevante Zitate als Blockquote hervor, und markiere sie zusätzlich mit Fettdruck, z.B.: > **"Das Produkt ist für den Preis unschlagbar!"** - Achte auf eine klare, strukturierte und lesbare Darstellung im Markdown-Format. --- ## Daten ` ); } function openExportWindowWithLLM(jsonlData, header, filename) { const w = window.open('', 'blank', 'width=950,height=800,resizable=yes,scrollbars=yes'); if (!w) { alert('Popup blockiert! Bitte Popup-Blocker für diese Seite deaktivieren.'); return; } w.document.title = 'MyDealz Kommentar-Export'; w.document.head.innerHTML = ` `; w.document.body.innerHTML = `
 
${KI_LINKS.map(btn => ``).join('')}

            
`; const pre = w.document.getElementById('exportText'); pre.textContent = header + jsonlData; const btnC = w.document.getElementById('copyBtn'); const btnS = w.document.getElementById('saveBtn'); const msg = w.document.getElementById('copiedMsg'); btnC.onclick = () => { w.navigator.clipboard.writeText(header + jsonlData).then(() => { msg.textContent = 'Copied!'; msg.style.opacity = 1; setTimeout(() => { msg.style.opacity = 0; msg.textContent = '\xa0'; }, 2000); }); }; btnS.onclick = () => { const blob = new Blob([header + jsonlData], { 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); }; KI_LINKS.forEach(btn => { const el = w.document.getElementById(btn.id); if (el) el.onclick = () => window.open(btn.url, '_blank'); }); } async function runExport() { scriptStart = Date.now(); collectedComments = []; const soll = getTotalCommentsFromLink(); const first = document.querySelector(SELECTORS.FIRST_PAGE); if (first) { first.click(); await sleep(INTERVAL.PAGE); } while (true) { await expandAllRepliesRobust(); await sleep(1000); collectCommentsOnPage(); const btn = document.querySelector(SELECTORS.NEXT_PAGE); if (!btn) break; btn.click(); await sleep(INTERVAL.PAGE); } const ist = collectedComments.length; const duration = Math.round((Date.now() - scriptStart) / 1000); const { title, url } = getThreadTitleAndUrl(); const maindescription = getMainDescription(); const metaObj = { "Metadaten": { "Kommentare ist": ist, "Kommentare soll": soll, "Titel": title, "URL": url, "Laufzeit des Script": duration + "s" } }; // Daten als JSONL formatieren (jedes Objekt in einer neuen Zeile) const jsonlData = JSON.stringify(metaObj) + '\n' + JSON.stringify({ "maindescription": maindescription }) + '\n' + collectedComments.map(obj => JSON.stringify(obj)).join('\n'); const header = buildIntroText(soll); openExportWindowWithLLM(jsonlData, header, title || 'mydealz-comments'); exportBtn.textContent = 'Fertig!'; exportBtn.disabled = false; } function injectExportBtn() { exportBtn = document.createElement('button'); exportBtn.textContent = 'Kommentare als JSONL exportieren'; Object.assign(exportBtn.style, { position: 'fixed', top: '20px', right: '20px', padding: '10px 16px', background: '#2c7ff3', color: '#fff', border: 'none', borderRadius: '4px', fontSize: '14px', cursor: 'pointer', zIndex: 9999 }); exportBtn.onclick = () => { exportBtn.disabled = true; exportBtn.textContent = 'Lade...'; runExport(); }; document.body.appendChild(exportBtn); } 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) { url.searchParams.set('page', '1'); url.hash = 'comments'; window.location.href = url.toString(); } else { injectExportBtn(); } } if (document.readyState === 'complete') { ensureStartOnPageOne(); } else { window.addEventListener('load', ensureStartOnPageOne); } })();