// =============================== // FULL BOT: League + Ticket + Levels + Giveaway (Slash Commands) // =============================== const { Client, GatewayIntentBits, Partials, ActionRowBuilder, ButtonBuilder, ButtonStyle, PermissionFlagsBits, SlashCommandBuilder, REST, Routes, EmbedBuilder, ChannelType } = require('discord.js'); const fs = require('fs'); const path = require('path'); const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMembers ], partials: [Partials.Channel] }); // ---------- CONFIG ---------- const BOT_TOKEN = 'YOUR_BOT_TOKEN'; const CLIENT_ID = 'YOUR_CLIENT_ID'; const GUILD_ID = 'YOUR_GUILD_ID'; // ---------- Files ---------- const LEAGUES_FILE = path.join(__dirname, 'leagues.json'); const TICKETS_FILE = path.join(__dirname, 'tickets.json'); const LEVELS_FILE = path.join(__dirname, 'levels.json'); const GIVEAWAYS_FILE = path.join(__dirname, 'giveaways.json'); // ---------- Data ---------- let leagues = new Map(); let tickets = new Map(); let levels = new Map(); // odId -> { xp, level, messages } let giveaways = new Map(); // messageId -> giveawayData // ---------- Staff Role IDs ---------- const MOD_ROLE_IDS = [ '1374933460845858867', '1375160502883455026', '1375482795631185931' ]; // ---------- Level Config ---------- const XP_PER_MESSAGE = 15; const XP_COOLDOWN = 60000; // 1 minute const xpCooldowns = new Map(); // ---------- Helpers ---------- function saveLeagues() { fs.writeFileSync(LEAGUES_FILE, JSON.stringify([...leagues.entries()], null, 2)); } function loadLeagues() { if (fs.existsSync(LEAGUES_FILE)) leagues = new Map(JSON.parse(fs.readFileSync(LEAGUES_FILE))); } function saveTickets() { fs.writeFileSync(TICKETS_FILE, JSON.stringify([...tickets.entries()], null, 2)); } function loadTickets() { if (fs.existsSync(TICKETS_FILE)) tickets = new Map(JSON.parse(fs.readFileSync(TICKETS_FILE))); } function saveLevels() { fs.writeFileSync(LEVELS_FILE, JSON.stringify([...levels.entries()], null, 2)); } function loadLevels() { if (fs.existsSync(LEVELS_FILE)) levels = new Map(JSON.parse(fs.readFileSync(LEVELS_FILE))); } function saveGiveaways() { fs.writeFileSync(GIVEAWAYS_FILE, JSON.stringify([...giveaways.entries()], null, 2)); } function loadGiveaways() { if (fs.existsSync(GIVEAWAYS_FILE)) giveaways = new Map(JSON.parse(fs.readFileSync(GIVEAWAYS_FILE))); } function calculateLevel(xp) { return Math.floor(0.1 * Math.sqrt(xp)); } function xpForLevel(level) { return Math.pow(level / 0.1, 2); } // ---------- Slash Commands Definition ---------- const commands = [ // ===== TICKET COMMANDS ===== new SlashCommandBuilder() .setName('ticket') .setDescription('Open a support ticket') .addStringOption(option => option.setName('type') .setDescription('Type of ticket') .addChoices( { name: 'General', value: 'general' }, { name: 'Support', value: 'support' }, { name: 'Ban Player', value: 'ban' }, { name: 'Unban Appeal', value: 'unban' } )), new SlashCommandBuilder().setName('close').setDescription('Close your open ticket'), new SlashCommandBuilder() .setName('add') .setDescription('Add a user to your ticket') .addUserOption(option => option.setName('user').setDescription('User to add').setRequired(true)), new SlashCommandBuilder() .setName('remove') .setDescription('Remove a user from your ticket') .addUserOption(option => option.setName('user').setDescription('User to remove').setRequired(true)), new SlashCommandBuilder().setName('transcript').setDescription('Get a transcript of your ticket'), new SlashCommandBuilder() .setName('panel') .setDescription('Send a ticket panel to a channel') .addChannelOption(option => option.setName('channel').setDescription('Channel to send panel').setRequired(true)), // ===== LEAGUE COMMANDS ===== new SlashCommandBuilder().setName('start').setDescription('Start a new league'), new SlashCommandBuilder().setName('leagues').setDescription('List all active leagues'), new SlashCommandBuilder() .setName('closeleague') .setDescription('Close a league you hosted') .addStringOption(option => option.setName('id').setDescription('League ID to close').setRequired(true)), new SlashCommandBuilder() .setName('leave') .setDescription('Leave a league you joined') .addStringOption(option => option.setName('id').setDescription('League ID to leave').setRequired(true)), new SlashCommandBuilder() .setName('kick') .setDescription('Kick a player from your league (host only)') .addStringOption(option => option.setName('id').setDescription('League ID').setRequired(true)) .addUserOption(option => option.setName('user').setDescription('User to kick').setRequired(true)), new SlashCommandBuilder() .setName('cancel') .setDescription('Cancel league setup (host only)') .addStringOption(option => option.setName('id').setDescription('League ID to cancel').setRequired(true)), // ===== LEVEL COMMANDS ===== new SlashCommandBuilder().setName('rank').setDescription('Check your level and XP') .addUserOption(option => option.setName('user').setDescription('User to check')), new SlashCommandBuilder().setName('leaderboard').setDescription('Show top 10 users by level'), new SlashCommandBuilder() .setName('setlevel') .setDescription('Set a user level (Admin only)') .addUserOption(option => option.setName('user').setDescription('User').setRequired(true)) .addIntegerOption(option => option.setName('level').setDescription('Level to set').setRequired(true)), new SlashCommandBuilder() .setName('addxp') .setDescription('Add XP to a user (Admin only)') .addUserOption(option => option.setName('user').setDescription('User').setRequired(true)) .addIntegerOption(option => option.setName('amount').setDescription('XP amount').setRequired(true)), // ===== GIVEAWAY COMMANDS ===== new SlashCommandBuilder() .setName('giveaway') .setDescription('Start a giveaway') .addStringOption(option => option.setName('prize').setDescription('Prize to give away').setRequired(true)) .addIntegerOption(option => option.setName('duration').setDescription('Duration in minutes').setRequired(true)) .addIntegerOption(option => option.setName('winners').setDescription('Number of winners').setRequired(true)), new SlashCommandBuilder() .setName('gend') .setDescription('End a giveaway early') .addStringOption(option => option.setName('messageid').setDescription('Giveaway message ID').setRequired(true)), new SlashCommandBuilder() .setName('greroll') .setDescription('Reroll giveaway winners') .addStringOption(option => option.setName('messageid').setDescription('Giveaway message ID').setRequired(true)), // ===== UTILITY ===== new SlashCommandBuilder() .setName('say') .setDescription('Make the bot say something') .addStringOption(option => option.setName('message').setDescription('Message to say').setRequired(true)) ].map(command => command.toJSON()); // ---------- Register Commands ---------- async function registerCommands() { const rest = new REST({ version: '10' }).setToken(BOT_TOKEN); try { console.log('šŸ”„ Registering slash commands...'); await rest.put(Routes.applicationCommands(CLIENT_ID), { body: commands }); console.log('āœ… Slash commands registered!'); } catch (error) { console.error('āŒ Error registering commands:', error); } } // ---------- Ready ---------- client.once('ready', async () => { console.log(`āœ… ${client.user.tag} online`); loadLeagues(); loadTickets(); loadLevels(); loadGiveaways(); await registerCommands(); checkGiveaways(); // Check for ended giveaways }); // ---------- XP on Message ---------- client.on('messageCreate', async message => { if (message.author.bot) return; const userId = message.author.id; const now = Date.now(); if (xpCooldowns.has(userId) && now - xpCooldowns.get(userId) < XP_COOLDOWN) return; xpCooldowns.set(userId, now); let userData = levels.get(userId) || { xp: 0, level: 0, messages: 0 }; const oldLevel = userData.level; userData.xp += XP_PER_MESSAGE + Math.floor(Math.random() * 10); userData.messages += 1; userData.level = calculateLevel(userData.xp); levels.set(userId, userData); saveLevels(); if (userData.level > oldLevel) { message.channel.send(`šŸŽ‰ <@${userId}> leveled up to **Level ${userData.level}**!`); } }); // =========================== // SLASH COMMAND HANDLER // =========================== client.on('interactionCreate', async interaction => { if (interaction.isChatInputCommand()) { const { commandName, options, user, guild, channel } = interaction; // ===== TICKET COMMANDS ===== if (commandName === 'ticket') { if (tickets.has(user.id)) return interaction.reply({ content: 'āŒ You already have an open ticket!', ephemeral: true }); const type = options.getString('type') || 'general'; const ticketChannel = await guild.channels.create({ name: `ticket-${user.username}`, type: ChannelType.GuildText, permissionOverwrites: [ { id: guild.id, deny: [PermissionFlagsBits.ViewChannel] }, { id: user.id, allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages] } ] }); tickets.set(user.id, ticketChannel.id); saveTickets(); const mentionString = MOD_ROLE_IDS.map(id => `<@&${id}>`).join(' '); ticketChannel.send(`${mentionString}\nšŸ“© Ticket by **${user.username}** (${type}). Staff will assist shortly!`); return interaction.reply({ content: `āœ… Ticket created: ${ticketChannel}`, ephemeral: true }); } if (commandName === 'close') { const chId = tickets.get(user.id); if (!chId) return interaction.reply({ content: 'āŒ No open ticket found', ephemeral: true }); const ch = await guild.channels.fetch(chId).catch(() => null); if (ch) await ch.delete().catch(() => {}); tickets.delete(user.id); saveTickets(); return interaction.reply({ content: 'āœ… Ticket closed', ephemeral: true }); } if (commandName === 'add') { const chId = tickets.get(user.id); if (!chId) return interaction.reply({ content: 'āŒ No ticket found', ephemeral: true }); const targetUser = options.getUser('user'); const ch = await guild.channels.fetch(chId); await ch.permissionOverwrites.edit(targetUser.id, { ViewChannel: true, SendMessages: true }); return interaction.reply({ content: `āœ… ${targetUser.tag} added to ticket`, ephemeral: true }); } if (commandName === 'remove') { const chId = tickets.get(user.id); if (!chId) return interaction.reply({ content: 'āŒ No ticket found', ephemeral: true }); const targetUser = options.getUser('user'); const ch = await guild.channels.fetch(chId); await ch.permissionOverwrites.edit(targetUser.id, { ViewChannel: false }); return interaction.reply({ content: `āœ… ${targetUser.tag} removed from ticket`, ephemeral: true }); } if (commandName === 'transcript') { const chId = tickets.get(user.id); if (!chId) return interaction.reply({ content: 'āŒ No ticket found', ephemeral: true }); const ch = await guild.channels.fetch(chId); const messages = await ch.messages.fetch({ limit: 1000 }); const content = messages.map(m => `[${m.createdAt.toLocaleString()}] ${m.author.tag}: ${m.content}`).reverse().join('\n'); const filePath = `transcript-${ch.name}.txt`; fs.writeFileSync(filePath, content); await user.send({ content: `šŸ“„ Transcript of ${ch.name}`, files: [filePath] }); fs.unlinkSync(filePath); return interaction.reply({ content: 'āœ… Transcript sent to your DM', ephemeral: true }); } if (commandName === 'panel') { const targetChannel = options.getChannel('channel'); const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId('ticket_support').setLabel('Support').setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId('ticket_ban').setLabel('Ban Player').setStyle(ButtonStyle.Danger), new ButtonBuilder().setCustomId('ticket_unban').setLabel('Unban Appeal').setStyle(ButtonStyle.Secondary) ); await targetChannel.send({ content: 'šŸ“© Click a button to open a ticket', components: [row] }); return interaction.reply({ content: `āœ… Ticket panel sent in ${targetChannel}`, ephemeral: true }); } // ===== LEAGUE COMMANDS ===== if (commandName === 'start') { const leagueId = Date.now().toString(); leagues.set(leagueId, { id: leagueId, host: user.id, mode: null, perks: null, region: null, players: [], max: 0, channelId: null }); saveLeagues(); const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId(`mode_1v1_${leagueId}`).setLabel('1v1').setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId(`mode_2v2_${leagueId}`).setLabel('2v2').setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId(`mode_3v3_${leagueId}`).setLabel('3v3').setStyle(ButtonStyle.Primary) ); return interaction.reply({ content: `šŸŽ® League started by ${user.tag}. Choose mode:`, components: [row] }); } if (commandName === 'leagues') { const activeLeagues = Array.from(leagues.values()).filter(l => l.mode); if (activeLeagues.length === 0) return interaction.reply({ content: 'šŸ“­ No active leagues.', ephemeral: true }); const embed = new EmbedBuilder() .setColor(0x5865F2) .setTitle('šŸ† Active Leagues') .setDescription(activeLeagues.map(l => `**${l.mode}** | Perks: ${l.perks ? 'ON' : 'OFF'} | Region: ${l.region || 'N/A'} | Players: ${l.players.length}/${l.max} | ID: \`${l.id}\``).join('\n')) .setTimestamp(); return interaction.reply({ embeds: [embed] }); } if (commandName === 'closeleague') { const leagueId = options.getString('id'); const league = leagues.get(leagueId); if (!league) return interaction.reply({ content: 'āŒ League not found.', ephemeral: true }); if (league.host !== user.id) return interaction.reply({ content: 'āŒ Only host can close.', ephemeral: true }); if (league.channelId) { const ch = await guild.channels.fetch(league.channelId).catch(() => null); if (ch) await ch.delete().catch(() => {}); } leagues.delete(leagueId); saveLeagues(); return interaction.reply({ content: `āœ… League \`${leagueId}\` closed.`, ephemeral: true }); } if (commandName === 'leave') { const leagueId = options.getString('id'); const league = leagues.get(leagueId); if (!league) return interaction.reply({ content: 'āŒ League not found.', ephemeral: true }); if (!league.players.includes(user.id)) return interaction.reply({ content: 'āŒ You are not in this league.', ephemeral: true }); if (league.host === user.id) return interaction.reply({ content: 'āŒ Host cannot leave. Use /closeleague instead.', ephemeral: true }); league.players = league.players.filter(id => id !== user.id); if (league.channelId) { const ch = await guild.channels.fetch(league.channelId).catch(() => null); if (ch) { await ch.permissionOverwrites.delete(user.id).catch(() => {}); ch.send(`šŸ‘‹ <@${user.id}> left the league.`); } } leagues.set(leagueId, league); saveLeagues(); return interaction.reply({ content: 'āœ… You left the league.', ephemeral: true }); } if (commandName === 'kick') { const leagueId = options.getString('id'); const targetUser = options.getUser('user'); const league = leagues.get(leagueId); if (!league) return interaction.reply({ content: 'āŒ League not found.', ephemeral: true }); if (league.host !== user.id) return interaction.reply({ content: 'āŒ Only host can kick.', ephemeral: true }); if (!league.players.includes(targetUser.id)) return interaction.reply({ content: 'āŒ User not in league.', ephemeral: true }); if (targetUser.id === league.host) return interaction.reply({ content: 'āŒ Cannot kick yourself.', ephemeral: true }); league.players = league.players.filter(id => id !== targetUser.id); if (league.channelId) { const ch = await guild.channels.fetch(league.channelId).catch(() => null); if (ch) { await ch.permissionOverwrites.delete(targetUser.id).catch(() => {}); ch.send(`🚫 <@${targetUser.id}> was kicked from the league.`); } } leagues.set(leagueId, league); saveLeagues(); return interaction.reply({ content: `āœ… Kicked ${targetUser.tag} from the league.`, ephemeral: true }); } if (commandName === 'cancel') { const leagueId = options.getString('id'); const league = leagues.get(leagueId); if (!league) return interaction.reply({ content: 'āŒ League not found.', ephemeral: true }); if (league.host !== user.id) return interaction.reply({ content: 'āŒ Only host can cancel.', ephemeral: true }); if (league.channelId) { const ch = await guild.channels.fetch(league.channelId).catch(() => null); if (ch) await ch.delete().catch(() => {}); } leagues.delete(leagueId); saveLeagues(); return interaction.reply({ content: `āœ… League \`${leagueId}\` cancelled.`, ephemeral: true }); } // ===== LEVEL COMMANDS ===== if (commandName === 'rank') { const targetUser = options.getUser('user') || user; const userData = levels.get(targetUser.id) || { xp: 0, level: 0, messages: 0 }; const nextLevelXp = xpForLevel(userData.level + 1); const progress = Math.floor((userData.xp / nextLevelXp) * 100); const embed = new EmbedBuilder() .setColor(0x5865F2) .setTitle(`šŸ“Š ${targetUser.username}'s Rank`) .setThumbnail(targetUser.displayAvatarURL()) .addFields( { name: 'šŸ† Level', value: `${userData.level}`, inline: true }, { name: '✨ XP', value: `${userData.xp.toLocaleString()}`, inline: true }, { name: 'šŸ’¬ Messages', value: `${userData.messages.toLocaleString()}`, inline: true }, { name: 'šŸ“ˆ Progress', value: `${progress}% to Level ${userData.level + 1}`, inline: false } ) .setTimestamp(); return interaction.reply({ embeds: [embed] }); } if (commandName === 'leaderboard') { const sorted = [...levels.entries()] .sort((a, b) => b[1].xp - a[1].xp) .slice(0, 10); if (sorted.length === 0) return interaction.reply({ content: 'šŸ“­ No data yet.', ephemeral: true }); const description = sorted.map(([userId, data], i) => { const medal = i === 0 ? 'šŸ„‡' : i === 1 ? '🄈' : i === 2 ? 'šŸ„‰' : `**${i + 1}.**`; return `${medal} <@${userId}> - Level ${data.level} (${data.xp.toLocaleString()} XP)`; }).join('\n'); const embed = new EmbedBuilder() .setColor(0xFFD700) .setTitle('šŸ† Leaderboard - Top 10') .setDescription(description) .setTimestamp(); return interaction.reply({ embeds: [embed] }); } if (commandName === 'setlevel') { if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) { return interaction.reply({ content: 'āŒ Admin only.', ephemeral: true }); } const targetUser = options.getUser('user'); const level = options.getInteger('level'); const xp = xpForLevel(level); let userData = levels.get(targetUser.id) || { xp: 0, level: 0, messages: 0 }; userData.level = level; userData.xp = xp; levels.set(targetUser.id, userData); saveLevels(); return interaction.reply({ content: `āœ… Set ${targetUser.tag} to Level ${level}.`, ephemeral: true }); } if (commandName === 'addxp') { if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) { return interaction.reply({ content: 'āŒ Admin only.', ephemeral: true }); } const targetUser = options.getUser('user'); const amount = options.getInteger('amount'); let userData = levels.get(targetUser.id) || { xp: 0, level: 0, messages: 0 }; userData.xp += amount; userData.level = calculateLevel(userData.xp); levels.set(targetUser.id, userData); saveLevels(); return interaction.reply({ content: `āœ… Added ${amount} XP to ${targetUser.tag}. Now Level ${userData.level}.`, ephemeral: true }); } // ===== GIVEAWAY COMMANDS ===== if (commandName === 'giveaway') { const prize = options.getString('prize'); const duration = options.getInteger('duration'); const winners = options.getInteger('winners'); const endTime = Date.now() + duration * 60000; const embed = new EmbedBuilder() .setColor(0xFF69B4) .setTitle('šŸŽ‰ GIVEAWAY šŸŽ‰') .setDescription(`**Prize:** ${prize}\n**Winners:** ${winners}\n**Ends:** \n\nReact with šŸŽ‰ to enter!`) .setFooter({ text: `Hosted by ${user.tag}` }) .setTimestamp(endTime); const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId('giveaway_enter').setLabel('šŸŽ‰ Enter').setStyle(ButtonStyle.Success) ); const msg = await channel.send({ embeds: [embed], components: [row] }); giveaways.set(msg.id, { messageId: msg.id, channelId: channel.id, prize, winners, endTime, host: user.id, participants: [], ended: false }); saveGiveaways(); setTimeout(() => endGiveaway(msg.id, guild), duration * 60000); return interaction.reply({ content: `āœ… Giveaway started for **${prize}**!`, ephemeral: true }); } if (commandName === 'gend') { const messageId = options.getString('messageid'); const giveaway = giveaways.get(messageId); if (!giveaway) return interaction.reply({ content: 'āŒ Giveaway not found.', ephemeral: true }); if (giveaway.ended) return interaction.reply({ content: 'āŒ Already ended.', ephemeral: true }); await endGiveaway(messageId, guild); return interaction.reply({ content: 'āœ… Giveaway ended!', ephemeral: true }); } if (commandName === 'greroll') { const messageId = options.getString('messageid'); const giveaway = giveaways.get(messageId); if (!giveaway) return interaction.reply({ content: 'āŒ Giveaway not found.', ephemeral: true }); if (!giveaway.ended) return interaction.reply({ content: 'āŒ Giveaway not ended yet.', ephemeral: true }); if (giveaway.participants.length === 0) return interaction.reply({ content: 'āŒ No participants.', ephemeral: true }); const newWinners = pickWinners(giveaway.participants, giveaway.winners); const ch = await guild.channels.fetch(giveaway.channelId); ch.send(`šŸŽ‰ **Rerolled!** New winner(s): ${newWinners.map(id => `<@${id}>`).join(', ')} for **${giveaway.prize}**!`); return interaction.reply({ content: 'āœ… Rerolled!', ephemeral: true }); } // ===== UTILITY ===== if (commandName === 'say') { const text = options.getString('message'); await channel.send(text); return interaction.reply({ content: 'āœ… Message sent', ephemeral: true }); } } // ===== BUTTON INTERACTIONS ===== if (interaction.isButton()) { // Giveaway entry if (interaction.customId === 'giveaway_enter') { const giveaway = giveaways.get(interaction.message.id); if (!giveaway) return interaction.reply({ content: 'āŒ Giveaway not found.', ephemeral: true }); if (giveaway.ended) return interaction.reply({ content: 'āŒ Giveaway has ended.', ephemeral: true }); if (giveaway.participants.includes(interaction.user.id)) { return interaction.reply({ content: 'āš ļø You already entered!', ephemeral: true }); } giveaway.participants.push(interaction.user.id); giveaways.set(interaction.message.id, giveaway); saveGiveaways(); return interaction.reply({ content: 'āœ… You entered the giveaway!', ephemeral: true }); } // Ticket buttons if (interaction.customId.startsWith('ticket_')) { const type = interaction.customId.split('_')[1]; if (tickets.has(interaction.user.id)) return interaction.reply({ content: 'āŒ You already have a ticket!', ephemeral: true }); const channel = await interaction.guild.channels.create({ name: `ticket-${interaction.user.username}`, type: ChannelType.GuildText, permissionOverwrites: [ { id: interaction.guild.id, deny: [PermissionFlagsBits.ViewChannel] }, { id: interaction.user.id, allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages] } ] }); tickets.set(interaction.user.id, channel.id); saveTickets(); const mentionString = MOD_ROLE_IDS.map(id => `<@&${id}>`).join(' '); channel.send(`${mentionString}\nšŸ“© Ticket by **${interaction.user.username}** (${type}). Staff will assist shortly!`); return interaction.reply({ content: `āœ… Ticket created: ${channel}`, ephemeral: true }); } // League buttons (mode, perks, region, join) const parts = interaction.customId.split('_'); if (['mode', 'perks', 'region', 'join'].includes(parts[0])) { const [type, value, leagueId] = parts; const league = leagues.get(leagueId); if (!league) return interaction.reply({ content: 'āŒ League does not exist', ephemeral: true }); if (type === 'mode') { if (interaction.user.id !== league.host) return interaction.reply({ content: 'āŒ Only host can choose mode', ephemeral: true }); league.mode = value; league.max = value === '1v1' ? 2 : value === '2v2' ? 4 : 6; leagues.set(leagueId, league); saveLeagues(); const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId(`perks_yes_${leagueId}`).setLabel('Perks').setStyle(ButtonStyle.Success), new ButtonBuilder().setCustomId(`perks_no_${leagueId}`).setLabel('No Perks').setStyle(ButtonStyle.Danger) ); return interaction.update({ content: `Mode **${value}** selected. Choose perks:`, components: [row] }); } if (type === 'perks') { if (interaction.user.id !== league.host) return interaction.reply({ content: 'āŒ Only host can choose perks', ephemeral: true }); league.perks = value === 'yes'; leagues.set(leagueId, league); saveLeagues(); const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId(`region_NA_${leagueId}`).setLabel('NA').setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId(`region_EU_${leagueId}`).setLabel('EU').setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId(`region_ASIA_${leagueId}`).setLabel('ASIA').setStyle(ButtonStyle.Primary) ); return interaction.update({ content: `Perks: **${league.perks ? 'ON' : 'OFF'}**. Choose region:`, components: [row] }); } if (type === 'region') { if (interaction.user.id !== league.host) return interaction.reply({ content: 'āŒ Only host can choose region', ephemeral: true }); league.region = value; leagues.set(leagueId, league); saveLeagues(); const embed = new EmbedBuilder() .setColor(0x5865F2) .setTitle(`šŸŽ® ${league.mode} League - ${league.region}`) .addFields( { name: 'Mode', value: league.mode, inline: true }, { name: 'Perks', value: league.perks ? 'ON' : 'OFF', inline: true }, { name: 'Region', value: league.region, inline: true }, { name: 'Players', value: `${league.players.length}/${league.max}`, inline: true }, { name: 'Host', value: `<@${league.host}>`, inline: true }, { name: 'ID', value: league.id, inline: true } ); const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId(`join_${leagueId}`).setLabel('Join League').setStyle(ButtonStyle.Success) ); return interaction.update({ content: null, embeds: [embed], components: [row] }); } if (type === 'join') { if (league.players.includes(interaction.user.id)) return interaction.reply({ content: 'āŒ Already in league', ephemeral: true }); if (league.players.length >= league.max) return interaction.reply({ content: 'āŒ League is full', ephemeral: true }); league.players.push(interaction.user.id); if (!league.channelId) { const ch = await interaction.guild.channels.create({ name: `league-${league.mode}-${leagueId.slice(-6)}`, type: ChannelType.GuildText, permissionOverwrites: [ { id: interaction.guild.id, deny: [PermissionFlagsBits.ViewChannel] }, { id: interaction.user.id, allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages] }, { id: league.host, allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages] } ] }); league.channelId = ch.id; ch.send(`šŸŽ® League channel! Players: <@${league.host}>, <@${interaction.user.id}>`); } else { const ch = await interaction.guild.channels.fetch(league.channelId); await ch.permissionOverwrites.edit(interaction.user.id, { ViewChannel: true, SendMessages: true }); ch.send(`āœ… <@${interaction.user.id}> joined!`); } leagues.set(leagueId, league); saveLeagues(); const embed = new EmbedBuilder() .setColor(0x5865F2) .setTitle(`šŸŽ® ${league.mode} League - ${league.region}`) .addFields( { name: 'Mode', value: league.mode, inline: true }, { name: 'Perks', value: league.perks ? 'ON' : 'OFF', inline: true }, { name: 'Region', value: league.region, inline: true }, { name: 'Players', value: `${league.players.length}/${league.max}`, inline: true }, { name: 'Host', value: `<@${league.host}>`, inline: true }, { name: 'ID', value: league.id, inline: true } ); const row = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId(`join_${leagueId}`) .setLabel(league.players.length >= league.max ? 'League Full' : 'Join League') .setStyle(league.players.length >= league.max ? ButtonStyle.Secondary : ButtonStyle.Success) .setDisabled(league.players.length >= league.max) ); await interaction.update({ embeds: [embed], components: [row] }); if (league.players.length >= league.max) { const ch = await interaction.guild.channels.fetch(league.channelId); if (ch) ch.send('šŸŽ‰ **League FULL!** Game can start!'); } } } } }); // ---------- Giveaway Helpers ---------- function pickWinners(participants, count) { const shuffled = [...participants].sort(() => 0.5 - Math.random()); return shuffled.slice(0, Math.min(count, shuffled.length)); } async function endGiveaway(messageId, guild) { const giveaway = giveaways.get(messageId); if (!giveaway || giveaway.ended) return; giveaway.ended = true; giveaways.set(messageId, giveaway); saveGiveaways(); const channel = await guild.channels.fetch(giveaway.channelId).catch(() => null); if (!channel) return; const message = await channel.messages.fetch(messageId).catch(() => null); if (!message) return; if (giveaway.participants.length === 0) { const embed = EmbedBuilder.from(message.embeds[0]) .setDescription(`**Prize:** ${giveaway.prize}\n\nāŒ No participants!`) .setColor(0xFF0000); await message.edit({ embeds: [embed], components: [] }); channel.send('😢 Giveaway ended with no participants.'); return; } const winners = pickWinners(giveaway.participants, giveaway.winners); const embed = EmbedBuilder.from(message.embeds[0]) .setDescription(`**Prize:** ${giveaway.prize}\n\nšŸŽ‰ **Winners:** ${winners.map(id => `<@${id}>`).join(', ')}`) .setColor(0x00FF00); await message.edit({ embeds: [embed], components: [] }); channel.send(`šŸŽ‰ Congratulations ${winners.map(id => `<@${id}>`).join(', ')}! You won **${giveaway.prize}**!`); } function checkGiveaways() { const now = Date.now(); giveaways.forEach(async (giveaway, messageId) => { if (!giveaway.ended && giveaway.endTime <= now) { const guild = client.guilds.cache.first(); if (guild) await endGiveaway(messageId, guild); } }); } // =========================== // LOGIN // =========================== client.login(BOT_TOKEN);