308 lines
12 KiB
JavaScript
308 lines
12 KiB
JavaScript
const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
|
const { getServerMode, setServerMode, getEmbedColor, getModeDisplayName, getModeEmoji, EMBED_COLORS } = require('../utils/modeHelper');
|
|
const { setRobloxConfig, getRobloxConfig } = require('../utils/robloxConfig');
|
|
|
|
module.exports = {
|
|
data: new SlashCommandBuilder()
|
|
.setName('config')
|
|
.setDescription('Configure server settings')
|
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
|
.addSubcommand(sub =>
|
|
sub.setName('view')
|
|
.setDescription('View current server configuration')
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('welcome')
|
|
.setDescription('Set the welcome channel')
|
|
.addChannelOption(opt =>
|
|
opt.setName('channel')
|
|
.setDescription('Channel for welcome messages')
|
|
.addChannelTypes(ChannelType.GuildText)
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('goodbye')
|
|
.setDescription('Set the goodbye channel')
|
|
.addChannelOption(opt =>
|
|
opt.setName('channel')
|
|
.setDescription('Channel for goodbye messages')
|
|
.addChannelTypes(ChannelType.GuildText)
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('modlog')
|
|
.setDescription('Set the moderation log channel')
|
|
.addChannelOption(opt =>
|
|
opt.setName('channel')
|
|
.setDescription('Channel for mod logs')
|
|
.addChannelTypes(ChannelType.GuildText)
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('levelup')
|
|
.setDescription('Set the level-up announcement channel')
|
|
.addChannelOption(opt =>
|
|
opt.setName('channel')
|
|
.setDescription('Channel for level-up messages')
|
|
.addChannelTypes(ChannelType.GuildText)
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('autorole')
|
|
.setDescription('Set auto-role for new members')
|
|
.addRoleOption(opt =>
|
|
opt.setName('role')
|
|
.setDescription('Role to assign on join')
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('levelrole')
|
|
.setDescription('Add a role reward for reaching a level')
|
|
.addRoleOption(opt =>
|
|
opt.setName('role')
|
|
.setDescription('Role to give')
|
|
.setRequired(true)
|
|
)
|
|
.addIntegerOption(opt =>
|
|
opt.setName('level')
|
|
.setDescription('Level required')
|
|
.setRequired(true)
|
|
.setMinValue(1)
|
|
.setMaxValue(100)
|
|
)
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('mode')
|
|
.setDescription('Switch between Federation and Standalone mode')
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('roblox')
|
|
.setDescription('Set up Roblox game integration')
|
|
.addStringOption(o => o.setName('universe_id').setDescription('Roblox Universe ID').setRequired(true))
|
|
.addStringOption(o => o.setName('open_cloud_key').setDescription('Roblox Open Cloud API key').setRequired(true))
|
|
.addStringOption(o => o.setName('group_id').setDescription('Roblox Group ID (for rank lookups)').setRequired(false))
|
|
),
|
|
|
|
async execute(interaction, supabase, client) {
|
|
if (!supabase) {
|
|
return interaction.reply({ content: 'Database not configured.', ephemeral: true });
|
|
}
|
|
|
|
const subcommand = interaction.options.getSubcommand();
|
|
await interaction.deferReply({ ephemeral: true });
|
|
|
|
try {
|
|
if (subcommand === 'mode') {
|
|
const currentMode = await getServerMode(supabase, interaction.guildId);
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setColor(getEmbedColor(currentMode))
|
|
.setTitle('Server Mode Configuration')
|
|
.setDescription(
|
|
`${getModeEmoji(currentMode)} Currently running in **${getModeDisplayName(currentMode)}** mode\n\n` +
|
|
'Choose your server mode:'
|
|
)
|
|
.addFields(
|
|
{
|
|
name: '🌐 Federation Mode',
|
|
value: '• Unified XP across all AeThex servers\n• Cross-server profiles and leaderboards\n• Realm selection and role sync',
|
|
inline: true
|
|
},
|
|
{
|
|
name: '🏠 Standalone Mode',
|
|
value: '• Isolated XP system for this server\n• Local leaderboards only\n• Full moderation features',
|
|
inline: true
|
|
}
|
|
)
|
|
.setFooter({ text: 'This affects how XP and profiles work in your server' });
|
|
|
|
const row = new ActionRowBuilder()
|
|
.addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('config_mode_federated')
|
|
.setLabel('Federation')
|
|
.setStyle(currentMode === 'federated' ? ButtonStyle.Primary : ButtonStyle.Secondary)
|
|
.setEmoji('🌐')
|
|
.setDisabled(currentMode === 'federated'),
|
|
new ButtonBuilder()
|
|
.setCustomId('config_mode_standalone')
|
|
.setLabel('Standalone')
|
|
.setStyle(currentMode === 'standalone' ? ButtonStyle.Primary : ButtonStyle.Secondary)
|
|
.setEmoji('🏠')
|
|
.setDisabled(currentMode === 'standalone')
|
|
);
|
|
|
|
const response = await interaction.editReply({ embeds: [embed], components: [row] });
|
|
|
|
const collector = response.createMessageComponentCollector({ time: 60000 });
|
|
|
|
collector.on('collect', async (i) => {
|
|
if (i.user.id !== interaction.user.id) {
|
|
return i.reply({ content: 'Only the command user can change this.', ephemeral: true });
|
|
}
|
|
|
|
const newMode = i.customId === 'config_mode_federated' ? 'federated' : 'standalone';
|
|
const success = await setServerMode(supabase, interaction.guildId, newMode);
|
|
|
|
if (success) {
|
|
const confirmEmbed = new EmbedBuilder()
|
|
.setColor(getEmbedColor(newMode))
|
|
.setTitle(`${getModeEmoji(newMode)} Mode Changed!`)
|
|
.setDescription(
|
|
`Server is now running in **${getModeDisplayName(newMode)}** mode.\n\n` +
|
|
(newMode === 'federated'
|
|
? 'XP will now count globally across the AeThex ecosystem.'
|
|
: 'XP is now tracked locally for this server only.')
|
|
)
|
|
.setTimestamp();
|
|
|
|
await i.update({ embeds: [confirmEmbed], components: [] });
|
|
collector.stop();
|
|
} else {
|
|
await i.reply({ content: 'Failed to change mode. Please try again.', ephemeral: true });
|
|
}
|
|
});
|
|
|
|
collector.on('end', async (collected, reason) => {
|
|
if (reason === 'time') {
|
|
await interaction.editReply({ components: [] }).catch(() => {});
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
if (subcommand === 'view') {
|
|
const { data: config } = await supabase
|
|
.from('server_config')
|
|
.select('*')
|
|
.eq('guild_id', interaction.guildId)
|
|
.single();
|
|
|
|
const currentMode = config?.mode || 'federated';
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setColor(getEmbedColor(currentMode))
|
|
.setTitle('Server Configuration')
|
|
.addFields(
|
|
{ name: 'Server Mode', value: `${getModeEmoji(currentMode)} ${getModeDisplayName(currentMode)}`, inline: true },
|
|
{ name: 'Welcome Channel', value: config?.welcome_channel ? `<#${config.welcome_channel}>` : 'Not set', inline: true },
|
|
{ name: 'Goodbye Channel', value: config?.goodbye_channel ? `<#${config.goodbye_channel}>` : 'Not set', inline: true },
|
|
{ name: 'Mod Log Channel', value: config?.modlog_channel ? `<#${config.modlog_channel}>` : 'Not set', inline: true },
|
|
{ name: 'Level-Up Channel', value: config?.level_up_channel ? `<#${config.level_up_channel}>` : 'Not set', inline: true },
|
|
{ name: 'Auto Role', value: config?.auto_role ? `<@&${config.auto_role}>` : 'Not set', inline: true }
|
|
)
|
|
.setTimestamp();
|
|
|
|
const { data: levelRoles } = await supabase
|
|
.from('level_roles')
|
|
.select('role_id, level_required')
|
|
.eq('guild_id', interaction.guildId)
|
|
.order('level_required', { ascending: true });
|
|
|
|
if (levelRoles && levelRoles.length > 0) {
|
|
const roleText = levelRoles.map(lr => `Level ${lr.level_required}: <@&${lr.role_id}>`).join('\n');
|
|
embed.addFields({ name: 'Level Roles', value: roleText });
|
|
}
|
|
|
|
return interaction.editReply({ embeds: [embed] });
|
|
}
|
|
|
|
const updateField = {
|
|
welcome: 'welcome_channel',
|
|
goodbye: 'goodbye_channel',
|
|
modlog: 'modlog_channel',
|
|
levelup: 'level_up_channel',
|
|
autorole: 'auto_role',
|
|
};
|
|
|
|
if (subcommand === 'roblox') {
|
|
const universeId = interaction.options.getString('universe_id');
|
|
const openCloudKey = interaction.options.getString('open_cloud_key');
|
|
const groupId = interaction.options.getString('group_id') || null;
|
|
|
|
const ok = await setRobloxConfig(supabase, interaction.guildId, {
|
|
universe_id: universeId,
|
|
open_cloud_key: openCloudKey,
|
|
roblox_group_id: groupId,
|
|
});
|
|
|
|
if (!ok) return interaction.editReply('❌ Failed to save Roblox config. Check Supabase.');
|
|
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0x22c55e)
|
|
.setTitle('✅ Roblox Integration Enabled')
|
|
.addFields(
|
|
{ name: 'Universe ID', value: universeId, inline: true },
|
|
{ name: 'Group ID', value: groupId || '—', inline: true },
|
|
{ name: 'Commands', value: '`/roblox ban` `//roblox unban` `/roblox kick` `/roblox rank` `/roblox stats`', inline: false },
|
|
)
|
|
.setFooter({ text: 'Open Cloud key stored securely.' })
|
|
],
|
|
});
|
|
}
|
|
|
|
if (subcommand === 'levelrole') {
|
|
const role = interaction.options.getRole('role');
|
|
const level = interaction.options.getInteger('level');
|
|
|
|
await supabase.from('level_roles').upsert({
|
|
guild_id: interaction.guildId,
|
|
role_id: role.id,
|
|
level_required: level,
|
|
}, { onConflict: 'guild_id,role_id' });
|
|
|
|
if (!client.serverConfigs) client.serverConfigs = new Map();
|
|
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0x00ff00)
|
|
.setDescription(`${role} will now be given at level ${level}!`)
|
|
]
|
|
});
|
|
}
|
|
|
|
const fieldName = updateField[subcommand];
|
|
let value;
|
|
|
|
if (subcommand === 'autorole') {
|
|
value = interaction.options.getRole('role').id;
|
|
} else {
|
|
value = interaction.options.getChannel('channel').id;
|
|
}
|
|
|
|
await supabase.from('server_config').upsert({
|
|
guild_id: interaction.guildId,
|
|
[fieldName]: value,
|
|
updated_at: new Date().toISOString(),
|
|
}, { onConflict: 'guild_id' });
|
|
|
|
if (!client.serverConfigs) client.serverConfigs = new Map();
|
|
const current = client.serverConfigs.get(interaction.guildId) || {};
|
|
current[fieldName] = value;
|
|
client.serverConfigs.set(interaction.guildId, current);
|
|
|
|
const displayValue = subcommand === 'autorole' ? `<@&${value}>` : `<#${value}>`;
|
|
|
|
await interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0x00ff00)
|
|
.setDescription(`${subcommand.charAt(0).toUpperCase() + subcommand.slice(1)} set to ${displayValue}!`)
|
|
]
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Config error:', error);
|
|
await interaction.editReply({ content: 'Failed to update configuration.' });
|
|
}
|
|
},
|
|
};
|