Refactor groupadd and delete
This commit is contained in:
		| @@ -3,7 +3,6 @@ let cancelDelete = false; | ||||
| let deletedMessages = new Set(); | ||||
| const CACHE_CLEANUP_INTERVAL = 30 * 60 * 1000; | ||||
|  | ||||
| // Cleanup deleted message cache periodically | ||||
| setInterval(() => { | ||||
|   if (deletedMessages.size > 1000) { | ||||
|     console.log(`[DELETE] Cleaning message cache (size: ${deletedMessages.size})`); | ||||
| @@ -31,11 +30,10 @@ module.exports = { | ||||
|     isDeleting = true; | ||||
|     cancelDelete = false; | ||||
|  | ||||
|     // Check for speed settings | ||||
|     let speed = 'medium'; | ||||
|     if (args[0] && ['slow', 'medium', 'fast'].includes(args[0].toLowerCase())) { | ||||
|       speed = args[0].toLowerCase(); | ||||
|       args.shift(); // Remove the speed argument | ||||
|       args.shift(); | ||||
|     } | ||||
|  | ||||
|     const deleteCount = parseInt(args[0], 10); | ||||
| @@ -55,7 +53,6 @@ module.exports = { | ||||
| }; | ||||
|  | ||||
| async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, speed = 'medium') { | ||||
|   // Human-like timing parameters based on speed setting | ||||
|   let deleteIntervalMin, deleteIntervalMax, jitterFactor, pauseChance, pauseLengthMin, pauseLengthMax, batchSize; | ||||
|    | ||||
|   switch(speed) { | ||||
| @@ -88,7 +85,6 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | ||||
|       batchSize = 10; | ||||
|   } | ||||
|    | ||||
|   // More human-like delay functions | ||||
|   const getHumanlikeDelay = () => { | ||||
|     const baseInterval = Math.floor(Math.random() * (deleteIntervalMax - deleteIntervalMin + 1)) + deleteIntervalMin; | ||||
|     const jitterAmount = baseInterval * jitterFactor; | ||||
| @@ -96,7 +92,7 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | ||||
|     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 { | ||||
|     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; | ||||
|      | ||||
|     while (deletedCount < deleteCount && !cancelDelete) { | ||||
|       // Show progress occasionally | ||||
|       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 }); // Fetch more to ensure we find user messages | ||||
|       const messages = await message.channel.messages.fetch({ limit: 100 }); | ||||
|        | ||||
|       const filteredMessages = messages.filter(msg =>  | ||||
|         msg.author.id === message.author.id &&  | ||||
| @@ -122,7 +117,6 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | ||||
|         break; | ||||
|       } | ||||
|        | ||||
|       // Process current batch | ||||
|       batchCount++; | ||||
|       let messagesInThisBatch = 0; | ||||
|        | ||||
| @@ -132,42 +126,35 @@ async function deleteMessagesFromChannel(message, deleteCount, deleteTimeout, sp | ||||
|           return; | ||||
|         } | ||||
|          | ||||
|         // Exit the loop if we've deleted enough messages | ||||
|         if (deletedCount >= deleteCount) break; | ||||
|          | ||||
|         try { | ||||
|           if (msg.deletable && !msg.deleted && !deletedMessages.has(msg.id)) { | ||||
|             // Simulate reading the message occasionally (25% chance) | ||||
|             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)); | ||||
|             } | ||||
|              | ||||
|             // Variable pre-delete delay | ||||
|             const preDeleteDelay = Math.floor(Math.random() * 1000) + 250; | ||||
|             await new Promise(resolve => setTimeout(resolve, preDeleteDelay)); | ||||
|              | ||||
|             // Attempt to delete | ||||
|             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...`); | ||||
|                 // Don't count this one, we'll try again later | ||||
|                 return; | ||||
|               } else { | ||||
|                 console.error(`[DELETE] Failed to delete message:`, err); | ||||
|               } | ||||
|             }); | ||||
|              | ||||
|             // Successfully deleted | ||||
|             deletedMessages.add(msg.id); | ||||
|             deletedCount++; | ||||
|             messagesInThisBatch++; | ||||
|              | ||||
|             // Add human-like delay between deletions | ||||
|             const delay = getHumanlikeDelay(); | ||||
|             console.log(`[DELETE] Waiting ${delay}ms before next deletion`); | ||||
|             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) { | ||||
|         console.log(`[DELETE] No deletable messages found in batch`); | ||||
|         break; | ||||
|       } | ||||
|        | ||||
|       // Take a natural pause between batches (with higher chance after several batches) | ||||
|       if (!cancelDelete && deletedCount < deleteCount) { | ||||
|         // More likely to pause after several consecutive batches | ||||
|         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; // Reset batch count after a pause | ||||
|           batchCount = 0; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     // Final status message | ||||
|     if (cancelDelete) { | ||||
|       const canceledMsg = await message.channel.send(`Delete operation canceled after removing ${deletedCount} messages.`); | ||||
|       setTimeout(() => canceledMsg.delete().catch(console.error), deleteTimeout); | ||||
| @@ -221,7 +204,6 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | ||||
|     return; | ||||
|   } | ||||
|    | ||||
|   // Human-like timing parameters based on speed setting | ||||
|   let deleteIntervalMin, deleteIntervalMax, jitterFactor, pauseChance, pauseLengthMin, pauseLengthMax, batchSize; | ||||
|    | ||||
|   switch(speed) { | ||||
| @@ -229,7 +211,7 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | ||||
|       deleteIntervalMin = 3000; | ||||
|       deleteIntervalMax = 6000; | ||||
|       jitterFactor = 0.5; | ||||
|       pauseChance = 0.4;  // Higher chance to pause between channels | ||||
|       pauseChance = 0.4; | ||||
|       pauseLengthMin = 30000; | ||||
|       pauseLengthMax = 90000; | ||||
|       batchSize = 5; | ||||
| @@ -254,7 +236,6 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | ||||
|       batchSize = 10; | ||||
|   } | ||||
|    | ||||
|   // More human-like delay functions | ||||
|   const getHumanlikeDelay = () => { | ||||
|     const baseInterval = Math.floor(Math.random() * (deleteIntervalMax - deleteIntervalMin + 1)) + deleteIntervalMin; | ||||
|     const jitterAmount = baseInterval * jitterFactor; | ||||
| @@ -300,7 +281,6 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | ||||
|           continue; | ||||
|         } | ||||
|  | ||||
|         // Process current batch | ||||
|         batchCount++; | ||||
|         let messagesInThisBatch = 0; | ||||
|          | ||||
| @@ -309,31 +289,26 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | ||||
|            | ||||
|           try { | ||||
|             if (msg.deletable && !msg.deleted && !deletedMessages.has(msg.id)) { | ||||
|               // Variable pre-delete delay | ||||
|               const preDeleteDelay = Math.floor(Math.random() * 1000) + 250; | ||||
|               await new Promise(resolve => setTimeout(resolve, preDeleteDelay)); | ||||
|                | ||||
|               // Attempt to delete | ||||
|               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...`); | ||||
|                   // Take an extra long break on rate limits | ||||
|                   return new Promise(resolve => setTimeout(resolve, 30000 + Math.random() * 30000)); | ||||
|                 } else { | ||||
|                   console.error(`[DELETE] Failed to delete message:`, err); | ||||
|                 } | ||||
|               }); | ||||
|                | ||||
|               // Successfully deleted | ||||
|               deletedMessages.add(msg.id); | ||||
|               totalDeleted++; | ||||
|               messagesDeletedInChannel++; | ||||
|               messagesInThisBatch++; | ||||
|                | ||||
|               // Add human-like delay between deletions | ||||
|               const delay = getHumanlikeDelay(); | ||||
|               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); | ||||
|           } | ||||
|            | ||||
|           // Break batch processing after certain number of messages to avoid long loops | ||||
|           if (messagesInThisBatch >= batchSize) break; | ||||
|         } | ||||
|  | ||||
|         // If we deleted fewer messages than the batch size, assume we've reached the end | ||||
|         if (messagesInThisBatch < batchSize) { | ||||
|           hasMoreMessages = false; | ||||
|         } else { | ||||
|           // Take a natural pause between batches within a channel | ||||
|           const shouldPause = Math.random() < pauseChance; | ||||
|           if (shouldPause && !cancelDelete) { | ||||
|             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`); | ||||
|        | ||||
|       // Take a longer pause between channels | ||||
|       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}`); | ||||
| @@ -369,7 +340,6 @@ async function deleteMessagesFromServer(message, guildId, deleteTimeout, speed = | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     // Final status message | ||||
|     if (cancelDelete) { | ||||
|       const canceledMsg = await message.channel.send(`Server cleanup canceled after removing ${totalDeleted} messages across ${processedChannels} channels.`); | ||||
|       setTimeout(() => canceledMsg.delete().catch(console.error), deleteTimeout); | ||||
|   | ||||
| @@ -2,14 +2,28 @@ let targetUserIds = new Set(); | ||||
| let isActive = false; | ||||
| let channelToWatch = null; | ||||
| let lastAddTimes = new Map(); | ||||
| let failedAttempts = new Map(); | ||||
| let recentAdds = new Map(); | ||||
|  | ||||
| const getRandomDelay = () => { | ||||
|     return Math.floor(Math.random() * 200) + 150; | ||||
| const getBackoffDelay = (userId) => { | ||||
|     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 = { | ||||
|     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) { | ||||
|         const { extractUserId } = require('../utils/userUtils'); | ||||
|          | ||||
| @@ -23,6 +37,8 @@ module.exports = { | ||||
|             isActive = false; | ||||
|             targetUserIds.clear(); | ||||
|             lastAddTimes.clear(); | ||||
|             failedAttempts.clear(); | ||||
|             recentAdds.clear(); | ||||
|             channelToWatch = null; | ||||
|             console.log('[GROUPADD] System deactivated'); | ||||
|  | ||||
| @@ -44,6 +60,8 @@ module.exports = { | ||||
|         channelToWatch = message.channel; | ||||
|         targetUserIds = new Set(validIds); | ||||
|         isActive = true; | ||||
|         failedAttempts.clear(); | ||||
|         recentAdds.clear(); | ||||
|  | ||||
|         console.log(`[GROUPADD] System activated - Targeting users: ${Array.from(targetUserIds).join(', ')}`); | ||||
|  | ||||
| @@ -51,39 +69,91 @@ module.exports = { | ||||
|             try { | ||||
|                 if (!channelToWatch.recipients.has(userId)) { | ||||
|                     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); | ||||
|                     lastAddTimes.set(userId, Date.now()); | ||||
|                     recentAdds.set(userId, true); | ||||
|                      | ||||
|                     setTimeout(() => { | ||||
|                         recentAdds.delete(userId); | ||||
|                     }, 10000); | ||||
|                      | ||||
|                     console.log(`[GROUPADD] Initial add successful for ${userId}`); | ||||
|                 } | ||||
|             } catch (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; | ||||
|  | ||||
|             const currentTime = Date.now(); | ||||
|             const lastAddTime = lastAddTimes.get(user.id) || 0; | ||||
|             const timeSinceLastAdd = currentTime - lastAddTime; | ||||
|              | ||||
|             if (timeSinceLastAdd < 1000) { | ||||
|                 console.log(`[GROUPADD] Rate limiting for ${user.id}, waiting...`); | ||||
|                 await new Promise(resolve => setTimeout(resolve, 1000 - timeSinceLastAdd)); | ||||
|             const isRecentlyAdded = recentAdds.has(user.id); | ||||
|             const failCount = failedAttempts.get(user.id) || 0; | ||||
|  | ||||
|             console.log(`[GROUPADD] User ${user.id} left. Time since last add: ${timeSinceLastAdd}ms, Recent add: ${isRecentlyAdded}, Failed attempts: ${failCount}`); | ||||
|              | ||||
|             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)); | ||||
|             } | ||||
|              | ||||
|             const delay = getRandomDelay(); | ||||
|             console.log(`[GROUPADD] User ${user.id} left, waiting ${delay}ms before re-adding`); | ||||
|             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)); | ||||
|             } | ||||
|  | ||||
|             await new Promise(resolve => setTimeout(resolve, delay)); | ||||
|             const addDelay = getAddDelay(); | ||||
|             console.log(`[GROUPADD] Will readd user ${user.id} after ${addDelay}ms`); | ||||
|              | ||||
|             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 { | ||||
|                 await channel.addUser(user.id); | ||||
|                 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}`); | ||||
|                  | ||||
|                 if (failedAttempts.get(user.id) > 0) { | ||||
|                     failedAttempts.set(user.id, Math.max(0, failedAttempts.get(user.id) - 1)); | ||||
|                 } | ||||
|             } catch (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; | ||||
|         message.channel.send(`Now watching for ${targetCount} user${targetCount > 1 ? 's' : ''} to leave the group.`) | ||||
|   | ||||
| @@ -1,176 +1,278 @@ | ||||
| let targetUserIds = []; | ||||
| let isKickActive = false; | ||||
| let voiceStateHandler = null; | ||||
| let lastKickTime = 0; | ||||
| let consecutiveKicks = 0; | ||||
| let cooldownTime = 0; | ||||
| let checkInterval = null; | ||||
| const { extractUserId } = require('../utils/userUtils'); | ||||
|  | ||||
| const getRandomDelay = () => { | ||||
|     const delay = Math.floor(Math.random() * 250) + 100; | ||||
|     console.log(`[KICKVC] Generated event delay: ${delay}ms`); | ||||
|     return delay; | ||||
| let activeKicks = new Map(); | ||||
| let cooldowns = new Map(); | ||||
| let voiceStateUpdateHandlers = new Map(); | ||||
| 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 delay = Math.floor(Math.random() * 250) + 200; | ||||
|     console.log(`[KICKVC] Generated interval check delay: ${delay}ms`); | ||||
|     return delay; | ||||
| const calculateCooldown = (kickCount) => { | ||||
|     if (kickCount <= 2) return 0; | ||||
|     if (kickCount <= 5) return Math.min((kickCount - 2) * 300, 900); | ||||
|     if (kickCount <= 10) return Math.min(900 + (kickCount - 5) * 400, 2900); | ||||
|     return Math.min(2900 + (kickCount - 10) * 500, 5000); | ||||
| }; | ||||
|  | ||||
| const getCooldown = (kicks) => { | ||||
|     let cooldown; | ||||
|     if (kicks <= 3) cooldown = 200; | ||||
|     else if (kicks <= 5) cooldown = 500; | ||||
|     else if (kicks <= 10) cooldown = 1000; | ||||
|     else cooldown = 2500; | ||||
|     console.log(`[KICKVC] New cooldown calculated for ${kicks} kicks: ${cooldown}ms`); | ||||
|     return cooldown; | ||||
| const getSafetyPause = () => { | ||||
|     if (Math.random() < 0.25) { | ||||
|         return Math.floor(Math.random() * 4000) + 3000; | ||||
|     } | ||||
|     return 0; | ||||
| }; | ||||
|  | ||||
| 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 = { | ||||
|     name: 'kickvc', | ||||
|     description: 'Automatically kicks specified users from voice channels.', | ||||
|     async execute(message, args, deleteTimeout) { | ||||
|         if (args[0]?.toLowerCase() === 'stop') { | ||||
|             if (voiceStateHandler) { | ||||
|                 message.client.removeListener('voiceStateUpdate', voiceStateHandler); | ||||
|                 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)); | ||||
|         if (args.length === 0) { | ||||
|             message.channel.send('Please provide a command: `start <userId(s)>` or `stop <userId or "all">`') | ||||
|                 .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||
|             return; | ||||
|         } | ||||
|         const userIds = args.filter(arg => /^\d{17,19}$/.test(arg)); | ||||
|         if (!userIds.length) { | ||||
|             console.log('[KICKVC] Invalid user IDs provided'); | ||||
|             message.channel.send('Please provide at least one valid user ID.') | ||||
|                 .then(msg => setTimeout(() => msg.delete().catch(console.error), deleteTimeout)); | ||||
|  | ||||
|         const command = args[0].toLowerCase(); | ||||
|  | ||||
|         if (command === 'stop') { | ||||
|             if (args.length < 2) { | ||||
|                 message.channel.send('Please specify a user ID or "all" to stop kicking.') | ||||
|                     .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||
|                 return; | ||||
|             } | ||||
|         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'); | ||||
|  | ||||
|             const target = args[1].toLowerCase(); | ||||
|              | ||||
|             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}`); | ||||
|                 } | ||||
|         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`); | ||||
|                 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; | ||||
|                 } | ||||
|             try { | ||||
|                 const selfMember = await guild.members.fetch(member.client.user.id); | ||||
|                 if (!selfMember.permissions.has("ADMINISTRATOR")) { | ||||
|                     console.log(`[KICKVC] No admin permissions in ${guild.name}, skipping`); | ||||
|  | ||||
|                 if (voiceStateUpdateHandlers.has(userId)) { | ||||
|                     message.client.off('voiceStateUpdate', voiceStateUpdateHandlers.get(userId)); | ||||
|                     activeKicks.delete(userId); | ||||
|                     cooldowns.delete(userId); | ||||
|                     clearInterval(checkIntervals.get(userId)); | ||||
|                     checkIntervals.delete(userId); | ||||
|                     console.log(`[KICKVC] Stopped kicking user: ${userId}`); | ||||
|                      | ||||
|                     message.channel.send(`Stopped kicking user: ${userId}`) | ||||
|                         .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||
|                 } else { | ||||
|                     message.channel.send(`No active kick for user: ${userId}`) | ||||
|                         .then(msg => setTimeout(() => msg.delete().catch(() => {}), deleteTimeout)); | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|                 const delay = fromEvent ? getRandomDelay() : getRandomCheckDelay(); | ||||
|                 console.log(`[KICKVC] Admin check passed in ${guild.name}, waiting ${delay}ms before kick...`); | ||||
|                 await new Promise(resolve => setTimeout(resolve, delay)); | ||||
|                 if (!member.voice.channel) return; | ||||
|                 console.log(`[KICKVC] Target in voice: ${member.user.tag} | ${guild.name} | ${member.voice.channel.name}`); | ||||
|                 await member.voice.disconnect(); | ||||
|                 lastKickTime = currentTime; | ||||
|                 consecutiveKicks++; | ||||
|                 cooldownTime = getCooldown(consecutiveKicks); | ||||
|                 setTimeout(() => { | ||||
|                     if (consecutiveKicks > 0) { | ||||
|                         consecutiveKicks--; | ||||
|                         cooldownTime = getCooldown(consecutiveKicks); | ||||
|         } | ||||
|                 }, 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'); | ||||
|  | ||||
|         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; | ||||
|                 } | ||||
|         }; | ||||
|         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; | ||||
|  | ||||
|                 activeKicks.set(userId, { count: 0, lastKick: 0 }); | ||||
|                 cooldowns.set(userId, 0); | ||||
|  | ||||
|                 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); | ||||
|                         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 { } | ||||
|                 } | ||||
|             } | ||||
|         }, 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)); | ||||
|                         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