├── .gitignore ├── README.md ├── index.js ├── package.json └── src ├── config.js ├── delete_bot.js ├── screen_name.js └── unlike_bot.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | npm-debug.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twitter Delete and Unlike Bot 2 | 3 | ## Warning 4 | 5 | Tweet deletion is irreversible. 6 | 7 | If you accidentally delete tweets you did not intend, that's your own silly fault for running code you got from a stranger on the internet. 8 | 9 | _This code is provided as is, you are encouraged to review it carefully before running it to ensure it does what you think it will do._ 10 | 11 | **I take no responsibility for erroneously deleted tweets!** 12 | 13 | My personal Twitter profile uses this bot every day. If you want to see what it looks like when I eat my own dog food, my timeline is here: [@JacksonBates](https://twitter.com/JacksonBates). I've tweeted thousands of times, but there is most likely around 100 or fewer tweets in my timeline at any given point. 14 | 15 | ## Features 16 | 17 | Designed to be run on a schedule, or simply from the command line. 18 | 19 | `npm start` triggers two bot actions: 20 | 21 | 1. Tweets older than 10 days will be deleted; 22 | 23 | 2. Liked / Favourited tweets older than 10 days will be unliked / unfaved. 24 | 25 | You can 'save' tweets from being deleted by liking your own tweets. Yes, this is a little like smelling your own toots, but it is the price of preserving your wittiest tweets. Once you no longer want to save them, unlike them again and they will be deleted the next time the script runs. 26 | 27 | ## Setup 28 | 29 | 1. Create your own Twitter App at [https://apps.twitter.com/app/new](https://apps.twitter.com/app/new) 30 | 31 | 2. Obtain the API keys: `API Key`, `API Secret`, `Access Token` and `Access Token Secret` 32 | 33 | 3. If you are planning on keeping your code on a public repo like GitHub, then store these keys in a `.env` file and remember to add the file to your `.gitignore` file. You can also use the `.env` file to manage your environment variables via the `dotenv` npm package include in the `package.json`. 34 | 35 | `.env` file formatting: 36 | ``` 37 | CONSUMER_KEY=U************************o 38 | CONSUMER_SECRET=U*******************************************S 39 | ACCESS_TOKEN=2******5-T************************************e 40 | ACCESS_TOKEN_SECRET=K*******************************************k 41 | ``` 42 | 43 | 4. If you are hosting via Heroku, remember to set the variables manually for your app. 44 | 45 | 5. To customise for your own Twitter account, change the value exported by the `screen_name.js` file in the `src` folder to **your** screen name. For obvious reasons, you can only delete your own tweets! 46 | 47 | 6. Schedule! On a Linux machine, you may want to set up a cronjob. Windows users can use Task Scheduler. Alternatively, you can host this for free on Heroku and have the Heroku Scheduler add-on run it for you remotely. 48 | 49 | 50 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require( './src/delete_bot' ); 2 | require( './src/unlike_bot'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ephemeral-twitter", 3 | "version": "1.0.0", 4 | "description": "Deletes and unlikes older tweets", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "author": "JacksonBates", 10 | "license": "GPL-3.0", 11 | "dependencies": { 12 | "dotenv": "^4.0.0", 13 | "twit": "^2.2.9" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | module.exports = { 4 | consumer_key: process.env.CONSUMER_KEY, 5 | consumer_secret: process.env.CONSUMER_SECRET, 6 | access_token: process.env.ACCESS_TOKEN, 7 | access_token_secret: process.env.ACCESS_TOKEN_SECRET, 8 | } -------------------------------------------------------------------------------- /src/delete_bot.js: -------------------------------------------------------------------------------- 1 | // require modules 2 | const Twit = require('twit'); 3 | const config = require('./config'); 4 | const screen_name = require('./screen_name'); 5 | 6 | // instantiate Twit 7 | const bot = new Twit(config); 8 | 9 | // create variable stores 10 | let statuses_count = 0; 11 | const now = Date.now(); 12 | const ten_days = 864000000; 13 | let to_delete = 0; 14 | 15 | // helper functions 16 | function olderThanTenDays(check_date) { 17 | return check_date < now - ten_days 18 | }; 19 | 20 | function promiseCountStatuses() { 21 | return new Promise(function(resolve, reject) { 22 | bot.get('users/lookup', { 23 | screen_name: screen_name 24 | }, function(e, d, r) { 25 | if (e) console.log(e); 26 | resolve(d[0].statuses_count); 27 | reject(e); 28 | }) 29 | } 30 | )} 31 | 32 | function getUserHistory(user, done) { 33 | let data = []; 34 | search(); 35 | 36 | function search(lastId) { 37 | let args = { 38 | screen_name: user, 39 | count: 200, 40 | include_rts: 1 41 | }; 42 | if(lastId) args.max_id = lastId; 43 | 44 | bot.get('statuses/user_timeline', args, onTimeline); 45 | 46 | function onTimeline(err, chunk) { 47 | if (err) { 48 | console.log('Twitter search failed!'); 49 | return done(err); 50 | } 51 | 52 | // if (!chunk.length) { 53 | // console.log('User has not tweeted yet'); 54 | // return done(err); 55 | // } 56 | 57 | //Get rid of the first element of each iteration (not the first time) 58 | if (data.length) { 59 | var popped = chunk.shift(); 60 | } 61 | 62 | data = data.concat(popped, chunk); 63 | // var thisId = parseInt(data[data.length - 1].id_str); 64 | 65 | if (chunk.length) { 66 | var thisId = parseInt(data[data.length - 1].id); 67 | return search(thisId); 68 | } 69 | console.log(data.length + ' tweets imported'); 70 | return done(undefined, data); 71 | } 72 | } 73 | } 74 | 75 | promiseCountStatuses() 76 | .then((statusCount) => { 77 | console.log('Total number of tweets:', statusCount); 78 | }) 79 | .catch((reject) => { console.log(reject) }); 80 | 81 | getUserHistory(screen_name, (undefined, data) => { 82 | data.shift(); 83 | data.pop(); 84 | console.log('data length after shift and pop',data.length) 85 | for (var i = data.length - 1; i > -1; i--) { 86 | if (olderThanTenDays(Date.parse(data[i].created_at)) && !data[i].favorited || 87 | olderThanTenDays(Date.parse(data[i].created_at)) && data[i].favorited && data[i].retweeted) { 88 | to_delete++; 89 | bot.post('statuses/destroy/:id', { 90 | id: data[i].id_str.toString() 91 | }, (e, d, r) => { 92 | if (e) console.log(e); 93 | }) 94 | } 95 | } 96 | console.log(to_delete, 'tweets deleted!'); 97 | }); 98 | 99 | -------------------------------------------------------------------------------- /src/screen_name.js: -------------------------------------------------------------------------------- 1 | module.exports = 'JacksonBates'; -------------------------------------------------------------------------------- /src/unlike_bot.js: -------------------------------------------------------------------------------- 1 | const Twit = require('twit'); 2 | const config = require('./config'); 3 | const screen_name = require('./screen_name'); 4 | 5 | const bot = new Twit(config); 6 | console.log(screen_name); 7 | const now = Date.now(); 8 | const ten_days = 864000000; 9 | 10 | // helper functions 11 | function olderThanTenDays(check_date) { 12 | return check_date < now - ten_days 13 | }; 14 | 15 | function getUserLikes(user, done) { 16 | let data = []; 17 | search(); 18 | 19 | function search(lastId) { 20 | let args = { 21 | screen_name: screen_name, 22 | count: 200 23 | }; 24 | if (lastId) args.max_id = lastId; 25 | 26 | 27 | bot.get('favorites/list', args, onTimeline); 28 | 29 | function onTimeline(err, chunk) { 30 | if (err) { 31 | console.log('Twitter search failed!'); 32 | return done(err); 33 | } 34 | 35 | // if (!chunk.length) { 36 | // console.log('No favourites found!'); 37 | // // return done(undefined, data); 38 | // } 39 | 40 | //Get rid of the first element of each iteration (not the first time) 41 | if (data.length) { 42 | var popped = chunk.shift(); 43 | } 44 | 45 | data = data.concat(popped, chunk); 46 | 47 | if (chunk.length) { 48 | var thisId = parseInt(data[data.length - 1].id); 49 | return search(thisId); 50 | } 51 | console.log(data.length + ' tweets imported'); 52 | return done(undefined, data); 53 | } 54 | }} 55 | 56 | getUserLikes(screen_name, (undefined, data) => { 57 | data.shift(); 58 | // console.log(data[90]); 59 | for (var i = 0; i < data.length -1; i++) { 60 | if (olderThanTenDays(Date.parse(data[i].created_at)) && data[i].user.screen_name !== screen_name) { 61 | bot.post('favorites/destroy', { 62 | id: data[i].id_str.toString() 63 | }, (e, d, r) => { 64 | if (e) console.log(e); 65 | }) 66 | } 67 | } 68 | }) 69 | 70 | --------------------------------------------------------------------------------