// ==UserScript== // @name HDEncode Filter Suite // @namespace https://hdencode.org/ // @version 1.5 // @description A Tampermonkey userscript that adds powerful filtering, searching and multi-page loading to HDEncode.org // @author mikeymuis // @homepage https://github.com/mikeymuis/hdencode-filter-suite // @supportURL https://github.com/mikeymuis/hdencode-filter-suite/issues // @updateURL https://raw.githubusercontent.com/mikeymuis/hdencode-filter-suite/main/hdencode-filter-suite.user.js // @downloadURL https://raw.githubusercontent.com/mikeymuis/hdencode-filter-suite/main/hdencode-filter-suite.user.js // @match *://hdencode.org/* // @match *://www.hdencode.org/* // @match *://hdencode.com/* // @match *://www.hdencode.com/* // @match *://hdencode.ro/* // @match *://www.hdencode.ro/* // @run-at document-end // @grant none // ==/UserScript== (function () { 'use strict'; // ─── Script constants ───────────────────────────────────────────────────── const SCRIPT_NAME = 'HDEncode Filter Suite'; const SCRIPT_VERSION = '1.5'; const SCRIPT_ID = 'hdencode-filter-suite'; // ─── Helpers: item data extraction ─────────────────────────────────────── function hasDV(item) { const span = item.querySelector('.imdb_r span'); return !!span && span.getAttribute('style')?.includes('dv.png'); } function hasHDR(item) { return item.querySelector('.buttonhdr') !== null; } function getRating(item) { const match = item.innerText.match(/Rating\s*:\s*(\d+\.\d+)\/10/i); return match ? parseFloat(match[1]) : 0; } function getSize(item) { const a = item.querySelector('h5 a'); const title = a?.innerText || a?.textContent || ''; const match = title.match(/–\s*(\d+(\.\d+)?)\s*GB/i); return match ? parseFloat(match[1]) : null; } function getGroup(item) { const a = item.querySelector('h5 a'); const title = a?.innerText || a?.textContent || ''; const clean = title.replace(/\s*–\s*[\d.]+\s*(GB|MB)\s*$/i, '').trim(); const parts = clean.split('-'); return parts.length > 1 ? parts.pop().trim() : ''; } function getResolution(item) { for (const span of item.querySelectorAll('.calidad3')) { if (span.innerText.match(/\d{3,4}p/i)) return span.innerText.trim(); } return ''; } function getCategory(item) { // Check .calidad4 links for tv-shows and tv-packs // Items without .calidad4 are movies const links = Array.from(item.querySelectorAll('.calidad4 a')); const hrefs = links.map(a => a.href || a.getAttribute('href') || ''); if (hrefs.some(h => h.includes('tv-packs'))) return 'tv-packs'; if (hrefs.some(h => h.includes('tv-shows'))) return 'tv-shows'; return 'movies'; } // ─── Release group dropdown ─────────────────────────────────────────────── function buildGroupDropdown(container) { const select = document.getElementById('f-group'); if (!select) return; const current = select.value; // Track count and first-seen index per casing variant, keyed by lowercase // so e.g. "ETHEL" and "Ethel" are treated as the same group. // The variant with the highest count wins; ties go to the first one seen. const groupMap = new Map(); // lowercase key → { variants: Map, firstSeen: number } let seen = 0; for (const item of container.querySelectorAll('.fit.item')) { if (item.style.display === 'none') continue; const g = getGroup(item); if (!g) continue; const key = g.toLowerCase(); if (!groupMap.has(key)) { groupMap.set(key, { variants: new Map(), firstSeen: seen++ }); } const entry = groupMap.get(key); entry.variants.set(g, (entry.variants.get(g) || 0) + 1); } // For each key, pick the variant with the highest count (first seen on tie) const groups = Array.from(groupMap.entries()).map(([key, entry]) => { let bestName = null; let bestCount = -1; for (const [name, count] of entry.variants) { if (count > bestCount) { bestName = name; bestCount = count; } } return { key, name: bestName }; }); groups.sort((a, b) => a.key.localeCompare(b.key)); select.innerHTML = ''; for (const { key, name } of groups) { const opt = document.createElement('option'); opt.value = key; opt.textContent = name; select.appendChild(opt); } if (current && Array.from(select.options).some(o => o.value === current)) { select.value = current; } } // ─── Filter logic ───────────────────────────────────────────────────────── function getFilterValues() { return { onlyDV: document.getElementById('f-dv')?.checked || false, onlyHDR: document.getElementById('f-hdr')?.checked || false, res: document.getElementById('f-res')?.value || '', category: document.getElementById('f-category')?.value || '', minRating: parseFloat(document.getElementById('f-rating')?.value) || 0, minSize: parseFloat(document.getElementById('f-minsize')?.value) || 0, maxSize: parseFloat(document.getElementById('f-maxsize')?.value) || Infinity, group: (document.getElementById('f-group')?.value || '').toLowerCase().trim(), search: (document.getElementById('f-search')?.value || '').toLowerCase().trim(), }; } function itemMatchesFilters(item, f) { if (f.onlyDV && !hasDV(item)) return false; if (f.onlyHDR && !hasHDR(item)) return false; if (f.res && getResolution(item) !== f.res) return false; if (f.category && getCategory(item) !== f.category) return false; if (getRating(item) < f.minRating) return false; const size = getSize(item); if (size !== null && (size < f.minSize || size > f.maxSize)) return false; if (f.group && getGroup(item).toLowerCase() !== f.group) return false; if (f.search && !item.innerText.toLowerCase().includes(f.search)) return false; return true; } function applyFilters(container) { const f = getFilterValues(); const items = Array.from(container.querySelectorAll('.fit.item')); // Pass 1: filter without group — determines which items are visible for the dropdown for (const item of items) { item.style.display = itemMatchesFilters(item, { ...f, group: '' }) ? '' : 'none'; } // Only rebuild the group dropdown when no group is selected — // otherwise the current selection disappears from the list. if (!f.group) buildGroupDropdown(container); let visible = 0; for (const item of items) { if (item.style.display === 'none') continue; if (f.group && getGroup(item).toLowerCase() !== f.group) { item.style.display = 'none'; } else { visible++; } } const counter = document.getElementById('f-counter'); if (!counter) return; if (visible === 0 && items.length > 0) { const hasActiveFilters = f.onlyDV || f.onlyHDR || f.res || f.category || f.minRating > 0 || f.minSize > 0 || f.maxSize < Infinity || f.group || f.search; counter.innerHTML = hasActiveFilters ? `No results — try adjusting your filters` : `Showing 0 / ${items.length} releases`; } else { counter.innerHTML = `Showing ${visible} / ${items.length} releases`; } for (const el of document.querySelectorAll(`#${SCRIPT_ID}-bar input, #${SCRIPT_ID}-bar select`)) { if (el.id === 'f-pagelimit') continue; // not a filter, don't highlight if (el.id.startsWith('fs-persist-')) continue; // settings checkboxes, don't highlight const active = el.type === 'checkbox' ? el.checked : el.value !== ''; el.style.borderColor = active ? '#00e5ff' : 'rgba(255,255,255,0.15)'; } saveFilters(); } // ─── Settings (persistence preferences) ────────────────────────────────── // These are the filter IDs the user can choose to persist or not. // The key is the element ID, the value is a human-readable label. const PERSISTABLE_FILTERS = { 'f-dv': 'Dolby Vision', 'f-hdr': 'HDR', 'f-res': 'Resolution', 'f-category': 'Content type', 'f-rating': 'Minimum rating', 'f-minsize': 'Min file size', 'f-maxsize': 'Max file size', 'f-group': 'Release group', 'f-search': 'Search text', 'f-pagelimit': 'Page limit', }; function loadSettings() { try { return JSON.parse(localStorage.getItem('hdencodeSettings') || '{}'); } catch (_) { return {}; } } function saveSettings(settings) { try { localStorage.setItem('hdencodeSettings', JSON.stringify(settings)); } catch (_) {} } function isFilterPersisted(id) { const settings = loadSettings(); // f-pagelimit defaults to false (not persisted) unless explicitly enabled if (id === 'f-pagelimit') return settings['persist_' + id] === true; // All other filters default to true unless explicitly disabled return settings['persist_' + id] !== false; } // ─── LocalStorage ───────────────────────────────────────────────────────── function saveFilters() { const data = {}; for (const el of document.querySelectorAll(`#${SCRIPT_ID}-bar input, #${SCRIPT_ID}-bar select`)) { if (el.id.startsWith('fs-persist-')) continue; // settings checkboxes, not filters if (!isFilterPersisted(el.id)) continue; // skip if user opted out data[el.id] = el.type === 'checkbox' ? el.checked : el.value; } try { localStorage.setItem('hdencodeFilters', JSON.stringify(data)); } catch (_) {} } function loadFilters() { try { const data = JSON.parse(localStorage.getItem('hdencodeFilters') || '{}'); for (const [id, val] of Object.entries(data)) { if (id.startsWith('fs-persist-')) continue; if (!isFilterPersisted(id)) continue; // skip if user opted out const el = document.getElementById(id); if (!el) continue; if (el.type === 'checkbox') el.checked = val; else el.value = val; } } catch (_) {} } function clearFilters(container) { for (const el of document.querySelectorAll(`#${SCRIPT_ID}-bar input, #${SCRIPT_ID}-bar select`)) { if (el.id.startsWith('fs-persist-')) continue; // never touch settings checkboxes else if (el.id === 'f-pagelimit' && !isFilterPersisted('f-pagelimit')) el.value = 'all'; else if (el.id === 'f-pagelimit') continue; // user chose to persist it, leave it alone else if (el.type === 'checkbox') el.checked = false; else el.value = ''; } try { localStorage.removeItem('hdencodeFilters'); } catch (_) {} applyFilters(container); } // ─── Quick links & NFO ──────────────────────────────────────────────────── const linkCache = new Map(); const nfoCache = new Map(); async function fetchDetailData(url) { // Returns { links, nfo } — both cached separately so either can be used independently. // We only fetch the detail page once; the NFO comes from the first GET, // the links come from the POST response. const linksAlready = linkCache.has(url); const nfoAlready = nfoCache.has(url); if (linksAlready && nfoAlready) { return { links: linkCache.get(url), nfo: nfoCache.get(url) }; } try { // Step 1: GET the detail page const getRes = await fetch(url, { credentials: 'same-origin' }); if (!getRes.ok) return { links: null, nfo: null }; const doc = new DOMParser().parseFromString(await getRes.text(), 'text/html'); // Extract NFO from the single
 in .entry-content
            const nfoText = doc.querySelector('.entry-content pre')?.innerText
                         || doc.querySelector('.entry-content pre')?.textContent
                         || null;
            nfoCache.set(url, nfoText);

            // Step 2: Find the content protector form
            const form = doc.querySelector('form[id^="content-protector-access-form"]');
            if (!form) {
                linkCache.set(url, []);
                return { links: [], nfo: nfoText };
            }

            const formData = new FormData();
            for (const input of form.querySelectorAll('input')) {
                if (input.name) formData.append(input.name, input.value);
            }

            // Step 3: POST the form to unlock the links
            const action = new URL(form.getAttribute('action'), url).href;
            const postRes = await fetch(action, {
                method: 'POST',
                credentials: 'same-origin',
                body: formData,
            });
            if (!postRes.ok) {
                linkCache.set(url, []);
                return { links: [], nfo: nfoText };
            }

            const unlockedDoc = new DOMParser().parseFromString(await postRes.text(), 'text/html');

            // Step 4: Extract links
            const HOST_NAMES = {
                'rg': 'Rapidgator', 'rapidgator': 'Rapidgator',
                'nf': 'Nitroflare', 'nitroflare': 'Nitroflare',
                'ddl': 'DDL', 'mega': 'Mega', '1fichier': '1Fichier',
                'ul': 'Uploadgig', 'uploadgig': 'Uploadgig',
                'katfile': 'Katfile', 'filefox': 'Filefox',
            };
            const links = [];
            for (const blockquote of unlockedDoc.querySelectorAll('.content-protector-access-form blockquote')) {
                const img = blockquote.previousElementSibling?.querySelector('img');
                const raw = (img?.alt || img?.src?.split('/').pop().replace(/\.(png|jpg|gif)$/i, '') || 'Link').toLowerCase().trim();
                const host = HOST_NAMES[raw] || raw.charAt(0).toUpperCase() + raw.slice(1);
                for (const a of blockquote.querySelectorAll('a')) {
                    links.push({ host, url: a.href });
                }
            }

            linkCache.set(url, links);
            return { links, nfo: nfoText };
        } catch (e) {
            console.error(`${SCRIPT_NAME}: failed to fetch detail data for`, url, e);
            return { links: null, nfo: null };
        }
    }

    // Keep fetchLinks as a thin wrapper so nothing else breaks
    async function fetchLinks(url) {
        const { links } = await fetchDetailData(url);
        return links;
    }

    // Shared helper: attach copy-to-clipboard handlers to all .fs-copy-btn inside a panel
    function attachCopyHandlers(panel) {
        panel.querySelectorAll('.fs-copy-btn').forEach(copyBtn => {
            copyBtn.addEventListener('click', async (e) => {
                e.stopPropagation();
                const original = copyBtn.textContent;
                try {
                    await navigator.clipboard.writeText(copyBtn.dataset.url);
                    copyBtn.textContent = '✓';
                    copyBtn.style.color = '#00e5ff';
                    copyBtn.style.borderColor = '#00e5ff';
                    setTimeout(() => {
                        copyBtn.textContent = original;
                        copyBtn.style.color = '#8b949e';
                        copyBtn.style.borderColor = '#30363d';
                    }, 1500);
                } catch (_) {
                    copyBtn.textContent = '✗';
                    setTimeout(() => { copyBtn.textContent = original; }, 1500);
                }
            });
        });
    }

    function makeBtn(label, color, borderColor) {
        const btn = document.createElement('span');
        btn.innerHTML = label;
        Object.assign(btn.style, {
            cursor: 'pointer',
            marginLeft: '8px',
            fontSize: '11px',
            color,
            border: `1px solid ${borderColor}`,
            borderRadius: '4px',
            padding: '1px 7px',
            background: 'transparent',
            userSelect: 'none',
            verticalAlign: 'middle',
            whiteSpace: 'nowrap',
            flexShrink: '0',
        });
        return btn;
    }

    function makePanel() {
        const panel = document.createElement('div');
        Object.assign(panel.style, {
            display: 'none',
            marginTop: '6px',
            padding: '8px 10px',
            background: '#161b22',
            border: '1px solid #21262d',
            borderRadius: '6px',
            fontSize: '12px',
            lineHeight: '1.8',
        });
        return panel;
    }

    function injectLinkButton(item) {
        if (item.querySelector('.fs-link-btn')) return;

        const h5 = item.querySelector('h5');
        if (!h5) return;

        const detailUrl = h5.querySelector('a')?.href;
        if (!detailUrl) return;

        // ── Links button ──────────────────────────────────────────────────────
        const linkBtn = makeBtn('🔗 Links', '#00e5ff', 'rgba(0,229,255,0.35)');
        linkBtn.className = 'fs-link-btn';
        linkBtn.title = 'Show download links';

        const linkPanel = makePanel();
        linkPanel.className = 'fs-link-panel';

        let linkOpen = false;

        linkBtn.addEventListener('click', async (e) => {
            e.preventDefault();
            e.stopPropagation();

            if (linkOpen) {
                linkPanel.style.display = 'none';
                linkBtn.style.opacity = '0.6';
                linkOpen = false;
                return;
            }

            linkBtn.innerHTML = '⏳';
            linkBtn.style.opacity = '1';

            const { links } = await fetchDetailData(detailUrl);

            if (!links || links.length === 0) {
                linkPanel.innerHTML = 'No links found.';
            } else {
                const grouped = {};
                for (const l of links) {
                    if (!grouped[l.host]) grouped[l.host] = [];
                    grouped[l.host].push(l.url);
                }

                const HOST_COLORS = {
                    'Rapidgator': '#00b4d8', 'Nitroflare': '#f59e0b',
                    'Mega': '#e74c3c', '1Fichier': '#8b5cf6',
                    'Uploadgig': '#22c55e', 'Katfile': '#ec4899',
                    'Filefox': '#f97316', 'DDL': '#8b949e',
                };

                linkPanel.innerHTML = Object.entries(grouped).map(([host, urls]) => {
                    const color = HOST_COLORS[host] || '#8b949e';
                    const allUrls = urls.join('\n');
                    return `
${host}
${urls.length > 1 ? `📋 Copy all` : ''}
${urls.map(u => ` ${u} 📋 ` ).join('
')}
`; }).join(''); attachCopyHandlers(linkPanel); } linkBtn.innerHTML = '🔗 Links'; linkPanel.style.display = 'block'; linkOpen = true; }); linkBtn.addEventListener('mouseover', () => { if (!linkOpen) linkBtn.style.background = 'rgba(0,229,255,0.08)'; }); linkBtn.addEventListener('mouseout', () => { if (!linkOpen) linkBtn.style.background = 'transparent'; }); // ── NFO button ──────────────────────────────────────────────────────── const nfoBtn = makeBtn('📄 NFO', '#a8b8c8', 'rgba(168,184,200,0.35)'); nfoBtn.className = 'fs-nfo-btn'; nfoBtn.title = 'Show NFO / media info'; const nfoPanel = makePanel(); nfoPanel.className = 'fs-nfo-panel'; let nfoOpen = false; nfoBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); if (nfoOpen) { nfoPanel.style.display = 'none'; nfoBtn.style.opacity = '0.6'; nfoOpen = false; return; } nfoBtn.innerHTML = '⏳'; nfoBtn.style.opacity = '1'; const { nfo } = await fetchDetailData(detailUrl); if (!nfo || !nfo.trim()) { nfoPanel.innerHTML = 'No NFO found.'; } else { // Escape HTML so tags inside the NFO text render as text const escaped = nfo .replace(/&/g, '&') .replace(//g, '>'); nfoPanel.innerHTML = `
${escaped}
`; } nfoBtn.innerHTML = '📄 NFO'; nfoPanel.style.display = 'block'; nfoOpen = true; }); nfoBtn.addEventListener('mouseover', () => { if (!nfoOpen) nfoBtn.style.background = 'rgba(168,184,200,0.08)'; }); nfoBtn.addEventListener('mouseout', () => { if (!nfoOpen) nfoBtn.style.background = 'transparent'; }); // ── Inject into DOM ─────────────────────────────────────────────────── h5.appendChild(linkBtn); h5.appendChild(nfoBtn); // Both panels appear below the h5, NFO below links h5.after(nfoPanel); h5.after(linkPanel); } function injectLinkButtons(container) { for (const item of container.querySelectorAll('.fit.item')) { injectLinkButton(item); } } const INPUT_STYLE = ` background: #161b22; color: #e6edf3; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 5px 8px; font-size: 13px; outline: none; transition: border-color 0.2s; height: 30px; box-sizing: border-box; `; // Consistent width for all number inputs const NUMBER_W = 'width:88px;'; function createBar() { const bar = document.createElement('div'); bar.id = `${SCRIPT_ID}-bar`; Object.assign(bar.style, { background: '#0d1117', padding: '14px 18px', borderRadius: '12px', border: '1px solid #21262d', margin: '16px 0', color: '#e6edf3', fontSize: '13px', boxShadow: '0 4px 20px rgba(0,0,0,0.4)', }); bar.innerHTML = `
⚡ ${SCRIPT_NAME}
⚙️
`; bar.addEventListener('mouseover', e => { if (e.target.id === 'f-loadall') { e.target.style.background = '#30363d'; e.target.style.borderColor = 'rgba(0,229,255,0.5)'; } if (e.target.id === 'f-clear') { e.target.style.background = 'rgba(224,108,117,0.12)'; e.target.style.borderColor = 'rgba(224,108,117,0.6)'; } if (e.target.id === 'f-stop') { e.target.style.background = 'rgba(245,158,11,0.12)'; e.target.style.borderColor = 'rgba(245,158,11,0.6)'; } }); bar.addEventListener('mouseout', e => { if (e.target.id === 'f-loadall') { e.target.style.background = '#21262d'; e.target.style.borderColor = 'rgba(0,229,255,0.25)'; } if (e.target.id === 'f-clear') { e.target.style.background = 'transparent'; e.target.style.borderColor = 'rgba(224,108,117,0.35)'; } if (e.target.id === 'f-stop') { e.target.style.background = 'transparent'; e.target.style.borderColor = 'rgba(245,158,11,0.35)'; } }); return bar; } // ─── Progress bar ───────────────────────────────────────────────────────── function showProgress() { document.getElementById('f-progress-wrap').style.display = 'block'; } function updateProgress(loaded, total) { const pct = Math.round((loaded / total) * 100); document.getElementById('f-progress-bar').style.width = `${pct}%`; document.getElementById('f-progress-label').textContent = `Page ${loaded + 1} of ${total}`; document.getElementById('f-progress-pct').textContent = `${pct}%`; } function hideProgress() { // Fill bar to 100% then hide after a short pause document.getElementById('f-progress-bar').style.width = '100%'; document.getElementById('f-progress-pct').textContent = '100%'; document.getElementById('f-progress-label').textContent = 'Done!'; setTimeout(() => { document.getElementById('f-progress-wrap').style.display = 'none'; document.getElementById('f-progress-bar').style.width = '0%'; }, 1500); } // ─── Load pages ─────────────────────────────────────────────────────────── let currentAbortController = null; function updateRecommendedHint() { const hint = document.getElementById('f-recommended-hint'); if (!hint) return; const path = window.location.pathname; const onCategory = /\/(tag\/|quality\/|top-downloads)/.test(path); const hasSearch = !!new URL(window.location.href).searchParams.get('s'); hint.style.display = (onCategory || hasSearch) ? 'none' : 'block'; } async function loadAllPages(container, statusEl) { const itemGrid = container.querySelector('.item_2.items') || container; const currentUrl = new URL(window.location.href); const limitVal = document.getElementById('f-pagelimit')?.value || 'all'; const limit = limitVal === 'all' ? 99999 : parseInt(limitVal); const loadBtn = document.getElementById('f-loadall'); const stopBtn = document.getElementById('f-stop'); currentAbortController = new AbortController(); const { signal } = currentAbortController; if (stopBtn) stopBtn.style.display = 'inline-block'; showProgress(); updateProgress(0, limit === 99999 ? 1 : limit); let loaded = 0; try { for (let p = 2; loaded < limit; p++) { if (signal.aborted) break; const url = currentUrl.pathname.match(/\/page\/\d+\//) ? window.location.href.replace(/\/page\/\d+\//, `/page/${p}/`) : currentUrl.origin + currentUrl.pathname.replace(/\/$/, '') + `/page/${p}/` + currentUrl.search; try { const res = await fetch(url, { credentials: 'same-origin', signal }); if (!res.ok) break; const doc = new DOMParser().parseFromString(await res.text(), 'text/html'); const sourceGrid = doc.querySelector('.item_2.items') || doc; sourceGrid.querySelector('#paginador')?.remove(); const fetchedItems = sourceGrid.querySelectorAll('.fit.item'); if (!fetchedItems.length) break; const fragment = document.createDocumentFragment(); for (const node of fetchedItems) { const clone = document.importNode(node, true); clone.removeAttribute('style'); fragment.appendChild(clone); } itemGrid.appendChild(fragment); loaded++; if (limit !== 99999) updateProgress(loaded, limit); else { document.getElementById('f-progress-bar').style.width = '100%'; document.getElementById('f-progress-label').textContent = `${loaded} page(s) loaded...`; document.getElementById('f-progress-pct').textContent = ''; } await new Promise(r => setTimeout(r, 300)); } catch (e) { if (e.name === 'AbortError') break; console.error(`${SCRIPT_NAME}: fetch failed for`, url, e); break; } } } finally { currentAbortController = null; if (stopBtn) stopBtn.style.display = 'none'; if (loadBtn) loadBtn.disabled = false; } hideProgress(); statusEl.textContent = `${loaded} page(s) loaded`; setTimeout(() => statusEl.textContent = '', 5000); } // ─── Init ───────────────────────────────────────────────────────────────── function init(container) { if (document.getElementById(`${SCRIPT_ID}-bar`)) return; const bar = createBar(); try { container.parentNode.insertBefore(bar, container); } catch (_) { document.body.insertBefore(bar, document.body.firstChild); } bar.querySelector('#f-clear').addEventListener('click', () => clearFilters(container)); // ── Settings panel toggle ──────────────────────────────────────────── const settingsToggle = bar.querySelector('#f-settings-toggle'); const settingsPanel = bar.querySelector('#f-settings-panel'); // Load saved persistence preferences into the checkboxes const savedSettings = loadSettings(); for (const id of Object.keys(PERSISTABLE_FILTERS)) { const cb = document.getElementById(`fs-persist-${id}`); if (!cb) continue; if (id === 'f-pagelimit') { cb.checked = savedSettings['persist_' + id] === true; // default false } else { cb.checked = savedSettings['persist_' + id] !== false; // default true } } settingsToggle.addEventListener('click', () => { const open = settingsPanel.style.display !== 'none'; settingsPanel.style.display = open ? 'none' : 'block'; settingsToggle.style.color = open ? '#8b949e' : '#00e5ff'; }); // Save persistence preference whenever a settings checkbox changes for (const id of Object.keys(PERSISTABLE_FILTERS)) { const cb = document.getElementById(`fs-persist-${id}`); if (!cb) continue; cb.addEventListener('change', () => { const settings = loadSettings(); settings['persist_' + id] = cb.checked; saveSettings(settings); }); } bar.querySelector('#f-stop').addEventListener('click', () => { if (currentAbortController) currentAbortController.abort(); }); bar.querySelector('#f-loadall').addEventListener('click', async function () { this.disabled = true; const status = document.getElementById('f-load-status'); // Hide the existing page pagination — no longer relevant once we load extra pages document.querySelector('#paginador')?.style.setProperty('display', 'none'); try { await loadAllPages(container, status); buildGroupDropdown(container); applyFilters(container); } catch (e) { console.error(`${SCRIPT_NAME}: load pages error`, e); status.textContent = 'Error loading pages'; } finally { this.disabled = false; } }); for (const el of bar.querySelectorAll('input, select')) { el.addEventListener('input', () => applyFilters(container)); } buildGroupDropdown(container); loadFilters(); applyFilters(container); injectLinkButtons(container); updateRecommendedHint(); let debounceTimer; new MutationObserver(() => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { applyFilters(container); injectLinkButtons(container); }, 150); }).observe(container, { childList: true, subtree: true }); } function findContainer() { return document.querySelector('div.peliculas') || document.querySelector('.box'); } function waitForContainer() { const container = findContainer(); if (container) { init(container); } else { setTimeout(waitForContainer, 400); } } waitForContainer(); })();