346 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| let isDeleting = false;
 | |
| let cancelDelete = false;
 | |
| let deletedMessages = new Set();
 | |
| const CACHE_CLEANUP_INTERVAL = 30 * 60 * 1000;
 | |
| const { sendCommandResponse } = require('../utils/messageUtils');
 | |
| 
 | |
| setInterval(() => {
 | |
|   if (deletedMessages.size > 1000) {
 | |
|     console.log(`[DELETE] Cleaning message cache (size: ${deletedMessages.size})`);
 | |
|     deletedMessages.clear();
 | |
|   }
 | |
| }, CACHE_CLEANUP_INTERVAL);
 | |
| 
 | |
| module.exports = {
 | |
|   name: 'delete',
 | |
|   description: 'Delete a specified number of your messages or all messages from a server with human-like delays.',
 | |
|   async execute(message, args, deleteTimeout) {
 | |
|     if (args[0] && args[0].toLowerCase() === 'cancel') {
 | |
|       cancelDelete = true;
 | |
|       await sendCommandResponse(message, 'Delete operation canceled.', deleteTimeout, true);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (isDeleting) {
 | |
|       await sendCommandResponse(message, 'A delete operation is already in progress. Please wait or cancel it with `.delete cancel`.', deleteTimeout, true);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     isDeleting = true;
 | |
|     cancelDelete = false;
 | |
| 
 | |
|     let speed = 'medium';
 | |
|     if (args[0] && ['slow', 'medium', 'fast'].includes(args[0].toLowerCase())) {
 | |
|       speed = args[0].toLowerCase();
 | |
|       args.shift();
 | |
|     }
 | |
| 
 | |
|     const deleteCount = parseInt(args[0], 10);
 | |
|     const targetGuildId = args[1];
 | |
| 
 | |
|     if (!isNaN(deleteCount) && deleteCount > 0) {
 | |
|       await deleteMessagesFromChannel(message, deleteCount, deleteTimeout, speed);
 | |
|     } else if (targetGuildId) {
 | |
|       await deleteMessagesFromServer(message, targetGuildId, deleteTimeout, speed);
 | |
|     } else {
 | |
|       await sendCommandResponse(message, 'Please specify how many messages to delete or a server ID. You can also set speed: `.delete [slow/medium/fast] [count/server]`', deleteTimeout, true);
 | |
|     }
 | |
| 
 | |
|     isDeleting = false;
 | |
|   },
 | |
| };
 | |
| 
 | |
| async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, speed = 'medium') {
 | |
|   let deleteIntervalMin, deleteIntervalMax, jitterFactor, pauseChance, pauseLengthMin, pauseLengthMax, batchSize;
 | |
|   
 | |
|   switch(speed) {
 | |
|     case 'slow':
 | |
|       deleteIntervalMin = 3000;
 | |
|       deleteIntervalMax = 6000;
 | |
|       jitterFactor = 0.5;
 | |
|       pauseChance = 0.25;
 | |
|       pauseLengthMin = 15000;
 | |
|       pauseLengthMax = 45000;
 | |
|       batchSize = 5;
 | |
|       break;
 | |
|     case 'fast':
 | |
|       deleteIntervalMin = 1500;
 | |
|       deleteIntervalMax = 3000;
 | |
|       jitterFactor = 0.3;
 | |
|       pauseChance = 0.05;
 | |
|       pauseLengthMin = 5000;
 | |
|       pauseLengthMax = 15000;
 | |
|       batchSize = 15;
 | |
|       break;
 | |
|     case 'medium':
 | |
|     default:
 | |
|       deleteIntervalMin = 2000;
 | |
|       deleteIntervalMax = 4500;
 | |
|       jitterFactor = 0.4;
 | |
|       pauseChance = 0.15;
 | |
|       pauseLengthMin = 10000;
 | |
|       pauseLengthMax = 30000;
 | |
|       batchSize = 10;
 | |
|   }
 | |
|   
 | |
|   const getHumanlikeDelay = () => {
 | |
|     const baseInterval = Math.floor(Math.random() * (deleteIntervalMax - deleteIntervalMin + 1)) + deleteIntervalMin;
 | |
|     const jitterAmount = baseInterval * jitterFactor;
 | |
|     const jitter = Math.random() * jitterAmount * 2 - jitterAmount;
 | |
|     return Math.max(1000, Math.floor(baseInterval + jitter));
 | |
|   };
 | |
|   
 | |
|   const getReadingDelay = () => Math.floor(Math.random() * 3000) + 1000;
 | |
| 
 | |
|   try {
 | |
|     console.log(`[DELETE] Starting deletion of up to ${deleteCount} messages with ${speed} speed`);
 | |
|     let deletedCount = 0;
 | |
|     let batchCount = 0;
 | |
|     
 | |
|     while (deletedCount < deleteCount && !cancelDelete) {
 | |
|       if (deletedCount > 0 && deletedCount % 25 === 0) {
 | |
|         console.log(`[DELETE] Progress: ${deletedCount}/${deleteCount} messages deleted`);
 | |
|       }
 | |
|       
 | |
|       const fetchLimit = Math.min(deleteCount - deletedCount, batchSize);
 | |
|       const messages = await message.channel.messages.fetch({ limit: 100 });
 | |
|       
 | |
|       const filteredMessages = messages.filter(msg => 
 | |
|         msg.author.id === message.author.id && 
 | |
|         !deletedMessages.has(msg.id)
 | |
|       );
 | |
|       
 | |
|       if (filteredMessages.size === 0) {
 | |
|         console.log(`[DELETE] No more messages found in this channel`);
 | |
|         break;
 | |
|       }
 | |
|       
 | |
|       batchCount++;
 | |
|       let messagesInThisBatch = 0;
 | |
|       
 | |
|       for (const msg of filteredMessages.values()) {
 | |
|         if (cancelDelete) {
 | |
|           console.log(`[DELETE] Operation canceled by user`);
 | |
|           return;
 | |
|         }
 | |
|         
 | |
|         if (deletedCount >= deleteCount) break;
 | |
|         
 | |
|         try {
 | |
|           if (msg.deletable && !msg.deleted && !deletedMessages.has(msg.id)) {
 | |
|             if (Math.random() < 0.25) {
 | |
|               const readingDelay = getReadingDelay();
 | |
|               console.log(`[DELETE] Taking ${readingDelay}ms to "read" before deleting message ${msg.id}`);
 | |
|               await new Promise(resolve => setTimeout(resolve, readingDelay));
 | |
|             }
 | |
|             
 | |
|             const preDeleteDelay = Math.floor(Math.random() * 1000) + 250;
 | |
|             await new Promise(resolve => setTimeout(resolve, preDeleteDelay));
 | |
|             
 | |
|             await msg.delete().catch(err => {
 | |
|               if (err.code === 10008) {
 | |
|                 console.log(`[DELETE] Message ${msg.id} already deleted`);
 | |
|                 deletedMessages.add(msg.id);
 | |
|               } else if (err.code === 429) {
 | |
|                 console.log(`[DELETE] Rate limited. Taking a longer break...`);
 | |
|                 return;
 | |
|               } else {
 | |
|                 console.error(`[DELETE] Failed to delete message:`, err);
 | |
|               }
 | |
|             });
 | |
|             
 | |
|             deletedMessages.add(msg.id);
 | |
|             deletedCount++;
 | |
|             messagesInThisBatch++;
 | |
|             
 | |
|             const delay = getHumanlikeDelay();
 | |
|             console.log(`[DELETE] Waiting ${delay}ms before next deletion`);
 | |
|             await new Promise(resolve => setTimeout(resolve, delay));
 | |
|           }
 | |
|         } catch (error) {
 | |
|           console.error('[DELETE] Error deleting message:', error);
 | |
|         }
 | |
|       }
 | |
|       
 | |
|       if (messagesInThisBatch === 0) {
 | |
|         console.log(`[DELETE] No deletable messages found in batch`);
 | |
|         break;
 | |
|       }
 | |
|       
 | |
|       if (!cancelDelete && deletedCount < deleteCount) {
 | |
|         const adjustedPauseChance = pauseChance * (1 + (Math.min(batchCount, 5) / 10));
 | |
|         
 | |
|         if (Math.random() < adjustedPauseChance) {
 | |
|           const pauseDuration = Math.floor(Math.random() * (pauseLengthMax - pauseLengthMin + 1)) + pauseLengthMin;
 | |
|           console.log(`[DELETE] Taking a break for ${Math.round(pauseDuration/1000)} seconds. Progress: ${deletedCount}/${deleteCount}`);
 | |
|           await new Promise(resolve => setTimeout(resolve, pauseDuration));
 | |
|           batchCount = 0;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     if (cancelDelete) {
 | |
|       await sendCommandResponse(message, `Delete operation canceled after removing ${deletedCount} messages.`, deleteTimeout, true);
 | |
|     } else {
 | |
|       await sendCommandResponse(message, `Finished deleting ${deletedCount} messages.`, deleteTimeout, true);
 | |
|     }
 | |
|   } catch (error) {
 | |
|     console.error('[DELETE] Failed to delete messages:', error);
 | |
|     await sendCommandResponse(message, 'There was an error while trying to delete messages.', deleteTimeout, true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = 'medium') {
 | |
|   const guild = message.client.guilds.cache.get(guildId);
 | |
| 
 | |
|   if (!guild) {
 | |
|     await sendCommandResponse(message, `Guild with ID ${guildId} not found.`, deleteTimeout, true);
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   let deleteIntervalMin, deleteIntervalMax, jitterFactor, pauseChance, pauseLengthMin, pauseLengthMax, batchSize;
 | |
|   
 | |
|   switch(speed) {
 | |
|     case 'slow':
 | |
|       deleteIntervalMin = 3000;
 | |
|       deleteIntervalMax = 6000;
 | |
|       jitterFactor = 0.5;
 | |
|       pauseChance = 0.4;
 | |
|       pauseLengthMin = 30000;
 | |
|       pauseLengthMax = 90000;
 | |
|       batchSize = 5;
 | |
|       break;
 | |
|     case 'fast':
 | |
|       deleteIntervalMin = 1500;
 | |
|       deleteIntervalMax = 3000;
 | |
|       jitterFactor = 0.3;
 | |
|       pauseChance = 0.2;
 | |
|       pauseLengthMin = 15000;
 | |
|       pauseLengthMax = 45000;
 | |
|       batchSize = 15;
 | |
|       break;
 | |
|     case 'medium':
 | |
|     default:
 | |
|       deleteIntervalMin = 2000;
 | |
|       deleteIntervalMax = 4500;
 | |
|       jitterFactor = 0.4;
 | |
|       pauseChance = 0.3;
 | |
|       pauseLengthMin = 20000;
 | |
|       pauseLengthMax = 60000;
 | |
|       batchSize = 10;
 | |
|   }
 | |
|   
 | |
|   const getHumanlikeDelay = () => {
 | |
|     const baseInterval = Math.floor(Math.random() * (deleteIntervalMax - deleteIntervalMin + 1)) + deleteIntervalMin;
 | |
|     const jitterAmount = baseInterval * jitterFactor;
 | |
|     const jitter = Math.random() * jitterAmount * 2 - jitterAmount;
 | |
|     return Math.max(1000, Math.floor(baseInterval + jitter));
 | |
|   };
 | |
| 
 | |
|   console.log(`[DELETE] Starting server-wide deletion in server: ${guild.name}`);
 | |
|   let totalDeleted = 0;
 | |
| 
 | |
|   try {
 | |
|     const channels = guild.channels.cache.filter(channel => channel.isText());
 | |
|     let processedChannels = 0;
 | |
|     
 | |
|     for (const [channelId, channel] of channels) {
 | |
|       if (cancelDelete) {
 | |
|         console.log(`[DELETE] Operation canceled by user`);
 | |
|         break;
 | |
|       }
 | |
|       
 | |
|       processedChannels++;
 | |
|       console.log(`[DELETE] Processing channel ${processedChannels}/${channels.size}: ${channel.name}`);
 | |
|       
 | |
|       let hasMoreMessages = true;
 | |
|       let messagesDeletedInChannel = 0;
 | |
|       let batchCount = 0;
 | |
|       
 | |
|       while (hasMoreMessages && !cancelDelete) {
 | |
|         const messages = await channel.messages.fetch({ limit: 100 });
 | |
| 
 | |
|         if (messages.size === 0) {
 | |
|           hasMoreMessages = false;
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         const filteredMessages = messages.filter(msg => 
 | |
|           msg.author.id === message.author.id && 
 | |
|           !deletedMessages.has(msg.id)
 | |
|         );
 | |
|         
 | |
|         if (filteredMessages.size === 0) {
 | |
|           hasMoreMessages = false;
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         batchCount++;
 | |
|         let messagesInThisBatch = 0;
 | |
|         
 | |
|         for (const msg of filteredMessages.values()) {
 | |
|           if (cancelDelete) return;
 | |
|           
 | |
|           try {
 | |
|             if (msg.deletable && !msg.deleted && !deletedMessages.has(msg.id)) {
 | |
|               const preDeleteDelay = Math.floor(Math.random() * 1000) + 250;
 | |
|               await new Promise(resolve => setTimeout(resolve, preDeleteDelay));
 | |
|               
 | |
|               await msg.delete().catch(err => {
 | |
|                 if (err.code === 10008) {
 | |
|                   console.log(`[DELETE] Message ${msg.id} already deleted`);
 | |
|                   deletedMessages.add(msg.id);
 | |
|                 } else if (err.code === 429) {
 | |
|                   console.log(`[DELETE] Rate limited. Taking a longer break...`);
 | |
|                   return new Promise(resolve => setTimeout(resolve, 30000 + Math.random() * 30000));
 | |
|                 } else {
 | |
|                   console.error(`[DELETE] Failed to delete message:`, err);
 | |
|                 }
 | |
|               });
 | |
|               
 | |
|               deletedMessages.add(msg.id);
 | |
|               totalDeleted++;
 | |
|               messagesDeletedInChannel++;
 | |
|               messagesInThisBatch++;
 | |
|               
 | |
|               const delay = getHumanlikeDelay();
 | |
|               await new Promise(resolve => setTimeout(resolve, delay));
 | |
|             }
 | |
|           } catch (error) {
 | |
|             console.error('[DELETE] Error deleting message:', error);
 | |
|           }
 | |
|           
 | |
|           if (messagesInThisBatch >= batchSize) break;
 | |
|         }
 | |
| 
 | |
|         if (messagesInThisBatch < batchSize) {
 | |
|           hasMoreMessages = false;
 | |
|         } else {
 | |
|           const shouldPause = Math.random() < pauseChance;
 | |
|           if (shouldPause && !cancelDelete) {
 | |
|             const pauseDuration = Math.floor(Math.random() * (pauseLengthMin - pauseLengthMin/2 + 1)) + pauseLengthMin/2;
 | |
|             console.log(`[DELETE] Taking a short break for ${Math.round(pauseDuration/1000)} seconds in channel ${channel.name}. Deleted so far: ${messagesDeletedInChannel}`);
 | |
|             await new Promise(resolve => setTimeout(resolve, pauseDuration));
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       
 | |
|       console.log(`[DELETE] Completed channel ${channel.name}: ${messagesDeletedInChannel} messages deleted`);
 | |
|       
 | |
|       if (!cancelDelete && processedChannels < channels.size) {
 | |
|         const pauseDuration = Math.floor(Math.random() * (pauseLengthMax - pauseLengthMin + 1)) + pauseLengthMin;
 | |
|         console.log(`[DELETE] Moving to next channel in ${Math.round(pauseDuration/1000)} seconds. Total deleted so far: ${totalDeleted}`);
 | |
|         await new Promise(resolve => setTimeout(resolve, pauseDuration));
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     if (cancelDelete) {
 | |
|       await sendCommandResponse(message, `Delete operation canceled after removing ${totalDeleted} messages across ${processedChannels} channels.`, deleteTimeout, true);
 | |
|     } else {
 | |
|       await sendCommandResponse(message, `Finished cleaning up ${guild.name}: ${totalDeleted} messages deleted across ${processedChannels} channels.`, deleteTimeout, true);
 | |
|     }
 | |
|   } catch (error) {
 | |
|     console.error('[DELETE] Failed to delete messages in the server:', error);
 | |
|     await sendCommandResponse(message, 'There was an error while trying to delete messages from the server.', deleteTimeout, true);
 | |
|   }
 | |
| } |