AeThex-Bot-Master/aethex-bot/commands/quests-manage.js
sirpiglr 7c10440040 Add quest system for users to complete objectives and earn rewards
Implement a new quest system with commands for users and administrators, including tracking progress for various objectives and awarding XP.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 3d62eaac-3ee6-4585-b52c-552b348253ee
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/zRLxuQq
Replit-Helium-Checkpoint-Created: true
2025-12-08 22:13:28 +00:00

605 lines
18 KiB
JavaScript

const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js');
const QUEST_TYPES = {
daily: { emoji: '☀️', name: 'Daily' },
weekly: { emoji: '📅', name: 'Weekly' },
special: { emoji: '⭐', name: 'Special' }
};
const OBJECTIVES = {
messages: { emoji: '💬', name: 'Send Messages' },
reactions: { emoji: '😄', name: 'Add Reactions' },
voice_minutes: { emoji: '🎙️', name: 'Voice Chat (minutes)' },
commands: { emoji: '⚡', name: 'Use Commands' },
daily_claims: { emoji: '🎁', name: 'Claim Daily Rewards' },
level_ups: { emoji: '📈', name: 'Level Up' },
xp_earned: { emoji: '✨', name: 'Earn XP' }
};
module.exports = {
data: new SlashCommandBuilder()
.setName('quests-manage')
.setDescription('Manage server quests (Admin only)')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addSubcommand(sub =>
sub.setName('create')
.setDescription('Create a new quest')
.addStringOption(opt =>
opt.setName('name')
.setDescription('Quest name')
.setRequired(true)
)
.addStringOption(opt =>
opt.setName('type')
.setDescription('Quest type')
.setRequired(true)
.addChoices(
{ name: '☀️ Daily', value: 'daily' },
{ name: '📅 Weekly', value: 'weekly' },
{ name: '⭐ Special', value: 'special' }
)
)
.addStringOption(opt =>
opt.setName('objective')
.setDescription('What users need to do')
.setRequired(true)
.addChoices(
{ name: '💬 Send Messages', value: 'messages' },
{ name: '😄 Add Reactions', value: 'reactions' },
{ name: '🎙️ Voice Chat (minutes)', value: 'voice_minutes' },
{ name: '⚡ Use Commands', value: 'commands' },
{ name: '🎁 Claim Daily Rewards', value: 'daily_claims' },
{ name: '📈 Level Up', value: 'level_ups' },
{ name: '✨ Earn XP', value: 'xp_earned' }
)
)
.addIntegerOption(opt =>
opt.setName('target')
.setDescription('Amount needed to complete')
.setRequired(true)
.setMinValue(1)
)
.addIntegerOption(opt =>
opt.setName('xp_reward')
.setDescription('XP reward for completion')
.setRequired(true)
.setMinValue(1)
)
.addStringOption(opt =>
opt.setName('description')
.setDescription('Quest description')
.setRequired(false)
)
.addRoleOption(opt =>
opt.setName('role_reward')
.setDescription('Role to grant on completion')
.setRequired(false)
)
.addBooleanOption(opt =>
opt.setName('repeatable')
.setDescription('Can users complete this quest multiple times?')
.setRequired(false)
)
.addIntegerOption(opt =>
opt.setName('duration_hours')
.setDescription('Quest duration in hours (auto-expires)')
.setRequired(false)
.setMinValue(1)
)
)
.addSubcommand(sub =>
sub.setName('delete')
.setDescription('Delete a quest')
.addIntegerOption(opt =>
opt.setName('quest_id')
.setDescription('The ID of the quest to delete')
.setRequired(true)
)
)
.addSubcommand(sub =>
sub.setName('edit')
.setDescription('Edit an existing quest')
.addIntegerOption(opt =>
opt.setName('quest_id')
.setDescription('The ID of the quest to edit')
.setRequired(true)
)
.addStringOption(opt =>
opt.setName('name')
.setDescription('New quest name')
.setRequired(false)
)
.addIntegerOption(opt =>
opt.setName('xp_reward')
.setDescription('New XP reward')
.setRequired(false)
.setMinValue(1)
)
.addIntegerOption(opt =>
opt.setName('target')
.setDescription('New target value')
.setRequired(false)
.setMinValue(1)
)
.addBooleanOption(opt =>
opt.setName('active')
.setDescription('Enable or disable the quest')
.setRequired(false)
)
)
.addSubcommand(sub =>
sub.setName('list')
.setDescription('List all quests')
)
.addSubcommand(sub =>
sub.setName('reset')
.setDescription('Reset quest progress for all users')
.addIntegerOption(opt =>
opt.setName('quest_id')
.setDescription('The ID of the quest to reset (leave empty to reset all)')
.setRequired(false)
)
.addStringOption(opt =>
opt.setName('type')
.setDescription('Reset all quests of a type')
.setRequired(false)
.addChoices(
{ name: '☀️ Daily', value: 'daily' },
{ name: '📅 Weekly', value: 'weekly' }
)
)
)
.addSubcommand(sub =>
sub.setName('stats')
.setDescription('View quest statistics')
),
async execute(interaction, supabase) {
if (!supabase) {
return interaction.reply({ content: 'Database not configured.', ephemeral: true });
}
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {
case 'create':
return handleCreate(interaction, supabase);
case 'delete':
return handleDelete(interaction, supabase);
case 'edit':
return handleEdit(interaction, supabase);
case 'list':
return handleList(interaction, supabase);
case 'reset':
return handleReset(interaction, supabase);
case 'stats':
return handleStats(interaction, supabase);
}
}
};
async function handleCreate(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
const guildId = interaction.guildId;
const name = interaction.options.getString('name');
const questType = interaction.options.getString('type');
const objective = interaction.options.getString('objective');
const target = interaction.options.getInteger('target');
const xpReward = interaction.options.getInteger('xp_reward');
const description = interaction.options.getString('description') || '';
const roleReward = interaction.options.getRole('role_reward');
const repeatable = interaction.options.getBoolean('repeatable') || false;
const durationHours = interaction.options.getInteger('duration_hours');
let expiresAt = null;
if (durationHours) {
expiresAt = new Date(Date.now() + durationHours * 60 * 60 * 1000).toISOString();
}
try {
const { data: newQuest, error } = await supabase
.from('quests')
.insert({
guild_id: guildId,
name: name,
description: description,
quest_type: questType,
objective: objective,
target_value: target,
xp_reward: xpReward,
role_reward: roleReward?.id || null,
repeatable: repeatable,
starts_at: new Date().toISOString(),
expires_at: expiresAt,
active: true
})
.select()
.single();
if (error) throw error;
const typeInfo = QUEST_TYPES[questType] || { emoji: '📋', name: questType };
const objInfo = OBJECTIVES[objective] || { emoji: '🎯', name: objective };
const embed = new EmbedBuilder()
.setColor(0x00ff00)
.setTitle('✅ Quest Created')
.addFields(
{ name: 'Quest ID', value: `#${newQuest.id}`, inline: true },
{ name: 'Name', value: name, inline: true },
{ name: 'Type', value: `${typeInfo.emoji} ${typeInfo.name}`, inline: true },
{ name: 'Objective', value: `${objInfo.emoji} ${target}x ${objInfo.name}`, inline: true },
{ name: 'XP Reward', value: `${xpReward.toLocaleString()} XP`, inline: true },
{ name: 'Repeatable', value: repeatable ? 'Yes' : 'No', inline: true }
);
if (description) {
embed.addFields({ name: 'Description', value: description, inline: false });
}
if (roleReward) {
embed.addFields({ name: 'Role Reward', value: `<@&${roleReward.id}>`, inline: true });
}
if (expiresAt) {
embed.addFields({
name: 'Expires',
value: `<t:${Math.floor(new Date(expiresAt).getTime() / 1000)}:R>`,
inline: true
});
}
embed.setTimestamp();
await interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error('Quest create error:', error);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xff0000)
.setDescription('Failed to create quest.')
]
});
}
}
async function handleDelete(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
const guildId = interaction.guildId;
const questId = interaction.options.getInteger('quest_id');
try {
const { data: quest } = await supabase
.from('quests')
.select('*')
.eq('id', questId)
.eq('guild_id', guildId)
.maybeSingle();
if (!quest) {
return interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xff6b6b)
.setDescription('Quest not found.')
]
});
}
await supabase
.from('quests')
.delete()
.eq('id', questId)
.eq('guild_id', guildId);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0x00ff00)
.setTitle('🗑️ Quest Deleted')
.setDescription(`**${quest.name}** has been deleted.`)
.setTimestamp()
]
});
} catch (error) {
console.error('Quest delete error:', error);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xff0000)
.setDescription('Failed to delete quest.')
]
});
}
}
async function handleEdit(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
const guildId = interaction.guildId;
const questId = interaction.options.getInteger('quest_id');
const newName = interaction.options.getString('name');
const newXpReward = interaction.options.getInteger('xp_reward');
const newTarget = interaction.options.getInteger('target');
const active = interaction.options.getBoolean('active');
try {
const { data: quest } = await supabase
.from('quests')
.select('*')
.eq('id', questId)
.eq('guild_id', guildId)
.maybeSingle();
if (!quest) {
return interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xff6b6b)
.setDescription('Quest not found.')
]
});
}
const updates = { updated_at: new Date().toISOString() };
const changes = [];
if (newName !== null) {
updates.name = newName;
changes.push(`Name: ${quest.name}${newName}`);
}
if (newXpReward !== null) {
updates.xp_reward = newXpReward;
changes.push(`XP Reward: ${quest.xp_reward}${newXpReward}`);
}
if (newTarget !== null) {
updates.target_value = newTarget;
changes.push(`Target: ${quest.target_value}${newTarget}`);
}
if (active !== null) {
updates.active = active;
changes.push(`Active: ${quest.active}${active}`);
}
if (changes.length === 0) {
return interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xfbbf24)
.setDescription('No changes specified.')
]
});
}
await supabase
.from('quests')
.update(updates)
.eq('id', questId);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0x00ff00)
.setTitle('✏️ Quest Updated')
.setDescription(`**${quest.name}** has been updated.`)
.addFields({ name: 'Changes', value: changes.join('\n') })
.setTimestamp()
]
});
} catch (error) {
console.error('Quest edit error:', error);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xff0000)
.setDescription('Failed to edit quest.')
]
});
}
}
async function handleList(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
const guildId = interaction.guildId;
try {
const { data: quests, error } = await supabase
.from('quests')
.select('*')
.eq('guild_id', guildId)
.order('quest_type')
.order('created_at', { ascending: false });
if (error || !quests || quests.length === 0) {
return interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xfbbf24)
.setTitle('📋 Server Quests')
.setDescription('No quests created yet.\n\nUse `/quests-manage create` to add quests.')
]
});
}
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('📋 Server Quests (Admin View)')
.setDescription(`Total quests: **${quests.length}**`)
.setTimestamp();
const questLines = quests.map(quest => {
const status = quest.active ? '✅' : '❌';
const typeInfo = QUEST_TYPES[quest.quest_type] || { emoji: '📋' };
const objInfo = OBJECTIVES[quest.objective] || { emoji: '🎯', name: quest.objective };
const expires = quest.expires_at && new Date(quest.expires_at) < new Date() ? ' ⏰' : '';
return `${status} ${typeInfo.emoji} \`#${quest.id}\` **${quest.name}** - ${objInfo.emoji} ${quest.target_value}x → ${quest.xp_reward} XP${expires}`;
});
const chunks = [];
let current = '';
for (const line of questLines) {
if ((current + '\n' + line).length > 1000) {
chunks.push(current);
current = line;
} else {
current = current ? current + '\n' + line : line;
}
}
if (current) chunks.push(current);
for (let i = 0; i < Math.min(chunks.length, 5); i++) {
embed.addFields({
name: i === 0 ? 'Quests' : '\u200b',
value: chunks[i],
inline: false
});
}
await interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error('Quest list error:', error);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xff0000)
.setDescription('Failed to list quests.')
]
});
}
}
async function handleReset(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
const guildId = interaction.guildId;
const questId = interaction.options.getInteger('quest_id');
const questType = interaction.options.getString('type');
try {
let deleteQuery = supabase
.from('user_quests')
.delete()
.eq('guild_id', guildId);
let description = '';
if (questId) {
deleteQuery = deleteQuery.eq('quest_id', questId);
description = `Progress reset for quest #${questId}`;
} else if (questType) {
const { data: quests } = await supabase
.from('quests')
.select('id')
.eq('guild_id', guildId)
.eq('quest_type', questType);
if (quests && quests.length > 0) {
deleteQuery = deleteQuery.in('quest_id', quests.map(q => q.id));
description = `Progress reset for all ${QUEST_TYPES[questType]?.name || questType} quests`;
} else {
return interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xfbbf24)
.setDescription('No quests found for that type.')
]
});
}
} else {
description = 'Progress reset for all quests';
}
await deleteQuery;
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0x00ff00)
.setTitle('🔄 Quest Progress Reset')
.setDescription(description)
.setTimestamp()
]
});
} catch (error) {
console.error('Quest reset error:', error);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xff0000)
.setDescription('Failed to reset quest progress.')
]
});
}
}
async function handleStats(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
const guildId = interaction.guildId;
try {
const { data: quests } = await supabase
.from('quests')
.select('*')
.eq('guild_id', guildId);
const { data: userQuests } = await supabase
.from('user_quests')
.select('*, quests(*)')
.eq('guild_id', guildId);
const totalQuests = quests?.length || 0;
const activeQuests = quests?.filter(q => q.active).length || 0;
const totalCompletions = userQuests?.filter(uq => uq.completed).length || 0;
const totalClaimed = userQuests?.filter(uq => uq.claimed).length || 0;
const uniqueParticipants = new Set(userQuests?.map(uq => uq.user_id)).size;
const totalXPRewarded = userQuests
?.filter(uq => uq.claimed && uq.quests)
.reduce((sum, uq) => sum + (uq.quests.xp_reward || 0), 0) || 0;
const questCompletions = {};
for (const uq of userQuests || []) {
if (uq.completed && uq.quests) {
const name = uq.quests.name;
questCompletions[name] = (questCompletions[name] || 0) + 1;
}
}
const topQuests = Object.entries(questCompletions)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.map(([name, count], i) => `${i + 1}. **${name}** (${count} completions)`)
.join('\n');
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('📊 Quest Statistics')
.addFields(
{ name: '📜 Total Quests', value: `${totalQuests} (${activeQuests} active)`, inline: true },
{ name: '✅ Total Completions', value: totalCompletions.toString(), inline: true },
{ name: '🎁 Total Claims', value: totalClaimed.toString(), inline: true },
{ name: '👥 Participants', value: uniqueParticipants.toString(), inline: true },
{ name: '💰 XP Rewarded', value: totalXPRewarded.toLocaleString(), inline: true }
)
.setTimestamp();
if (topQuests) {
embed.addFields({ name: '🏆 Most Completed Quests', value: topQuests, inline: false });
}
await interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error('Quest stats error:', error);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xff0000)
.setDescription('Failed to get statistics.')
]
});
}
}