Facebook Enhancer with Smart Menu

Enhance Facebook: block ads, stop autoplay, mute videos, unwrap links, auto-expand comments, hide suggestions, hide reels, apply themes, keyword filter, draggable UI, reset options, and debug logging. Fully customizable with smart settings menu and CSS injection support.

// ==UserScript==
// @name         Facebook Enhancer with Smart Menu
// @namespace    https://github.com/TamperMonkeyDevelopment/TamperMonkeyScripts
// @version      2.8
// @description  Enhance Facebook: block ads, stop autoplay, mute videos, unwrap links, auto-expand comments, hide suggestions, hide reels, apply themes, keyword filter, draggable UI, reset options, and debug logging. Fully customizable with smart settings menu and CSS injection support.
// @author       Eliminater74
// @match        *://www.facebook.com/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const settings = {
        blockSponsored: true,
        blockSuggested: true,
        disableAutoplay: true,
        muteVideos: true,
        removeBubbles: true,
        unwrapLinks: true,
        moveMessengerDock: false,
        forceDarkMode: false,
        forceMostRecentFeed: true,
        hidePeopleYouMayKnow: true,
        autoExpandComments: true,
        autoClosePopups: true,
        classicBlueTheme: false,
        hideReels: true,
        themePreset: 'default',
        keywordFilter: 'kardashian,tiktok,reaction',
        debugMode: false,
        customCSS: ''
    };

    const labelMap = {
        blockSponsored: 'Block Sponsored Posts',
        blockSuggested: 'Block "Suggested for You"',
        disableAutoplay: 'Stop Video Autoplay',
        muteVideos: 'Mute Videos on Load',
        removeBubbles: 'Remove Notification Bubbles',
        unwrapLinks: 'Unwrap Redirect Links',
        moveMessengerDock: 'Move Messenger Dock to Bottom',
        forceDarkMode: 'Force Dark Mode',
        forceMostRecentFeed: 'Use "Most Recent" Feed',
        hidePeopleYouMayKnow: 'Hide "People You May Know"',
        autoExpandComments: 'Auto-Expand Comments',
        autoClosePopups: 'Auto-Close Popups',
        classicBlueTheme: 'Enable Classic Blue Theme',
        hideReels: 'Hide Facebook Reels',
        themePreset: 'Theme Preset (default, dark, minimal)',
        keywordFilter: 'Filter Posts with Keywords',
        debugMode: 'Enable Debug Logs',
        customCSS: 'Custom CSS (Advanced)'
    };

    const storageKey = 'fb-enhancer-settings';
    let menuVisible = false;

    function loadSettings() {
        const saved = localStorage.getItem(storageKey);
        if (saved) Object.assign(settings, JSON.parse(saved));
    }

    function saveSettings() {
        localStorage.setItem(storageKey, JSON.stringify(settings));
    }

    function resetSettings() {
        localStorage.removeItem(storageKey);
        alert('Settings reset. Reloading...');
        location.reload();
    }

    function downloadSettings() {
        const blob = new Blob([JSON.stringify(settings, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'fb-enhancer-settings.json';
        a.click();
        URL.revokeObjectURL(url);
    }

    function uploadSettings() {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = 'application/json';
        input.addEventListener('change', e => {
            const file = e.target.files[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = event => {
                try {
                    const imported = JSON.parse(event.target.result);
                    Object.assign(settings, imported);
                    saveSettings();
                    alert('Settings loaded. Reloading...');
                    location.reload();
                } catch (err) {
                    alert('Failed to load settings file.');
                }
            };
            reader.readAsText(file);
        });
        input.click();
    }

    function applyCustomCSS() {
        if (settings.customCSS) {
            const style = document.createElement('style');
            style.id = 'fb-enhancer-custom-css';
            style.textContent = settings.customCSS;
            document.head.appendChild(style);
        }
    }

    function applyThemePreset(preset) {
        switch (preset) {
            case 'dark':
                settings.customCSS = 'body { background-color: #121212 !important; color: #ccc !important; }';
                break;
            case 'minimal':
                settings.customCSS = '[role="complementary"], [role="banner"], [aria-label="Stories"] { display: none !important; }';
                break;
            default:
                settings.customCSS = '';
                break;
        }
        saveSettings();
        document.getElementById('custom-css').value = settings.customCSS;
        document.getElementById('fb-enhancer-custom-css')?.remove();
        applyCustomCSS();
    }

    function makeDraggable(el) {
        let posX = 0, posY = 0, mouseX = 0, mouseY = 0;
        el.style.position = 'fixed';
        el.style.cursor = 'move';
        el.onmousedown = function (e) {
            e.preventDefault();
            mouseX = e.clientX;
            mouseY = e.clientY;
            document.onmouseup = () => { document.onmousemove = null; };
            document.onmousemove = (e) => {
                posX = mouseX - e.clientX;
                posY = mouseY - e.clientY;
                mouseX = e.clientX;
                mouseY = e.clientY;
                el.style.top = (el.offsetTop - posY) + "px";
                el.style.left = (el.offsetLeft - posX) + "px";
            };
        };
    }

    function createToggleButton() {
        const toggle = document.createElement('div');
        toggle.id = 'fb-enhancer-toggle';
        toggle.textContent = '⚙ FB Enhancer';
        toggle.style.cssText = 'top:60px;right:10px;background:#4267B2;color:#fff;font-weight:bold;padding:6px 10px;border-radius:6px;z-index:99999;font-size:14px;box-shadow:0 0 5px #000;';
        toggle.addEventListener('click', () => {
            const panel = document.getElementById('fb-enhancer-menu');
            menuVisible = !menuVisible;
            panel.style.display = menuVisible ? 'block' : 'none';
        });
        document.body.appendChild(toggle);
        makeDraggable(toggle);
    }

    function createSettingsMenu() {
        const menu = document.createElement('div');
        menu.id = 'fb-enhancer-menu';
        menu.style.cssText = 'top:100px;right:10px;background:#fff;color:#000;font-size:14px;padding:10px;border:1px solid #ccc;z-index:99998;width:280px;max-height:80vh;overflow-y:auto;border-radius:6px;box-shadow:0 0 5px rgba(0,0,0,0.5);font-family:sans-serif;display:none;position:fixed;';
        menu.innerHTML = `<h4>Facebook Enhancer Settings</h4>
            ${Object.keys(settings).filter(k => k !== 'customCSS').map(key => `
                <label style="display:block;margin-bottom:6px;">
                    ${key === 'keywordFilter' || key === 'themePreset' ? `<input type="text" id="toggle-${key}" value="${settings[key]}" style="width:100%;">` :
                        `<input type="checkbox" id="toggle-${key}" ${settings[key] ? 'checked' : ''}>`} ${labelMap[key] || key}
                </label>`).join('')}
            <label>${labelMap.customCSS}<textarea id="custom-css">${settings.customCSS}</textarea></label>
            <button id="btn-save-settings">Save</button>
            <button id="btn-load-settings">Load</button>
            <button id="btn-reset-settings">Reset</button>
            <div style="margin-top:10px; font-size:12px; text-align:right; opacity:0.6;">v2.8</div>`;
        document.body.appendChild(menu);

        Object.keys(settings).forEach(key => {
            const el = document.getElementById(`toggle-${key}`);
            if (!el) return;
            if (el.type === 'checkbox') {
                el.addEventListener('change', () => {
                    settings[key] = el.checked;
                    saveSettings();
                    location.reload();
                });
            } else if (el.type === 'text') {
                el.addEventListener('change', () => {
                    settings[key] = el.value.trim();
                    if (key === 'themePreset') applyThemePreset(settings[key]);
                    saveSettings();
                });
            }
        });

        document.getElementById('custom-css').addEventListener('input', e => {
            settings.customCSS = e.target.value;
            saveSettings();
            document.getElementById('fb-enhancer-custom-css')?.remove();
            applyCustomCSS();
        });

        document.getElementById('btn-save-settings').addEventListener('click', downloadSettings);
        document.getElementById('btn-load-settings').addEventListener('click', uploadSettings);
        document.getElementById('btn-reset-settings').addEventListener('click', resetSettings);
        makeDraggable(menu);
    }

    function stopVisibleVideoAutoplay() {
        const observer = new IntersectionObserver((entries) => {
            for (const entry of entries) {
                const video = entry.target;
                if (video.dataset.fbEnhanced || video.closest('[class*="reel"]')) continue;
                if (entry.isIntersecting && video.readyState >= 2 && !video.paused) {
                    video.pause();
                    video.removeAttribute('autoplay');
                    if (settings.muteVideos) video.muted = true;
                    video.dataset.fbEnhanced = 'true';
                    if (settings.debugMode) console.log('[FB Enhancer] Video paused and muted.');
                }
            }
        }, { threshold: 0.5 });

        document.querySelectorAll('video:not([data-fb-enhanced])').forEach(video => {
            if (video.closest('[class*="reel"]')) return;
            if (video.readyState >= 2) {
                video.pause();
                video.removeAttribute('autoplay');
                if (settings.muteVideos) video.muted = true;
                video.dataset.fbEnhanced = 'true';
            }
            observer.observe(video);
        });
    }

    function hideReels() {
        document.querySelectorAll('a[href*="/reels/"], [aria-label*="Reels"], [class*="reel"]').forEach(el => el.remove());
    }

    function filterKeywordPosts() {
        const keywords = settings.keywordFilter.split(',').map(k => k.trim().toLowerCase());
        document.querySelectorAll('[role="article"]').forEach(post => {
            const text = post.innerText.toLowerCase();
            if (keywords.some(keyword => text.includes(keyword))) post.remove();
        });
    }

    function runEnhancements() {
        document.querySelectorAll('[role="feed"] [role="article"]').forEach(post => {
            if (settings.blockSponsored && /Sponsored/i.test(post.innerText)) post.remove();
            if (settings.blockSuggested && /Suggested for you/i.test(post.innerText)) post.remove();
            if (settings.autoExpandComments) {
                post.querySelectorAll('[role="button"]').forEach(btn => {
                    if (btn.innerText.includes('View more comments') || btn.innerText.includes('replies')) btn.click();
                });
            }
        });

        if (settings.hidePeopleYouMayKnow) {
            document.querySelectorAll('[role="complementary"], section, aside').forEach(el => {
                const text = el.innerText?.trim();
                if (text?.match(/^People you may know$/i) || el.querySelector('button')?.textContent.toLowerCase().includes('add friend')) {
                    el.remove();
                }
            });
        }

        if (settings.removeBubbles) {
            document.querySelectorAll('span').forEach(el => {
                if (/^\\d+$/.test(el.textContent) && el.closest('[aria-label]')) el.textContent = '';
            });
        }

        if (settings.unwrapLinks) {
            document.querySelectorAll('a[href*="l.facebook.com/l.php"]').forEach(link => {
                const url = new URL(link.href);
                const real = decodeURIComponent(url.searchParams.get('u') || '');
                if (real.startsWith('http')) link.href = real;
            });
        }

        if (settings.disableAutoplay) stopVisibleVideoAutoplay();
        if (settings.hideReels) hideReels();
        if (settings.keywordFilter) filterKeywordPosts();

        if (settings.autoClosePopups) {
            document.querySelectorAll('[role="dialog"]').forEach(d => {
                const text = d.innerText.toLowerCase();
                if (text.includes('log in') || text.includes('feedback') || text.includes('report')) d.remove();
            });
        }

        if (settings.forceMostRecentFeed) {
            const feedSwitch = document.querySelector('a[href*="sk=h_chr"]');
            if (feedSwitch) feedSwitch.click();
        }

        if (settings.classicBlueTheme) document.body.style.backgroundColor = '#e9ebee';

        if (settings.moveMessengerDock) {
            const dock = document.querySelector('[aria-label="Chat tab bar"]');
            if (dock) {
                dock.style.bottom = '0';
                dock.style.top = 'auto';
            }
        }

        document.documentElement.classList.toggle('fb-dark-mode', settings.forceDarkMode);
    }

    function init() {
        loadSettings();
        applyCustomCSS();
        createToggleButton();
        createSettingsMenu();
        setInterval(runEnhancements, 2500);
        window.addEventListener('load', runEnhancements);
    }

    init();
})();