const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); const { getServerMode, EMBED_COLORS } = require('../utils/modeHelper'); module.exports = { data: new SlashCommandBuilder() .setName('federation') .setDescription('AeThex Federation - Global protection & cross-server network') .addSubcommandGroup(group => group .setName('roles') .setDescription('Manage cross-server role synchronization') .addSubcommand(sub => sub.setName('link').setDescription('Link a role for cross-server sync') .addRoleOption(opt => opt.setName('role').setDescription('Role to sync').setRequired(true))) .addSubcommand(sub => sub.setName('unlink').setDescription('Remove a role from sync') .addRoleOption(opt => opt.setName('role').setDescription('Role to remove').setRequired(true))) .addSubcommand(sub => sub.setName('list').setDescription('List all linked roles')) ) .addSubcommandGroup(group => group .setName('bans') .setDescription('Global Ban List - Protect the network') .addSubcommand(sub => sub.setName('add').setDescription('Add user to global ban list') .addUserOption(opt => opt.setName('user').setDescription('User to ban').setRequired(true)) .addStringOption(opt => opt.setName('reason').setDescription('Reason for ban').setRequired(true)) .addStringOption(opt => opt.setName('severity').setDescription('Severity level') .addChoices( { name: 'Low - Minor offense', value: 'low' }, { name: 'Medium - Moderate offense', value: 'medium' }, { name: 'High - Serious offense', value: 'high' }, { name: 'Critical - Nuker/Scammer', value: 'critical' } ))) .addSubcommand(sub => sub.setName('lookup').setDescription('Check if user is on global ban list') .addUserOption(opt => opt.setName('user').setDescription('User to lookup').setRequired(true))) .addSubcommand(sub => sub.setName('list').setDescription('View recent global bans')) .addSubcommand(sub => sub.setName('remove').setDescription('Remove user from global ban list') .addUserOption(opt => opt.setName('user').setDescription('User to remove').setRequired(true))) ) .addSubcommandGroup(group => group .setName('servers') .setDescription('Federation Server Directory') .addSubcommand(sub => sub.setName('directory').setDescription('Browse all federation servers')) .addSubcommand(sub => sub.setName('info').setDescription('View a specific server') .addStringOption(opt => opt.setName('server').setDescription('Server name or ID').setRequired(true))) .addSubcommand(sub => sub.setName('featured').setDescription('View featured servers')) ) .addSubcommandGroup(group => group .setName('membership') .setDescription('Join or manage federation membership') .addSubcommand(sub => sub.setName('apply').setDescription('Apply to join the federation') .addStringOption(opt => opt.setName('category').setDescription('Server category') .addChoices( { name: 'Gaming', value: 'gaming' }, { name: 'Creative', value: 'creative' }, { name: 'Development', value: 'development' }, { name: 'Education', value: 'education' }, { name: 'Community', value: 'community' }, { name: 'Business', value: 'business' } ).setRequired(true)) .addStringOption(opt => opt.setName('description').setDescription('Brief description of your server').setRequired(true))) .addSubcommand(sub => sub.setName('status').setDescription('Check your server\'s federation status')) .addSubcommand(sub => sub.setName('treaty').setDescription('View the Federation Treaty')) ) .addSubcommandGroup(group => group .setName('scouts') .setDescription('Talent Scout - Cross-server reputation') .addSubcommand(sub => sub.setName('leaderboard').setDescription('View global reputation leaderboard')) .addSubcommand(sub => sub.setName('profile').setDescription('View cross-server profile') .addUserOption(opt => opt.setName('user').setDescription('User to lookup'))) ) .addSubcommandGroup(group => group .setName('partners') .setDescription('Server partnership management') .addSubcommand(sub => sub.setName('request').setDescription('Send a partnership request to another server') .addStringOption(opt => opt.setName('server_id').setDescription('Server ID to partner with').setRequired(true)) .addStringOption(opt => opt.setName('message').setDescription('Partnership proposal message').setRequired(true).setMaxLength(500))) .addSubcommand(sub => sub.setName('list').setDescription('View your server partnerships')) .addSubcommand(sub => sub.setName('pending').setDescription('View pending partnership requests')) .addSubcommand(sub => sub.setName('accept').setDescription('Accept a partnership request') .addStringOption(opt => opt.setName('request_id').setDescription('Request ID to accept').setRequired(true))) .addSubcommand(sub => sub.setName('reject').setDescription('Reject a partnership request') .addStringOption(opt => opt.setName('request_id').setDescription('Request ID to reject').setRequired(true)) .addStringOption(opt => opt.setName('reason').setDescription('Rejection reason'))) .addSubcommand(sub => sub.setName('end').setDescription('End an existing partnership') .addStringOption(opt => opt.setName('partner_id').setDescription('Partner server ID').setRequired(true))) ) .addSubcommandGroup(group => group .setName('events') .setDescription('Cross-server events') .addSubcommand(sub => sub.setName('create').setDescription('Create a federation-wide event') .addStringOption(opt => opt.setName('name').setDescription('Event name').setRequired(true).setMaxLength(100)) .addStringOption(opt => opt.setName('description').setDescription('Event description').setRequired(true).setMaxLength(500)) .addStringOption(opt => opt.setName('type').setDescription('Event type') .addChoices( { name: 'Competition', value: 'competition' }, { name: 'Collaboration', value: 'collaboration' }, { name: 'Social', value: 'social' }, { name: 'Gaming', value: 'gaming' }, { name: 'Learning', value: 'learning' } ).setRequired(true)) .addStringOption(opt => opt.setName('start_date').setDescription('Start date (YYYY-MM-DD)').setRequired(true)) .addStringOption(opt => opt.setName('end_date').setDescription('End date (YYYY-MM-DD)').setRequired(true)) .addIntegerOption(opt => opt.setName('xp_reward').setDescription('XP reward for participants').setMinValue(0).setMaxValue(10000))) .addSubcommand(sub => sub.setName('list').setDescription('View active federation events')) .addSubcommand(sub => sub.setName('info').setDescription('View details of a specific event') .addStringOption(opt => opt.setName('event_id').setDescription('Event ID').setRequired(true))) .addSubcommand(sub => sub.setName('join').setDescription('Join your server to an event') .addStringOption(opt => opt.setName('event_id').setDescription('Event ID').setRequired(true))) .addSubcommand(sub => sub.setName('leave').setDescription('Leave an event') .addStringOption(opt => opt.setName('event_id').setDescription('Event ID').setRequired(true))) .addSubcommand(sub => sub.setName('cancel').setDescription('Cancel an event you created') .addStringOption(opt => opt.setName('event_id').setDescription('Event ID').setRequired(true))) ), async execute(interaction, supabase, client) { const mode = await getServerMode(supabase, interaction.guildId); const group = interaction.options.getSubcommandGroup(); const subcommand = interaction.options.getSubcommand(); if (mode === 'standalone' && group !== 'membership') { const embed = new EmbedBuilder() .setColor(EMBED_COLORS.standalone) .setTitle('Standalone Mode') .setDescription('Federation features are disabled in standalone mode.\n\nUse `/federation membership apply` to join the network, or `/config mode` to switch to federated mode.'); return interaction.reply({ embeds: [embed], ephemeral: true }); } if (group === 'roles') { await handleRoles(interaction, supabase, client, subcommand); } else if (group === 'bans') { await handleBans(interaction, supabase, client, subcommand); } else if (group === 'servers') { await handleServers(interaction, supabase, client, subcommand); } else if (group === 'membership') { await handleMembership(interaction, supabase, client, subcommand); } else if (group === 'scouts') { await handleScouts(interaction, supabase, client, subcommand); } else if (group === 'partners') { await handlePartners(interaction, supabase, client, subcommand); } else if (group === 'events') { await handleEvents(interaction, supabase, client, subcommand); } }, }; async function handleRoles(interaction, supabase, client, subcommand) { if (subcommand === 'link') { if (!interaction.member.permissions.has(PermissionFlagsBits.ManageRoles)) { return interaction.reply({ content: 'You need Manage Roles permission.', ephemeral: true }); } const role = interaction.options.getRole('role'); const mappingData = { name: role.name, guildId: interaction.guildId, guildName: interaction.guild.name, linkedAt: Date.now(), }; client.federationMappings.set(role.id, mappingData); if (client.saveFederationMapping) { await client.saveFederationMapping(role.id, mappingData); } const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('Role Linked') .setDescription(`${role} is now linked for federation sync.`) .addFields( { name: 'Role ID', value: role.id, inline: true }, { name: 'Server', value: interaction.guild.name, inline: true } ) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'unlink') { if (!interaction.member.permissions.has(PermissionFlagsBits.ManageRoles)) { return interaction.reply({ content: 'You need Manage Roles permission.', ephemeral: true }); } const role = interaction.options.getRole('role'); if (client.federationMappings.has(role.id)) { client.federationMappings.delete(role.id); if (client.deleteFederationMapping) { await client.deleteFederationMapping(role.id); } const embed = new EmbedBuilder() .setColor(0xff6600) .setTitle('Role Unlinked') .setDescription(`${role} has been removed from federation sync.`) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } else { await interaction.reply({ content: `${role} is not currently linked.`, ephemeral: true }); } } if (subcommand === 'list') { const mappings = [...client.federationMappings.entries()]; if (mappings.length === 0) { const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Federation Roles') .setDescription('No roles are currently linked.\nUse `/federation roles link` to add roles.') .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const roleList = mappings.map(([roleId, data]) => `<@&${roleId}> - ${data.guildName}` ).join('\n'); const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Federation Roles') .setDescription(roleList) .setFooter({ text: `${mappings.length} role(s) linked` }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } } async function handleBans(interaction, supabase, client, subcommand) { if (!supabase) { return interaction.reply({ content: 'Database not available.', ephemeral: true }); } if (subcommand === 'add') { if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers)) { return interaction.reply({ content: 'You need Ban Members permission.', ephemeral: true }); } const user = interaction.options.getUser('user'); const reason = interaction.options.getString('reason'); const severity = interaction.options.getString('severity') || 'medium'; const { data: existing } = await supabase .from('federation_bans') .select('id') .eq('user_id', user.id) .eq('active', true) .maybeSingle(); if (existing) { return interaction.reply({ content: `${user.tag} is already on the global ban list.`, ephemeral: true }); } const { error } = await supabase.from('federation_bans').insert({ user_id: user.id, username: user.tag, reason, severity, banned_by_guild_id: interaction.guildId, banned_by_user_id: interaction.user.id, }); if (error) { console.error('Federation ban error:', error); return interaction.reply({ content: 'Failed to add ban.', ephemeral: true }); } const severityColors = { low: 0xffff00, medium: 0xff9900, high: 0xff3300, critical: 0xff0000 }; const severityEmojis = { low: '⚠️', medium: '🔶', high: '🔴', critical: '☠️' }; const embed = new EmbedBuilder() .setColor(severityColors[severity]) .setTitle(`${severityEmojis[severity]} Global Ban Added`) .setThumbnail(user.displayAvatarURL()) .addFields( { name: 'User', value: `${user.tag}\n\`${user.id}\``, inline: true }, { name: 'Severity', value: severity.toUpperCase(), inline: true }, { name: 'Reason', value: reason } ) .setFooter({ text: `Added by ${interaction.user.tag}` }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); await createBanAlerts(supabase, client, user.id, severity); } if (subcommand === 'lookup') { const user = interaction.options.getUser('user'); const { data: ban } = await supabase .from('federation_bans') .select('*') .eq('user_id', user.id) .eq('active', true) .maybeSingle(); if (!ban) { const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('User Clear') .setThumbnail(user.displayAvatarURL()) .setDescription(`${user.tag} is **not** on the global ban list.`) .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const severityColors = { low: 0xffff00, medium: 0xff9900, high: 0xff3300, critical: 0xff0000 }; const embed = new EmbedBuilder() .setColor(severityColors[ban.severity]) .setTitle('User Flagged') .setThumbnail(user.displayAvatarURL()) .addFields( { name: 'User', value: `${user.tag}\n\`${user.id}\``, inline: true }, { name: 'Severity', value: ban.severity.toUpperCase(), inline: true }, { name: 'Reason', value: ban.reason }, { name: 'Banned On', value: new Date(ban.created_at).toLocaleDateString(), inline: true } ) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'list') { const { data: bans } = await supabase .from('federation_bans') .select('*') .eq('active', true) .order('created_at', { ascending: false }) .limit(10); if (!bans || bans.length === 0) { return interaction.reply({ content: 'No active global bans.', ephemeral: true }); } const severityEmojis = { low: '⚠️', medium: '🔶', high: '🔴', critical: '☠️' }; const banList = bans.map(b => `${severityEmojis[b.severity]} **${b.username || b.user_id}** - ${b.reason.substring(0, 50)}${b.reason.length > 50 ? '...' : ''}` ).join('\n'); const embed = new EmbedBuilder() .setColor(0xff0000) .setTitle('Global Ban List') .setDescription(banList) .setFooter({ text: `Showing ${bans.length} most recent bans` }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'remove') { if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) { return interaction.reply({ content: 'You need Administrator permission.', ephemeral: true }); } const user = interaction.options.getUser('user'); const { error } = await supabase .from('federation_bans') .update({ active: false, updated_at: new Date().toISOString() }) .eq('user_id', user.id) .eq('active', true); if (error) { return interaction.reply({ content: 'Failed to remove ban.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('Ban Removed') .setDescription(`${user.tag} has been removed from the global ban list.`) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } } async function handleServers(interaction, supabase, client, subcommand) { if (!supabase) { return interaction.reply({ content: 'Database not available.', ephemeral: true }); } if (subcommand === 'directory') { const { data: servers } = await supabase .from('federation_servers') .select('*') .eq('status', 'approved') .order('member_count', { ascending: false }) .limit(15); if (!servers || servers.length === 0) { const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Federation Directory') .setDescription('No servers in the federation yet.\nUse `/federation membership apply` to be the first!') .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const categoryEmojis = { gaming: '🎮', creative: '🎨', development: '💻', education: '📚', community: '👥', business: '🏢' }; const serverList = servers.map((s, i) => { const emoji = categoryEmojis[s.category] || '🌐'; const featured = s.featured ? '⭐' : ''; return `${i + 1}. ${emoji} **${s.guild_name}** ${featured}\n ${s.member_count?.toLocaleString() || '?'} members • ${s.category || 'general'}`; }).join('\n\n'); const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Federation Directory') .setDescription(serverList) .setFooter({ text: `${servers.length} servers in the federation` }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'info') { const serverQuery = interaction.options.getString('server'); const { data: server } = await supabase .from('federation_servers') .select('*') .or(`guild_id.eq.${serverQuery},guild_name.ilike.%${serverQuery}%`) .eq('status', 'approved') .maybeSingle(); if (!server) { return interaction.reply({ content: 'Server not found in the federation.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle(server.guild_name) .setDescription(server.description || 'No description provided.') .addFields( { name: 'Members', value: server.member_count?.toLocaleString() || 'Unknown', inline: true }, { name: 'Category', value: server.category || 'General', inline: true }, { name: 'Tier', value: server.tier?.toUpperCase() || 'FREE', inline: true }, { name: 'Joined Federation', value: new Date(server.joined_at).toLocaleDateString(), inline: true } ) .setTimestamp(); if (server.guild_icon) { embed.setThumbnail(`https://cdn.discordapp.com/icons/${server.guild_id}/${server.guild_icon}.png`); } await interaction.reply({ embeds: [embed] }); } if (subcommand === 'featured') { const { data: featured } = await supabase .from('federation_servers') .select('*') .eq('featured', true) .eq('status', 'approved'); if (!featured || featured.length === 0) { return interaction.reply({ content: 'No featured servers at this time.', ephemeral: true }); } const serverList = featured.map(s => `⭐ **${s.guild_name}**\n${s.description?.substring(0, 100) || 'No description'}${s.description?.length > 100 ? '...' : ''}` ).join('\n\n'); const embed = new EmbedBuilder() .setColor(0xffd700) .setTitle('Featured Servers') .setDescription(serverList) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } } async function handleMembership(interaction, supabase, client, subcommand) { if (!supabase) { return interaction.reply({ content: 'Database not available.', ephemeral: true }); } if (subcommand === 'apply') { if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) { return interaction.reply({ content: 'Only administrators can apply to the federation.', ephemeral: true }); } const { data: existing } = await supabase .from('federation_servers') .select('status') .eq('guild_id', interaction.guildId) .maybeSingle(); if (existing) { return interaction.reply({ content: `Your server is already ${existing.status} in the federation.`, ephemeral: true }); } const { data: pendingApp } = await supabase .from('federation_applications') .select('status') .eq('guild_id', interaction.guildId) .maybeSingle(); if (pendingApp) { return interaction.reply({ content: `Application already ${pendingApp.status}. Use /federation membership status to check.`, ephemeral: true }); } const category = interaction.options.getString('category'); const description = interaction.options.getString('description'); const { error } = await supabase.from('federation_applications').insert({ guild_id: interaction.guildId, guild_name: interaction.guild.name, guild_icon: interaction.guild.icon, member_count: interaction.guild.memberCount, category, description, admin_id: interaction.user.id, admin_username: interaction.user.tag, treaty_agreed: true, }); if (error) { console.error('Federation application error:', error); return interaction.reply({ content: 'Failed to submit application.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('Application Submitted') .setDescription('Your server has been submitted for federation membership!') .addFields( { name: 'Server', value: interaction.guild.name, inline: true }, { name: 'Category', value: category, inline: true }, { name: 'Status', value: 'Pending Review', inline: true } ) .setFooter({ text: 'You will be notified when your application is reviewed.' }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'status') { const { data: server } = await supabase .from('federation_servers') .select('*') .eq('guild_id', interaction.guildId) .maybeSingle(); if (server) { const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('Federation Member') .addFields( { name: 'Status', value: server.status.toUpperCase(), inline: true }, { name: 'Tier', value: server.tier?.toUpperCase() || 'FREE', inline: true }, { name: 'Joined', value: new Date(server.joined_at).toLocaleDateString(), inline: true } ) .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const { data: application } = await supabase .from('federation_applications') .select('*') .eq('guild_id', interaction.guildId) .maybeSingle(); if (application) { const statusColors = { pending: 0xffff00, approved: 0x00ff00, rejected: 0xff0000 }; const embed = new EmbedBuilder() .setColor(statusColors[application.status] || 0x7c3aed) .setTitle('Application Status') .addFields( { name: 'Status', value: application.status.toUpperCase(), inline: true }, { name: 'Submitted', value: new Date(application.created_at).toLocaleDateString(), inline: true } ) .setTimestamp(); if (application.rejection_reason) { embed.addFields({ name: 'Reason', value: application.rejection_reason }); } return interaction.reply({ embeds: [embed] }); } const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Not a Member') .setDescription('This server is not in the federation.\nUse `/federation membership apply` to join!') .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'treaty') { const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('The AeThex Federation Treaty') .setDescription('By joining the Federation, your server agrees to:') .addFields( { name: '1. Contribute to Global Safety', value: 'Report nukers, scammers, and bad actors to the Global Ban List.' }, { name: '2. Maintain Standards', value: 'Uphold basic moderation and community guidelines.' }, { name: '3. Respect the Network', value: 'Do not exploit federation features or share protected data.' }, { name: '4. Display Membership', value: 'Optionally display Federation badge to verify authenticity.' } ) .setFooter({ text: 'Together, we are untouchable.' }) .setTimestamp(); const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId('federation_agree_treaty') .setLabel('I Agree') .setStyle(ButtonStyle.Success), new ButtonBuilder() .setLabel('Learn More') .setStyle(ButtonStyle.Link) .setURL('https://aethex.dev/federation') ); await interaction.reply({ embeds: [embed], components: [row] }); } } async function handleScouts(interaction, supabase, client, subcommand) { if (!supabase) { return interaction.reply({ content: 'Database not available.', ephemeral: true }); } if (subcommand === 'leaderboard') { const { data: leaders } = await supabase .from('federation_reputation') .select('*') .order('reputation_score', { ascending: false }) .limit(15); if (!leaders || leaders.length === 0) { const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Federation Leaderboard') .setDescription('No reputation data yet. Be active across federation servers to appear here!') .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const tierEmojis = { newcomer: '🌱', member: '⭐', veteran: '🏆', elite: '💎', legend: '👑' }; const leaderList = leaders.map((l, i) => { const emoji = tierEmojis[l.rank_tier] || '🌱'; const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `${i + 1}.`; return `${medal} ${emoji} <@${l.discord_id}> - **${l.reputation_score?.toLocaleString() || 0}** rep`; }).join('\n'); const embed = new EmbedBuilder() .setColor(0xffd700) .setTitle('Federation Leaderboard') .setDescription(leaderList) .setFooter({ text: 'Reputation earned across all federation servers' }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'profile') { const user = interaction.options.getUser('user') || interaction.user; const { data: rep } = await supabase .from('federation_reputation') .select('*') .eq('discord_id', user.id) .maybeSingle(); if (!rep) { const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('No Federation Profile') .setDescription(`${user.tag} doesn't have a federation profile yet.\nBe active across federation servers to build reputation!`) .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const tierEmojis = { newcomer: '🌱', member: '⭐', veteran: '🏆', elite: '💎', legend: '👑' }; const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle(`${user.tag}'s Federation Profile`) .setThumbnail(user.displayAvatarURL()) .addFields( { name: 'Reputation', value: rep.reputation_score?.toLocaleString() || '0', inline: true }, { name: 'Rank', value: `${tierEmojis[rep.rank_tier] || '🌱'} ${rep.rank_tier?.toUpperCase() || 'NEWCOMER'}`, inline: true }, { name: 'Active In', value: `${rep.servers_active_in || 0} servers`, inline: true }, { name: 'Total XP', value: rep.total_xp?.toLocaleString() || '0', inline: true }, { name: 'Highest Level', value: `${rep.highest_level || 0}`, inline: true }, { name: 'Prestige', value: `${rep.prestige_total || 0}`, inline: true } ) .setFooter({ text: `Last active: ${rep.last_active ? new Date(rep.last_active).toLocaleDateString() : 'Unknown'}` }) .setTimestamp(); if (rep.badges && rep.badges.length > 0) { embed.addFields({ name: 'Badges', value: rep.badges.join(' ') }); } await interaction.reply({ embeds: [embed] }); } } async function createBanAlerts(supabase, client, userId, severity) { try { const { data: servers } = await supabase .from('federation_servers') .select('guild_id, tier') .eq('status', 'approved'); if (!servers) return; const alerts = servers .filter(s => severity === 'critical' || s.tier === 'premium') .map(s => ({ guild_id: s.guild_id, alert_type: 'new_ban', })); if (alerts.length > 0) { const { data: ban } = await supabase .from('federation_bans') .select('id') .eq('user_id', userId) .eq('active', true) .maybeSingle(); if (ban) { const alertsWithBanId = alerts.map(a => ({ ...a, ban_id: ban.id })); await supabase.from('federation_alerts').insert(alertsWithBanId); } } } catch (err) { console.error('Error creating ban alerts:', err); } } async function notifyPartnerServer(client, supabase, targetGuildId, embed) { try { const targetGuild = client.guilds.cache.get(targetGuildId); if (!targetGuild) return; const { data: config } = await supabase .from('server_config') .select('mod_log_channel') .eq('guild_id', targetGuildId) .maybeSingle(); if (config?.mod_log_channel) { const channel = targetGuild.channels.cache.get(config.mod_log_channel); if (channel) { await channel.send({ embeds: [embed] }); return; } } const systemChannel = targetGuild.systemChannel; if (systemChannel) { await systemChannel.send({ embeds: [embed] }); } } catch (err) { console.error('Failed to notify partner server:', err); } } async function handlePartners(interaction, supabase, client, subcommand) { if (!supabase) { return interaction.reply({ content: 'Database not available.', ephemeral: true }); } if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) { return interaction.reply({ content: 'Only administrators can manage partnerships.', ephemeral: true }); } if (subcommand === 'request') { const targetServerId = interaction.options.getString('server_id'); const message = interaction.options.getString('message'); if (targetServerId === interaction.guildId) { return interaction.reply({ content: 'You cannot partner with your own server.', ephemeral: true }); } const { data: targetServer } = await supabase .from('federation_servers') .select('guild_id, guild_name') .eq('guild_id', targetServerId) .eq('status', 'approved') .maybeSingle(); if (!targetServer) { return interaction.reply({ content: 'Target server is not in the federation or not found.', ephemeral: true }); } const { data: existing } = await supabase .from('server_partnerships') .select('id, status') .or(`and(requester_guild_id.eq.${interaction.guildId},target_guild_id.eq.${targetServerId}),and(requester_guild_id.eq.${targetServerId},target_guild_id.eq.${interaction.guildId})`) .in('status', ['pending', 'accepted']) .maybeSingle(); if (existing) { return interaction.reply({ content: `A partnership ${existing.status === 'accepted' ? 'already exists' : 'request is pending'} with this server.`, ephemeral: true }); } const { error } = await supabase.from('server_partnerships').insert({ requester_guild_id: interaction.guildId, requester_guild_name: interaction.guild.name, target_guild_id: targetServerId, target_guild_name: targetServer.guild_name, message, status: 'pending', }); if (error) { console.error('Partnership request error:', error); return interaction.reply({ content: 'Failed to send partnership request.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('Partnership Request Sent') .setDescription(`Your partnership request has been sent to **${targetServer.guild_name}**!`) .addFields( { name: 'Your Message', value: message }, { name: 'Status', value: 'Pending', inline: true } ) .setFooter({ text: 'They will be notified of your request.' }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'list') { const { data: partnerships } = await supabase .from('server_partnerships') .select('*') .eq('status', 'accepted') .or(`requester_guild_id.eq.${interaction.guildId},target_guild_id.eq.${interaction.guildId}`) .order('accepted_at', { ascending: false }); if (!partnerships || partnerships.length === 0) { const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Server Partnerships') .setDescription('No active partnerships.\nUse `/federation partners request` to request one!') .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const partnerList = partnerships.map((p, i) => { const partnerName = p.requester_guild_id === interaction.guildId ? p.target_guild_name : p.requester_guild_name; const partnerId = p.requester_guild_id === interaction.guildId ? p.target_guild_id : p.requester_guild_id; const since = p.accepted_at ? new Date(p.accepted_at).toLocaleDateString() : 'Unknown'; return `${i + 1}. **${partnerName}** (ID: \`${partnerId}\`)\n Since: ${since}`; }).join('\n\n'); const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Server Partnerships') .setDescription(partnerList) .setFooter({ text: `${partnerships.length} active partnership(s)` }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'pending') { const { data: pending } = await supabase .from('server_partnerships') .select('*') .eq('target_guild_id', interaction.guildId) .eq('status', 'pending') .order('created_at', { ascending: false }); if (!pending || pending.length === 0) { const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Pending Partnership Requests') .setDescription('No pending partnership requests.') .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const pendingList = pending.map(p => { const date = new Date(p.created_at).toLocaleDateString(); return `**Request #${p.id}** from **${p.requester_guild_name}**\n` + `> ${p.message?.substring(0, 100) || 'No message'}${p.message?.length > 100 ? '...' : ''}\n` + `Received: ${date}`; }).join('\n\n'); const embed = new EmbedBuilder() .setColor(0xffff00) .setTitle('Pending Partnership Requests') .setDescription(pendingList) .addFields({ name: 'Actions', value: '`/federation partners accept ` - Accept a request\n' + '`/federation partners reject ` - Reject a request' }) .setFooter({ text: `${pending.length} pending request(s)` }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'accept') { const requestId = interaction.options.getString('request_id'); const { data: request } = await supabase .from('server_partnerships') .select('*') .eq('id', requestId) .eq('target_guild_id', interaction.guildId) .eq('status', 'pending') .maybeSingle(); if (!request) { return interaction.reply({ content: 'Partnership request not found or not pending.', ephemeral: true }); } const { error } = await supabase .from('server_partnerships') .update({ status: 'accepted', accepted_at: new Date().toISOString(), updated_at: new Date().toISOString() }) .eq('id', requestId); if (error) { return interaction.reply({ content: 'Failed to accept partnership.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('Partnership Accepted!') .setDescription(`You are now partners with **${request.requester_guild_name}**!`) .addFields( { name: 'Partner Server ID', value: `\`${request.requester_guild_id}\``, inline: true } ) .setTimestamp(); await interaction.reply({ embeds: [embed] }); const notifyEmbed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('Partnership Request Accepted!') .setDescription(`**${interaction.guild.name}** has accepted your partnership request!`) .setTimestamp(); await notifyPartnerServer(client, supabase, request.requester_guild_id, notifyEmbed); } if (subcommand === 'reject') { const requestId = interaction.options.getString('request_id'); const reason = interaction.options.getString('reason') || 'No reason provided'; const { data: request } = await supabase .from('server_partnerships') .select('*') .eq('id', requestId) .eq('target_guild_id', interaction.guildId) .eq('status', 'pending') .maybeSingle(); if (!request) { return interaction.reply({ content: 'Partnership request not found or not pending.', ephemeral: true }); } const { error } = await supabase .from('server_partnerships') .update({ status: 'rejected', rejection_reason: reason, updated_at: new Date().toISOString() }) .eq('id', requestId); if (error) { return interaction.reply({ content: 'Failed to reject partnership.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0xff6600) .setTitle('Partnership Rejected') .setDescription(`Request from **${request.requester_guild_name}** has been rejected.`) .addFields({ name: 'Reason', value: reason }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); const notifyEmbed = new EmbedBuilder() .setColor(0xff6600) .setTitle('Partnership Request Declined') .setDescription(`**${interaction.guild.name}** has declined your partnership request.`) .addFields({ name: 'Reason', value: reason }) .setTimestamp(); await notifyPartnerServer(client, supabase, request.requester_guild_id, notifyEmbed); } if (subcommand === 'end') { const partnerId = interaction.options.getString('partner_id'); const { data: partnership } = await supabase .from('server_partnerships') .select('*') .eq('status', 'accepted') .or(`and(requester_guild_id.eq.${interaction.guildId},target_guild_id.eq.${partnerId}),and(requester_guild_id.eq.${partnerId},target_guild_id.eq.${interaction.guildId})`) .maybeSingle(); if (!partnership) { return interaction.reply({ content: 'No active partnership found with this server.', ephemeral: true }); } const partnerName = partnership.requester_guild_id === interaction.guildId ? partnership.target_guild_name : partnership.requester_guild_name; const { error } = await supabase .from('server_partnerships') .update({ status: 'ended', ended_at: new Date().toISOString(), updated_at: new Date().toISOString() }) .eq('id', partnership.id); if (error) { return interaction.reply({ content: 'Failed to end partnership.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0xff0000) .setTitle('Partnership Ended') .setDescription(`Your partnership with **${partnerName}** has been ended.`) .setTimestamp(); await interaction.reply({ embeds: [embed] }); const notifyEmbed = new EmbedBuilder() .setColor(0xff0000) .setTitle('Partnership Ended') .setDescription(`**${interaction.guild.name}** has ended their partnership with your server.`) .setTimestamp(); await notifyPartnerServer(client, supabase, partnerId, notifyEmbed); } } async function handleEvents(interaction, supabase, client, subcommand) { if (!supabase) { return interaction.reply({ content: 'Database not available.', ephemeral: true }); } const typeEmojis = { competition: '🏆', collaboration: '🤝', social: '🎉', gaming: '🎮', learning: '📚' }; if (subcommand === 'create') { if (!interaction.member.permissions.has(PermissionFlagsBits.ManageGuild)) { return interaction.reply({ content: 'You need Manage Server permission to create events.', ephemeral: true }); } const name = interaction.options.getString('name'); const description = interaction.options.getString('description'); const eventType = interaction.options.getString('type'); const startDateStr = interaction.options.getString('start_date'); const endDateStr = interaction.options.getString('end_date'); const xpReward = interaction.options.getInteger('xp_reward') || 0; const startDate = new Date(startDateStr); const endDate = new Date(endDateStr); if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { return interaction.reply({ content: 'Invalid date format. Please use YYYY-MM-DD.', ephemeral: true }); } if (endDate <= startDate) { return interaction.reply({ content: 'End date must be after start date.', ephemeral: true }); } if (startDate < new Date()) { return interaction.reply({ content: 'Start date cannot be in the past.', ephemeral: true }); } const { data: event, error } = await supabase .from('federation_events') .insert({ name, description, event_type: eventType, host_guild_id: interaction.guildId, host_guild_name: interaction.guild.name, created_by_id: interaction.user.id, created_by_name: interaction.user.tag, start_date: startDate.toISOString(), end_date: endDate.toISOString(), xp_reward: xpReward, }) .select() .single(); if (error) { console.error('Event creation error:', error); return interaction.reply({ content: 'Failed to create event.', ephemeral: true }); } await supabase.from('federation_event_participants').insert({ event_id: event.id, guild_id: interaction.guildId, guild_name: interaction.guild.name, }); const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle(`${typeEmojis[eventType]} Event Created!`) .setDescription(`**${name}**\n\n${description}`) .addFields( { name: 'Event ID', value: `\`${event.id}\``, inline: true }, { name: 'Type', value: eventType, inline: true }, { name: 'XP Reward', value: xpReward.toLocaleString(), inline: true }, { name: 'Starts', value: startDate.toLocaleDateString(), inline: true }, { name: 'Ends', value: endDate.toLocaleDateString(), inline: true } ) .setFooter({ text: `Hosted by ${interaction.guild.name}` }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'list') { const { data: events } = await supabase .from('federation_events') .select('*') .eq('status', 'active') .gte('end_date', new Date().toISOString()) .order('start_date', { ascending: true }) .limit(10); if (!events || events.length === 0) { const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Federation Events') .setDescription('No active events right now.\nUse `/federation events create` to host one!') .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const eventList = events.map(e => { const emoji = typeEmojis[e.event_type] || '📅'; const starts = new Date(e.start_date).toLocaleDateString(); const ends = new Date(e.end_date).toLocaleDateString(); return `${emoji} **${e.name}** (ID: \`${e.id}\`)\n` + ` ${e.description?.substring(0, 60) || 'No description'}${e.description?.length > 60 ? '...' : ''}\n` + ` 📅 ${starts} - ${ends} | 🎁 ${e.xp_reward} XP`; }).join('\n\n'); const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Federation Events') .setDescription(eventList) .addFields({ name: 'Actions', value: '`/federation events join ` - Join an event\n' + '`/federation events info ` - View event details' }) .setFooter({ text: `${events.length} active event(s)` }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'info') { const eventId = interaction.options.getString('event_id'); const { data: event } = await supabase .from('federation_events') .select('*') .eq('id', eventId) .maybeSingle(); if (!event) { return interaction.reply({ content: 'Event not found.', ephemeral: true }); } const { data: participants } = await supabase .from('federation_event_participants') .select('guild_name') .eq('event_id', eventId); const participantList = participants?.map(p => p.guild_name).join(', ') || 'None'; const emoji = typeEmojis[event.event_type] || '📅'; const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle(`${emoji} ${event.name}`) .setDescription(event.description || 'No description') .addFields( { name: 'Type', value: event.event_type, inline: true }, { name: 'Status', value: event.status, inline: true }, { name: 'XP Reward', value: event.xp_reward?.toLocaleString() || '0', inline: true }, { name: 'Starts', value: new Date(event.start_date).toLocaleDateString(), inline: true }, { name: 'Ends', value: new Date(event.end_date).toLocaleDateString(), inline: true }, { name: 'Host', value: event.host_guild_name, inline: true }, { name: `Participants (${participants?.length || 0})`, value: participantList.substring(0, 1000) } ) .setFooter({ text: `Event ID: ${event.id}` }) .setTimestamp(); const isParticipant = participants?.some(p => p.guild_name === interaction.guild.name); const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId(`fed_event_${isParticipant ? 'leave' : 'join'}_${eventId}`) .setLabel(isParticipant ? 'Leave Event' : 'Join Event') .setStyle(isParticipant ? ButtonStyle.Danger : ButtonStyle.Success) ); await interaction.reply({ embeds: [embed], components: [row] }); } if (subcommand === 'join') { if (!interaction.member.permissions.has(PermissionFlagsBits.ManageGuild)) { return interaction.reply({ content: 'You need Manage Server permission to join events.', ephemeral: true }); } const eventId = interaction.options.getString('event_id'); const { data: event } = await supabase .from('federation_events') .select('*') .eq('id', eventId) .eq('status', 'active') .maybeSingle(); if (!event) { return interaction.reply({ content: 'Event not found or not active.', ephemeral: true }); } const { data: existing } = await supabase .from('federation_event_participants') .select('id') .eq('event_id', eventId) .eq('guild_id', interaction.guildId) .maybeSingle(); if (existing) { return interaction.reply({ content: 'Your server is already participating in this event.', ephemeral: true }); } const { error } = await supabase.from('federation_event_participants').insert({ event_id: parseInt(eventId), guild_id: interaction.guildId, guild_name: interaction.guild.name, }); if (error) { console.error('Event join error:', error); return interaction.reply({ content: 'Failed to join event.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('Joined Event!') .setDescription(`**${interaction.guild.name}** has joined **${event.name}**!`) .addFields( { name: 'XP Reward', value: event.xp_reward?.toLocaleString() || '0', inline: true }, { name: 'Ends', value: new Date(event.end_date).toLocaleDateString(), inline: true } ) .setTimestamp(); await interaction.reply({ embeds: [embed] }); const notifyEmbed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('New Event Participant!') .setDescription(`**${interaction.guild.name}** has joined your event **${event.name}**!`) .setTimestamp(); await notifyPartnerServer(client, supabase, event.host_guild_id, notifyEmbed); } if (subcommand === 'leave') { if (!interaction.member.permissions.has(PermissionFlagsBits.ManageGuild)) { return interaction.reply({ content: 'You need Manage Server permission to leave events.', ephemeral: true }); } const eventId = interaction.options.getString('event_id'); const { data: event } = await supabase .from('federation_events') .select('*') .eq('id', eventId) .eq('status', 'active') .maybeSingle(); if (!event) { return interaction.reply({ content: 'Event not found or not active.', ephemeral: true }); } if (event.host_guild_id === interaction.guildId) { return interaction.reply({ content: 'You cannot leave an event you are hosting. Use cancel instead.', ephemeral: true }); } const { data: participation } = await supabase .from('federation_event_participants') .select('id') .eq('event_id', eventId) .eq('guild_id', interaction.guildId) .maybeSingle(); if (!participation) { return interaction.reply({ content: 'Your server is not participating in this event.', ephemeral: true }); } const { error } = await supabase .from('federation_event_participants') .delete() .eq('event_id', eventId) .eq('guild_id', interaction.guildId); if (error) { return interaction.reply({ content: 'Failed to leave event.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0xff6600) .setTitle('Left Event') .setDescription(`**${interaction.guild.name}** has left **${event.name}**.`) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'cancel') { if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) { return interaction.reply({ content: 'You need Administrator permission to cancel events.', ephemeral: true }); } const eventId = interaction.options.getString('event_id'); const { data: event } = await supabase .from('federation_events') .select('*') .eq('id', eventId) .eq('host_guild_id', interaction.guildId) .eq('status', 'active') .maybeSingle(); if (!event) { return interaction.reply({ content: 'Event not found, already cancelled, or you are not the host.', ephemeral: true }); } const { error } = await supabase .from('federation_events') .update({ status: 'cancelled', updated_at: new Date().toISOString() }) .eq('id', eventId); if (error) { return interaction.reply({ content: 'Failed to cancel event.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0xff0000) .setTitle('Event Cancelled') .setDescription(`**${event.name}** has been cancelled.`) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } }