MyDealz Comment Viewer

Zeigt die letzten Kommentare eines Benutzers an

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://updategf.qytechs.cn/scripts/528796/1803908/MyDealz%20Comment%20Viewer.js

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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

})();