From be66035f495eb46bc4acd9bf67c938fbc5e4f5fb Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sat, 13 Dec 2025 00:31:44 +0000 Subject: [PATCH] Add functionality to manage cross-server events and partnerships Adds a new subcommand group 'events' to the federation command, enabling creation, listing, joining, leaving, and cancellation of federation-wide events. Includes permission checks and database interactions for event management. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 65540b36-63d8-4d86-86f9-d787ce48519b Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/auRwRay Replit-Helium-Checkpoint-Created: true --- aethex-bot/commands/federation.js | 352 ++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) diff --git a/aethex-bot/commands/federation.js b/aethex-bot/commands/federation.js index 740faaf..6a575c2 100644 --- a/aethex-bot/commands/federation.js +++ b/aethex-bot/commands/federation.js @@ -86,6 +86,34 @@ module.exports = { .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) { @@ -113,6 +141,8 @@ module.exports = { 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); } }, }; @@ -1033,3 +1063,325 @@ async function handlePartners(interaction, supabase, client, subcommand) { await notifyPartnerServer(client, supabase, partnerId, notifyEmbed); } } + +async function handleEvents(interaction, supabase, client, subcommand) { + if (!supabase) { + return interaction.reply({ content: 'Database not available.', ephemeral: true }); + } + + const typeEmojis = { competition: '🏆', collaboration: '🤝', social: '🎉', gaming: '🎮', learning: '📚' }; + + if (subcommand === 'create') { + if (!interaction.member.permissions.has(PermissionFlagsBits.ManageGuild)) { + return interaction.reply({ content: 'You need Manage Server permission to create events.', ephemeral: true }); + } + + const name = interaction.options.getString('name'); + const description = interaction.options.getString('description'); + const eventType = interaction.options.getString('type'); + const startDateStr = interaction.options.getString('start_date'); + const endDateStr = interaction.options.getString('end_date'); + const xpReward = interaction.options.getInteger('xp_reward') || 0; + + const startDate = new Date(startDateStr); + const endDate = new Date(endDateStr); + + if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { + return interaction.reply({ content: 'Invalid date format. Please use YYYY-MM-DD.', ephemeral: true }); + } + + if (endDate <= startDate) { + return interaction.reply({ content: 'End date must be after start date.', ephemeral: true }); + } + + if (startDate < new Date()) { + return interaction.reply({ content: 'Start date cannot be in the past.', ephemeral: true }); + } + + const { data: event, error } = await supabase + .from('federation_events') + .insert({ + name, + description, + event_type: eventType, + host_guild_id: interaction.guildId, + host_guild_name: interaction.guild.name, + created_by_id: interaction.user.id, + created_by_name: interaction.user.tag, + start_date: startDate.toISOString(), + end_date: endDate.toISOString(), + xp_reward: xpReward, + }) + .select() + .single(); + + if (error) { + console.error('Event creation error:', error); + return interaction.reply({ content: 'Failed to create event.', ephemeral: true }); + } + + await supabase.from('federation_event_participants').insert({ + event_id: event.id, + guild_id: interaction.guildId, + guild_name: interaction.guild.name, + }); + + const embed = new EmbedBuilder() + .setColor(0x00ff00) + .setTitle(`${typeEmojis[eventType]} Event Created!`) + .setDescription(`**${name}**\n\n${description}`) + .addFields( + { name: 'Event ID', value: `\`${event.id}\``, inline: true }, + { name: 'Type', value: eventType, inline: true }, + { name: 'XP Reward', value: xpReward.toLocaleString(), inline: true }, + { name: 'Starts', value: startDate.toLocaleDateString(), inline: true }, + { name: 'Ends', value: endDate.toLocaleDateString(), inline: true } + ) + .setFooter({ text: `Hosted by ${interaction.guild.name}` }) + .setTimestamp(); + + await interaction.reply({ embeds: [embed] }); + } + + if (subcommand === 'list') { + const { data: events } = await supabase + .from('federation_events') + .select('*') + .eq('status', 'active') + .gte('end_date', new Date().toISOString()) + .order('start_date', { ascending: true }) + .limit(10); + + if (!events || events.length === 0) { + const embed = new EmbedBuilder() + .setColor(0x7c3aed) + .setTitle('Federation Events') + .setDescription('No active events right now.\nUse `/federation events create` to host one!') + .setTimestamp(); + return interaction.reply({ embeds: [embed] }); + } + + const eventList = events.map(e => { + const emoji = typeEmojis[e.event_type] || '📅'; + const starts = new Date(e.start_date).toLocaleDateString(); + const ends = new Date(e.end_date).toLocaleDateString(); + return `${emoji} **${e.name}** (ID: \`${e.id}\`)\n` + + ` ${e.description?.substring(0, 60) || 'No description'}${e.description?.length > 60 ? '...' : ''}\n` + + ` 📅 ${starts} - ${ends} | 🎁 ${e.xp_reward} XP`; + }).join('\n\n'); + + const embed = new EmbedBuilder() + .setColor(0x7c3aed) + .setTitle('Federation Events') + .setDescription(eventList) + .addFields({ + name: 'Actions', + value: '`/federation events join ` - Join an event\n' + + '`/federation events info ` - View event details' + }) + .setFooter({ text: `${events.length} active event(s)` }) + .setTimestamp(); + + await interaction.reply({ embeds: [embed] }); + } + + if (subcommand === 'info') { + const eventId = interaction.options.getString('event_id'); + + const { data: event } = await supabase + .from('federation_events') + .select('*') + .eq('id', eventId) + .maybeSingle(); + + if (!event) { + return interaction.reply({ content: 'Event not found.', ephemeral: true }); + } + + const { data: participants } = await supabase + .from('federation_event_participants') + .select('guild_name') + .eq('event_id', eventId); + + const participantList = participants?.map(p => p.guild_name).join(', ') || 'None'; + const emoji = typeEmojis[event.event_type] || '📅'; + + const embed = new EmbedBuilder() + .setColor(0x7c3aed) + .setTitle(`${emoji} ${event.name}`) + .setDescription(event.description || 'No description') + .addFields( + { name: 'Type', value: event.event_type, inline: true }, + { name: 'Status', value: event.status, inline: true }, + { name: 'XP Reward', value: event.xp_reward?.toLocaleString() || '0', inline: true }, + { name: 'Starts', value: new Date(event.start_date).toLocaleDateString(), inline: true }, + { name: 'Ends', value: new Date(event.end_date).toLocaleDateString(), inline: true }, + { name: 'Host', value: event.host_guild_name, inline: true }, + { name: `Participants (${participants?.length || 0})`, value: participantList.substring(0, 1000) } + ) + .setFooter({ text: `Event ID: ${event.id}` }) + .setTimestamp(); + + const isParticipant = participants?.some(p => p.guild_name === interaction.guild.name); + + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(`fed_event_${isParticipant ? 'leave' : 'join'}_${eventId}`) + .setLabel(isParticipant ? 'Leave Event' : 'Join Event') + .setStyle(isParticipant ? ButtonStyle.Danger : ButtonStyle.Success) + ); + + await interaction.reply({ embeds: [embed], components: [row] }); + } + + if (subcommand === 'join') { + if (!interaction.member.permissions.has(PermissionFlagsBits.ManageGuild)) { + return interaction.reply({ content: 'You need Manage Server permission to join events.', ephemeral: true }); + } + + const eventId = interaction.options.getString('event_id'); + + const { data: event } = await supabase + .from('federation_events') + .select('*') + .eq('id', eventId) + .eq('status', 'active') + .maybeSingle(); + + if (!event) { + return interaction.reply({ content: 'Event not found or not active.', ephemeral: true }); + } + + const { data: existing } = await supabase + .from('federation_event_participants') + .select('id') + .eq('event_id', eventId) + .eq('guild_id', interaction.guildId) + .maybeSingle(); + + if (existing) { + return interaction.reply({ content: 'Your server is already participating in this event.', ephemeral: true }); + } + + const { error } = await supabase.from('federation_event_participants').insert({ + event_id: parseInt(eventId), + guild_id: interaction.guildId, + guild_name: interaction.guild.name, + }); + + if (error) { + console.error('Event join error:', error); + return interaction.reply({ content: 'Failed to join event.', ephemeral: true }); + } + + const embed = new EmbedBuilder() + .setColor(0x00ff00) + .setTitle('Joined Event!') + .setDescription(`**${interaction.guild.name}** has joined **${event.name}**!`) + .addFields( + { name: 'XP Reward', value: event.xp_reward?.toLocaleString() || '0', inline: true }, + { name: 'Ends', value: new Date(event.end_date).toLocaleDateString(), inline: true } + ) + .setTimestamp(); + + await interaction.reply({ embeds: [embed] }); + + const notifyEmbed = new EmbedBuilder() + .setColor(0x00ff00) + .setTitle('New Event Participant!') + .setDescription(`**${interaction.guild.name}** has joined your event **${event.name}**!`) + .setTimestamp(); + + await notifyPartnerServer(client, supabase, event.host_guild_id, notifyEmbed); + } + + if (subcommand === 'leave') { + if (!interaction.member.permissions.has(PermissionFlagsBits.ManageGuild)) { + return interaction.reply({ content: 'You need Manage Server permission to leave events.', ephemeral: true }); + } + + const eventId = interaction.options.getString('event_id'); + + const { data: event } = await supabase + .from('federation_events') + .select('*') + .eq('id', eventId) + .eq('status', 'active') + .maybeSingle(); + + if (!event) { + return interaction.reply({ content: 'Event not found or not active.', ephemeral: true }); + } + + if (event.host_guild_id === interaction.guildId) { + return interaction.reply({ content: 'You cannot leave an event you are hosting. Use cancel instead.', ephemeral: true }); + } + + const { data: participation } = await supabase + .from('federation_event_participants') + .select('id') + .eq('event_id', eventId) + .eq('guild_id', interaction.guildId) + .maybeSingle(); + + if (!participation) { + return interaction.reply({ content: 'Your server is not participating in this event.', ephemeral: true }); + } + + const { error } = await supabase + .from('federation_event_participants') + .delete() + .eq('event_id', eventId) + .eq('guild_id', interaction.guildId); + + if (error) { + return interaction.reply({ content: 'Failed to leave event.', ephemeral: true }); + } + + const embed = new EmbedBuilder() + .setColor(0xff6600) + .setTitle('Left Event') + .setDescription(`**${interaction.guild.name}** has left **${event.name}**.`) + .setTimestamp(); + + await interaction.reply({ embeds: [embed] }); + } + + if (subcommand === 'cancel') { + if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) { + return interaction.reply({ content: 'You need Administrator permission to cancel events.', ephemeral: true }); + } + + const eventId = interaction.options.getString('event_id'); + + const { data: event } = await supabase + .from('federation_events') + .select('*') + .eq('id', eventId) + .eq('host_guild_id', interaction.guildId) + .eq('status', 'active') + .maybeSingle(); + + if (!event) { + return interaction.reply({ content: 'Event not found, already cancelled, or you are not the host.', ephemeral: true }); + } + + const { error } = await supabase + .from('federation_events') + .update({ status: 'cancelled', updated_at: new Date().toISOString() }) + .eq('id', eventId); + + if (error) { + return interaction.reply({ content: 'Failed to cancel event.', ephemeral: true }); + } + + const embed = new EmbedBuilder() + .setColor(0xff0000) + .setTitle('Event Cancelled') + .setDescription(`**${event.name}** has been cancelled.`) + .setTimestamp(); + + await interaction.reply({ embeds: [embed] }); + } +}