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:
parent
0931891163
commit
be66035f49
1 changed files with 352 additions and 0 deletions
|
|
@ -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] });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue