-
-
Save IljaGrebel/4bf1c78f22342d76b19c9aaca1c1890d to your computer and use it in GitHub Desktop.
Delete batch messages Microsoft Teams
This file contains hidden or 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
| /* | |
| 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 | |
| */ |
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
/*
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));
};
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;
};
const scanMessages = () => {
const found = Array.from(document.querySelectorAll("div[data-mid]"))
.reverse()
.filter(m => !queue.includes(m));
};
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 });
};
start();
})();