Greasy Fork is available in English.
🗑 使用 Discord API v10 批量删除任意频道或私聊中的消息(智能限速、重试、日期/内容过滤)。👁 实时记录被删消息。👻 自动检测 Ghost Ping 并弹出提示。📦 将频道历史导出为 HTML/JSON/TXT。🔑 一键获取 Token + 账户信息。全能 Discord 工具包——数据不离开您的浏览器。
// ==UserScript==
// @name 🛠️ Discord Plus+ V1 — Bulk Delete, Msg Logger, Ghost Ping & Export
// @name:vi Discord Plus+ V1 — Xóa tin nhắn hàng loạt, theo dõi xóa, ghost ping & xuất file
// @name:zh-CN Discord Plus+ V1 — 批量删消息、消息记录、Ghost Ping 检测与导出
// @name:zh-TW Discord Plus+ V1 — 批量刪訊息、訊息記錄、Ghost Ping 偵測與匯出
// @name:ru Discord Plus+ V1 — Массовое удаление, логгер сообщений, Ghost Ping и экспорт
// @name:ja Discord Plus+ V1 — 一括削除・メッセージログ・Ghost Ping 検出・エクスポート
// @name:ko Discord Plus+ V1 — 대량 삭제, 메시지 로거, Ghost Ping 감지 및 내보내기
// @name:es Discord Plus+ V1 — Borrado masivo, registro de mensajes, Ghost Ping y exportación
// @name:pt-BR Discord Plus+ V1 — Exclusão em massa, logger de mensagens, Ghost Ping e exportação
// @name:fr Discord Plus+ V1 — Suppression en masse, journal, Ghost Ping et export
// @name:de Discord Plus+ V1 — Massenlöschung, Nachrichten-Logger, Ghost Ping & Export
// @name:tr Discord Plus+ V1 — Toplu Silme, Mesaj Kaydedici, Ghost Ping ve Dışa Aktarma
// @name:id Discord Plus+ V1 — Hapus Massal, Logger Pesan, Ghost Ping & Ekspor
// @name:pl Discord Plus+ V1 — Masowe usuwanie, logger wiadomości, Ghost Ping i eksport
// @name:th Discord Plus+ V1 — ลบข้อความจำนวนมาก, บันทึกข้อความ, Ghost Ping และส่งออก
// @name:ar Discord Plus+ V1 — الحذف الجماعي, تسجيل الرسائل, Ghost Ping والتصدير
// @description 🗑 Bulk delete YOUR messages in any channel or DM using Discord API v10 (smart rate-limit, retry, date/content filters). 👁 Log deleted messages in real-time. 👻 Auto-detect ghost pings with toast alerts. 📦 Export channel history to HTML/JSON/TXT. 🔑 One-click token detector + account info. All-in-one Discord web toolkit — no data leaves your browser. Works with Tampermonkey & Violentmonkey.
// @description:vi 🗑 Xóa hàng loạt TIN NHẮN CỦA BẠN trong bất kỳ kênh hoặc DM nào bằng Discord API v10 (giới hạn tốc độ thông minh, thử lại, bộ lọc ngày/nội dung). 👁 Ghi lại tin nhắn bị xóa theo thời gian thực. 👻 Tự động phát hiện ghost ping với thông báo. 📦 Xuất lịch sử kênh sang HTML/JSON/TXT. 🔑 Phát hiện token một cú nhấp + thông tin tài khoản. Bộ công cụ Discord web all-in-one — không có dữ liệu rời khỏi trình duyệt của bạn.
// @description:zh-CN 🗑 使用 Discord API v10 批量删除任意频道或私聊中的消息(智能限速、重试、日期/内容过滤)。👁 实时记录被删消息。👻 自动检测 Ghost Ping 并弹出提示。📦 将频道历史导出为 HTML/JSON/TXT。🔑 一键获取 Token + 账户信息。全能 Discord 工具包——数据不离开您的浏览器。
// @description:ru 🗑 Массовое удаление ВАШИХ сообщений в любом канале или ЛС через Discord API v10 (умный rate-limit, повторные попытки, фильтры по дате и содержимому). 👁 Логирование удалённых сообщений в реальном времени. 👻 Автодетект ghost ping с уведомлениями. 📦 Экспорт истории канала в HTML/JSON/TXT. 🔑 Автоопределение токена + информация об аккаунте.
// @description:ja 🗑 Discord API v10でチャンネルまたはDM内のメッセージを一括削除(スマートなレートリミット・リトライ・日付/内容フィルタ)。👁 削除されたメッセージをリアルタイムで記録。👻 Ghost Pingを自動検出しトースト通知。📦 チャンネル履歴をHTML/JSON/TXTへエクスポート。🔑 ワンクリックでトークン取得&アカウント情報表示。
// @description:ko 🗑 Discord API v10으로 채널/DM에서 메시지 대량 삭제(스마트 rate-limit, 재시도, 날짜/내용 필터). 👁 삭제된 메시지 실시간 기록. 👻 Ghost Ping 자동 감지 + 토스트 알림. 📦 채널 기록을 HTML/JSON/TXT로 내보내기. 🔑 원클릭 토큰 감지 + 계정 정보.
// @description:zh-TW 🗑 使用 Discord API v10 批量刪除任意頻道或私聊中的訊息(智能限速、重試、日期/內容過濾)。👁 即時記錄被刪訊息。👻 自動偵測 Ghost Ping 並彈出提示。📦 將頻道歷史匯出為 HTML/JSON/TXT。🔑 一鍵獲取 Token + 帳戶資訊。全能 Discord 工具包——資料不離開您的瀏覽器。
// @description:es 🗑 Elimina en masa TUS mensajes en cualquier canal o DM con Discord API v10 (límite de velocidad inteligente, reintentos, filtros de fecha/contenido). 👁 Registra mensajes eliminados en tiempo real. 👻 Detecta automáticamente ghost pings con alertas. 📦 Exporta el historial del canal a HTML/JSON/TXT. 🔑 Detector de token con un clic + info de cuenta. Cero datos salen de tu navegador.
// @description:pt-BR 🗑 Exclua em massa SUAS mensagens em qualquer canal ou DM com Discord API v10 (rate-limit inteligente, tentativas, filtros de data/conteúdo). 👁 Registre mensagens deletadas em tempo real. 👻 Detecte automaticamente ghost pings com alertas. 📦 Exporte o histórico do canal para HTML/JSON/TXT. 🔑 Detector de token com um clique + info da conta. Nenhum dado sai do seu navegador.
// @description:fr 🗑 Supprimez en masse VOS messages dans n'importe quel salon ou DM via Discord API v10 (rate-limit intelligent, nouvelles tentatives, filtres date/contenu). 👁 Enregistrez les messages supprimés en temps réel. 👻 Détectez automatiquement les ghost pings. 📦 Exportez l'historique du salon en HTML/JSON/TXT. 🔑 Détection de token en un clic + infos du compte. Aucune donnée ne quitte votre navigateur.
// @description:de 🗑 Massenlöschung DEINER Nachrichten in jedem Kanal oder DM mit Discord API v10 (intelligentes Rate-Limit, Wiederholungen, Datum-/Inhaltsfilter). 👁 Gelöschte Nachrichten in Echtzeit protokollieren. 👻 Ghost Pings automatisch erkennen mit Benachrichtigung. 📦 Kanalverlauf als HTML/JSON/TXT exportieren. 🔑 Ein-Klick-Token-Erkennung + Kontoinformationen. Keine Daten verlassen Ihren Browser.
// @description:tr 🗑 Discord API v10 ile herhangi bir kanal veya DM'deki MESAJLARINIZI toplu silin (akıllı rate-limit, yeniden deneme, tarih/içerik filtreleri). 👁 Silinen mesajları gerçek zamanlı kaydedin. 👻 Ghost ping'leri otomatik tespit edin. 📦 Kanal geçmişini HTML/JSON/TXT olarak dışa aktarın. 🔑 Tek tıkla token tespiti + hesap bilgisi. Hiçbir veri tarayıcınızdan çıkmaz.
// @description:id 🗑 Hapus massal PESAN ANDA di channel atau DM mana pun menggunakan Discord API v10 (rate-limit cerdas, percobaan ulang, filter tanggal/konten). 👁 Catat pesan yang dihapus secara real-time. 👻 Deteksi ghost ping otomatis dengan notifikasi. 📦 Ekspor riwayat channel ke HTML/JSON/TXT. 🔑 Deteksi token satu klik + info akun. Tidak ada data yang keluar dari browser Anda.
// @description:pl 🗑 Masowe usuwanie TWOICH wiadomości na dowolnym kanale lub DM przez Discord API v10 (inteligentny rate-limit, ponowne próby, filtry daty/treści). 👁 Rejestruj usunięte wiadomości w czasie rzeczywistym. 👻 Automatyczne wykrywanie ghost pingów z powiadomieniami. 📦 Eksportuj historię kanału do HTML/JSON/TXT. 🔑 Wykrywanie tokena jednym kliknięciem + informacje o koncie.
// @description:th 🗑 ลบข้อความของคุณจำนวนมากในช่องหรือ DM ใดก็ได้ด้วย Discord API v10 (จำกัดอัตราอัจฉริยะ, ลองใหม่, กรองวันที่/เนื้อหา) 👁 บันทึกข้อความที่ถูกลบแบบเรียลไทม์ 👻 ตรวจจับ ghost ping อัตโนมัติพร้อมแจ้งเตือน 📦 ส่งออกประวัติช่องเป็น HTML/JSON/TXT 🔑 ตรวจจับ token คลิกเดียว + ข้อมูลบัญชี ไม่มีข้อมูลออกจากเบราว์เซอร์ของคุณ
// @description:ar 🗑 احذف رسائلك بشكل جماعي في أي قناة أو DM باستخدام Discord API v10 (حد معدل ذكي، إعادة المحاولة، فلاتر التاريخ/المحتوى). 👁 سجّل الرسائل المحذوفة في الوقت الفعلي. 👻 اكتشف Ghost Ping تلقائيًا مع إشعارات. 📦 صدّر تاريخ القناة بصيغة HTML/JSON/TXT. 🔑 كشف التوكن بنقرة واحدة + معلومات الحساب. لا تغادر أي بيانات متصفحك.
// @namespace http://greasyfork.icu/users/1510019
// @version 1.0.0
// @author 2pixel
// @license MIT
// @icon https://raw.githubusercontent.com/not2pixel/TampermonkeyProjects/refs/heads/main/EasyTube.png
// @icon64 https://raw.githubusercontent.com/not2pixel/TampermonkeyProjects/refs/heads/main/EasyTube.png
// @homepageURL https://discord.gg/Gvmd7deFtS
// @supportURL https://discord.gg/Gvmd7deFtS
// @match https://discord.com/*
// @match https://discordapp.com/*
// @exclude https://discord.com/developers/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @connect discord.com
// @connect discordapp.com
// @run-at document-idle
// @compatible chrome Tested on Chrome 120+ with Tampermonkey
// @compatible firefox Tested on Firefox 120+ with Tampermonkey / Violentmonkey
// @compatible edge Tested on Edge 120+ with Tampermonkey
// @compatible brave Recommended (Manifest V3 compatible)
// ==/UserScript==
'use strict';
// ─── CONSTANTS ───────────────────────────────────────────────────────────────
const VERSION = '1.0.0';
const API = 'https://discord.com/api/v10';
const DISCORD_EPOCH = 1420070400000n;
const COMMUNITY = 'https://discord.gg/Gvmd7deFtS';
const MAX_RETRIES = 5;
const PANEL_ID = 'dp2_panel';
const TOGGLE_ID = 'dp2_toggle';
// ─── STATE ───────────────────────────────────────────────────────────────────
const S = {
token: null,
activeTab: 'delete',
delRunning: false,
delStopped: false,
delCount: 0,
failCount: 0,
scanCount: 0,
deletedLog: [], // { id, content, author, channel, time }
ghostPings: [], // { author, channel, content, time }
observers: [],
};
// ─── UTILS ───────────────────────────────────────────────────────────────────
const sleep = ms => new Promise(r => setTimeout(r, ms));
function snowflakeFromDate(d) {
return ((BigInt(d.getTime()) - DISCORD_EPOCH) << 22n).toString();
}
function esc(s) {
return String(s ?? '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
}
function fmtTime(d) {
return new Date(d).toLocaleTimeString('en-GB', { hour12:false });
}
function fmtDate(d) {
return new Date(d).toLocaleDateString('en-GB') + ' ' + fmtTime(d);
}
// Multi-strategy token extraction
function grabToken() {
// Strategy 1: iframe sandbox (most reliable)
try {
const f = document.createElement('iframe');
f.style.display = 'none';
document.body.appendChild(f);
const t = f.contentWindow.localStorage.getItem('token');
document.body.removeChild(f);
if (t) return t.replace(/"/g,'');
} catch(_) {}
// Strategy 2: direct localStorage
try {
const t = localStorage.getItem('token');
if (t) return t.replace(/"/g,'');
} catch(_) {}
// Strategy 3: webpack module cache
try {
const wpReq = window.webpackChunkdiscord_app?.push?.([[Symbol()],{},e=>e]);
if (wpReq) {
for (const id of Object.keys(wpReq.m || {})) {
try {
const mod = wpReq(id);
if (mod?.default?.getToken) {
const t = mod.default.getToken();
if (t) return t;
}
} catch(_) {}
}
}
} catch(_) {}
return null;
}
function getChannelId() {
return location.pathname.match(/channels\/(?:@me|\d+)\/(\d+)/)?.[1] || null;
}
function getGuildId() {
return location.pathname.match(/channels\/(\d+)\//)?.[1] || null;
}
// ─── API LAYER ───────────────────────────────────────────────────────────────
async function apiCall(method, path, body, retries = 0) {
if (!S.token) throw new Error('No token set');
const opts = {
method,
headers: {
'Authorization': S.token,
'Content-Type': 'application/json',
'X-Discord-Locale': 'en-US',
'X-Super-Properties': btoa(unescape(encodeURIComponent(JSON.stringify({
os:'Windows', browser:'Chrome', device:'',
browser_version:'122.0.0.0', os_version:'10',
release_channel:'stable', client_build_number:263192,
})))),
},
};
if (body) opts.body = JSON.stringify(body);
let res;
try { res = await fetch(`${API}${path}`, opts); }
catch(e) {
if (retries < MAX_RETRIES) { await sleep(1500); return apiCall(method, path, body, retries+1); }
throw e;
}
if (res.status === 429) {
const d = await res.json().catch(()=>({}));
const w = Math.ceil((d.retry_after||2)*1000)+300;
appendLog(`⏳ Rate limited — waiting ${(w/1000).toFixed(1)}s`, 'warn');
await sleep(w);
if (retries < MAX_RETRIES) return apiCall(method, path, body, retries+1);
throw new Error('Too many rate-limit retries');
}
if (res.status === 401) throw new Error('Invalid token — refresh Discord');
if (res.status === 403) throw new Error('No permission');
if (res.status === 404) return null;
return res;
}
async function getMe() {
const r = await apiCall('GET','/users/@me');
return r ? r.json() : null;
}
async function searchMsgs({ guildId, channelId, authorId, minId, maxId, content, hasLink, hasFile, nsfw }) {
const p = new URLSearchParams();
if (authorId) p.set('author_id', authorId);
if (minId) p.set('min_id', minId);
if (maxId) p.set('max_id', maxId);
if (content) p.set('content', content);
if (hasLink) p.set('has','link');
if (hasFile) p.set('has','file');
p.set('include_nsfw', nsfw ? 'true':'false');
p.set('limit','25');
const ep = guildId
? `/guilds/${guildId}/messages/search?${p}`
: `/channels/${channelId}/messages/search?${p}`;
const r = await apiCall('GET', ep);
if (!r) return null;
if (r.status === 202) { await sleep(1600); return searchMsgs(...arguments); }
return r.json();
}
async function deleteMsg(channelId, msgId) {
const r = await apiCall('DELETE', `/channels/${channelId}/messages/${msgId}`);
return r !== null;
}
// ─── FEATURE: BULK DELETE ────────────────────────────────────────────────────
async function runDelete(opts) {
const { channelId, authorId, guildId, minDate, maxDate, content, hasLink, hasFile, nsfw, delayMs } = opts;
S.delRunning = true; S.delStopped = false;
S.delCount = S.failCount = S.scanCount = 0;
setStatus('running');
appendLog('🗑 Bulk delete started', 'brand');
appendLog(`Channel: ${channelId}${guildId ? ` | Guild: ${guildId}` : ''}`, 'info');
const minId = minDate ? snowflakeFromDate(minDate) : undefined;
let maxId = maxDate ? snowflakeFromDate(maxDate) : undefined;
let round = 0;
while (!S.delStopped) {
round++;
appendLog(`Round ${round} — searching…`, 'info');
let data;
try {
data = await searchMsgs({ guildId, channelId, authorId, minId, maxId, content, hasLink, hasFile, nsfw });
} catch(e) { appendLog('Search error: '+e.message,'error'); break; }
await sleep(950);
if (!data) { appendLog('Nothing returned.','warn'); break; }
const msgs = (data.messages||[]).flat();
S.scanCount += msgs.length;
uiSync();
if (!msgs.length) { appendLog('✅ No more messages found.','ok'); break; }
const toDelete = authorId ? msgs.filter(m=>m.author?.id===authorId) : msgs;
if (!toDelete.length) { appendLog('No matches in batch.','ok'); break; }
appendLog(`Found ${toDelete.length} to delete`, 'info');
for (const msg of toDelete) {
if (S.delStopped) break;
try {
const ok = await deleteMsg(msg.channel_id||channelId, msg.id);
if (ok) { S.delCount++; appendLog(`✓ ${msg.id}`, 'ok'); }
else { appendLog(`Already gone: ${msg.id}`, 'warn'); }
} catch(e) {
S.failCount++;
appendLog(`✗ ${msg.id}: ${e.message}`, 'error');
}
uiSync();
await sleep(delayMs + Math.floor(Math.random()*250));
}
const oldest = toDelete.reduce((a,b)=>BigInt(a.id)<BigInt(b.id)?a:b);
maxId = (BigInt(oldest.id)-1n).toString();
}
S.delRunning = false;
setStatus(S.delStopped ? '' : 'done');
appendLog(
S.delStopped
? '⏹ Stopped by user.'
: `✅ Done! Deleted: ${S.delCount} | Failed: ${S.failCount}`,
S.delStopped ? 'warn' : 'brand'
);
toggleBtn('start', true);
toggleBtn('stop', false);
}
// ─── FEATURE: DELETED MSG LOGGER ─────────────────────────────────────────────
function startLogger() {
const obs = new MutationObserver(muts => {
for (const m of muts) {
for (const node of m.removedNodes) {
if (node.nodeType !== 1) continue;
const msgEl = node.id?.startsWith('chat-messages-') ? node
: node.querySelector?.('[id^="chat-messages-"]');
if (!msgEl) continue;
const contentEl = msgEl.querySelector('[id^="message-content-"]');
const authorEl = msgEl.querySelector('h3 span[class*="username"]')
|| msgEl.querySelector('[class*="username"]');
const content = contentEl?.innerText?.trim() || '';
const author = authorEl?.innerText?.trim() || 'Unknown';
if (content.length < 1) continue;
const entry = { id: msgEl.id, content, author, channel: location.pathname.split('/').pop(), time: Date.now() };
S.deletedLog.unshift(entry);
if (S.deletedLog.length > 500) S.deletedLog.pop();
renderLogList();
// Ghost ping detection
if (/@(everyone|here|\w+)/.test(content)) {
S.ghostPings.unshift({ author, channel: entry.channel, content, time: entry.time });
if (S.ghostPings.length > 200) S.ghostPings.pop();
renderGhostList();
showToast(`👻 Ghost ping by ${author}`, content.slice(0,80), '#7b1fa2');
}
}
}
});
obs.observe(document.body, { childList:true, subtree:true });
S.observers.push(obs);
}
// ─── FEATURE: EXPORT ─────────────────────────────────────────────────────────
async function exportChannel(channelId, format, limit) {
appendLog(`Fetching up to ${limit} messages…`, 'info');
let all = [], before;
let remaining = limit;
while (remaining > 0 && !S.delStopped) {
const batch = Math.min(remaining, 100);
const qs = `limit=${batch}${before ? `&before=${before}` : ''}`;
const r = await apiCall('GET', `/channels/${channelId}/messages?${qs}`);
if (!r) break;
const d = await r.json();
if (!Array.isArray(d)||!d.length) break;
all = all.concat(d);
before = d[d.length-1].id;
remaining -= d.length;
appendLog(`Fetched ${all.length}…`, 'info');
await sleep(600);
}
appendLog(`Collected ${all.length} messages.`, 'ok');
let blob, filename;
if (format === 'json') {
blob = new Blob([JSON.stringify(all,null,2)], { type:'application/json' });
filename = `discord-${channelId}.json`;
} else if (format === 'txt') {
const lines = all.map(m=>`[${fmtDate(m.timestamp)}] ${m.author.username}: ${m.content}`).join('\n');
blob = new Blob([lines], { type:'text/plain' });
filename = `discord-${channelId}.txt`;
} else {
const rows = all.map(m=>`<tr><td>${esc(fmtDate(m.timestamp))}</td><td style="color:#5865F2;font-weight:700">${esc(m.author.username)}</td><td>${esc(m.content)}</td></tr>`).join('');
const html = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Export · ${esc(channelId)}</title><style>
body{font-family:Whitney,sans-serif;background:#313338;color:#dbdee1;margin:0;padding:20px}
h2{color:#fff;font-size:18px;margin-bottom:4px}p{color:#949ba4;font-size:12px;margin-bottom:16px}
table{border-collapse:collapse;width:100%}th,td{padding:8px 12px;border-bottom:1px solid #3f4248;font-size:13px;text-align:left;vertical-align:top}
th{color:#949ba4;font-size:11px;text-transform:uppercase;letter-spacing:.5px}
tr:hover td{background:#3e4148}td:first-child{white-space:nowrap;color:#6d6f78;width:160px}
</style></head><body><h2>Discord Export · #${esc(channelId)}</h2>
<p>${all.length} messages · ${fmtDate(Date.now())}</p>
<table><thead><tr><th>Time</th><th>Author</th><th>Content</th></tr></thead><tbody>${rows}</tbody></table>
</body></html>`;
blob = new Blob([html], { type:'text/html' });
filename = `discord-${channelId}.html`;
}
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
appendLog(`✅ Saved as ${filename}`, 'ok');
}
// ─── TOAST ────────────────────────────────────────────────────────────────────
function showToast(title, body, color='#5865F2') {
document.getElementById('dp2_toast')?.remove();
const el = document.createElement('div');
el.id = 'dp2_toast';
Object.assign(el.style, {
position:'fixed', bottom:'90px', right:'80px',
background: color, color:'#fff', padding:'10px 16px',
borderRadius:'12px', fontSize:'12px', fontWeight:'700',
zIndex:'1000001', pointerEvents:'none', maxWidth:'260px',
boxShadow:'0 8px 24px rgba(0,0,0,.4)', lineHeight:'1.4',
animation:'dp2fade 3s forwards',
});
el.innerHTML = `<div>${esc(title)}</div><div style="font-weight:400;opacity:.85;font-size:11px">${esc(body)}</div>`;
document.body.appendChild(el);
setTimeout(()=>el.remove(), 3100);
}
// ─── CSS ──────────────────────────────────────────────────────────────────────
GM_addStyle(`
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@600;700;800;900&display=swap');
@keyframes dp2fade {
0% { opacity:1; transform:translateY(0); }
75% { opacity:1; }
100% { opacity:0; transform:translateY(-8px); }
}
@keyframes dp2pulse {
0%,100% { opacity:1; }
50% { opacity:.35; }
}
@keyframes dp2spin {
to { transform:rotate(360deg); }
}
@keyframes dp2bar {
0% { margin-left:-40%; width:40%; }
60% { margin-left:100%; width:40%; }
100% { margin-left:100%; width:40%; }
}
#${PANEL_ID}, #${PANEL_ID} * {
box-sizing:border-box;
font-family:'Nunito', system-ui, sans-serif;
}
/* ── Float toggle button ──────────────────────────────────── */
#${TOGGLE_ID} {
position:fixed; bottom:90px; right:18px;
width:56px; height:36px; border-radius:999px;
background:rgba(255,255,255,0.16);
border:1px solid rgba(255,255,255,0.22);
box-shadow:0 8px 24px rgba(0,0,0,.25);
z-index:99998; cursor:pointer;
display:flex; align-items:center; justify-content:center;
backdrop-filter:blur(20px); -webkit-backdrop-filter:blur(20px);
transition:transform .18s, box-shadow .18s;
}
#${TOGGLE_ID}:hover { transform:translateY(-2px); box-shadow:0 12px 30px rgba(0,0,0,.32); }
#${TOGGLE_ID}:active { transform:scale(.97); }
.dp2-tog-icon { font-size:20px; transition:transform .3s; }
#${TOGGLE_ID}.dp2-active .dp2-tog-icon { transform:rotate(20deg); }
/* ── Panel ────────────────────────────────────────────────── */
#${PANEL_ID} {
position:fixed; top:0; left:0;
width:370px; max-width:94vw;
max-height:min(580px, calc(100vh - 120px));
display:flex; flex-direction:column;
background:rgba(30,32,40,0.82);
backdrop-filter:blur(36px) saturate(180%);
-webkit-backdrop-filter:blur(36px) saturate(180%);
border:1px solid rgba(255,255,255,0.10);
border-radius:24px;
box-shadow:0 24px 64px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,.04) inset;
z-index:99999; overflow:hidden;
opacity:0; pointer-events:none;
transform:translateY(18px) scale(.97);
transition:opacity .32s ease, transform .38s cubic-bezier(.25,.46,.45,.94);
}
#${PANEL_ID}.dp2-show { opacity:1; pointer-events:all; transform:translateY(0) scale(1); }
#${PANEL_ID}.dp2-dragging { transition:none !important; }
/* ── Header ───────────────────────────────────────────────── */
.dp2-hdr {
background:linear-gradient(135deg, #5865F2, #3b3f8c);
padding:13px 15px 12px; cursor:move; user-select:none;
display:flex; align-items:center; justify-content:space-between;
flex:0 0 auto; border-radius:24px 24px 0 0;
}
.dp2-hdr-l { display:flex; align-items:center; gap:10px; }
.dp2-logo {
width:38px; height:38px;
background:rgba(255,255,255,0.16);
border-radius:12px;
display:flex; align-items:center; justify-content:center;
font-size:20px;
}
.dp2-hdr-title { color:#fff; font-size:15px; font-weight:900; letter-spacing:.2px; line-height:1.2; }
.dp2-hdr-sub { color:rgba(255,255,255,.72); font-size:10px; font-weight:700; letter-spacing:.3px; margin-top:1px; }
.dp2-drag-dot { color:rgba(255,255,255,.7); font-size:22px; cursor:move; }
.dp2-win-btns { display:flex; gap:5px; align-items:center; }
.dp2-win-btn {
width:11px; height:11px; border-radius:50%;
border:none; cursor:pointer; padding:0;
transition:filter .15s;
}
.dp2-win-btn:hover { filter:brightness(1.4); }
.dp2-win-btn.close { background:#ff5f56; }
.dp2-win-btn.minimize { background:#27293d; }
/* ── Stat pills ───────────────────────────────────────────── */
.dp2-pills {
display:flex; gap:6px; padding:8px 14px;
background:rgba(0,0,0,.18); border-bottom:1px solid rgba(255,255,255,.07);
flex:0 0 auto;
}
.dp2-pill {
flex:1; display:flex; align-items:center; justify-content:center; gap:4px;
background:rgba(255,255,255,.10); border:1px solid rgba(255,255,255,.08);
border-radius:999px; padding:5px 10px;
font-size:11px; font-weight:800; color:#dbdee1;
}
.dp2-pill span { font-size:13px; font-weight:900; color:#fff; }
/* ── Tabs ─────────────────────────────────────────────────── */
.dp2-tabs {
display:flex; gap:0; padding:0 14px;
background:rgba(0,0,0,.14); border-bottom:1px solid rgba(255,255,255,.07);
flex:0 0 auto; overflow-x:auto;
scrollbar-width:none;
}
.dp2-tabs::-webkit-scrollbar { display:none; }
.dp2-tab {
padding:9px 10px 8px; font-size:11px; font-weight:800;
color:rgba(255,255,255,.35); cursor:pointer; white-space:nowrap;
border-bottom:2px solid transparent; letter-spacing:.2px;
transition:color .15s, border-color .15s;
}
.dp2-tab:hover { color:rgba(255,255,255,.65); }
.dp2-tab.active { color:#fff; border-bottom-color:#5865F2; }
/* ── Body ─────────────────────────────────────────────────── */
.dp2-body {
padding:14px; overflow-y:auto; flex:1 1 auto;
scrollbar-width:thin; scrollbar-color:rgba(255,255,255,.15) transparent;
display:flex; flex-direction:column; gap:10px;
}
.dp2-body::-webkit-scrollbar { width:6px; }
.dp2-body::-webkit-scrollbar-thumb { background:rgba(255,255,255,.15); border-radius:999px; }
.dp2-pane { display:none; flex-direction:column; gap:10px; }
.dp2-pane.active { display:flex; }
/* ── Section label ────────────────────────────────────────── */
.dp2-lbl {
font-size:9.5px; font-weight:900; letter-spacing:.9px;
text-transform:uppercase; color:rgba(255,255,255,.3); margin-bottom:4px;
}
/* ── Cards ────────────────────────────────────────────────── */
.dp2-card {
background:rgba(255,255,255,.06);
border:1px solid rgba(255,255,255,.09);
border-radius:16px; padding:12px;
}
/* ── Inputs ───────────────────────────────────────────────── */
.dp2-field { position:relative; flex:1; }
.dp2-field label {
display:block; font-size:9.5px; font-weight:800; letter-spacing:.6px;
text-transform:uppercase; color:rgba(255,255,255,.3); margin-bottom:4px;
}
.dp2-row { display:flex; gap:7px; }
.dp2-input {
width:100%; background:rgba(0,0,0,.25);
border:1px solid rgba(255,255,255,.10);
border-radius:10px; padding:8px 10px;
color:#dbdee1; font-family:inherit; font-size:12px;
font-weight:700; outline:none;
transition:border-color .15s, box-shadow .15s;
}
.dp2-input:focus { border-color:#5865F2; box-shadow:0 0 0 3px rgba(88,101,242,.2); }
.dp2-input::placeholder { color:rgba(255,255,255,.18); font-weight:600; }
.dp2-input.error { border-color:#ed4245; }
.dp2-input[type="password"] { letter-spacing:3px; }
.dp2-input[type="password"]::placeholder { letter-spacing:normal; }
.dp2-inline-btn {
position:absolute; right:7px; top:50%; transform:translateY(-50%);
background:rgba(88,101,242,.25); border:none; border-radius:6px;
color:#949cf7; font-size:9.5px; font-weight:900; font-family:inherit;
padding:2px 6px; cursor:pointer; letter-spacing:.3px;
transition:background .15s;
}
.dp2-inline-btn:hover { background:rgba(88,101,242,.45); }
/* ── Checkbox grid ────────────────────────────────────────── */
.dp2-chk-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; }
.dp2-chk {
display:flex; align-items:center; gap:7px;
background:rgba(255,255,255,.05); border:1px solid rgba(255,255,255,.08);
border-radius:10px; padding:8px 10px; cursor:pointer;
transition:border-color .15s;
}
.dp2-chk:hover { border-color:rgba(255,255,255,.15); }
.dp2-chk.on { border-color:#5865F244; background:rgba(88,101,242,.1); }
.dp2-chk-box {
width:14px; height:14px; border-radius:4px;
border:1.5px solid rgba(255,255,255,.2); background:rgba(0,0,0,.25);
display:flex; align-items:center; justify-content:center; flex-shrink:0;
transition:all .15s;
}
.dp2-chk.on .dp2-chk-box { background:#5865F2; border-color:#5865F2; }
.dp2-chk.on .dp2-chk-box::after {
content:''; width:5px; height:3px;
border-left:1.5px solid #fff; border-bottom:1.5px solid #fff;
transform:rotate(-45deg) translate(.5px,-.5px);
}
.dp2-chk-lbl { font-size:11.5px; font-weight:700; color:rgba(255,255,255,.45); }
.dp2-chk.on .dp2-chk-lbl { color:#dbdee1; }
/* ── Slider ───────────────────────────────────────────────── */
.dp2-slider-row { display:flex; align-items:center; gap:10px; }
.dp2-slider {
flex:1; -webkit-appearance:none;
height:3px; border-radius:2px;
background:rgba(255,255,255,.12); outline:none; cursor:pointer;
}
.dp2-slider::-webkit-slider-thumb {
-webkit-appearance:none; width:14px; height:14px; border-radius:50%;
background:#5865F2; cursor:pointer;
box-shadow:0 0 8px rgba(88,101,242,.6);
transition:transform .15s;
}
.dp2-slider::-webkit-slider-thumb:hover { transform:scale(1.2); }
.dp2-slider-val {
font-size:11.5px; font-weight:800;
color:#949cf7; min-width:42px; text-align:right;
}
/* ── Stats row ────────────────────────────────────────────── */
.dp2-stats-row {
display:grid; grid-template-columns:1fr 1fr 1fr;
background:rgba(255,255,255,.05);
border:1px solid rgba(255,255,255,.08);
border-radius:14px; padding:10px;
gap:0;
}
.dp2-stat { text-align:center; }
.dp2-stat-val {
font-size:20px; font-weight:900;
color:#fff; line-height:1;
}
.dp2-stat-lbl {
font-size:9px; font-weight:800; text-transform:uppercase;
letter-spacing:.6px; color:rgba(255,255,255,.3); margin-top:3px;
}
/* ── Progress bar ─────────────────────────────────────────── */
.dp2-bar-track {
height:3px; background:rgba(255,255,255,.08);
border-radius:2px; overflow:hidden; margin-top:6px;
}
.dp2-bar-fill {
height:100%;
background:linear-gradient(90deg, #5865F2, #949cf7);
border-radius:2px; width:0; transition:width .3s;
}
.dp2-bar-fill.running { animation:dp2bar 1.5s ease infinite; }
/* ── Log ──────────────────────────────────────────────────── */
.dp2-log {
background:rgba(0,0,0,.3);
border:1px solid rgba(255,255,255,.07);
border-radius:10px; padding:8px 10px;
height:80px; overflow-y:auto;
font-family:'JetBrains Mono','Courier New',monospace;
font-size:10px; color:rgba(255,255,255,.3);
}
.dp2-log::-webkit-scrollbar { width:3px; }
.dp2-log::-webkit-scrollbar-thumb { background:rgba(255,255,255,.1); border-radius:2px; }
.dp2-ll { margin:1px 0; line-height:1.55; }
.dp2-ll.info { color:rgba(255,255,255,.4); }
.dp2-ll.ok { color:#57f287; }
.dp2-ll.warn { color:#fee75c; }
.dp2-ll.error { color:#ed4245; }
.dp2-ll.brand { color:#949cf7; }
/* ── Buttons ──────────────────────────────────────────────── */
.dp2-btn-row { display:flex; gap:7px; }
.dp2-btn {
flex:1; padding:10px 12px; border:none; border-radius:12px;
font-family:inherit; font-size:12px; font-weight:900; cursor:pointer;
transition:all .15s; letter-spacing:.2px;
display:flex; align-items:center; justify-content:center; gap:6px;
}
.dp2-btn:active { transform:scale(.97); }
.dp2-btn-primary {
background:linear-gradient(135deg, #5865F2, #3b3f8c);
color:#fff; box-shadow:0 4px 16px rgba(88,101,242,.35);
}
.dp2-btn-primary:hover { filter:brightness(1.1); box-shadow:0 6px 22px rgba(88,101,242,.5); }
.dp2-btn-primary:disabled { background:rgba(255,255,255,.08); color:rgba(255,255,255,.25); box-shadow:none; cursor:not-allowed; }
.dp2-btn-secondary {
background:rgba(255,255,255,.08);
border:1px solid rgba(255,255,255,.10);
color:rgba(255,255,255,.5);
}
.dp2-btn-secondary:hover { background:rgba(255,255,255,.12); color:#dbdee1; }
.dp2-btn-danger {
background:rgba(237,66,69,.15); color:#ed4245;
border:1px solid rgba(237,66,69,.25);
}
.dp2-btn-danger:hover { background:rgba(237,66,69,.25); border-color:#ed4245; }
.dp2-btn-green {
background:linear-gradient(135deg, #3ba55d, #2d7d46);
color:#fff; box-shadow:0 4px 14px rgba(59,165,93,.3);
}
.dp2-btn-green:hover { filter:brightness(1.08); }
/* ── Toggle cards (3-col) ─────────────────────────────────── */
.dp2-tc-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:7px; }
.dp2-tc {
background:rgba(255,255,255,.06);
border:1px solid rgba(255,255,255,.09);
border-radius:14px; padding:10px 9px;
display:flex; flex-direction:column; gap:7px;
transition:transform .15s, border-color .2s;
}
.dp2-tc.on { border-color:#5865F244; background:rgba(88,101,242,.1); }
.dp2-tc:hover { transform:translateY(-1px); }
.dp2-tc-top { display:flex; align-items:center; justify-content:space-between; }
.dp2-tc-ico { font-size:18px; line-height:1; }
.dp2-tc-bot { display:flex; align-items:center; justify-content:space-between; }
.dp2-tc-title { font-size:11px; font-weight:900; color:#dbdee1; }
.dp2-tc-state { font-size:10px; font-weight:800; color:rgba(255,255,255,.3); letter-spacing:.3px; }
.dp2-tc.on .dp2-tc-state { color:#949cf7; }
/* Mac-style switch */
.dp2-sw {
width:38px; height:22px; border-radius:999px; border:none;
background:rgba(255,255,255,.15); position:relative; cursor:pointer;
flex-shrink:0; transition:background .18s;
box-shadow:inset 0 0 0 1px rgba(0,0,0,.15);
}
.dp2-sw.on { background:#5865F2; }
.dp2-sw-thumb {
position:absolute; top:2px; left:2px;
width:18px; height:18px; border-radius:50%;
background:#fff; box-shadow:0 3px 8px rgba(0,0,0,.2);
transition:transform .18s;
}
.dp2-sw.on .dp2-sw-thumb { transform:translateX(16px); }
/* ── Warning box ──────────────────────────────────────────── */
.dp2-warn {
background:rgba(254,231,92,.08);
border:1px solid rgba(254,231,92,.18);
border-radius:10px; padding:8px 10px;
font-size:10.5px; font-weight:700;
color:rgba(254,231,92,.75); line-height:1.5;
}
/* ── Message lists ────────────────────────────────────────── */
.dp2-list {
display:flex; flex-direction:column; gap:5px;
max-height:240px; overflow-y:auto;
}
.dp2-list::-webkit-scrollbar { width:4px; }
.dp2-list::-webkit-scrollbar-thumb { background:rgba(255,255,255,.1); border-radius:2px; }
.dp2-list-empty {
text-align:center; color:rgba(255,255,255,.2);
font-size:11.5px; padding:24px 0; font-weight:700; line-height:1.6;
}
.dp2-li {
background:rgba(255,255,255,.05);
border:1px solid rgba(255,255,255,.08);
border-radius:10px; padding:8px 10px; font-size:11.5px;
}
.dp2-li-head { display:flex; justify-content:space-between; margin-bottom:3px; }
.dp2-li-author { font-weight:900; color:#949cf7; font-size:11px; }
.dp2-li-time { font-size:10px; color:rgba(255,255,255,.25); font-family:monospace; }
.dp2-li-body { color:rgba(255,255,255,.55); line-height:1.45; word-break:break-word; font-weight:600; }
.dp2-li.ghost { border-color:rgba(123,31,162,.4); background:rgba(123,31,162,.1); }
.dp2-li.ghost .dp2-li-author { color:#c084fc; }
/* ── Select ───────────────────────────────────────────────── */
.dp2-select {
width:100%; background:rgba(0,0,0,.25);
border:1px solid rgba(255,255,255,.10);
border-radius:10px; padding:8px 10px;
color:#dbdee1; font-family:inherit; font-size:12px;
font-weight:700; outline:none; appearance:none; cursor:pointer;
}
.dp2-select:focus { border-color:#5865F2; box-shadow:0 0 0 3px rgba(88,101,242,.2); }
/* ── About card ───────────────────────────────────────────── */
.dp2-about {
background:rgba(255,255,255,.05);
border:1px solid rgba(255,255,255,.09);
border-radius:18px; padding:18px; text-align:center;
}
.dp2-about-logo {
width:52px; height:52px; margin:0 auto 12px;
background:linear-gradient(135deg, #5865F2, #3b3f8c);
border-radius:16px; display:flex; align-items:center; justify-content:center;
font-size:26px; box-shadow:0 6px 20px rgba(88,101,242,.4);
}
.dp2-about-title { font-size:18px; font-weight:900; color:#fff; }
.dp2-about-sub { font-size:10.5px; color:rgba(255,255,255,.35); margin:4px 0 14px; font-weight:700; }
.dp2-feat-list { text-align:left; margin-bottom:14px; }
.dp2-feat-list li {
list-style:none; font-size:11.5px; font-weight:700;
color:rgba(255,255,255,.55); padding:3px 0; line-height:1.45;
}
.dp2-feat-list li::before { content:'→ '; color:#5865F2; font-weight:900; }
.dp2-community-btn {
display:inline-flex; align-items:center; justify-content:center; gap:8px;
width:100%; padding:11px 16px; border-radius:12px;
background:linear-gradient(135deg, #5865F2, #3b3f8c);
color:#fff; font-size:13px; font-weight:900;
text-decoration:none; cursor:pointer;
box-shadow:0 4px 16px rgba(88,101,242,.35);
transition:filter .15s, transform .15s;
}
.dp2-community-btn:hover { filter:brightness(1.1); transform:translateY(-1px); }
.dp2-foot {
padding:9px 14px;
background:rgba(0,0,0,.15);
border-top:1px solid rgba(255,255,255,.06);
border-radius:0 0 24px 24px;
text-align:center; flex:0 0 auto;
}
.dp2-foot-txt { font-size:10px; font-weight:700; color:rgba(255,255,255,.25); }
/* ── User info card ───────────────────────────────────────── */
.dp2-user-card {
background:rgba(0,0,0,.25);
border:1px solid rgba(255,255,255,.08);
border-radius:12px; padding:12px;
font-size:12px; font-weight:700;
}
.dp2-status-dot {
display:inline-block; width:8px; height:8px; border-radius:50%;
background:rgba(255,255,255,.15); margin-right:4px;
vertical-align:middle; transition:background .3s, box-shadow .3s;
}
.dp2-status-dot.running { background:#fee75c; box-shadow:0 0 6px #fee75c; animation:dp2pulse 1.2s ease infinite; }
.dp2-status-dot.ok { background:#57f287; box-shadow:0 0 6px #57f287; }
`);
// ─── BUILD UI ─────────────────────────────────────────────────────────────────
function buildPanel() {
const tog = document.createElement('div');
tog.id = TOGGLE_ID;
const togIcon = document.createElement('span');
togIcon.className = 'dp2-tog-icon';
togIcon.textContent = '🛠️';
tog.appendChild(togIcon);
document.body.appendChild(tog);
const panel = document.createElement('div');
panel.id = PANEL_ID;
panel.innerHTML = `
<!-- Header -->
<div class="dp2-hdr">
<div class="dp2-hdr-l">
<div class="dp2-logo">🛠️</div>
<div>
<div class="dp2-hdr-title">Discord Plus+ <span style="font-size:10px;opacity:.6;font-weight:700">V1</span></div>
<div class="dp2-hdr-sub">DELETE · LOG · GHOST · EXPORT · TOKEN</div>
</div>
</div>
<div style="display:flex;align-items:center;gap:8px">
<span class="dp2-drag-dot">⋮</span>
<div class="dp2-win-btns">
<button class="dp2-win-btn minimize" id="dp2-min"></button>
<button class="dp2-win-btn close" id="dp2-close"></button>
</div>
</div>
</div>
<!-- Stats pills -->
<div class="dp2-pills">
<div class="dp2-pill">🗑 Deleted: <span id="dp2-s-del">0</span></div>
<div class="dp2-pill">✗ Failed: <span id="dp2-s-fail">0</span></div>
<div class="dp2-pill">🔍 Scanned: <span id="dp2-s-scan">0</span></div>
</div>
<!-- Tabs -->
<div class="dp2-tabs">
<div class="dp2-tab active" data-tab="delete">🗑 Delete</div>
<div class="dp2-tab" data-tab="logger">👁 Logger</div>
<div class="dp2-tab" data-tab="ghost">👻 Ghost</div>
<div class="dp2-tab" data-tab="export">📦 Export</div>
<div class="dp2-tab" data-tab="token">🔑 Token</div>
<div class="dp2-tab" data-tab="about">ℹ About</div>
</div>
<!-- Body -->
<div class="dp2-body">
<!-- ═══ DELETE ═══ -->
<div class="dp2-pane active" id="dp2-pane-delete">
<div class="dp2-card">
<div class="dp2-lbl">Authentication</div>
<div class="dp2-field">
<label>Token</label>
<input class="dp2-input" id="dp2-token" type="password" placeholder="auto-detected or paste here">
<button class="dp2-inline-btn" id="dp2-auto-tok">AUTO</button>
</div>
<div class="dp2-warn" style="margin-top:8px">⚠ Token never leaves your browser. NEVER share it.</div>
</div>
<div class="dp2-card">
<div class="dp2-lbl">Target</div>
<div class="dp2-row">
<div class="dp2-field">
<label>Channel ID</label>
<input class="dp2-input" id="dp2-channel" placeholder="from URL">
<button class="dp2-inline-btn" id="dp2-auto-ch">AUTO</button>
</div>
<div class="dp2-field">
<label>Guild ID (opt)</label>
<input class="dp2-input" id="dp2-guild" placeholder="server">
<button class="dp2-inline-btn" id="dp2-auto-guild">AUTO</button>
</div>
</div>
<div class="dp2-field" style="margin-top:7px">
<label>Author ID (blank = all)</label>
<input class="dp2-input" id="dp2-author" placeholder="your user ID">
<button class="dp2-inline-btn" id="dp2-auto-me">ME</button>
</div>
</div>
<div class="dp2-card">
<div class="dp2-lbl">Filters</div>
<div class="dp2-row" style="margin-bottom:7px">
<div class="dp2-field"><label>From date</label><input class="dp2-input" id="dp2-from" type="date"></div>
<div class="dp2-field"><label>To date</label><input class="dp2-input" id="dp2-to" type="date"></div>
</div>
<div class="dp2-field" style="margin-bottom:8px">
<label>Contains text</label>
<input class="dp2-input" id="dp2-content" placeholder="keyword…">
</div>
<div class="dp2-chk-grid">
<div class="dp2-chk" id="chk-link"><div class="dp2-chk-box"></div><span class="dp2-chk-lbl">Has Link</span></div>
<div class="dp2-chk" id="chk-file"><div class="dp2-chk-box"></div><span class="dp2-chk-lbl">Has File</span></div>
<div class="dp2-chk" id="chk-nsfw"><div class="dp2-chk-box"></div><span class="dp2-chk-lbl">NSFW</span></div>
<div class="dp2-chk" id="chk-pin"><div class="dp2-chk-box"></div><span class="dp2-chk-lbl">Pinned</span></div>
</div>
</div>
<div class="dp2-card">
<div class="dp2-lbl">Delete delay <span id="dp2-status-lbl" style="margin-left:6px;font-size:9px;color:rgba(255,255,255,.3)"><span class="dp2-status-dot" id="dp2-sdot"></span>Idle</span></div>
<div class="dp2-slider-row">
<input class="dp2-slider" id="dp2-delay" type="range" min="300" max="3000" value="750" step="50">
<span class="dp2-slider-val" id="dp2-delay-lbl">750ms</span>
</div>
</div>
<div class="dp2-stats-row">
<div class="dp2-stat"><div class="dp2-stat-val" id="dp2-v-del">0</div><div class="dp2-stat-lbl">Deleted</div></div>
<div class="dp2-stat"><div class="dp2-stat-val" id="dp2-v-fail" style="color:#ed4245">0</div><div class="dp2-stat-lbl">Failed</div></div>
<div class="dp2-stat"><div class="dp2-stat-val" id="dp2-v-scan" style="color:rgba(255,255,255,.4)">0</div><div class="dp2-stat-lbl">Scanned</div></div>
</div>
<div class="dp2-bar-track"><div class="dp2-bar-fill" id="dp2-bar"></div></div>
<div class="dp2-log" id="dp2-log"></div>
<div class="dp2-btn-row">
<button class="dp2-btn dp2-btn-secondary" id="dp2-clear-btn">Clear</button>
<button class="dp2-btn dp2-btn-primary" id="dp2-start-btn">🗑 Start</button>
<button class="dp2-btn dp2-btn-danger" id="dp2-stop-btn" style="display:none">⏹ Stop</button>
</div>
</div>
<!-- ═══ LOGGER ═══ -->
<div class="dp2-pane" id="dp2-pane-logger">
<div class="dp2-card">
<div class="dp2-lbl">Deleted Message Log <span id="dp2-log-cnt" style="color:#949cf7"></span></div>
<p style="font-size:11px;color:rgba(255,255,255,.3);font-weight:700;margin-bottom:8px">
Captures messages removed from the DOM while you're in a channel.
</p>
<div class="dp2-list" id="dp2-del-list"><div class="dp2-list-empty">No deleted messages captured yet.<br>Open a channel and wait.</div></div>
</div>
<div class="dp2-btn-row">
<button class="dp2-btn dp2-btn-secondary" id="dp2-clear-log">Clear</button>
<button class="dp2-btn dp2-btn-green" id="dp2-export-log">📥 Export JSON</button>
</div>
</div>
<!-- ═══ GHOST ═══ -->
<div class="dp2-pane" id="dp2-pane-ghost">
<div class="dp2-card">
<div class="dp2-lbl">Ghost Ping Tracker <span id="dp2-ghost-cnt" style="color:#c084fc"></span></div>
<p style="font-size:11px;color:rgba(255,255,255,.3);font-weight:700;margin-bottom:8px">
Detects @mentions deleted immediately — shows toast notification.
</p>
<div class="dp2-list" id="dp2-ghost-list"><div class="dp2-list-empty">No ghost pings detected.</div></div>
</div>
<div class="dp2-btn-row">
<button class="dp2-btn dp2-btn-secondary" id="dp2-clear-ghost">Clear</button>
</div>
</div>
<!-- ═══ EXPORT ═══ -->
<div class="dp2-pane" id="dp2-pane-export">
<div class="dp2-card">
<div class="dp2-lbl">Export Channel Messages</div>
<div class="dp2-field" style="margin-bottom:8px">
<label>Channel ID</label>
<input class="dp2-input" id="dp2-exp-ch" placeholder="channel to export">
<button class="dp2-inline-btn" id="dp2-exp-auto">AUTO</button>
</div>
<div class="dp2-row" style="margin-bottom:8px">
<div class="dp2-field">
<label>Format</label>
<select class="dp2-select" id="dp2-exp-fmt">
<option value="html">HTML (pretty)</option>
<option value="json">JSON (raw)</option>
<option value="txt">TXT (plain)</option>
</select>
</div>
<div class="dp2-field">
<label>Limit</label>
<input class="dp2-input" id="dp2-exp-lim" type="number" value="500" min="1" max="10000">
</div>
</div>
<div class="dp2-warn">⚠ Token must be set in the Delete tab first.</div>
</div>
<div class="dp2-log" id="dp2-exp-log"></div>
<div class="dp2-btn-row">
<button class="dp2-btn dp2-btn-primary" id="dp2-exp-start">📦 Export</button>
</div>
</div>
<!-- ═══ TOKEN ═══ -->
<div class="dp2-pane" id="dp2-pane-token">
<div class="dp2-card">
<div class="dp2-lbl">Your Token</div>
<div class="dp2-field" style="margin-bottom:8px">
<label>Token (hidden)</label>
<input class="dp2-input" id="dp2-tok-disp" type="password" placeholder="not loaded" readonly>
</div>
<div class="dp2-btn-row" style="margin-bottom:8px">
<button class="dp2-btn dp2-btn-secondary" id="dp2-tok-detect">🔍 Detect</button>
<button class="dp2-btn dp2-btn-secondary" id="dp2-tok-copy">📋 Copy</button>
<button class="dp2-btn dp2-btn-danger" id="dp2-tok-clear">🗑 Clear</button>
</div>
<div class="dp2-warn">⚠ <strong>NEVER share your token.</strong> It gives full access to your account. If exposed — logout, change password, enable 2FA immediately.</div>
</div>
<div class="dp2-user-card" id="dp2-userinfo" style="color:rgba(255,255,255,.3);font-weight:700">
Detect token to see account info.
</div>
</div>
<!-- ═══ ABOUT ═══ -->
<div class="dp2-pane" id="dp2-pane-about">
<div class="dp2-about">
<div class="dp2-about-logo">🛠️</div>
<div class="dp2-about-title">Discord Plus+ V1</div>
<div class="dp2-about-sub">v${VERSION} · The all-in-one Discord web toolkit</div>
<ul class="dp2-feat-list">
<li>Bulk Message Deleter — Discord API v10, smart rate-limit & retry</li>
<li>Deleted Message Logger — real-time DOM capture, 500 msg buffer</li>
<li>Ghost Ping Detector — auto-alert with toast notification</li>
<li>Message Exporter — HTML / JSON / TXT download</li>
<li>Token Detector — 3-strategy extraction + account info</li>
<li>Draggable panel — tabbed UI, glassmorphism design</li>
<li>Alt+D shortcut — toggle panel anytime</li>
<li>100% local — zero external requests</li>
</ul>
<a class="dp2-community-btn" href="${COMMUNITY}" target="_blank" rel="noopener">
💬 Join Community · discord.gg/Gvmd7deFtS
</a>
<div style="font-size:10px;color:rgba(255,255,255,.2);margin-top:10px;font-weight:700">
⚠ Self-bot actions may violate Discord TOS. Use at your own risk.<br>
MIT License · Works with Tampermonkey & Violentmonkey
</div>
</div>
</div>
</div><!-- /body -->
<!-- Footer -->
<div class="dp2-foot">
<div class="dp2-foot-txt">© Discord Plus+ v${VERSION} by 2pixel · Alt+D to toggle</div>
</div>
`;
document.body.appendChild(panel);
return { panel, tog };
}
// ─── WIRE EVENTS ──────────────────────────────────────────────────────────────
function wire(panel, tog) {
// Drag
const hdr = panel.querySelector('.dp2-hdr');
let drag = false, ox = 0, oy = 0, cx, cy;
const vw = innerWidth, vh = innerHeight;
cx = vw - 390; cy = Math.max(8, vh - 620);
panel.style.transform = `translate3d(${cx}px,${cy}px,0)`;
hdr.addEventListener('pointerdown', e => {
drag = true;
panel.classList.add('dp2-dragging');
const r = panel.getBoundingClientRect();
ox = e.clientX - r.left; oy = e.clientY - r.top;
try { hdr.setPointerCapture(e.pointerId); } catch(_){}
e.preventDefault();
}, { passive: false });
hdr.addEventListener('pointermove', e => {
if (!drag) return;
e.preventDefault();
const maxX = innerWidth - panel.offsetWidth - 8;
const maxY = innerHeight - panel.offsetHeight - 8;
cx = Math.max(8, Math.min(maxX, e.clientX - ox));
cy = Math.max(8, Math.min(maxY, e.clientY - oy));
panel.style.transform = `translate3d(${cx}px,${cy}px,0)`;
}, { passive: false });
hdr.addEventListener('pointerup', () => { drag = false; panel.classList.remove('dp2-dragging'); });
hdr.addEventListener('pointercancel', () => { drag = false; panel.classList.remove('dp2-dragging'); });
// Toggle
let vis = false;
tog.addEventListener('click', () => {
vis = !vis;
panel.classList.toggle('dp2-show', vis);
tog.classList.toggle('dp2-active', vis);
});
// Close / Minimize
panel.querySelector('#dp2-close').onclick = () => { vis = false; panel.classList.remove('dp2-show'); tog.classList.remove('dp2-active'); };
panel.querySelector('#dp2-min').onclick = () => {
const b = panel.querySelector('.dp2-body');
const f = panel.querySelector('.dp2-foot');
const p = panel.querySelector('.dp2-pills');
const t = panel.querySelector('.dp2-tabs');
const hidden = b.style.display === 'none';
[b, f, p, t].forEach(el => { if(el) el.style.display = hidden ? '' : 'none'; });
};
// Tabs
panel.querySelectorAll('.dp2-tab').forEach(tab => {
tab.onclick = () => {
panel.querySelectorAll('.dp2-tab').forEach(t=>t.classList.remove('active'));
panel.querySelectorAll('.dp2-pane').forEach(p=>p.classList.remove('active'));
tab.classList.add('active');
panel.querySelector(`#dp2-pane-${tab.dataset.tab}`).classList.add('active');
};
});
// Checkboxes
panel.querySelectorAll('.dp2-chk').forEach(c => c.onclick = ()=>c.classList.toggle('on'));
// Delay slider
const slider = panel.querySelector('#dp2-delay');
const slLbl = panel.querySelector('#dp2-delay-lbl');
slider.oninput = () => { slLbl.textContent = slider.value + 'ms'; };
// ── Delete tab ──
const tokenIn = panel.querySelector('#dp2-token');
panel.querySelector('#dp2-auto-tok').onclick = () => {
const t = grabToken();
if (t) { tokenIn.value = t; S.token = t; appendLog('Token auto-detected ✓', 'ok'); }
else appendLog('Could not detect — paste manually', 'warn');
};
panel.querySelector('#dp2-auto-ch').onclick = () => {
const ch = getChannelId();
if (ch) { panel.querySelector('#dp2-channel').value = ch; appendLog(`Channel: ${ch}`, 'ok'); }
else appendLog('Navigate to a channel first', 'warn');
};
panel.querySelector('#dp2-auto-guild').onclick = () => {
const g = getGuildId();
if (g) { panel.querySelector('#dp2-guild').value = g; appendLog(`Guild: ${g}`, 'ok'); }
else appendLog('Not in a guild', 'warn');
};
panel.querySelector('#dp2-auto-me').onclick = async () => {
const t = tokenIn.value.trim() || grabToken();
if (!t) { appendLog('Set token first', 'warn'); return; }
S.token = t;
try {
const me = await getMe();
panel.querySelector('#dp2-author').value = me.id;
appendLog(`Author: ${me.id} (${me.username})`, 'ok');
} catch(e) { appendLog('Error: '+e.message, 'error'); }
};
panel.querySelector('#dp2-clear-btn').onclick = () => {
if (S.delRunning) return;
['#dp2-token','#dp2-channel','#dp2-guild','#dp2-author','#dp2-content','#dp2-from','#dp2-to']
.forEach(s => { panel.querySelector(s).value = ''; });
panel.querySelectorAll('.dp2-chk').forEach(c=>c.classList.remove('on'));
panel.querySelector('#dp2-log').innerHTML = '';
setStatus('');
S.delCount = S.failCount = S.scanCount = 0;
uiSync();
};
panel.querySelector('#dp2-stop-btn').onclick = () => { S.delStopped = true; S.delRunning = false; };
panel.querySelector('#dp2-start-btn').onclick = async () => {
if (S.delRunning) return;
const token = tokenIn.value.trim() || grabToken();
const ch = panel.querySelector('#dp2-channel').value.trim();
if (!token) { appendLog('Token required', 'error'); return; }
if (!ch) { panel.querySelector('#dp2-channel').classList.add('error'); appendLog('Channel ID required', 'error'); return; }
panel.querySelectorAll('.dp2-input').forEach(i=>i.classList.remove('error'));
S.token = token;
toggleBtn('start', false);
toggleBtn('stop', true);
await runDelete({
channelId: ch,
authorId: panel.querySelector('#dp2-author').value.trim() || null,
guildId: panel.querySelector('#dp2-guild').value.trim() || null,
content: panel.querySelector('#dp2-content').value.trim() || null,
minDate: panel.querySelector('#dp2-from').value ? new Date(panel.querySelector('#dp2-from').value+'T00:00:00') : null,
maxDate: panel.querySelector('#dp2-to').value ? new Date(panel.querySelector('#dp2-to').value+'T23:59:59') : null,
hasLink: panel.querySelector('#chk-link').classList.contains('on'),
hasFile: panel.querySelector('#chk-file').classList.contains('on'),
nsfw: panel.querySelector('#chk-nsfw').classList.contains('on'),
delayMs: parseInt(slider.value),
});
toggleBtn('stop', false);
toggleBtn('start', true);
};
// ── Logger tab ──
panel.querySelector('#dp2-clear-log').onclick = () => { S.deletedLog = []; renderLogList(); };
panel.querySelector('#dp2-export-log').onclick = () => {
const b = new Blob([JSON.stringify(S.deletedLog, null, 2)], { type:'application/json' });
const a = document.createElement('a');
a.href = URL.createObjectURL(b);
a.download = 'deleted-messages.json';
a.click();
};
// ── Ghost tab ──
panel.querySelector('#dp2-clear-ghost').onclick = () => { S.ghostPings = []; renderGhostList(); };
// ── Export tab ──
panel.querySelector('#dp2-exp-auto').onclick = () => {
const ch = getChannelId();
if (ch) panel.querySelector('#dp2-exp-ch').value = ch;
};
panel.querySelector('#dp2-exp-start').onclick = async () => {
const t = tokenIn.value.trim() || grabToken();
if (!t) { showToast('Error', 'Set token in Delete tab first', '#ed4245'); return; }
S.token = t;
const ch = panel.querySelector('#dp2-exp-ch').value.trim();
const fmt = panel.querySelector('#dp2-exp-fmt').value;
const lim = parseInt(panel.querySelector('#dp2-exp-lim').value) || 500;
if (!ch) return;
await exportChannel(ch, fmt, lim);
};
// ── Token tab ──
panel.querySelector('#dp2-tok-detect').onclick = async () => {
const t = grabToken();
if (!t) { showToast('Error', 'Could not detect token', '#ed4245'); return; }
S.token = t;
tokenIn.value = t;
panel.querySelector('#dp2-tok-disp').value = t;
try {
const me = await getMe();
panel.querySelector('#dp2-userinfo').innerHTML = `
<div style="color:#fff;font-size:14px;font-weight:900;margin-bottom:6px">${esc(me.username)}<span style="color:rgba(255,255,255,.3)">#${me.discriminator||'0'}</span></div>
<div style="color:rgba(255,255,255,.4);margin-bottom:2px">ID: <span style="color:#949cf7">${me.id}</span></div>
<div style="color:rgba(255,255,255,.4);margin-bottom:2px">Email: <span style="color:#949cf7">${esc(me.email||'hidden')}</span></div>
<div style="color:rgba(255,255,255,.4)">2FA: <span style="color:${me.mfa_enabled?'#57f287':'#ed4245'}">${me.mfa_enabled?'Enabled ✓':'Disabled ✗'}</span></div>
`;
} catch(e) {
panel.querySelector('#dp2-userinfo').innerHTML = `<span style="color:#ed4245">${esc(e.message)}</span>`;
}
};
panel.querySelector('#dp2-tok-copy').onclick = () => {
const t = S.token || panel.querySelector('#dp2-tok-disp').value;
if (t) navigator.clipboard.writeText(t).then(()=>showToast('Copied!','Token copied — keep it safe','#3ba55d'));
};
panel.querySelector('#dp2-tok-clear').onclick = () => {
S.token = null;
tokenIn.value = '';
panel.querySelector('#dp2-tok-disp').value = '';
panel.querySelector('#dp2-userinfo').textContent = 'Detect token to see account info.';
panel.querySelector('#dp2-userinfo').style.color = 'rgba(255,255,255,.3)';
};
// Alt+D shortcut
document.addEventListener('keydown', e => {
if (e.altKey && e.key === 'D') {
vis = !vis;
panel.classList.toggle('dp2-show', vis);
tog.classList.toggle('dp2-active', vis);
}
});
// Auto-detect on load
setTimeout(() => {
const t = grabToken();
if (t) { S.token = t; tokenIn.value = t; }
}, 1500);
}
// ─── UI STATE HELPERS ─────────────────────────────────────────────────────────
function uiSync() {
const set = (id, v) => { const el = document.getElementById(id); if(el) el.textContent = v; };
set('dp2-s-del', S.delCount);
set('dp2-s-fail', S.failCount);
set('dp2-s-scan', S.scanCount);
set('dp2-v-del', S.delCount);
set('dp2-v-fail', S.failCount);
set('dp2-v-scan', S.scanCount);
}
function appendLog(msg, type='info') {
['#dp2-log','#dp2-exp-log'].forEach(sel => {
const el = document.querySelector(sel);
if (!el) return;
const t = new Date().toLocaleTimeString('en-GB',{hour12:false});
const line = document.createElement('div');
line.className = 'dp2-ll ' + type;
line.textContent = `[${t}] ${msg}`;
el.appendChild(line);
el.scrollTop = el.scrollHeight;
while (el.childElementCount > 300) el.removeChild(el.firstChild);
});
}
function setStatus(state) {
const dot = document.getElementById('dp2-sdot');
const lbl = document.getElementById('dp2-status-lbl');
const bar = document.getElementById('dp2-bar');
if (dot) dot.className = 'dp2-status-dot' + (state ? ' '+state : '');
if (lbl) {
const labels = { running:'Running…', done:'Done ✓', '':'Idle' };
lbl.innerHTML = `<span class="dp2-status-dot${state?' '+state:''}" id="dp2-sdot"></span>${labels[state]||'Idle'}`;
}
if (bar) {
bar.classList.toggle('running', state==='running');
if (state==='done') bar.style.width='100%';
if (!state) bar.style.width='0';
}
}
function toggleBtn(id, show) {
const el = document.getElementById(id==='start' ? 'dp2-start-btn' : 'dp2-stop-btn');
if (!el) return;
if (id==='start') el.disabled = !show;
el.style.display = show ? '' : 'none';
}
function renderLogList() {
const list = document.getElementById('dp2-del-list');
const cnt = document.getElementById('dp2-log-cnt');
if (!list) return;
if (cnt) cnt.textContent = `(${S.deletedLog.length})`;
if (!S.deletedLog.length) { list.innerHTML = '<div class="dp2-list-empty">No deleted messages captured yet.<br>Open a channel and wait.</div>'; return; }
list.innerHTML = S.deletedLog.slice(0,100).map(e=>`
<div class="dp2-li">
<div class="dp2-li-head">
<span class="dp2-li-author">${esc(e.author)}</span>
<span class="dp2-li-time">${fmtTime(e.time)}</span>
</div>
<div class="dp2-li-body">${esc(e.content.slice(0,200))}${e.content.length>200?'…':''}</div>
</div>`).join('');
}
function renderGhostList() {
const list = document.getElementById('dp2-ghost-list');
const cnt = document.getElementById('dp2-ghost-cnt');
if (!list) return;
if (cnt) cnt.textContent = `(${S.ghostPings.length})`;
if (!S.ghostPings.length) { list.innerHTML = '<div class="dp2-list-empty">No ghost pings detected.</div>'; return; }
list.innerHTML = S.ghostPings.slice(0,50).map(e=>`
<div class="dp2-li ghost">
<div class="dp2-li-head">
<span class="dp2-li-author">👻 ${esc(e.author)}</span>
<span class="dp2-li-time">${fmtTime(e.time)}</span>
</div>
<div class="dp2-li-body">${esc(e.content.slice(0,200))}</div>
</div>`).join('');
}
// ─── BOOT ─────────────────────────────────────────────────────────────────────
function boot() {
if (document.getElementById(PANEL_ID)) return;
const { panel, tog } = buildPanel();
wire(panel, tog);
setTimeout(() => startLogger(), 2000);
console.log(`%cDiscord Plus+ v${VERSION} loaded 🛠️`, 'color:#949cf7;font-weight:bold;font-size:13px;background:#1e2028;padding:4px 10px;border-radius:8px');
console.log(`%cCommunity: ${COMMUNITY}`, 'color:#5865F2;font-size:11px');
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setTimeout(boot, 1200));
} else {
setTimeout(boot, 1200);
}