Clean Up Your X For You Feed
- x twitter javascript automation
At some point the For You tab on X (formerly Twitter) decided I needed nonstop updates about topics I never cared about. The culprit lives under Settings → Privacy and safety → Interests, where the platform keeps silently re-checking categories based on past engagement. Manually unchecking hundreds of boxes is tedious, and in my experience the rate limits tend to be aggressive.
To reclaim a clean feed without playing whack-a-mole, I put together a conservative automation helper. It is intentionally slow and backs off when it sees any non-200 response. You can grab the latest copy as a gist: Twitter Interests Bulk Unchecker (Rate-Limit Safe Script).
How to use it
- Open
https://x.com/settings/your_twitter_data/twitter_interestsin your browser. - Open the developer console.
- Paste the script below and press Enter.
- Leave the tab focused. The script will log progress and pause when it hits errors.
The script is also available at the gist link above. Here’s the full code:
/**
* X (formerly Twitter) Interests Bulk Unchecking Tool
*
* Purpose: Automatically unchecks interests on X's settings page
* URL: https://x.com/settings/your_twitter_data/twitter_interests
*
* Based on: https://gist.github.com/TheSethRose/63d4cd0fd3b9fe33c3e51e83f87da26f
*
* Usage: Run this script in the browser console while on the X interests page.
* The script will automatically uncheck all selected interests with rate limiting protection.
*
* Note: Initial delay set to 15 seconds per uncheck (as of 1 November 2025) because
* X's rate limiting was found to be extremely aggressive. This conservative
* approach prioritizes reliability over speed.
*/
const section = document.querySelector('section:nth-child(2)');
const checkboxes = section
? section.querySelectorAll('input[type="checkbox"]')
: [];
// Configuration
const CONFIG = {
initialDelay: 15000, // 15 seconds - conservative delay due to aggressive rate limiting (Nov 2025)
maxDelay: 600000,
backoffMultiplier: 2,
maxRetries: 5,
errorCooldown: 120000,
consecutiveErrorLimit: 2
};
let uncheckedCount = 0;
let skippedCount = 0;
let errorCount = 0;
let consecutiveErrors = 0;
let currentDelay = CONFIG.initialDelay;
if (!section) {
console.error('[ERROR] Unable to locate the interests section. Nothing to uncheck.');
}
// Intercepts ALL fetch/XHR requests to detect rate limits and errors
// This allows the script to react to ANY non-200 response, not just explicit errors
const setupRequestMonitor = () => {
const requestStatus = {
lastError: null,
errorTime: null,
lastStatus: null
};
const originalFetch = window.fetch;
window.fetch = async (...args) => {
try {
const response = await originalFetch(...args);
if (response.status !== 200) {
requestStatus.lastError = `HTTP_${response.status}`;
requestStatus.lastStatus = response.status;
requestStatus.errorTime = Date.now();
if (response.status === 429) {
console.warn('[WARN] Rate limit detected (HTTP 429)');
} else if (response.status >= 500) {
console.warn(`[WARN] Server error detected (HTTP ${response.status})`);
} else if (response.status >= 400) {
console.warn(`[WARN] Client error detected (HTTP ${response.status})`);
} else {
console.warn(`[WARN] Non-200 response detected (HTTP ${response.status})`);
}
} else {
requestStatus.lastError = null;
requestStatus.lastStatus = 200;
consecutiveErrors = 0;
// Gradually reduce delay on success (adaptive timing)
currentDelay = Math.max(CONFIG.initialDelay, currentDelay * 0.9);
}
return response;
} catch (error) {
requestStatus.lastError = 'NETWORK_ERROR';
requestStatus.errorTime = Date.now();
console.error('[ERROR] Network error detected:', error.message);
throw error;
}
};
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (...args) {
this._url = args[1];
return originalXHROpen.apply(this, args);
};
XMLHttpRequest.prototype.send = function (...args) {
this.addEventListener('load', function () {
if (this.status !== 200) {
requestStatus.lastError = `HTTP_${this.status}`;
requestStatus.lastStatus = this.status;
requestStatus.errorTime = Date.now();
console.warn(`[WARN] XHR non-200 response: ${this.status}`);
} else {
requestStatus.lastError = null;
requestStatus.lastStatus = 200;
consecutiveErrors = 0;
currentDelay = Math.max(CONFIG.initialDelay, currentDelay * 0.9);
}
});
this.addEventListener('error', function () {
requestStatus.lastError = 'NETWORK_ERROR';
requestStatus.errorTime = Date.now();
console.error('[ERROR] XHR network error');
});
return originalXHRSend.apply(this, args);
};
return requestStatus;
};
// Exponential backoff with jitter to avoid thundering herd
const calculateBackoff = (attempt) => {
const exponentialDelay = Math.min(
currentDelay * Math.pow(CONFIG.backoffMultiplier, attempt),
CONFIG.maxDelay
);
const jitter = exponentialDelay * 0.15 * (Math.random() - 0.5);
return Math.floor(exponentialDelay + jitter);
};
const formatTime = (ms) => {
const minutes = Math.floor(ms / 60000);
const seconds = Math.floor((ms % 60000) / 1000);
if (minutes > 0) {
return `${minutes}m ${seconds}s`;
}
return `${seconds}s`;
};
// Countdown timer for long waits to show progress
const smartDelay = async (ms, reason = '') => {
const formatted = formatTime(ms);
if (reason) {
console.log(`[INFO] Waiting ${formatted} ${reason}...`);
}
if (ms > 30000) {
const supportsStdout =
typeof process !== 'undefined' &&
process.stdout &&
typeof process.stdout.write === 'function';
const startTime = Date.now();
const interval = setInterval(() => {
const elapsed = Date.now() - startTime;
const remaining = ms - elapsed;
if (remaining <= 0) {
return;
}
const message = `[INFO] Time remaining: ${formatTime(remaining)} `;
if (supportsStdout) {
process.stdout.write(`\r${message}`);
} else {
console.log(message);
}
}, 5000);
await new Promise(resolve => setTimeout(resolve, ms));
clearInterval(interval);
if (supportsStdout) {
process.stdout.write('\r' + ' '.repeat(50) + '\r');
}
} else {
await new Promise(resolve => setTimeout(resolve, ms));
}
};
const uncheckWithRetry = async (checkbox, index, requestMonitor) => {
const label = checkbox.closest('label');
const nameElement = label?.querySelector('[dir="ltr"]');
const name = nameElement?.textContent || `Interest ${index + 1}`;
for (let attempt = 0; attempt < CONFIG.maxRetries; attempt++) {
try {
// Extended cooldown after multiple consecutive errors
if (consecutiveErrors >= CONFIG.consecutiveErrorLimit) {
console.warn(`[WARN] ${consecutiveErrors} consecutive errors detected. Extended cooldown period...`);
await smartDelay(CONFIG.errorCooldown, '(error cooldown)');
consecutiveErrors = 0;
}
// Check if a recent error occurred (within 3 seconds)
if (
requestMonitor.lastError &&
Date.now() - requestMonitor.errorTime < 3000
) {
consecutiveErrors++;
const backoffTime = calculateBackoff(attempt + 1);
currentDelay = backoffTime;
console.error(`[ERROR] Non-200 response detected: ${requestMonitor.lastError}`);
await smartDelay(backoffTime, `(backoff after ${requestMonitor.lastError})`);
}
checkbox.click();
// Wait for request to complete
await smartDelay(2000);
// Verify no error occurred during the click operation
if (
requestMonitor.lastError &&
Date.now() - requestMonitor.errorTime < 3000
) {
throw new Error(`${requestMonitor.lastError} (Status: ${requestMonitor.lastStatus})`);
}
uncheckedCount++;
console.log(`[SUCCESS] [${uncheckedCount}] ${name}`);
return true;
} catch (error) {
errorCount++;
consecutiveErrors++;
console.error(`[ERROR] Error on "${name}" (attempt ${attempt + 1}/${CONFIG.maxRetries}): ${error.message}`);
if (attempt < CONFIG.maxRetries - 1) {
const retryDelay = calculateBackoff(attempt + 1);
await smartDelay(retryDelay, `(retry ${attempt + 2}/${CONFIG.maxRetries})`);
}
}
}
skippedCount++;
console.error(`[SKIP] Skipped "${name}" after ${CONFIG.maxRetries} failed attempts`);
return false;
};
const uncheck = async () => {
const checkedBoxes = Array.from(checkboxes).filter(cb => cb.checked);
const totalChecked = checkedBoxes.length;
if (!section) {
console.log('[INFO] No section found. Aborting.');
return;
}
if (totalChecked === 0) {
console.log('[INFO] No checked interests found. Nothing to uncheck!');
return;
}
console.log('[INFO] Starting conservative bulk uncheck process...');
console.log(`[INFO] Total interests found: ${checkboxes.length}`);
console.log(`[INFO] Currently checked: ${totalChecked}`);
console.log('[INFO] Configuration:');
console.log(`[INFO] Base delay: ${formatTime(CONFIG.initialDelay)}`);
console.log(`[INFO] Max backoff: ${formatTime(CONFIG.maxDelay)}`);
console.log(`[INFO] Max retries per item: ${CONFIG.maxRetries}`);
console.log('[INFO] Strategy: ANY non-200 response triggers backoff\n');
const requestMonitor = setupRequestMonitor();
const startTime = Date.now();
let processedCount = 0;
for (let i = 0; i < checkboxes.length; i++) {
const checkbox = checkboxes[i];
if (!checkbox.checked) {
continue;
}
processedCount++;
console.log(`\n[INFO] Processing ${processedCount} of ${totalChecked}...`);
await uncheckWithRetry(checkbox, i, requestMonitor);
if (processedCount < totalChecked) {
const nextDelay = Math.max(CONFIG.initialDelay, currentDelay);
await smartDelay(nextDelay, '(standard delay)');
}
}
const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
console.log(`\n${'='.repeat(60)}`);
console.log('[INFO] PROCESS COMPLETE');
console.log(`${'='.repeat(60)}`);
console.log(`[INFO] Total duration: ${duration} minutes`);
console.log(`[INFO] Checked interests found: ${totalChecked}`);
console.log(`[INFO] Successfully unchecked: ${uncheckedCount}`);
console.log(`[INFO] Skipped (failed): ${skippedCount}`);
console.log(`[INFO] Total errors encountered: ${errorCount}`);
console.log(`[INFO] Final delay setting: ${formatTime(currentDelay)}`);
console.log(`${'='.repeat(60)}`);
};
uncheck();