Skip to content

Instantly share code, notes, and snippets.

@IljaGrebel
Forked from kiki67100/Delete-Teams-Messages.js
Last active October 13, 2025 11:56
Show Gist options
  • Select an option

  • Save IljaGrebel/4bf1c78f22342d76b19c9aaca1c1890d to your computer and use it in GitHub Desktop.

Select an option

Save IljaGrebel/4bf1c78f22342d76b19c9aaca1c1890d to your computer and use it in GitHub Desktop.
Delete batch messages Microsoft Teams
/*
This version is probably out of date, look at the dsci4-hacks ( Thanks to him x) ) instead:
*** not tested ***
https://github.com/dsci4-hacks/Delete-Teams-Messages/blob/main/delete_my_teams_messages.js
Thank you for all your contributions in comments
**** Original ****
By kiki67100 https://gist.github.com/kiki67100
Tested on Google Chrome
Open Microsoft teams web version, select a conversation
Open Developper tools and copy / paste (Change YOUR_NAME by your)
Work on March 2020 maybe no work on the future...
*/
/*
Delete Teams Messages – robust v2.1 (2025-09)
*/
(function () {
console.log("[Automation Script] Start.");
// --- Config ---
const DELETE_INTERVAL_MS = 2000;
const MAX_RETRIES = 3;
const SCAN_INTERVAL_MS = 400;
// -- Add here how your "delete" button is called in Teams, if you use another language
const DELETE_LABELS = [
"delete", "remove",
"löschen", "nachricht löschen",
];
const processed = new Set();
const retryMap = new Map();
const queue = [];
let isProcessing = false;
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const normalizeText = (el) => (el.textContent || "")
.replace(/\s+/g, " ")
.trim()
.toLowerCase();
const waitFor = (selector, timeout = 5000, root = document) =>
new Promise((resolve, reject) => {
const t0 = performance.now();
const tick = () => {
const el = root.querySelector(selector);
if (el) return resolve(el);
if (performance.now() - t0 >= timeout) return reject(new Error(`waitFor timeout: ${selector}`));
requestAnimationFrame(tick);
};
tick();
});
const getScrollContainer = () => {
const all = Array.from(document.querySelectorAll("div"));
for (const el of all) {
const cs = getComputedStyle(el);
if ((cs.overflowY === "auto" || cs.overflowY === "scroll") && el.querySelector("[data-mid]")) {
return el;
}
}
return document.scrollingElement || document.body;
};
const scrollToLoadMessages = () => {
const sc = getScrollContainer();
sc.scrollBy({ top: -400, behavior: "auto" });
console.log("[Automation Script] Scrolled up to load more.");
};
const openActionsMenu = async (msg) => {
const candidates = [
'button[aria-label*="More"]',
'button[aria-label*="Option"]',
'button[title*="More"]',
'[data-tid*="action"][role="button"]',
'[data-tid*="overflow"][role="button"]'
];
for (const sel of candidates) {
const btn = msg.querySelector(sel);
if (btn) {
btn.click();
return true;
}
}
const rect = msg.getBoundingClientRect();
const cx = rect.left + Math.min(40, Math.max(8, rect.width * 0.2));
const cy = rect.top + Math.min(rect.height - 10, Math.max(12, rect.height * 0.5));
const evt = new MouseEvent("contextmenu", {
bubbles: true, cancelable: true, view: window, button: 2, clientX: cx, clientY: cy
});
msg.dispatchEvent(evt);
return true;
};
const findDeleteMenuItem = () => {
const menus = Array.from(document.querySelectorAll('[role="menu"], [data-tid*="context"], .fui-Menu__listbox'));
for (const menu of menus) {
const items = Array.from(menu.querySelectorAll('[role="menuitem"], [role="option"], button, div'));
for (const it of items) {
const t = normalizeText(it);
if (DELETE_LABELS.some(lbl => t === lbl)) return it;
if (DELETE_LABELS.some(lbl => t.includes(lbl))) return it;
}
}
return null;
};
const confirmIfPrompt = async () => {
const candidates = Array.from(document.querySelectorAll('button, [role="button"]'));
for (const b of candidates) {
const t = normalizeText(b);
if (["delete", "löschen", "удалить"].includes(t)) {
b.click();
return true;
}
}
return false;
};
const isAlreadyDeleted = (msg) =>
!!msg.querySelector("[data-tid=message-undo-delete-btn]");
const processMessage = async (msg) => {
if (!msg) return;
const mid = msg.getAttribute("data-mid") || "(no-mid)";
if (processed.has(mid)) return;
try {
msg.style.outline = "3px solid #2d5af1";
if (isAlreadyDeleted(msg)) {
console.log(`[Automation Script] Message ${mid} already deleted (Undo visible).`);
processed.add(mid);
msg.style.outline = "3px solid #2ecc71";
return;
}
await openActionsMenu(msg);
await sleep(150);
const delItem = await (async () => {
const t0 = performance.now();
while (performance.now() - t0 < 5000) {
const found = findDeleteMenuItem();
if (found) return found;
await sleep(100);
}
return null;
})();
if (!delItem) {
console.warn(`[Automation Script] Delete menu item NOT found for ${mid} (likely not your message or UI changed).`);
processed.add(mid);
msg.style.outline = "3px dashed #ff9800";
return;
}
delItem.click();
await sleep(120);
await confirmIfPrompt();
await sleep(250);
processed.add(mid);
msg.style.outline = "3px solid #2ecc71";
console.log(`[Automation Script] Deleted message ID: ${mid}`);
} catch (err) {
console.error(`[Automation Script] Error processing message ID: ${mid}`, err);
const retries = retryMap.get(mid) || 0;
if (retries < MAX_RETRIES) {
retryMap.set(mid, retries + 1);
queue.push(msg);
msg.style.outline = "3px solid #e53935";
console.log(`[Automation Script] Retrying ${mid} (${retries + 1}/${MAX_RETRIES})`);
} else {
retryMap.delete(mid);
processed.add(mid);
msg.style.outline = "3px dashed #e53935";
console.warn(`[Automation Script] Skip ${mid} after ${MAX_RETRIES} retries.`);
}
}
await sleep(DELETE_INTERVAL_MS);
};
const scanMessages = () => {
const found = Array.from(document.querySelectorAll("div[data-mid]"))
.reverse()
.filter(m => !processed.has(m.getAttribute("data-mid")) && !queue.includes(m));
if (found.length) {
console.log(`[Automation Script] Found ${found.length} messages to process.`);
queue.push(...found);
}
};
const processQueue = async () => {
if (isProcessing) return;
isProcessing = true;
try {
while (queue.length) {
const m = queue.shift();
await processMessage(m);
}
} finally {
isProcessing = false;
}
};
const start = () => {
console.log("[Automation Script] Running…");
const obs = new MutationObserver(() => {
scanMessages();
if (!isProcessing) processQueue();
});
obs.observe(document.body, { childList: true, subtree: true });
scanMessages();
processQueue();
setInterval(() => {
if (queue.length === 0 && !isProcessing) {
scrollToLoadMessages();
scanMessages();
}
}, SCAN_INTERVAL_MS);
};
start();
})();
/*
- Extract messages id
On Google Chrome developper tools :
copy(id.join("\n"))
Delete a message, copy the ajax request and export as CURL (bash) (on network tools maybe end by "softDelete")
On notepad ++ on replace regex mode
REPLACE (.*)
Your CURL shell command find the id curl 'https://emea.[...]/121545326566?behavior=softDelete by $1 like this 'https://emea.[...]/$1?behavior=softDelete
lauch on linux terminal all lines
*/
@ctlaltdelete
Copy link

ctlaltdelete commented Oct 13, 2025

/*
Brute Force Delete All Messages in Teams – Ultra Fast v2.5 (2025-09)
*/

(function () {
console.log("[Automation Script] Start.");

const DELETE_INTERVAL_MS = 100; // Further reduced interval for processing each message
const SCAN_INTERVAL_MS = 50; // Further reduced interval for scanning messages

// -- Labels for delete action in various languages
const DELETE_LABELS = [
"delete", "remove",
"löschen", "nachricht löschen",
];

const queue = [];
let isProcessing = false;

const sleep = (ms) => new Promise(r => setTimeout(r, ms));

const normalizeText = (el) => (el.textContent || "")
.replace(/\s+/g, " ")
.trim()
.toLowerCase();

const getScrollContainer = () => {
const all = Array.from(document.querySelectorAll("div"));
for (const el of all) {
const cs = getComputedStyle(el);
if ((cs.overflowY === "auto" || cs.overflowY === "scroll") && el.querySelector("[data-mid]")) {
return el;
}
}
return document.scrollingElement || document.body;
};

const scrollToLoadMessages = () => {
const sc = getScrollContainer();
sc.scrollBy({ top: -400, behavior: "auto" });
console.log("[Automation Script] Scrolled up to load more messages.");
};

const rightClickAndDelete = async (msg) => {
const rect = msg.getBoundingClientRect();
const cx = rect.left + Math.min(40, Math.max(8, rect.width * 0.2));
const cy = rect.top + Math.min(rect.height - 10, Math.max(12, rect.height * 0.5));

// Create and dispatch the right-click event to open the context menu
const evt = new MouseEvent("contextmenu", {
  bubbles: true, cancelable: true, view: window, button: 2, clientX: cx, clientY: cy
});
msg.dispatchEvent(evt);

await sleep(100); // Reduced wait time for the context menu to open

const delItem = findDeleteMenuItem();
if (delItem) {
  delItem.click(); // Click delete immediately
  await sleep(80); // Wait for the action to be processed
  console.log(`[Automation Script] Deleted message ID: ${msg.getAttribute("data-mid")}`);
} else {
  console.warn("[Automation Script] Delete menu item not found.");
}

};

const findDeleteMenuItem = () => {
const menus = Array.from(document.querySelectorAll('[role="menu"], [data-tid*="context"], .fui-Menu__listbox'));
for (const menu of menus) {
const items = Array.from(menu.querySelectorAll('[role="menuitem"], [role="option"], button, div'));
for (const it of items) {
const t = normalizeText(it);
if (DELETE_LABELS.some(lbl => t === lbl) || DELETE_LABELS.some(lbl => t.includes(lbl))) {
return it;
}
}
}
return null;
};

const processMessage = async (msg) => {
if (!msg) return;

try {
  await rightClickAndDelete(msg); // Open options menu and delete message
} catch (err) {
  console.error("[Automation Script] Error processing message:", err);
}

await sleep(DELETE_INTERVAL_MS);

};

const scanMessages = () => {
const found = Array.from(document.querySelectorAll("div[data-mid]"))
.reverse()
.filter(m => !queue.includes(m));

if (found.length) {
  console.log(`[Automation Script] Found ${found.length} messages to process.`);
  queue.push(...found);
}

};

const processQueue = async () => {
if (isProcessing) return;
isProcessing = true;
try {
while (queue.length) {
const m = queue.shift();
await processMessage(m);
}
} finally {
isProcessing = false;
}
};

const start = () => {
console.log("[Automation Script] Running…");
const obs = new MutationObserver(() => {
scanMessages();
if (!isProcessing) processQueue();
});
obs.observe(document.body, { childList: true, subtree: true });

scanMessages();
processQueue();

setInterval(() => {
  if (queue.length === 0 && !isProcessing) {
    scrollToLoadMessages();
    scanMessages();
  }
}, SCAN_INTERVAL_MS);

};

start();
})();

@ctlaltdelete
Copy link

Modified the script to take into account the changes in Teams since the last update. Open console (F12), paste and enter. Let it run.

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