MyDealz Comment Viewer

Zeigt die letzten Kommentare eines Benutzers an

Ce script ne devrait pas être installé directement. C'est une librairie créée pour d'autres scripts. Elle doit être inclus avec la commande // @require https://update.greasyfork.org/scripts/528796/1803908/MyDealz%20Comment%20Viewer.js

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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);
    }

})();