I remember using twitter years back then and needed people to follow my account. I used some free websites where you get points from following people so you can list your profile with the points and get followers back. It worked but was slow. That was when I wrote a javascript script to mass follow with the hope of getting some to follow back. I look at an account followers list and run the script to follow them.
It was fun and it worked. I got rate limited, suspended few minutes subject to reverification by twitter but overall I accomplished my goal.
The script works by manipulating the DOM (webpage elements).
I encountered something similar yesterday on X(Formerly Twitter). I needed to delete all tweets, retweets of an X account. I initially tried to use apps like circleboom and tweethunter but they only have paid version for everything I wanted. I tried to load my debit card, but it didn’t work. That’s one of the disadvantage of being a Nigerian, we get locked out on most platforms and are limited to few like spotify, apple music, Netflix and starlink.
I decided to do it manually. I have around 305 posts to delete. It was slow and stressful manually. It occurred to me that I can actually write a script that will do the work faster.
The script automates the manual process of deleting tweets by simulating user clicks on X web interface. It's meant to be run directly in the browser's developer console.
It works by interacting with the DOM (webpage elements) rather than using X API
Setup
In order to use this script, visit your profile on https://x.com/username
Open the developer tools on web browser and click the console tab
The code and how it works
/**
Twitter Mass Delete
*/
(async function() {
'use strict';
console.log(`Twitter Mass deleter`);
// Configuration
const CONFIG = {
DELAY_BETWEEN_ACTIONS: 2000,
DELAY_AFTER_CLICK: 500,
MAX_BATCHES: 50,
SCROLL_DELAY: 2000
};
// Utility function for delays
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
// Statistics
let stats = {
deleted: 0,
errors: 0,
startTime: Date.now()
};
// Main deletion function
async function deleteTweetsAndReplies() {
console.log('Starting deletion process...');
console.log('This will take time. Please be patient.\n');
for (let batch = 0; batch < CONFIG.MAX_BATCHES; batch++) {
console.log(`Processing batch ${batch + 1}/${CONFIG.MAX_BATCHES}...`);
// Find all "More" buttons (three dots menu)
const moreButtons = document.querySelectorAll('[aria-label="More"]');
if (moreButtons.length === 0) {
console.log('No more tweets visible on this page.');
console.log(' Reload the page and run the script again to continue.');
break;
}
console.log(` Found ${moreButtons.length} tweets in this batch.`);
// Process each tweet
for (let i = 0; i < moreButtons.length; i++) {
const button = moreButtons[i];
try {
// Click the "More" menu
button.click();
await delay(CONFIG.DELAY_AFTER_CLICK);
// Find and click "Delete" option
const menuItems = document.querySelectorAll('[role="menuitem"]');
let deleteClicked = false;
for (let item of menuItems) {
const text = item.textContent || item.innerText;
if (text && text.includes('Delete')) {
item.click();
deleteClicked = true;
await delay(CONFIG.DELAY_AFTER_CLICK);
break;
}
}
if (!deleteClicked) {
console.log('Delete option not found, skipping...');
continue;
}
// Confirm deletion
const confirmButton = document.querySelector('[data-testid="confirmationSheetConfirm"]');
if (confirmButton) {
confirmButton.click();
stats.deleted++;
console.log(` Deleted tweet #${stats.deleted}`);
await delay(CONFIG.DELAY_BETWEEN_ACTIONS);
} else {
console.log('Confirmation button not found');
}
// Close any remaining menus
const closeButton = document.querySelector('[aria-label="Close"]');
if (closeButton) {
closeButton.click();
await delay(200);
}
} catch (error) {
stats.errors++;
console.log(`Error: ${error.message}`);
}
}
// Scroll to load more tweets
console.log('Scrolling to load more tweets...');
window.scrollTo(0, document.body.scrollHeight);
await delay(CONFIG.SCROLL_DELAY);
}
// Print final statistics
printStats();
}
// Print statistics
function printStats() {
const elapsed = Math.round((Date.now() - stats.startTime) / 1000);
const minutes = Math.floor(elapsed / 60);
const seconds = elapsed % 60;
console.log(`BATCH COMPLETE!
Statistics:
Deleted: ${stats.deleted} tweets
Errors: ${stats.errors}
Time: ${minutes}m ${seconds}s
Next Steps:
1. Reload the page (F5 or Ctrl+R)
2. Run this script again
3. Repeat until no more tweets remain
Note: Twitter only loads ~20-50 tweets at a time.
For large accounts, you may need to run this 20-50+ times.
`);
}
// Start the deletion process
await deleteTweetsAndReplies();
})();
How it works
(async function() {
'use strict';
console.log(`Twitter Mass deleter`);
The script is an asynchronous immediately-invoked function expression (IIFE) that runs a series of functions to simulate clicks and delete tweets. The function runs immediately without it being called and the enabled strict mode let the script catch common javascript errors.
The console.log shows what the script is about
const CONFIG = {
DELAY_BETWEEN_ACTIONS: 2000,
DELAY_AFTER_CLICK: 500,
MAX_BATCHES: 50,
SCROLL_DELAY: 2000
};
- DELAY_BETWEEN_ACTIONS: 2000 milliseconds ( 2 seconds) the script delays for 2seconds between tweet deletion
- DELAY_AFTER_CLICK: 500 milliseconds (0.5 seconds) the script delays for 0.5s on every click.
- MAX_BATCHES: It loads up 50 tweets and work on it at a time. That way, it falls within X range which is 20-50 tweets that can be loaded at a time.
- SCROLL_DELAY: 2000 milliseconds (2 seconds) It delays for 2seconds after scrolling to allow new tweet load.
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
It ensures that the script doesn’t click too fast so the system doesn’t think it is a bot and flag the account.
let stats = {
deleted: 0,
errors: 0,
startTime: Date.now()
};
This is an object to record the number of tweets deleted, the number of errors encountered, and the when the process started to know the duration.
async function deleteTweetsAndReplies() {
console.log('Starting deletion process...');
console.log('This will take time. Please be patient.\n');
This starts the deletion process and also logs the information in the console
for (let batch = 0; batch < CONFIG.MAX_BATCHES; batch++) {
console.log(`Processing batch ${batch + 1}/${CONFIG.MAX_BATCHES}...`);
This is the first loop and it iterates through the batches 50 times to find a tweet, then deletes that tweet and scrolls to load more tweets.
const moreButtons = document.querySelectorAll('[aria-label="More"]');
if (moreButtons.length === 0) {
console.log('No more tweets visible on this page.');
console.log(' Reload the page and run the script again to continue.');
break;
}
We work with the DOM here by selects all tweet with the three-dot menu buttons visible on the page. This is how the script knows it is one tweet. If it doesn’t see one, the script stops with the messages in the console and advises you to reload.
console.log(` Found ${moreButtons.length} tweets in this batch.`);
Process each tweet
for (let i = 0; i < moreButtons.length; i++) {
const button = moreButtons[i];
try {
// Click the "More" menu
button.click();
await delay(CONFIG.DELAY_AFTER_CLICK);
However, if another tweet is seen, it is clicked and the 0.5s delay is implemented.
const menuItems = document.querySelectorAll('[role="menuitem"]');
let deleteClicked = false;
for (let item of menuItems) {
const text = item.textContent || item.innerText;
if (text && text.includes('Delete')) {
item.click();
deleteClicked = true;
await delay(CONFIG.DELAY_AFTER_CLICK);
break;
}
}
if (!deleteClicked) {
console.log('Delete option not found, skipping...');
continue;
}
This code opens all menu items on the tweet and search for the word Delete, then it clicks it and marks it as deleteClicked. If there is no delete option, it simply skips the tweet and also logs it in the console.
const confirmButton = document.querySelector('[data-testid="confirmationSheetConfirm"]');
if (confirmButton) {
confirmButton.click();
stats.deleted++;
console.log(` Deleted tweet #${stats.deleted}`);
await delay(CONFIG.DELAY_BETWEEN_ACTIONS);
} else {
console.log('Confirmation button not found');
}
// Close any remaining menus
const closeButton = document.querySelector('[aria-label="Close"]');
if (closeButton) {
closeButton.click();
await delay(200);
}
This finds the delete in the popup and clicks it, then update the deleted counter and implements the 2seconds delay.
Then the script tries to close any open menus before continuing operation after 0.2seconds
} catch (error) {
stats.errors++;
console.log(` Error: ${error.message}`);
}
This is to catch any error during the process.
console.log(' Scrolling to load more tweets...');
window.scrollTo(0, document.body.scrollHeight);
await delay(CONFIG.SCROLL_DELAY);
After finishing a batch, it scrolls to the bottom of the page to load the next set of older tweets. It delays for 2seconds to allow the new posts to load before the next batch processing starts.
// Print final statistics
printStats();
}
// Print statistics
function printStats() {
const elapsed = Math.round((Date.now() - stats.startTime) / 1000);
const minutes = Math.floor(elapsed / 60);
const seconds = elapsed % 60;
console.log(`BATCH COMPLETE!
Statistics:
Deleted: ${stats.deleted} tweets
Errors: ${stats.errors}
Time: ${minutes}m ${seconds}s
Next Steps:
1. Reload the page (F5 or Ctrl+R)
2. Run this script again
3. Repeat until no more tweets remain
Note: Twitter only loads ~20-50 tweets at a time.
For large accounts, you may need to run this 20-50+ times.
`);
}
Once all the batches are processed with no tweet found, this printStats function will calculate the duration and number of tweets deleted with the errors encountered. It will also log the instructions for the next step in the console
The code works but it has some limitations in the future. It relies on X current website structure that might change in the future. It requires you to reload many times if you have a lot of tweet to delete and you have to be careful not to be rate limited by X.
Try it out and let me know what you think.