Skip to content

Instantly share code, notes, and snippets.

@thomasantony
Created November 27, 2024 04:03
Show Gist options
  • Save thomasantony/b27c86e62d27f6517b5f5bf9017103ca to your computer and use it in GitHub Desktop.
Save thomasantony/b27c86e62d27f6517b5f5bf9017103ca to your computer and use it in GitHub Desktop.
TamperMonkey Script to Remove and Block X users
// ==UserScript==
// @name Twitter Remove and Block
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Adds a "Remove and Block" button to Twitter followers page
// @author Your username
// @match https://twitter.com/*/followers
// @match https://x.com/*/followers
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Store users to be removed and blocked
const usersToProcess = new Set();
// Create floating Go button and counter
function createFloatingButton() {
const floatingContainer = document.createElement('div');
floatingContainer.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
display: flex;
gap: 10px;
align-items: center;
background: rgba(0, 0, 0, 0.8);
padding: 10px;
border-radius: 20px;
display: none;
`;
const counter = document.createElement('div');
counter.style.cssText = `
color: white;
font-weight: bold;
font-size: 14px;
`;
counter.textContent = '0 selected';
const goButton = document.createElement('button');
goButton.className = 'css-175oi2r r-sdzlij r-1phboty r-rs99b7 r-1loqt21 r-o7ynqc r-6416eg r-1ny4l3l';
goButton.style.cssText = `
background-color: rgb(244, 33, 46);
border: none;
color: white;
padding: 8px 16px;
border-radius: 20px;
font-weight: bold;
cursor: pointer;
`;
goButton.textContent = 'Process All';
floatingContainer.appendChild(counter);
floatingContainer.appendChild(goButton);
document.body.appendChild(floatingContainer);
return { container: floatingContainer, counter, goButton };
}
const { container: floatingContainer, counter, goButton } = createFloatingButton();
// Update counter and visibility
function updateCounter() {
counter.textContent = `${usersToProcess.size} selected`;
floatingContainer.style.display = usersToProcess.size > 0 ? 'flex' : 'none';
}
// Process a single user
async function processUser(userInfo) {
const { userCell, username } = userInfo;
// Click the more options button
const moreButton = userCell.querySelector('[aria-label="More"]');
moreButton.click();
await new Promise(resolve => setTimeout(resolve, 500));
// Try to remove
const removeButton = Array.from(document.querySelectorAll('span')).find(
span => span.textContent === 'Remove this follower'
)?.closest('div[role="menuitem"]');
if (removeButton) {
removeButton.click();
await new Promise(resolve => setTimeout(resolve, 500));
const confirmRemove = document.querySelector('[data-testid="confirmationSheetConfirm"]');
if (confirmRemove) {
confirmRemove.click();
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// Click more options again for block
moreButton.click();
await new Promise(resolve => setTimeout(resolve, 500));
// Block
const blockButton = Array.from(document.querySelectorAll('span')).find(
span => span.textContent.includes('Block @')
)?.closest('div[role="menuitem"]');
if (blockButton) {
blockButton.click();
await new Promise(resolve => setTimeout(resolve, 500));
const confirmBlock = document.querySelector('[data-testid="confirmationSheetConfirm"]');
if (confirmBlock) {
confirmBlock.click();
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}
// Process all selected users
async function processAllUsers() {
goButton.disabled = true;
goButton.textContent = 'Processing...';
for (const userInfo of usersToProcess) {
await processUser(userInfo);
usersToProcess.delete(userInfo);
updateCounter();
// Add small delay between users
await new Promise(resolve => setTimeout(resolve, 1000));
}
goButton.disabled = false;
goButton.textContent = 'Process All';
}
// Add click handler to Go button
goButton.addEventListener('click', processAllUsers);
// Function to add the Remove and Block button
function addRemoveBlockButton(userCell) {
// Check if button already exists
if (userCell.querySelector('.remove-block-btn')) return;
// Get the more options button container
const moreButtonContainer = userCell.querySelector('[aria-label="More"]').closest('.r-18u37iz');
if (!moreButtonContainer) return;
// Get username
const usernameElement = userCell.querySelector('div[dir="ltr"] span');
const username = usernameElement?.textContent || 'unknown';
// Create new button
const removeBlockBtn = document.createElement('button');
removeBlockBtn.className = 'remove-block-btn css-175oi2r r-sdzlij r-1phboty r-rs99b7 r-lrvibr r-15ysp7h r-4wgw6l r-3pj75a r-1loqt21 r-o7ynqc r-6416eg r-1ny4l3l';
removeBlockBtn.style.backgroundColor = 'rgb(244, 33, 46)';
removeBlockBtn.style.borderColor = 'rgba(0, 0, 0, 0)';
removeBlockBtn.innerHTML = `
<div dir="ltr" class="css-146c3p1 r-bcqeeo r-qvutc0 r-37j5jr r-q4m81j r-a023e6 r-rjixqe r-b88u0q r-1awozwy r-6koalj r-18u37iz r-16y2uox r-1777fci">
<span class="css-1jxf684 r-dnmrzs r-1udh08x r-3s2u2q r-bcqeeo r-1ttztb7 r-qvutc0 r-poiln3 r-1b43r93 r-1cwl3u0" style="color: white;">
Remove & Block
</span>
</div>
`;
// Toggle selection
removeBlockBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const userInfo = { userCell, username };
if (usersToProcess.has(userInfo)) {
usersToProcess.delete(userInfo);
removeBlockBtn.style.backgroundColor = 'rgb(244, 33, 46)';
} else {
usersToProcess.add(userInfo);
removeBlockBtn.style.backgroundColor = 'rgb(120, 16, 23)';
}
updateCounter();
});
// Insert button before the more options button
moreButtonContainer.insertBefore(removeBlockBtn, moreButtonContainer.firstChild);
}
// Observe DOM for new followers being loaded
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
// Find user cells and add buttons
const userCells = node.querySelectorAll('[data-testid="UserCell"]');
userCells.forEach(addRemoveBlockButton);
}
}
}
});
// Start observing
function initObserver() {
const targetNode = document.querySelector('[data-testid="primaryColumn"]');
if (targetNode) {
observer.observe(targetNode, { childList: true, subtree: true });
// Add buttons to existing cells
const userCells = document.querySelectorAll('[data-testid="UserCell"]');
userCells.forEach(addRemoveBlockButton);
} else {
// If target node not found, retry after a short delay
setTimeout(initObserver, 1000);
}
}
// Initialize
initObserver();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment