Skip to content

Instantly share code, notes, and snippets.

@jongan69
Last active December 30, 2024 14:08
Show Gist options
  • Save jongan69/3a17740e814f08078c34df5c0b622ab2 to your computer and use it in GitHub Desktop.
Save jongan69/3a17740e814f08078c34df5c0b622ab2 to your computer and use it in GitHub Desktop.
Open x.com/following and paste this in the console
(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);
}
})();
(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