diff --git a/deleteDiscordMessages.user.js b/deleteDiscordMessages.user.js index 86052745..d281d486 100644 --- a/deleteDiscordMessages.user.js +++ b/deleteDiscordMessages.user.js @@ -29,10 +29,8 @@ * @author Victornpb * @see https://github.com/victornpb/deleteDiscordMessages */ -async function deleteMessages(authToken, authorId, guildId, channelId, minId, maxId, content,hasLink, hasFile, includeNsfw, includePinned, extLogger, stopHndl, onProgress) { +async function deleteMessages(authToken, authorId, guildId, channelId, minId, maxId, content, hasLink, hasFile, includeNsfw, includePinned, searchDelay, deleteDelay, extLogger, stopHndl, onProgress) { const start = new Date(); - let deleteDelay = 100; - let searchDelay = 100; let delCount = 0; let failCount = 0; let avgPing; @@ -42,16 +40,16 @@ async function deleteMessages(authToken, authorId, guildId, channelId, minId, ma let throttledTotalTime = 0; let offset = 0; let iterations = -1; - + const wait = async ms => new Promise(done => setTimeout(done, ms)); const msToHMS = s => `${s / 3.6e6 | 0}h ${(s % 3.6e6) / 6e4 | 0}m ${(s % 6e4) / 1000 | 0}s`; const escapeHTML = html => html.replace(/[&<"']/g, m => ({ '&': '&', '<': '<', '"': '"', '\'': ''' })[m]); const redact = str => `${escapeHTML(str)}REDACTED`; 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(window.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 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); }, @@ -73,25 +71,25 @@ async function deleteMessages(authToken, authorId, guildId, channelId, minId, ma const headers = { 'Authorization': authToken }; - + let resp; try { const s = Date.now(); resp = await fetch(API_SEARCH_URL + 'search?' + queryString([ - [ 'author_id', authorId || undefined ], - [ 'channel_id', (guildId !== '@me' ? channelId : undefined) || undefined ], - [ 'min_id', minId ? toSnowflake(minId) : undefined ], - [ 'max_id', maxId ? toSnowflake(maxId) : undefined ], - [ 'sort_by', 'timestamp' ], - [ 'sort_order', 'desc' ], - [ 'offset', offset ], - [ 'has', hasLink ? 'link' : undefined ], - [ 'has', hasFile ? 'file' : undefined ], - [ 'content', content || undefined ], - [ 'include_nsfw', includeNsfw ? true : undefined ], + ['author_id', authorId || undefined], + ['channel_id', (guildId !== '@me' ? channelId : undefined) || undefined], + ['min_id', minId ? toSnowflake(minId) : undefined], + ['max_id', maxId ? toSnowflake(maxId) : undefined], + ['sort_by', 'timestamp'], + ['sort_order', 'desc'], + ['offset', offset], + ['has', hasLink ? 'link' : undefined], + ['has', hasFile ? 'file' : undefined], + ['content', content || undefined], + ['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); } @@ -116,8 +114,8 @@ async function deleteMessages(authToken, authorId, guildId, channelId, minId, ma log.warn(`Being rate limited by the API for ${w}ms! Increasing search delay...`); printDelayStats(); log.verb(`Cooling down for ${w * 2}ms before retrying...`); - - await wait(w*2); + + await wait(w * 2); return await recurse(); } else { return log.error(`Error searching messages, API responded with status ${resp.status}!\n`, await resp.json()); @@ -127,11 +125,11 @@ async function deleteMessages(authToken, authorId, guildId, channelId, minId, ma const data = await resp.json(); const total = data.total_results; if (!grandTotal) grandTotal = total; - const discoveredMessages = data.messages.map(convo => convo.find(message => message.hit===true)); + 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 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())}`); @@ -144,27 +142,27 @@ async function deleteMessages(authToken, authorId, guildId, channelId, minId, ma 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 (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` + messagesToDelete.map(m => `${m.author.username}#${m.author.discriminator}: ${m.attachments.length ? '[ATTACHMENTS]' : m.content}`).join('\n'))) - return end(log.error('Aborted by you!')); + return end(log.error('Aborted by you!')); log.verb(`OK`); } - + for (let i = 0; i < messagesToDelete.length; i++) { const message = messagesToDelete[i]; - if (stopHndl && stopHndl()===false) return end(log.error('Stopped by you!')); + 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,'↵')}`, + `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 { const s = Date.now(); @@ -174,7 +172,7 @@ async function deleteMessages(authToken, authorId, guildId, channelId, minId, ma method: 'DELETE' }); lastPing = (Date.now() - s); - avgPing = (avgPing*0.9) + (lastPing*0.1); + avgPing = (avgPing * 0.9) + (lastPing * 0.1); delCount++; } catch (err) { log.error('Delete request throwed an error:', err); @@ -191,8 +189,8 @@ async function deleteMessages(authToken, authorId, guildId, channelId, minId, ma deleteDelay = w; // increase delay log.warn(`Being rate limited by the API for ${w}ms! Adjusted delete delay to ${deleteDelay}ms.`); printDelayStats(); - log.verb(`Cooling down for ${w*2}ms before retrying...`); - await wait(w*2); + log.verb(`Cooling down for ${w * 2}ms before retrying...`); + await wait(w * 2); i--; // retry } else { log.error(`Error deleting message, API responded with status ${resp.status}!`, await resp.json()); @@ -200,7 +198,7 @@ async function deleteMessages(authToken, authorId, guildId, channelId, minId, ma failCount++; } } - + await wait(deleteDelay); } @@ -209,11 +207,11 @@ async function deleteMessages(authToken, authorId, guildId, channelId, minId, ma 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})` : '') ); + + log.verb(`Searching next messages in ${searchDelay}ms...`, (offset ? `(offset: ${offset})` : '')); await wait(searchDelay); - if (stopHndl && stopHndl()===false) return end(log.error('Stopped by you!')); + if (stopHndl && stopHndl() === false) return end(log.error('Stopped by you!')); return await recurse(); } else { @@ -259,7 +257,7 @@ function initUI() { #undiscord .toolbar span{margin-right:8px} #undiscord button,#undiscord .btn{color:#fff;background:#7289da;border:0;border-radius:4px;font-size:14px} #undiscord button:disabled{display:none} - #undiscord input[type="text"],#undiscord input[type="search"],#undiscord input[type="password"],#undiscord input[type="datetime-local"]{background-color:#202225;color:#b9bbbe;border-radius:4px;border:0;padding:0 .5em;height:24px;width:144px;margin:2px} + #undiscord input[type="text"],#undiscord input[type="search"],#undiscord input[type="password"],#undiscord input[type="datetime-local"],#undiscord input[type="number"]{background-color:#202225;color:#b9bbbe;border-radius:4px;border:0;padding:0 .5em;height:24px;width:144px;margin:2px} #undiscord input#file{display:none} #undiscord hr{border-color:rgba(255,255,255,0.1)} #undiscord .header{padding:12px 16px;background-color:var(--background-tertiary);color:var(--text-muted)} @@ -305,6 +303,16 @@ function initUI() {

+
+ Search Delay ?
+
+
+ Delete Delay ?
+

@@ -347,7 +355,7 @@ function initUI() { function mountBtn() { const toolbar = document.querySelector('[class^=toolbar]'); - if (toolbar) toolbar.appendChild(btn); + if (toolbar) toolbar.appendChild(btn); } const observer = new MutationObserver(function (_mutationsList, _observer) { @@ -362,7 +370,7 @@ function initUI() { 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(); @@ -377,6 +385,8 @@ function initUI() { const hasFile = $('input#hasFile').checked; const includeNsfw = $('input#includeNsfw').checked; const includePinned = $('input#includePinned').checked; + const searchDelay = parseInt($('input#searchDelay').value.trim()); + const deleteDelay = parseInt($('input#deleteDelay').value.trim()); const progress = $('#progress'); const progress2 = btn.querySelector('progress'); const percent = $('.percent'); @@ -411,7 +421,7 @@ function initUI() { stop = stopBtn.disabled = !(startBtn.disabled = true); 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); + await deleteMessages(authToken, authorId, guildId, channelIds[i], minId || minDate, maxId || maxDate, content, hasLink, hasFile, includeNsfw, includePinned, searchDelay, deleteDelay, logger, stopHndl, onProg); stop = stopBtn.disabled = !(startBtn.disabled = false); } }; @@ -432,12 +442,12 @@ function initUI() { }; $('#redact').onchange = e => { popover.classList.toggle('redact') && - window.alert('This will attempt to hide personal information, but make sure to double check before sharing screenshots.'); + window.alert('This will attempt to hide personal information, but make sure to double check before sharing screenshots.'); }; - const logger = (type='', args) => { + const logger = (type = '', args) => { const style = { '': '', info: 'color:#00b0f4;', verb: 'color:#72767d;', warn: 'color:#faa61a;', error: 'color:#f04747;', success: 'color:#43b581;' }[type]; - logArea.insertAdjacentHTML('beforeend', `
${Array.from(args).map(o => typeof o === 'object' ? JSON.stringify(o, o instanceof Error && Object.getOwnPropertyNames(o)) : o).join('\t')}
`); + logArea.insertAdjacentHTML('beforeend', `
${Array.from(args).map(o => typeof o === 'object' ? JSON.stringify(o, o instanceof Error && Object.getOwnPropertyNames(o)) : o).join('\t')}
`); if (autoScroll.checked) logArea.querySelector('div:last-child').scrollIntoView(false); }; diff --git a/help/delay.md b/help/delay.md new file mode 100644 index 00000000..73331805 --- /dev/null +++ b/help/delay.md @@ -0,0 +1,3 @@ +# Search and Delete Delay + +This setting controls the initial delay for for searching messages. Sometimes the Discord API will quickly rate limit you, in this case setting the delays to something higher might help. This is especially true for the **Delete Delay**.