diff --git a/.replit b/.replit index 9585c0f..696ae75 100644 --- a/.replit +++ b/.replit @@ -21,6 +21,10 @@ externalPort = 80 localPort = 8080 externalPort = 8080 +[[ports]] +localPort = 45655 +externalPort = 3000 + [workflows] runButton = "Project" diff --git a/aethex-bot/bot.js b/aethex-bot/bot.js index 7ecb390..5f226de 100644 --- a/aethex-bot/bot.js +++ b/aethex-bot/bot.js @@ -718,7 +718,7 @@ if (fs.existsSync(sentinelPath)) { // ============================================================================= const listenersPath = path.join(__dirname, "listeners"); -const generalListenerFiles = ['welcome.js', 'goodbye.js', 'xpTracker.js', 'reactionXp.js', 'voiceXp.js', 'starboard.js']; +const generalListenerFiles = ['welcome.js', 'goodbye.js', 'xpTracker.js', 'reactionXp.js', 'voiceXp.js', 'starboard.js', 'federationProtection.js']; for (const file of generalListenerFiles) { const filePath = path.join(listenersPath, file); if (fs.existsSync(filePath)) { @@ -2500,33 +2500,22 @@ client.login(token).catch((error) => { }); // ============================================================================= -// DYNAMIC STATUS ROTATION +// BOT STATUS - WATCHING PROTECTING THE FEDERATION // ============================================================================= -function startDynamicStatus(client) { - const statuses = [ - () => ({ name: `${client.guilds.cache.size} servers`, type: 3 }), // Watching - () => ({ name: `${client.guilds.cache.reduce((sum, g) => sum + g.memberCount, 0).toLocaleString()} members`, type: 3 }), // Watching - () => ({ name: '/help | aethex.studio', type: 0 }), // Playing - () => ({ name: '🛡️ Guarding the Federation', type: 4 }), // Custom - () => ({ name: 'for threats', type: 3 }), // Watching - () => ({ name: '⚔️ Warden • Free Forever', type: 4 }), // Custom - ]; - - let currentIndex = 0; - - const updateStatus = () => { - try { - const status = statuses[currentIndex](); - client.user.setActivity(status.name, { type: status.type }); - currentIndex = (currentIndex + 1) % statuses.length; - } catch (e) { - console.error('[Status] Error updating status:', e.message); - } - }; - - updateStatus(); - setInterval(updateStatus, 30000); // Rotate every 30 seconds +function setWardenStatus(client) { + try { + client.user.setPresence({ + activities: [{ + name: 'Protecting the Federation', + type: 3 // ActivityType.Watching + }], + status: 'online' + }); + console.log('[Status] Set to: WATCHING Protecting the Federation'); + } catch (e) { + console.error('[Status] Error setting status:', e.message); + } } client.once("clientReady", async () => { @@ -2550,8 +2539,8 @@ client.once("clientReady", async () => { console.error("Failed to register commands:", regResult.error); } - // Dynamic rotating status - startDynamicStatus(client); + // Static status: WATCHING Protecting the Federation + setWardenStatus(client); if (setupFeedListener && supabase) { setupFeedListener(client); diff --git a/aethex-bot/commands/federation.js b/aethex-bot/commands/federation.js index 678aa32..504c077 100644 --- a/aethex-bot/commands/federation.js +++ b/aethex-bot/commands/federation.js @@ -1,132 +1,696 @@ -const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); +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('Manage cross-server role synchronization') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles) - .addSubcommand(subcommand => - subcommand - .setName('link') - .setDescription('Link a role for cross-server sync') - .addRoleOption(option => - option.setName('role') - .setDescription('Role to sync across servers') - .setRequired(true) - ) + .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')) ) - .addSubcommand(subcommand => - subcommand - .setName('unlink') - .setDescription('Remove a role from sync') - .addRoleOption(option => - option.setName('role') - .setDescription('Role to remove from sync') - .setRequired(true) - ) + .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))) ) - .addSubcommand(subcommand => - subcommand - .setName('list') - .setDescription('List all linked roles') + .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'))) ), 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') { + 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\nThis server operates independently and does not sync roles across the AeThex network.\n\nUse `/config mode` to switch to federated mode.'); + .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 }); } - const subcommand = interaction.options.getSubcommand(); - - if (subcommand === 'link') { - 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') { - 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 { - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle('Not Found') - .setDescription(`${role} is not currently linked.`) - .setTimestamp(); - - await interaction.reply({ embeds: [embed], 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 for federation sync.\nUse `/federation link` to add roles.') - .setTimestamp(); - - await interaction.reply({ embeds: [embed] }); - return; - } - - 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] }); + 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); } }, }; + +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); + } +} diff --git a/aethex-bot/listeners/federationProtection.js b/aethex-bot/listeners/federationProtection.js new file mode 100644 index 0000000..04413e4 --- /dev/null +++ b/aethex-bot/listeners/federationProtection.js @@ -0,0 +1,118 @@ +const { EmbedBuilder } = require('discord.js'); + +module.exports = { + name: 'guildMemberAdd', + + async execute(member, client, supabase) { + if (!supabase) return; + + try { + const { data: serverConfig } = await supabase + .from('federation_servers') + .select('tier, status') + .eq('guild_id', member.guild.id) + .eq('status', 'approved') + .maybeSingle(); + + if (!serverConfig) return; + + const { data: ban } = await supabase + .from('federation_bans') + .select('*') + .eq('user_id', member.id) + .eq('active', true) + .maybeSingle(); + + if (!ban) return; + + const isPremium = serverConfig.tier === 'premium'; + const isCritical = ban.severity === 'critical'; + + if (!isPremium && !isCritical) { + return; + } + + const severityColors = { low: 0xffff00, medium: 0xff9900, high: 0xff3300, critical: 0xff0000 }; + const severityEmojis = { low: '⚠️', medium: '🔶', high: '🔴', critical: '☠️' }; + + const alertEmbed = new EmbedBuilder() + .setColor(severityColors[ban.severity]) + .setTitle(`${severityEmojis[ban.severity]} Federation Alert: Flagged User Joined`) + .setThumbnail(member.displayAvatarURL()) + .addFields( + { name: 'User', value: `${member.user.tag}\n\`${member.id}\``, inline: true }, + { name: 'Severity', value: ban.severity.toUpperCase(), inline: true }, + { name: 'Reason', value: ban.reason || 'No reason provided' } + ) + .setFooter({ text: 'AeThex Federation Protection' }) + .setTimestamp(); + + const { data: config } = await supabase + .from('server_config') + .select('modlog_channel') + .eq('guild_id', member.guild.id) + .maybeSingle(); + + if (config?.modlog_channel) { + const logChannel = await client.channels.fetch(config.modlog_channel).catch(() => null); + if (logChannel) { + await logChannel.send({ embeds: [alertEmbed] }); + } + } + + if (isCritical) { + try { + await member.ban({ reason: `[Federation] Global ban: ${ban.reason}` }); + + alertEmbed.setTitle(`${severityEmojis[ban.severity]} Federation Auto-Ban: Critical Threat Removed`); + alertEmbed.addFields({ name: 'Action Taken', value: 'User was automatically banned' }); + + await supabase.from('federation_alerts').update({ + delivered: true, + delivered_at: new Date().toISOString(), + action_taken: 'auto_ban' + }).eq('guild_id', member.guild.id).eq('ban_id', ban.id); + + } catch (banError) { + console.error('[Federation] Failed to auto-ban:', banError.message); + alertEmbed.addFields({ name: 'Action Required', value: 'Auto-ban failed. Please ban manually.' }); + } + } else if (isPremium) { + try { + await member.kick(`[Federation] Global ban (${ban.severity} severity): ${ban.reason}`); + alertEmbed.addFields({ name: 'Action Taken', value: 'User was automatically kicked (Premium Protection)' }); + + await supabase.from('federation_alerts').update({ + delivered: true, + delivered_at: new Date().toISOString(), + action_taken: 'auto_kick' + }).eq('guild_id', member.guild.id).eq('ban_id', ban.id); + } catch (kickError) { + console.error('[Federation] Failed to auto-kick:', kickError.message); + alertEmbed.addFields({ name: 'Action Required', value: 'Auto-kick failed. Please remove user manually.' }); + } + } + + const owner = await member.guild.fetchOwner().catch(() => null); + if (owner && ban.severity === 'critical') { + try { + const dmEmbed = new EmbedBuilder() + .setColor(0xff0000) + .setTitle('Federation Alert: Critical Threat') + .setDescription(`A user on the global ban list (Critical severity) joined **${member.guild.name}** and was automatically banned.`) + .addFields( + { name: 'User', value: `${member.user.tag} (\`${member.id}\`)` }, + { name: 'Reason', value: ban.reason } + ) + .setTimestamp(); + + await owner.send({ embeds: [dmEmbed] }); + } catch (dmError) { + } + } + + } catch (error) { + console.error('[Federation Protection] Error:', error.message); + } + }, +}; diff --git a/aethex-bot/public/federation.html b/aethex-bot/public/federation.html new file mode 100644 index 0000000..14d3c1a --- /dev/null +++ b/aethex-bot/public/federation.html @@ -0,0 +1,625 @@ + + + + + + Federation - AeThex | Warden + + + +
+ +
+
+ + + + + Add to Server +
+
+ +
+
+
+

The Federation

+

A network of protected servers. Ban one, ban all.

+
+
+ +
+
+
+
-
+
Member Servers
+
+
+
-
+
Active Bans
+
+
+
-
+
Pending Applications
+
+
+ +
+ + + + +
+ +
+
+
Loading servers...
+
+
+ +
+
+
+ Global Ban List +
+ + + + + + + + + + + + +
UserSeverityReasonDate
Loading bans...
+
+
+ +
+
+
+ Pending Applications +
+ + + + + + + + + + + + + +
ServerCategoryMembersStatusActions
Loading applications...
+
+
+ +
+
+
+ Federation Reputation Leaders +
+
+
Loading leaderboard...
+
+
+
+
+
+ + + + diff --git a/aethex-bot/server/webServer.js b/aethex-bot/server/webServer.js index 65be149..f799337 100644 --- a/aethex-bot/server/webServer.js +++ b/aethex-bot/server/webServer.js @@ -1038,6 +1038,192 @@ function createWebServer(discordClient, supabase, options = {}) { } }); + // ============ FEDERATION API ============ + + app.get('/api/federation/stats', async (req, res) => { + if (!supabase) { + return res.status(503).json({ error: 'Database not available' }); + } + + try { + const [servers, bans, applications] = await Promise.all([ + supabase.from('federation_servers').select('id', { count: 'exact' }).eq('status', 'approved'), + supabase.from('federation_bans').select('id', { count: 'exact' }).eq('active', true), + supabase.from('federation_applications').select('id', { count: 'exact' }).eq('status', 'pending') + ]); + + res.json({ + totalServers: servers.count || 0, + activeBans: bans.count || 0, + pendingApplications: applications.count || 0 + }); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch stats' }); + } + }); + + app.get('/api/federation/bans', async (req, res) => { + if (!supabase) { + return res.status(503).json({ error: 'Database not available' }); + } + + const { limit = 50, severity } = req.query; + + try { + let query = supabase + .from('federation_bans') + .select('*') + .eq('active', true) + .order('created_at', { ascending: false }) + .limit(parseInt(limit)); + + if (severity) { + query = query.eq('severity', severity); + } + + const { data: bans } = await query; + res.json({ bans: bans || [] }); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch bans' }); + } + }); + + app.get('/api/federation/servers', async (req, res) => { + if (!supabase) { + return res.status(503).json({ error: 'Database not available' }); + } + + try { + const { data: servers } = await supabase + .from('federation_servers') + .select('*') + .eq('status', 'approved') + .order('member_count', { ascending: false }); + + res.json({ servers: servers || [] }); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch servers' }); + } + }); + + app.get('/api/federation/applications', async (req, res) => { + if (!supabase) { + return res.status(503).json({ error: 'Database not available' }); + } + + const userId = req.session.user?.id; + if (!userId) { + return res.status(401).json({ error: 'Not authenticated' }); + } + + try { + const { data: applications } = await supabase + .from('federation_applications') + .select('*') + .order('created_at', { ascending: false }); + + res.json({ applications: applications || [] }); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch applications' }); + } + }); + + app.post('/api/federation/applications/:appId/approve', async (req, res) => { + if (!supabase) { + return res.status(503).json({ error: 'Database not available' }); + } + + const userId = req.session.user?.id; + if (!userId) { + return res.status(401).json({ error: 'Not authenticated' }); + } + + const { appId } = req.params; + + try { + const { data: app } = await supabase + .from('federation_applications') + .select('*') + .eq('id', appId) + .maybeSingle(); + + if (!app) { + return res.status(404).json({ error: 'Application not found' }); + } + + await supabase.from('federation_applications').update({ + status: 'approved', + reviewed_by: userId, + reviewed_at: new Date().toISOString() + }).eq('id', appId); + + await supabase.from('federation_servers').insert({ + guild_id: app.guild_id, + guild_name: app.guild_name, + guild_icon: app.guild_icon, + description: app.description, + category: app.category, + member_count: app.member_count, + owner_id: app.admin_id, + status: 'approved', + treaty_accepted: true, + treaty_accepted_at: new Date().toISOString() + }); + + res.json({ success: true }); + } catch (error) { + console.error('Failed to approve application:', error); + res.status(500).json({ error: 'Failed to approve application' }); + } + }); + + app.post('/api/federation/applications/:appId/reject', async (req, res) => { + if (!supabase) { + return res.status(503).json({ error: 'Database not available' }); + } + + const userId = req.session.user?.id; + if (!userId) { + return res.status(401).json({ error: 'Not authenticated' }); + } + + const { appId } = req.params; + const { reason } = req.body; + + try { + await supabase.from('federation_applications').update({ + status: 'rejected', + reviewed_by: userId, + reviewed_at: new Date().toISOString(), + rejection_reason: reason || 'No reason provided' + }).eq('id', appId); + + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: 'Failed to reject application' }); + } + }); + + app.get('/api/federation/leaderboard', async (req, res) => { + if (!supabase) { + return res.status(503).json({ error: 'Database not available' }); + } + + const { limit = 50 } = req.query; + + try { + const { data: leaders } = await supabase + .from('federation_reputation') + .select('*') + .order('reputation_score', { ascending: false }) + .limit(parseInt(limit)); + + res.json({ leaderboard: leaders || [] }); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch leaderboard' }); + } + }); + app.get('/health', (req, res) => { res.json({ status: 'ok', @@ -1046,6 +1232,10 @@ function createWebServer(discordClient, supabase, options = {}) { }); }); + app.get('/federation', (req, res) => { + res.sendFile(path.join(__dirname, '../public/federation.html')); + }); + app.get('/dashboard', (req, res) => { res.sendFile(path.join(__dirname, '../public/dashboard.html')); }); diff --git a/attached_assets/Pasted-This-is-a-Network-Effect-strategy-You-aren-t-selling-th_1765329237794.txt b/attached_assets/Pasted-This-is-a-Network-Effect-strategy-You-aren-t-selling-th_1765329237794.txt new file mode 100644 index 0000000..687e6d1 --- /dev/null +++ b/attached_assets/Pasted-This-is-a-Network-Effect-strategy-You-aren-t-selling-th_1765329237794.txt @@ -0,0 +1,46 @@ +This is a Network Effect strategy. You aren't selling the code; you are selling Membership. + +If the AeThex | Warden bot allows external servers to "Join the Federation," you are essentially building a Digital Alliance (like NATO or the United Nations of Roblox). + +Here is how you extract massive value (money, power, and data) from letting other servers join your Federation. + +1. The "Global Ban List" (Data Supremacy) +The Mechanism: When a Federation server bans a user for being a "Nuker" or "Scammer," that ID is pushed to your central Supabase database. + +The Benefit: You build the ultimate Global Blacklist. + +The Monetization: + +Free Tier: Servers contribute bans but only get protection from "High Risk" nukers. + +Paid Tier ($50/mo): Servers get real-time protection from all Federation bans. "Protect your server from scammers before they even join." + +2. The "Cross-Pollination" (Traffic) +The Mechanism: The bot includes a /federation directory or a "Featured Server" channel that is synced across all Federation members. + +The Benefit: Free advertising for your projects (Lone Star). + +The Monetization: You can charge member servers for "Featured Spots." + +Example: "Want your game promoted to the 50,000 users across the entire Federation network? That's $200/week." + +3. The "Diplomatic" Leverage (Power) +The Mechanism: To join the Federation, a server admin must agree to your "Treaty" (Terms of Service). + +The Benefit: You become the Standard Setter. + +The Strategy: If a studio wants to be in the Federation (to look safe/official), they have to use your standards. This positions AeThex as the "Governing Body" of the alliance. It elevates your brand from "Studio" to "Authority." + +4. The "Talent Scout" (Recruitment) +The Mechanism: With Federation Sync, you can see high-level users across all connected servers. + +The Benefit: You can identify top developers or active users in other people's servers. + +The Strategy: If you see a user who is Level 50 in three different Federation coding servers, you know they are legit. You can invite them to StarFoundry directly. + +Summary: How to Pitch It +You don't sell the bot. You sell Safety in Numbers. + +"Join the AeThex Federation. When a nuker attacks one of us, they are banned from all of us instantly. Together, we are untouchable." + +Verdict: This is the smartest "Long Game" move. It costs you nothing but server fees, but it builds a massive defensive network that you control. \ No newline at end of file