Last active
February 28, 2024 05:11
-
-
Save james-harper/7e076763312db9be30d3f33fcaa1472c to your computer and use it in GitHub Desktop.
Instagram.com - Unsend All Messages
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
// instagram.com utilities | |
// | |
// currently I only have unsend all messages in thread implemented. | |
// but may add more if I need them | |
// (this won't work in incognito due to localStorage, session, & cookie values being required) | |
// | |
// To use, navigate to the thread in a web browser (I only tested with Chrome) | |
// and copy and paste the code into a Developer tools console | |
// "threadId" will need to be updated with the appropriate value | |
class InstagramHelper { | |
constructor(userId = null) { | |
this.userId = userId || this.getCookie("ds_user_id"); | |
this.prevCursor = ""; | |
this.oldestCursor = ""; | |
} | |
/** | |
* Pause execution for n milliseconds | |
* This is useful for avoiding Rate Limiting errors | |
* @param {number} ms Number of ms to pause for | |
*/ | |
sleep(ms) { | |
const end = Date.now() + ms; | |
while (Date.now() < end) { continue }; | |
} | |
/** | |
* Get cookie value if it exists, | |
* Return empty string otherwise | |
* @param {string} name Key to lookup | |
* @returns {string} Value of cookie | |
*/ | |
getCookie(name) { | |
if (document.cookie.length > 0) { | |
let start = document.cookie.indexOf(name + "="); | |
if (start != -1) { | |
start = start + name.length + 1; | |
let end = document.cookie.indexOf(";", start); | |
if (end == -1) { | |
end = document.cookie.length; | |
} | |
return unescape(document.cookie.substring(start, end)); | |
} | |
} | |
return ""; | |
} | |
/** | |
* Unsend all messages in the given thread | |
* (May need to be run a few times to completely remove everything) | |
* (I'm too lazy to find the optimal interval to avoid 429 errors - rate limits) | |
* | |
* @param {string?} threadId The thread ID | |
* @param {number} delay Number of ms between each delete request | |
* @returns {boolean} Were all messages successfully deleted | |
*/ | |
async unsendAll(threadId = undefined, delay = 3500) { | |
if (threadId == null || threadId == undefined) { | |
// Get the chat id automatically from the url | |
var threadId = window.location.href.split('/')[5]; | |
console.warn("Starting deleting from thread Id : " + threadId); | |
} | |
const threadLink = "https://www.instagram.com/direct/t/" + threadId; | |
// Array of message ids | |
const itemIds = []; | |
// Array of delete message ids | |
const deletedItemIds = []; | |
// Messages hash (keyed by id) | |
const messages = {}; | |
while (this.prevCursor != "MINCURSOR") { | |
let getMessageAPIUrl = "https://i.instagram.com/api/v1/direct_v2/threads/" + threadId + "/"; | |
if (this.oldestCursor != undefined && this.oldestCursor != null && this.oldestCursor.length > 0) { | |
getMessageAPIUrl = getMessageAPIUrl + "?cursor=" + this.oldestCursor + ""; | |
} | |
const requestOptions = { | |
"credentials": "include", | |
"headers": { | |
"accept": "*/*", | |
"accept-encoding": "gzip, deflate, br", | |
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8", | |
"sec-fetch-dest": "empty", | |
"sec-fetch-mode": "cors", | |
"sec-fetch-site": "same-site", | |
"x-ig-app-id": localStorage.getItem("instagramWebFBAppId"), | |
"x-ig-www-claim": sessionStorage.getItem("www-claim-v2"), | |
"x-requested-with": "XMLHttpRequest", | |
"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 105.0.0.11.118 (iPhone11,8; iOS 12_3_1; en_US; en-US; scale=2.00; 828x1792; 165586599)" | |
}, | |
"referrer": threadLink, | |
"referrerPolicy": "no-referrer-when-downgrade", | |
"body": null, | |
"method": "GET", | |
"mode": "cors" | |
}; | |
await fetch( | |
getMessageAPIUrl, | |
requestOptions | |
) | |
.then((response) => { | |
if (response.status != 200) { | |
throw response.status; | |
} | |
return response.json(); | |
}).then(async (data) => { | |
console.log("getting messages..."); | |
data.thread.items.forEach(element => { | |
if (element.user_id.toString() == this.userId.toString()) { | |
if (!itemIds.includes(element.item_id.toString())) { | |
// Keep track of just ids | |
itemIds.push(element.item_id.toString()); | |
// Keep track of the full message in case we need it later | |
messages[element.item_id.toString()] = element; | |
} | |
} | |
}); | |
console.log('About to delete', itemIds) | |
//#region Deleting Messages | |
for (let itemIdIndex = 0; itemIdIndex < itemIds.length; itemIdIndex++) { | |
const messageItemId = itemIds[itemIdIndex]; | |
let alreadyDeleted = false; | |
let rateLimitReached = false; | |
const unsendRequestOptions = { | |
"credentials": "include", | |
"headers": { | |
"accept": "*/*", | |
"accept-encoding": "gzip, deflate, br", | |
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8", | |
"content-type": "application/x-www-form-urlencoded", | |
"sec-fetch-dest": "empty", | |
"sec-fetch-mode": "cors", | |
"sec-fetch-site": "same-site", | |
"x-csrftoken": this.getCookie("csrftoken"), | |
"x-ig-app-id": localStorage.getItem("instagramWebFBAppId"), | |
"x-ig-www-claim": sessionStorage.getItem("www-claim-v2"), | |
"x-requested-with": "XMLHttpRequest", | |
"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 105.0.0.11.118 (iPhone11,8; iOS 12_3_1; en_US; en-US; scale=2.00; 828x1792; 165586599)" | |
}, | |
"referrer": threadLink, | |
"referrerPolicy": "no-referrer-when-downgrade", | |
"body": null, | |
"method": "POST", | |
"mode": "cors" | |
}; | |
alreadyDeleted = deletedItemIds.includes(messageItemId); | |
if (!alreadyDeleted) { | |
await fetch( | |
"https://i.instagram.com/api/v1/direct_v2/threads/" + threadId + "/items/" + messageItemId + "/delete/", | |
unsendRequestOptions | |
).then((response) => { | |
const message = messages[messageItemId]; | |
rateLimitReached = response.status === 429; | |
if (response.status != 200) { | |
console.error(`Message skipped... ${messageItemId}: ${message.text}`); | |
console.log(response); | |
} else { | |
console.info(`Deleting... ${messageItemId}: ${message.text}`); | |
deletedItemIds.push(messageItemId); | |
} | |
if (rateLimitReached) { | |
// Add item to end of list so it can be retried later | |
itemIds.push(messageItemId); | |
} | |
}).catch((error) => { | |
console.error(error); | |
}); | |
} else { | |
console.log(`Skipping: ${messageItemId} - Already deleted`) | |
} | |
let sleepFor = rateLimitReached ? 6000 : (alreadyDeleted ? 0 : delay); | |
this.sleep(sleepFor); | |
} | |
//#endregion Deleting Messages | |
console.log('Done deleting batch'); | |
this.oldestCursor = data.thread.oldest_cursor; | |
this.prevCursor = data.thread.prev_cursor; | |
}); | |
} | |
console.warn("All messages deleted."); | |
return itemIds.length === deletedItemIds.length; | |
} | |
} | |
// Navigate to your inbox on Instagram and go to the thread you want to delete | |
// The URL should be something like: | |
// https://www.instagram.com/direct/t/12345678901234567890 | |
// The long list of numbers at the end is the thread id | |
// Because it is such a long number, | |
// it needs to be used as a string in this script | |
let threadId = "123456789012345678901234567890123456789"; | |
let Instagram = new InstagramHelper(); | |
Instagram.unsendAll(threadId, 1500); | |
// If no threadId is passed, we will try and guess it from the url | |
// So if this code is executed on the page containing the messages that need | |
// deleting it is sufficient to just use the following: | |
// const ig = new InstagramHelper; ig.unsendAll(); |
Also there is not "x-ig-app-id" in local storage and I hard coded it.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Also getting threads is not possible without sending CSRF token.