Advertisement
kilo

Discord Mass Delete Messages

Dec 17th, 2019
931
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Paste this code inside discordapp.com console
  2.  
  3. (function () {
  4.     let stop;
  5.     let popup;
  6.     popup = window.open('', '', `top=0,left=${screen.width-800},width=800,height=${screen.height}`);
  7.     if (!popup) return console.error('Popup blocked! Please allow popups and try again.');
  8.     popup.document.write(/*html*/`<!DOCTYPE html>
  9.     <html><head><meta charset='utf-8'><title>Delete Discord Messages</title><base target="_blank">
  10.     <style>body{background-color:#36393f;color:#dcddde;font-family:sans-serif;} a{color:#00b0f4;}
  11.     body.redact .priv{display:none;} body:not(.redact) .mask{display:none;} body.redact [priv]{-webkit-text-security:disc;}
  12.     .toolbar span{margin-right:8px;}
  13.     button{color:#fff;background:#7289da;border:0;border-radius:4px;font-size:14px;} button:disabled{display:none;}
  14.     input[type="text"],input[type="password"]{background-color:#202225;color:#b9bbbe;border-radius:4px;border:0;padding:0 .5em;height:24px;width:144px;margin:2px;}
  15.     </style></head><body>
  16.     <div class="toolbar" style="position:fixed;top:0;left:0;right:0;padding:8px;background:#36393f;box-shadow: 0 1px 0 rgba(0,0,0,.2), 0 1.5px 0 rgba(0,0,0,.05), 0 2px 0 rgba(0,0,0,.05);">
  17.         <div style="display:flex;flex-wrap:wrap;">
  18.             <span>Authorization <a href="https://github.com/victornpb/deleteDiscordMessages/blob/master/help/authToken.md" title="Help">?</a>
  19.                 <button id="getToken">Get</button>
  20.                 <br><input type="password" id="authToken" placeholder="Auth Token" autofocus>*</span>
  21.             <span>Author <a href="https://github.com/victornpb/deleteDiscordMessages/blob/master/help/authorId.md" title="Help">?</a>
  22.                 <button id="getAuthor">Me</button><br><input id="authorId" type="text" placeholder="Author ID" priv>*</span>
  23.             <span>Guild/Channel <a href="https://github.com/victornpb/deleteDiscordMessages/blob/master/help/channelId.md" title="Help">?</a>
  24.                 <button id="getGuildAndChannel">Get</button><br>
  25.                 <input id="guildId" type="text" placeholder="Guild ID" priv>*<br>
  26.                 <input id="channelId" type="text" placeholder="Channel ID" priv>*</span><br>
  27.             <span>Range <a href="https://github.com/victornpb/deleteDiscordMessages/blob/master/help/messageId.md" title="Help">?</a><br>
  28.                 <input id="afterMessageId" type="text" placeholder="After messageId" priv><br>
  29.                 <input id="beforeMessageId" type="text" placeholder="Before messageId" priv>
  30.             </span>
  31.             <span>Filter <a href="https://github.com/victornpb/deleteDiscordMessages/blob/master/help/filters.md" title="Help">?</a><br>
  32.                 <label><input id="hasLink" type="checkbox">has: link</label><br>
  33.                 <label><input id="hasFile" type="checkbox">has: file</label>
  34.             </span>
  35.         </div>
  36.         <button id="start" style="background:#43b581;width:80px;">Start</button>
  37.         <button id="stop" style="background:#f04747;width:80px;" disabled>Stop</button>
  38.         <button id="clear" style="width:80px;">Clear log</button>
  39.         <label><input id="redact" type="checkbox"><small>Hide sensitive information</small></label> <span></span>
  40.         <label><input id="autoScroll" type="checkbox" checked><small>Auto scroll</small></label> <span></span>
  41.     </div>
  42.     <pre style="margin-top:150px;font-size:0.75rem;font-family:Consolas,Liberation Mono,Menlo,Courier,monospace;">
  43.         <center>Star this project on <a href="https://github.com/victornpb/deleteDiscordMessages" target="_blank">github.com/victornpb/deleteDiscordMessages</a>!\n\n
  44.             <a href="https://github.com/victornpb/deleteDiscordMessages/issues" target="_blank">Issues or help</a></center>
  45.         </pre></body></html>`);
  46.  
  47.     const logArea = popup.document.querySelector('pre');
  48.     const startBtn = popup.document.querySelector('button#start');
  49.     const stopBtn = popup.document.querySelector('button#stop');
  50.     const autoScroll = popup.document.querySelector('#autoScroll');
  51.     startBtn.onclick = e => {
  52.         const authToken = popup.document.querySelector('input#authToken').value.trim();
  53.         const authorId = popup.document.querySelector('input#authorId').value.trim();
  54.         const guildId = popup.document.querySelector('input#guildId').value.trim();
  55.         const channelId = popup.document.querySelector('input#channelId').value.trim();
  56.         const afterMessageId = popup.document.querySelector('input#afterMessageId').value.trim();
  57.         const beforeMessageId = popup.document.querySelector('input#beforeMessageId').value.trim();
  58.         const hasLink = popup.document.querySelector('input#hasLink').checked;
  59.         const hasFile = popup.document.querySelector('input#hasFile').checked;
  60.         stop = stopBtn.disabled = !(startBtn.disabled = true);
  61.         deleteMessages(authToken, authorId, guildId, channelId, afterMessageId, beforeMessageId, hasLink, hasFile, logger, () => !(stop === true || popup.closed)).then(() => {
  62.             stop = stopBtn.disabled = !(startBtn.disabled = false);
  63.         });
  64.     };
  65.     stopBtn.onclick = e => stop = stopBtn.disabled = !(startBtn.disabled = false);
  66.     popup.document.querySelector('button#clear').onclick = e => { logArea.innerHTML = ''; };
  67.     popup.document.querySelector('button#getToken').onclick = e => {
  68.         window.dispatchEvent(new Event('beforeunload'));
  69.         popup.document.querySelector('input#authToken').value = JSON.parse(popup.localStorage.token);
  70.     };
  71.     popup.document.querySelector('button#getAuthor').onclick = e => {
  72.         popup.document.querySelector('input#authorId').value = JSON.parse(popup.localStorage.user_id_cache);
  73.     };
  74.     popup.document.querySelector('button#getGuildAndChannel').onclick = e => {
  75.         const m = location.href.match(/channels\/([\w@]+)\/(\d+)/);
  76.         popup.document.querySelector('input#guildId').value = m[1];
  77.         popup.document.querySelector('input#channelId').value = m[2];
  78.     };
  79.     popup.document.querySelector('#redact').onchange = e => {
  80.         popup.document.body.classList.toggle('redact') &&
  81.         popup.alert('This will attempt to hide personal information, but make sure to double check before sharing screenshots.');
  82.     };
  83.  
  84.     const logger = (type='', args) => {
  85.         const style = { '': '', info: 'color:#00b0f4;', verb: 'color:#72767d;', warn: 'color:#faa61a;', error: 'color:#f04747;', success: 'color:#43b581;' }[type];
  86.         logArea.insertAdjacentHTML('beforeend', `<div style="${style}">${Array.from(args).map(o => typeof o === 'object' ?  JSON.stringify(o, o instanceof Error && Object.getOwnPropertyNames(o)) : o).join('\t')}</div>`);
  87.         if (autoScroll.checked) logArea.querySelector('div:last-child').scrollIntoView(false);
  88.     };
  89.  
  90.     return 'Looking good!';
  91.     /**
  92.      * Delete all messages in a Discord channel or DM
  93.      * @param {string} authToken Your authorization token
  94.      * @param {string} authorId Author of the messages you want to delete
  95.      * @param {string} guildId Server were the messages are located
  96.      * @param {string} channelId Channel were the messages are located
  97.      * @param {string} afterMessageId Only delete messages after this, leave blank do delete all
  98.      * @param {string} beforeMessageId Only delete messages before this, leave blank do delete all
  99.      * @param {boolean} hasLink Filter messages that contains link
  100.      * @param {boolean} hasFile Filter messages that contains file
  101.      * @param {function(string, Array)} extLogger Function for logging
  102.      * @param {function} stopHndl stopHndl used for stopping
  103.      * @author Victornpb <https://www.github.com/victornpb>
  104.      * @see https://github.com/victornpb/deleteDiscordMessages
  105.      */
  106.     async function deleteMessages(authToken, authorId, guildId, channelId, afterMessageId, beforeMessageId, hasLink, hasFile, extLogger, stopHndl) {
  107.         const start = new Date();
  108.         let deleteDelay = 100;
  109.         let searchDelay = 100;
  110.         let delCount = 0;
  111.         let failCount = 0;
  112.         let avgPing;
  113.         let lastPing;
  114.         let grandTotal;
  115.         let throttledCount = 0;
  116.         let throttledTotalTime = 0;
  117.         let offset = 0;
  118.         let iterations = -1;
  119.        
  120.         const wait = async ms => new Promise(done => setTimeout(done, ms));
  121.         const msToHMS = s => `${s / 3.6e6 | 0}h ${(s % 3.6e6) / 6e4 | 0}m ${(s % 6e4) / 1000 | 0}s`;
  122.         const escapeHTML = html => html.replace(/[&<"']/g, m => ({ '&': '&amp;', '<': '&lt;', '"': '&quot;', '\'': '&#039;' })[m]);
  123.         const redact = str => `<span class="priv">${escapeHTML(str)}</span><span class="mask">REDACTED</span>`;
  124.         const queryString = params => params.filter(p => p[1] !== undefined).map(p => p[0] + '=' + encodeURIComponent(p[1])).join('&');
  125.         const ask = async msg => new Promise(resolve => setTimeout(() => resolve(popup.confirm(msg)), 10));
  126.         const printDelayStats = () => log.verb(`Delete delay: ${deleteDelay}ms, Search delay: ${searchDelay}ms`, `Last Ping: ${lastPing}ms, Average Ping: ${avgPing|0}ms`);
  127.  
  128.         const log = {
  129.             debug() { extLogger ? extLogger('debug', arguments) : console.debug.apply(console, arguments); },
  130.             info() { extLogger ? extLogger('info', arguments) : console.info.apply(console, arguments); },
  131.             verb() { extLogger ? extLogger('verb', arguments) : console.log.apply(console, arguments); },
  132.             warn() { extLogger ? extLogger('warn', arguments) : console.warn.apply(console, arguments); },
  133.             error() { extLogger ? extLogger('error', arguments) : console.error.apply(console, arguments); },
  134.             success() { extLogger ? extLogger('success', arguments) : console.info.apply(console, arguments); },
  135.         };
  136.  
  137.         async function recurse() {
  138.             iterations++;
  139.  
  140.             let API_SEARCH_URL;
  141.             if (guildId === '@me') {
  142.                 API_SEARCH_URL = `https://discordapp.com/api/v6/channels/${channelId}/messages/`; // DMs
  143.             }
  144.             else {
  145.                 API_SEARCH_URL = `https://discordapp.com/api/v6/guilds/${guildId}/messages/`; // Server
  146.             }
  147.  
  148.             const headers = {
  149.                 'Authorization': authToken
  150.             };
  151.            
  152.             let resp;
  153.             try {
  154.                 const s = Date.now();
  155.                 resp = await fetch(API_SEARCH_URL + 'search?' + queryString([
  156.                     [ 'author_id', authorId || undefined ],
  157.                     [ 'channel_id', (guildId !== '@me' ? channelId : undefined) || undefined ],
  158.                     [ 'min_id', afterMessageId || undefined ],
  159.                     [ 'max_id', beforeMessageId || undefined ],
  160.                     [ 'sort_by', 'timestamp' ],
  161.                     [ 'sort_order', 'desc' ],
  162.                     [ 'offset', offset ],
  163.                     [ 'has', hasLink ? 'link' : undefined ],
  164.                     [ 'has', hasFile ? 'file' : undefined ],
  165.                 ]), { headers });
  166.                 lastPing = (Date.now() - s);
  167.                 avgPing = avgPing>0 ? (avgPing*0.9) + (lastPing*0.1):lastPing;
  168.             } catch (err) {
  169.                 return log.error('Search request throwed an error:', err);
  170.             }
  171.    
  172.             // not indexed yet
  173.             if (resp.status === 202) {
  174.                 const w = (await resp.json()).retry_after;
  175.                 throttledCount++;
  176.                 throttledTotalTime += w;
  177.                 log.warn(`This channel wasn't indexed, waiting ${w}ms for discord to index it...`);
  178.                await wait(w);
  179.                return await recurse();
  180.            }
  181.    
  182.            if (!resp.ok) {
  183.                // searching messages too fast
  184.                if (resp.status === 429) {
  185.                    const w = (await resp.json()).retry_after;
  186.                    throttledCount++;
  187.                    throttledTotalTime += w;
  188.                    searchDelay += w; // increase delay
  189.                    log.warn(`Being rate limited by the API for ${w}ms! Increasing search delay...`);
  190.                    printDelayStats();
  191.                    log.verb(`Cooling down for ${w * 2}ms before retrying...`);
  192.                    
  193.                    await wait(w*2);
  194.                    return await recurse();
  195.                } else {
  196.                    return log.error(`Error searching messages, API responded with status ${resp.status}!\n`, await resp.json());
  197.                }
  198.            }
  199.    
  200.            const data = await resp.json();
  201.            const total = data.total_results;
  202.            if (!grandTotal) grandTotal = total;
  203.            const myMessages = data.messages.map(convo => convo.find(message => message.hit===true));
  204.            const systemMessages = myMessages.filter(msg => msg.type !== 0); // https://discordapp.com/developers/docs/resources/channel#message-object-message-types
  205.            const deletableMessages = myMessages.filter(msg => msg.type === 0);
  206.            const end = () => {
  207.                log.success(`Ended at ${new Date().toLocaleString()}! Total time: ${msToHMS(Date.now() - start.getTime())}`);
  208.                printDelayStats();
  209.                log.verb(`Rate Limited: ${throttledCount} times. Total time throttled: ${msToHMS(throttledTotalTime)}.`);
  210.                log.debug(`Deleted ${delCount} messages, ${failCount} failed.\n`);
  211.            }
  212.  
  213.            const etr = msToHMS((searchDelay * Math.round(total / 25)) + ((deleteDelay + avgPing) * total));
  214.            log.info(`Total messages found: ${data.total_results}`, `(Messages in current page: ${data.messages.length}, Author: ${deletableMessages.length}, System: ${systemMessages.length})`, `offset: ${offset}`);
  215.            printDelayStats();
  216.            log.verb(`Estimated time remaining: ${etr}`)
  217.            
  218.            
  219.            if (myMessages.length > 0) {
  220.  
  221.                if (iterations < 1) {
  222.                    log.verb(`Waiting for your confirmation...`);
  223.                    if (!await ask(`Do you want to delete ~${total} messages?\nEstimated time: ${etr}\n\n---- Preview ----\n` +
  224.                        myMessages.map(m => `${m.author.username}#${m.author.discriminator}: ${m.attachments.length ? '[ATTACHMENTS]' : m.content}`).join('\n')))
  225.                            return end(log.error('Aborted by you!'));
  226.                    log.verb(`OK`);
  227.                }
  228.                
  229.                for (let i = 0; i < deletableMessages.length; i++) {
  230.                    const message = deletableMessages[i];
  231.                    if (stopHndl && stopHndl()===false) return end(log.error('Stopped by you!'));
  232.  
  233.                    log.debug(`${((delCount + 1) / grandTotal * 100).toFixed(2)}% (${delCount + 1}/${grandTotal})`,
  234.                        `Deleting ID:${redact(message.id)} <b>${redact(message.author.username+'#'+message.author.discriminator)} <small>(${redact(new Date(message.timestamp).toLocaleString())})</small>:</b> <i>${redact(message.content).replace(/\n/g,'')}</i>`,
  235.                        message.attachments.length ? redact(JSON.stringify(message.attachments)) : '');
  236.                    
  237.                    let resp;
  238.                    try {
  239.                        const s = Date.now();
  240.                        const API_DELETE_URL = `https://discordapp.com/api/v6/channels/${channelId}/messages/`;
  241.                        resp = await fetch(API_DELETE_URL + message.id, {
  242.                            headers,
  243.                            method: 'DELETE'
  244.                        });
  245.                        lastPing = (Date.now() - s);
  246.                        avgPing = (avgPing*0.9) + (lastPing*0.1);
  247.                        delCount++;
  248.                    } catch (err) {
  249.                        log.error('Delete request throwed an error:', err);
  250.                        log.verb('Related object:', redact(JSON.stringify(message)));
  251.                        failCount++;
  252.                    }
  253.  
  254.                    if (!resp.ok) {
  255.                        // deleting messages too fast
  256.                        if (resp.status === 429) {
  257.                            const w = (await resp.json()).retry_after;
  258.                            throttledCount++;
  259.                            throttledTotalTime += w;
  260.                            deleteDelay += w; // increase delay
  261.                            log.warn(`Being rate limited by the API for ${w}ms! Adjusted delete delay to ${deleteDelay}ms.`);
  262.                            printDelayStats();
  263.                            log.verb(`Cooling down for ${w*2}ms before retrying...`);
  264.                            await wait(w*2);
  265.                            i--; // retry
  266.                        } else {
  267.                            log.error(`Error deleting message, API responded with status ${resp.status}!`, await resp.json());
  268.                            log.verb('Related object:', redact(JSON.stringify(message)));
  269.                            failCount++;
  270.                        }
  271.                    }
  272.                    
  273.                    await wait(deleteDelay);
  274.                }
  275.  
  276.                if (systemMessages.length > 0) {
  277.                    grandTotal -= systemMessages.length;
  278.                    offset += systemMessages.length;
  279.                    log.verb(`Found ${systemMessages.length} system messages! Decreasing grandTotal to ${grandTotal} and increasing offset to ${offset}.`);
  280.                }
  281.                
  282.                log.verb(`Searching next messages in ${searchDelay}ms...`, (offset ? `(offset: ${offset})` : '') );
  283.                await wait(searchDelay);
  284.  
  285.                if (stopHndl && stopHndl()===false) return end(log.error('Stopped by you!'));
  286.  
  287.                return await recurse();
  288.            } else {
  289.                if (total - offset > 0) log.warn('Ended because API returned an empty page.');
  290.                return end();
  291.            }
  292.        }
  293.  
  294.        log.success(`\nStarted at ${start.toLocaleString()}`);
  295.        log.debug(`authorId="${redact(authorId)}" guildId="${redact(guildId)}" channelId="${redact(channelId)}" afterMessageId="${redact(afterMessageId)}" beforeMessageId="${redact(beforeMessageId)}" hasLink=${!!hasLink} hasFile=${!!hasFile}`);
  296.        return await recurse();
  297.    }
  298. })();
  299.  
  300. //END.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement