├── .gitignore ├── chunking.js ├── db └── .gitkeep ├── index.js ├── package-lock.json ├── package.json ├── readme.md └── twit.js /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/ 3 | .DS_Store 4 | haters/ 5 | db/*.json -------------------------------------------------------------------------------- /chunking.js: -------------------------------------------------------------------------------- 1 | function chunkify(array, chunkSize = 10) { 2 | // make a new array 3 | const chunks = Array.from( 4 | // give it however many slots are needed - in our case 8 5 | // 1-7 with 10 items, and 8th slot will have 6 6 | { length: Math.ceil(array.length / chunkSize) }, 7 | // this is a map function that will fill up our slots 8 | (_, i) => { 9 | // make a slice of 100 items 10 | const start = chunkSize * i; 11 | // slice our the piece of the array we need 12 | return array.slice(start, start + chunkSize); 13 | } 14 | ); 15 | return chunks; 16 | } 17 | 18 | module.exports = chunkify; 19 | -------------------------------------------------------------------------------- /db/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/twitter-unfollower/22de7d11abc4a26742148df571873caed2b53c0c/db/.gitkeep -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const ms = require('ms'); 3 | const db = require('diskdb'); 4 | const T = require('./twit'); 5 | const chunkify = require('./chunking'); 6 | 7 | db.connect('./db', ['users', 'hydratedUsers']); 8 | 9 | const cutLimit = ms('280 days'); 10 | 11 | async function getListOfPeopleYouFollow(cursor = -1) { 12 | const { data } = await T.get('friends/list', { 13 | screen_name: process.env.username, 14 | count: 200, 15 | cursor, 16 | }); 17 | const followerSet = data.users.map(user => ({ 18 | screen_name: user.screen_name, 19 | name: user.name, 20 | id: user.id, 21 | })); 22 | 23 | console.log(followerSet); 24 | const screenNames = followerSet.map(user => user.screen_name); 25 | // save to DB 26 | db.users.save(screenNames); 27 | if (data.next_cursor) { 28 | getListOfPeopleYouFollow(data.next_cursor); 29 | } else { 30 | console.log('Thats it! Here is your list'); 31 | console.log(db.users.find()); 32 | } 33 | } 34 | 35 | async function findLastTweet(screenNames) { 36 | // given an array of screen names, look up their last tweet 37 | const { data: users } = await T.post('users/lookup', { 38 | screen_name: screenNames, 39 | }); 40 | 41 | const cutList = users 42 | // some people deleted all their tweets, we mark them as old 43 | .map(user => { 44 | user.status = user.status || { created_at: 0 }; 45 | return user; 46 | }) 47 | // massage the data into what we need 48 | .map(user => ({ 49 | screen_name: user.screen_name, 50 | id: user.id, 51 | lastTweet: new Date(user.status.created_at).getTime(), 52 | timeSinceLastTweet: 53 | Date.now() - new Date(user.status.created_at).getTime(), 54 | })) 55 | // cut anyone over the limit 56 | .map(user => { 57 | console.log( 58 | `${user.screen_name} last tweeted ${ms(user.timeSinceLastTweet)}` 59 | ); 60 | return user; 61 | }) 62 | .filter(user => user.timeSinceLastTweet > cutLimit); 63 | console.log(cutList); 64 | return cutList; 65 | } 66 | 67 | async function hydrateUsers() { 68 | const users = chunkify(db.users.find(), 100); 69 | const userPromises = users.map(findLastTweet); 70 | const usersChunked = await Promise.all(userPromises); 71 | console.log('Fetched all data!'); 72 | const usersFlat = usersChunked.flat(); 73 | db.hydratedUsers.save(usersFlat); 74 | } 75 | 76 | async function massUnfollow() { 77 | const users = db.hydratedUsers.find(); 78 | const userPromises = users.map(user => unfollow(user.screen_name)); 79 | const statuses = await Promise.allSettled(userPromises); 80 | console.log(statuses); 81 | } 82 | async function unfollow(screen_name) { 83 | console.log('Unfollowing ', screen_name); 84 | const { status } = await T.post('friendships/destroy', { screen_name }); 85 | console.log(status); 86 | return status; 87 | } 88 | 89 | function handleError(err) { 90 | console.log('-------------'); 91 | console.log('Oh no!'); 92 | console.log(err); 93 | console.log('-------------'); 94 | } 95 | 96 | async function go() { 97 | await getListOfPeopleYouFollow(); 98 | await hydrateUsers(); 99 | await massUnfollow(); 100 | } 101 | 102 | go().catch(handleError); 103 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "0244-untweeps", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "6.10.2", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", 10 | "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", 11 | "requires": { 12 | "fast-deep-equal": "^2.0.1", 13 | "fast-json-stable-stringify": "^2.0.0", 14 | "json-schema-traverse": "^0.4.1", 15 | "uri-js": "^4.2.2" 16 | } 17 | }, 18 | "ansi-styles": { 19 | "version": "1.0.0", 20 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", 21 | "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=" 22 | }, 23 | "asn1": { 24 | "version": "0.2.4", 25 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 26 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 27 | "requires": { 28 | "safer-buffer": "~2.1.0" 29 | } 30 | }, 31 | "assert-plus": { 32 | "version": "1.0.0", 33 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 34 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 35 | }, 36 | "asynckit": { 37 | "version": "0.4.0", 38 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 39 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 40 | }, 41 | "aws-sign2": { 42 | "version": "0.7.0", 43 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 44 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 45 | }, 46 | "aws4": { 47 | "version": "1.8.0", 48 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 49 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 50 | }, 51 | "bcrypt-pbkdf": { 52 | "version": "1.0.2", 53 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 54 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 55 | "requires": { 56 | "tweetnacl": "^0.14.3" 57 | } 58 | }, 59 | "bluebird": { 60 | "version": "3.7.1", 61 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", 62 | "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==" 63 | }, 64 | "caseless": { 65 | "version": "0.12.0", 66 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 67 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 68 | }, 69 | "chalk": { 70 | "version": "0.4.0", 71 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", 72 | "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", 73 | "requires": { 74 | "ansi-styles": "~1.0.0", 75 | "has-color": "~0.1.0", 76 | "strip-ansi": "~0.1.0" 77 | } 78 | }, 79 | "combined-stream": { 80 | "version": "1.0.8", 81 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 82 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 83 | "requires": { 84 | "delayed-stream": "~1.0.0" 85 | } 86 | }, 87 | "core-util-is": { 88 | "version": "1.0.2", 89 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 90 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 91 | }, 92 | "dashdash": { 93 | "version": "1.14.1", 94 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 95 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 96 | "requires": { 97 | "assert-plus": "^1.0.0" 98 | } 99 | }, 100 | "delayed-stream": { 101 | "version": "1.0.0", 102 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 103 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 104 | }, 105 | "diskdb": { 106 | "version": "0.1.17", 107 | "resolved": "https://registry.npmjs.org/diskdb/-/diskdb-0.1.17.tgz", 108 | "integrity": "sha1-ir0JUZazO0BnkfFJS2sTtEIiQMQ=", 109 | "requires": { 110 | "chalk": "^0.4.0", 111 | "merge": "^1.1.3", 112 | "node-uuid": "^1.4.1" 113 | }, 114 | "dependencies": { 115 | "node-uuid": { 116 | "version": "1.4.8", 117 | "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", 118 | "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" 119 | } 120 | } 121 | }, 122 | "dotenv": { 123 | "version": "8.2.0", 124 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 125 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 126 | }, 127 | "ecc-jsbn": { 128 | "version": "0.1.2", 129 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 130 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 131 | "requires": { 132 | "jsbn": "~0.1.0", 133 | "safer-buffer": "^2.1.0" 134 | } 135 | }, 136 | "extend": { 137 | "version": "3.0.2", 138 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 139 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 140 | }, 141 | "extsprintf": { 142 | "version": "1.3.0", 143 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 144 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 145 | }, 146 | "fast-deep-equal": { 147 | "version": "2.0.1", 148 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 149 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 150 | }, 151 | "fast-json-stable-stringify": { 152 | "version": "2.0.0", 153 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 154 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 155 | }, 156 | "forever-agent": { 157 | "version": "0.6.1", 158 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 159 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 160 | }, 161 | "form-data": { 162 | "version": "2.3.3", 163 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 164 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 165 | "requires": { 166 | "asynckit": "^0.4.0", 167 | "combined-stream": "^1.0.6", 168 | "mime-types": "^2.1.12" 169 | } 170 | }, 171 | "getpass": { 172 | "version": "0.1.7", 173 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 174 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 175 | "requires": { 176 | "assert-plus": "^1.0.0" 177 | } 178 | }, 179 | "har-schema": { 180 | "version": "2.0.0", 181 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 182 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 183 | }, 184 | "har-validator": { 185 | "version": "5.1.3", 186 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 187 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 188 | "requires": { 189 | "ajv": "^6.5.5", 190 | "har-schema": "^2.0.0" 191 | } 192 | }, 193 | "has-color": { 194 | "version": "0.1.7", 195 | "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", 196 | "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=" 197 | }, 198 | "http-signature": { 199 | "version": "1.2.0", 200 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 201 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 202 | "requires": { 203 | "assert-plus": "^1.0.0", 204 | "jsprim": "^1.2.2", 205 | "sshpk": "^1.7.0" 206 | } 207 | }, 208 | "is-typedarray": { 209 | "version": "1.0.0", 210 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 211 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 212 | }, 213 | "isstream": { 214 | "version": "0.1.2", 215 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 216 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 217 | }, 218 | "jsbn": { 219 | "version": "0.1.1", 220 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 221 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 222 | }, 223 | "json-schema": { 224 | "version": "0.2.3", 225 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 226 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 227 | }, 228 | "json-schema-traverse": { 229 | "version": "0.4.1", 230 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 231 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 232 | }, 233 | "json-stringify-safe": { 234 | "version": "5.0.1", 235 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 236 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 237 | }, 238 | "jsprim": { 239 | "version": "1.4.1", 240 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 241 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 242 | "requires": { 243 | "assert-plus": "1.0.0", 244 | "extsprintf": "1.3.0", 245 | "json-schema": "0.2.3", 246 | "verror": "1.10.0" 247 | } 248 | }, 249 | "merge": { 250 | "version": "1.2.1", 251 | "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", 252 | "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==" 253 | }, 254 | "mime": { 255 | "version": "1.6.0", 256 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 257 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 258 | }, 259 | "mime-db": { 260 | "version": "1.40.0", 261 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 262 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" 263 | }, 264 | "mime-types": { 265 | "version": "2.1.24", 266 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 267 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 268 | "requires": { 269 | "mime-db": "1.40.0" 270 | } 271 | }, 272 | "ms": { 273 | "version": "2.1.2", 274 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 275 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 276 | }, 277 | "oauth-sign": { 278 | "version": "0.9.0", 279 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 280 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 281 | }, 282 | "performance-now": { 283 | "version": "2.1.0", 284 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 285 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 286 | }, 287 | "psl": { 288 | "version": "1.4.0", 289 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", 290 | "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" 291 | }, 292 | "punycode": { 293 | "version": "2.1.1", 294 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 295 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 296 | }, 297 | "qs": { 298 | "version": "6.5.2", 299 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 300 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 301 | }, 302 | "request": { 303 | "version": "2.88.0", 304 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 305 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 306 | "requires": { 307 | "aws-sign2": "~0.7.0", 308 | "aws4": "^1.8.0", 309 | "caseless": "~0.12.0", 310 | "combined-stream": "~1.0.6", 311 | "extend": "~3.0.2", 312 | "forever-agent": "~0.6.1", 313 | "form-data": "~2.3.2", 314 | "har-validator": "~5.1.0", 315 | "http-signature": "~1.2.0", 316 | "is-typedarray": "~1.0.0", 317 | "isstream": "~0.1.2", 318 | "json-stringify-safe": "~5.0.1", 319 | "mime-types": "~2.1.19", 320 | "oauth-sign": "~0.9.0", 321 | "performance-now": "^2.1.0", 322 | "qs": "~6.5.2", 323 | "safe-buffer": "^5.1.2", 324 | "tough-cookie": "~2.4.3", 325 | "tunnel-agent": "^0.6.0", 326 | "uuid": "^3.3.2" 327 | } 328 | }, 329 | "safe-buffer": { 330 | "version": "5.2.0", 331 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 332 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 333 | }, 334 | "safer-buffer": { 335 | "version": "2.1.2", 336 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 337 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 338 | }, 339 | "sshpk": { 340 | "version": "1.16.1", 341 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 342 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 343 | "requires": { 344 | "asn1": "~0.2.3", 345 | "assert-plus": "^1.0.0", 346 | "bcrypt-pbkdf": "^1.0.0", 347 | "dashdash": "^1.12.0", 348 | "ecc-jsbn": "~0.1.1", 349 | "getpass": "^0.1.1", 350 | "jsbn": "~0.1.0", 351 | "safer-buffer": "^2.0.2", 352 | "tweetnacl": "~0.14.0" 353 | } 354 | }, 355 | "strip-ansi": { 356 | "version": "0.1.1", 357 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", 358 | "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=" 359 | }, 360 | "tough-cookie": { 361 | "version": "2.4.3", 362 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 363 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 364 | "requires": { 365 | "psl": "^1.1.24", 366 | "punycode": "^1.4.1" 367 | }, 368 | "dependencies": { 369 | "punycode": { 370 | "version": "1.4.1", 371 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 372 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 373 | } 374 | } 375 | }, 376 | "tunnel-agent": { 377 | "version": "0.6.0", 378 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 379 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 380 | "requires": { 381 | "safe-buffer": "^5.0.1" 382 | } 383 | }, 384 | "tweetnacl": { 385 | "version": "0.14.5", 386 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 387 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 388 | }, 389 | "twit": { 390 | "version": "2.2.11", 391 | "resolved": "https://registry.npmjs.org/twit/-/twit-2.2.11.tgz", 392 | "integrity": "sha512-BkdwvZGRVoUTcEBp0zuocuqfih4LB+kEFUWkWJOVBg6pAE9Ebv9vmsYTTrfXleZGf45Bj5H3A1/O9YhF2uSYNg==", 393 | "requires": { 394 | "bluebird": "^3.1.5", 395 | "mime": "^1.3.4", 396 | "request": "^2.68.0" 397 | } 398 | }, 399 | "uri-js": { 400 | "version": "4.2.2", 401 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 402 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 403 | "requires": { 404 | "punycode": "^2.1.0" 405 | } 406 | }, 407 | "uuid": { 408 | "version": "3.3.3", 409 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", 410 | "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" 411 | }, 412 | "verror": { 413 | "version": "1.10.0", 414 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 415 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 416 | "requires": { 417 | "assert-plus": "^1.0.0", 418 | "core-util-is": "1.0.2", 419 | "extsprintf": "^1.2.0" 420 | } 421 | } 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "0244-untweeps", 3 | "version": "1.0.0", 4 | "description": "Unfollow Stale Accounts", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "diskdb": "^0.1.17", 13 | "dotenv": "^8.2.0", 14 | "ms": "^2.1.2", 15 | "twit": "^2.2.11" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Requirements: 2 | 3 | * Node 12.9.0+ because it uses Promise.allSettled() 4 | * Twitter dev keys (more below) 5 | * A good attitude 6 | 7 | # Unfollow Stale accounts 8 | 9 | You'll need a developer keys from twitter → https://developer.twitter.com/en/apps - this might be hard because they don't just hand out keys anymore. You need to apply if you don't have a legacy app from before the crackdown. 10 | 11 | Put your keys in a file called `.env` like this: 12 | 13 | ```bash 14 | username=wesbos 15 | consumer_key=wowowowo 16 | consumer_secret=yayayaay 17 | access_token=teeheee 18 | access_token_secret=errrrnngggg 19 | ``` 20 | 21 | Set the `cutLimit` variable in `index.js` and then run it with `npm start`. 22 | 23 | ## Some things to note 24 | 25 | Rate limits on twitter are real. If you follow more than 3000 people, you'll hit the 200 x 15 rate limit. 26 | 27 | You could fix this by `npm install waait` 28 | 29 | then: 30 | 31 | ``` 32 | const wait = require('waait'); 33 | // ... then in your getListOfPeopleYouFollow function, add this: 34 | if (data.next_cursor) { 35 | // wait 1 min 36 | await wait(60000); 37 | getListOfPeopleYouFollow(data.next_cursor); 38 | ``` 39 | -------------------------------------------------------------------------------- /twit.js: -------------------------------------------------------------------------------- 1 | const Twit = require('twit'); 2 | 3 | const T = new Twit({ 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 | }); 9 | 10 | module.exports = T; 11 | --------------------------------------------------------------------------------