Skip to content

Instantly share code, notes, and snippets.

@shelldandy
Last active September 14, 2021 15:13
Show Gist options
  • Save shelldandy/a283ed9d1aedfa26542aea4c6da9593e to your computer and use it in GitHub Desktop.
Save shelldandy/a283ed9d1aedfa26542aea4c6da9593e to your computer and use it in GitHub Desktop.
Scrub your likes from Twitter
  • Create an app on apps.twitter.com
  • Grab the Consumer Key (API Key) and Consumer Secret (API Secret) from Keys and Access Tokens
  • Make sure you set the right access level for your app
  • If you want to use user-based authentication, grab the access token key and secret as well

Make a package.json with the above packages:

yarn add twitter-lite sleep chalk dotenv

Create a .env file like the following:

CONSUMER_KEY=xx
CONSUMER_SECRET=xx
ACCESS_TOKEN_KEY=xx
ACCESS_TOKEN_SECRET=xx

Replace the xx with your actual credentials, duh.

If you're running a modern-ish version of node then you can use imports directly by adding this field on your package.json

{
  "type": "module",
  "scripts": {
    "start": "node -r dotenv/config index",
    "debug": "node inspect -r dotenv/config index"
  }
}

Also added some useful scripts.

yarn start

Boom, it will recursively clean likes until it finishes...

import Twitter from 'twitter-lite'
import sleep from 'sleep'
import chalk from 'chalk'
const options = {
consumer_key: process.env.CONSUMER_KEY,
consumer_secret: process.env.CONSUMER_SECRET,
access_token_key: process.env.ACCESS_TOKEN_KEY,
access_token_secret: process.env.ACCESS_TOKEN_SECRET
}
const client = new Twitter(options)
export const promiser = (promise, improved) => promise
.then((data) => [null, data])
.catch((err) => {
if (improved) {
Object.assign(err, improved)
}
return [err] // which is same as [err, undefined];
})
const handleError = e => {
if ('errors' in e) {
if (e.errors[0].code === 88) {
console.log('Rate limit will reset on', new Date(e._headers.get('x-rate-limit-reset') * 1000))
} else {
console.log('API Related Error...')
console.log(e.errors)
}
} else {
console.log('non-API Error')
console.log(e)
}
}
const unlike = async tweet => {
let next = 1
let remaining = 0
const [e, res] = await promiser(client.post('favorites/destroy', {
id: tweet.id_str
}))
if (e) {
console.log(chalk.red(`❌ - https://www.twitter.com/${tweet.user.screen_name}/status/${tweet.id_str}`))
handleError(e)
return
}
const headers = res._headers
remaining = parseInt(headers.get('x-rate-limit-remaining'))
if (!isNaN(remaining) && remaining === 0) {
console.log(chalk.cyan('Waiting'))
next = parseInt(headers.get('x-rate-limit-reset')) - Date.now()
next = next / 1000
}
console.log(chalk.green(`✅ - https://www.twitter.com/${res.user.screen_name}/status/${res.id_str}`))
return next
}
const fetchLikes = async () => {
const favs = await client.get('favorites/list', {
count: 200
})
console.log(`${favs.length} favs found...`)
return favs
}
const handleUnlikes = async favs => {
for (let i = 0, len = favs.length; i < len; i++) {
const next = await unlike(favs[i])
console.log(chalk.green(`Sleeping for ${next}s...`))
sleep.sleep(next)
}
return true
}
;(async () => {
let favs = await fetchLikes()
while (favs && favs.length > 0) {
await handleUnlikes(favs)
favs = await fetchLikes()
}
console.log('Eskeler!!!')
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment