Deleting tweets with JS console
Asked Answered
V

9

9

I am trying to delete tweets in the JS console on my Twitter page. If I do it manually by selecting the button and then doing a click, I can manually run 3 different functions.

const delButton=()=>{
    document.querySelectorAll('[data-testid="caret"]')[0].click()
};
const clickDel=()=>{
    document.querySelector("#layers > div.css-1dbjc4n.r-1d2f490.r-105ug2t.r-u8s1d.r-zchlnj.r-ipm5af > div > div > div > div:nth-child(2) > div.css-1dbjc4n.r-yfoy6g.r-z2wwpe.r-xnswec.r-1ekmkwe.r-1udh08x.r-u8s1d > div > div > div > div:nth-child(1) > div.css-1dbjc4n.r-16y2uox.r-1wbh5a2 > div > span");
};
const confirmDel=()=>{
    document.querySelector("#layers > div:nth-child(2) > div > div > div > div > div > div.css-1dbjc4n.r-1awozwy.r-1kihuf0.r-18u37iz.r-1pi2tsx.r-1777fci.r-1pjcn9w.r-fxte16.r-1xcajam.r-ipm5af.r-9dcw1g > div.css-1dbjc4n.r-1awozwy.r-yfoy6g.r-1867qdf.r-1jgb5lz.r-pm9dpa.r-1ye8kvj.r-1rnoaur.r-d9fdf6.r-1sxzll1.r-13qz1uu > div.css-1dbjc4n.r-18u37iz.r-13qz1uu > div.css-18t94o4.css-1dbjc4n.r-1dgebii.r-42olwf.r-sdzlij.r-1phboty.r-rs99b7.r-16y2uox.r-1w2pmg.r-1vuscfd.r-1dhvaqw.r-1ny4l3l.r-1fneopy.r-o7ynqc.r-6416eg.r-lrvibr > div > span > span").click()
} 

delButton();
clickDel();
confirmDel();

I have tried a few different ways, if I run them manually by running each function in the console, it works. However when i try to put them into a function that runs all 3, the second one doesn't click because it isn't ready yet. I tried using a setTimeout before firing them but it still hasn't worked. I also tried using async and await but I think my understanding of the problem is limited.

const runEmAll=()=>{
    setTimeout(delButton(), 1000); 
    setTimeout(clickDel(), 1000);
    setTimeout(confirmDel(), 1000);
}
runEmAll();

// or I also tried...
const runDel = async ()=>{
    await delButton();
}
runDel().then(clickDel());

Again that didn't work.. The error I get is

VM1649:2 Uncaught TypeError: Cannot read property 'click' of null

referring to the clickDel function

Viburnum answered 16/11, 2020 at 17:48 Comment(6)
I don't want to be that guy, but is it possible for you to use something like puppeteer?Putrefy
I could use puppeteer or selenium but I just wanted something quick and dirty!Viburnum
Gotcha, one thing that stands out is you're calling the function in your setTimeouts, which will execute them immediately. Change to setTimeout(delButton, 1000) etc.Putrefy
Thanks Phix, tried that but it still didn't work. It needs to run delButton, wait a second so that the menu appears, and then hit the clickDel()Viburnum
Tried nesting them too nestedTimeouts=()=>{ setTimeout(delButton,1000, setTimeout(clickDel,1000)); }Viburnum
Okay that works ()=>{ setTimeout(delButton, 1000); setTimeout(clickDel, 1000); setTimeout(confirmDel, 1000); }Viburnum
R
18

. open Chrome or whatever browser.

(click order and buttons may different vary if browser is not Chrome).

. open twitter.

. click profile or click tweets and replies

. press F12 (developer options)

. click Console Tab

. paste this codes (twitter language must be English)

var delTweets = function () {
var tweetsRemaining = 
document.querySelectorAll('[role="heading"]+div')[1].textContent;
console.log('Remaining: ', tweetsRemaining);
window.scrollBy(0, 10000);
document.querySelectorAll('[aria-label="More"]').forEach(function 
(v, i, a) {
    v.click();
    document.querySelectorAll('span').forEach(function (v2, i2, a2) {
        if (v2.textContent === 'Delete') {
            v2.click();
            document.querySelectorAll('[data-testid="confirmationSheetConfirm"]').forEach(function (v3, i3, a3) {
                v3.click();
            });
        }
        else {
            document.body.click();
        }
    });
});
setTimeout(delTweets, 4000); //less than 4000 might be rate limited or account suspended. increase timeout if any suspend or rate limit happens
}
delTweets();
Repercussion answered 6/6, 2022 at 9:52 Comment(4)
Works like a charm. And likes?Cocker
Gracias, still works at time of posting.Twophase
you need to refresh page, when you are doneRepercussion
It's a bit buggy in 2024, but still works. When it gets stuck trying to open random popovers, just refresh and run it again.Underhand
L
12

UPDATED 29 May 2024 due to changes in the Twitter UI

I was using Uzay's solution but it stopped working. Here is my solution, which also removes retweets.

To use it, go to your Twitter timeline then go to "tweets" tab to delete all tweets, OR go to "replies" tab to delete replies.

Paste the following code into the browser JavaScript console.

Make sure you set your twitter handle in the code before pasting it!

Script 1: - delete all tweets updated 29 May 2024

// TO DELETE TWEETS MUST BE RUN FROM https://twitter.com/{yourTwitterHandle}
// TO DELETE REPLIES MUST BE RUN FROM https://twitter.com/{yourTwitterHandle}/with_replies

// IMPORTANT IMPORTANT IMPORTANT - SET YOUR TWITTER HANDLE IN THE NEXT LINE!
const yourTwitterHandle = "@yourtwitterhandle";
// one every 10 seconds to avoid Twitter noticing
const waitTimeSeconds = 10;
const sleep = async (seconds) => new Promise(resolve => setTimeout(resolve, seconds * 1000));

const main = async () => {
        await walkTweets();
};

const walkTweets = async () => {
    let articles = document.getElementsByTagName('article');
    for (article of articles) {
        article.scrollIntoView();
        if (article.textContent.includes(yourTwitterHandle)) {
            console.log(`found tweet or reply, sleeping: ${waitTimeSeconds}`);
            await sleep(waitTimeSeconds);
            console.log(`finished sleeping: ${waitTimeSeconds}`);
            try {
                const tweetElement = article.querySelector('[aria-label="More"]');
                if (tweetElement) {
                    article.scrollIntoView();
                    console.log('clicking:');
                    tweetElement.click();
                    // Wait for the menu item to appear using MutationObserver
                    await waitForMenuItem();
                    console.log('clicking delete confirm button');
                    document.querySelector('[data-testid="confirmationSheetConfirm"]').click();
                    console.log('clicking delete confirm button clicked');
                }
            } catch (e) {
                console.error(e);
            }
        }
    }
};

const waitForMenuItem = async () => {
    return new Promise((resolve, reject) => {
        const observer = new MutationObserver((mutations) => {
            const menuItem = document.querySelector('div[role="menuitem"][tabindex="0"]');
            if (menuItem) {
                console.log('Delete menu item found, clicking:', menuItem);
                menuItem.click();
                observer.disconnect();
                resolve();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // Timeout after 10 seconds if the element is not found
        setTimeout(() => {
            observer.disconnect();
            reject(new Error('Element not found within timeout period'));
        }, 10000);
    });
};

main();

Script 2: - delete all likes 29 May 2024 - I doubt this still works, needs updating.

// MUST BE RUN FROM https://twitter.com/{yourTwitterHandle}/likes
// one every 10 seconds to avoid Twitter noticing

const waitTimeSeconds = 2;
const maxRetries = 3;
const sleep = async (seconds) => new Promise(resolve => setTimeout(resolve, seconds * 1000));
const unlikeAllTweets = async () => {
    let counter = 0;
    let retryCount = 0;
    while (true) {
        try {
            await sleep(waitTimeSeconds);
            const element = document.querySelector('[data-testid="unlike"]');
            if (element === null) {
                if (retryCount >= maxRetries) {
                    console.log('element was null, no more likes found after retries, finished');
                    return;
                }
                console.log(`element was null, retrying... (${retryCount + 1}/${maxRetries})`);
                retryCount++;
                continue;
            }
            element.scrollIntoView();
            element.click();
            counter++;
            retryCount = 0;  
            console.log(`Unliked a tweet. Total so far: ${counter}`);
            await sleep(0.5);
            // Remove the element's parent from the DOM to avoid it clagging up
            if (element.parentElement) {
                element.parentElement.remove();
            }
        } catch (error) {
            console.error('An error occurred:', error);
        }
    }
};

unlikeAllTweets();
Linda answered 8/3, 2023 at 20:12 Comment(6)
Works fine. But you might need to adjust "You Retweeted", [aria-label="More"], 'Delete' strings to your language.Hateful
After switching the interface language to English, it worked fine. I only edited the code to run every second. This was very helpful. Thanks!Hardandfast
There's apparently too many pending edits to suggest an edit. But "You retweeted" has now become "You reposted"Subconscious
I was a frequent retweeter so the accepted answer stalled out for me as Twitter’s UX wasn’t loading any more original posts to delete after some time. This script work better for me as a result, but it is a fair bit slower than the accepted answer’s script because of the 10 second delay. Your mileage may vary depending on your use case.Crankpin
@JeffPeterson I set it to 2 seconds and it seems to work fine. Choose whatever value you like.Linda
@DukeDougal yep, I did similar, setting it to 1 second. Using that short of an iteration will cause Twitter to stop getting new messages to show after processing 100+ messages though, so the 2 second delay may be more ideal to avoid Twitter throttling fetching new messages to delete.Crankpin
U
5

Adding to Mazen Alhrazi answer (thanks!), tested 21st Dec 2022, this is working:

    (async () => {
      function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
      }
    
      let found;
    
      while (found = document.querySelectorAll('[data-testid="caret"]').length) {
    
        // get first tweet
        let tweet = document.querySelectorAll('[data-testid="tweet"]')[0];
    
        // if it is a retweet, undo it
        if (tweet.querySelectorAll('[data-testid="unretweet"]').length) {
          tweet.querySelectorAll('[data-testid="unretweet"]')[0].click()
          await sleep(1000)
          document.querySelectorAll('[data-testid="unretweetConfirm"]')[0].click()
          await sleep(1000)      
        }
        // is a tweet
        else {
          if (new Date(document.querySelectorAll('[datetime]')[0].getAttribute('datetime')) < new Date('2018')) {
            console.log('Limit date reach')
            break;
          }
    
          tweet.querySelectorAll('[data-testid="caret"]')[0].click()
          await sleep(1000)
          document.querySelectorAll('[role="menuitem"]')[0].click()
          await sleep(1000)
          document.querySelectorAll('[data-testid="confirmationSheetConfirm"]')[0].click()
        }
      }
    
      if (!found)
        console.log('No more tweets found');
    
    })();

My main problem was retweets are in the middle of your own tweets, so you have to delete both: unretweeting and deleting depending of the type.

I added a filter so you do not delete tweets older than a Date (2018 in the example). Edit or remove that if if not needed.

It's not perfect, have some problems with some conversations/replies, but it worked for me. Maybe somebody else will fix that cases :)

Uno answered 21/12, 2022 at 15:13 Comment(1)
is this full or partial?Jemima
V
1

Following Phix' advice I got it working,

()=>{
    setTimeout(delButton, 1000); 
    setTimeout(clickDel, 1000);
    setTimeout(confirmDel, 1000);
}
Viburnum answered 16/11, 2020 at 18:40 Comment(0)
B
1

login > on your profile > Tweets tab > execute

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
document.querySelectorAll('[data-testid="caret"]')[0].click()
await sleep(1000)
document.querySelectorAll('[role="menuitem"]')[0].click()
await sleep(1000)
document.querySelectorAll('[data-testid="confirmationSheetConfirm"]')[0].click()
Bohaty answered 3/3, 2022 at 7:4 Comment(3)
THis is not working, can you please update itStocker
it works well, imgur.com/a/wEYNqFOBohaty
please use codes that I've just postedRepercussion
R
1

var delRetweets = function () {
  var tweetsRemaining = document.querySelectorAll('[role="heading"]+div')[1]
    .textContent;
  window.scrollBy(0, 10000);

  document
    .querySelectorAll('[style="color: rgb(0, 186, 124);"]')
    .forEach(function (v) {
      v.click();
      document.querySelectorAll("span").forEach(function (v2, i2, a2) {
        if (v2.textContent === "Undo repost") {
          v2.click();
        } else {
          document.body.click();
        }
      });
    });

  setTimeout(delRetweets, 4000); //less than 4000 might be rate limited or account suspended. increase timeout if any suspend or rate limit happens
};
delRetweets();

var likeTweets = function () {
  var tweets = document.querySelectorAll('[style="color: rgb(249, 24, 128);"]');
  var index = 0;
  var clickCount = 0;

  var clickNextTweet = function () {
    if (index < tweets.length && clickCount < 999) {
      tweets[index].click();
      index++;
      clickCount++;

      if (clickCount % 50 === 0) {
        // Check if 50 clicks limit reached, wait for 15 minutes before continuing
        setTimeout(clickNextTweet, 900000); // Timeout for 15 minutes (900000 milliseconds)
      } else {
        setTimeout(clickNextTweet, 9000); // Timeout between each click action (adjust as needed)
      }
    } else {
      window.scrollBy(0, 10000);

      if (clickCount >= 999) {
        console.log("Maximum click limit reached. Stopping the process.");
      } else {
        setTimeout(likeTweets, 9000); // Timeout before scrolling and starting the process again (adjust as needed)
      }
    }
  };

  clickNextTweet();
};

likeTweets();
Ruhr answered 14/6, 2023 at 18:38 Comment(0)
F
1

After many attempts and using various solutions, I found the best one after many tries and with the help of ChatGPT. If you're looking to automate the deleting of all activities on X (Twitter), I found a great solution, thanks to Lionbytes. The script handles undoing reposts, retries, and scrolling to load more posts efficiently. Just press F12 in Chrome, choose the console, copy the code, and select the tab that needs to be removed.

Here's the link to the script: Lionbytes Undo Repost Script.

Script to Delete Tweets

//Run this script from https://twitter.com/{yourTwitterHandle}: 
    
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const randomDelay = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

const deleteTweetsAndUndoReposts = async () => {
  let actionsCount = 0;
  const maxActions = 1000;  // change this number depend on your number of posts
  let retryCount = 0;
  const maxRetries = 3;

  const deleteTweet = async (button) => {
    try {
      button.click();
      await sleep(randomDelay(100, 300));
      let menuItems = document.querySelectorAll('div[role="menuitem"]');
      let deleteOption = Array.from(menuItems).find(item => item.innerText.includes('Delete') || item.innerText.includes('삭제'));
      if (deleteOption) {
        deleteOption.click();
        await sleep(randomDelay(300, 500));
        let confirmDelete = document.querySelector('[data-testid="confirmationSheetConfirm"]');
        if (confirmDelete) {
          confirmDelete.click();
          await sleep(randomDelay(300, 500));
          actionsCount++;
          console.log('Deleted tweet. Total actions:', actionsCount);
          return true;
        } else {
          console.log('Confirmation button not found');
        }
      } else {
        console.log('Delete option not found');
      }
    } catch (e) {
      console.error('Error deleting tweet:', e);
    }
    return false;
  };

  const undoRepost = async (button) => {
    try {
      button.click();
      await sleep(randomDelay(300, 500));
      let confirmUndo = document.querySelector('[data-testid="unretweetConfirm"]');
      if (confirmUndo) {
        confirmUndo.click();
        await sleep(randomDelay(300, 500));
        actionsCount++;
        console.log('Undid repost. Total actions:', actionsCount);
        return true;
      } else {
        console.log('Undo Repost confirmation button not found');
      }
    } catch (e) {
      console.error('Error undoing repost:', e);
    }
    return false;
  };

  while (actionsCount < maxActions) {
    let deleteButtons = Array.from(document.querySelectorAll('[data-testid="caret"]'))
                            .filter(btn => btn.closest('article'));
    let undoRepostButtons = Array.from(document.querySelectorAll('[data-testid="unretweet"]'))
                                 .filter(btn => btn.closest('article'));

    if (deleteButtons.length === 0 && undoRepostButtons.length === 0) {
      window.scrollTo(0, document.body.scrollHeight);
      await sleep(randomDelay(300, 500));
      deleteButtons = Array.from(document.querySelectorAll('[data-testid="caret"]'))
                           .filter(btn => btn.closest('article'));
      undoRepostButtons = Array.from(document.querySelectorAll('[data-testid="unretweet"]'))
                               .filter(btn => btn.closest('article'));
    }

    if (deleteButtons.length === 0 && undoRepostButtons.length === 0) break;

    let actionPerformed = false;

    for (let button of deleteButtons) {
      if (actionsCount >= maxActions) break;
      const success = await deleteTweet(button);
      if (success) {
        actionPerformed = true;
      } else {
        console.log('Failed to delete tweet, moving to next.');
      }
    }

    for (let button of undoRepostButtons) {
      if (actionsCount >= maxActions) break;
      const success = await undoRepost(button);
      if (success) {
        actionPerformed = true;
      } else {
        console.log('Failed to undo repost, moving to next.');
      }
    }

    if (!actionPerformed) {
      retryCount++;
      if (retryCount >= maxRetries) {
        console.log('Too many retries without actions. Skipping problematic posts and scrolling.');
        retryCount = 0;
        window.scrollBy(0, 500);
        await sleep(randomDelay(300, 500));
      } else {
        console.log('Retrying to find actions. Attempt:', retryCount);
        await sleep(1500);
      }
    } else {
      retryCount = 0;
    }
  }

  console.log('Stopped after performing', actionsCount, 'delete/undo repost actions');
};

deleteTweetsAndUndoReposts();

Script to Delete Replies

  //Run this script from https://twitter.com/{yourTwitterHandle}/with_replies:


const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const randomDelay = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

const scrollToBottom = async () => {
  window.scrollTo(0, document.body.scrollHeight);
  await sleep(randomDelay(500, 1000)); // Adjust sleep time if needed
};

const deleteReplies = async () => {
  let actionsCount = 0;
  const maxActions = 1000;  // Change this number based on your number of replies

  const deleteReply = async (button) => {
    try {
      button.click();
      await sleep(randomDelay(100, 300));
      let menuItems = document.querySelectorAll('div[role="menuitem"]');
      let deleteOption = Array.from(menuItems).find(item => item.innerText.includes('Delete') || item.innerText.includes('삭제'));
      if (deleteOption) {
        deleteOption.click();
        await sleep(randomDelay(300, 500));
        let confirmDelete = document.querySelector('[data-testid="confirmationSheetConfirm"]');
        if (confirmDelete) {
          confirmDelete.click();
          await sleep(randomDelay(300, 500));
          actionsCount++;
          console.log('Deleted reply. Total actions:', actionsCount);
          return true;
        } else {
          console.log('Confirmation button not found');
        }
      } else {
        console.log('Delete option not found');
      }
    } catch (e) {
      console.error('Error deleting reply:', e);
    }
    return false;
  };

  while (actionsCount < maxActions) {
    let deleteButtons = Array.from(document.querySelectorAll('[data-testid="caret"]'))
                            .filter(btn => btn.closest('article'));

    if (deleteButtons.length === 0) {
      await scrollToBottom();
      deleteButtons = Array.from(document.querySelectorAll('[data-testid="caret"]'))
                           .filter(btn => btn.closest('article'));
    }

    if (deleteButtons.length === 0) break;

    for (let button of deleteButtons) {
      if (actionsCount >= maxActions) break;
      await deleteReply(button);
    }

    await scrollToBottom(); // Scroll again to load more replies
  }

  console.log('Stopped after performing', actionsCount, 'delete actions');
};

deleteReplies();

Script to Unlike Tweets

//Run this script from https://twitter.com/{yourTwitterHandle}/likes:
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const randomDelay = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

const unlikeTweets = async () => {
  let actionsCount = 0;
  const maxActions = 1000;  // change this number based on your number of likes
  let lastScrollHeight = 0;

  const unlikeTweet = async (button) => {
    try {
      button.click();
      await sleep(randomDelay(300, 500));
      actionsCount++;
      console.log('Unliked tweet. Total actions:', actionsCount);
      return true;
    } catch (e) {
      console.error('Error unliking tweet:', e);
    }
    return false;
  };

  while (actionsCount < maxActions) {
    let unlikeButtons = Array.from(document.querySelectorAll('[data-testid="unlike"]'))
                             .filter(btn => btn.closest('article'));

    if (unlikeButtons.length === 0) {
      window.scrollTo(0, document.body.scrollHeight);
      await sleep(randomDelay(300, 500));
      unlikeButtons = Array.from(document.querySelectorAll('[data-testid="unlike"]'))
                           .filter(btn => btn.closest('article'));
    }

    if (unlikeButtons.length === 0) {
      const newScrollHeight = document.body.scrollHeight;
      if (newScrollHeight === lastScrollHeight) {
        console.log('No more tweets to unlike.');
        break;
      }
      lastScrollHeight = newScrollHeight;
      window.scrollTo(0, document.body.scrollHeight);
      await sleep(randomDelay(300, 500));
      continue;
    }

    for (let button of unlikeButtons) {
      if (actionsCount >= maxActions) break;
      await unlikeTweet(button);
      await sleep(10000); // Delay to avoid Twitter noticing
    }

    window.scrollTo(0, document.body.scrollHeight);
    await sleep(randomDelay(300, 500));
  }

  console.log('Stopped after performing', actionsCount, 'unlike actions');
};

unlikeTweets();
Forthcoming answered 29/5 at 7:6 Comment(0)
N
0

Building on Adam Boyle's answer, if you want to delete only 50 retweets in one go:

var counter = 0;
var delRetweets = function () {
  if (counter >= 50) {
    return;
  }
  
  window.scrollBy(0, 10000);

  document
    .querySelectorAll('[style="color: rgb(0, 186, 124);"]')
    .forEach(function (v) {
      if (counter >= 50) {
        return;
      }
      v.click();
      document.querySelectorAll("span").forEach(function (v2) {
        if (v2.textContent === "Undo Retweet") {
          v2.click();
          counter++;
          console.log(`Retweet delete triggered. Total retweets deleted: ${counter}`); // log the count here
        } else {
          document.body.click();
        }
      });
    });

  setTimeout(delRetweets, 4000);
};
delRetweets();
Nivernais answered 16/7, 2023 at 19:29 Comment(0)
I
0

What works for me to delete both Posts and Replies, July 2024. The key difference was to look for this div:

const divUnderArticle = article.querySelector(`div[data-testid="UserAvatar-Container-${yourTwitterHandle}"]`);

under the "article".

If you want to do it in batches, use a Search first such as @mytwitter handle since:2020-01-01 until:2021-01-01 and do it one year at a time, say.

// TO DELETE TWEETS MUST BE RUN FROM https://twitter.com/{yourTwitterHandle}
// TO DELETE REPLIES MUST BE RUN FROM https://twitter.com/{yourTwitterHandle}/with_replies

// IMPORTANT IMPORTANT IMPORTANT - SET YOUR TWITTER HANDLE IN THE NEXT LINE!
// Without @

const yourTwitterHandle = "mytwitterhandle";

// one every 10 seconds to avoid Twitter noticing
const waitTimeSeconds = 3;
const sleep = async (seconds) => new Promise(resolve => setTimeout(resolve, seconds * 1000));

const main = async () => {
    for (let i = 1; i < 50; i++) {
        console.log(`O========== Walking tweets, round: ${i} ==========O`);
        await walkTweets();
    }
    console.log('Fin');
};

const walkTweets = async () => {
    let articles = document.getElementsByTagName('article');
    console.log(`Number of articles: ${articles.length}`);
    i = 0;
    for (article of articles) {
        i += 1;
        article.scrollIntoView({ inline: 'start'});
        console.log("Processing article", i, "of", articles.length);

        // This finds only posts, not replies to me
        const divUnderArticle = article.querySelector(`div[data-testid="UserAvatar-Container-${yourTwitterHandle}"]`);


        // if (article.textContent.includes(yourTwitterHandle)) {
        if (divUnderArticle) {

            console.log(`found tweet or reply, sleeping: ${waitTimeSeconds}`);
            await sleep(waitTimeSeconds);
            try {
                const tweetElement = article.querySelector('[aria-label="More"]');
                if (tweetElement) {
                    article.scrollIntoView();
                    console.log('clicking:');
                    tweetElement.click();
                    // Wait for the menu item to appear using MutationObserver
                    await waitForMenuItem();
                    console.log('clicking delete confirm button');
                    // Delete menu item found, clicking: <div role=​"menuitem" tabindex=​"0" class=​"css-175oi2r r-1loqt21 r-18u37iz r-kritb0 r-ubg91z r-13qz1uu r-o7ynqc r-6416eg r-1ny4l3l">​<div class=​"css-175oi2r r-1777fci r-d1g4j7">​…​</div>​<div class=​"css-175oi2r r-16y2uox r-1wbh5a2">​…​</div>​</div>​

                    top_menu_item = document.querySelector('[data-testid="confirmationSheetConfirm"]')

                    if (top_menu_item) {
                        text1 = top_menu_item.textContent
                        console.log('text of top_menu_item:', text1);
                        if (text1.includes("Delete")) {
                            top_menu_item.click();
                            console.log('delete confirm button clicked');
                        } else {
                            console.log('Delete button not found');
                        }
                    }
                }
            } catch (e) {
                console.error(e);
            }
        }
    }
    console.log('Finished walking tweets');
};

const waitForMenuItem = async () => {
    return new Promise((resolve, reject) => {
        const observer = new MutationObserver((mutations) => {
            const menuItem = document.querySelector('div[role="menuitem"][tabindex="0"]');
            if (menuItem) {
                console.log('Delete menu item found, clicking:', menuItem);
                menuItem.click();
                observer.disconnect();
                resolve();
            } else {
                console.log('Delete menu item not found');
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // Timeout after 10 seconds if the element is not found
        setTimeout(() => {
            observer.disconnect();
            reject(new Error('Element not found within timeout period'));
        }, 10000);
    });
};

main();

What works to undo Reposts. The key was to look for the button that says, "61 reposts. Reposted" ([aria-label*=" reposts. Reposted"]).

// TO undo reposts TWEETS MUST BE RUN FROM https://twitter.com/{yourTwitterHandle}

// one every 10 seconds to avoid Twitter noticing
const waitTimeSeconds = 3;
const sleep = async (seconds) => new Promise(resolve => setTimeout(resolve, seconds * 1000));

const main = async () => {
    for (let i = 1; i < 20; i++) {
        console.log(`O========== Walking tweets, round: ${i} ==========O`);
        await walkTweets();
    }
    console.log('Fin');
};

const walkTweets = async () => {
    // let buttons = document.getElementsByTagName('button');
    const buttons = document.querySelectorAll('div');
    console.log(`Number of buttons: ${buttons.length}`);
    i = 0;
    for (button of buttons) {
        i += 1;
        button.scrollIntoView({ inline: 'start'});
        console.log("Processing button", i, "of", buttons.length);


        // if (dataTestId == "tweet") {

        // <button aria-expanded="false" aria-haspopup="menu" aria-label="61 reposts. Reposted" role="button" class="css-175oi2r r-1777fci r-3vrnjh r-bztko3 r-lrvibr r-1loqt21 r-1ny4l3l" data-testid="unretweet" type="button"><div dir="ltr" class="css-146c3p1 r-bcqeeo r-1ttztb7 r-qvutc0 r-1qd0xha r-1b43r93 r-hjklzo r-16dba41 r-1awozwy r-6koalj r-1h0z5md r-o7ynqc r-clp7b1 r-3s2u2q" style="text-overflow: unset; color: rgb(0, 186, 124);"><div class="css-175oi2r r-xoduu5"><div class="css-175oi2r r-xoduu5 r-1p0dtai r-1d2f490 r-u8s1d r-zchlnj r-ipm5af r-1niwhzg r-sdzlij r-xf4iuw r-o7ynqc r-6416eg r-1ny4l3l"></div><svg viewBox="0 0 24 24" aria-hidden="true" class="r-4qtqp9 r-yyyyoo r-dnmrzs r-bnwqim r-lrvibr r-m6rgpd r-1xvli5t r-1hdv0qi"><g><path d="M4.75 3.79l4.603 4.3-1.706 1.82L6 8.38v7.37c0 .97.784 1.75 1.75 1.75H13V20H7.75c-2.347 0-4.25-1.9-4.25-4.25V8.38L1.853 9.91.147 8.09l4.603-4.3zm11.5 2.71H11V4h5.25c2.347 0 4.25 1.9 4.25 4.25v7.37l1.647-1.53 1.706 1.82-4.603 4.3-4.603-4.3 1.706-1.82L18 15.62V8.25c0-.97-.784-1.75-1.75-1.75z"></path></g></svg></div><div class="css-175oi2r r-xoduu5 r-1udh08x"><span data-testid="app-text-transition-container" style="transition-property: transform; transition-duration: 0.3s; transform: translate3d(0px, 0px, 0px);"><span class="css-1jxf684 r-1ttztb7 r-qvutc0 r-poiln3 r-1enofrn r-fxxt2n r-n7gxbd r-11pglpa r-1yz1tyy r-1noe1sz" style="text-overflow: unset;"><span class="css-1jxf684 r-bcqeeo r-1ttztb7 r-qvutc0 r-poiln3" style="text-overflow: unset;">61</span></span></span></div></div></button>
        const repost_button = button.querySelector('[aria-label*=" reposts. Reposted"], [aria-label*=" repost. Reposted"]');

        // document.querySelector('')
        // const repost_button = document.querySelector('button[aria-haspop="menu"][data-testid="caret"]');


        if (repost_button) {
            console.log(`found repost, sleeping: ${waitTimeSeconds}`);
            await sleep(waitTimeSeconds);
            try {
                console.log('clicking:');
                repost_button.click();
                // Wait for the menu item to appear using MutationObserver
                await waitForMenuItem();
                console.log('clicking undo repost button');
                // FIXME : later
                // document.querySelector('[data-testid="confirmationSheetConfirm"]').click();
                console.log('undo repost button clicked');
            } catch (e) {
                console.error(e);
            }
        }
    }
    console.log('Finished walking tweets');
};

const waitForMenuItem = async () => {
    return new Promise((resolve, reject) => {
        const observer = new MutationObserver((mutations) => {
            const menuItem = document.querySelector('div[role="menuitem"][tabindex="0"]');
            if (menuItem) {
                console.log('menu item found, clicking:', menuItem);
                menuItem.click();
                observer.disconnect();
                resolve();
            } else {
                console.log('menu item not found');
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // Timeout after 10 seconds if the element is not found
        setTimeout(() => {
            observer.disconnect();
            reject(new Error('Element not found within timeout period'));
        }, 10000);
    });
};

main();
Inept answered 23/7 at 4:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.