AeThex-Bot-Master/aethex-bot/utils/brandingManager.js

375 lines
10 KiB
JavaScript

/**
* 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
};