diff --git a/deleteDiscordMessages.js b/deleteDiscordMessages.js index 5a46946c..8dffd289 100644 --- a/deleteDiscordMessages.js +++ b/deleteDiscordMessages.js @@ -3,84 +3,122 @@ (function () { let stop; let popup; - popup = window.open('', '', `top=0,left=${screen.width-800},width=800,height=${screen.height}`); - if (!popup) return console.error('Popup blocked! Please allow popups and try again.'); + popup = window.open('', '', `top=0,left=${screen.width-800},width=850,height=${screen.height}`); + if(!popup || !popup.document || !popup.document.write) return console.error('Popup blocked! Please allow popups and try again.'); popup.document.write(/*html*/` Delete Discord Messages -
- Authorization ? -
+ Authorization ?
*
- Author ? -
+ Author ? +
Guild/Channel ? -
+

-

+
+

+ +
Range ?
-
- +
+
+
+
- Filter ?
+ Search messages ?



- +
+
- - + + + +
-
+    
         
Star this project on github.com/victornpb/deleteDiscordMessages!\n\n Issues or help
`); - const logArea = popup.document.querySelector('pre'); - const startBtn = popup.document.querySelector('button#start'); - const stopBtn = popup.document.querySelector('button#stop'); - const autoScroll = popup.document.querySelector('#autoScroll'); - startBtn.onclick = e => { - const authToken = popup.document.querySelector('input#authToken').value.trim(); - const authorId = popup.document.querySelector('input#authorId').value.trim(); - const guildId = popup.document.querySelector('input#guildId').value.trim(); - const channelId = popup.document.querySelector('input#channelId').value.trim(); - const afterMessageId = popup.document.querySelector('input#afterMessageId').value.trim(); - const beforeMessageId = popup.document.querySelector('input#beforeMessageId').value.trim(); - const content = popup.document.querySelector('input#content').value.trim(); - const hasLink = popup.document.querySelector('input#hasLink').checked; - const hasFile = popup.document.querySelector('input#hasFile').checked; - const includeNsfw = popup.document.querySelector('input#includeNsfw').checked; + const $ = s => popup.document.querySelector(s); + const logArea = $('pre'); + const startBtn = $('button#start'); + const stopBtn = $('button#stop'); + const autoScroll = $('#autoScroll'); + startBtn.onclick = async e => { + const authToken = $('input#authToken').value.trim(); + const authorId = $('input#authorId').value.trim(); + const guildId = $('input#guildId').value.trim(); + const channelIds = $('input#channelId').value.trim().split(/\s*,\s*/); + const minId = $('input#minId').value.trim(); + const maxId = $('input#maxId').value.trim(); + const minDate = $('input#minDate').value.trim(); + const maxDate = $('input#maxDate').value.trim(); + const content = $('input#content').value.trim(); + const hasLink = $('input#hasLink').checked; + const hasFile = $('input#hasFile').checked; + const includeNsfw = $('input#includeNsfw').checked; + const includePinned = $('input#includePinned').checked; + const progress = $('#progress'); + + const fileSelection = $("input#file"); + fileSelection.addEventListener("change", () => { + const files = fileSelection.files; + const channelIdField = $('input#channelId'); + if (files.length > 0) { + const file = files[0]; + file.text().then(text => { + let json = JSON.parse(text); + let channels = Object.keys(json); + channelIdField.value = channels.join(","); + }); + } + }, false); + + const stopHndl = () => !(stop === true || popup.closed); + + const onProg = (value, max) => { + progress.setAttribute('max', max); + progress.value = value; + progress.style.display = max ? '' : 'none'; + }; + + stop = stopBtn.disabled = !(startBtn.disabled = true); - deleteMessages(authToken, authorId, guildId, channelId, afterMessageId, beforeMessageId, content, hasLink, hasFile, includeNsfw, logger, () => !(stop === true || popup.closed)).then(() => { + for (let i = 0; i < channelIds.length; i++) { + await deleteMessages(authToken, authorId, guildId, channelIds[i], minId || minDate, maxId || maxDate, content, hasLink, hasFile, includeNsfw, includePinned, logger, stopHndl, onProg); stop = stopBtn.disabled = !(startBtn.disabled = false); - }); + } }; stopBtn.onclick = e => stop = stopBtn.disabled = !(startBtn.disabled = false); - popup.document.querySelector('button#clear').onclick = e => { logArea.innerHTML = ''; }; - popup.document.querySelector('button#getToken').onclick = e => { + $('button#clear').onclick = e => { logArea.innerHTML = ''; }; + $('button#getToken').onclick = e => { window.dispatchEvent(new Event('beforeunload')); - popup.document.querySelector('input#authToken').value = JSON.parse(popup.localStorage.token); + $('input#authToken').value = JSON.parse(popup.localStorage.token); }; - popup.document.querySelector('button#getAuthor').onclick = e => { - popup.document.querySelector('input#authorId').value = JSON.parse(popup.localStorage.user_id_cache); + $('button#getAuthor').onclick = e => { + $('input#authorId').value = JSON.parse(popup.localStorage.user_id_cache); }; - popup.document.querySelector('button#getGuildAndChannel').onclick = e => { + $('button#getGuildAndChannel').onclick = e => { const m = location.href.match(/channels\/([\w@]+)\/(\d+)/); - popup.document.querySelector('input#guildId').value = m[1]; - popup.document.querySelector('input#channelId').value = m[2]; + $('input#guildId').value = m[1]; + $('input#channelId').value = m[2]; }; - popup.document.querySelector('#redact').onchange = e => { + $('#redact').onchange = e => { popup.document.body.classList.toggle('redact') && popup.alert('This will attempt to hide personal information, but make sure to double check before sharing screenshots.'); }; @@ -98,8 +136,8 @@ * @param {string} authorId Author of the messages you want to delete * @param {string} guildId Server were the messages are located * @param {string} channelId Channel were the messages are located - * @param {string} afterMessageId Only delete messages after this, leave blank do delete all - * @param {string} beforeMessageId Only delete messages before this, leave blank do delete all + * @param {string} minId Only delete messages after this, leave blank do delete all + * @param {string} maxId Only delete messages before this, leave blank do delete all * @param {string} content Filter messages that contains this text content * @param {boolean} hasLink Filter messages that contains link * @param {boolean} hasFile Filter messages that contains file @@ -109,7 +147,7 @@ * @author Victornpb * @see https://github.com/victornpb/deleteDiscordMessages */ - async function deleteMessages(authToken, authorId, guildId, channelId, afterMessageId, beforeMessageId, content,hasLink, hasFile, includeNsfw, extLogger, stopHndl) { + async function deleteMessages(authToken, authorId, guildId, channelId, minId, maxId, content,hasLink, hasFile, includeNsfw, includePinned, extLogger, stopHndl, onProgress) { const start = new Date(); let deleteDelay = 100; let searchDelay = 100; @@ -130,7 +168,8 @@ const queryString = params => params.filter(p => p[1] !== undefined).map(p => p[0] + '=' + encodeURIComponent(p[1])).join('&'); const ask = async msg => new Promise(resolve => setTimeout(() => resolve(popup.confirm(msg)), 10)); const printDelayStats = () => log.verb(`Delete delay: ${deleteDelay}ms, Search delay: ${searchDelay}ms`, `Last Ping: ${lastPing}ms, Average Ping: ${avgPing|0}ms`); - + const toSnowflake = (date) => /:/.test(date) ? ((new Date(date).getTime() - 1420070400000) * Math.pow(2, 22)) : date; + const log = { debug() { extLogger ? extLogger('debug', arguments) : console.debug.apply(console, arguments); }, info() { extLogger ? extLogger('info', arguments) : console.info.apply(console, arguments); }, @@ -159,8 +198,8 @@ resp = await fetch(API_SEARCH_URL + 'search?' + queryString([ [ 'author_id', authorId || undefined ], [ 'channel_id', (guildId !== '@me' ? channelId : undefined) || undefined ], - [ 'min_id', afterMessageId || undefined ], - [ 'max_id', beforeMessageId || undefined ], + [ 'min_id', minId ? toSnowflake(minId) : undefined ], + [ 'max_id', maxId ? toSnowflake(maxId) : undefined ], [ 'sort_by', 'timestamp' ], [ 'sort_order', 'desc' ], [ 'offset', offset ], @@ -170,7 +209,7 @@ [ 'include_nsfw', includeNsfw ? true : undefined ], ]), { headers }); lastPing = (Date.now() - s); - avgPing = avgPing>0 ? (avgPing*0.9) + (lastPing*0.1):lastPing; + avgPing = avgPing>0 ? (avgPing*0.9) + (lastPing*0.1) : lastPing; } catch (err) { return log.error('Search request threw an error:', err); } @@ -206,9 +245,12 @@ const data = await resp.json(); const total = data.total_results; if (!grandTotal) grandTotal = total; - const myMessages = data.messages.map(convo => convo.find(message => message.hit===true)); - const systemMessages = myMessages.filter(msg => msg.type !== 0); // https://discord.com/developers/docs/resources/channel#message-object-message-types - const deletableMessages = myMessages.filter(msg => msg.type === 0 || msg.type === 6); + const discoveredMessages = data.messages.map(convo => convo.find(message => message.hit===true)); + const messagesToDelete = discoveredMessages.filter(msg => { + return msg.type === 0 || msg.type === 6 || (msg.pinned && includePinned); + }); + const skippedMessages = discoveredMessages.filter(msg=>!messagesToDelete.find(m=> m.id===msg.id)); + const end = () => { log.success(`Ended at ${new Date().toLocaleString()}! Total time: ${msToHMS(Date.now() - start.getTime())}`); printDelayStats(); @@ -217,28 +259,29 @@ } const etr = msToHMS((searchDelay * Math.round(total / 25)) + ((deleteDelay + avgPing) * total)); - log.info(`Total messages found: ${data.total_results}`, `(Messages in current page: ${data.messages.length}, Author: ${deletableMessages.length}, System: ${systemMessages.length})`, `offset: ${offset}`); + log.info(`Total messages found: ${data.total_results}`, `(Messages in current page: ${data.messages.length}, To be deleted: ${messagesToDelete.length}, System: ${skippedMessages.length})`, `offset: ${offset}`); printDelayStats(); log.verb(`Estimated time remaining: ${etr}`) - if (myMessages.length > 0) { + if (messagesToDelete.length > 0) { if (++iterations < 1) { log.verb(`Waiting for your confirmation...`); if (!await ask(`Do you want to delete ~${total} messages?\nEstimated time: ${etr}\n\n---- Preview ----\n` + - myMessages.map(m => `${m.author.username}#${m.author.discriminator}: ${m.attachments.length ? '[ATTACHMENTS]' : m.content}`).join('\n'))) + messagesToDelete.map(m => `${m.author.username}#${m.author.discriminator}: ${m.attachments.length ? '[ATTACHMENTS]' : m.content}`).join('\n'))) return end(log.error('Aborted by you!')); log.verb(`OK`); } - for (let i = 0; i < deletableMessages.length; i++) { - const message = deletableMessages[i]; + for (let i = 0; i < messagesToDelete.length; i++) { + const message = messagesToDelete[i]; if (stopHndl && stopHndl()===false) return end(log.error('Stopped by you!')); log.debug(`${((delCount + 1) / grandTotal * 100).toFixed(2)}% (${delCount + 1}/${grandTotal})`, `Deleting ID:${redact(message.id)} ${redact(message.author.username+'#'+message.author.discriminator)} (${redact(new Date(message.timestamp).toLocaleString())}): ${redact(message.content).replace(/\n/g,'↵')}`, message.attachments.length ? redact(JSON.stringify(message.attachments)) : ''); + if (onProgress) onProgress(delCount + 1, grandTotal); let resp; try { @@ -279,10 +322,10 @@ await wait(deleteDelay); } - if (systemMessages.length > 0) { - grandTotal -= systemMessages.length; - offset += systemMessages.length; - log.verb(`Found ${systemMessages.length} system messages! Decreasing grandTotal to ${grandTotal} and increasing offset to ${offset}.`); + if (skippedMessages.length > 0) { + grandTotal -= skippedMessages.length; + offset += skippedMessages.length; + log.verb(`Found ${skippedMessages.length} system messages! Decreasing grandTotal to ${grandTotal} and increasing offset to ${offset}.`); } log.verb(`Searching next messages in ${searchDelay}ms...`, (offset ? `(offset: ${offset})` : '') ); @@ -298,7 +341,8 @@ } log.success(`\nStarted at ${start.toLocaleString()}`); - log.debug(`authorId="${redact(authorId)}" guildId="${redact(guildId)}" channelId="${redact(channelId)}" afterMessageId="${redact(afterMessageId)}" beforeMessageId="${redact(beforeMessageId)}" hasLink=${!!hasLink} hasFile=${!!hasFile}`); + log.debug(`authorId="${redact(authorId)}" guildId="${redact(guildId)}" channelId="${redact(channelId)}" minId="${redact(minId)}" maxId="${redact(maxId)}" hasLink=${!!hasLink} hasFile=${!!hasFile}`); + if (onProgress) onProgress(null, 1); return await recurse(); } })(); diff --git a/help/channelId.md b/help/channelId.md index afbe9d7b..11c50149 100644 --- a/help/channelId.md +++ b/help/channelId.md @@ -17,15 +17,28 @@ ## The manual way ### For public channels: -- Right click a channel, [Copy ID](./developerMode.md) +- Right click a channel, [Copy ID](./developerMode.md) ### For a DM/Direct messages: -- copy the number after /@me/ in the URL) +- copy the number after /@me/ in the URL) +--- + +You can target multiple channels in sequence by separating them with a comma. + +--- + +## Deleting all messages by using the "Request a Copy of your Data" option + +To delete all message from every (user) channel do following: +1. Go to "User Settings -> Privacy and Safety" and click on "Request all my Data." +2. You should receive an email within the next 30 days +3. Click on the "Import JSON" button the right JSON file is called "index.json" and is located in the messages folder (messages/index.json). +4. The channel IDs will be imported separated by a comma. -----