const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder } = require('discord.js'); const { getBranding, updateBranding, claimHandle, hasFeature, BRANDING_TIERS, createBrandedEmbed } = require('../utils/brandingManager'); module.exports = { data: new SlashCommandBuilder() .setName('branding') .setDescription('Configure white-label branding for your server') .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addSubcommand(sub => sub .setName('view') .setDescription('View current branding settings') ) .addSubcommand(sub => sub .setName('name') .setDescription('Set custom bot name (Basic tier+)') .addStringOption(opt => opt .setName('name') .setDescription('Custom bot name (max 80 chars)') .setRequired(true) .setMaxLength(80) ) ) .addSubcommand(sub => sub .setName('avatar') .setDescription('Set custom bot avatar (Pro tier+)') .addStringOption(opt => opt .setName('url') .setDescription('Avatar image URL (must be https)') .setRequired(true) ) ) .addSubcommand(sub => sub .setName('footer') .setDescription('Set custom embed footer (Basic tier+)') .addStringOption(opt => opt .setName('text') .setDescription('Footer text (max 200 chars)') .setRequired(true) .setMaxLength(200) ) ) .addSubcommand(sub => sub .setName('color') .setDescription('Set custom embed color (Basic tier+)') .addStringOption(opt => opt .setName('hex') .setDescription('Hex color (e.g., #ff5500)') .setRequired(true) ) ) .addSubcommand(sub => sub .setName('handle') .setDescription('Claim your custom URL handle (Pro tier+)') .addStringOption(opt => opt .setName('handle') .setDescription('Your custom handle (aethex.bot/YOUR-HANDLE)') .setRequired(true) .setMinLength(3) .setMaxLength(30) ) ) .addSubcommand(sub => sub .setName('landing') .setDescription('Configure your landing page (Pro tier+)') .addStringOption(opt => opt .setName('title') .setDescription('Page title') .setMaxLength(100) ) .addStringOption(opt => opt .setName('description') .setDescription('Page description') .setMaxLength(500) ) .addStringOption(opt => opt .setName('banner') .setDescription('Banner image URL') ) .addStringOption(opt => opt .setName('invite') .setDescription('Discord invite URL') ) ) .addSubcommand(sub => sub .setName('toggle') .setDescription('Enable or disable branding') .addBooleanOption(opt => opt .setName('enabled') .setDescription('Enable custom branding?') .setRequired(true) ) ) .addSubcommand(sub => sub .setName('preview') .setDescription('Preview your branded embeds') ) .addSubcommand(sub => sub .setName('tiers') .setDescription('View branding tier pricing and features') ) .addSubcommand(sub => sub .setName('reset') .setDescription('Reset branding to defaults') ), async execute(interaction, supabase) { if (!supabase) { return interaction.reply({ content: 'Database not available.', ephemeral: true }); } const subcommand = interaction.options.getSubcommand(); const guildId = interaction.guildId; await interaction.deferReply({ ephemeral: true }); try { const branding = await getBranding(supabase, guildId); // View current branding if (subcommand === 'view') { const tierInfo = BRANDING_TIERS[branding.tier || 'free']; const embed = new EmbedBuilder() .setColor(branding.custom_embed_color || '#6366f1') .setTitle('šŸ·ļø White-Label Branding Settings') .setDescription( `**Current Tier:** ${tierInfo.name} (${tierInfo.price > 0 ? `$${tierInfo.price}/mo` : 'Free'})\n` + `**Status:** ${branding.branding_enabled ? 'āœ… Enabled' : 'āŒ Disabled'}` ) .addFields( { name: 'Custom Bot Name', value: branding.custom_bot_name || '*Not set*', inline: true }, { name: 'Custom Footer', value: branding.custom_footer_text || '*Not set*', inline: true }, { name: 'Embed Color', value: branding.custom_embed_color || '*Default*', inline: true }, { name: 'Custom Avatar', value: branding.custom_bot_avatar_url ? 'āœ… Set' : '*Not set*', inline: true }, { name: 'Custom Handle', value: branding.custom_handle ? `aethex.bot/${branding.custom_handle}` : '*Not claimed*', inline: true }, { name: 'Landing Page', value: branding.landing_title ? 'āœ… Configured' : '*Not set*', inline: true } ) .setFooter({ text: 'Use /branding tiers to see upgrade options' }) .setTimestamp(); if (branding.custom_bot_avatar_url) { embed.setThumbnail(branding.custom_bot_avatar_url); } const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setLabel('Manage on Dashboard') .setURL(`https://aethex.bot/dashboard?guild=${guildId}&page=branding`) .setStyle(ButtonStyle.Link), new ButtonBuilder() .setLabel('Upgrade Tier') .setURL('https://aethex.bot/pricing#branding') .setStyle(ButtonStyle.Link) ); return interaction.editReply({ embeds: [embed], components: [row] }); } // View pricing tiers if (subcommand === 'tiers') { const embed = new EmbedBuilder() .setColor('#6366f1') .setTitle('šŸ·ļø White-Label Branding Tiers') .setDescription('Customize Warden to match your community brand!') .addFields( { name: 'šŸ†“ Free', value: '• Default AeThex | Warden branding\n• All bot features included\n• No customization', inline: true }, { name: 'šŸ’Ž Basic - $15/mo', value: '• Custom bot name\n• Custom footer text\n• Custom embed color\n• Removes "AeThex" branding', inline: true }, { name: '⭐ Pro - $35/mo', value: '• Everything in Basic\n• Custom bot avatar\n• Custom URL handle\n• Landing page\n• `aethex.bot/your-name`', inline: true }, { name: 'šŸ† Enterprise - $75/mo', value: '• Everything in Pro\n• Analytics dashboard\n• Priority support\n• Custom domain support\n• White-glove setup', inline: false } ) .setFooter({ text: 'All tiers include full bot functionality • Payments via Stripe' }); const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setLabel('Subscribe Now') .setURL('https://aethex.bot/pricing#branding') .setStyle(ButtonStyle.Link) .setEmoji('šŸ’³') ); return interaction.editReply({ embeds: [embed], components: [row] }); } // Set custom name if (subcommand === 'name') { if (!hasFeature(branding.tier, 'custom_name')) { return interaction.editReply({ content: 'āŒ Custom bot names require **Basic tier** or higher. Use `/branding tiers` to see pricing.', ephemeral: true }); } const name = interaction.options.getString('name'); const result = await updateBranding(supabase, guildId, { custom_bot_name: name }, interaction.user.id); if (result.success) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor('#22c55e') .setTitle('āœ… Bot Name Updated') .setDescription(`Bot will now appear as **${name}** in branded messages.`) .setFooter({ text: 'Make sure branding is enabled with /branding toggle' }) ] }); } return interaction.editReply({ content: `āŒ Failed: ${result.error}` }); } // Set custom avatar if (subcommand === 'avatar') { if (!hasFeature(branding.tier, 'custom_avatar')) { return interaction.editReply({ content: 'āŒ Custom avatars require **Pro tier** or higher. Use `/branding tiers` to see pricing.', ephemeral: true }); } const url = interaction.options.getString('url'); // Basic URL validation if (!url.startsWith('https://')) { return interaction.editReply({ content: 'āŒ Avatar URL must start with https://' }); } const result = await updateBranding(supabase, guildId, { custom_bot_avatar_url: url }, interaction.user.id); if (result.success) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor('#22c55e') .setTitle('āœ… Bot Avatar Updated') .setDescription('Bot will now use your custom avatar in branded messages.') .setThumbnail(url) .setFooter({ text: 'Webhook-based messages will show custom avatar' }) ] }); } return interaction.editReply({ content: `āŒ Failed: ${result.error}` }); } // Set custom footer if (subcommand === 'footer') { if (!hasFeature(branding.tier, 'custom_footer')) { return interaction.editReply({ content: 'āŒ Custom footers require **Basic tier** or higher. Use `/branding tiers` to see pricing.', ephemeral: true }); } const text = interaction.options.getString('text'); const result = await updateBranding(supabase, guildId, { custom_footer_text: text }, interaction.user.id); if (result.success) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor('#22c55e') .setTitle('āœ… Footer Updated') .setDescription('Embeds will now show your custom footer.') .setFooter({ text: text }) ] }); } return interaction.editReply({ content: `āŒ Failed: ${result.error}` }); } // Set custom color if (subcommand === 'color') { if (!hasFeature(branding.tier, 'custom_color')) { return interaction.editReply({ content: 'āŒ Custom colors require **Basic tier** or higher. Use `/branding tiers` to see pricing.', ephemeral: true }); } let hex = interaction.options.getString('hex'); // Validate and normalize hex color if (!hex.startsWith('#')) hex = '#' + hex; if (!/^#[0-9A-Fa-f]{6}$/.test(hex)) { return interaction.editReply({ content: 'āŒ Invalid hex color. Use format: #ff5500' }); } const result = await updateBranding(supabase, guildId, { custom_embed_color: hex }, interaction.user.id); if (result.success) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(hex) .setTitle('āœ… Embed Color Updated') .setDescription(`Embeds will now use **${hex}** as the accent color.`) ] }); } return interaction.editReply({ content: `āŒ Failed: ${result.error}` }); } // Claim custom handle if (subcommand === 'handle') { const handle = interaction.options.getString('handle'); const result = await claimHandle(supabase, guildId, handle, branding.tier); if (result.success) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor('#22c55e') .setTitle('āœ… Handle Claimed!') .setDescription(`Your community page is now live at:\n\nšŸ”— **https://aethex.bot/${result.handle}**`) .addFields( { name: 'Configure Your Page', value: 'Use `/branding landing` to customize your page content.' } ) .setFooter({ text: 'Share this link to promote your server!' }) ] }); } return interaction.editReply({ content: `āŒ ${result.error}` }); } // Configure landing page if (subcommand === 'landing') { if (!hasFeature(branding.tier, 'landing_page')) { return interaction.editReply({ content: 'āŒ Landing pages require **Pro tier** or higher. Use `/branding tiers` to see pricing.', ephemeral: true }); } if (!branding.custom_handle) { return interaction.editReply({ content: 'āŒ You must claim a handle first with `/branding handle`', ephemeral: true }); } const updates = {}; const title = interaction.options.getString('title'); const description = interaction.options.getString('description'); const banner = interaction.options.getString('banner'); const invite = interaction.options.getString('invite'); if (title) updates.landing_title = title; if (description) updates.landing_description = description; if (banner) updates.landing_banner_url = banner; if (invite) updates.landing_invite_url = invite; if (Object.keys(updates).length === 0) { return interaction.editReply({ content: 'Please provide at least one option to update.', ephemeral: true }); } const result = await updateBranding(supabase, guildId, updates, interaction.user.id); if (result.success) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor('#22c55e') .setTitle('āœ… Landing Page Updated') .setDescription(`View your page at:\nšŸ”— **https://aethex.bot/${branding.custom_handle}**`) .addFields( Object.entries(updates).map(([key, value]) => ({ name: key.replace('landing_', '').replace('_', ' '), value: value.length > 50 ? value.substring(0, 50) + '...' : value, inline: true })) ) ] }); } return interaction.editReply({ content: `āŒ Failed: ${result.error}` }); } // Toggle branding if (subcommand === 'toggle') { const enabled = interaction.options.getBoolean('enabled'); if (enabled && branding.tier === 'free') { return interaction.editReply({ content: 'āŒ Custom branding requires a paid tier. Use `/branding tiers` to see pricing.', ephemeral: true }); } const result = await updateBranding(supabase, guildId, { branding_enabled: enabled }, interaction.user.id); if (result.success) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(enabled ? '#22c55e' : '#ef4444') .setTitle(enabled ? 'āœ… Branding Enabled' : 'āŒ Branding Disabled') .setDescription( enabled ? 'Bot messages will now use your custom branding!' : 'Bot messages will show default AeThex | Warden branding.' ) ] }); } return interaction.editReply({ content: `āŒ Failed: ${result.error}` }); } // Preview branded embed if (subcommand === 'preview') { const previewEmbed = await createBrandedEmbed(supabase, guildId, { title: 'šŸŽ‰ Sample Announcement', description: 'This is how your branded embeds will look!\n\nNotice the custom color and footer below.', fields: [ { name: 'Feature', value: 'Custom branding', inline: true }, { name: 'Status', value: branding.branding_enabled ? 'āœ… Active' : 'āŒ Inactive', inline: true } ], timestamp: true }); return interaction.editReply({ content: branding.branding_enabled ? '**Preview of your branded embed:**' : '**Preview (branding not enabled - showing defaults):**', embeds: [previewEmbed] }); } // Reset branding if (subcommand === 'reset') { const result = await updateBranding(supabase, guildId, { custom_bot_name: null, custom_bot_avatar_url: null, custom_footer_text: null, custom_embed_color: null, landing_title: null, landing_description: null, landing_banner_url: null, branding_enabled: false }, interaction.user.id); if (result.success) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor('#f59e0b') .setTitle('šŸ”„ Branding Reset') .setDescription('All custom branding has been cleared. Default AeThex | Warden branding restored.') .setFooter({ text: 'Your custom handle has been preserved' }) ] }); } return interaction.editReply({ content: `āŒ Failed: ${result.error}` }); } } catch (error) { console.error('[Branding Command] Error:', error); return interaction.editReply({ content: 'āŒ An error occurred. Please try again.' }); } } };