mydealz Script

mydealz Deal-Management: Deals filtern und ausblenden, alle Einstellungen per UI verwalten.

目前為 2024-12-30 提交的版本,檢視 最新版本

// ==UserScript==
// @name         mydealz Script
// @namespace    http://tampermonkey.net/
// @version      1.7.0
// @description  mydealz Deal-Management: Deals filtern und ausblenden, alle Einstellungen per UI verwalten.
// @author       Moritz Baumeister (https://www.mydealz.de/profile/BobBaumeister), Flo (https://www.mydealz.de/profile/Basics0119)
// @license      MIT
// @match        https://www.mydealz.de/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=mydealz.de
// @grant        none
// ==/UserScript==

// Versions-Änderungen:
// 1.6.3: @description angepasst
// 1.6.2: Geändert – Grau hinterlegter Settings-Button im mydealz Stil; Behoben – Animation des Settings-Buttons entfernt
// 1.6.1: Deal Ausblenden Button auch auf Geräten mit Touch verfügbar.
// 1.6.0: Hinzugefügt – Schaltfläche „Einstellungen“ entspricht dem Stil von mydealz; Geändert – Verbesserte Schaltflächenplatzierung und Container;
//        Behoben – Schaltflächenanimationen und -zustände; Hinzugefügt – Bessere UI/UX für Einstellungssteuerungen
// 1.5.2: Fix – Versteckte Angebote bleiben jetzt nach dem Öffnen des Einstellungsdialogs verborgen; Update – Verbesserte Händler-ID-Erkennung; Fix – Persistentes Ausblenden von Angeboten; Hinzufügen – Bessere Fehlerbehandlung für JSON-Parsing; Fix – Statusverwaltung der Angebotssichtbarkeit
// 1.5.1: Behoben: Händler-ID-basiertes Ausblenden von Deals funktioniert jetzt korrekt. Hinzugefügt: Unterstützung für Gutscheinartikel beim Ausblenden von Deals. Hinzugefügt: Verbesserte HTML-Entitätsbehandlung im Datenverlauf. Behoben: Zuverlässigkeit der Händlerlink-Erkennung. Behoben: Probleme mit maskiertem JSON im Datenverlaufsattribut.
// 1.4.0: Überarbeitete Benutzeroberfläche für die Exclude-Einstellungen mit sinnvoller Button-Anordnung, verbesserte Funktionalität für Backup und Wiederherstellen, Schließen des Einstellungsfensters ohne automatisch zu schließen und verbesserte Darstellung des Dateiuploads für das Wiederherstellen.
// 1.3.0: Hinzugefügt: Backup- und Restore-Funktionen für excludeWords und excludeMerchantIDs. UI wird nach Wiederherstellung automatisch aktualisiert. Neue Buttons zum Erstellen und Wiederherstellen von Backups im Einstellungsfenster.
// 1.2.0: excludeWords und excludeMerchantIDs in localStorage ausgelagert, um Daten bei Updates zu erhalten. Hinzugefügt: Benutzeroberfläche (UI) zur Bearbeitung von excludeWords und excludeMerchantIDs direkt auf der Website. Hinzugefügt: Backup und Restore der Daten.
// 1.1.0: Tooltip für den Ausblenden-Button hinzugefügt, Fehlerbehebung bei Anzeige von Deals.
// 1.0.1: Fehlerbehebung bei Preisumrechnung, Performance-Optimierung bei Deal-Hervorhebung.

// Einbinden von Font Awesome für Icons
const fontAwesomeLink = document.createElement('link');
fontAwesomeLink.rel = 'stylesheet';
fontAwesomeLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css';
document.head.appendChild(fontAwesomeLink);

(function() {
    'use strict';

    // --- 1. Konfiguration ---
    const EXCLUDE_WORDS_KEY = 'excludeWords';
    const EXCLUDE_MERCHANTS_KEY = 'excludeMerchantIDs';
    const HIDDEN_DEALS_KEY = 'hiddenDeals';

    // Lade gespeicherte Werte oder setze Standardwerte
    let excludeWords = JSON.parse(localStorage.getItem(EXCLUDE_WORDS_KEY)) || [];
    let excludeMerchantIDs = JSON.parse(localStorage.getItem(EXCLUDE_MERCHANTS_KEY)) || [];
    let hiddenDeals = JSON.parse(localStorage.getItem(HIDDEN_DEALS_KEY)) || [];

    // Funktion zum Speichern der ausgeblendeten Deals
    function saveHiddenDeals() {
        localStorage.setItem(HIDDEN_DEALS_KEY, JSON.stringify(hiddenDeals));
    }

    // Speichern der `excludeWords` und `excludeMerchantIDs`
    function saveExcludeWords(words) {
        localStorage.setItem('excludeWords', JSON.stringify(words));
    }
    function loadExcludeWords() {
        return JSON.parse(localStorage.getItem('excludeWords')) || [];
    }

    function saveExcludeMerchants(merchants) {
        localStorage.setItem(EXCLUDE_MERCHANTS_KEY, JSON.stringify(merchants));
    }

    // Fügt Event Listener hinzu, um Auto-Speichern zu ermöglichen
    function addAutoSaveListeners() {
        // Event Listener für Eingabefelder
        const excludeWordsInput = document.getElementById('excludeWordsInput');
        excludeWordsInput.addEventListener('input', () => {
            const newWords = excludeWordsInput.value.split('\n').map(w => w.trim()).filter(Boolean);
            saveExcludeWords(newWords);
            excludeWords = newWords;
            processArticles();
        });

        const excludeMerchantIDsInput = document.getElementById('excludeMerchantIDsInput');
        excludeMerchantIDsInput.addEventListener('input', () => {
            const newMerchantIDs = excludeMerchantIDsInput.value.split('\n').map(id => id.trim()).filter(Boolean);
            saveExcludeMerchants(newMerchantIDs);
            excludeMerchantIDs = newMerchantIDs;
            processArticles();
        });
    }

    // Remove highlighting-related event listeners

    // --- 2. Hilfsfunktionen ---
    function shouldExcludeArticle(article) {
        const titleElement = article.querySelector('.thread-title');
        const merchantLink = article.querySelector('a[href*="merchant-id="]');

        if (titleElement && excludeWords.some(word => titleElement.textContent.toLowerCase().includes(word.toLowerCase()))) {
            return true;
        }

        if (merchantLink) {
            const merchantIDMatch = merchantLink.getAttribute('href').match(/merchant-id=(\d+)/);
            if (merchantIDMatch) {
                const merchantID = merchantIDMatch[1];
                if (excludeMerchantIDs.includes(merchantID)) {
                    return true;
                }
            }
        }

        return false;
    }

    function hideDeal(deal) {
        deal.style.display = 'none';
    }

    // UI-Button zum Öffnen der Einstellungen wird nur einmal erstellt
    const settingsButton = document.createElement('button');
    settingsButton.innerHTML = '⚙️';
    settingsButton.style.padding = '10px';
    settingsButton.style.background = 'transparent'; // Hintergrund transparent machen
    settingsButton.style.color = 'white';
    settingsButton.style.border = 'none';
    settingsButton.style.borderRadius = '5px';
    settingsButton.style.cursor = 'pointer';

    // UI-Button zum Verbergen der Deals wird nur einmal erstellt
    const hideButton = document.createElement('button');
    hideButton.innerHTML = '❌';
    hideButton.style.marginLeft = '10px';
    hideButton.title = 'Deal verbergen'; // Tooltip hinzufügen
    hideButton.style.padding = '10px';
    hideButton.style.background = 'transparent'; // Hintergrund transparent machen
    hideButton.style.color = 'white';
    hideButton.style.border = 'none';
    hideButton.style.borderRadius = '5px';
    hideButton.style.cursor = 'pointer';

    // Funktion, um das Verbergen der Deals zu ermöglichen
    hideButton.addEventListener('click', function(event) {
        const deal = event.target.closest('article');
        if (deal) {
            const dealId = deal.getAttribute('id');
            hiddenDeals.push(dealId);
            saveHiddenDeals();
            hideDeal(deal);
        }
    });

    function addSettingsButton() {
        const deals = document.querySelectorAll('article:not([data-settings-added])');

        deals.forEach(deal => {
            if (deal.hasAttribute('data-settings-added')) return;

            const settingsBtn = document.createElement('button');
            settingsBtn.innerHTML = '⚙️';
            settingsBtn.className = 'vote-button overflow--visible'; // Removed animation classes
            settingsBtn.title = 'Einstellungen';
            settingsBtn.style.cssText = `
                border: none;
                cursor: pointer;
                height: 32px;
                width: 32px;
                display: flex;
                align-items: center;
                justify-content: center;
                font-size: 16px;
                padding: 0;
            `;

            const settingsContainer = document.createElement('div');
            settingsContainer.className = 'vote-box border color--border-TranslucentPrimary bRad--a space--h-1 bRad--circle fadeOuterEdge--r';
            settingsContainer.style.cssText = `
                display: flex;
                align-items: center;
                margin-left: 8px;
                height: 32px;
            `;

            settingsBtn.addEventListener('click', createSettingsUI);
            settingsContainer.appendChild(settingsBtn);

            const voteBox = deal.querySelector('.vote-box');
            if (voteBox) {
                voteBox.parentNode.insertBefore(settingsContainer, voteBox.nextSibling);
                deal.setAttribute('data-settings-added', 'true');
            }
        });
    }

    function addHideButtons() {
        const deals = document.querySelectorAll('article:not([data-button-added])');

        deals.forEach(deal => {
            if (deal.hasAttribute('data-button-added')) return;

            const voteTemp = deal.querySelector('.cept-vote-temp');
            if (!voteTemp) return;

            const hideButton = document.createElement('button');
            hideButton.innerHTML = '❌';
            hideButton.className = 'vote-button overflow--visible vote-button--with-animation';
            hideButton.title = 'Deal verbergen';
            hideButton.style.cssText = `
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                display: none;
                z-index: 100;
                background: transparent;
                border: none;
                cursor: pointer;
                padding: 5px;
            `;

            voteTemp.style.position = 'relative';

            // Desktop hover
            if (!('ontouchstart' in window)) {
                voteTemp.addEventListener('mouseenter', () => {
                    hideButton.style.display = 'block';
                });

                voteTemp.addEventListener('mouseleave', () => {
                    hideButton.style.display = 'none';
                });
            }

            // Mobile touch
            let isButtonVisible = false;
            voteTemp.addEventListener('touchstart', (e) => {
                if (!isButtonVisible) {
                    e.preventDefault();
                    hideButton.style.display = 'block';
                    isButtonVisible = true;
                } else if (e.target === voteTemp) {
                    hideButton.style.display = 'none';
                    isButtonVisible = false;
                }
            }, {passive: false});

            hideButton.addEventListener('click', function(event) {
                event.stopPropagation();
                const deal = event.target.closest('article');
                if (deal) {
                    const dealId = deal.getAttribute('id');
                    hiddenDeals.push(dealId);
                    saveHiddenDeals();
                    hideDeal(deal);
                }
            });

            voteTemp.appendChild(hideButton);
            deal.setAttribute('data-button-added', 'true');
        });
    }

    // Update observer setup
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.addedNodes.length) {
                addHideButtons();
                addSettingsButton();
            }
        });
    });

    // Start observing with the correct configuration
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Initial setup
    document.addEventListener('DOMContentLoaded', () => {
        addHideButtons();
        addSettingsButton();
    });

    // HTML Decoder Funktion hinzufügen
    function decodeHtml(html) {
        const txt = document.createElement('textarea');
        txt.innerHTML = html;
        return txt.value;
    }

    function processArticles() {
        const deals = document.querySelectorAll('article.thread--deal, article.thread--voucher');
        deals.forEach(deal => {
            let shouldHide = false;
            const dealId = deal.getAttribute('id');

            // Check if deal was manually hidden
            if (hiddenDeals.includes(dealId)) {
                shouldHide = true;
            }

            // Check data-history
            if (!shouldHide) {
                const dataHistory = deal.getAttribute('data-history');
                if (dataHistory) {
                    const decodedHistory = decodeHtml(dataHistory);
                    try {
                        const historyData = JSON.parse(decodedHistory);
                        if (historyData.endpoint && historyData.endpoint.includes(`merchant-id=${excludeMerchantIDs}`)) {
                            shouldHide = true;
                        }
                    } catch (e) {
                        console.error('JSON parse error:', e);
                    }
                }
            }

            // Check merchant links
            if (!shouldHide) {
                const merchantLinks = deal.querySelectorAll('a[data-t="merchantLink"], a[href*="merchant-id"]');
                merchantLinks.forEach(link => {
                    if (link.href && link.href.includes(`merchant-id=${excludeMerchantIDs}`)) {
                        shouldHide = true;
                    }
                });
            }

            // Apply visibility
            if (shouldHide || shouldExcludeArticle(deal)) {
                hideDeal(deal);
            } else {
                deal.style.display = 'block';
                deal.style.opacity = '1';
            }
        });

        addHideButtons();
    }

    // --- 3. Backup- und Restore-Funktionen ---
    function backupData() {
        const backup = {
            excludeWords: excludeWords,
            excludeMerchantIDs: excludeMerchantIDs
        };

        const blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'mydealz_backup.json';
        a.click();
        URL.revokeObjectURL(url);
    }

    // Restore-Funktion
    function restoreData(event) {
        const file = event.target.files[0];
        if (file && file.type === 'application/json') {
            const reader = new FileReader();
            reader.onload = function(e) {
                const restoredData = JSON.parse(e.target.result);
                if (restoredData.excludeWords && restoredData.excludeMerchantIDs) {
                    // Wiederhergestellte Daten in den localStorage speichern
                    saveExcludeWords(restoredData.excludeWords);
                    saveExcludeMerchants(restoredData.excludeMerchantIDs);

                    // Arrays aktualisieren
                    excludeWords.length = 0;
                    excludeWords.push(...restoredData.excludeWords);

                    excludeMerchantIDs.length = 0;
                    excludeMerchantIDs.push(...restoredData.excludeMerchantIDs);

                    // UI-Felder mit neuen Daten füllen
                    document.getElementById('excludeWordsInput').value = restoredData.excludeWords.join('\n');
                    document.getElementById('excludeMerchantIDsInput').value = restoredData.excludeMerchantIDs.join('\n');

                    // Deals erneut prüfen
                    processArticles();
                } else {
                    alert('Die Backup-Datei ist nicht im richtigen Format.');
                }
            };
            reader.readAsText(file);
        } else {
            alert('Bitte wählen Sie eine gültige JSON-Datei aus.');
        }
    }

    // --- 4. Benutzeroberfläche (UI) ---
    let settingsDiv = null;
    let isSettingsOpen = false;

    function createSettingsUI() {
        if (isSettingsOpen) return;
        isSettingsOpen = true;

        const currentExcludeWords = JSON.parse(localStorage.getItem(EXCLUDE_WORDS_KEY)) || [];
        const currentExcludeMerchantIDs = JSON.parse(localStorage.getItem(EXCLUDE_MERCHANTS_KEY)) || [];

        settingsDiv = document.createElement('div');
        settingsDiv.style.position = 'fixed';
        settingsDiv.style.bottom = '10px';
        settingsDiv.style.right = '10px';
        settingsDiv.style.padding = '15px';
        settingsDiv.style.background = '#f9f9f9';
        settingsDiv.style.border = '1px solid #ccc';
        settingsDiv.style.borderRadius = '5px';
        settingsDiv.style.zIndex = '1000';
        settingsDiv.style.maxWidth = '300px';
        settingsDiv.style.overflow = 'auto';

        settingsDiv.innerHTML = `
        <h4 style="margin-bottom: 10px;">Einstellungen zum Ausblenden</h4>
        <div style="margin-bottom: 15px;">
            <label for="excludeWordsInput" style="font-weight: bold;">Schlagworte</label>
            <textarea id="excludeWordsInput" rows="2" style="width: 80%; margin-top: 5px; padding: 5px; border: 1px solid #ccc; border-radius: 3px; background: #f0f0f0;">${currentExcludeWords.join('\n')}</textarea>
        </div>
        <div style="margin-bottom: 15px;">
            <label for="excludeMerchantIDsInput" style="font-weight: bold;">Händler IDs</label>
            <textarea id="excludeMerchantIDsInput" rows="2" style="width: 100%; margin-top: 5px; padding: 5px; border: 1px solid #ccc; border-radius: 3px; background: #f0f0f0;">${currentExcludeMerchantIDs.join('\n')}</textarea>
        </div>
        <div style="text-align: right;">
            <button id="saveSettingsButton" style="padding: 8px 12px; background: none; border: none; cursor: pointer;" title="Speichern">
                <i class="fas fa-save"></i>
            </button>
            <button id="createBackupButton" style="padding: 8px 12px; background: none; border: none; cursor: pointer;" title="Backup erstellen">
                <i class="fas fa-file-export"></i>
            </button>
            <button id="restoreBackupButton" style="padding: 8px 12px; background: none; border: none; cursor: pointer;" title="Wiederherstellen">
                <i class="fas fa-file-import"></i>
            </button>
            <input type="file" id="restoreFileInput" style="display: none;" />
            <button id="closeSettingsButton" style="padding: 8px 12px; background: none; border: none; cursor: pointer;" title="Schließen">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `;


        document.body.appendChild(settingsDiv);

        // Speichern der Einstellungen
        document.getElementById('saveSettingsButton').addEventListener('click', () => {
            const newWords = document.getElementById('excludeWordsInput').value.split('\n').map(w => w.trim()).filter(Boolean);
            const newMerchantIDs = document.getElementById('excludeMerchantIDsInput').value.split('\n').map(id => id.trim()).filter(Boolean);

            // Auto-Speichern aktivieren
            addAutoSaveListeners();

            // Exclude-Listen speichern
            saveExcludeWords(newWords);
            saveExcludeMerchants(newMerchantIDs);

            // Konfiguration aktualisieren
            excludeWords = newWords;
            excludeMerchantIDs = newMerchantIDs;

            // Nach dem Speichern der Einstellungen die Deals erneut überprüfen
            processArticles();
        });

        // Backup/Restore Event Listeners
        document.getElementById('createBackupButton').addEventListener('click', backupData);

        document.getElementById('restoreBackupButton').addEventListener('click', () => {
            document.getElementById('restoreFileInput').click();
        });

        document.getElementById('restoreFileInput').addEventListener('change', restoreData);

        // Close Settings
        document.getElementById('closeSettingsButton').addEventListener('click', () => {
            isSettingsOpen = false;
            settingsDiv.remove();
        });
    }

    // Initial processing
    processArticles();

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

})();

QingJ © 2025

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