AeThex-Bot-Master/aethex-bot/commands/federation.js
sirpiglr a1912fc48a Add verification success endpoint and improve trust level system
Adds a POST endpoint `/api/verify-success` to `webServer.js` for handling verification callbacks from aethex.dev, including secret validation and role assignment. Updates `federationProtection.js` and `federation.js` to integrate trust level calculations and benefits. Introduces a new `trustLevels.js` utility file to manage trust level definitions, progression, and related logic.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 98ebd9a6-5755-4d7f-ae64-9ed93f1eae4f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/3tJ1Z1J
Replit-Helium-Checkpoint-Created: true
2025-12-13 10:11:52 +00:00

1487 lines
58 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const { getServerMode, EMBED_COLORS } = require('../utils/modeHelper');
const { getTrustLevelInfo, getProgressToNextLevel, TRUST_LEVELS, calculateReputationChange } = require('../utils/trustLevels');
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('stats').setDescription('View detailed trust level stats and progression'))
.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 reputationGain = calculateReputationChange('ban_report', true);
const { data: currentServer } = await supabase
.from('federation_servers')
.select('reports_submitted, reputation_score')
.eq('guild_id', interaction.guildId)
.maybeSingle();
if (currentServer) {
await supabase
.from('federation_servers')
.update({
reports_submitted: (currentServer.reports_submitted || 0) + 1,
reputation_score: (currentServer.reputation_score || 0) + reputationGain,
last_activity: new Date().toISOString()
})
.eq('guild_id', interaction.guildId);
}
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 },
{ name: 'Reputation', value: `+${reputationGain} points`, inline: true }
)
.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 trustLevel = server.trust_level || 'bronze';
const trustInfo = getTrustLevelInfo(trustLevel);
const embed = new EmbedBuilder()
.setColor(trustInfo.color)
.setTitle(`${trustInfo.emoji} Federation Member - ${trustInfo.name}`)
.addFields(
{ name: 'Status', value: server.status.toUpperCase(), inline: true },
{ name: 'Tier', value: server.tier?.toUpperCase() || 'FREE', inline: true },
{ name: 'Trust Level', value: `${trustInfo.emoji} ${trustInfo.name}`, inline: true },
{ name: 'Reputation', value: `${server.reputation_score || 0} points`, inline: true },
{ name: 'Joined', value: new Date(server.joined_at).toLocaleDateString(), inline: true }
)
.setFooter({ text: 'Use /federation membership stats for detailed progression' })
.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 === 'stats') {
const { data: server } = await supabase
.from('federation_servers')
.select('*')
.eq('guild_id', interaction.guildId)
.maybeSingle();
if (!server) {
return interaction.reply({
content: 'This server is not a federation member. Use `/federation membership apply` to join!',
ephemeral: true
});
}
const trustLevel = server.trust_level || 'bronze';
const trustInfo = getTrustLevelInfo(trustLevel);
const progression = getProgressToNextLevel(server);
const embed = new EmbedBuilder()
.setColor(trustInfo.color)
.setTitle(`${trustInfo.emoji} Trust Level: ${trustInfo.name}`)
.setDescription('Your server\'s federation standing and progression.')
.addFields(
{ name: 'Current Benefits', value: [
`**Auto-Action:** ${trustInfo.benefits.autoAction === 'ban' ? 'Auto-ban threats' : trustInfo.benefits.autoAction === 'kick' ? 'Auto-kick threats' : 'Alert only'}`,
`**Severity Threshold:** ${trustInfo.benefits.severityThreshold.charAt(0).toUpperCase() + trustInfo.benefits.severityThreshold.slice(1)}+`,
`**Featured Eligible:** ${trustInfo.benefits.featuredEligible ? 'Yes' : 'No'}`,
`**Partnership Limit:** ${trustInfo.benefits.partnershipLimit} servers`
].join('\n') }
);
if (progression.nextLevel) {
const progressBars = [];
const makeBar = (pct) => {
const filled = Math.floor(pct / 10);
return '█'.repeat(filled) + '░'.repeat(10 - filled);
};
progressBars.push(`**Members:** ${server.member_count || 0}/${progression.progress.members.required} ${progression.progress.members.met ? '✅' : ''}\n\`${makeBar(progression.progress.members.percentage)}\` ${progression.progress.members.percentage}%`);
progressBars.push(`**Days in Federation:** ${progression.progress.days.current}/${progression.progress.days.required} ${progression.progress.days.met ? '✅' : ''}\n\`${makeBar(progression.progress.days.percentage)}\` ${progression.progress.days.percentage}%`);
progressBars.push(`**Reputation:** ${server.reputation_score || 0}/${progression.progress.reputation.required} ${progression.progress.reputation.met ? '✅' : ''}\n\`${makeBar(progression.progress.reputation.percentage)}\` ${progression.progress.reputation.percentage}%`);
embed.addFields({
name: `Progress to ${progression.nextLevelInfo.emoji} ${progression.nextLevelInfo.name}`,
value: progressBars.join('\n\n')
});
if (progression.allMet) {
embed.addFields({
name: '🎉 Ready for Upgrade!',
value: 'Your server meets all requirements for the next trust level! Upgrades are processed weekly.'
});
}
} else {
embed.addFields({
name: '👑 Maximum Level',
value: 'Your server has reached the highest trust level!'
});
}
embed.addFields(
{ name: 'Statistics', value: [
`**Reports Submitted:** ${server.reports_submitted || 0}`,
`**False Positives:** ${server.false_positives || 0}`,
`**Last Activity:** ${server.last_activity ? new Date(server.last_activity).toLocaleDateString() : 'N/A'}`
].join('\n'), inline: true }
);
embed.setTimestamp();
return 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 <request_id>` - Accept a request\n' +
'`/federation partners reject <request_id>` - 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 <event_id>` - Join an event\n' +
'`/federation events info <event_id>` - 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] });
}
}