Greasy Fork

Greasy Fork is available in English.

DMM Top Filter Buttons

Adds quality + ⚡instantRD filter buttons that remember activation and reapply automatically when input changes

当前为 2025-08-23 提交的版本,查看 最新版本

// ==UserScript==
// @name         DMM Top Filter Buttons
// @namespace    http://tampermonkey.net/
// @version      1.36
// @description  Adds quality + ⚡instantRD filter buttons that remember activation and reapply automatically when input changes
// @author       Waseem
// @match        https://debridmediamanager.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    let instantRDActive = false; // persistent state

    function setReactValue(element, value) {
        const nativeSetter = Object.getOwnPropertyDescriptor(
            window.HTMLInputElement.prototype, 'value'
        ).set;
        nativeSetter.call(element, value);
        element.dispatchEvent(new Event('input', { bubbles: true }));
    }

    const buttons = [
        { text: '⚡instantRD', value: '__bgonly__', class: 'dmm-bgonly' },
        { text: '4K',           value: '2160p|4k',               class: 'dmm-4k' },
        { text: '1080p',        value: '1080p',                  class: 'dmm-1080p' },
        { text: '720p',         value: '720p',                   class: 'dmm-720p' },
        { text: 'Dolby Vision', value: 'dovi|dv|dolby|vision',   class: 'dmm-dolbyvision' },
        { text: 'HDR',          value: 'hdr',                    class: 'dmm-hdr' },
        { text: 'Remux',        value: 'remux',                  class: 'dmm-remux' }
    ];

    const qualityValues = ['2160p|4k', '1080p', '720p'];

    function escapeRegex(str) {
        return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }

    function applyInstantRDFilter() {
        const grid = document.querySelector(
            '#__next > div > div.mx-1.my-1.grid.grid-cols-1.gap-2.overflow-x-auto.sm\\:grid-cols-2.md\\:grid-cols-3.lg\\:grid-cols-4.xl\\:grid-cols-6'
        );
        if (!grid) return;

        const cards = grid.querySelectorAll('div.border-2');
        cards.forEach(card => {
            const hasBg = card.classList.contains('bg-gray-800');
            card.style.display = instantRDActive ? (hasBg ? '' : 'none') : '';
        });

        const instantButton = document.querySelector('span.dmm-bgonly');
        if (instantButton) {
            if (instantRDActive) {
                instantButton.classList.add('active', 'bg-green-800');
            } else {
                instantButton.classList.remove('active', 'bg-green-800');
            }
        }
    }

    function addFilterButtons() {
        const container = document.querySelector(
            '#__next > div > div.mb-2.flex.items-center.gap-2.overflow-x-auto.p-2 > div'
        );
        const input = document.querySelector('#query');
        if (!container || !input) return;

        let lastInserted = null;

        buttons.forEach(btn => {
            if (container.querySelector(`span.${btn.class}`)) return;

            const span = document.createElement('span');
            span.textContent = btn.text;
            span.className =
                `${btn.class} cursor-pointer whitespace-nowrap rounded border border-blue-500 bg-blue-900/30 px-2 py-0.5 text-xs text-blue-100 transition-colors hover:bg-blue-800/50`;

            if (lastInserted) {
                container.insertBefore(span, lastInserted.nextSibling);
            } else {
                container.insertBefore(span, container.firstChild);
            }
            lastInserted = span;

            span.addEventListener('click', () => {
                if (btn.value === '__bgonly__') {
                    instantRDActive = !instantRDActive;
                    applyInstantRDFilter();
                    return;
                }

                // Quality buttons
                if (qualityValues.includes(btn.value)) {
                    let current = input.value.trim();
                    const clickedQuality = btn.value;

                    const escaped = escapeRegex(clickedQuality);
                    const regex = new RegExp(`(^|\\||\\s)${escaped}($|\\||\\s)`);
                    if (current.match(regex)) {
                        const parts = current.split(' ');
                        const filteredParts = parts.map(part => {
                            const qualityRegex = new RegExp(`(^|\\|)${escaped}($|\\|)`);
                            let cleaned = part.replace(qualityRegex, (match, before, after) => {
                                if (before === '|' && after === '|') return '|';
                                return '';
                            });
                            cleaned = cleaned.replace(/\|{2,}/g, '|').replace(/^\|+|\|+$/g, '');
                            return cleaned;
                        }).filter(part => part.length > 0);

                        current = filteredParts.join(' ').trim();
                        setReactValue(input, current);
                        applyInstantRDFilter();
                        return;
                    }

                    let lastFound = null;
                    qualityValues.forEach(q => {
                        const idx = current.lastIndexOf(q);
                        if (idx !== -1) lastFound = { value: q, index: idx };
                    });

                    if (lastFound) {
                        const before = current.slice(0, lastFound.index + lastFound.value.length);
                        const after = current.slice(lastFound.index + lastFound.value.length);
                        current = before + '|' + clickedQuality + after;
                    } else {
                        current = current ? current + ' ' + clickedQuality : clickedQuality;
                    }

                    current = current.replace(/\|{2,}/g, '|').replace(/^\|+|\|+$/g, '');
                    current = current.replace(/\s+/g, ' ').trim();
                    setReactValue(input, current);
                    applyInstantRDFilter();
                } else {
                    let current = input.value.trim();
                    if (current.includes(btn.value)) {
                        const regex = new RegExp(`\\s*\\b${escapeRegex(btn.value)}\\b\\s*`, 'g');
                        current = current.replace(regex, ' ').trim();
                        current = current.replace(/\s+/g, ' ');
                        setReactValue(input, current);
                        applyInstantRDFilter();
                    } else {
                        current = current ? current + ' ' + btn.value : btn.value;
                        setReactValue(input, current);
                        applyInstantRDFilter();
                    }
                }
            });
        });

        // Apply instantRD filter if it was already active
        applyInstantRDFilter();
    }

    function setupEscapeClear() {
        const input = document.querySelector('#query');
        if (!input) return;

        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') setReactValue(input, '');
        });

        // Reapply instantRD filter whenever input changes
        input.addEventListener('input', () => {
            applyInstantRDFilter();
        });
    }

    const observer = new MutationObserver(() => {
        addFilterButtons();
        setupEscapeClear();
    });
    observer.observe(document.body, { childList: true, subtree: true });
})();