Skip to content

Instantly share code, notes, and snippets.

@innateessence
Last active December 12, 2025 02:39
Show Gist options
  • Select an option

  • Save innateessence/43706a983b0f6cbf3b16058ada373baa to your computer and use it in GitHub Desktop.

Select an option

Save innateessence/43706a983b0f6cbf3b16058ada373baa to your computer and use it in GitHub Desktop.
Unsend and remove all Facebook Messenger messages
const SHORT_DELAY = 100;
const MEDIUM_DELAY = 250;
const LONG_DELAY = 500;
let messageChatIdx = 0
let isRunning = false
let exceptionsInARow = 0
const exceptionsInARowUntilNextChat = 40
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function __getLastMessageMoreIcon() {
const elements = document.querySelectorAll('div[aria-label="More"]')
const lastElement = elements[elements.length - 1]
return lastElement
}
async function __unsendMessage() {
const element = document.querySelector('div[aria-label="Remove message"]');
if (element) {
await sleep(SHORT_DELAY);
element.click();
await sleep(SHORT_DELAY);
const removeButton = await __getRemoveButton();
await sleep(SHORT_DELAY);
removeButton.click();
}
}
async function __findElementsByAriaLabel(label) {
const elements = document.querySelectorAll(`[aria-label="${label}"]`);
return Array.from(elements);
}
async function __findElementsByAriaLabelSubstring(substring) {
const elements = document.querySelectorAll('[aria-label]');
const retval = Array.from(elements).filter(el =>
el.getAttribute('aria-label').includes(substring)
);
return retval
}
async function __findElementByAriaLabel(label) {
const elements = document.querySelectorAll(`[aria-label="${label}"]`);
if (Array.isArray(elements) && elements.length > 0) {
return elements[0]
}
return elements
}
async function __getRemoveButton() {
const elems = Array.from(await __findElementsByAriaLabel("Remove"))
if (elems.length === 0) {
console.warn("No remove button found")
return
}
for (let i = 0; i < elems.length; i++) {
const elem = elems[i]
if (elem.ariaDisabled === 'true') {
continue
}
if (elem.textContent === "Remove") {
return elem
}
}
}
async function __triggerMouseHover(element) {
const mouseOverEvent = new MouseEvent('mouseover', {
bubbles: true,
cancelable: true,
view: window,
});
element.dispatchEvent(mouseOverEvent);
}
async function __triggerMouseHoverExit(element) {
const mouseOutEvent = new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
view: window,
});
element.dispatchEvent(mouseOutEvent);
}
async function __unsendLastMessage() {
await sleep(SHORT_DELAY)
const moreIcon = await __getLastMessageMoreIcon()
moreIcon.click()
await sleep(SHORT_DELAY)
await __unsendMessage()
await sleep(SHORT_DELAY)
}
async function deleteLastMessage() {
await sleep(SHORT_DELAY)
let elems = await getAllMessageElements()
console.log(elems)
let lastElement = elems[elems.length - 1]
const lastElementText = lastElement?.textContent || ""
console.log("lastElement: ", lastElement)
const children = []
lastElement.childNodes.forEach((childNode) => {
children.push(childNode)
})
const targetElements = [lastElement].concat(...children)
console.log("targetElements: ", targetElements)
targetElements.forEach(async (elem) => {
try {
await __triggerMouseHover(elem)
} catch { }
})
await sleep(SHORT_DELAY)
try {
await __unsendLastMessage()
} catch (e) {
console.log("error", e)
}
await sleep(SHORT_DELAY)
targetElements.forEach(async (elem) => {
try {
await __triggerMouseHoverExit(elem)
} catch { }
})
await sleep(MEDIUM_DELAY)
elems = await getAllMessageElements()
const newLastElement = elems[elems.length - 1]
if (lastElement === newLastElement && lastElementText === newLastElement.textContent) {
lastElement?.remove()
}
await sleep(SHORT_DELAY)
}
async function getAllMessageElements() {
let conversationRoot = await __findElementsByAriaLabelSubstring("Conversation with")
if (conversationRoot.length === 0) {
console.warn("No conversation root found")
return
}
conversationRoot = conversationRoot[0]
const elems = conversationRoot.querySelectorAll('div[role="row"]')
return Array.from(elems)
}
async function removeErrorMessages() {
// get all DOM elements <div role="presentation">
const elems = Array.from(document.querySelectorAll('div[role="presentation"]'))
elems.forEach(async (elem) => {
if (elem && elem.textContent === 'Error displaying this message') {
elem?.remove()
await sleep(SHORT_DELAY)
}
})
}
async function clickOnChat(chatElem) {
const iterations = 5
for (let i = 0; i < iterations; i++) {
chatElem = chatElem.childNodes[0]
}
chatElem.scrollIntoView()
await sleep(SHORT_DELAY)
chatElem.click()
await sleep(LONG_DELAY)
}
async function getMessageChats() {
let elem = await __findElementsByAriaLabel("Chats")
const idx = elem.length - 1
elem = elem?.[idx]
while (elem.childNodes.length == 2 || elem.childNodes.length == 1) {
const idx = elem.childNodes.length - 1
const target = elem?.childNodes?.[idx]
if (target) {
elem = target
}
}
return Array.from(elem.childNodes)
}
async function Main(loopForever = false) {
isRunning = true
console.log("Starting... to stop, issue `isRunning = false`")
while (isRunning) {
if (exceptionsInARow > exceptionsInARowUntilNextChat) {
messageChatIdx++
try {
// we probably unsent all the messages
const chats = await getMessageChats()
if (messageChatIdx >= chats.length) {
console.log("Processed all chats!")
isRunning = false
return
}
await clickOnChat(chats[messageChatIdx])
exceptionsInARow = 0
} catch (e) {
console.log(e)
exceptionsInARow++
}
}
try {
console.count("Deleting last message")
await deleteLastMessage()
exceptionsInARow = 0
} catch (e) {
console.log(e)
exceptionsInARow++
}
await sleep(SHORT_DELAY)
await removeErrorMessages()
}
if (loopForever) {
await Restart(loopForever)
}
console.log("Done!")
}
async function Restart(loopForever = false) {
// reset globals
messageChatIdx = 0
isRunning = false
exceptionsInARow = 0
const chats = await getMessageChats()
await clickOnChat(chats[messageChatIdx]) // go back to first chat
await Main(loopForever) // process it again
}
@innateessence
Copy link
Author

Might not work for you because large companies like Facebook roll out different versions of things to different users at different times

@innateessence
Copy link
Author

innateessence commented Sep 17, 2024

If you run this in a tab that is not in focus for the given browser window, Facebook will not render things in the UI until the tab is in focus.

You can simply run this in a browser where it has only 1 tab to avoid this being an issue.
On certain OS's/browsers/environments you may need to keep the browser un-minimized, and in the same workspace that you're currently using.
If you don't know what a workspace is, you can probably just ignore that comment.

@innateessence
Copy link
Author

innateessence commented Sep 26, 2024

Archive any chat's you wish to save before running this.

This will delete every message in every chat by individually unsending/removing every message.

@innateessence
Copy link
Author

innateessence commented Sep 27, 2024

You may need to run this multiple times for two reasons:

  1. Unsending and deleting messages can change the order of the chats. And if you read the code, you'll see I wrote this with the assumption that the index is a 1-to-1 match with the chat position. If I rewrite this to load all chats in the DOM and iterate backwards this problem should go away entirely. Running it multiple times also solves the issue, albeit, less elegantly.

  2. Occasionally I find that the page just reloads itself for some unknown reason... So even if you tell it to loop infinitely, you may need to manually rerun this a few times to make sure it handles everything.

@vandakdominik
Copy link

it stopped working due to changes in UI so i made a fix. hope its ok. newer version works as of 8/22/2025 (25.8.2025) on:
https://github.com/vandakdominik/Messenger-massage-unsender-removal/tree/main

@innateessence
Copy link
Author

No worries, feel free to do whatever you want with the code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment