您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hide/soft-remove Reels, Stories, Suggestions, PYMK, right-rail ads; pause/mute videos; unwrap links on click; keyword+regex filters; sticky 'Most Recent'; draggable settings panel (position save) + import/export; blur/review/show-hidden modes; per-post "Hide posts like this" control; hotkeys; throttled observer; locale-aware 'Sponsored'.
// ==UserScript== // @name Facebook Enhancer v3.30 // @namespace https://github.com/TamperMonkeyDevelopment/TamperMonkeyScripts // @version 3.30 // @description Hide/soft-remove Reels, Stories, Suggestions, PYMK, right-rail ads; pause/mute videos; unwrap links on click; keyword+regex filters; sticky 'Most Recent'; draggable settings panel (position save) + import/export; blur/review/show-hidden modes; per-post "Hide posts like this" control; hotkeys; throttled observer; locale-aware 'Sponsored'. // @author Eliminater74 // @match *://*.facebook.com/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @license MIT // @run-at document-end // ==/UserScript== (function () { 'use strict'; // ----------------------- // Storage / Defaults // ----------------------- const STORAGE_KEY = 'fb-enhancer-settings'; const POS_KEY = 'fb-enhancer-positions'; const defaultSettings = { // General / Visual debugMode: false, softRemove: true, // display:none instead of remove() blurHidden: false, // visually blur hidden instead of hide/remove reviewMode: false, // outline hidden items for debugging showHidden: false, // temporarily show items we hid (softRemove only) forceDarkMode: false, customCSS: '', // Feed & Hiding blockSponsored: true, blockSuggested: true, hideReels: true, hideReelLinks: true, // remove any unit that links to /reel/ or /reels/ aggressiveReelsBlock: true, // broader heuristics (video-count etc.) reelsHeadingPhrases: 'reels,reels and short videos,short videos', hideStories: true, hidePeopleYouMayKnow: true, hideRightRailAds: true, keywordFilter: 'kardashian,tiktok,reaction', keywordRegex: '', // Video disableAutoplay: true, muteVideos: true, // Navigation / Sidebar toggleMarketplace: false, toggleEvents: false, toggleShortcuts: false, // Behavior unwrapLinks: true, // rewrite tracking URLs on click unwrapOnHover: false, // (safer off) autoExpandComments: true, forceMostRecentFeed: true, mostRecentRetryMs: 4000, // Hotkeys gearHotkey: 'Alt+E', forceMostRecentHotkey: 'Alt+R', showHiddenHotkey: 'Alt+H' }; let settings = loadSettings(); const positions = loadPositions(); function loadSettings() { try { const saved = JSON.parse(localStorage.getItem(STORAGE_KEY)); return Object.assign({}, defaultSettings, saved || {}); } catch { return { ...defaultSettings }; } } function saveSettings() { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); applyVisualModes(); } function loadPositions() { try { return JSON.parse(localStorage.getItem(POS_KEY)) || {}; } catch { return {}; } } function savePositions() { localStorage.setItem(POS_KEY, JSON.stringify(positions)); } // ----------------------- // Utilities // ----------------------- const log = (...args) => settings.debugMode && console.log('[FB Enhancer]', ...args); const throttle = (fn, ms) => { let last = 0, timer; return (...args) => { const now = Date.now(); if (now - last >= ms) { last = now; fn(...args); } else { clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...args); }, ms - (now - last)); } }; }; // ----------------------- // CSS // ----------------------- GM_addStyle(` .fb-enhancer-btn { position:fixed; top:60px; right:10px; background:#4267B2; color:#fff; padding:6px 10px; border-radius:6px; z-index:999999; cursor:pointer; font-weight:bold; user-select:none; } .fb-enhancer-panel { position:fixed; top:110px; right:10px; z-index:999999; background:#fff; color:#111; padding:10px; width:340px; max-height:70vh; overflow:auto; border:1px solid #ccc; border-radius:8px; font-size:14px; display:none; } .fb-enhancer-panel h3 { margin: 0 0 8px; } .fb-enhancer-group { margin:8px 0; padding:8px; border:1px dashed #ddd; border-radius:6px; } .fb-enhancer-row { display:flex; align-items:center; gap:8px; margin:6px 0; } .fb-enhancer-row label { flex: 1; } .fb-enhancer-actions { margin-top:8px; display:flex; gap:8px; flex-wrap:wrap; } html.fb-dark-mode { filter: invert(1) hue-rotate(180deg); } /* cheap global dark flip */ html.fb-dark-mode img, html.fb-dark-mode video { filter: invert(1) hue-rotate(180deg); } .fe-blur { filter: blur(10px) opacity(.35); pointer-events:none; } .fe-review { outline: 2px dashed #f36 !important; position: relative; } .fe-review::after { content: 'FB Enhancer: hidden'; position: absolute; top: -10px; left: -2px; font-size: 11px; background: #f36; color: #fff; padding: 0 4px; border-radius: 3px; } body.fe-show-hidden [data-fbEnhanced="1"] { display: initial !important; visibility: visible !important; } /* Per-post control */ .fe-hide-btn { position:absolute; top:6px; right:6px; z-index:9999; font-size:12px; background:rgba(66,103,178,.95); color:#fff; border:none; border-radius:4px; padding:3px 6px; cursor:pointer; display:none; } [role="article"]:hover .fe-hide-btn { display:block; } `); // ----------------------- // Visual Modes / Custom CSS // ----------------------- function applyCustomCSS() { const id = 'fb-enhancer-custom-style'; document.getElementById(id)?.remove(); if (settings.customCSS) { const style = document.createElement('style'); style.id = id; style.textContent = settings.customCSS; document.head.appendChild(style); } } function applyVisualModes() { // Dark if (settings.forceDarkMode) document.documentElement.classList.add('fb-dark-mode'); else document.documentElement.classList.remove('fb-dark-mode'); // Show hidden document.body.classList.toggle('fe-show-hidden', !!settings.showHidden); } // ----------------------- // UI: Gear + Panel // ----------------------- const ui = { btn:null, panel:null, toggle(){ ui.panel.style.display = ui.panel.style.display === 'none' ? 'block' : 'none'; } }; function addRow(container, key, label, type = 'boolean', placeholder = '') { const row = document.createElement('div'); row.className = 'fb-enhancer-row'; const id = `fe_${key}`; if (type === 'boolean') { row.innerHTML = `<label><input type="checkbox" id="${id}" ${settings[key] ? 'checked' : ''}/> ${label}</label>`; } else { row.innerHTML = `<label>${label}</label><input type="text" id="${id}" value="${settings[key] ?? ''}" placeholder="${placeholder}" style="flex:2;">`; } container.appendChild(row); return id; } function createSettingsMenu() { // Button const button = document.createElement('div'); button.textContent = '⚙ Enhancer'; button.className = 'fb-enhancer-btn'; button.id = 'fb-enhancer-toggle'; // Panel const panel = document.createElement('div'); panel.id = 'fb-enhancer-panel'; panel.className = 'fb-enhancer-panel'; const root = document.createElement('div'); root.innerHTML = `<h3>Facebook Enhancer</h3>`; // Groups const gGeneral = document.createElement('div'); gGeneral.className = 'fb-enhancer-group'; gGeneral.innerHTML = `<strong>General / Visual</strong>`; addRow(gGeneral, 'debugMode', 'Enable debug logs'); addRow(gGeneral, 'softRemove', 'Soft remove (display:none) instead of remove()'); addRow(gGeneral, 'blurHidden', 'Blur hidden items (reviewable)'); addRow(gGeneral, 'reviewMode', 'Outline hidden items (debug)'); addRow(gGeneral, 'showHidden', 'Temporarily show hidden items'); addRow(gGeneral, 'forceDarkMode', 'Force Dark Mode (CSS invert)'); addRow(gGeneral, 'customCSS', 'Custom CSS', 'text', '/* your CSS here */'); const gFeed = document.createElement('div'); gFeed.className = 'fb-enhancer-group'; gFeed.innerHTML = `<strong>Feed & Hiding</strong>`; addRow(gFeed, 'blockSponsored', 'Hide Sponsored posts'); addRow(gFeed, 'blockSuggested', 'Hide Suggested for you'); addRow(gFeed, 'hideReels', 'Hide Reels modules'); addRow(gFeed, 'hideReelLinks', 'Hide units that contain /reel/ links'); addRow(gFeed, 'aggressiveReelsBlock', 'Aggressive Reels heuristics'); addRow(gFeed, 'reelsHeadingPhrases', 'Reels heading phrases (comma)', 'text', 'reels,reels and short videos,short videos'); addRow(gFeed, 'hideStories', 'Hide Stories'); addRow(gFeed, 'hidePeopleYouMayKnow', 'Hide People You May Know'); addRow(gFeed, 'hideRightRailAds', 'Hide Right Rail Ads'); addRow(gFeed, 'keywordFilter', 'Keyword filter (comma-separated)', 'text', 'kardashian,tiktok,reaction'); addRow(gFeed, 'keywordRegex', 'Keyword Regex (optional)', 'text', '(giveaway|crypto)\\b'); const gVideo = document.createElement('div'); gVideo.className = 'fb-enhancer-group'; gVideo.innerHTML = `<strong>Video</strong>`; addRow(gVideo, 'disableAutoplay', 'Disable autoplay'); addRow(gVideo, 'muteVideos', 'Mute videos'); const gNav = document.createElement('div'); gNav.className = 'fb-enhancer-group'; gNav.innerHTML = `<strong>Navigation / Sidebar</strong>`; addRow(gNav, 'toggleMarketplace', 'Hide Marketplace'); addRow(gNav, 'toggleEvents', 'Hide Events'); addRow(gNav, 'toggleShortcuts', 'Hide Your Shortcuts'); const gBehavior = document.createElement('div'); gBehavior.className = 'fb-enhancer-group'; gBehavior.innerHTML = `<strong>Behavior</strong>`; addRow(gBehavior, 'unwrapLinks', 'Unwrap tracking links on click'); addRow(gBehavior, 'unwrapOnHover', 'Unwrap on hover (riskier—leave off)'); addRow(gBehavior, 'autoExpandComments', 'Auto-expand comments'); addRow(gBehavior, 'forceMostRecentFeed', 'Force Most Recent feed'); addRow(gBehavior, 'mostRecentRetryMs', 'Most Recent retry (ms)', 'text', '4000'); const gHotkeys = document.createElement('div'); gHotkeys.className = 'fb-enhancer-group'; gHotkeys.innerHTML = `<strong>Hotkeys</strong>`; addRow(gHotkeys, 'gearHotkey', 'Toggle panel hotkey', 'text', 'Alt+E'); addRow(gHotkeys, 'forceMostRecentHotkey', 'Force Most Recent hotkey', 'text', 'Alt+R'); addRow(gHotkeys, 'showHiddenHotkey', 'Toggle "Show Hidden" hotkey', 'text', 'Alt+H'); root.append(gGeneral, gFeed, gVideo, gNav, gBehavior, gHotkeys); const actions = document.createElement('div'); actions.className = 'fb-enhancer-actions'; actions.innerHTML = ` <button id="fe-save">Save</button> <button id="fe-reset">Reset</button> <button id="fe-export">Export</button> <button id="fe-import">Import</button> `; root.appendChild(actions); panel.appendChild(root); document.body.append(button, panel); restorePosition(button, 'gear'); restorePosition(panel, 'panel'); button.onclick = () => ui.toggle(); makeDraggable(button, 'gear'); makeDraggable(panel, 'panel'); document.getElementById('fe-save').onclick = () => { const collect = (key) => { const el = document.getElementById(`fe_${key}`); if (!el) return; if (el.type === 'checkbox') settings[key] = el.checked; else { const val = el.value; if (key === 'mostRecentRetryMs') settings[key] = Math.max(1000, parseInt(val || '4000', 10) || 4000); else settings[key] = val; } }; Object.keys(defaultSettings).forEach(collect); saveSettings(); applyCustomCSS(); alert('Settings saved. Reloading…'); location.reload(); }; document.getElementById('fe-reset').onclick = () => { localStorage.removeItem(STORAGE_KEY); localStorage.removeItem(POS_KEY); alert('Settings reset. Reloading…'); location.reload(); }; document.getElementById('fe-export').onclick = () => { const blob = new Blob([JSON.stringify(settings, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = Object.assign(document.createElement('a'), { href: url, download: 'fb-enhancer-settings.json' }); document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); }; document.getElementById('fe-import').onclick = () => { const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json'; input.onchange = () => { const file = input.files && input.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = () => { try { const obj = JSON.parse(reader.result); localStorage.setItem(STORAGE_KEY, JSON.stringify(Object.assign({}, defaultSettings, obj))); alert('Imported. Reloading…'); location.reload(); } catch { alert('Invalid JSON.'); } }; reader.readAsText(file); }; input.click(); }; ui.btn = button; ui.panel = panel; if (typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand('Toggle Enhancer Panel', () => ui.toggle()); GM_registerMenuCommand('Export Settings', () => document.getElementById('fe-export').click()); GM_registerMenuCommand('Import Settings', () => document.getElementById('fe-import').click()); GM_registerMenuCommand('Toggle Show Hidden', () => { settings.showHidden = !settings.showHidden; saveSettings(); }); } } function makeDraggable(el, key) { let offsetX = 0, offsetY = 0, isDragging = false; el.style.position = 'fixed'; el.addEventListener('mousedown', e => { isDragging = true; offsetX = e.clientX - el.getBoundingClientRect().left; offsetY = e.clientY - el.getBoundingClientRect().top; e.preventDefault(); }); document.addEventListener('mousemove', e => { if (!isDragging) return; el.style.left = `${e.clientX - offsetX}px`; el.style.top = `${e.clientY - offsetY}px`; el.style.right = 'auto'; el.style.bottom = 'auto'; }); document.addEventListener('mouseup', () => { if (!isDragging) return; isDragging = false; const rect = el.getBoundingClientRect(); positions[key] = { left: rect.left, top: rect.top }; savePositions(); }); } function restorePosition(el, key) { const pos = positions[key]; if (pos) { el.style.left = `${pos.left}px`; el.style.top = `${pos.top}px`; el.style.right = 'auto'; el.style.bottom = 'auto'; } } // ----------------------- // Link unwrapping // ----------------------- function unwrapUrl(href) { try { const u = new URL(href, location.origin); if ((u.hostname || '').includes('l.facebook.com') && u.pathname.startsWith('/l.php')) { const real = u.searchParams.get('u'); if (real) return decodeURIComponent(real); } } catch {} return href; } function installLinkHandlers() { if (!settings.unwrapLinks) return; document.addEventListener('click', e => { const a = e.target.closest('a[href]'); if (!a) return; const newHref = unwrapUrl(a.getAttribute('href') || ''); if (newHref && newHref !== a.href) { a.setAttribute('href', newHref); log('Unwrapped link on click:', newHref); } }, true); if (settings.unwrapOnHover) { document.addEventListener('mouseover', e => { const a = e.target.closest('a[href]'); if (!a) return; const newHref = unwrapUrl(a.getAttribute('href') || ''); if (newHref && newHref !== a.href) a.setAttribute('href', newHref); }, true); } } // ----------------------- // Video control // ----------------------- function handleVideo(video) { if (video.dataset.fbEnhanced === 'v') return; if (settings.muteVideos) video.muted = true; if (settings.disableAutoplay) { video.removeAttribute('autoplay'); if (!video.paused) video.pause(); } video.dataset.fbEnhanced = 'v'; } function scanVideos(root = document) { root.querySelectorAll('video:not([data-fb-enhanced="v"])').forEach(handleVideo); } function pauseVideosOffscreen() { if (!settings.disableAutoplay) return; let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { window.requestAnimationFrame(() => { document.querySelectorAll('video').forEach(video => { const rect = video.getBoundingClientRect(); const inView = rect.top >= 0 && rect.bottom <= window.innerHeight; if (!inView && !video.paused) { video.pause(); log('Paused offscreen video'); } }); ticking = false; }); ticking = true; } }, { passive: true }); } // ----------------------- // Post filtering // ----------------------- const sponsoredWords = [ 'sponsored','publicidad','gesponsert','sponsorisé','patrocinado', 'patrocinada','sponsorizzato','gesponsord' ]; function appearsSponsored(el) { const text = (el.innerText || el.textContent || '').toLowerCase(); return sponsoredWords.some(w => text.includes(w)); } function matchesKeyword(text) { if (!text) return false; const hay = text.toLowerCase(); const list = (settings.keywordFilter || '') .split(',').map(s => s.trim()).filter(Boolean); if (list.some(k => hay.includes(k.toLowerCase()))) return true; if (settings.keywordRegex) { try { return new RegExp(settings.keywordRegex, 'i').test(text); } catch { /* invalid regex ignored */ } } return false; } function processArticle(article) { if (article.dataset.fbEnhanced === 'a') return; try { const text = (article.innerText || ''); if (settings.blockSponsored && appearsSponsored(article)) { softOrHardRemove(article); return; } if (settings.blockSuggested && /suggested for you/i.test(text)) { softOrHardRemove(article); return; } if (matchesKeyword(text)) { softOrHardRemove(article); return; } if (settings.autoExpandComments) { article.querySelectorAll('div[role="button"]').forEach(btn => { if (/view (more )?comments|replies/i.test(btn.textContent)) btn.click(); }); } installPerPostHideControl(article); } catch (err) { log('Article error:', err); } article.dataset.fbEnhanced = 'a'; } function installPerPostHideControl(article) { if (article.querySelector('.fe-hide-btn')) return; article.style.position = article.style.position || 'relative'; const btn = document.createElement('button'); btn.className = 'fe-hide-btn'; btn.textContent = 'Hide posts like this'; btn.addEventListener('click', (e) => { e.stopPropagation(); const base = buildKeywordSuggestion((article.innerText || '')); const user = prompt('Add keywords (comma-separated). These will be appended to your filter:', base); if (!user) return; const existing = new Set((settings.keywordFilter || '').split(',') .map(s=>s.trim()).filter(Boolean).map(s=>s.toLowerCase())); user.split(',').map(s=>s.trim()).filter(Boolean).forEach(k => existing.add(k.toLowerCase())); settings.keywordFilter = Array.from(existing).join(','); saveSettings(); sweepAll(); alert('Added. Filter updated.'); }); article.appendChild(btn); } function buildKeywordSuggestion(text) { const stop = new Set(('the,a,an,and,or,for,with,from,that,this,those,these,of,to,at,by,on,in,is,are,was,were,be,been,am,as,it,if,not,no,yes,do,does,did,you,your,me,my,our,we,they,them,he,she,his,her,him,who,what,when,where,why,how,about,into,over,under,more,most,so,just,can,will,up,down,out,get,got,have,has,had' + ',http,https,www,com,net,org,facebook,reel,reels,video,watch,like,share,comment').split(',')); const words = (text.toLowerCase().match(/[a-z0-9]+/g) || []) .filter(w => w.length >= 4 && !stop.has(w)); const freq = new Map(); for (const w of words) freq.set(w, (freq.get(w) || 0) + 1); const picks = Array.from(freq.entries()).sort((a,b)=>b[1]-a[1]).slice(0,5).map(([w])=>w); return picks.join(','); } // ----------------------- // Hide helpers / modes // ----------------------- function softOrHardRemove(el) { if (!el || el.dataset.fbEnhanced === '1') return; if (settings.blurHidden) { el.classList.add('fe-blur'); } else if (settings.softRemove) { el.style.display = 'none'; } else { el.remove(); } if (settings.reviewMode) el.classList.add('fe-review'); el.dataset.fbEnhanced = '1'; } // ----------------------- // Reels / Stories / PYMK / Nav // ----------------------- function closestContentContainer(el) { return el.closest('div[data-pagelet]') || el.closest('[role="article"]') || el.closest('section') || el.closest('div[role="complementary"]') || el; } function isReelsHeadingText(txt) { if (!txt) return false; const hay = String(txt).toLowerCase(); return (settings.reelsHeadingPhrases || '') .split(',').map(s => s.trim()).filter(Boolean) .some(needle => hay.includes(needle)); } function isLikelyReelsUnit(node) { try { const text = (node.innerText || node.textContent || '').trim(); const vids = node.querySelectorAll('video').length; if (isReelsHeadingText(text) && vids >= 2) return true; const dp = node.getAttribute && (node.getAttribute('data-pagelet') || ''); if (/Reel|Reels|VideoHome|HomeUnit|video/i.test(dp)) return true; if (settings.aggressiveReelsBlock && vids >= 3) return true; return false; } catch { return false; } } function hideReels() { if (!settings.hideReels) return; // Headings / modules document.querySelectorAll('h2, h3, [role="heading"], [aria-label], div[data-pagelet]').forEach(el => { const label = el.getAttribute?.('aria-label') || el.textContent || ''; if (!label) return; if (isReelsHeadingText(label) || isLikelyReelsUnit(el)) { const box = closestContentContainer(el); if (box && box.dataset.fbEnhanced !== '1') { softOrHardRemove(box); log('Reels module removed'); } } }); // Horizontal scrollers in feed document.querySelectorAll('[role="feed"] div').forEach(el => { if (el.dataset.fbEnhanced === '1') return; if (isLikelyReelsUnit(el)) softOrHardRemove(closestContentContainer(el)); }); // Right rail document.querySelectorAll('div[data-pagelet*="RightRail"], [role="complementary"]').forEach(el => { if (el.dataset.fbEnhanced === '1') return; if (isLikelyReelsUnit(el)) softOrHardRemove(closestContentContainer(el)); }); } function hideReelLinksSweep(root = document) { if (!settings.hideReelLinks) return; root.querySelectorAll('a[href*="/reel/"], a[href*="/reels/"]').forEach(a => { const box = closestContentContainer(a); if (box && box.dataset.fbEnhanced !== '1') { softOrHardRemove(box); log('Reel link unit removed'); } }); } function hideStories() { if (!settings.hideStories) return; document.querySelectorAll('div[aria-label="Stories"], div[data-pagelet*="Stories"]').forEach(softOrHardRemove); } function hidePeopleYouMayKnow() { if (!settings.hidePeopleYouMayKnow) return; document.querySelectorAll('[role="feed"] div').forEach(block => { const text = (block.innerText || '').toLowerCase(); const hasButtons = [...block.querySelectorAll('button')].some(b => (b.innerText || '').toLowerCase().includes('add friend')); if (text.includes('people you may know') && hasButtons) softOrHardRemove(block); }); } function hideRightRailAds() { if (!settings.hideRightRailAds) return; document.querySelectorAll('div[data-pagelet*="RightRail"]').forEach(node => { if (appearsSponsored(node)) softOrHardRemove(node); }); } function collapseSidebarSections() { const map = { toggleMarketplace: 'marketplace', toggleEvents: 'events', toggleShortcuts: 'your shortcuts' }; for (let key in map) { if (!settings[key]) continue; const matchText = map[key]; document.querySelectorAll('span, div').forEach(el => { const txt = (el.textContent || '').toLowerCase(); if (!txt || !txt.includes(matchText)) return; const container = el.closest('ul') || el.closest('li') || el.closest('div[role="navigation"]'); if (container) softOrHardRemove(container); }); } } // ----------------------- // Force Most Recent (sticky) // ----------------------- let mostRecentTimer = null; function forceMostRecent() { if (!settings.forceMostRecentFeed) return; const link = document.querySelector('a[href*="sk=h_chr"]'); if (link) { link.click(); log('Forcing Most Recent…'); } } function patchHistoryEvents() { try { const push = history.pushState; history.pushState = function() { const r = push.apply(this, arguments); window.dispatchEvent(new Event('pushstate')); return r; }; const rep = history.replaceState; history.replaceState = function() { const r = rep.apply(this, arguments); window.dispatchEvent(new Event('replacestate')); return r; }; } catch {} } function startMostRecentSticky() { if (!settings.forceMostRecentFeed) return; clearInterval(mostRecentTimer); mostRecentTimer = setInterval(forceMostRecent, Math.max(1000, settings.mostRecentRetryMs | 0 || 4000)); window.addEventListener('popstate', () => setTimeout(forceMostRecent, 350)); window.addEventListener('pushstate', () => setTimeout(forceMostRecent, 350)); window.addEventListener('replacestate', () => setTimeout(forceMostRecent, 350)); } // ----------------------- // Observers (throttled) // ----------------------- const processMutations = throttle((mutations) => { for (const m of mutations) { m.addedNodes.forEach(node => { if (node.nodeType !== 1) return; if (node.getAttribute?.('role') === 'article') processArticle(node); node.querySelectorAll?.('[role="article"]').forEach(processArticle); if (node.matches?.('video')) handleVideo(node); node.querySelectorAll?.('video').forEach(handleVideo); }); } hideReels(); hideReelLinksSweep(); hideStories(); hidePeopleYouMayKnow(); hideRightRailAds(); collapseSidebarSections(); }, 500); function observePage() { const mo = new MutationObserver(processMutations); mo.observe(document.body, { childList: true, subtree: true }); // initial pass document.querySelectorAll('[role="article"]').forEach(processArticle); scanVideos(); hideReels(); hideReelLinksSweep(); hideStories(); hidePeopleYouMayKnow(); hideRightRailAds(); collapseSidebarSections(); } function sweepAll() { document.querySelectorAll('[role="article"]').forEach(a => { a.dataset.fbEnhanced = ''; processArticle(a); }); hideReels(); hideReelLinksSweep(); hideStories(); hidePeopleYouMayKnow(); hideRightRailAds(); collapseSidebarSections(); } // ----------------------- // Hotkeys // ----------------------- function parseHotkey(s) { const parts = (s || '').toLowerCase().split('+').map(p => p.trim()).filter(Boolean); return { alt: parts.includes('alt'), ctrl: parts.includes('ctrl') || parts.includes('control'), shift: parts.includes('shift'), key: parts[parts.length - 1] || '' }; } const hkPanel = parseHotkey(settings.gearHotkey); const hkMostRecent = parseHotkey(settings.forceMostRecentHotkey); const hkShowHidden = parseHotkey(settings.showHiddenHotkey); document.addEventListener('keydown', (e) => { const k = e.key.toLowerCase(); const match = (hk) => (!!hk.alt === e.altKey) && (!!hk.ctrl === e.ctrlKey) && (!!hk.shift === e.shiftKey) && (hk.key === k); if (match(hkPanel)) { e.preventDefault(); ui.toggle(); } if (match(hkMostRecent)) { e.preventDefault(); forceMostRecent(); } if (match(hkShowHidden)) { e.preventDefault(); settings.showHidden = !settings.showHidden; saveSettings(); } }); // ----------------------- // Init // ----------------------- function init() { applyCustomCSS(); createSettingsMenu(); applyVisualModes(); installLinkHandlers(); observePage(); pauseVideosOffscreen(); patchHistoryEvents(); startMostRecentSticky(); setInterval(() => { hideReels(); hideReelLinksSweep(); }, 5000); // periodic safety sweep } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();