AeThex-Bot-Master/aethex-bot/commands/seasonal.js
sirpiglr 0241e09e80 Add welcome card customization and seasonal event features
Introduces customizable welcome cards using the canvas library and enhances the XP tracking system to incorporate active seasonal events with multipliers and bonus XP. Also adds presets and custom creation for seasonal events.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 6d6bb71e-5e8e-4841-9c13-fd5e76c7d9e6
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
2025-12-13 00:45:03 +00:00

404 lines
15 KiB
JavaScript

const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js');
const PRESET_EVENTS = {
halloween: {
name: 'Halloween Spooktacular',
description: 'Spooky season is here! Earn bonus XP and unlock exclusive Halloween rewards.',
emoji: '🎃',
color: 0xff6600,
xp_multiplier: 1.5,
month: 10,
start_day: 15,
end_day: 31
},
christmas: {
name: 'Winter Wonderland',
description: 'Celebrate the holidays with festive rewards and XP bonuses!',
emoji: '🎄',
color: 0x00a651,
xp_multiplier: 2.0,
month: 12,
start_day: 15,
end_day: 31
},
newyear: {
name: 'New Year Celebration',
description: 'Ring in the new year with special bonuses!',
emoji: '🎆',
color: 0xffd700,
xp_multiplier: 2.0,
month: 1,
start_day: 1,
end_day: 7
},
valentine: {
name: 'Valentine\'s Day',
description: 'Spread the love with bonus XP for reactions and kind messages!',
emoji: '💕',
color: 0xff69b4,
xp_multiplier: 1.5,
month: 2,
start_day: 10,
end_day: 14
},
easter: {
name: 'Easter Egg Hunt',
description: 'Hunt for hidden eggs and earn bonus rewards!',
emoji: '🐰',
color: 0x87ceeb,
xp_multiplier: 1.5,
month: 4,
start_day: 1,
end_day: 15
},
summer: {
name: 'Summer Bash',
description: 'Beat the heat with cool rewards and XP boosts!',
emoji: '☀️',
color: 0xffa500,
xp_multiplier: 1.25,
month: 7,
start_day: 1,
end_day: 31
},
anniversary: {
name: 'Server Anniversary',
description: 'Celebrate your server\'s special day!',
emoji: '🎂',
color: 0x9b59b6,
xp_multiplier: 2.0,
month: null,
start_day: null,
end_day: null
}
};
module.exports = {
data: new SlashCommandBuilder()
.setName('seasonal')
.setDescription('Manage seasonal events and XP bonuses')
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
.addSubcommand(sub => sub.setName('active').setDescription('View currently active seasonal events'))
.addSubcommand(sub => sub.setName('list').setDescription('List all configured seasonal events'))
.addSubcommand(sub => sub.setName('create').setDescription('Create a custom seasonal event')
.addStringOption(opt => opt.setName('name').setDescription('Event name').setRequired(true).setMaxLength(50))
.addStringOption(opt => opt.setName('description').setDescription('Event description').setRequired(true).setMaxLength(200))
.addStringOption(opt => opt.setName('start').setDescription('Start date (YYYY-MM-DD)').setRequired(true))
.addStringOption(opt => opt.setName('end').setDescription('End date (YYYY-MM-DD)').setRequired(true))
.addNumberOption(opt => opt.setName('multiplier').setDescription('XP multiplier (e.g., 1.5)').setMinValue(1).setMaxValue(5))
.addIntegerOption(opt => opt.setName('bonus_xp').setDescription('Bonus XP per message during event'))
.addStringOption(opt => opt.setName('emoji').setDescription('Event emoji').setMaxLength(10)))
.addSubcommand(sub => sub.setName('preset').setDescription('Enable a preset seasonal event')
.addStringOption(opt => opt.setName('event').setDescription('Preset event to enable')
.addChoices(
{ name: '🎃 Halloween', value: 'halloween' },
{ name: '🎄 Christmas', value: 'christmas' },
{ name: '🎆 New Year', value: 'newyear' },
{ name: '💕 Valentine\'s Day', value: 'valentine' },
{ name: '🐰 Easter', value: 'easter' },
{ name: '☀️ Summer Bash', value: 'summer' },
{ name: '🎂 Anniversary', value: 'anniversary' }
).setRequired(true))
.addIntegerOption(opt => opt.setName('year').setDescription('Year for the event (defaults to current/next)'))
.addStringOption(opt => opt.setName('start').setDescription('Custom start date for anniversary (YYYY-MM-DD)')))
.addSubcommand(sub => sub.setName('delete').setDescription('Delete a seasonal event')
.addStringOption(opt => opt.setName('name').setDescription('Event name to delete').setRequired(true).setAutocomplete(true)))
.addSubcommand(sub => sub.setName('announce').setDescription('Announce an active event to a channel')
.addChannelOption(opt => opt.setName('channel').setDescription('Channel to announce in').setRequired(true))),
async execute(interaction, supabase) {
if (!supabase) {
return interaction.reply({ content: 'Database not available.', ephemeral: true });
}
const subcommand = interaction.options.getSubcommand();
if (subcommand === 'active') {
const now = new Date();
const { data: events } = await supabase
.from('seasonal_events')
.select('*')
.eq('guild_id', interaction.guildId)
.lte('start_date', now.toISOString())
.gte('end_date', now.toISOString())
.order('start_date', { ascending: true });
if (!events || events.length === 0) {
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('🎉 Active Seasonal Events')
.setDescription('No seasonal events are currently active.\nUse `/seasonal create` or `/seasonal preset` to set one up!')
.setTimestamp();
return interaction.reply({ embeds: [embed] });
}
const embed = new EmbedBuilder()
.setColor(0x22c55e)
.setTitle('🎉 Active Seasonal Events')
.setTimestamp();
for (const event of events) {
const endDate = new Date(event.end_date);
const daysLeft = Math.ceil((endDate - now) / (1000 * 60 * 60 * 24));
embed.addFields({
name: `${event.emoji || '🎊'} ${event.name}`,
value: `${event.description}\n**XP Multiplier:** ${event.xp_multiplier}x\n**Bonus XP:** ${event.bonus_xp || 0}/message\n**Ends:** <t:${Math.floor(endDate.getTime() / 1000)}:R> (${daysLeft} days left)`,
inline: false
});
}
await interaction.reply({ embeds: [embed] });
}
if (subcommand === 'list') {
const { data: events } = await supabase
.from('seasonal_events')
.select('*')
.eq('guild_id', interaction.guildId)
.order('start_date', { ascending: true });
if (!events || events.length === 0) {
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('📅 Seasonal Events')
.setDescription('No seasonal events configured.\nUse `/seasonal create` or `/seasonal preset` to add one!')
.setTimestamp();
return interaction.reply({ embeds: [embed] });
}
const now = new Date();
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('📅 Seasonal Events')
.setFooter({ text: `${events.length} event(s) configured` })
.setTimestamp();
for (const event of events) {
const startDate = new Date(event.start_date);
const endDate = new Date(event.end_date);
const isActive = now >= startDate && now <= endDate;
const isPast = now > endDate;
const status = isActive ? '🟢 Active' : isPast ? '⚫ Ended' : '🟡 Upcoming';
embed.addFields({
name: `${event.emoji || '🎊'} ${event.name} ${status}`,
value: `${event.description}\n**Period:** <t:${Math.floor(startDate.getTime() / 1000)}:D> - <t:${Math.floor(endDate.getTime() / 1000)}:D>\n**XP:** ${event.xp_multiplier}x | **Bonus XP:** +${event.bonus_xp || 0}`,
inline: false
});
}
await interaction.reply({ embeds: [embed] });
}
if (subcommand === 'create') {
const name = interaction.options.getString('name');
const description = interaction.options.getString('description');
const startStr = interaction.options.getString('start');
const endStr = interaction.options.getString('end');
const multiplier = interaction.options.getNumber('multiplier') || 1.5;
const bonusXp = interaction.options.getInteger('bonus_xp') || 0;
const emoji = interaction.options.getString('emoji') || '🎊';
const startDate = new Date(startStr);
startDate.setHours(0, 0, 0, 0);
const endDate = new Date(endStr);
endDate.setHours(23, 59, 59, 999);
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
return interaction.reply({ content: 'Invalid date format. Use YYYY-MM-DD.', ephemeral: true });
}
if (endDate <= startDate) {
return interaction.reply({ content: 'End date must be after start date.', ephemeral: true });
}
const { count } = await supabase
.from('seasonal_events')
.select('*', { count: 'exact', head: true })
.eq('guild_id', interaction.guildId);
if (count >= 20) {
return interaction.reply({ content: 'Maximum of 20 seasonal events per server.', ephemeral: true });
}
const { error } = await supabase.from('seasonal_events').insert({
guild_id: interaction.guildId,
name,
description,
emoji,
start_date: startDate.toISOString(),
end_date: endDate.toISOString(),
xp_multiplier: multiplier,
bonus_xp: bonusXp,
created_by: interaction.user.id
});
if (error) {
console.error('Seasonal event create error:', error);
return interaction.reply({ content: 'Failed to create event.', ephemeral: true });
}
const embed = new EmbedBuilder()
.setColor(0x22c55e)
.setTitle(`${emoji} Seasonal Event Created!`)
.setDescription(`**${name}**\n${description}`)
.addFields(
{ name: 'Start', value: `<t:${Math.floor(startDate.getTime() / 1000)}:F>`, inline: true },
{ name: 'End', value: `<t:${Math.floor(endDate.getTime() / 1000)}:F>`, inline: true },
{ name: 'XP Multiplier', value: `${multiplier}x`, inline: true },
{ name: 'Bonus XP', value: `+${bonusXp}/message`, inline: true }
)
.setTimestamp();
await interaction.reply({ embeds: [embed] });
}
if (subcommand === 'preset') {
const presetKey = interaction.options.getString('event');
const preset = PRESET_EVENTS[presetKey];
const year = interaction.options.getInteger('year') || new Date().getFullYear();
const customStart = interaction.options.getString('start');
let startDate, endDate;
if (presetKey === 'anniversary' && customStart) {
startDate = new Date(customStart);
endDate = new Date(startDate);
endDate.setDate(endDate.getDate() + 7);
} else if (preset.month) {
startDate = new Date(year, preset.month - 1, preset.start_day);
endDate = new Date(year, preset.month - 1, preset.end_day, 23, 59, 59);
if (startDate < new Date() && presetKey !== 'newyear') {
startDate.setFullYear(startDate.getFullYear() + 1);
endDate.setFullYear(endDate.getFullYear() + 1);
}
} else {
return interaction.reply({ content: 'Anniversary events require a custom start date.', ephemeral: true });
}
const { error } = await supabase.from('seasonal_events').insert({
guild_id: interaction.guildId,
name: preset.name,
description: preset.description,
emoji: preset.emoji,
start_date: startDate.toISOString(),
end_date: endDate.toISOString(),
xp_multiplier: preset.xp_multiplier,
bonus_xp: 0,
preset_type: presetKey,
created_by: interaction.user.id
});
if (error) {
if (error.code === '23505') {
return interaction.reply({ content: 'This preset event already exists for that time period.', ephemeral: true });
}
console.error('Preset event error:', error);
return interaction.reply({ content: 'Failed to create preset event.', ephemeral: true });
}
const embed = new EmbedBuilder()
.setColor(preset.color)
.setTitle(`${preset.emoji} ${preset.name} Enabled!`)
.setDescription(preset.description)
.addFields(
{ name: 'Start', value: `<t:${Math.floor(startDate.getTime() / 1000)}:F>`, inline: true },
{ name: 'End', value: `<t:${Math.floor(endDate.getTime() / 1000)}:F>`, inline: true },
{ name: 'XP Multiplier', value: `${preset.xp_multiplier}x`, inline: true }
)
.setTimestamp();
await interaction.reply({ embeds: [embed] });
}
if (subcommand === 'delete') {
const eventName = interaction.options.getString('name');
const { data: event } = await supabase
.from('seasonal_events')
.select('id, name, emoji')
.eq('guild_id', interaction.guildId)
.ilike('name', `%${eventName}%`)
.maybeSingle();
if (!event) {
return interaction.reply({ content: `No event found matching "${eventName}".`, ephemeral: true });
}
const { error } = await supabase
.from('seasonal_events')
.delete()
.eq('id', event.id);
if (error) {
return interaction.reply({ content: 'Failed to delete event.', ephemeral: true });
}
const embed = new EmbedBuilder()
.setColor(0xef4444)
.setTitle('Event Deleted')
.setDescription(`${event.emoji || '🎊'} **${event.name}** has been removed.`)
.setTimestamp();
await interaction.reply({ embeds: [embed] });
}
if (subcommand === 'announce') {
const channel = interaction.options.getChannel('channel');
const now = new Date();
const { data: events } = await supabase
.from('seasonal_events')
.select('*')
.eq('guild_id', interaction.guildId)
.lte('start_date', now.toISOString())
.gte('end_date', now.toISOString());
if (!events || events.length === 0) {
return interaction.reply({ content: 'No active events to announce.', ephemeral: true });
}
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('🎉 Seasonal Event Active!')
.setDescription('Special bonuses are now available!')
.setTimestamp();
for (const event of events) {
const endDate = new Date(event.end_date);
embed.addFields({
name: `${event.emoji || '🎊'} ${event.name}`,
value: `${event.description}\n\n**Bonuses:**\n${event.xp_multiplier}x XP multiplier\n${event.bonus_xp > 0 ? `• +${event.bonus_xp} XP per message\n` : ''}• Ends: <t:${Math.floor(endDate.getTime() / 1000)}:R>`,
inline: false
});
}
embed.setFooter({ text: `${events.length} event(s) active` });
try {
await channel.send({ embeds: [embed] });
await interaction.reply({ content: `Event announcement sent to ${channel}!`, ephemeral: true });
} catch (err) {
await interaction.reply({ content: 'Failed to send announcement. Check bot permissions.', ephemeral: true });
}
}
},
async autocomplete(interaction, supabase) {
if (!supabase) return;
const focusedValue = interaction.options.getFocused();
const { data: events } = await supabase
.from('seasonal_events')
.select('name')
.eq('guild_id', interaction.guildId)
.ilike('name', `%${focusedValue}%`)
.limit(25);
const choices = (events || []).map(e => ({ name: e.name, value: e.name }));
await interaction.respond(choices);
}
};