Adds new API endpoints and frontend components for managing automatic role rewards based on various user activity milestones, including validation for milestone values and improved handling of guild data. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 2417c2c2-9cf1-48e0-b7b0-da47f0f0530a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/bakeZwZ Replit-Helium-Checkpoint-Created: true
282 lines
10 KiB
JavaScript
282 lines
10 KiB
JavaScript
const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js');
|
|
|
|
module.exports = {
|
|
data: new SlashCommandBuilder()
|
|
.setName('activity-roles')
|
|
.setDescription('Manage automatic role rewards for activity milestones')
|
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
|
.addSubcommand(sub =>
|
|
sub.setName('add')
|
|
.setDescription('Add a role reward for an activity milestone')
|
|
.addRoleOption(opt =>
|
|
opt.setName('role')
|
|
.setDescription('The role to award')
|
|
.setRequired(true))
|
|
.addStringOption(opt =>
|
|
opt.setName('type')
|
|
.setDescription('Type of activity milestone')
|
|
.setRequired(true)
|
|
.addChoices(
|
|
{ name: 'Messages - Total messages sent', value: 'messages' },
|
|
{ name: 'Voice Hours - Time in voice channels', value: 'voice_hours' },
|
|
{ name: 'Daily Streak - Consecutive daily claims', value: 'daily_streak' },
|
|
{ name: 'Reactions Given - Reactions added to messages', value: 'reactions_given' },
|
|
{ name: 'Reactions Received - Reactions on your messages', value: 'reactions_received' },
|
|
{ name: 'Commands Used - Bot commands used', value: 'commands_used' }
|
|
))
|
|
.addIntegerOption(opt =>
|
|
opt.setName('value')
|
|
.setDescription('Milestone value (message count, hours, streak days, etc.)')
|
|
.setRequired(true)
|
|
.setMinValue(1))
|
|
.addBooleanOption(opt =>
|
|
opt.setName('stack')
|
|
.setDescription('Keep previous milestone roles (true) or replace them (false)')
|
|
.setRequired(false)))
|
|
.addSubcommand(sub =>
|
|
sub.setName('remove')
|
|
.setDescription('Remove an activity role reward')
|
|
.addRoleOption(opt =>
|
|
opt.setName('role')
|
|
.setDescription('The role to remove from rewards')
|
|
.setRequired(true)))
|
|
.addSubcommand(sub =>
|
|
sub.setName('list')
|
|
.setDescription('View all configured activity role rewards'))
|
|
.addSubcommand(sub =>
|
|
sub.setName('clear')
|
|
.setDescription('Clear all role rewards for an activity type')
|
|
.addStringOption(opt =>
|
|
opt.setName('type')
|
|
.setDescription('Type of activity to clear')
|
|
.setRequired(true)
|
|
.addChoices(
|
|
{ name: 'Message roles', value: 'messages' },
|
|
{ name: 'Voice hour roles', value: 'voice_hours' },
|
|
{ name: 'Daily streak roles', value: 'daily_streak' },
|
|
{ name: 'Reactions given roles', value: 'reactions_given' },
|
|
{ name: 'Reactions received roles', value: 'reactions_received' },
|
|
{ name: 'Commands used roles', value: 'commands_used' },
|
|
{ name: 'All activity roles', value: 'all' }
|
|
))),
|
|
|
|
async execute(interaction, client, supabase) {
|
|
if (!supabase) {
|
|
return interaction.reply({
|
|
content: '❌ Database not configured. Activity roles require Supabase.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
|
|
const guildId = interaction.guildId;
|
|
const subcommand = interaction.options.getSubcommand();
|
|
|
|
switch (subcommand) {
|
|
case 'add':
|
|
return handleAdd(interaction, supabase, guildId);
|
|
case 'remove':
|
|
return handleRemove(interaction, supabase, guildId);
|
|
case 'list':
|
|
return handleList(interaction, supabase, guildId);
|
|
case 'clear':
|
|
return handleClear(interaction, supabase, guildId);
|
|
}
|
|
}
|
|
};
|
|
|
|
const ACTIVITY_TYPES = ['messages', 'voice_hours', 'daily_streak', 'reactions_given', 'reactions_received', 'commands_used'];
|
|
|
|
async function handleAdd(interaction, supabase, guildId) {
|
|
const role = interaction.options.getRole('role');
|
|
const type = interaction.options.getString('type');
|
|
const value = interaction.options.getInteger('value');
|
|
const stack = interaction.options.getBoolean('stack') ?? true;
|
|
|
|
if (role.managed) {
|
|
return interaction.reply({
|
|
content: '❌ Cannot use managed roles (bot roles, integration roles) as rewards.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
|
|
const botMember = interaction.guild.members.me;
|
|
if (role.position >= botMember.roles.highest.position) {
|
|
return interaction.reply({
|
|
content: '❌ I cannot assign this role. It is higher than or equal to my highest role.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
|
|
try {
|
|
const { error } = await supabase
|
|
.from('activity_roles')
|
|
.upsert({
|
|
guild_id: guildId,
|
|
role_id: role.id,
|
|
milestone_type: type,
|
|
milestone_value: value,
|
|
stack_roles: stack,
|
|
created_at: new Date().toISOString()
|
|
}, { onConflict: 'guild_id,role_id' });
|
|
|
|
if (error) throw error;
|
|
|
|
const typeNames = {
|
|
messages: 'Messages',
|
|
voice_hours: 'Voice Hours',
|
|
daily_streak: 'Daily Streak',
|
|
reactions_given: 'Reactions Given',
|
|
reactions_received: 'Reactions Received',
|
|
commands_used: 'Commands Used'
|
|
};
|
|
|
|
const valueDisplay = type === 'voice_hours' ? `${value} hour(s)` :
|
|
type === 'daily_streak' ? `${value} day(s)` :
|
|
value.toLocaleString();
|
|
|
|
return interaction.reply({
|
|
content: `✅ ${role} will be awarded at **${valueDisplay} ${typeNames[type]}**.\nRole stacking: ${stack ? 'Enabled (keep previous roles)' : 'Disabled (replace previous roles)'}`,
|
|
ephemeral: true
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to add activity role:', error.message);
|
|
return interaction.reply({
|
|
content: '❌ Failed to add activity role reward. Please try again.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
}
|
|
|
|
async function handleRemove(interaction, supabase, guildId) {
|
|
const role = interaction.options.getRole('role');
|
|
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('activity_roles')
|
|
.delete()
|
|
.eq('guild_id', guildId)
|
|
.eq('role_id', role.id)
|
|
.select();
|
|
|
|
if (error) throw error;
|
|
|
|
if (!data || data.length === 0) {
|
|
return interaction.reply({
|
|
content: `❌ ${role} is not configured as an activity role reward.`,
|
|
ephemeral: true
|
|
});
|
|
}
|
|
|
|
return interaction.reply({
|
|
content: `✅ Removed ${role} from activity role rewards.`,
|
|
ephemeral: true
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to remove activity role:', error.message);
|
|
return interaction.reply({
|
|
content: '❌ Failed to remove activity role reward. Please try again.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
}
|
|
|
|
async function handleList(interaction, supabase, guildId) {
|
|
try {
|
|
const { data: roles, error } = await supabase
|
|
.from('activity_roles')
|
|
.select('*')
|
|
.eq('guild_id', guildId)
|
|
.order('milestone_type')
|
|
.order('milestone_value', { ascending: true });
|
|
|
|
if (error) throw error;
|
|
|
|
if (!roles || roles.length === 0) {
|
|
return interaction.reply({
|
|
content: '📋 No activity role rewards configured. Use `/activity-roles add` to create some!',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
|
|
const messageRoles = roles.filter(r => r.milestone_type === 'messages');
|
|
const voiceRoles = roles.filter(r => r.milestone_type === 'voice_hours');
|
|
const streakRoles = roles.filter(r => r.milestone_type === 'daily_streak');
|
|
const reactGivenRoles = roles.filter(r => r.milestone_type === 'reactions_given');
|
|
const reactReceivedRoles = roles.filter(r => r.milestone_type === 'reactions_received');
|
|
const commandRoles = roles.filter(r => r.milestone_type === 'commands_used');
|
|
|
|
const formatRoles = (roleList, suffix) => {
|
|
if (roleList.length === 0) return 'None configured';
|
|
return roleList.map(r => {
|
|
const stackIcon = r.stack_roles ? '📚' : '🔄';
|
|
return `${stackIcon} <@&${r.role_id}> → ${r.milestone_value.toLocaleString()}${suffix}`;
|
|
}).join('\n');
|
|
};
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setTitle('🎯 Activity Role Rewards')
|
|
.setColor(0x10B981)
|
|
.setDescription('Roles awarded automatically when users reach activity milestones.')
|
|
.addFields(
|
|
{ name: '💬 Message Roles', value: formatRoles(messageRoles, ' messages'), inline: false },
|
|
{ name: '🎤 Voice Hour Roles', value: formatRoles(voiceRoles, ' hours'), inline: false },
|
|
{ name: '🔥 Daily Streak Roles', value: formatRoles(streakRoles, ' day streak'), inline: false }
|
|
)
|
|
.setFooter({ text: '📚 = Stack roles | 🔄 = Replace previous' })
|
|
.setTimestamp();
|
|
|
|
if (reactGivenRoles.length > 0 || reactReceivedRoles.length > 0 || commandRoles.length > 0) {
|
|
embed.addFields(
|
|
{ name: '👍 Reactions Given Roles', value: formatRoles(reactGivenRoles, ' reactions'), inline: false },
|
|
{ name: '❤️ Reactions Received Roles', value: formatRoles(reactReceivedRoles, ' reactions'), inline: false },
|
|
{ name: '⚡ Command Usage Roles', value: formatRoles(commandRoles, ' commands'), inline: false }
|
|
);
|
|
}
|
|
|
|
return interaction.reply({ embeds: [embed] });
|
|
} catch (error) {
|
|
console.error('Failed to list activity roles:', error.message);
|
|
return interaction.reply({
|
|
content: '❌ Failed to fetch activity role rewards. Please try again.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
}
|
|
|
|
async function handleClear(interaction, supabase, guildId) {
|
|
const type = interaction.options.getString('type');
|
|
|
|
try {
|
|
let query = supabase.from('activity_roles').delete().eq('guild_id', guildId);
|
|
|
|
if (type !== 'all') {
|
|
query = query.eq('milestone_type', type);
|
|
}
|
|
|
|
const { data, error } = await query.select();
|
|
|
|
if (error) throw error;
|
|
|
|
const count = data?.length || 0;
|
|
const typeNames = {
|
|
messages: 'message',
|
|
voice_hours: 'voice hour',
|
|
daily_streak: 'daily streak',
|
|
reactions_given: 'reactions given',
|
|
reactions_received: 'reactions received',
|
|
commands_used: 'command usage',
|
|
all: 'activity'
|
|
};
|
|
|
|
return interaction.reply({
|
|
content: `✅ Cleared **${count}** ${typeNames[type]} role reward(s).`,
|
|
ephemeral: true
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to clear activity roles:', error.message);
|
|
return interaction.reply({
|
|
content: '❌ Failed to clear activity role rewards. Please try again.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
}
|