Last active
March 7, 2021 19:30
-
-
Save aravindanve/2800fe099b43fe279d776438f10e37ef to your computer and use it in GitHub Desktop.
Unsaved all saved instagram posts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* unsaveAllInstagramPosts.js | |
* usage: visit https://instagram.com and run in the browser console (you must be logged in) | |
* version: 2021-03-07 | |
* issues: does not unsave sponsored posts | |
*/ | |
unsaveAllInstagramPosts = async () => { | |
const baseUrl = 'https://www.instagram.com'; | |
// get user id and csrf token from cookie | |
const userId = document.cookie.match(/ds_user_id=([^;]+);/)[1]; | |
const csrftoken = document.cookie.match(/csrftoken=([^;]+);/)[1]; | |
// set request options | |
const credentials = 'include'; | |
const headers = { | |
'x-csrftoken': csrftoken, | |
}; | |
// text parser | |
const text = (it) => it.text(); | |
// sleep | |
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | |
// get possible query hashes for saved posts query | |
const queryHashes = await (async () => { | |
const baseBody = await fetch(baseUrl, { | |
method: 'GET', | |
credentials, | |
}).then(text); | |
const jsBundlePath = baseBody.match( | |
/(\/static\/bundles\/es6\/Consumer\.js[^"]+)/, | |
)[1]; | |
const jsBundleUrl = baseUrl + jsBundlePath; | |
const jsBundleBody = await fetch(jsBundleUrl, { | |
method: 'GET', | |
credentials, | |
}).then(text); | |
// console.log({ jsBundleBody }); | |
const queryIds = jsBundleBody | |
.match(/queryId:"[^"]*"/g) | |
.map((it) => it.match(/queryId:"([^"]*)"/)[1]); | |
// console.log({ queryIds }); | |
return queryIds; | |
})(); | |
// saved posts getter | |
const getSavedPosts = async (first, after) => { | |
console.log(`Fetching ${first} posts${after ? ` after ${after}` : ''}`); | |
for (const queryHash of queryHashes) { | |
const initialVariables = encodeURI( | |
JSON.stringify({ id: userId, first, after }), | |
); | |
const initialSavedPostsQueryUrl = | |
`${baseUrl}/graphql/query/` + | |
`?query_hash=${queryHash}&variables=${initialVariables}`; | |
let result = await fetch(initialSavedPostsQueryUrl, { | |
method: 'GET', | |
credentials, | |
headers, | |
}) | |
.then((it) => it.json()) | |
.catch(() => null); | |
if (result && result.data.user.edge_saved_media) { | |
return result.data.user.edge_saved_media; | |
} | |
} | |
}; | |
let cursor; | |
while (true) { | |
const posts = await getSavedPosts(100, cursor); | |
console.log(`Saved posts remaining: ${posts.count}`); | |
if (posts.edges.length) { | |
cursor = undefined; | |
console.log(`Deleting ${posts.edges.length} posts`); | |
for (const postId of posts.edges.map((it) => it.node.id)) { | |
console.log(`Deleting post ${postId}`); | |
const unsavePostUrl = `https://www.instagram.com/web/save/${postId}/unsave/`; | |
await fetch(unsavePostUrl, { | |
method: 'POST', | |
credentials, | |
headers, | |
}); | |
} | |
} else if (posts.page_info.has_next_page) { | |
cursor = posts.page_info.end_cursor; | |
console.log('No posts on this page, sleeping to avoid 429 errors'); | |
await sleep(5000); | |
continue; | |
} else { | |
console.log('No more posts to delete!'); | |
break; | |
} | |
} | |
}; | |
unsaveAllInstagramPosts().then(console.log).catch(console.error); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment