DMM Top Filter Buttons

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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