diff --git a/commands/delete.js b/commands/delete.js index aa3e949..e2615f4 100644 --- a/commands/delete.js +++ b/commands/delete.js @@ -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); diff --git a/commands/groupadd.js b/commands/groupadd.js index 0654a28..68479b4 100644 --- a/commands/groupadd.js +++ b/commands/groupadd.js @@ -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; + + const isRecentlyAdded = recentAdds.has(user.id); + const failCount = failedAttempts.get(user.id) || 0; - if (timeSinceLastAdd < 1000) { - console.log(`[GROUPADD] Rate limiting for ${user.id}, waiting...`); - await new Promise(resolve => setTimeout(resolve, 1000 - timeSinceLastAdd)); + 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)); + } + + 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(); - console.log(`[GROUPADD] User ${user.id} left, waiting ${delay}ms before re-adding`); - - 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.`) diff --git a/commands/kickvc.js b/commands/kickvc.js index 9a9f489..9984473 100644 --- a/commands/kickvc.js +++ b/commands/kickvc.js @@ -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)); - 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'); - } - 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`); + + 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; } - 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`); + + 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}`); + } + 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; } - 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 (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; } - }; - 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)); + } }; \ No newline at end of file diff --git a/commands/ssh.js b/commands/ssh.js deleted file mode 100644 index 3fefa00..0000000 --- a/commands/ssh.js +++ /dev/null @@ -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)); - } - }, -}; \ No newline at end of file