Refactor groupadd and delete
This commit is contained in:
		| @@ -3,7 +3,6 @@ let cancelDelete = false; | |||||||
| let deletedMessages = new Set(); | let deletedMessages = new Set(); | ||||||
| const CACHE_CLEANUP_INTERVAL = 30 * 60 * 1000; | const CACHE_CLEANUP_INTERVAL = 30 * 60 * 1000; | ||||||
|  |  | ||||||
| // Cleanup deleted message cache periodically |  | ||||||
| setInterval(() => { | setInterval(() => { | ||||||
|   if (deletedMessages.size > 1000) { |   if (deletedMessages.size > 1000) { | ||||||
|     console.log(`[DELETE] Cleaning message cache (size: ${deletedMessages.size})`); |     console.log(`[DELETE] Cleaning message cache (size: ${deletedMessages.size})`); | ||||||
| @@ -31,11 +30,10 @@ module.exports = { | |||||||
|     isDeleting = true; |     isDeleting = true; | ||||||
|     cancelDelete = false; |     cancelDelete = false; | ||||||
|  |  | ||||||
|     // Check for speed settings |  | ||||||
|     let speed = 'medium'; |     let speed = 'medium'; | ||||||
|     if (args[0] && ['slow', 'medium', 'fast'].includes(args[0].toLowerCase())) { |     if (args[0] && ['slow', 'medium', 'fast'].includes(args[0].toLowerCase())) { | ||||||
|       speed = args[0].toLowerCase(); |       speed = args[0].toLowerCase(); | ||||||
|       args.shift(); // Remove the speed argument |       args.shift(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const deleteCount = parseInt(args[0], 10); |     const deleteCount = parseInt(args[0], 10); | ||||||
| @@ -55,7 +53,6 @@ module.exports = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, speed = 'medium') { | async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, speed = 'medium') { | ||||||
|   // Human-like timing parameters based on speed setting |  | ||||||
|   let deleteIntervalMin, deleteIntervalMax, jitterFactor, pauseChance, pauseLengthMin, pauseLengthMax, batchSize; |   let deleteIntervalMin, deleteIntervalMax, jitterFactor, pauseChance, pauseLengthMin, pauseLengthMax, batchSize; | ||||||
|    |    | ||||||
|   switch(speed) { |   switch(speed) { | ||||||
| @@ -88,7 +85,6 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | |||||||
|       batchSize = 10; |       batchSize = 10; | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   // More human-like delay functions |  | ||||||
|   const getHumanlikeDelay = () => { |   const getHumanlikeDelay = () => { | ||||||
|     const baseInterval = Math.floor(Math.random() * (deleteIntervalMax - deleteIntervalMin + 1)) + deleteIntervalMin; |     const baseInterval = Math.floor(Math.random() * (deleteIntervalMax - deleteIntervalMin + 1)) + deleteIntervalMin; | ||||||
|     const jitterAmount = baseInterval * jitterFactor; |     const jitterAmount = baseInterval * jitterFactor; | ||||||
| @@ -96,7 +92,7 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | |||||||
|     return Math.max(1000, Math.floor(baseInterval + jitter)); |     return Math.max(1000, Math.floor(baseInterval + jitter)); | ||||||
|   }; |   }; | ||||||
|    |    | ||||||
|   const getReadingDelay = () => Math.floor(Math.random() * 3000) + 1000; // 1-4 seconds |   const getReadingDelay = () => Math.floor(Math.random() * 3000) + 1000; | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
|     console.log(`[DELETE] Starting deletion of up to ${deleteCount} messages with ${speed} speed`); |     console.log(`[DELETE] Starting deletion of up to ${deleteCount} messages with ${speed} speed`); | ||||||
| @@ -104,13 +100,12 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | |||||||
|     let batchCount = 0; |     let batchCount = 0; | ||||||
|      |      | ||||||
|     while (deletedCount < deleteCount && !cancelDelete) { |     while (deletedCount < deleteCount && !cancelDelete) { | ||||||
|       // Show progress occasionally |  | ||||||
|       if (deletedCount > 0 && deletedCount % 25 === 0) { |       if (deletedCount > 0 && deletedCount % 25 === 0) { | ||||||
|         console.log(`[DELETE] Progress: ${deletedCount}/${deleteCount} messages deleted`); |         console.log(`[DELETE] Progress: ${deletedCount}/${deleteCount} messages deleted`); | ||||||
|       } |       } | ||||||
|        |        | ||||||
|       const fetchLimit = Math.min(deleteCount - deletedCount, batchSize); |       const fetchLimit = Math.min(deleteCount - deletedCount, batchSize); | ||||||
|       const messages = await message.channel.messages.fetch({ limit: 100 }); // Fetch more to ensure we find user messages |       const messages = await message.channel.messages.fetch({ limit: 100 }); | ||||||
|        |        | ||||||
|       const filteredMessages = messages.filter(msg =>  |       const filteredMessages = messages.filter(msg =>  | ||||||
|         msg.author.id === message.author.id &&  |         msg.author.id === message.author.id &&  | ||||||
| @@ -122,7 +117,6 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | |||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|        |        | ||||||
|       // Process current batch |  | ||||||
|       batchCount++; |       batchCount++; | ||||||
|       let messagesInThisBatch = 0; |       let messagesInThisBatch = 0; | ||||||
|        |        | ||||||
| @@ -132,42 +126,35 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | |||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         // Exit the loop if we've deleted enough messages |  | ||||||
|         if (deletedCount >= deleteCount) break; |         if (deletedCount >= deleteCount) break; | ||||||
|          |          | ||||||
|         try { |         try { | ||||||
|           if (msg.deletable && !msg.deleted && !deletedMessages.has(msg.id)) { |           if (msg.deletable && !msg.deleted && !deletedMessages.has(msg.id)) { | ||||||
|             // Simulate reading the message occasionally (25% chance) |  | ||||||
|             if (Math.random() < 0.25) { |             if (Math.random() < 0.25) { | ||||||
|               const readingDelay = getReadingDelay(); |               const readingDelay = getReadingDelay(); | ||||||
|               console.log(`[DELETE] Taking ${readingDelay}ms to "read" before deleting message ${msg.id}`); |               console.log(`[DELETE] Taking ${readingDelay}ms to "read" before deleting message ${msg.id}`); | ||||||
|               await new Promise(resolve => setTimeout(resolve, readingDelay)); |               await new Promise(resolve => setTimeout(resolve, readingDelay)); | ||||||
|             } |             } | ||||||
|              |              | ||||||
|             // Variable pre-delete delay |  | ||||||
|             const preDeleteDelay = Math.floor(Math.random() * 1000) + 250; |             const preDeleteDelay = Math.floor(Math.random() * 1000) + 250; | ||||||
|             await new Promise(resolve => setTimeout(resolve, preDeleteDelay)); |             await new Promise(resolve => setTimeout(resolve, preDeleteDelay)); | ||||||
|              |              | ||||||
|             // Attempt to delete |  | ||||||
|             await msg.delete().catch(err => { |             await msg.delete().catch(err => { | ||||||
|               if (err.code === 10008) { |               if (err.code === 10008) { | ||||||
|                 console.log(`[DELETE] Message ${msg.id} already deleted`); |                 console.log(`[DELETE] Message ${msg.id} already deleted`); | ||||||
|                 deletedMessages.add(msg.id); |                 deletedMessages.add(msg.id); | ||||||
|               } else if (err.code === 429) { |               } else if (err.code === 429) { | ||||||
|                 console.log(`[DELETE] Rate limited. Taking a longer break...`); |                 console.log(`[DELETE] Rate limited. Taking a longer break...`); | ||||||
|                 // Don't count this one, we'll try again later |  | ||||||
|                 return; |                 return; | ||||||
|               } else { |               } else { | ||||||
|                 console.error(`[DELETE] Failed to delete message:`, err); |                 console.error(`[DELETE] Failed to delete message:`, err); | ||||||
|               } |               } | ||||||
|             }); |             }); | ||||||
|              |              | ||||||
|             // Successfully deleted |  | ||||||
|             deletedMessages.add(msg.id); |             deletedMessages.add(msg.id); | ||||||
|             deletedCount++; |             deletedCount++; | ||||||
|             messagesInThisBatch++; |             messagesInThisBatch++; | ||||||
|              |              | ||||||
|             // Add human-like delay between deletions |  | ||||||
|             const delay = getHumanlikeDelay(); |             const delay = getHumanlikeDelay(); | ||||||
|             console.log(`[DELETE] Waiting ${delay}ms before next deletion`); |             console.log(`[DELETE] Waiting ${delay}ms before next deletion`); | ||||||
|             await new Promise(resolve => setTimeout(resolve, delay)); |             await new Promise(resolve => setTimeout(resolve, delay)); | ||||||
| @@ -177,27 +164,23 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|        |        | ||||||
|       // Break if we couldn't delete any messages in this batch |  | ||||||
|       if (messagesInThisBatch === 0) { |       if (messagesInThisBatch === 0) { | ||||||
|         console.log(`[DELETE] No deletable messages found in batch`); |         console.log(`[DELETE] No deletable messages found in batch`); | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|        |        | ||||||
|       // Take a natural pause between batches (with higher chance after several batches) |  | ||||||
|       if (!cancelDelete && deletedCount < deleteCount) { |       if (!cancelDelete && deletedCount < deleteCount) { | ||||||
|         // More likely to pause after several consecutive batches |  | ||||||
|         const adjustedPauseChance = pauseChance * (1 + (Math.min(batchCount, 5) / 10)); |         const adjustedPauseChance = pauseChance * (1 + (Math.min(batchCount, 5) / 10)); | ||||||
|          |          | ||||||
|         if (Math.random() < adjustedPauseChance) { |         if (Math.random() < adjustedPauseChance) { | ||||||
|           const pauseDuration = Math.floor(Math.random() * (pauseLengthMax - pauseLengthMin + 1)) + pauseLengthMin; |           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}`); |           console.log(`[DELETE] Taking a break for ${Math.round(pauseDuration/1000)} seconds. Progress: ${deletedCount}/${deleteCount}`); | ||||||
|           await new Promise(resolve => setTimeout(resolve, pauseDuration)); |           await new Promise(resolve => setTimeout(resolve, pauseDuration)); | ||||||
|           batchCount = 0; // Reset batch count after a pause |           batchCount = 0; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // Final status message |  | ||||||
|     if (cancelDelete) { |     if (cancelDelete) { | ||||||
|       const canceledMsg = await message.channel.send(`Delete operation canceled after removing ${deletedCount} messages.`); |       const canceledMsg = await message.channel.send(`Delete operation canceled after removing ${deletedCount} messages.`); | ||||||
|       setTimeout(() => canceledMsg.delete().catch(console.error), deleteTimeout); |       setTimeout(() => canceledMsg.delete().catch(console.error), deleteTimeout); | ||||||
| @@ -221,7 +204,6 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   // Human-like timing parameters based on speed setting |  | ||||||
|   let deleteIntervalMin, deleteIntervalMax, jitterFactor, pauseChance, pauseLengthMin, pauseLengthMax, batchSize; |   let deleteIntervalMin, deleteIntervalMax, jitterFactor, pauseChance, pauseLengthMin, pauseLengthMax, batchSize; | ||||||
|    |    | ||||||
|   switch(speed) { |   switch(speed) { | ||||||
| @@ -229,7 +211,7 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | |||||||
|       deleteIntervalMin = 3000; |       deleteIntervalMin = 3000; | ||||||
|       deleteIntervalMax = 6000; |       deleteIntervalMax = 6000; | ||||||
|       jitterFactor = 0.5; |       jitterFactor = 0.5; | ||||||
|       pauseChance = 0.4;  // Higher chance to pause between channels |       pauseChance = 0.4; | ||||||
|       pauseLengthMin = 30000; |       pauseLengthMin = 30000; | ||||||
|       pauseLengthMax = 90000; |       pauseLengthMax = 90000; | ||||||
|       batchSize = 5; |       batchSize = 5; | ||||||
| @@ -254,7 +236,6 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | |||||||
|       batchSize = 10; |       batchSize = 10; | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   // More human-like delay functions |  | ||||||
|   const getHumanlikeDelay = () => { |   const getHumanlikeDelay = () => { | ||||||
|     const baseInterval = Math.floor(Math.random() * (deleteIntervalMax - deleteIntervalMin + 1)) + deleteIntervalMin; |     const baseInterval = Math.floor(Math.random() * (deleteIntervalMax - deleteIntervalMin + 1)) + deleteIntervalMin; | ||||||
|     const jitterAmount = baseInterval * jitterFactor; |     const jitterAmount = baseInterval * jitterFactor; | ||||||
| @@ -300,7 +281,6 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | |||||||
|           continue; |           continue; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Process current batch |  | ||||||
|         batchCount++; |         batchCount++; | ||||||
|         let messagesInThisBatch = 0; |         let messagesInThisBatch = 0; | ||||||
|          |          | ||||||
| @@ -309,31 +289,26 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | |||||||
|            |            | ||||||
|           try { |           try { | ||||||
|             if (msg.deletable && !msg.deleted && !deletedMessages.has(msg.id)) { |             if (msg.deletable && !msg.deleted && !deletedMessages.has(msg.id)) { | ||||||
|               // Variable pre-delete delay |  | ||||||
|               const preDeleteDelay = Math.floor(Math.random() * 1000) + 250; |               const preDeleteDelay = Math.floor(Math.random() * 1000) + 250; | ||||||
|               await new Promise(resolve => setTimeout(resolve, preDeleteDelay)); |               await new Promise(resolve => setTimeout(resolve, preDeleteDelay)); | ||||||
|                |                | ||||||
|               // Attempt to delete |  | ||||||
|               await msg.delete().catch(err => { |               await msg.delete().catch(err => { | ||||||
|                 if (err.code === 10008) { |                 if (err.code === 10008) { | ||||||
|                   console.log(`[DELETE] Message ${msg.id} already deleted`); |                   console.log(`[DELETE] Message ${msg.id} already deleted`); | ||||||
|                   deletedMessages.add(msg.id); |                   deletedMessages.add(msg.id); | ||||||
|                 } else if (err.code === 429) { |                 } else if (err.code === 429) { | ||||||
|                   console.log(`[DELETE] Rate limited. Taking a longer break...`); |                   console.log(`[DELETE] Rate limited. Taking a longer break...`); | ||||||
|                   // Take an extra long break on rate limits |  | ||||||
|                   return new Promise(resolve => setTimeout(resolve, 30000 + Math.random() * 30000)); |                   return new Promise(resolve => setTimeout(resolve, 30000 + Math.random() * 30000)); | ||||||
|                 } else { |                 } else { | ||||||
|                   console.error(`[DELETE] Failed to delete message:`, err); |                   console.error(`[DELETE] Failed to delete message:`, err); | ||||||
|                 } |                 } | ||||||
|               }); |               }); | ||||||
|                |                | ||||||
|               // Successfully deleted |  | ||||||
|               deletedMessages.add(msg.id); |               deletedMessages.add(msg.id); | ||||||
|               totalDeleted++; |               totalDeleted++; | ||||||
|               messagesDeletedInChannel++; |               messagesDeletedInChannel++; | ||||||
|               messagesInThisBatch++; |               messagesInThisBatch++; | ||||||
|                |                | ||||||
|               // Add human-like delay between deletions |  | ||||||
|               const delay = getHumanlikeDelay(); |               const delay = getHumanlikeDelay(); | ||||||
|               await new Promise(resolve => setTimeout(resolve, delay)); |               await new Promise(resolve => setTimeout(resolve, delay)); | ||||||
|             } |             } | ||||||
| @@ -341,15 +316,12 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | |||||||
|             console.error('[DELETE] Error deleting message:', error); |             console.error('[DELETE] Error deleting message:', error); | ||||||
|           } |           } | ||||||
|            |            | ||||||
|           // Break batch processing after certain number of messages to avoid long loops |  | ||||||
|           if (messagesInThisBatch >= batchSize) break; |           if (messagesInThisBatch >= batchSize) break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // If we deleted fewer messages than the batch size, assume we've reached the end |  | ||||||
|         if (messagesInThisBatch < batchSize) { |         if (messagesInThisBatch < batchSize) { | ||||||
|           hasMoreMessages = false; |           hasMoreMessages = false; | ||||||
|         } else { |         } else { | ||||||
|           // Take a natural pause between batches within a channel |  | ||||||
|           const shouldPause = Math.random() < pauseChance; |           const shouldPause = Math.random() < pauseChance; | ||||||
|           if (shouldPause && !cancelDelete) { |           if (shouldPause && !cancelDelete) { | ||||||
|             const pauseDuration = Math.floor(Math.random() * (pauseLengthMin - pauseLengthMin/2 + 1)) + pauseLengthMin/2; |             const pauseDuration = Math.floor(Math.random() * (pauseLengthMin - pauseLengthMin/2 + 1)) + pauseLengthMin/2; | ||||||
| @@ -361,7 +333,6 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | |||||||
|        |        | ||||||
|       console.log(`[DELETE] Completed channel ${channel.name}: ${messagesDeletedInChannel} messages deleted`); |       console.log(`[DELETE] Completed channel ${channel.name}: ${messagesDeletedInChannel} messages deleted`); | ||||||
|        |        | ||||||
|       // Take a longer pause between channels |  | ||||||
|       if (!cancelDelete && processedChannels < channels.size) { |       if (!cancelDelete && processedChannels < channels.size) { | ||||||
|         const pauseDuration = Math.floor(Math.random() * (pauseLengthMax - pauseLengthMin + 1)) + pauseLengthMin; |         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}`); |         console.log(`[DELETE] Moving to next channel in ${Math.round(pauseDuration/1000)} seconds. Total deleted so far: ${totalDeleted}`); | ||||||
| @@ -369,7 +340,6 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // Final status message |  | ||||||
|     if (cancelDelete) { |     if (cancelDelete) { | ||||||
|       const canceledMsg = await message.channel.send(`Server cleanup canceled after removing ${totalDeleted} messages across ${processedChannels} channels.`); |       const canceledMsg = await message.channel.send(`Server cleanup canceled after removing ${totalDeleted} messages across ${processedChannels} channels.`); | ||||||
|       setTimeout(() => canceledMsg.delete().catch(console.error), deleteTimeout); |       setTimeout(() => canceledMsg.delete().catch(console.error), deleteTimeout); | ||||||
|   | |||||||
| @@ -2,14 +2,28 @@ let targetUserIds = new Set(); | |||||||
| let isActive = false; | let isActive = false; | ||||||
| let channelToWatch = null; | let channelToWatch = null; | ||||||
| let lastAddTimes = new Map(); | let lastAddTimes = new Map(); | ||||||
|  | let failedAttempts = new Map(); | ||||||
|  | let recentAdds = new Map(); | ||||||
|  |  | ||||||
| const getRandomDelay = () => { | const getBackoffDelay = (userId) => { | ||||||
|     return Math.floor(Math.random() * 200) + 150; |     const attempts = failedAttempts.get(userId) || 0; | ||||||
|  |      | ||||||
|  |     if (attempts <= 1) return 2000; | ||||||
|  |     if (attempts <= 3) return 4000; | ||||||
|  |     if (attempts <= 5) return 7000; | ||||||
|  |     if (attempts <= 10) return 15000; | ||||||
|  |     return 30000; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const getAddDelay = () => { | ||||||
|  |     const baseDelay = Math.floor(Math.random() * 700) + 800; | ||||||
|  |     const jitter = Math.floor(Math.random() * 300) - 150; | ||||||
|  |     return Math.max(500, baseDelay + jitter); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     name: 'groupadd', |     name: 'groupadd', | ||||||
|     description: 'Automatically re-adds users to group when they leave. Use multiple IDs for multiple targets.', |     description: 'Automatically re-adds users to group when they leave.', | ||||||
|     async execute(message, args, deleteTimeout) { |     async execute(message, args, deleteTimeout) { | ||||||
|         const { extractUserId } = require('../utils/userUtils'); |         const { extractUserId } = require('../utils/userUtils'); | ||||||
|          |          | ||||||
| @@ -23,6 +37,8 @@ module.exports = { | |||||||
|             isActive = false; |             isActive = false; | ||||||
|             targetUserIds.clear(); |             targetUserIds.clear(); | ||||||
|             lastAddTimes.clear(); |             lastAddTimes.clear(); | ||||||
|  |             failedAttempts.clear(); | ||||||
|  |             recentAdds.clear(); | ||||||
|             channelToWatch = null; |             channelToWatch = null; | ||||||
|             console.log('[GROUPADD] System deactivated'); |             console.log('[GROUPADD] System deactivated'); | ||||||
|  |  | ||||||
| @@ -44,6 +60,8 @@ module.exports = { | |||||||
|         channelToWatch = message.channel; |         channelToWatch = message.channel; | ||||||
|         targetUserIds = new Set(validIds); |         targetUserIds = new Set(validIds); | ||||||
|         isActive = true; |         isActive = true; | ||||||
|  |         failedAttempts.clear(); | ||||||
|  |         recentAdds.clear(); | ||||||
|  |  | ||||||
|         console.log(`[GROUPADD] System activated - Targeting users: ${Array.from(targetUserIds).join(', ')}`); |         console.log(`[GROUPADD] System activated - Targeting users: ${Array.from(targetUserIds).join(', ')}`); | ||||||
|  |  | ||||||
| @@ -51,39 +69,91 @@ module.exports = { | |||||||
|             try { |             try { | ||||||
|                 if (!channelToWatch.recipients.has(userId)) { |                 if (!channelToWatch.recipients.has(userId)) { | ||||||
|                     console.log(`[GROUPADD] Target ${userId} not in group, attempting initial add`); |                     console.log(`[GROUPADD] Target ${userId} not in group, attempting initial add`); | ||||||
|  |                      | ||||||
|  |                     const initialDelay = Math.floor(Math.random() * 500) + 300; | ||||||
|  |                     await new Promise(resolve => setTimeout(resolve, initialDelay)); | ||||||
|  |                      | ||||||
|                     await channelToWatch.addUser(userId); |                     await channelToWatch.addUser(userId); | ||||||
|  |                     lastAddTimes.set(userId, Date.now()); | ||||||
|  |                     recentAdds.set(userId, true); | ||||||
|  |                      | ||||||
|  |                     setTimeout(() => { | ||||||
|  |                         recentAdds.delete(userId); | ||||||
|  |                     }, 10000); | ||||||
|  |                      | ||||||
|                     console.log(`[GROUPADD] Initial add successful for ${userId}`); |                     console.log(`[GROUPADD] Initial add successful for ${userId}`); | ||||||
|                 } |                 } | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 console.log(`[GROUPADD] Initial add failed for ${userId}:`, error); |                 console.log(`[GROUPADD] Initial add failed for ${userId}:`, error); | ||||||
|  |                 failedAttempts.set(userId, (failedAttempts.get(userId) || 0) + 1); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         message.client.on('channelRecipientRemove', async (channel, user) => { |         const handleRecipientRemove = async (channel, user) => { | ||||||
|             if (!isActive || channel.id !== channelToWatch.id || !targetUserIds.has(user.id)) return; |             if (!isActive || channel.id !== channelToWatch.id || !targetUserIds.has(user.id)) return; | ||||||
|  |  | ||||||
|             const currentTime = Date.now(); |             const currentTime = Date.now(); | ||||||
|             const lastAddTime = lastAddTimes.get(user.id) || 0; |             const lastAddTime = lastAddTimes.get(user.id) || 0; | ||||||
|             const timeSinceLastAdd = currentTime - lastAddTime; |             const timeSinceLastAdd = currentTime - lastAddTime; | ||||||
|  |              | ||||||
|  |             const isRecentlyAdded = recentAdds.has(user.id); | ||||||
|  |             const failCount = failedAttempts.get(user.id) || 0; | ||||||
|  |  | ||||||
|             if (timeSinceLastAdd < 1000) { |             console.log(`[GROUPADD] User ${user.id} left. Time since last add: ${timeSinceLastAdd}ms, Recent add: ${isRecentlyAdded}, Failed attempts: ${failCount}`); | ||||||
|                 console.log(`[GROUPADD] Rate limiting for ${user.id}, waiting...`); |              | ||||||
|                 await new Promise(resolve => setTimeout(resolve, 1000 - timeSinceLastAdd)); |             if (isRecentlyAdded) { | ||||||
|  |                 console.log(`[GROUPADD] User ${user.id} was recently added and left immediately. Waiting longer.`); | ||||||
|  |                 await new Promise(resolve => setTimeout(resolve, 5000 + Math.random() * 5000)); | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             if (timeSinceLastAdd < 2000) { | ||||||
|  |                 const backoffTime = getBackoffDelay(user.id); | ||||||
|  |                 console.log(`[GROUPADD] Rate limiting for ${user.id}, waiting ${backoffTime}ms...`); | ||||||
|  |                 await new Promise(resolve => setTimeout(resolve, backoffTime)); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             const delay = getRandomDelay(); |             const addDelay = getAddDelay(); | ||||||
|             console.log(`[GROUPADD] User ${user.id} left, waiting ${delay}ms before re-adding`); |             console.log(`[GROUPADD] Will readd user ${user.id} after ${addDelay}ms`); | ||||||
|  |              | ||||||
|             await new Promise(resolve => setTimeout(resolve, delay)); |             await new Promise(resolve => setTimeout(resolve, addDelay)); | ||||||
|  |              | ||||||
|  |             if (!isActive) { | ||||||
|  |                 console.log(`[GROUPADD] Command was deactivated during delay, cancelling re-add for ${user.id}`); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             try { |             try { | ||||||
|                 await channel.addUser(user.id); |                 await channel.addUser(user.id); | ||||||
|                 lastAddTimes.set(user.id, Date.now()); |                 lastAddTimes.set(user.id, Date.now()); | ||||||
|  |                 recentAdds.set(user.id, true); | ||||||
|  |                  | ||||||
|  |                 setTimeout(() => { | ||||||
|  |                     recentAdds.delete(user.id); | ||||||
|  |                 }, 10000); | ||||||
|  |                  | ||||||
|                 console.log(`[GROUPADD] Successfully re-added user ${user.id}`); |                 console.log(`[GROUPADD] Successfully re-added user ${user.id}`); | ||||||
|  |                  | ||||||
|  |                 if (failedAttempts.get(user.id) > 0) { | ||||||
|  |                     failedAttempts.set(user.id, Math.max(0, failedAttempts.get(user.id) - 1)); | ||||||
|  |                 } | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 console.log(`[GROUPADD] Failed to re-add user ${user.id}:`, error); |                 console.log(`[GROUPADD] Failed to re-add user ${user.id}:`, error); | ||||||
|  |                 failedAttempts.set(user.id, (failedAttempts.get(user.id) || 0) + 1); | ||||||
|  |                  | ||||||
|  |                 if (Math.random() < 0.4 && timeSinceLastAdd > 5000) { | ||||||
|  |                     console.log(`[GROUPADD] Will try again after a pause`); | ||||||
|  |                     setTimeout(() => { | ||||||
|  |                         if (isActive && !channel.recipients.has(user.id)) { | ||||||
|  |                             channel.addUser(user.id).catch(e =>  | ||||||
|  |                                 console.log(`[GROUPADD] Retry failed for ${user.id}:`, e) | ||||||
|  |                             ); | ||||||
|  |                         } | ||||||
|  |                     }, 3000 + Math.random() * 2000); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         }); |         }; | ||||||
|  |  | ||||||
|  |         message.client.on('channelRecipientRemove', handleRecipientRemove); | ||||||
|  |  | ||||||
|         const targetCount = targetUserIds.size; |         const targetCount = targetUserIds.size; | ||||||
|         message.channel.send(`Now watching for ${targetCount} user${targetCount > 1 ? 's' : ''} to leave the group.`) |         message.channel.send(`Now watching for ${targetCount} user${targetCount > 1 ? 's' : ''} to leave the group.`) | ||||||
|   | |||||||
| @@ -1,176 +1,278 @@ | |||||||
| let targetUserIds = []; | const { extractUserId } = require('../utils/userUtils'); | ||||||
| let isKickActive = false; |  | ||||||
| let voiceStateHandler = null; |  | ||||||
| let lastKickTime = 0; |  | ||||||
| let consecutiveKicks = 0; |  | ||||||
| let cooldownTime = 0; |  | ||||||
| let checkInterval = null; |  | ||||||
|  |  | ||||||
| const getRandomDelay = () => { | let activeKicks = new Map(); | ||||||
|     const delay = Math.floor(Math.random() * 250) + 100; | let cooldowns = new Map(); | ||||||
|     console.log(`[KICKVC] Generated event delay: ${delay}ms`); | let voiceStateUpdateHandlers = new Map(); | ||||||
|     return delay; | let checkIntervals = new Map(); | ||||||
|  |  | ||||||
|  | const getKickDelay = () => { | ||||||
|  |     const baseDelay = Math.floor(Math.random() * 400) + 600; | ||||||
|  |     const jitter = Math.floor(Math.random() * 200) - 100; | ||||||
|  |     return Math.max(400, baseDelay + jitter); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const getRandomCheckDelay = () => { | const calculateCooldown = (kickCount) => { | ||||||
|     const delay = Math.floor(Math.random() * 250) + 200; |     if (kickCount <= 2) return 0; | ||||||
|     console.log(`[KICKVC] Generated interval check delay: ${delay}ms`); |     if (kickCount <= 5) return Math.min((kickCount - 2) * 300, 900); | ||||||
|     return delay; |     if (kickCount <= 10) return Math.min(900 + (kickCount - 5) * 400, 2900); | ||||||
|  |     return Math.min(2900 + (kickCount - 10) * 500, 5000); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const getCooldown = (kicks) => { | const getSafetyPause = () => { | ||||||
|     let cooldown; |     if (Math.random() < 0.25) { | ||||||
|     if (kicks <= 3) cooldown = 200; |         return Math.floor(Math.random() * 4000) + 3000; | ||||||
|     else if (kicks <= 5) cooldown = 500; |     } | ||||||
|     else if (kicks <= 10) cooldown = 1000; |     return 0; | ||||||
|     else cooldown = 2500; | }; | ||||||
|     console.log(`[KICKVC] New cooldown calculated for ${kicks} kicks: ${cooldown}ms`); |  | ||||||
|     return cooldown; | const performUserKick = async (userId, guild, voiceChannel, kickData) => { | ||||||
|  |     if (!activeKicks.has(userId)) return false; | ||||||
|  |      | ||||||
|  |     try { | ||||||
|  |         const member = guild.members.cache.get(userId); | ||||||
|  |         if (!member || !member.voice.channelId) return false; | ||||||
|  |          | ||||||
|  |         console.log(`[KICKVC] Found user ${userId} in VC: ${voiceChannel.name}`); | ||||||
|  |          | ||||||
|  |         const currentTime = Date.now(); | ||||||
|  |         const lastKickTime = kickData.lastKick; | ||||||
|  |         const timeSinceLastKick = currentTime - lastKickTime; | ||||||
|  |          | ||||||
|  |         let cooldownTime = cooldowns.get(userId) || 0; | ||||||
|  |          | ||||||
|  |         if (timeSinceLastKick < 3000) { | ||||||
|  |             cooldownTime = calculateCooldown(kickData.count); | ||||||
|  |             cooldowns.set(userId, cooldownTime); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         if (cooldownTime > 0) { | ||||||
|  |             console.log(`[KICKVC] Cooldown active for ${userId}: ${cooldownTime}ms`); | ||||||
|  |             await new Promise(resolve => setTimeout(resolve, cooldownTime)); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         const safetyPause = getSafetyPause(); | ||||||
|  |         if (safetyPause > 0) { | ||||||
|  |             console.log(`[KICKVC] Adding safety pause of ${safetyPause}ms before kicking ${userId}`); | ||||||
|  |             await new Promise(resolve => setTimeout(resolve, safetyPause)); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         const delay = getKickDelay(); | ||||||
|  |         console.log(`[KICKVC] Will kick ${userId} after ${delay}ms delay`); | ||||||
|  |          | ||||||
|  |         await new Promise(resolve => setTimeout(resolve, delay)); | ||||||
|  |          | ||||||
|  |         if (!activeKicks.has(userId)) { | ||||||
|  |             console.log(`[KICKVC] Kick for ${userId} was stopped during delay`); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         if (member && member.voice.channelId) { | ||||||
|  |             await member.voice.disconnect(); | ||||||
|  |              | ||||||
|  |             kickData.count++; | ||||||
|  |             kickData.lastKick = Date.now(); | ||||||
|  |             activeKicks.set(userId, kickData); | ||||||
|  |              | ||||||
|  |             console.log(`[KICKVC] Successfully kicked ${userId} (${kickData.count} kicks so far)`); | ||||||
|  |              | ||||||
|  |             if (kickData.count % 5 === 0) { | ||||||
|  |                 cooldowns.set(userId, calculateCooldown(kickData.count) + 2000); | ||||||
|  |                 console.log(`[KICKVC] Increased cooldown after ${kickData.count} kicks`); | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return false; | ||||||
|  |     } catch (error) { | ||||||
|  |         console.log(`[KICKVC] Failed to kick ${userId}:`, error); | ||||||
|  |          | ||||||
|  |         if (Math.random() < 0.3 && kickData.count > 0) { | ||||||
|  |             setTimeout(() => { | ||||||
|  |                 try { | ||||||
|  |                     const member = guild.members.cache.get(userId); | ||||||
|  |                     if (member && member.voice.channelId) { | ||||||
|  |                         member.voice.disconnect().catch(e =>  | ||||||
|  |                             console.log(`[KICKVC] Retry failed for ${userId}:`, e) | ||||||
|  |                         ); | ||||||
|  |                     } | ||||||
|  |                 } catch (retryError) { | ||||||
|  |                     console.log(`[KICKVC] Retry setup failed for ${userId}:`, retryError); | ||||||
|  |                 } | ||||||
|  |             }, 2000 + Math.random() * 1000); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     name: 'kickvc', |     name: 'kickvc', | ||||||
|     description: 'Automatically kicks specified users from voice channels.', |     description: 'Automatically kicks specified users from voice channels.', | ||||||
|     async execute(message, args, deleteTimeout) { |     async execute(message, args, deleteTimeout) { | ||||||
|         if (args[0]?.toLowerCase() === 'stop') { |         if (args.length === 0) { | ||||||
|             if (voiceStateHandler) { |             message.channel.send('Please provide a command: `start <userId(s)>` or `stop <userId or "all">`') | ||||||
|                 message.client.removeListener('voiceStateUpdate', voiceStateHandler); |                 .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|                 voiceStateHandler = null; |  | ||||||
|             } |  | ||||||
|             if (checkInterval) { |  | ||||||
|                 clearInterval(checkInterval); |  | ||||||
|                 checkInterval = null; |  | ||||||
|             } |  | ||||||
|             isKickActive = false; |  | ||||||
|             targetUserIds = []; |  | ||||||
|             lastKickTime = 0; |  | ||||||
|             consecutiveKicks = 0; |  | ||||||
|             cooldownTime = 0; |  | ||||||
|             console.log('[KICKVC] System deactivated - all variables reset'); |  | ||||||
|             message.channel.send('Voice kick has been deactivated.') |  | ||||||
|                 .then(msg => setTimeout(() => msg.delete().catch(console.error), deleteTimeout)); |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         const userIds = args.filter(arg => /^\d{17,19}$/.test(arg)); |  | ||||||
|         if (!userIds.length) { |         const command = args[0].toLowerCase(); | ||||||
|             console.log('[KICKVC] Invalid user IDs provided'); |  | ||||||
|             message.channel.send('Please provide at least one valid user ID.') |         if (command === 'stop') { | ||||||
|                 .then(msg => setTimeout(() => msg.delete().catch(console.error), deleteTimeout)); |             if (args.length < 2) { | ||||||
|             return; |                 message.channel.send('Please specify a user ID or "all" to stop kicking.') | ||||||
|         } |                     .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|         targetUserIds = userIds; |  | ||||||
|         isKickActive = true; |  | ||||||
|         console.log(`[KICKVC] System activated - Targeting user IDs: ${targetUserIds.join(', ')}`); |  | ||||||
|         if (voiceStateHandler) { |  | ||||||
|             message.client.removeListener('voiceStateUpdate', voiceStateHandler); |  | ||||||
|             console.log('[KICKVC] Removed old voice state handler'); |  | ||||||
|         } |  | ||||||
|         if (checkInterval) { |  | ||||||
|             clearInterval(checkInterval); |  | ||||||
|             console.log('[KICKVC] Cleared old check interval'); |  | ||||||
|         } |  | ||||||
|         const kickUser = async (member, guild, fromEvent = false) => { |  | ||||||
|             if (!isKickActive) return; |  | ||||||
|             const currentTime = Date.now(); |  | ||||||
|             const timeSinceLastKick = currentTime - lastKickTime; |  | ||||||
|             if (timeSinceLastKick < cooldownTime) { |  | ||||||
|                 console.log(`[KICKVC] On cooldown - ${cooldownTime - timeSinceLastKick}ms remaining`); |  | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             try { |  | ||||||
|                 const selfMember = await guild.members.fetch(member.client.user.id); |             const target = args[1].toLowerCase(); | ||||||
|                 if (!selfMember.permissions.has("ADMINISTRATOR")) { |              | ||||||
|                     console.log(`[KICKVC] No admin permissions in ${guild.name}, skipping`); |             if (target === 'all') { | ||||||
|  |                 for (const [userId, handler] of voiceStateUpdateHandlers.entries()) { | ||||||
|  |                     message.client.off('voiceStateUpdate', handler); | ||||||
|  |                     activeKicks.delete(userId); | ||||||
|  |                     cooldowns.delete(userId); | ||||||
|  |                     clearInterval(checkIntervals.get(userId)); | ||||||
|  |                     checkIntervals.delete(userId); | ||||||
|  |                     console.log(`[KICKVC] Stopped kicking user: ${userId}`); | ||||||
|  |                 } | ||||||
|  |                 voiceStateUpdateHandlers.clear(); | ||||||
|  |                  | ||||||
|  |                 message.channel.send('Stopped all active VC kicks.') | ||||||
|  |                     .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|  |                 return; | ||||||
|  |             } else { | ||||||
|  |                 const userId = extractUserId(target); | ||||||
|  |                  | ||||||
|  |                 if (!userId) { | ||||||
|  |                     message.channel.send('Invalid user ID.') | ||||||
|  |                         .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|                 const delay = fromEvent ? getRandomDelay() : getRandomCheckDelay(); |  | ||||||
|                 console.log(`[KICKVC] Admin check passed in ${guild.name}, waiting ${delay}ms before kick...`); |                 if (voiceStateUpdateHandlers.has(userId)) { | ||||||
|                 await new Promise(resolve => setTimeout(resolve, delay)); |                     message.client.off('voiceStateUpdate', voiceStateUpdateHandlers.get(userId)); | ||||||
|                 if (!member.voice.channel) return; |                     activeKicks.delete(userId); | ||||||
|                 console.log(`[KICKVC] Target in voice: ${member.user.tag} | ${guild.name} | ${member.voice.channel.name}`); |                     cooldowns.delete(userId); | ||||||
|                 await member.voice.disconnect(); |                     clearInterval(checkIntervals.get(userId)); | ||||||
|                 lastKickTime = currentTime; |                     checkIntervals.delete(userId); | ||||||
|                 consecutiveKicks++; |                     console.log(`[KICKVC] Stopped kicking user: ${userId}`); | ||||||
|                 cooldownTime = getCooldown(consecutiveKicks); |                      | ||||||
|                 setTimeout(() => { |                     message.channel.send(`Stopped kicking user: ${userId}`) | ||||||
|                     if (consecutiveKicks > 0) { |                         .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|                         consecutiveKicks--; |                 } else { | ||||||
|                         cooldownTime = getCooldown(consecutiveKicks); |                     message.channel.send(`No active kick for user: ${userId}`) | ||||||
|                     } |                         .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|                 }, 15000); |  | ||||||
|             } catch (error) { |  | ||||||
|                 console.log(`[KICKVC] Error kicking in ${guild.name}:`, error); |  | ||||||
|                 try { |  | ||||||
|                     await member.voice.setChannel(null); |  | ||||||
|                     console.log('[KICKVC] Succeeded with alternate method (setChannel null)'); |  | ||||||
|                 } catch { |  | ||||||
|                     try { |  | ||||||
|                         await member.voice.channel.permissionOverwrites.create(member, { |  | ||||||
|                             Connect: false, |  | ||||||
|                             Speak: false |  | ||||||
|                         }); |  | ||||||
|                         await member.voice.disconnect(); |  | ||||||
|                         console.log('[KICKVC] Succeeded with permissions override'); |  | ||||||
|                     } catch { |  | ||||||
|                         console.log('[KICKVC] All disconnect methods failed'); |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|  |                 return; | ||||||
|             } |             } | ||||||
|         }; |  | ||||||
|         voiceStateHandler = async (oldState, newState) => { |  | ||||||
|             if (!isKickActive || targetUserIds.length === 0) return; |  | ||||||
|             const id = newState?.member?.id || oldState?.member?.id; |  | ||||||
|             if (!targetUserIds.includes(id)) return; |  | ||||||
|             const voiceState = newState?.channelId ? newState : oldState; |  | ||||||
|             if (!voiceState?.channel) return; |  | ||||||
|             console.log('[KICKVC] Voice state update detected for target'); |  | ||||||
|             try { |  | ||||||
|                 const guild = voiceState.guild; |  | ||||||
|                 const member = await guild.members.fetch(id).catch(() => null); |  | ||||||
|                 if (member?.voice?.channel) { |  | ||||||
|                     await kickUser(member, guild, true); |  | ||||||
|                 } |  | ||||||
|             } catch (error) { |  | ||||||
|                 console.log('[KICKVC] Error in voice state handler:', error); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|         const intervalTime = Math.floor(Math.random() * 500) + 1000; |  | ||||||
|         console.log(`[KICKVC] Setting up interval check every ${intervalTime}ms`); |  | ||||||
|         checkInterval = setInterval(async () => { |  | ||||||
|             if (!isKickActive) return; |  | ||||||
|             for (const guild of message.client.guilds.cache.values()) { |  | ||||||
|                 for (const id of targetUserIds) { |  | ||||||
|                     try { |  | ||||||
|                         const member = await guild.members.fetch(id).catch(() => null); |  | ||||||
|                         if (member?.voice?.channel) { |  | ||||||
|                             await kickUser(member, guild, false); |  | ||||||
|                         } |  | ||||||
|                     } catch { } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }, intervalTime); |  | ||||||
|         message.client.on('voiceStateUpdate', voiceStateHandler); |  | ||||||
|         console.log('[KICKVC] New voice state handler and check interval registered'); |  | ||||||
|         try { |  | ||||||
|             const users = await Promise.all(targetUserIds.map(id => message.client.users.fetch(id).catch(() => null))); |  | ||||||
|             const userTags = users.filter(u => u).map(u => `${u.tag} (${u.id})`); |  | ||||||
|             console.log(`[KICKVC] Successfully fetched target users: ${userTags.join(', ')}`); |  | ||||||
|             message.channel.send(`Now automatically kicking: ${userTags.join(', ')} from voice channels.`) |  | ||||||
|                 .then(msg => setTimeout(() => msg.delete().catch(console.error), deleteTimeout)); |  | ||||||
|             console.log('[KICKVC] Performing initial guild check'); |  | ||||||
|             message.client.guilds.cache.forEach(async (guild) => { |  | ||||||
|                 for (const id of targetUserIds) { |  | ||||||
|                     const member = await guild.members.fetch(id).catch(() => null); |  | ||||||
|                     if (member?.voice?.channel) { |  | ||||||
|                         console.log(`[KICKVC] Target found in voice during initial check - Server: ${guild.name}`); |  | ||||||
|                         await kickUser(member, guild, true); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } catch (error) { |  | ||||||
|             console.log('[KICKVC] Could not fetch user information:', error); |  | ||||||
|             message.channel.send(`Now automatically kicking user IDs: ${targetUserIds.join(', ')} from voice channels.`) |  | ||||||
|                 .then(msg => setTimeout(() => msg.delete().catch(console.error), deleteTimeout)); |  | ||||||
|         } |         } | ||||||
|     }, |  | ||||||
|  |         if (command === 'start') { | ||||||
|  |             if (args.length < 2) { | ||||||
|  |                 message.channel.send('Please provide at least one user ID to kick.') | ||||||
|  |                     .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const userIds = args.slice(1) | ||||||
|  |                 .map(arg => extractUserId(arg)) | ||||||
|  |                 .filter(id => id !== null); | ||||||
|  |  | ||||||
|  |             if (userIds.length === 0) { | ||||||
|  |                 message.channel.send('No valid user IDs provided.') | ||||||
|  |                     .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const startedKicking = []; | ||||||
|  |             const alreadyKicking = []; | ||||||
|  |  | ||||||
|  |             for (const userId of userIds) { | ||||||
|  |                 if (activeKicks.has(userId)) { | ||||||
|  |                     alreadyKicking.push(userId); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 activeKicks.set(userId, { count: 0, lastKick: 0 }); | ||||||
|  |                 cooldowns.set(userId, 0); | ||||||
|  |  | ||||||
|  |                 for (const guild of message.client.guilds.cache.values()) { | ||||||
|  |                     try { | ||||||
|  |                         const member = await guild.members.fetch(userId).catch(() => null); | ||||||
|  |                         if (member && member.voice.channelId) { | ||||||
|  |                             const kickData = activeKicks.get(userId); | ||||||
|  |                             console.log(`[KICKVC] Found target ${userId} already in voice in ${guild.name}`); | ||||||
|  |                             performUserKick(userId, guild, member.voice.channel, kickData); | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                     } catch (error) { | ||||||
|  |                         console.log(`[KICKVC] Error checking guild ${guild.name} for user ${userId}:`, error); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 const checkInterval = setInterval(async () => { | ||||||
|  |                     if (!activeKicks.has(userId)) { | ||||||
|  |                         clearInterval(checkInterval); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|  |                     const kickData = activeKicks.get(userId); | ||||||
|  |                      | ||||||
|  |                     for (const guild of message.client.guilds.cache.values()) { | ||||||
|  |                         try { | ||||||
|  |                             const member = await guild.members.fetch(userId).catch(() => null); | ||||||
|  |                             if (member && member.voice.channelId) { | ||||||
|  |                                 performUserKick(userId, guild, member.voice.channel, kickData); | ||||||
|  |                                 return; | ||||||
|  |                             } | ||||||
|  |                         } catch (error) {} | ||||||
|  |                     } | ||||||
|  |                 }, 4000 + Math.floor(Math.random() * 2000)); | ||||||
|  |                  | ||||||
|  |                 checkIntervals.set(userId, checkInterval); | ||||||
|  |  | ||||||
|  |                 const handleVoiceStateUpdate = async (oldState, newState) => { | ||||||
|  |                     if (!activeKicks.has(userId)) return; | ||||||
|  |                      | ||||||
|  |                     const member = newState.member || oldState.member; | ||||||
|  |                     if (!member || member.user.id !== userId) return; | ||||||
|  |                      | ||||||
|  |                     const kickData = activeKicks.get(userId); | ||||||
|  |                      | ||||||
|  |                     if ((!oldState.channelId && newState.channelId) ||  | ||||||
|  |                         (oldState.channelId !== newState.channelId && newState.channelId)) { | ||||||
|  |                          | ||||||
|  |                         const guild = newState.guild; | ||||||
|  |                         const voiceChannel = newState.channel; | ||||||
|  |                          | ||||||
|  |                         console.log(`[KICKVC] Target user ${userId} joined/moved to VC: ${voiceChannel.name}`); | ||||||
|  |                         performUserKick(userId, guild, voiceChannel, kickData); | ||||||
|  |                     } | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 voiceStateUpdateHandlers.set(userId, handleVoiceStateUpdate); | ||||||
|  |                 message.client.on('voiceStateUpdate', handleVoiceStateUpdate); | ||||||
|  |                 startedKicking.push(userId); | ||||||
|  |                 console.log(`[KICKVC] Started kicking user: ${userId}`); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let responseMessage = ''; | ||||||
|  |              | ||||||
|  |             if (startedKicking.length > 0) { | ||||||
|  |                 responseMessage += `Started kicking ${startedKicking.length} user(s): ${startedKicking.join(', ')}\n`; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             if (alreadyKicking.length > 0) { | ||||||
|  |                 responseMessage += `Already kicking: ${alreadyKicking.join(', ')}`; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             message.channel.send(responseMessage) | ||||||
|  |                 .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         message.channel.send('Unknown command. Use `start <userId(s)>` or `stop <userId or "all">`') | ||||||
|  |             .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||||
|  |     } | ||||||
| }; | }; | ||||||
							
								
								
									
										135
									
								
								commands/ssh.js
									
									
									
									
									
								
							
							
						
						
									
										135
									
								
								commands/ssh.js
									
									
									
									
									
								
							| @@ -1,135 +0,0 @@ | |||||||
| const { Client: SSHClient } = require('ssh2'); |  | ||||||
|  |  | ||||||
| let sshConnection = null; |  | ||||||
|  |  | ||||||
| module.exports = { |  | ||||||
|     name: 'ssh', |  | ||||||
|     description: 'Manage an SSH connection. Subcommands: connect, exec, disconnect', |  | ||||||
|     async execute(message, args, deleteTimeout) { |  | ||||||
|         if (!args.length) { |  | ||||||
|             return message.channel |  | ||||||
|                 .send("Usage: `.ssh <connect|exec|disconnect> ...`") |  | ||||||
|                 .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         const subcommand = args.shift().toLowerCase(); |  | ||||||
|  |  | ||||||
|         if (subcommand === 'connect') { |  | ||||||
|             if (args.length < 3) { |  | ||||||
|                 return message.channel |  | ||||||
|                     .send("Usage: `.ssh connect <host> <username> <password> [port]`") |  | ||||||
|                     .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (sshConnection) { |  | ||||||
|                 return message.channel |  | ||||||
|                     .send("Already connected. Disconnect first using `.ssh disconnect`.") |  | ||||||
|                     .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             const host = args[0]; |  | ||||||
|             const username = args[1]; |  | ||||||
|             const password = args[2]; |  | ||||||
|             const port = args[3] ? parseInt(args[3]) : 22; |  | ||||||
|  |  | ||||||
|             sshConnection = new SSHClient(); |  | ||||||
|  |  | ||||||
|             sshConnection |  | ||||||
|                 .on('ready', () => { |  | ||||||
|                     message.channel |  | ||||||
|                         .send(`Connected to ${host}`) |  | ||||||
|                         .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|                 }) |  | ||||||
|                 .on('error', (err) => { |  | ||||||
|                     message.channel |  | ||||||
|                         .send(`SSH Connection error: ${err.message}`) |  | ||||||
|                         .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|                     sshConnection = null; |  | ||||||
|                 }) |  | ||||||
|                 .on('close', () => { |  | ||||||
|                     message.channel |  | ||||||
|                         .send(`SSH Connection closed.`) |  | ||||||
|                         .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|                     sshConnection = null; |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|             sshConnection.connect({ host, port, username, password }); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         else if (subcommand === 'exec') { |  | ||||||
|             if (!sshConnection) { |  | ||||||
|                 return message.channel |  | ||||||
|                     .send("No active SSH connection. Connect first using `.ssh connect ...`") |  | ||||||
|                     .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|             } |  | ||||||
|             if (!args.length) { |  | ||||||
|                 return message.channel |  | ||||||
|                     .send("Usage: `.ssh exec <command>`") |  | ||||||
|                     .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             const cmd = args.join(' '); |  | ||||||
|  |  | ||||||
|             sshConnection.exec(cmd, (err, stream) => { |  | ||||||
|                 if (err) { |  | ||||||
|                     return message.channel |  | ||||||
|                         .send(`Error executing command: ${err.message}`) |  | ||||||
|                         .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 let outputBuffer = ''; |  | ||||||
|  |  | ||||||
|                 message.channel.send(`Executing command: \`${cmd}\`\n\`\`\`\n...\n\`\`\``) |  | ||||||
|                     .then((sentMsg) => { |  | ||||||
|                         const updateInterval = setInterval(() => { |  | ||||||
|                             let display = outputBuffer; |  | ||||||
|                             if (display.length > 1900) { |  | ||||||
|                                 display = display.slice(-1900); |  | ||||||
|                             } |  | ||||||
|                             sentMsg.edit(`Executing command: \`${cmd}\`\n\`\`\`\n${display}\n\`\`\``) |  | ||||||
|                                 .catch(() => { }); |  | ||||||
|                         }, 2000); |  | ||||||
|  |  | ||||||
|                         stream.on('data', (data) => { |  | ||||||
|                             outputBuffer += data.toString(); |  | ||||||
|                         }); |  | ||||||
|                         stream.stderr.on('data', (data) => { |  | ||||||
|                             outputBuffer += data.toString(); |  | ||||||
|                         }); |  | ||||||
|                         stream.on('close', (code, signal) => { |  | ||||||
|                             clearInterval(updateInterval); |  | ||||||
|                             outputBuffer += `\nProcess exited with code ${code}${signal ? ' and signal ' + signal : ''}`; |  | ||||||
|                             let display = outputBuffer; |  | ||||||
|                             if (display.length > 1900) { |  | ||||||
|                                 display = display.slice(-1900); |  | ||||||
|                             } |  | ||||||
|                             sentMsg.edit(`Executing command: \`${cmd}\`\n\`\`\`\n${display}\n\`\`\``) |  | ||||||
|                                 .catch(() => { }); |  | ||||||
|                         }); |  | ||||||
|                     }) |  | ||||||
|                     .catch(console.error); |  | ||||||
|             }); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         else if (subcommand === 'disconnect') { |  | ||||||
|             if (!sshConnection) { |  | ||||||
|                 return message.channel |  | ||||||
|                     .send("No active SSH connection.") |  | ||||||
|                     .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|             } |  | ||||||
|             sshConnection.end(); |  | ||||||
|             sshConnection = null; |  | ||||||
|             return message.channel |  | ||||||
|                 .send("Disconnecting SSH...") |  | ||||||
|                 .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         else { |  | ||||||
|             return message.channel |  | ||||||
|                 .send("Unknown subcommand. Use `connect`, `exec`, or `disconnect`.") |  | ||||||
|                 .then((msg) => setTimeout(() => msg.delete().catch(() => { }), deleteTimeout)); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
		Reference in New Issue
	
	Block a user