Greasy Fork

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

Exportiert Kommentare als LLM- und Markdown-optimiertes JSON-Array (eine Zeile pro Objekt) mit präzisem Prompt, Metadatenobjekt, maindescription, LLM-Buttons, robustem Expandieren, UTF-8

当前为 2025-06-08 提交的版本,查看 最新版本

// ==UserScript==
// @name         MyDealz JSON-Kommentar Extraktor für LLM mit Markdown 🧙‍♂️
// @namespace    violentmonkey
// @version      2.5
// @description  Exportiert Kommentare als LLM- und Markdown-optimiertes JSON-Array (eine Zeile pro Objekt) mit präzisem Prompt, Metadatenobjekt, maindescription, LLM-Buttons, robustem Expandieren, UTF-8
// @match        https://www.mydealz.de/diskussion/*
// @match        https://www.mydealz.de/deals/*
// @grant        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 als JSON-Array vor:

- **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(jsonFlat, 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 = `
            <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: 100vh;
                }
                .export-btns {
                    display: flex;
                    align-items: center;
                    margin-bottom: 8px;
                    flex-wrap: wrap;
                }
                .ki-btn-row {
                    display: flex;
                    align-items: center;
                    margin-bottom: 16px;
                    flex-wrap: wrap;
                }
                button {
                    display: inline-flex;
                    align-items: center;
                    padding: 10px 16px;
                    margin-right: 8px;
                    margin-bottom: 0;
                    border: none;
                    border-radius: 4px;
                    font-size: 16px;
                    cursor: pointer;
                }
                #copyBtn { background: #d32f2f; color: #fff; }
                #saveBtn { background: #007bff; color: #fff; }
                #copiedMsg {
                    min-width: 60px;
                    display: inline-block;
                    margin-right: 8px;
                    margin-left: 0;
                    color: #4caf50;
                    font-weight: bold;
                    opacity: 0;
                    transition: opacity .3s;
                    text-align: left;
                }
                .ki-btn {
                    background: #eee; color: #333;
                    margin-bottom: 0;
                }
                .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;
                }
            </style>
        `;
        w.document.body.innerHTML = `
            <div class="export-btns">
                <button id="copyBtn">Copy All</button>
                <button id="saveBtn">Save .json</button>
                <span id="copiedMsg">&nbsp;</span>
            </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>
        `;
        const pre = w.document.getElementById('exportText');
        pre.textContent = header + jsonFlat;
        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 + jsonFlat).then(() => {
                msg.textContent = 'Copied!';
                msg.style.opacity = 1;
                setTimeout(() => {
                    msg.style.opacity = 0;
                    msg.textContent = '\xa0';
                }, 2000);
            });
        };

        btnS.onclick = () => {
            const blob = new Blob([header + jsonFlat], { type: 'application/json;charset=utf-8' });
            const url = w.URL.createObjectURL(blob);
            const a = w.document.createElement('a');
            a.href = url;
            a.download = filename + '.json';
            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"
            }
        };
        const jsonFlat = '[\n'
            + JSON.stringify(metaObj) + ',\n'
            + JSON.stringify({ "maindescription": maindescription }) + ',\n'
            + collectedComments.map(obj => JSON.stringify(obj)).join(',\n')
            + '\n]';
        const header = buildIntroText(soll);

        openExportWindowWithLLM(jsonFlat, header, title || 'mydealz-comments');
        exportBtn.textContent = 'Fertig!';
        exportBtn.disabled = false;
    }

    function injectExportBtn() {
        exportBtn = document.createElement('button');
        exportBtn.textContent = 'Kommentare als JSON 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);
    }
})();