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
This commit is contained in:
sirpiglr 2025-12-13 00:31:44 +00:00
parent 0931891163
commit be66035f49

View file

@ -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 <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] });
}
}