您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Adds filter buttons to DMM with robust InstantRD and Cached filtering. Persistent active background for all buttons. Buttons remain first children in correct order.
// ==UserScript== // @name DMM Top Filter Buttons (Stable + Cached) // @namespace http://tampermonkey.net/ // @version 1.61 // @description Adds filter buttons to DMM with robust InstantRD and Cached filtering. Persistent active background for all buttons. Buttons remain first children in correct order. // @author Waseem // @match https://debridmediamanager.com/* // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; function setReactValue(element, value) { const nativeSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, 'value' ).set; nativeSetter.call(element, value); element.dispatchEvent(new Event('input', { bubbles: true })); } function normalizeText(txt) { return (txt || '').toLowerCase().replace(/\s+/g, ' ').trim(); } function escapeRegex(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } let instantRDActive = false; let cachedActive = false; const buttons = [ { text: '⚡instantRD', value: '__bgonly__', class: 'dmm-bgonly', instantRD: true, color: '#166534' }, { text: 'Cached', class: 'dmm-cached', cached: true, color: '#f59e0b' }, // amber { text: '4K', value: '2160p|4k', class: 'dmm-4k', color: '#1e40af' }, { text: '1080p', value: '1080p', class: 'dmm-1080p', color: '#1e40af' }, { text: '720p', value: '720p', class: 'dmm-720p', color: '#1e40af' }, { text: 'Dolby Vision', value: 'dovi|dv|dolby|vision', class: 'dmm-dolbyvision', color: '#1e40af' }, { text: 'HDR', value: 'hdr', class: 'dmm-hdr', color: '#1e40af' }, { text: 'Remux', value: 'remux', class: 'dmm-remux', color: '#1e40af' } ]; const qualityValues = ['2160p|4k', '1080p', '720p']; function getGrid() { return 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' ); } function cardHasInstantRD(card) { const btns = card.querySelectorAll('button'); for (const b of btns) { const t = normalizeText(b.textContent); if (t.includes('instant rd')) return true; if (t.includes('rd (100%)')) return true; } return false; } function cardHasCached(card) { const btns = card.querySelectorAll('button'); for (const b of btns) { const t = normalizeText(b.textContent); if (t.includes('rd (100%)')) return true; } return false; } function applyFilters() { const grid = getGrid(); if (!grid) return; const cards = grid.querySelectorAll(':scope > div'); cards.forEach(card => { const instantRDKeep = !instantRDActive || cardHasInstantRD(card); const cachedKeep = !cachedActive || cardHasCached(card); card.style.display = (instantRDKeep && cachedKeep) ? '' : 'none'; }); updateButtonActiveStates(); } function showAllCards() { const grid = getGrid(); if (!grid) return; const cards = grid.querySelectorAll(':scope > div'); cards.forEach(card => (card.style.display = '')); } function updateButtonActiveStates() { const input = document.querySelector('#query'); if (!input) return; const val = input.value.trim(); buttons.forEach(btn => { const span = document.querySelector(`span.${btn.class}`); if (!span) return; let active = false; if (btn.instantRD) active = instantRDActive; else if (btn.cached) active = cachedActive; else if (val.includes(btn.value)) active = true; if (active) { span.classList.add('active'); span.style.setProperty('background-color', btn.color, 'important'); } else { span.classList.remove('active'); span.style.removeProperty('background-color'); } }); } 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; const reference = container.firstChild; 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`; span.dataset.color = btn.color; if (reference) container.insertBefore(span, reference); else container.appendChild(span); span.addEventListener('click', () => { if (btn.instantRD) { instantRDActive = !instantRDActive; if (instantRDActive) applyFilters(); else { showAllCards(); applyFilters(); } updateButtonActiveStates(); return; } if (btn.cached) { cachedActive = !cachedActive; applyFilters(); return; } let current = input.value.trim(); if (qualityValues.includes(btn.value)) { 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(Boolean); current = filteredParts.join(' ').trim(); setReactValue(input, current); applyFilters(); updateButtonActiveStates(); 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); applyFilters(); updateButtonActiveStates(); return; } 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); } else { current = current ? current + ' ' + btn.value : btn.value; setReactValue(input, current); } applyFilters(); updateButtonActiveStates(); }); }); updateButtonActiveStates(); applyFilters(); } function setupGlobalListeners() { const input = document.querySelector('#query'); if (!input) return; document.addEventListener('keydown', (e) => { if (e.key === 'Escape') setReactValue(input, ''); }); input.addEventListener('input', () => { applyFilters(); }); } const observer = new MutationObserver(() => { addFilterButtons(); setupGlobalListeners(); applyFilters(); }); observer.observe(document.body, { childList: true, subtree: true }); addFilterButtons(); setupGlobalListeners(); applyFilters(); })();