Highly efficient message deletion script. Now with customizable delay and batch sleep settings.
// ==UserScript==
// @name Undiscord
// @namespace Forked from "https://github.com/victornpb/undiscord", https://github.com/BeBubbled
// @version 8.4.1
// @description Highly efficient message deletion script. Now with customizable delay and batch sleep settings.
// @author BeBubble
// @license MIT
// @match *://discord.com/*
// @match *://*.discord.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
console.log("[Undiscord] Script loaded successfully. Initializing...");
const css = `
#undiscord-toggle-btn {
display: flex; align-items: center; justify-content: center;
background: transparent; color: var(--interactive-normal);
border: none; padding: 0 8px; margin: 0; cursor: pointer;
transition: color 0.2s ease; height: 24px;
}
#undiscord-toggle-btn:hover { color: var(--interactive-hover); }
#undiscord-toggle-btn svg { width: 24px; height: 24px; }
#undiscord-ui {
display: none; position: fixed; z-index: 2147483647; top: 60px; right: 20px; width: 440px;
background: var(--background-secondary, #2b2d31); color: var(--text-normal, #dbdee1);
border: 1px solid var(--background-tertiary, #1e1f22); border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.4); font-family: 'gg sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
flex-direction: column; overflow: hidden;
}
.u-header {
padding: 16px; background: var(--background-tertiary, #1e1f22); display: flex;
justify-content: space-between; align-items: center; border-bottom: 1px solid var(--background-modifier-accent, #111214);
user-select: none;
}
.u-header b { font-size: 16px; color: var(--header-primary, #f2f3f5); }
.u-body { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.u-label { font-size: 12px; font-weight: 700; color: var(--header-secondary, #b5bac1); text-transform: uppercase; margin-bottom: -6px; }
.u-label.warning { color: var(--text-danger, #fa777a); }
.u-input {
background: var(--input-background, #1e1f22); border: 1px solid transparent;
color: var(--text-normal, #dbdee1); padding: 10px; border-radius: 4px; box-sizing: border-box; font-size: 14px;
transition: border-color 0.2s; outline: none; width: 100%;
}
.u-input:focus { border-color: var(--text-link, #5865f2); }
.u-log {
background: var(--background-primary, #111214); height: 160px; overflow-y: auto;
font-size: 12px; font-family: 'Consolas', 'Courier New', monospace; padding: 8px;
border-radius: 4px; border: 1px solid var(--background-tertiary, #1e1f22); word-break: break-all; line-height: 1.4;
}
.u-log::-webkit-scrollbar { width: 8px; }
.u-log::-webkit-scrollbar-track { background: transparent; }
.u-log::-webkit-scrollbar-thumb { background: var(--scrollbar-auto-thumb, #313338); border-radius: 4px; }
.u-btn {
cursor: pointer; background: var(--brand-experiment, #5865f2); color: white; border: none;
padding: 10px; border-radius: 4px; font-weight: 600; font-size: 14px; transition: background 0.2s;
}
.u-btn:hover:not(:disabled) { background: var(--brand-experiment-hover, #4752c4); }
.u-btn:disabled { background: var(--background-modifier-accent, #4e5058); color: #80848e; cursor: not-allowed; }
.u-btn-stop { background: var(--button-danger-background, #da373c); margin-top: 4px; }
.u-btn-stop:hover:not(:disabled) { background: var(--button-danger-background-hover, #a1282c); }
.u-btn-secondary { background: var(--button-secondary-background, #4e5058); font-size: 12px; padding: 8px;}
.u-btn-secondary:hover:not(:disabled) { background: var(--button-secondary-background-hover, #6d6f78); }
.flex-row { display: flex; gap: 8px; }
.flex-row input { flex: 1; min-width: 0; }
.flex-row button { background: var(--button-positive-background, #23a559); font-size: 12px; padding: 0 12px; white-space: nowrap; }
.flex-row button:hover { background: var(--button-positive-background-hover, #1da24a); }
/* 修复了这里:去掉了负边距,增加了正确的间距 */
.delay-label { font-size: 11px; font-weight: bold; color: var(--header-secondary, #b5bac1); text-align: center; padding-bottom: 2px; text-transform: uppercase;}
`;
function initCore() {
if (document.getElementById('undiscord-style')) return;
const s = document.createElement('style');
s.id = 'undiscord-style';
s.textContent = css;
document.head.appendChild(s);
const container = document.createElement('div');
container.id = 'undiscord-ui';
container.innerHTML = `
<div class="u-header">
<b>Undiscord</b>
<button id="close" style="background:none;color:var(--interactive-normal);border:none;cursor:pointer;font-size:20px;display:flex;align-items:center;justify-content:center;">×</button>
</div>
<div class="u-body">
<label class="u-label warning">AUTH TOKEN (Plaintext - Do not share!)</label>
<div class="flex-row">
<input id="token" class="u-input" type="text" placeholder="Leave empty or fetch ->">
<button id="get-token-btn" class="u-btn">Fetch Token</button>
</div>
<div class="flex-row" style="margin-top: 4px;">
<div style="flex:1; display:flex; flex-direction:column; gap:4px;">
<label class="u-label">GUILD ID (Or @me)</label>
<input id="guildId" class="u-input" placeholder="Server ID">
</div>
<div style="flex:1; display:flex; flex-direction:column; gap:4px;">
<label class="u-label">CHANNEL ID</label>
<input id="channelId" class="u-input" placeholder="Channel ID">
</div>
</div>
<label class="u-label">AUTHOR ID</label>
<input id="authorId" class="u-input" placeholder="Your User ID">
<label class="u-label" style="margin-top: 4px; color: #00b0f4;">ADVANCED DELAYS</label>
<div class="flex-row">
<div style="flex:1; display:flex; flex-direction:column; gap:2px;">
<span class="delay-label">Delay (ms)</span>
<input id="deleteDelay" class="u-input" type="number" value="1500" title="Delay between each deletion in ms">
</div>
<div style="flex:1; display:flex; flex-direction:column; gap:2px;">
<span class="delay-label">Sleep After</span>
<input id="batchSize" class="u-input" type="number" value="50" title="Amount of messages to delete before pausing">
</div>
<div style="flex:1; display:flex; flex-direction:column; gap:2px;">
<span class="delay-label">Sleep Time (ms)</span>
<input id="batchDelay" class="u-input" type="number" value="5000" title="How long to pause in ms">
</div>
</div>
<button id="getInfo" class="u-btn u-btn-secondary">Auto-fill Current Server/DM & Channel IDs</button>
<button id="start" class="u-btn">Start (Search & Destroy)</button>
<button id="stop" class="u-btn u-btn-stop" disabled>Stop / Pause</button>
<div id="log" class="u-log"></div>
</div>
`;
document.body.appendChild(container);
const log = (m, err=false, info=false) => {
const d = document.createElement('div');
d.style.color = err ? 'var(--text-danger, #fa777a)' : (info ? '#00b0f4' : 'var(--text-muted, #b5bac1)');
d.textContent = `[${new Date().toLocaleTimeString()}] ${m}`;
const lb = container.querySelector('#log');
lb.appendChild(d);
lb.scrollTop = lb.scrollHeight;
};
const getModernToken = () => {
let extractedToken = "";
try {
window.webpackChunkdiscord_app.push([
[Symbol('undiscord')], {},
(req) => {
for (const key in req.c) {
const module = req.c[key].exports;
if (!module) continue;
if (module.default && typeof module.default.getToken === 'function') {
const t = module.default.getToken();
if (typeof t === 'string' && t.length > 20) { extractedToken = t; return; }
}
if (typeof module.getToken === 'function') {
const t = module.getToken();
if (typeof t === 'string' && t.length > 20) { extractedToken = t; return; }
}
}
}
]);
} catch(e) {}
if (extractedToken) return extractedToken;
try {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
const t = iframe.contentWindow.localStorage.getItem('token');
document.body.removeChild(iframe);
if (typeof t === 'string' && t.length > 20) { return t.replace(/^"|"$/g, ''); }
} catch(e) {}
return "";
};
container.querySelector('#close').onclick = () => { container.style.display = 'none'; };
container.querySelector('#get-token-btn').onclick = () => {
log("Attempting to extract Token...");
const token = getModernToken();
if (token) {
container.querySelector('#token').value = token;
log("✅ Token successfully fetched and filled!");
} else {
log("❌ Auto-fetch failed. Grab manually via F12 -> Network.", true);
}
};
container.querySelector('#getInfo').onclick = () => {
const m = location.href.match(/channels\/([\w@]+)\/(\d+)/);
if (m) {
container.querySelector('#guildId').value = m[1];
container.querySelector('#channelId').value = m[2];
log("✅ Current location IDs filled.");
} else {
log("❌ Failed to parse IDs from URL. Ensure you are viewing a channel or DM.", true);
}
};
let running = false;
container.querySelector('#stop').onclick = () => { running = false; log("Stopping sequence initiated...", true); };
container.querySelector('#start').onclick = async () => {
let tokenInput = container.querySelector('#token').value.trim().replace(/^"|"$/g, '');
if (!tokenInput) tokenInput = getModernToken();
const guildId = container.querySelector('#guildId').value.trim();
const channelId = container.querySelector('#channelId').value.trim();
let authorId = container.querySelector('#authorId').value.trim();
// User Parameters
const deleteDelay = parseInt(container.querySelector('#deleteDelay').value, 10) || 1500;
const batchSize = parseInt(container.querySelector('#batchSize').value, 10) || 0;
const batchDelay = parseInt(container.querySelector('#batchDelay').value, 10) || 5000;
if (!tokenInput || !guildId) return log("❌ Error: Missing Token or Server (Guild) ID.", true);
container.querySelector('#token').value = tokenInput;
if (!authorId) {
try {
const meRes = await fetch('https://discord.com/api/v9/users/@me', { headers: { 'Authorization': tokenInput } });
if (meRes.status === 401) return log("❌ Error: Token validation failed (401).", true);
const me = await meRes.json();
if (!me.id) throw new Error("Invalid Token Response");
authorId = me.id;
container.querySelector('#authorId').value = authorId;
} catch (e) {
return log("❌ Error: Network issue or invalid token.", true);
}
}
running = true;
container.querySelector('#start').disabled = true;
container.querySelector('#stop').disabled = false;
const isHistoryMode = (guildId === '@me');
if (isHistoryMode) {
log("🚀 DMs detected. Using [History API] for zero-residue wipe...", false, true);
} else {
log("🚀 Server detected. Using [Search API] for deep scanning...", false, true);
}
let offset = 0;
let historyLastId = null;
let messagesDeletedSinceLastSleep = 0;
while (running) {
try {
let apiUrl = "";
if (isHistoryMode) {
if (!channelId) { log("❌ Error: Channel ID is required for DMs.", true); break; }
apiUrl = `https://discord.com/api/v9/channels/${channelId}/messages?limit=100`;
if (historyLastId) apiUrl += `&before=${historyLastId}`;
} else {
apiUrl = `https://discord.com/api/v9/guilds/${guildId}/messages/search?author_id=${authorId}&offset=${offset}`;
if (channelId) apiUrl += `&channel_id=${channelId}`;
}
const res = await fetch(apiUrl, { headers: { 'Authorization': tokenInput } });
if (res.status === 202) {
const data = await res.json();
log(`⏳ Indexing... Waiting ${data.retry_after}s...`, true);
await new Promise(r => setTimeout(r, data.retry_after * 1000));
continue;
}
if (res.status === 429) {
const wait = (await res.json()).retry_after || 5;
log(`⚠️ Global Rate limit. Waiting ${wait}s...`, true);
await new Promise(r => setTimeout(r, wait * 1000));
continue;
}
if (res.status === 401 || res.status === 403) {
log(`❌ Error: API Access Denied (${res.status}).`, true);
break;
}
const data = await res.json();
let messagesToDelete = [];
if (isHistoryMode) {
if (!Array.isArray(data) || data.length === 0) {
log("✅ Reached the beginning of the chat history. Cleanup complete!", false, true);
break;
}
historyLastId = data[data.length - 1].id;
messagesToDelete = data.filter(m => m.author && m.author.id === authorId);
if (messagesToDelete.length === 0) {
log(`🔎 Scrolling up...`);
await new Promise(r => setTimeout(r, 1000));
continue;
}
} else {
if(!data || typeof data.total_results === 'undefined' || data.total_results === 0) {
log("✅ No matching messages found. Cleanup complete!", false, true);
break;
}
messagesToDelete = data.messages ? data.messages.map(group => group.find(m => m.hit === true)).filter(m => m) : [];
if (messagesToDelete.length === 0) {
log("✅ No more matching messages found. Cleanup complete!", false, true);
break;
}
}
for (const msg of messagesToDelete) {
if (!running) break;
const delRes = await fetch(`https://discord.com/api/v9/channels/${msg.channel_id}/messages/${msg.id}`, {
method: 'DELETE',
headers: { 'Authorization': tokenInput }
});
if (delRes.status === 204) {
log(`🗑️ Deleted: ${msg.content.slice(0, 25)}...`);
messagesDeletedSinceLastSleep++;
} else if (delRes.status === 429) {
const wait = (await delRes.json()).retry_after || 2;
log(`⚠️ Deleting too fast. Waiting ${wait}s...`, true);
await new Promise(r => setTimeout(r, wait * 1000));
continue;
} else if (delRes.status === 401 || delRes.status === 403) {
log(`❌ Missing perms to delete msg (ID: ${msg.id})`, true);
}
if (batchSize > 0 && messagesDeletedSinceLastSleep >= batchSize) {
log(`💤 Batch limit reached (${batchSize}). Sleeping for ${batchDelay}ms...`, false, true);
await new Promise(r => setTimeout(r, batchDelay));
messagesDeletedSinceLastSleep = 0;
} else {
await new Promise(r => setTimeout(r, deleteDelay));
}
}
await new Promise(r => setTimeout(r, 1500));
} catch (e) {
log("❌ Error occurred: " + e, true);
break;
}
}
running = false;
if(container.querySelector('#start')) {
container.querySelector('#start').disabled = false;
container.querySelector('#stop').disabled = true;
}
log("🛑 Process stopped.");
};
}
function injectToolbarButton() {
const toolbar = document.querySelector('[class*="toolbar_"]');
if (toolbar && !document.getElementById('undiscord-toggle-btn')) {
const toggleBtn = document.createElement('button');
toggleBtn.id = 'undiscord-toggle-btn';
toggleBtn.title = 'Undiscord (Bulk Delete)';
toggleBtn.setAttribute('aria-label', 'Undiscord');
toggleBtn.innerHTML = `
<svg aria-hidden="false" width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor" d="M15 3.999V2H9V3.999H3V5.999H21V3.999H15Z"></path>
<path fill="currentColor" d="M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z"></path>
</svg>
`;
toggleBtn.onclick = () => {
const ui = document.getElementById('undiscord-ui');
if (ui) ui.style.display = (ui.style.display === 'flex') ? 'none' : 'flex';
};
toolbar.prepend(toggleBtn);
}
}
setTimeout(() => {
initCore();
injectToolbarButton();
}, 2000);
setInterval(() => {
injectToolbarButton();
}, 1500);
})();