/** * White-Label Branding System * Allows paying servers to customize bot identity */ const { WebhookClient, EmbedBuilder } = require('discord.js'); // Branding tier definitions const BRANDING_TIERS = { free: { name: 'Free', price: 0, features: [] }, basic: { name: 'Basic', price: 15, features: ['custom_name', 'custom_footer', 'custom_color'] }, pro: { name: 'Pro', price: 35, features: ['custom_name', 'custom_footer', 'custom_color', 'custom_avatar', 'custom_handle', 'landing_page'] }, enterprise: { name: 'Enterprise', price: 75, features: ['custom_name', 'custom_footer', 'custom_color', 'custom_avatar', 'custom_handle', 'landing_page', 'analytics', 'priority_support', 'custom_domain'] } }; // Reserved handles that can't be claimed const SYSTEM_HANDLES = [ 'admin', 'api', 'dashboard', 'login', 'auth', 'oauth', 'federation', 'pricing', 'features', 'commands', 'support', 'help', 'aethex', 'warden', 'official', 'staff', 'mod', 'moderator', 'bot', 'system', 'root' ]; // Cache for branding data (reduces DB calls) const brandingCache = new Map(); const CACHE_TTL = 5 * 60 * 1000; // 5 minutes /** * Get branding configuration for a guild */ async function getBranding(supabase, guildId) { // Check cache first const cached = brandingCache.get(guildId); if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached.data; } try { const { data, error } = await supabase .from('server_branding') .select('*') .eq('guild_id', guildId) .maybeSingle(); if (error) throw error; const result = data || getDefaultBranding(); // Cache the result brandingCache.set(guildId, { data: result, timestamp: Date.now() }); return result; } catch (err) { console.error('[Branding] Error fetching branding:', err); return getDefaultBranding(); } } /** * Get default branding (AeThex Warden) */ function getDefaultBranding() { return { custom_bot_name: null, custom_bot_avatar_url: null, custom_footer_text: null, custom_embed_color: null, custom_handle: null, tier: 'free', branding_enabled: false }; } /** * Check if a feature is available for a tier */ function hasFeature(tier, feature) { const tierConfig = BRANDING_TIERS[tier]; return tierConfig?.features?.includes(feature) || false; } /** * Get effective bot name for a guild */ async function getEffectiveBotName(supabase, guildId, defaultName = 'AeThex | Warden') { const branding = await getBranding(supabase, guildId); if (branding.branding_enabled && branding.custom_bot_name && hasFeature(branding.tier, 'custom_name')) { return branding.custom_bot_name; } return defaultName; } /** * Get effective avatar URL for a guild */ async function getEffectiveAvatar(supabase, guildId, defaultAvatar) { const branding = await getBranding(supabase, guildId); if (branding.branding_enabled && branding.custom_bot_avatar_url && hasFeature(branding.tier, 'custom_avatar')) { return branding.custom_bot_avatar_url; } return defaultAvatar; } /** * Get effective embed color for a guild */ async function getEffectiveColor(supabase, guildId, defaultColor = '#6366f1') { const branding = await getBranding(supabase, guildId); if (branding.branding_enabled && branding.custom_embed_color && hasFeature(branding.tier, 'custom_color')) { return branding.custom_embed_color; } return defaultColor; } /** * Get effective footer text for a guild */ async function getEffectiveFooter(supabase, guildId, defaultFooter = 'AeThex | Warden') { const branding = await getBranding(supabase, guildId); if (branding.branding_enabled && branding.custom_footer_text && hasFeature(branding.tier, 'custom_footer')) { return branding.custom_footer_text; } return defaultFooter; } /** * Create a branded embed for a guild */ async function createBrandedEmbed(supabase, guildId, options = {}) { const branding = await getBranding(supabase, guildId); const embed = new EmbedBuilder(); // Set color const color = branding.branding_enabled && branding.custom_embed_color && hasFeature(branding.tier, 'custom_color') ? branding.custom_embed_color : options.defaultColor || '#6366f1'; embed.setColor(color); // Set footer const footerText = branding.branding_enabled && branding.custom_footer_text && hasFeature(branding.tier, 'custom_footer') ? branding.custom_footer_text : options.defaultFooter || 'AeThex | Warden'; embed.setFooter({ text: footerText }); // Apply other options if (options.title) embed.setTitle(options.title); if (options.description) embed.setDescription(options.description); if (options.fields) embed.addFields(options.fields); if (options.thumbnail) embed.setThumbnail(options.thumbnail); if (options.image) embed.setImage(options.image); if (options.timestamp) embed.setTimestamp(); return embed; } /** * Send a branded message via webhook (for full white-label experience) */ async function sendBrandedMessage(channel, supabase, guildId, options = {}) { const branding = await getBranding(supabase, guildId); // If branding not enabled or no custom avatar, send normally if (!branding.branding_enabled || !hasFeature(branding.tier, 'custom_avatar')) { return channel.send(options); } try { // Get or create webhook for branding const webhooks = await channel.fetchWebhooks(); let webhook = webhooks.find(wh => wh.name === 'AeThex-Branding'); if (!webhook) { webhook = await channel.createWebhook({ name: 'AeThex-Branding', reason: 'White-label branding system' }); } // Send via webhook with custom identity const webhookClient = new WebhookClient({ url: webhook.url }); return await webhookClient.send({ username: branding.custom_bot_name || 'AeThex | Warden', avatarURL: branding.custom_bot_avatar_url || undefined, content: options.content, embeds: options.embeds, files: options.files, components: options.components }); } catch (err) { console.error('[Branding] Webhook send failed, falling back to normal:', err.message); return channel.send(options); } } /** * Update branding configuration */ async function updateBranding(supabase, guildId, updates, updatedBy) { try { const { error } = await supabase .from('server_branding') .upsert({ guild_id: guildId, ...updates, updated_at: new Date().toISOString(), created_by: updatedBy }, { onConflict: 'guild_id' }); if (error) throw error; // Invalidate cache brandingCache.delete(guildId); return { success: true }; } catch (err) { console.error('[Branding] Update error:', err); return { success: false, error: err.message }; } } /** * Claim a custom handle */ async function claimHandle(supabase, guildId, handle, tier) { // Validate handle format const cleanHandle = handle.toLowerCase().replace(/[^a-z0-9-]/g, ''); if (cleanHandle.length < 3 || cleanHandle.length > 30) { return { success: false, error: 'Handle must be 3-30 characters (letters, numbers, hyphens only)' }; } // Check if system reserved if (SYSTEM_HANDLES.includes(cleanHandle)) { return { success: false, error: 'This handle is reserved' }; } // Check tier permission if (!hasFeature(tier, 'custom_handle')) { return { success: false, error: 'Custom handles require Pro tier or higher' }; } try { // Check if reserved in database const { data: reserved } = await supabase .from('reserved_handles') .select('handle') .eq('handle', cleanHandle) .maybeSingle(); if (reserved) { return { success: false, error: 'This handle is reserved' }; } // Check if already taken const { data: existing } = await supabase .from('server_branding') .select('guild_id') .eq('custom_handle', cleanHandle) .neq('guild_id', guildId) .maybeSingle(); if (existing) { return { success: false, error: 'This handle is already taken' }; } // Claim it const { error } = await supabase .from('server_branding') .upsert({ guild_id: guildId, custom_handle: cleanHandle, handle_verified: true, updated_at: new Date().toISOString() }, { onConflict: 'guild_id' }); if (error) throw error; // Invalidate cache brandingCache.delete(guildId); return { success: true, handle: cleanHandle }; } catch (err) { console.error('[Branding] Claim handle error:', err); return { success: false, error: 'Failed to claim handle' }; } } /** * Get branding by custom handle */ async function getBrandingByHandle(supabase, handle) { try { const { data, error } = await supabase .from('server_branding') .select('*, federation_servers!inner(guild_name, guild_icon, member_count, description)') .eq('custom_handle', handle.toLowerCase()) .eq('handle_verified', true) .maybeSingle(); if (error) throw error; return data; } catch (err) { console.error('[Branding] Get by handle error:', err); return null; } } /** * Track landing page analytics */ async function trackAnalytics(supabase, guildId, eventType, metadata = {}) { try { await supabase.from('branding_analytics').insert({ guild_id: guildId, event_type: eventType, referrer: metadata.referrer || null, user_agent: metadata.userAgent || null, ip_hash: metadata.ipHash || null, metadata: metadata.extra || {} }); } catch (err) { // Silent fail for analytics console.error('[Branding] Analytics error:', err.message); } } /** * Invalidate branding cache for a guild */ function invalidateCache(guildId) { brandingCache.delete(guildId); } module.exports = { BRANDING_TIERS, getBranding, getDefaultBranding, hasFeature, getEffectiveBotName, getEffectiveAvatar, getEffectiveColor, getEffectiveFooter, createBrandedEmbed, sendBrandedMessage, updateBranding, claimHandle, getBrandingByHandle, trackAnalytics, invalidateCache };