I wrote this script to wrest some control over the content I see from the Twitter algorithm.
It mutes everyone I follow and un-mutes 30 random people.
You can run it as a scheduled Firebase Cloud Function so you have a new feed every day.
I wrote this script to wrest some control over the content I see from the Twitter algorithm.
It mutes everyone I follow and un-mutes 30 random people.
You can run it as a scheduled Firebase Cloud Function so you have a new feed every day.
| // First you need to generate a pair of OAuth client tokens that give | |
| // you access to your Twitter account. | |
| // | |
| // 1. Create a Twitter developer application | |
| // 2. Get the oauth key pair | |
| // 3. Enter it in the OAuth constructor below | |
| // 4. Run the script | |
| // | |
| // Read more here: https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/obtaining-user-access-tokens | |
| const { OAuth } = require("oauth"); | |
| const { createInterface } = require("readline"); | |
| const client = new OAuth( | |
| "https://api.twitter.com/oauth/request_token", | |
| "https://api.twitter.com/oauth/access_token", | |
| "<oauth_consumer_key>", | |
| "<oauth_consumer_secret>", | |
| "1.0A", | |
| null, | |
| "HMAC-SHA1" | |
| ); | |
| const fetchToken = () => { | |
| const stdin = createInterface(process.stdin); | |
| client.getOAuthRequestToken( | |
| { oauth_callback: "oob" }, | |
| (error, token, secret) => { | |
| if (error) { | |
| console.error(error); | |
| stdin.close(); | |
| return; | |
| } | |
| const url = "https://twitter.com/oauth/authenticate?oauth_token=" + token; | |
| console.log("Go to " + url + " and enter the code here:"); | |
| stdin.question("> ", verifier => { | |
| client.getOAuthAccessToken( | |
| token, | |
| secret, | |
| verifier, | |
| (err, userToken, userSecret) => { | |
| if (err) { | |
| console.error(err); | |
| stdin.close(); | |
| return; | |
| } | |
| console.log(`userToken = "${userToken}"`); | |
| console.log(`userSecret = "${userSecret}"`); | |
| stdin.close(); | |
| } | |
| ); | |
| }); | |
| } | |
| ); | |
| }; | |
| fetchToken(); |
| // Run this script with the user credentials generated in the previous step. | |
| // It will unmute 30 random people you follow and mute everyone else. | |
| const { OAuth } = require("oauth"); | |
| const client = new OAuth( | |
| "https://api.twitter.com/oauth/request_token", | |
| "https://api.twitter.com/oauth/access_token", | |
| "<twitter oauth token>", | |
| "<twitter oauth secret>", | |
| "1.0A", | |
| null, | |
| "HMAC-SHA1" | |
| ); | |
| const yourTwitterHandle = "<your twitter handle without the @>"; | |
| const numToUnmute = 30; | |
| const userToken = "<client token from previous step>"; | |
| const userSecret = "<client secret from previous step>"; | |
| // Twitter API methods | |
| const send = (method, url) => { | |
| return new Promise((resolve, reject) => { | |
| client.getProtectedResource( | |
| "https://api.twitter.com/1.1" + url, | |
| method, | |
| userToken, | |
| userSecret, | |
| (err, data) => { | |
| if (err) { | |
| reject(err); | |
| } else { | |
| resolve(JSON.parse(data.toString())); | |
| } | |
| } | |
| ); | |
| }); | |
| }; | |
| const getMuted = () => send("GET", "/mutes/users/ids.json"); | |
| const getFollowing = screenName => | |
| send("GET", "/friends/ids.json?screen_name=" + screenName); | |
| const mute = id => send("POST", "/mutes/users/create.json?user_id=" + id); | |
| const unmute = id => send("POST", "/mutes/users/destroy.json?user_id=" + id); | |
| // Back-off / retry logic | |
| const sleep = duration => new Promise(resolve => setTimeout(resolve, duration)); | |
| const withRetry = async (func, maxRetries = 25) => { | |
| let retries = 0; | |
| while (retries < maxRetries) { | |
| try { | |
| await func(); | |
| return; | |
| } catch (error) { | |
| if (error.statusCode !== 429) { | |
| throw error; | |
| } | |
| retries++; | |
| } | |
| const backoff = Math.min(Math.pow(2, retries), 30) + Math.random(); | |
| await sleep(backoff * 1000); | |
| } | |
| throw new Error('timed out'); | |
| }; | |
| // This is the main function | |
| const switchTwitterMutes = async () => { | |
| console.log("fetching followers for", yourTwitterHandle); | |
| let { ids: following } = await getFollowing(yourTwitterHandle); | |
| const { ids: muted } = await getMuted(); | |
| // Shuffle list of people you follow | |
| following = following.sort(() => Math.random() - 0.5); | |
| // Choose 30 random people to un-mute | |
| const unmuted = []; | |
| while (unmuted.length < numToUnmute) { | |
| const id = following.pop(); | |
| try { | |
| await withRetry(() => unmute(id)); | |
| unmuted.push(id); | |
| console.log("unmuted userid", id); | |
| } catch (err) { | |
| console.warn("error unmuting userid", id, ":", err.statusCode); | |
| } | |
| await sleep(1000); | |
| } | |
| // Mute everyone else | |
| const toMute = following.filter( | |
| id => !muted.includes(id) && !unmuted.includes(id) | |
| ); | |
| for (const id of toMute) { | |
| try { | |
| await withRetry(() => mute(id)); | |
| console.log("muted userid", id); | |
| } catch (error) { | |
| console.warn("error: muting userid", id, ":", error.statusCode); | |
| } | |
| await sleep(1000); | |
| } | |
| console.log("Done!"); | |
| }; | |
| switchTwitterMutes().catch(error => console.error(error)); | |
| // To run the script every day, add this code to a Firebase Cloud Function | |
| // then comment the previous line and uncomment the following lines: | |
| // | |
| // const functions = require("firebase-functions"); | |
| // | |
| // export const onCheckTwitter = functions.pubsub | |
| // .schedule("2 20 * * *") // run every day at 2:20am | |
| // .onRun(() => switchTwitterMutes()); |