Skip to content

Instantly share code, notes, and snippets.

@Throwaway-MM
Last active June 25, 2023 18:31
Show Gist options
  • Star 42 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save Throwaway-MM/5a8e74895293eae0071cec612477c72f to your computer and use it in GitHub Desktop.
Save Throwaway-MM/5a8e74895293eae0071cec612477c72f to your computer and use it in GitHub Desktop.
// This is a hack, a quick and dirty console script for RT/tweets (with replies) removal w/o API
// To be used in: https://twitter.com/Username/with_replies
// Set your username (without @) below (case-sensitive) to correctly trigger the right Menu
const tweetUser = 'Username'
// BUG, With above we still trigger Menu on some replies but relatively harmless.
// @Hack Implement simple has() for querySelector
const querySelectorHas = function( parent, child ){
return [].filter.call( document.querySelectorAll( parent ), function( elem ){
if(elem.querySelector( child ) !== null ) {
return true
}
else {
return false
}
});
}
// @Hack Implement xpath text() selector returning matching holder element
// equiv XPath //find[text()='inner']
const querySelectorInner = function( parent, inner ){
return [].filter.call( document.querySelectorAll( parent ), function( elem ){
if(elem.innerHTML == inner ) {
return true
}
else {
return false
}
});
}
setInterval(() => {
console.log('--- Twitter Removal Clicks Round')
// For Old RT's may need (?) to RT and then Unretweet - Just watch for the UI rate limits
var unretweets = 0
for (const d of document.querySelectorAll('div[data-testid="unretweet"]')) {
unretweets++
d.click()
}
var unretweetconfirms = 0
for (const r of document.querySelectorAll('div[data-testid="unretweetConfirm"]')) {
unretweetconfirms++
r.click()
}
console.log('Unretweets: ' + unretweets + ', Confirms: ' + unretweetconfirms)
var clicks = 0
for(const d of querySelectorHas("div[data-testid='tweet']", "a[href='/" + tweetUser + "']")) {
const moreButton = d.querySelector("div[aria-label='More']")
clicks++
moreButton.click()
}
var clickDeletes = 0
for(const deleteButton of querySelectorInner("span", 'Delete')) {
deleteButton.click()
clickDeletes++
}
var clickConfirms = 0
for(const deleteConfirm of document.querySelectorAll("div[data-testid='confirmationSheetConfirm']")) {
deleteConfirm.click()
clickConfirms++
}
console.log('Menu: ' + clicks + ', Deletes: ' + clickDeletes + ', Confirms: ' + clickConfirms)
// Scrolling is more involved/difficult as twitter does not hide rest of the thread if reply deleted
// As work-around scroll yourself when removals hit zero. API would be best option but this solution is WEB UI driven.
window.scrollTo(0, document.body.scrollHeight)
}, 3000) // Run every 3s
/*******************************************************************************************
*
* THIS GIST IS PROVIDED "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL WE BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*******************************************************************************************/
@Throwaway-MM
Copy link
Author

Throwaway-MM commented Jun 10, 2020

If you also want to delete Likes see:
https://gist.github.com/aymericbeaumet/d1d6799a1b765c3c8bc0b675b1a1547d

Deleting RT, replies and tweets is more involved on twitter REACT UI wise as there is

  1. A menu to toggle
  2. Delete button to press
  3. Modal Confirm
  4. Scrolling required (threads are a big mess)

Somebody wrote one earlier for this but it,

a) didn't delete multiple RT/tweets per interval run
b) was hard coded reliant on looking DOM div elements by ever-changing "obfuscated" classes strings therefore matching wrong things
c) broke execution chain (infinite recursion) if there was one unmatched element at any stage

This gist finds the correct menu elements (set your username so it finds your own tweets), runs from interval timer and catch-all-actions from window scroll point.

BUGS
i) Deleting replies is trickier since twitter UI leaves the underlying thread lying around so scrolling is not accurate - just scroll back up manually to hit some more.
ii) Manual scrolling back up may be required while interval function runs
iii) Does not detect twitter rate limit for deletions so you may need to re-run and maybe re-tweet and unretweet (?)

To stop the script just close the page as it runs in your console.
Yes I should have used XPath instead of two filter funcs but didn't think straight tonight 🙄

Also I left logging there to see what's going on and note it is Web2.0 thingy so even if twitter UI shows deleted it may not be the truth.

@KDReeves
Copy link

KDReeves commented Jun 16, 2020

This worked brilliantly and was exactly what I was looking for. Bravo, ma'am. Yes, I noticed that occasional scroll-up is necessary, but that is a far, far better situation than going one by one. This is nicely executed. I appreciate it, and will try your like script next! CHEERS!

@Throwaway-MM
Copy link
Author

Throwaway-MM commented Jun 16, 2020

@KDReeves Thanks and much appreciated for your kind words :) 💁

@guihkx
Copy link

guihkx commented Jul 15, 2020

const tweetUser = window.location.pathname.match(/^\/([^/]+)/)[1]

@mbproaz
Copy link

mbproaz commented Aug 12, 2020

I love the script, I tested it and it works great!
But could you also make a script that only unretweet retweets. This scripts deletes all tweets as well.
Thanks for your reply!

@aphirst
Copy link

aphirst commented Sep 23, 2020

I too would love to see a working version that only removes retweets, not tweets.

@svpn
Copy link

svpn commented Sep 24, 2020

My "Tweets & replies" is empty but "Tweets" tab is full of retweets, and they don't get deleted.

Also there's no option manually to Undo Retweet, and if I Retweet again then Undo Retweet it seems to get removed but after refreshing the page they come back again.

@AtchadBoy
Copy link

how use this

@eraykisabacak
Copy link

eraykisabacak commented Nov 30, 2020

Today, This code only un retweet working. But delete tweets not working

You haven't retweet under code running for delete tweets.

setInterval(() => {
  for (const d of document.querySelectorAll('div[data-testid="caret"]')) {
    d.click()
    document.querySelector('div[role="menu"] div[role="menuitem"]').click() 
    document.querySelector('div[role="alertdialog"] div[data-testid="confirmationSheetConfirm"]').click()
  }
  window.scrollTo(0, document.body.scrollHeight)
}, 1000)

@Tilduke
Copy link

Tilduke commented Feb 9, 2021

For those asking for unretweet only then you just need to keep only the top portion of the setInterval loop. Modified code below. The other code is for deleting tweets.

setInterval(() => {

  console.log('--- Twitter Removal Clicks Round')
// For Old RT's may need (?) to RT and then Unretweet - Just watch for the UI rate limits
  var unretweets = 0
  for (const d of document.querySelectorAll('div[data-testid="unretweet"]')) {
    unretweets++
    d.click()
  }
  var unretweetconfirms = 0
  for (const r of document.querySelectorAll('div[data-testid="unretweetConfirm"]')) {
    unretweetconfirms++
    r.click()
  }
  console.log('Unretweets: ' + unretweets + ', Confirms: ' + unretweetconfirms)

}, 3000) // Run every 3s

Thanks original author for the base code. Love it! Only code snippet I found for the new UI.

@Nikunj-Goyal
Copy link

Nikunj-Goyal commented Mar 2, 2021

i'm sorry, i dont get it, im confused using this. can you tell step by step to unretweet?

Steps for only unretweeting-

  1. Open your profile page on twitter in Chrome Desktop.(url will be like - https://twitter.com/ "Your profile name")
  2. Select the "tweets" tab
  3. Press F12 to open Developers Console.
  4. Select the "Console" Tab
  5. Paste the script snippet by @Tilduke (for only deleting your retweets and not your own tweets) and Press "Enter".
  6. You may also be needed to scroll down to load your retweets occasionally.

@1x6
Copy link

1x6 commented Aug 15, 2021

it worked, thanks so much!

@SeanDS
Copy link

SeanDS commented Nov 26, 2021

Thanks for the script! It only seemed to only work for retweets for me though. To get it to work for tweets I hacked it yet further. I got rid of the querySelectorHas and just matched the "More" button on each tweet directly. This has the side-effect of opening up other menus on the page with "More" such as trends, but these don't open a menu with a "Delete" item so nothing bad happens. Note this only deletes tweets now, not retweets, so run the original code to get them too. It also doesn't run with a delay between deletes, so you eventually get rate limited. For a few hundred tweets this is fine but if you've got thousands you might want to adapt the script to add a delay (my JavaScript knowledge is not good enough and I'm too lazy to look it up since it worked well enough for me already).

// This is a hack, a quick and dirty console script for RT/tweets (with replies) removal w/o API
// To be used in: https://twitter.com/Username/with_replies
// Set your username (without @) below (case-sensitive) to correctly trigger the right Menu

const tweetUser = 'Username'

// BUG, With above we still trigger Menu on some replies but relatively harmless.

// @Hack Implement xpath text() selector returning matching holder element
// equiv XPath //find[text()='inner']
const querySelectorInner = function( parent, inner ){
  return [].filter.call( document.querySelectorAll( parent ), function( elem ){
    if(elem.innerHTML == inner ) {
      return true
    }
    else {
      return false
    }
  });
}


setInterval(() => {

  console.log('--- Twitter Removal Clicks Round')

  var clicks = 0
  for(const d of document.querySelectorAll("div[data-testid='tweet'], div[aria-label='More']")) {
   clicks++
   d.click()
  }
  var clickDeletes = 0
  for(const deleteButton of querySelectorInner("span", 'Delete')) {
    deleteButton.click()
    clickDeletes++
  }
  var clickConfirms = 0
  for(const deleteConfirm of document.querySelectorAll("div[data-testid='confirmationSheetConfirm']")) {
    deleteConfirm.click()
    clickConfirms++
  }
  console.log('Menu: ' + clicks + ', Deletes: ' + clickDeletes + ', Confirms: ' + clickConfirms)

  // Scrolling is more involved/difficult as twitter does not hide rest of the thread if reply deleted
  // As work-around scroll yourself when removals hit zero. API would be best option but this solution is WEB UI driven.
  window.scrollTo(0, document.body.scrollHeight)

}, 3000) // Run every 3s

/*******************************************************************************************
 * 
 *   THIS GIST IS PROVIDED "AS IS" AND
 *   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *   DISCLAIMED. IN NO EVENT SHALL WE BE LIABLE FOR ANY
 *   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 *******************************************************************************************/

@timenough
Copy link

timenough commented Feb 9, 2023

Guys, it needed a fresh update for 2023+:


// BUG, With above we still trigger Menu on some replies but relatively harmless.

// @Hack Implement xpath text() selector returning matching holder element
// equiv XPath //find[text()='inner']
const querySelectorInner = function( parent, inner ){
  return [].filter.call( document.querySelectorAll( parent ), function( elem ){
    if(elem.innerHTML == inner ) {
      return true
    }
    else {
      return false
    }
  });
}


setInterval(() => {

  console.log('--- Twitter Removal Clicks Round')

  var clicks = 0
  for(const d of document.querySelectorAll("div[data-testid='tweet'], div[aria-label='Plus']")) {
   clicks++
   d.click()
  }
  var clickDeletes = 0
  for(const deleteButton of querySelectorInner("span", 'Supprimer')) {
    deleteButton.click()
    clickDeletes++
  }
  var clickConfirms = 0
  for(const deleteConfirm of document.querySelectorAll("div[data-testid='confirmationSheetDialog']")) {
  for(const deleteButton2 of querySelectorInner("span", 'Supprimer')) {
    deleteButton2.click()
    clickConfirms++
  }
  }
  console.log('Menu: ' + clicks + ', Deletes: ' + clickDeletes + ', Confirms: ' + clickConfirms)

  // Scrolling is more involved/difficult as twitter does not hide rest of the thread if reply deleted
  // As work-around scroll yourself when removals hit zero. API would be best option but this solution is WEB UI driven.
  window.scrollTo(0, document.body.scrollHeight)

}, 3000);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment