MyDealz Kommentarvolltextsuche

Suchbox für Volltextsuche in allen Kommentaren eines Deals / einer Diskussion

// ==UserScript==
// @name         MyDealz Kommentarvolltextsuche
// @namespace    https://mydealz.de/
// @version      1.0
// @description  Suchbox für Volltextsuche in allen Kommentaren eines Deals / einer Diskussion
// @match        https://www.mydealz.de/deals/*
// @match        https://www.mydealz.de/diskussion/*
// @match        https://www.mydealz.de/feedback/*
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let newWindow;
    const API_URL = 'https://www.mydealz.de/graphql';

    // Basis-Funktionen
    function getDealId() {
        const match = window.location.href.match(/-(\d+)(?=[/?#]|$)/);
        return match ? match[1] : null;
    }

    function extractThreadId() {
        const mainElement = document.getElementById('main');
        if (!mainElement) return null;

        const dataAttribute = mainElement.getAttribute('data-t-d');
        if (!dataAttribute) return null;

        return JSON.parse(dataAttribute.replace(/"/g, '"')).threadId;
    }

    function cleanHTML(html) {
        return html.replace(/<.*?>/g, '');
    }

    function highlightSearchTerm(text, searchTerm) {
        return text.replace(
            new RegExp(searchTerm, 'gi'),
            match => `<b style="color:#4CAF50;">${match}</b>`
        );
    }

    // GraphQL-Funktionen
    async function fetchGraphQLData(query, variables) {
        const response = await fetch(API_URL, {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({query, variables})
        });

        if (response.status === 429) {
            await new Promise(resolve => setTimeout(resolve, 10000));
            return fetchGraphQLData(query, variables);
        }

        const data = await response.json();
        if (data.errors) throw new Error(data.errors[0].message);
        return data.data.comments;
    }

    async function fetchAllPages(query, variables) {
        let currentPage = 1;
        let allData = [];
        while (true) {
            const data = await fetchGraphQLData(query, {...variables, page: currentPage});
            allData.push(...data.items);
            if (!data.pagination.next) break;
            currentPage++;
        }
        return allData;
    }

    async function fetchAllComments() {
        let allComments = [];
        let currentPage = 1;
        let hasMorePages = true;
        const threadId = extractThreadId();

        while (hasMorePages) {
            const query = `
                query comments($filter: CommentFilter!, $limit: Int, $page: Int) {
                    comments(filter: $filter, limit: $limit, page: $page) {
                        items { commentId replyCount }
                        pagination { current next }
                    }
                }
            `;
            const variables = {
                filter: { threadId: {eq: threadId} },
                limit: 100,
                page: currentPage
            };

            const response = await fetch(API_URL, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({query, variables})
            });

            const data = await response.json();
            if (data.errors) throw new Error(data.errors[0].message);

            allComments = allComments.concat(data.data.comments.items);
            hasMorePages = !!data.data.comments.pagination.next;
            if (hasMorePages) currentPage++;
        }
        return allComments;
    }

    async function fetchReplies(commentId, threadId) {
        const query = `
            query comments($filter: CommentFilter!, $limit: Int, $page: Int) {
                comments(filter: $filter, limit: $limit, page: $page) {
                    items {
                        commentId preparedHtmlContent user { userId username }
                        replyCount createdAt parentReply { user { username } }
                    }
                    pagination { current next }
                }
            }
        `;

        return await fetchAllPages(query, {
            filter: {
                mainCommentId: commentId,
                threadId: {eq: threadId},
                order: {direction: "Ascending"}
            },
            limit: 100
        });
    }

    async function fetchDataAndReplies(forceReload = false) {
        const dealId = getDealId();
        const threadId = extractThreadId();
        const savedComments = JSON.parse(localStorage.getItem('dealComments_' + dealId)) || [];

        if (!forceReload && savedComments.length > 0) {
            const allComments = await fetchAllComments();
            let totalReplies = 0;
            allComments.forEach(comment => {
                totalReplies += comment.replyCount || 0;
            });

            const onlineCommentCount = allComments.length + totalReplies;
            const localCommentCount = savedComments.reduce((acc, comment) =>
                acc + 1 + (comment.replies?.length || 0), 0);

            if (localCommentCount < onlineCommentCount) {
                const newCommentCount = onlineCommentCount - localCommentCount;
                newWindow.document.getElementById('newCommentsStatus').innerHTML =
                    `Es sind ${newCommentCount} neue Kommentare vorhanden.
                     <button onclick="reloadFromServer()"
                             style="background-color:#4CAF50;color:white;padding:5px 10px;
                                    border:none;border-radius:5px;font-size:14px;cursor:pointer;
                                    box-shadow:0 2px 4px rgba(0,0,0,0.2);">
                         Neue Kommentare laden
                     </button>`;
                return savedComments;
            }
            return savedComments;
        }

        const query = `
            query comments($filter: CommentFilter!, $limit: Int, $page: Int) {
                comments(filter: $filter, limit: $limit, page: $page) {
                    items {
                        commentId preparedHtmlContent user { userId username }
                        replyCount createdAt
                    }
                    pagination { current next }
                }
            }
        `;

        newWindow.document.getElementById('progressBar').style.display = 'block';
        let allData = await fetchAllPages(query, {
            filter: {
                threadId: {eq: threadId},
                order: {direction: "Ascending"}
            },
            limit: 100
        });

        let totalItems = allData.length + allData.reduce((acc, c) => acc + (c.replyCount || 0), 0);
        let processedItems = 0;

        for (const comment of allData) {
            processedItems++;
            updateProgress(processedItems, totalItems);

            if (comment.replyCount > 0) {
                const replies = await fetchReplies(comment.commentId, threadId);
                comment.replies = replies;
                processedItems += replies.length;
                updateProgress(processedItems, totalItems);
            }
        }

        localStorage.setItem('dealComments_' + dealId, JSON.stringify(allData));
        localStorage.setItem('dealComments_' + dealId + '_timestamp', new Date().toISOString());

        return allData;
    }

    function updateProgress(processed, total) {
        const percentage = Math.round((processed / total) * 100);
        newWindow.document.getElementById('progress').innerText =
            processed === total ? 'Alle Kommentare durchsucht' :
                                `Fortschritt: ${percentage}%`;
        newWindow.document.getElementById('progressBarFill').style.width = `${percentage}%`;
    }

    function processComments(allData, searchTerm) {
        const outputType = newWindow.document.querySelector('input[name="outputType"]:checked').value;
        const sortType = newWindow.document.querySelector('input[name="sortType"]:checked').value;
        const filteredComments = [];
        let totalComments = allData.length;

        allData.forEach(comment => {
            if (comment.preparedHtmlContent.toLowerCase().includes(searchTerm.toLowerCase())) {
                filteredComments.push({...comment, type: 'comment'});
            }
            if (comment.replies) {
                comment.replies.forEach(reply => {
                    totalComments++;
                    if (reply.preparedHtmlContent.toLowerCase().includes(searchTerm.toLowerCase())) {
                        filteredComments.push({...reply, type: 'reply'});
                    }
                });
            }
        });

        filteredComments.sort((a, b) => {
            return sortType === 'newest' ? b.commentId - a.commentId : a.commentId - b.commentId;
        });

        let html = '<div class="comments-container">';
        html += `<p>Es wurden ${totalComments} Kommentare durchsucht und
                   ${filteredComments.length} Kommentare mit '${searchTerm}' gefunden.</p>`;

        filteredComments.forEach(item => {
            const dealId = getDealId();
            const url = `https://www.mydealz.de/${dealId}#${item.type}-${item.commentId}`;
            html += `
                <div class="${item.type}"
                     style="padding:10px;margin-bottom:10px;background-color:white;
                            border-radius:5px;box-shadow:0 2px 4px rgba(0,0,0,0.1);">
                    <a href="${url}" target="_blank">
                        🔗 ${item.createdAt} ${item.user.username}
                    </a>:
                    ${highlightSearchTerm(
                        outputType === 'compact' ?
                            cleanHTML(item.preparedHtmlContent) :
                            item.preparedHtmlContent,
                        searchTerm
                    )}
                </div>`;
        });

        html += '</div>';
        newWindow.document.getElementById('results').innerHTML = html;
        newWindow.document.getElementById('progressBar').style.display = 'none';
    }

    function searchComments(forceReload = false) {
        const searchTerm = newWindow.document.getElementById('searchTerm').value;
        if (!searchTerm) {
            alert("Kein Suchbegriff eingegeben.");
            return;
        }

        newWindow.document.getElementById('results').innerHTML = 'Suche läuft...';
        newWindow.document.getElementById('progressBar').style.display = 'block';

        fetchDataAndReplies(forceReload)
            .then(allData => {
                processComments(allData, searchTerm);
            })
            .catch(error => {
                console.error('Error:', error);
                newWindow.document.getElementById('results').innerHTML =
                    'Fehler bei der Suche: ' + error.message;
            });
    }

    function reloadFromServer() {
        const dealId = getDealId();
        localStorage.removeItem('dealComments_' + dealId);
        localStorage.removeItem('dealComments_' + dealId + '_timestamp');
        searchComments(true);
    }

    function attachEventListeners() {
        newWindow.document.querySelectorAll('input[name="outputType"], input[name="sortType"]')
            .forEach(radio => {
                radio.addEventListener('change', () => {
                    searchComments();
                });
            });
    }

    function handleSearch(searchInput) {
        const searchTerm = searchInput.value.trim();
        if (!searchTerm || searchTerm === searchInput.placeholder) return;

        const title = document.title.replace(" | mydealz", "");
        newWindow = window.open('', '_blank');

        newWindow.document.write(`
            <html>
                <head>
                    <title>Kommentar-Suche</title>
                    <style>
                        body { margin: 0; padding: 0; background-color: #f9f9f9; }
                        #header {
                            background-color: #005293;
                            height: 56px;
                            display: flex;
                            align-items: center;
                            width: 100%;
                            color: white;
                        }
                        #header img { height: 40px; margin-left: 20px; }
                        #header h2 {
                            margin-left: auto;
                            margin-right: auto;
                            font-size: x-large;
                        }
                        #results { margin-top: 20px; padding: 20px; }
                        h2, input, button, label {
                            font-family: sans-serif;
                            font-size: 14px;
                        }
                        input[type=text] { border-radius: 5px; border-width: 1px; }
                        button:hover { background-color: #45a049; }
                        #progress {
                            text-align: center;
                            margin-top: 15px;
                            font-size: .9em;
                            color: #555;
                        }
                        #progressBarFill:hover { opacity: .8; }
                    </style>
                </head>
                <body>
                    <div id="header">
                        <img src="https://www.mydealz.de/assets/img/logo/default-light_d4b86.svg"
                             alt="mydealz logo">
                        <h2>Kommentarvolltextsuche</h2>
                    </div>
                    ${createSearchForm(title, searchTerm)}
                </body>
            </html>
        `);

        newWindow.document.close();
        newWindow.searchComments = searchComments;
        newWindow.reloadFromServer = reloadFromServer;
        attachEventListeners();
        newWindow.searchComments();
    }

    function createSearchForm(title, searchTerm) {
        return `
            <div style="padding:20px;text-align:center;">
                <form id="searchForm" onsubmit="searchComments(); return false;"
                      style="display:flex;justify-content:center;align-items:center;gap:10px;">
                    <input type="text" id="searchTerm" placeholder="Suchbegriff eingeben"
                           value="${searchTerm}"
                           style="width:50%;padding:10px;border-radius:5px;border:1px solid #ccc;">
                    <button type="submit"
                            style="background-color:#4CAF50;color:white;padding:10px 20px;
                                   border:none;border-radius:5px;font-size:16px;cursor:pointer;
                                   box-shadow:0 2px 4px rgba(0,0,0,0.2);display:flex;
                                   align-items:center;">
                        <span style="margin-right:8px;">▶</span>Suchen
                    </button>
                </form>
                <div id="options" style="text-align:left;margin-top:10px;margin-left:25%;
                                      font-size:14px;">
                    <div>
                        Darstellung
                        <input type="radio" id="compact" name="outputType" value="compact" checked>
                        <label for="compact"> kompakt</label>
                        <input type="radio" id="detailed" name="outputType" value="detailed"
                               style="margin-left:10px;">
                        <label for="detailed"> ausführlich</label>
                        <span style="margin-left:20px;">
                            Sortierung
                            <input type="radio" id="sortNewest" name="sortType" value="newest"
                                   checked>
                            <label for="sortNewest"> neueste zuerst</label>
                            <input type="radio" id="sortOldest" name="sortType" value="oldest"
                                   style="margin-left:10px;">
                            <label for="sortOldest"> älteste zuerst</label>
                        </span>
                    </div>
                    <div id="newCommentsStatus" style="margin-top:10px;"></div>
                </div>
            </div>
            <div id="results" style="width:90%;margin:20px auto;background-color:#f9f9f9;
                                   padding:20px;border-radius:5px;"></div>
            <div id="progress" style="text-align:center;margin-top:20px;font-size:14px;"></div>
            <div id="progressBar" style="width:75%;margin:20px auto;background-color:#e0e0e0;
                                       height:20px;border-radius:10px;overflow:hidden;">
                <div id="progressBarFill" style="width:0%;height:100%;background-color:#4CAF50;
                                                transition:width 0.3s;"></div>
            </div>
        `;
    }

    // Initialisierung
    const observer = new MutationObserver((mutations, obs) => {
        if (document.querySelector('.comment-search-container')) return;

        const sortLabel = document.querySelector('.size--all-m.size--fromW3-l.overflow--wrap-off');
        if (sortLabel && sortLabel.textContent.includes('sortiert nach')) {
            injectSearchBox(sortLabel);
            obs.disconnect();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    function injectSearchBox(targetElement) {
        const searchContainer = document.createElement('div');
        searchContainer.className = 'comment-search-container';
        searchContainer.style.cssText = `
            display: inline-flex;
            align-items: center;
            margin-left: 15px;
            margin-right: 15px;
            flex: 0 1 auto;
        `;

        const searchInput = document.createElement('input');
        searchInput.type = 'text';
        searchInput.placeholder = 'In allen Kommentaren suchen';
        searchInput.style.cssText = `
            width: 240px;
            padding: 4px 8px;
            border: 1px solid #e0e0e0;
            border-radius: 4px;
            font-size: 14px;
            height: 28px;
            color: #666;
            background-color: white;
        `;

        const searchButton = document.createElement('button');
        searchButton.textContent = 'Suchen';
        searchButton.style.cssText = `
            margin-left: 8px;
            padding: 4px 12px;
            border: none;
            border-radius: 4px;
            background-color: #4CAF50;
            color: white;
            cursor: pointer;
        `;

        searchInput.addEventListener('focus', () => {
            if (searchInput.value === searchInput.placeholder) {
                searchInput.value = '';
                searchInput.style.color = '#000';
            }
        });

        searchInput.addEventListener('blur', () => {
            if (!searchInput.value.trim()) {
                searchInput.value = searchInput.placeholder;
                searchInput.style.color = '#666';
            }
        });

        searchButton.addEventListener('click', () => handleSearch(searchInput));
        searchInput.addEventListener('keydown', e => {
            if (e.key === 'Enter') handleSearch(searchInput);
        });

        searchContainer.appendChild(searchInput);
        searchContainer.appendChild(searchButton);

        const bellIcon = targetElement.parentNode.querySelector('button[title="Folgen"]');
        if (bellIcon) {
            bellIcon.parentNode.insertBefore(searchContainer, bellIcon);
        } else {
            targetElement.parentNode.appendChild(searchContainer);
        }
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址