479 lines
18 KiB
JavaScript
479 lines
18 KiB
JavaScript
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.dev/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.dev/${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://bot.aethex.dev/dashboard?guild=${guildId}&page=branding`)
|
|
.setStyle(ButtonStyle.Link),
|
|
new ButtonBuilder()
|
|
.setLabel('Upgrade Tier')
|
|
.setURL('https://bot.aethex.dev/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.dev/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://bot.aethex.dev/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://bot.aethex.dev/${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://bot.aethex.dev/${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.' });
|
|
}
|
|
}
|
|
};
|