MyDealz Comment Viewer

Zeigt die letzten Kommentare eines Benutzers an

Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greasyfork.org/scripts/528796/1803908/MyDealz%20Comment%20Viewer.js

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         MyDealz Comment Viewer
// @namespace    http://tampermonkey.net/
// @version      2.6
// @description  Zeigt die letzten Kommentare eines Benutzers an
// @author       MD928835
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Modal-Styles einmalig einfügen
    const style = document.createElement('style');
    style.textContent = `
        #mdcv-overlay {
            position: fixed; inset: 0; background: rgba(0,0,0,0.6);
            z-index: 999999; display: flex; align-items: center; justify-content: center;
        }
        #mdcv-modal {
            background: #f5f5f5; width: 90vw; max-width: 1000px;
            height: 85vh; border-radius: 8px; display: flex; flex-direction: column;
            overflow: hidden; box-shadow: 0 8px 32px rgba(0,0,0,0.3);
        }
        #mdcv-header {
            background: #00a000; height: 56px; display: flex;
            align-items: center; justify-content: center;
            color: white; font-size: 20px; position: relative; flex-shrink: 0;
        }
        #mdcv-header img { height: 36px; position: absolute; left: 16px; }
        #mdcv-close {
            position: absolute; right: 16px; background: none; border: none;
            color: white; font-size: 24px; cursor: pointer; line-height: 1;
        }
        #mdcv-sort { text-align: center; padding: 10px; background: #fff;
            border-bottom: 1px solid #ddd; flex-shrink: 0; }
        #mdcv-body { overflow-y: auto; padding: 16px; flex: 1; }
        .mdcv-card {
            background: white; padding: 1rem; margin: 0.75rem 0;
            border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        #mdcv-mute {
            display: flex; align-items: center; gap: 6px;
            padding: 6px 12px; border-radius: 4px; cursor: pointer;
            background: none; border: 1px solid white; color: white;
            position: absolute; right: 56px; font-size: 13px;
        }
        #mdcv-mute:disabled { opacity: 0.5; cursor: not-allowed; }
    `;
    document.head.appendChild(style);

    window.viewUserComments = async function(username) {

        const fetchDealTitle = async (threadId) => {
            const query = `query getThread($filter: IDFilter!) {
                thread(threadId: $filter) { title }
            }`;
            try {
                const res = await fetch("https://www.mydealz.de/graphql", {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ query, variables: { filter: { eq: threadId } } })
                });
                const result = await res.json();
                return result.data.thread.title || "Titel nicht verfügbar";
            } catch {
                return "Titel nicht verfügbar";
            }
        };

        // Ladeindikator zeigen
        showModal(username, '<p style="text-align:center;padding:2rem">Lade Kommentare…</p>', [], null, null);

        try {
            const response = await fetch(`https://www.mydealz.de/profile/${username}?page=1`);
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            const html = await response.text();

            const pattern = /href=https:\/\/www\.mydealz\.de\/.*?-(\d+)#(?:comment|reply)-(\d+)/g;
            const ids = [...html.matchAll(pattern)].map(m => ({
                threadId: m[1],
                commentId: m[2],
                url: m[0].replace('href=', '')
            }));

            // User-Metadaten (mutable, isMuted)
            const userQuery = `query userProfile($username: String) {
                user(username: $username) { mutable isMuted }
            }`;
            const userRes = await fetch('/graphql', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ query: userQuery, variables: { username } })
            });
            const userData = await userRes.json();
            const { mutable, isMuted: initialMuted } = userData.data.user;

            // Kommentare parallel laden
            const commentQuery = `query comment($id: ID!) {
                comment(id: $id) { preparedHtmlContent createdAt createdAtTs }
            }`;
            const fetchPromises = ids.map(async ({ threadId, commentId, url }) => {
                try {
                    const [commentRes, title] = await Promise.all([
                        fetch("https://www.mydealz.de/graphql", {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ query: commentQuery, variables: { id: commentId } })
                        }).then(r => r.json()),
                        fetchDealTitle(threadId)
                    ]);
                    const cd = commentRes?.data?.comment;
                    if (!cd) return null;
                    const comment = cd.preparedHtmlContent.replace(/<img[^>]*>/g, '');
                    const date = new Date(cd.createdAtTs * 1000)
                        .toLocaleString('de-DE', {
                            day: '2-digit', month: '2-digit', year: '2-digit',
                            hour: '2-digit', minute: '2-digit'
                        }).replace(',', '');
                    return { title, comment, date, createdAt: cd.createdAt,
                             dealId: threadId, commentId, url };
                } catch {
                    return null;
                }
            });

            const results = (await Promise.all(fetchPromises)).filter(Boolean);
            showModal(username, null, results, mutable, initialMuted);

        } catch (error) {
            console.error("Fehler:", error);
            const body = document.getElementById('mdcv-body');
            if (body) body.innerHTML = `<p style="color:red">Fehler: ${error.message}</p>`;
        }
    };

    function renderCards(results) {
        return results.map(r => `
            <div class="mdcv-card">
                <span title="${r.date}">${r.createdAt}</span>
                <b>${r.title}</b><br>
                ${r.comment}<br>
                <svg width="15" height="16" style="vertical-align:middle">
                    <use xlink:href="/assets/img/ico_632f5.svg#comment"></use>
                </svg>
                <a href="${r.url}" target="_blank">Zum Kommentar</a>
            </div>`).join('');
    }

    function showModal(username, loadingHtml, results, mutable, initialMuted) {
        // Altes Modal entfernen
        document.getElementById('mdcv-overlay')?.remove();

        let isMuted = initialMuted;

        const overlay = document.createElement('div');
        overlay.id = 'mdcv-overlay';

        const modal = document.createElement('div');
        modal.id = 'mdcv-modal';

        // Header
        const header = document.createElement('div');
        header.id = 'mdcv-header';
        header.innerHTML = `
            <img src="https://www.mydealz.de/assets/img/logo/default-light_d4b86.svg" alt="mydealz">
            <a href="https://www.mydealz.de/profile/${username}"
               style="color:white;text-decoration:none" target="_blank">
                ${username}s letzte ${results.length} Kommentare
            </a>`;

        // FIX #3: Mute-Button existiert jetzt tatsächlich im DOM
        if (mutable) {
            const muteBtn = document.createElement('button');
            muteBtn.id = 'mdcv-mute';
            muteBtn.innerHTML = `
                <svg width="15" height="15">
                    <use id="mdcv-mute-icon" xlink:href="/assets/img/ico_632f5.svg#${isMuted ? 'unmute' : 'mute'}"></use>
                </svg>
                <span id="mdcv-mute-text">${username} ${isMuted ? 'nicht mehr stumm schalten' : 'stumm schalten'}</span>`;
            muteBtn.addEventListener('click', async () => {
                muteBtn.disabled = true;
                const endpoint = isMuted
                    ? `/profile/${username}/unmute`
                    : `/profile/${username}/mute`;
                try {
                    const xsrf = document.cookie.split('xsrf_t=')[1]?.split(';')[0]?.replace(/"/g, '');
                    const res = await fetch(endpoint, {
                        method: 'POST',
                        headers: {
                            'X-Request-Type': 'application/vnd.pepper.v1+json',
                            'X-Requested-With': 'XMLHttpRequest',
                            'X-Pepper-Txn': 'user.profile.overview',
                            'X-XSRF-TOKEN': xsrf
                        }
                    });
                    const data = await res.json();
                    if (data.status === 'success') {
                        isMuted = !isMuted;
                        document.getElementById('mdcv-mute-text').textContent =
                            `${username} ${isMuted ? 'nicht mehr stumm schalten' : 'stumm schalten'}`;
                        document.getElementById('mdcv-mute-icon')
                            .setAttribute('xlink:href',
                                `/assets/img/ico_632f5.svg#${isMuted ? 'unmute' : 'mute'}`);
                    }
                } catch (e) {
                    console.error('Mute-Fehler:', e);
                } finally {
                    muteBtn.disabled = false;
                }
            });
            header.appendChild(muteBtn);
        }

        const closeBtn = document.createElement('button');
        closeBtn.id = 'mdcv-close';
        closeBtn.textContent = '×';
        closeBtn.title = 'Schließen';
        closeBtn.addEventListener('click', () => overlay.remove());
        header.appendChild(closeBtn);

        // Sortierung
        const sort = document.createElement('div');
        sort.id = 'mdcv-sort';
        sort.innerHTML = `
            Kommentare sortieren nach
            <label><input type="radio" name="mdcv-sort" checked value="all"> alle chronologisch</label>
            <label><input type="radio" name="mdcv-sort" value="deal"> beitragschronologisch</label>`;

        // Body
        const body = document.createElement('div');
        body.id = 'mdcv-body';
        body.innerHTML = loadingHtml || renderCards(results);

        // Sortierung — kein sessionStorage mehr nötig
        sort.addEventListener('change', (e) => {
            const sorted = [...results];
            if (e.target.value === 'deal') {
                sorted.sort((a, b) =>
                    b.dealId !== a.dealId
                        ? b.dealId - a.dealId
                        : b.commentId - a.commentId);
            } else {
                sorted.sort((a, b) => b.commentId - a.commentId);
            }
            body.innerHTML = renderCards(sorted);
        });

        // Klick außerhalb schließt Modal
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) overlay.remove();
        });

        modal.appendChild(header);
        modal.appendChild(sort);
        modal.appendChild(body);
        overlay.appendChild(modal);
        document.body.appendChild(overlay);
    }

})();