Last active
December 30, 2024 14:08
-
-
Save jongan69/3a17740e814f08078c34df5c0b622ab2 to your computer and use it in GitHub Desktop.
Open x.com/following and paste this in the console
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
(async function deleteTweetsWithZeroLikes() { | |
const delay = (min, max) => new Promise((resolve) => setTimeout(resolve, Math.random() * (max - min) + min)); | |
async function deleteTweet(tweetElement) { | |
try { | |
console.log("Attempting to delete a tweet..."); | |
// Click the "More" button (three dots) on the tweet | |
const moreButton = tweetElement.querySelector('[aria-label="More"]'); | |
if (moreButton) { | |
console.log("Found 'More' button. Clicking it..."); | |
moreButton.click(); | |
await delay(1000, 1500); // Wait for the dropdown to appear | |
// Locate the "Delete" button in the dropdown menu | |
const deleteButton = Array.from(document.querySelectorAll('div[role="menuitem"]')).find((item) => { | |
const span = item.querySelector('span'); | |
return span && span.innerText.trim() === "Delete"; | |
}); | |
if (deleteButton) { | |
console.log("Found 'Delete' button. Clicking it..."); | |
deleteButton.click(); | |
await delay(1000, 1500); | |
// Confirm deletion in the dialog | |
const confirmDeleteButton = document.querySelector('button[data-testid="confirmationSheetConfirm"]'); | |
if (confirmDeleteButton) { | |
console.log("Found confirmation button. Confirming deletion..."); | |
confirmDeleteButton.click(); | |
console.log("Tweet successfully deleted."); | |
await delay(1500, 2000); | |
} else { | |
console.warn("Confirmation button not found."); | |
} | |
} else { | |
console.warn("Delete button not found in the dropdown menu."); | |
} | |
} else { | |
console.warn("More button not found on the tweet."); | |
} | |
} catch (error) { | |
console.error("Error deleting tweet:", error); | |
} | |
} | |
async function processTweets() { | |
console.log("Processing tweets on the current screen..."); | |
const tweets = document.querySelectorAll('article'); | |
let deletedCount = 0; | |
for (const tweet of tweets) { | |
try { | |
// Skip the tweet if it's a poll | |
const isPoll = tweet.querySelector('[data-testid="poll"]'); // Check for poll indicator | |
if (isPoll) { | |
console.log("Skipping a poll tweet."); | |
continue; | |
} | |
// Check for the number of likes | |
const likeElement = tweet.querySelector('[data-testid="like"] span'); | |
const likeCount = likeElement ? parseInt(likeElement.innerText.replace(',', '')) || 0 : 0; | |
console.log(`Tweet detected with ${likeCount} likes.`); | |
if (likeCount === 0) { | |
console.log("Tweet has 0 likes. Preparing to delete..."); | |
await deleteTweet(tweet); | |
deletedCount++; | |
} | |
} catch (error) { | |
console.error("Error processing tweet:", error); | |
} | |
await delay(1000, 1500); // Add a small delay to avoid rate-limiting | |
} | |
console.log(`${deletedCount} tweets deleted in this batch.`); | |
return deletedCount > 0; | |
} | |
async function scrollAndDelete() { | |
console.log("Starting the scroll and delete process..."); | |
let previousScrollHeight = 0; | |
while (true) { | |
const actionTaken = await processTweets(); | |
console.log("Scrolling down to load more tweets..."); | |
window.scrollTo(0, document.body.scrollHeight); | |
await delay(2000, 2500); | |
const currentScrollHeight = document.body.scrollHeight; | |
if (currentScrollHeight === previousScrollHeight && !actionTaken) { | |
console.log("No more tweets to process. Exiting..."); | |
break; | |
} | |
console.log("Scroll height updated. Continuing..."); | |
previousScrollHeight = currentScrollHeight; | |
} | |
} | |
try { | |
console.log("Starting the script..."); | |
await scrollAndDelete(); | |
console.log("Script completed successfully."); | |
} catch (error) { | |
console.error("An error occurred during the deletion process:", error); | |
} | |
})(); |
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
(async function unfollowInactiveUsers() { | |
// Define a delay function with random variability | |
const delay = (min, max) => new Promise((resolve) => setTimeout(resolve, Math.random() * (max - min) + min)); | |
// Function to click the "Unfollow" button within the confirmation popup | |
async function confirmUnfollow() { | |
try { | |
// Select the "Unfollow" button in the confirmation dialog | |
const confirmButton = document.querySelector('button[data-testid="confirmationSheetConfirm"]'); | |
if (confirmButton) { | |
confirmButton.click(); | |
console.log("Confirmed unfollow."); | |
await delay(1000, 1500); // Random delay between 1s and 1.5s | |
} else { | |
console.warn("No confirmation dialog found."); | |
} | |
} catch (error) { | |
console.error("Error confirming unfollow:", error); | |
} | |
} | |
// Function to check recent tweets from the user's profile | |
async function checkRecentTweet(userProfileUrl) { | |
const timeoutDuration = 10000; // 10 seconds timeout for profile loading | |
const userProfileWindow = window.open(userProfileUrl, '_blank'); | |
let timeoutReached = false; | |
const timeoutPromise = new Promise((resolve, reject) => { | |
setTimeout(() => { | |
timeoutReached = true; | |
reject(new Error("Profile load timed out")); | |
}, timeoutDuration); | |
}); | |
const loadPromise = new Promise(async (resolve) => { | |
await delay(2000, 2500); // Random delay between 2s and 2.5s for page load | |
try { | |
// Wait for the most recent tweets to load or click Retry if there's an error | |
const interval = setInterval(() => { | |
const retryButton = userProfileWindow.document.querySelector('button[role="button"]'); | |
const tweetDateElements = userProfileWindow.document.querySelectorAll('time'); | |
const handleElement = userProfileWindow.document.querySelector('div[data-testid="UserProfileHeader_Items"] a'); | |
const messagesTab = userProfileWindow.document.querySelector('[aria-label="Messages"]'); // Target messages tab | |
// Check if the messages tab is selected first and unselect it | |
if (messagesTab && messagesTab.getAttribute('aria-selected') === 'true') { | |
messagesTab.click(); // Unselect the Messages tab if it is selected | |
console.log("Unselected the Messages tab."); | |
} | |
if (tweetDateElements.length > 1 && handleElement && !messagesTab) { | |
clearInterval(interval); | |
resolve(true); | |
} | |
// Check if the Retry button contains the "Retry" text and click it if found | |
if (retryButton) { | |
const retryText = retryButton.innerText || retryButton.textContent; | |
if (retryText.includes("Retry")) { | |
retryButton.click(); | |
console.log("Retrying to load tweets..."); | |
} | |
} | |
}, 1000); // Check every 1 second for tweets to load | |
} catch (error) { | |
console.error("Error during profile load:", error); | |
resolve(false); | |
} | |
}); | |
try { | |
await Promise.race([timeoutPromise, loadPromise]); | |
// Now that tweets are loaded, check the most recent non-pinned tweet | |
const tweetDateElements = userProfileWindow.document.querySelectorAll('time'); | |
const handleElement = userProfileWindow.document.querySelector('div[data-testid="UserProfileHeader_Items"] a'); | |
const pinnedTweetIndicator = userProfileWindow.document.querySelector('div[data-testid="socialContext"]'); // Look for "Pinned" label | |
if (tweetDateElements && handleElement) { | |
// If the "Pinned" label is found, skip the pinned tweet and find the second most recent tweet | |
let recentTweetDate = null; | |
let recentTweet = null; | |
// Loop through tweets and skip the pinned tweet | |
let tweetIndex = 0; | |
for (const tweetDateElement of tweetDateElements) { | |
const tweetDate = new Date(tweetDateElement.getAttribute('datetime')); | |
const tweetElement = tweetDateElement.closest('article'); | |
// Check if the tweet is pinned by looking for the "Pinned" label | |
const isPinned = pinnedTweetIndicator && pinnedTweetIndicator.innerText.includes("Pinned"); | |
if (!isPinned || tweetIndex > 0) { // Skip the pinned tweet and start checking from the second most recent tweet | |
recentTweetDate = tweetDate; | |
recentTweet = tweetElement; | |
break; // Found a valid tweet, break the loop | |
} | |
tweetIndex++; | |
} | |
if (recentTweet && recentTweetDate) { | |
const currentDate = new Date(); | |
const oneYearAgo = currentDate.setFullYear(currentDate.getFullYear() - 1); | |
// Log the user's handle and the date of the most recent non-pinned tweet | |
const handle = handleElement.innerText; | |
console.log(`User @${handle} last tweeted at ${recentTweetDate.toLocaleString()}`); | |
// If the tweet date is older than a year, return true (indicating the user is inactive) | |
return recentTweetDate.getTime() < oneYearAgo; | |
} | |
} | |
return false; // If no valid tweets are found | |
} catch (error) { | |
console.error("Error checking tweet date or profile load timeout:", error); | |
return false; | |
} finally { | |
// Ensure the profile tab is closed after the check | |
if (userProfileWindow) { | |
userProfileWindow.close(); | |
} | |
} | |
} | |
// Function to perform the unfollow process | |
async function performUnfollow() { | |
console.log("Scanning for users to unfollow..."); | |
const userCells = document.querySelectorAll('[data-testid="UserCell"]'); | |
let unfollowCount = 0; | |
for (let userCell of userCells) { | |
const followBadge = userCell.querySelector('[data-testid="userFollowIndicator"]'); | |
const unfollowButton = userCell.querySelector('button[data-testid$="-unfollow"]'); | |
const userProfileUrl = userCell.querySelector('a')?.href; // Get the user profile URL | |
// Check if the user is not following back and has tweeted in the last year | |
if (unfollowButton && (!followBadge || followBadge.innerText.trim() !== "Follows you")) { | |
try { | |
// Check if the user is inactive (tweeted over a year ago) | |
const isInactive = await checkRecentTweet(userProfileUrl); | |
if (isInactive) { | |
console.log("User is inactive. Proceeding to unfollow."); | |
// Click the "Unfollow" button to trigger the popup | |
unfollowButton.click(); | |
console.log("Clicked unfollow for an inactive user."); | |
// Wait for the confirmation dialog to appear and confirm unfollow | |
await delay(500, 1000); // Random delay between 0.5s and 1s | |
await confirmUnfollow(); | |
unfollowCount++; | |
} | |
} catch (error) { | |
console.error("Error checking user activity:", error); | |
} | |
// Add a delay between actions to avoid rate-limiting | |
await delay(1500, 2000); // Random delay between 1.5s and 2s | |
} | |
} | |
console.log(`Unfollowed ${unfollowCount} users in this batch.`); | |
return unfollowCount > 0; | |
} | |
// Continuously scroll and unfollow until no more users are found | |
async function scrollAndUnfollow() { | |
let previousScrollHeight = 0; | |
while (true) { | |
const actionTaken = await performUnfollow(); // Unfollow visible users | |
// Scroll down to load more users | |
window.scrollTo(0, document.body.scrollHeight); | |
await delay(2000, 2500); // Random delay between 2s and 2.5s for scrolling | |
// Check if scrolling brought new users | |
const currentScrollHeight = document.body.scrollHeight; | |
if (currentScrollHeight === previousScrollHeight && !actionTaken) { | |
console.log("No more users to unfollow."); | |
break; // Exit loop if no more new users are loaded | |
} | |
previousScrollHeight = currentScrollHeight; | |
} | |
} | |
// Start the unfollowing process | |
try { | |
await scrollAndUnfollow(); | |
} catch (error) { | |
console.error("An error occurred during the unfollowing process:", error); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment