Greasy Fork

Greasy Fork is available in English.

Facebook Activity Auto Deleter (2025) - Optimized

Fast and efficient Facebook activity log cleaner with smart error handling and problematic item skipping.

当前为 2025-04-06 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Facebook Activity Auto Deleter (2025) - Optimized
// @namespace    http://greasyfork.icu/en/users/1454546-shawnfrost13
// @version      5.02
// @description  Fast and efficient Facebook activity log cleaner with smart error handling and problematic item skipping.
// @author       shawnfrost13 (optimized by Claude)
// @license      MIT
// @match        https://www.facebook.com/*/allactivity*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    // ===== CONFIGURATION =====
    const CONFIG = {
        // Delays (in ms)
        menuClickDelay: 300,      // Time to wait after clicking menu button
        deleteClickDelay: 300,    // Time to wait after clicking delete option
        betweenItemsDelay: 800,   // Random base delay between deletion attempts
        randomDelayMax: 400,      // Additional random delay to avoid detection
        errorCheckDelay: 600,     // How long to wait to check for errors
        scrollDelay: 1500,        // How long to wait after scrolling
        
        // UI
        uiUpdateInterval: 500,    // How often to update stats display
        
        // Error handling
        maxRetries: 2,            // Maximum retries before skipping item permanently
        errorTexts: [             // Text patterns that indicate errors
            "Something went wrong",
            "Please try again",
            "An error occurred",
            "We couldn't process",
            "Action blocked"
        ]
    };

    // ===== STATE =====
    const STATE = {
        isRunning: false,
        deletionCount: 0,
        skipCount: 0,
        currentItem: null,
        problemItems: new Map(),  // Map of signatures to retry counts
        lastActionTime: 0,        // For monitoring performance
        processingTime: [],       // Track how long each deletion takes
    };

    // ===== DOM ELEMENT REFERENCES =====
    const DOM = {
        statusDisplay: null,
        statsDisplay: null,
        toggleButton: null,
        resetButton: null,
        speedSlider: null
    };

    // ===== UTILITY FUNCTIONS =====
    function log(message, type = 'info') {
        const prefix = {
            'info': '📋',
            'success': '✅',
            'error': '❌',
            'warn': '⚠️',
            'skip': '⏭️',
            'scroll': '🔄'
        }[type] || '🔹';
        
        console.log(`${prefix} ${message}`);
    }

    function getRandomDelay() {
        return CONFIG.betweenItemsDelay + Math.floor(Math.random() * CONFIG.randomDelayMax);
    }

    function updateStatus(text, type = 'normal') {
        if (!DOM.statusDisplay) return;
        
        // Set color based on type
        const colors = {
            'normal': 'lime',
            'error': '#ff5555',
            'warning': '#ffaa00',
            'success': '#55ff55'
        };
        
        DOM.statusDisplay.textContent = text;
        DOM.statusDisplay.style.color = colors[type] || colors.normal;
    }

    function updateStats() {
        if (!DOM.statsDisplay) return;
        
        // Calculate average processing time
        let avgTime = 0;
        if (STATE.processingTime.length > 0) {
            avgTime = STATE.processingTime.reduce((sum, time) => sum + time, 0) / STATE.processingTime.length;
        }
        
        DOM.statsDisplay.innerHTML = `
            <div style="font-weight:bold;margin-bottom:5px;border-bottom:1px solid #444;padding-bottom:3px;">FB CLEANER STATS</div>
            <div>Deleted: <span style="color:#55ff55">${STATE.deletionCount}</span></div>
            <div>Skipped: <span style="color:#ffaa00">${STATE.skipCount}</span></div>
            <div>Avg Time: <span style="color:#55aaff">${Math.round(avgTime)}ms</span></div>
        `;
    }

    // ===== UI CREATION =====
    function createUI() {
        const css = `
            .fb-cleaner-ui {
                position: fixed;
                bottom: 20px;
                right: 20px;
                background: rgba(10, 10, 10, 0.85);
                border: 1px solid #444;
                border-radius: 10px;
                color: white;
                font-family: 'Segoe UI', Tahoma, Geneva, sans-serif;
                z-index: 999999;
                backdrop-filter: blur(5px);
                box-shadow: 0 4px 12px rgba(0,0,0,0.2);
                overflow: hidden;
                transition: all 0.3s ease;
                width: 180px;
            }
            
            .fb-cleaner-header {
                background: #222;
                padding: 8px 12px;
                font-weight: bold;
                border-bottom: 1px solid #444;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            
            .fb-cleaner-title {
                font-size: 14px;
                color: #fff;
            }
            
            .fb-cleaner-version {
                font-size: 10px;
                color: #aaa;
                background: #333;
                padding: 2px 6px;
                border-radius: 10px;
            }
            
            .fb-cleaner-body {
                padding: 10px;
            }
            
            .fb-cleaner-status {
                margin-bottom: 10px;
                padding: 5px;
                background: rgba(0,0,0,0.2);
                border-radius: 5px;
                font-size: 13px;
                color: lime;
                min-height: 20px;
            }
            
            .fb-cleaner-stats {
                font-size: 12px;
                margin: 10px 0;
                line-height: 1.5;
            }
            
            .fb-cleaner-button {
                padding: 8px;
                border: none;
                border-radius: 5px;
                cursor: pointer;
                font-weight: bold;
                font-size: 12px;
                transition: all 0.2s;
                width: 100%;
                margin-bottom: 8px;
            }
            
            .fb-cleaner-button:hover {
                transform: translateY(-2px);
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            }
            
            .fb-cleaner-button.start {
                background: #4CAF50;
                color: white;
            }
            
            .fb-cleaner-button.pause {
                background: #FF9800;
                color: black;
            }
            
            .fb-cleaner-button.reset {
                background: #f44336;
                color: white;
            }
            
            .fb-cleaner-speed {
                margin-top: 5px;
                display: flex;
                flex-direction: column;
                font-size: 12px;
            }
            
            .fb-cleaner-speed-label {
                display: flex;
                justify-content: space-between;
                margin-bottom: 5px;
            }
            
            .fb-cleaner-slider {
                width: 100%;
                cursor: pointer;
            }
        `;
        
        // Add CSS
        const styleEl = document.createElement('style');
        styleEl.textContent = css;
        document.head.appendChild(styleEl);
        
        // Create main container
        const container = document.createElement('div');
        container.className = 'fb-cleaner-ui';
        
        // Create header
        const header = document.createElement('div');
        header.className = 'fb-cleaner-header';
        
        const title = document.createElement('div');
        title.className = 'fb-cleaner-title';
        title.textContent = 'FB Cleaner';
        
        const version = document.createElement('div');
        version.className = 'fb-cleaner-version';
        version.textContent = 'v5.02';
        
        header.appendChild(title);
        header.appendChild(version);
        
        // Create body
        const body = document.createElement('div');
        body.className = 'fb-cleaner-body';
        
        // Status display
        const status = document.createElement('div');
        status.className = 'fb-cleaner-status';
        status.textContent = 'Ready to start';
        DOM.statusDisplay = status;
        
        // Stats display
        const stats = document.createElement('div');
        stats.className = 'fb-cleaner-stats';
        DOM.statsDisplay = stats;
        
        // Toggle button
        const toggleBtn = document.createElement('button');
        toggleBtn.className = 'fb-cleaner-button start';
        toggleBtn.textContent = '▶️ Start Cleaning';
        toggleBtn.addEventListener('click', toggleRunning);
        DOM.toggleButton = toggleBtn;
        
        // Reset button
        const resetBtn = document.createElement('button');
        resetBtn.className = 'fb-cleaner-button reset';
        resetBtn.textContent = '🔄 Reset Skip List';
        resetBtn.addEventListener('click', () => {
            STATE.problemItems.clear();
            STATE.skipCount = 0;
            updateStatus('Skip list cleared', 'success');
            updateStats();
        });
        DOM.resetButton = resetBtn;
        
        // Speed control
        const speedControl = document.createElement('div');
        speedControl.className = 'fb-cleaner-speed';
        
        const speedLabel = document.createElement('div');
        speedLabel.className = 'fb-cleaner-speed-label';
        
        const speedText = document.createElement('span');
        speedText.textContent = 'Speed:';
        
        const speedValue = document.createElement('span');
        speedValue.textContent = 'Normal';
        
        speedLabel.appendChild(speedText);
        speedLabel.appendChild(speedValue);
        
        const speedSlider = document.createElement('input');
        speedSlider.type = 'range';
        speedSlider.min = '1';
        speedSlider.max = '3';
        speedSlider.value = '2';
        speedSlider.className = 'fb-cleaner-slider';
        
        speedSlider.addEventListener('input', () => {
            const value = parseInt(speedSlider.value);
            const labels = ['Careful', 'Normal', 'Speedy'];
            speedValue.textContent = labels[value - 1];
            
            // Adjust delays based on speed setting
            const multiplier = value === 1 ? 1.5 : value === 2 ? 1 : 0.6;
            CONFIG.menuClickDelay = 300 * multiplier;
            CONFIG.deleteClickDelay = 300 * multiplier;
            CONFIG.betweenItemsDelay = 800 * multiplier;
            CONFIG.errorCheckDelay = 600 * multiplier;
        });
        
        DOM.speedSlider = speedSlider;
        
        speedControl.appendChild(speedLabel);
        speedControl.appendChild(speedSlider);
        
        // Assemble UI
        body.appendChild(status);
        body.appendChild(stats);
        body.appendChild(toggleBtn);
        body.appendChild(resetBtn);
        body.appendChild(speedControl);
        
        container.appendChild(header);
        container.appendChild(body);
        document.body.appendChild(container);
        
        // Initialize stats display
        updateStats();
        
        // Make the UI draggable (simple implementation)
        let isDragging = false;
        let offsetX, offsetY;
        
        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - container.getBoundingClientRect().left;
            offsetY = e.clientY - container.getBoundingClientRect().top;
        });
        
        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            
            container.style.left = (e.clientX - offsetX) + 'px';
            container.style.top = (e.clientY - offsetY) + 'px';
            container.style.right = 'auto';
            container.style.bottom = 'auto';
        });
        
        document.addEventListener('mouseup', () => {
            isDragging = false;
        });
    }

    // ===== CORE FUNCTIONALITY =====
    function toggleRunning() {
        STATE.isRunning = !STATE.isRunning;
        
        if (STATE.isRunning) {
            DOM.toggleButton.textContent = '⏸️ Pause Cleaning';
            DOM.toggleButton.className = 'fb-cleaner-button pause';
            updateStatus('Running...', 'normal');
            deleteNext();
        } else {
            DOM.toggleButton.textContent = '▶️ Start Cleaning';
            DOM.toggleButton.className = 'fb-cleaner-button start';
            updateStatus('Paused', 'warning');
        }
    }

    function getItemSignature(element) {
        if (!element) return null;
        
        // Find the containing item
        let container = element.closest('[data-visualcompletion="ignore-dynamic"]') || 
                       element.closest('div[role="article"]') ||
                       element.closest('div[data-pagelet*="Feed"]');
        
        if (!container) {
            // Fallback to parent element
            container = element.parentElement;
            if (!container) return null;
        }
        
        // Get text content
        let textContent = '';
        try {
            // Try to get first 50 chars of text content
            textContent = container.innerText.replace(/\s+/g, ' ').trim().substring(0, 50);
        } catch (e) {
            // Fallback if innerText fails
            textContent = (container.textContent || '').replace(/\s+/g, ' ').trim().substring(0, 50);
        }
        
        // Try to find unique identifiers like timestamps
        const timestamp = container.querySelector('abbr[data-utime]');
        const timeValue = timestamp ? timestamp.getAttribute('data-utime') : '';
        
        // Use position or size information to add uniqueness
        const rect = container.getBoundingClientRect();
        const posInfo = `${Math.round(rect.width)}_${Math.round(rect.height)}`;
        
        return `${timeValue}:${textContent}:${posInfo}`;
    }

    function findMenuButtons() {
        return Array.from(document.querySelectorAll('[role="button"]')).filter(btn => {
            const label = btn.getAttribute('aria-label') || '';
            return (
                btn.offsetParent !== null &&
                (label.toLowerCase().includes("activity options") ||
                 label.toLowerCase().includes("action options"))
            );
        });
    }

    function checkForErrorPopups() {
        // Check for any error popups or notifications
        for (const errorText of CONFIG.errorTexts) {
            const containsError = Array.from(document.querySelectorAll('body *')).some(
                el => el.offsetParent !== null && // is visible
                      el.innerText && 
                      el.innerText.includes(errorText)
            );
            
            if (containsError) return true;
        }
        
        // Additional check for specific Facebook error elements
        const errorElements = document.querySelectorAll('[role="alert"], [role="status"]');
        for (const el of errorElements) {
            if (el.offsetParent !== null && CONFIG.errorTexts.some(txt => el.innerText.includes(txt))) {
                return true;
            }
        }
        
        return false;
    }

    function closeAllErrorPopups() {
        // Find all close buttons in error popups
        const errorPopups = Array.from(document.querySelectorAll('[role="alert"], [role="dialog"], [role="status"]')).filter(
            popup => CONFIG.errorTexts.some(txt => popup.innerText && popup.innerText.includes(txt))
        );
        
        let closed = false;
        
        errorPopups.forEach(popup => {
            // Look for close buttons
            const closeBtn = popup.querySelector('[aria-label="Close"], [aria-label="Dismiss"]') || 
                           popup.querySelector('div[role="button"]') ||
                           popup.querySelector('button');
            
            if (closeBtn) {
                closeBtn.click();
                closed = true;
                log("Closed error popup", "info");
            }
        });
        
        return closed;
    }

    function markCurrentItemAsProblem() {
        if (!STATE.currentItem) return;
        
        const signature = getItemSignature(STATE.currentItem);
        if (!signature) return;
        
        // Get current count or default to 0
        const currentCount = STATE.problemItems.get(signature) || 0;
        STATE.problemItems.set(signature, currentCount + 1);
        
        // If this is a permanent skip, increment skip counter
        if (currentCount + 1 >= CONFIG.maxRetries) {
            STATE.skipCount++;
            log(`Permanently skipping item: "${signature.substring(0, 30)}..."`, "skip");
        } else {
            log(`Marking item for retry: "${signature.substring(0, 30)}..."`, "warn");
        }
    }

    function shouldSkipItem(btn) {
        const signature = getItemSignature(btn);
        if (!signature) return false;
        
        const failCount = STATE.problemItems.get(signature) || 0;
        return failCount >= CONFIG.maxRetries;
    }

    function autoConfirmPopups() {
        const dialogs = Array.from(document.querySelectorAll('[role="dialog"], [role="alertdialog"]'));
        
        dialogs.forEach(dialog => {
            // Find the delete button
            const deleteBtn = Array.from(dialog.querySelectorAll('div[role="button"], button'))
                .find(btn => 
                    btn.offsetParent !== null && 
                    (btn.innerText.trim().toLowerCase() === "delete" || 
                     btn.innerText.trim().toLowerCase() === "delete post" ||
                     btn.innerText.trim().toLowerCase() === "remove")
                );
            
            if (deleteBtn) {
                deleteBtn.click();
                log("Auto-confirmed deletion dialog", "success");
            }
        });
    }

    function autoScrollAndRetry() {
        log("Scrolling to load more content", "scroll");
        updateStatus("Scrolling to find more items...", "normal");
        
        // Scroll down
        window.scrollTo({
            top: document.body.scrollHeight,
            behavior: 'smooth'
        });
        
        // Wait and then try again
        setTimeout(deleteNext, CONFIG.scrollDelay);
    }

    function deleteNext() {
        if (!STATE.isRunning) return;
        
        const startTime = performance.now();
        STATE.lastActionTime = startTime;
        
        // Reset current item
        STATE.currentItem = null;
        
        // Clean up any dialogs and popups
        closeAllErrorPopups();
        autoConfirmPopups();
        
        // Find menu buttons
        const buttons = findMenuButtons();
        
        if (buttons.length === 0) {
            updateStatus("No items found, scrolling...", "warning");
            autoScrollAndRetry();
            return;
        }
        
        // Filter out buttons we should skip
        const validButtons = buttons.filter(btn => !shouldSkipItem(btn));
        
        if (validButtons.length === 0) {
            updateStatus("Only skippable items found, scrolling...", "warning");
            autoScrollAndRetry();
            return;
        }
        
        // Get the first valid button
        const btn = validButtons[0];
        STATE.currentItem = btn;
        
        // Scroll to the button and click it
        btn.scrollIntoView({ block: "center", behavior: "instant" });
        
        setTimeout(() => {
            // Click the menu button
            btn.click();
            log(`Opened menu for item`, "info");
            updateStatus("Opened menu...", "normal");
            
            setTimeout(() => {
                // Find and click the delete option
                const menuItems = Array.from(document.querySelectorAll('[role="menuitem"]'));
                const deleteOption = menuItems.find(el =>
                    el.innerText.includes("Move to Recycle bin") ||
                    el.innerText.includes("Delete") ||
                    el.innerText.includes("Remove") ||
                    el.innerText.includes("Unlike") ||
                    el.innerText.includes("Remove reaction") ||
                    el.innerText.includes("Remove tag")
                );
                
                if (deleteOption) {
                    deleteOption.click();
                    log(`Clicked delete option`, "info");
                    updateStatus("Deleting...", "normal");
                    
                    // Check for errors after a short delay
                    setTimeout(() => {
                        if (checkForErrorPopups()) {
                            // Error detected
                            log("Error detected during deletion", "error");
                            updateStatus("Error detected, skipping item", "error");
                            markCurrentItemAsProblem();
                            closeAllErrorPopups();
                            
                            // Record processing time
                            const endTime = performance.now();
                            STATE.processingTime.push(endTime - startTime);
                            
                            // Move to next item
                            setTimeout(deleteNext, getRandomDelay());
                        } else {
                            // Success!
                            STATE.deletionCount++;
                            log(`Successfully deleted item #${STATE.deletionCount}`, "success");
                            updateStatus(`Deleted item #${STATE.deletionCount}`, "success");
                            
                            // Record processing time
                            const endTime = performance.now();
                            STATE.processingTime.push(endTime - startTime);
                            
                            // Keep only the last 10 times for the average
                            if (STATE.processingTime.length > 10) {
                                STATE.processingTime.shift();
                            }
                            
                            // Move to next item
                            setTimeout(deleteNext, getRandomDelay());
                        }
                    }, CONFIG.errorCheckDelay);
                    
                } else {
                    // No delete option found
                    log("No delete option found", "warn");
                    updateStatus("No delete option found, skipping", "warning");
                    markCurrentItemAsProblem();
                    
                    // Click elsewhere to close the menu
                    document.body.click();
                    
                    // Record processing time
                    const endTime = performance.now();
                    STATE.processingTime.push(endTime - startTime);
                    
                    // Move to next item
                    setTimeout(deleteNext, getRandomDelay());
                }
            }, CONFIG.menuClickDelay);
        }, 100); // Very short delay after scrolling
    }

    // ===== INITIALIZATION =====
    function initialize() {
        log("Facebook Activity Auto Deleter v5.02 loaded", "info");
        createUI();
        
        // Start periodic update of the UI
        setInterval(updateStats, CONFIG.uiUpdateInterval);
        
        // Set up periodic error popup checker
        setInterval(() => {
            if (STATE.isRunning) {
                closeAllErrorPopups();
                autoConfirmPopups();
            }
        }, 1000);
    }
    
    // Start the script
    initialize();
})();