// ==UserScript==
// @name Facebook Activity Auto Deleter (2025) - Optimized
// @namespace https://greasyfork.org/en/users/1454546-shawnfrost13
// @version 5.03
// @description Fast and efficient Facebook activity log cleaner with improved item detection and progressive 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: 800, // How long to wait to check for errors (increased)
scrollDelay: 1500, // How long to wait after scrolling
// UI
uiUpdateInterval: 500, // How often to update stats display
// Error handling
maxRetries: 3, // Maximum retries before skipping item permanently (increased)
skipIncrement: 1, // How many items to skip when encountering problems (NEW)
maxSkipAhead: 5, // Maximum items to skip ahead at once (NEW)
errorTexts: [ // Text patterns that indicate errors
"Something went wrong",
"Please try again",
"An error occurred",
"We couldn't process",
"Action blocked",
"You're temporarily restricted" // Added this common error
],
// Progressive operation (NEW)
resetSkipCountAfterSuccess: 3, // Reset skip counter after this many successful operations
};
// ===== 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
// Progressive operation (NEW)
consecutiveSuccesses: 0, // Count consecutive successful deletions
currentSkipAhead: 1, // How many items to skip if problems persist
lastSuccessTimestamp: 0, // When was the last successful deletion
};
// ===== 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>Skip Ahead: <span style="color:#55aaff">${STATE.currentSkipAhead}</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.03';
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;
STATE.currentSkipAhead = 1;
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');
}
}
// IMPROVED: More reliable item signature generation
function getItemSignature(element) {
if (!element) return null;
// Find the containing item - search wider for containers
let container = element.closest('[data-visualcompletion="ignore-dynamic"]') ||
element.closest('div[role="article"]') ||
element.closest('div[data-pagelet*="Feed"]') ||
element.closest('[data-testid]') || // Added support for more Facebook selectors
element.parentElement?.parentElement?.parentElement;
if (!container) {
container = element.parentElement;
if (!container) return null;
}
// Get text content - use less text for the signature to reduce false uniqueness
let textContent = '';
try {
// Try to get first 30 chars of cleaned text content (was 50 before)
textContent = container.innerText.replace(/\s+/g, ' ').trim().substring(0, 30);
} catch (e) {
// Fallback if innerText fails
textContent = (container.textContent || '').replace(/\s+/g, ' ').trim().substring(0, 30);
}
// Look for timestamps but don't make them a critical part of the signature
const timestamp = container.querySelector('abbr[data-utime]');
const timeValue = timestamp ? timestamp.getAttribute('data-utime').slice(-5) : ''; // Only use last 5 digits
// Use rough position info
const rect = container.getBoundingClientRect();
const posInfo = `${Math.round(rect.width/10)*10}`; // Round to nearest 10px for less sensitivity
return `${textContent}:${posInfo}:${timeValue}`;
}
// IMPROVED: Better menu button detection
function findMenuButtons() {
// Start with standard buttons
const standardButtons = 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") ||
label.toLowerCase().includes("more options") || // Added more option keywords
label.toLowerCase().includes("menu"))
);
});
// If we found standard buttons, return them
if (standardButtons.length > 0) return standardButtons;
// Fallback to visual detection
return Array.from(document.querySelectorAll('*')).filter(el => {
if (!el.offsetParent) return false; // Must be visible
// Look for elements that resemble menu buttons
const rect = el.getBoundingClientRect();
const isSquarish = Math.abs(rect.width - rect.height) < 5;
const isReasonableSize = rect.width >= 20 && rect.width <= 40;
// Check if it's in a reasonable place in the document
const isInViewport = rect.top >= 0 && rect.top <= window.innerHeight;
// Has some content that might indicate a button
const hasIconContent = el.innerHTML.includes('svg') ||
el.innerHTML.includes('img') ||
el.textContent.includes('...') ||
el.textContent.includes('⋮');
return isSquarish && isReasonableSize && isInViewport && hasIconContent;
});
}
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"], [aria-label="OK"]') ||
popup.querySelector('div[role="button"]') ||
popup.querySelector('button');
if (closeBtn) {
closeBtn.click();
closed = true;
log("Closed error popup", "info");
}
});
return closed;
}
// IMPROVED: Smarter handling of problem items
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");
// Increase the skip ahead count for progressive skipping
STATE.currentSkipAhead = Math.min(
STATE.currentSkipAhead + CONFIG.skipIncrement,
CONFIG.maxSkipAhead
);
// Reset consecutive successes
STATE.consecutiveSuccesses = 0;
} 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() === "delete activity" || // Added variants
btn.innerText.trim().toLowerCase() === "remove" ||
btn.innerText.trim().toLowerCase() === "confirm" ||
btn.innerText.trim().toLowerCase() === "ok")
);
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;
}
// NEW: Get a button based on the current skip-ahead setting
let buttonIndex = 0;
if (STATE.currentSkipAhead > 1 && validButtons.length >= STATE.currentSkipAhead) {
buttonIndex = STATE.currentSkipAhead - 1; // -1 because array is 0-indexed
log(`Skipping ahead ${STATE.currentSkipAhead} items due to previous errors`, "skip");
}
const btn = validButtons[buttonIndex];
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"], [role="button"]'));
const deleteOption = menuItems.find(el =>
el.offsetParent !== null && // Must be visible
(
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") ||
el.innerText.includes("Hide") || // Added more options
el.innerText.includes("Remove from profile")
)
);
if (deleteOption) {
deleteOption.click();
log(`Clicked delete option: "${deleteOption.innerText}"`, "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);
// Reset consecutive successes
STATE.consecutiveSuccesses = 0;
// Move to next item
setTimeout(deleteNext, getRandomDelay());
} else {
// Success!
STATE.deletionCount++;
STATE.consecutiveSuccesses++;
STATE.lastSuccessTimestamp = Date.now();
log(`Successfully deleted item #${STATE.deletionCount}`, "success");
updateStatus(`Deleted item #${STATE.deletionCount}`, "success");
// NEW: Reduce skip-ahead after consecutive successes
if (STATE.consecutiveSuccesses >= CONFIG.resetSkipCountAfterSuccess && STATE.currentSkipAhead > 1) {
STATE.currentSkipAhead = Math.max(1, STATE.currentSkipAhead - 1);
log(`Reduced skip-ahead to ${STATE.currentSkipAhead} after ${STATE.consecutiveSuccesses} successes`, "info");
STATE.consecutiveSuccesses = 0; // Reset the counter
}
// 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 - click elsewhere to close the menu
document.body.click();
// Wait a bit and look for confirmation dialogs that might have appeared
setTimeout(() => {
autoConfirmPopups();
// Still no success, mark as problem
log("No delete option found", "warn");
updateStatus("No delete option found, trying next", "warning");
markCurrentItemAsProblem();
// Record processing time
const endTime = performance.now();
STATE.processingTime.push(endTime - startTime);
// Move to next item
setTimeout(deleteNext, getRandomDelay());
}, 300);
}
}, CONFIG.menuClickDelay);
}, 100); // Very short delay after scrolling
}
// ===== INITIALIZATION =====
function initialize() {
log("Facebook Activity Auto Deleter v5.03 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);
// NEW: Add occasional complete page refresh to fix stuck states
setInterval(() => {
// If running and no successful deletion in 2 minutes, refresh
if (STATE.isRunning &&
STATE.lastSuccessTimestamp > 0 &&
Date.now() - STATE.lastSuccessTimestamp > 120000) {
log("No successful deletions for 2 minutes, refreshing page", "warn");
updateStatus("Stuck detected, refreshing page...", "warning");
// Save state data to sessionStorage
sessionStorage.setItem('fbCleanerState', JSON.stringify({
deletionCount: STATE.deletionCount,
skipCount: STATE.skipCount,
problemItems: Array.from(STATE.problemItems.entries())
}));
// Reload page
setTimeout(() => {
window.location.reload();
}, 1000);
}
}, 30000); // Check every 30 seconds
// Try to restore state from previous session
try {
const savedState = sessionStorage.getItem('fbCleanerState');
if (savedState) {
const parsedState = JSON.parse(savedState);
STATE.deletionCount = parsedState.deletionCount || 0;
STATE.skipCount = parsedState.skipCount || 0;
if (parsedState.problemItems && Array.isArray(parsedState.problemItems)) {
STATE.problemItems = new Map(parsedState.problemItems);
}
log(`Restored state: ${STATE.deletionCount} deleted, ${STATE.skipCount} skipped`, "info");
updateStats();
// Clear the saved state
sessionStorage.removeItem('fbCleanerState');
}
} catch (err) {
console.error("Error restoring state:", err);
}
}
// Start the script
initialize();
})();