Created
November 27, 2024 04:03
-
-
Save thomasantony/b27c86e62d27f6517b5f5bf9017103ca to your computer and use it in GitHub Desktop.
TamperMonkey Script to Remove and Block X users
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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