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()); |