268 lines
11 KiB
JavaScript
268 lines
11 KiB
JavaScript
const { extractUserId } = require('../utils/userUtils');
|
|
const { sendCommandResponse } = require('../utils/messageUtils');
|
|
|
|
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 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 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.length === 0) {
|
|
await sendCommandResponse(message, 'Please provide a command: `start <userId(s)>` or `stop <userId or "all">`', deleteTimeout, true);
|
|
return;
|
|
}
|
|
|
|
const command = args[0].toLowerCase();
|
|
|
|
if (command === 'stop') {
|
|
if (args.length < 2) {
|
|
await sendCommandResponse(message, 'Please specify a user ID or "all" to stop kicking.', deleteTimeout, true);
|
|
return;
|
|
}
|
|
|
|
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();
|
|
|
|
await sendCommandResponse(message, 'Stopped all active VC kicks.', deleteTimeout, true);
|
|
return;
|
|
} else {
|
|
const userId = extractUserId(target);
|
|
|
|
if (!userId) {
|
|
await sendCommandResponse(message, 'Invalid user ID.', deleteTimeout, true);
|
|
return;
|
|
}
|
|
|
|
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}`);
|
|
|
|
await sendCommandResponse(message, `Stopped kicking user: ${userId}`, deleteTimeout, true);
|
|
} else {
|
|
await sendCommandResponse(message, `No active kick for user: ${userId}`, deleteTimeout, true);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (command === 'start') {
|
|
if (args.length < 2) {
|
|
await sendCommandResponse(message, 'Please provide at least one user ID to kick.', deleteTimeout, true);
|
|
return;
|
|
}
|
|
|
|
const userIds = args.slice(1)
|
|
.map(arg => extractUserId(arg))
|
|
.filter(id => id !== null);
|
|
|
|
if (userIds.length === 0) {
|
|
await sendCommandResponse(message, 'No valid user IDs provided.', deleteTimeout, true);
|
|
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}`);
|
|
}
|
|
|
|
if (startedKicking.length > 0) {
|
|
await sendCommandResponse(
|
|
message,
|
|
`Started kicking: ${startedKicking.join(', ')}${alreadyKicking.length > 0 ? `\nAlready kicking: ${alreadyKicking.join(', ')}` : ''}`,
|
|
deleteTimeout,
|
|
true
|
|
);
|
|
} else if (alreadyKicking.length > 0) {
|
|
await sendCommandResponse(message, `Already kicking: ${alreadyKicking.join(', ')}`, deleteTimeout, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
await sendCommandResponse(message, 'Unknown command. Use `start <userId(s)>` or `stop <userId or "all">`.', deleteTimeout, true);
|
|
}
|
|
}; |