From 43394c8fe1967b93c72c6a3882a634acad8b98e4 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Mon, 8 Dec 2025 04:50:38 +0000 Subject: [PATCH] Add management API endpoints for server configuration and whitelisting Adds GET and POST endpoints for managing server configurations, a GET endpoint for whitelisted servers, and associated CSS styling for dashboard elements in `bot.js` and `dashboard.html`. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: d92bdd14-c3cd-4d3a-92c1-cb648a408c03 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/ocC7ZpF Replit-Helium-Checkpoint-Created: true --- .replit | 4 - aethex-bot/bot.js | 248 +++++++++++ aethex-bot/public/dashboard.html | 718 ++++++++++++++++++++++++++++++- 3 files changed, 959 insertions(+), 11 deletions(-) diff --git a/.replit b/.replit index e23466d..5b6f647 100644 --- a/.replit +++ b/.replit @@ -22,10 +22,6 @@ externalPort = 80 localPort = 8080 externalPort = 8080 -[[ports]] -localPort = 35789 -externalPort = 3000 - [workflows] runButton = "Project" diff --git a/aethex-bot/bot.js b/aethex-bot/bot.js index 14d0658..8c84dd4 100644 --- a/aethex-bot/bot.js +++ b/aethex-bot/bot.js @@ -1317,6 +1317,254 @@ http } } + // ============================================================================= + // MANAGEMENT API ENDPOINTS (Server Config, Whitelist, Roles, Announcements) + // ============================================================================= + + // GET /server-config/:guildId - Get server configuration + if (req.url.startsWith("/server-config/") && req.method === "GET") { + const guildId = req.url.split("/server-config/")[1].split("?")[0]; + + if (!guildId) { + res.writeHead(400); + res.end(JSON.stringify({ error: "Guild ID required" })); + return; + } + + const config = serverConfigs.get(guildId) || { + guild_id: guildId, + welcome_channel: null, + goodbye_channel: null, + modlog_channel: null, + level_up_channel: null, + auto_role: null, + }; + + const guild = client.guilds.cache.get(guildId); + const channels = guild ? guild.channels.cache + .filter(c => c.type === 0) // Text channels + .map(c => ({ id: c.id, name: c.name })) : []; + const roles = guild ? guild.roles.cache + .filter(r => r.name !== '@everyone') + .map(r => ({ id: r.id, name: r.name, color: r.hexColor })) : []; + + res.writeHead(200); + res.end(JSON.stringify({ + config, + channels, + roles, + guildName: guild?.name || 'Unknown', + })); + return; + } + + // POST /server-config - Save server configuration + if (req.url === "/server-config" && req.method === "POST") { + if (!checkAdminAuth(req)) { + res.writeHead(401); + res.end(JSON.stringify({ error: "Unauthorized" })); + return; + } + + let body = ""; + req.on("data", chunk => body += chunk); + req.on("end", async () => { + try { + const data = JSON.parse(body); + const guildId = data.guild_id; + + if (!guildId) { + res.writeHead(400); + res.end(JSON.stringify({ error: "Guild ID required" })); + return; + } + + const configData = { + guild_id: guildId, + welcome_channel: data.welcome_channel || null, + goodbye_channel: data.goodbye_channel || null, + modlog_channel: data.modlog_channel || null, + level_up_channel: data.level_up_channel || null, + auto_role: data.auto_role || null, + updated_at: new Date().toISOString(), + }; + + serverConfigs.set(guildId, configData); + + if (supabase) { + const { error: dbError } = await supabase.from('server_config').upsert(configData); + if (dbError) { + console.error('[Config] Failed to save to database:', dbError.message); + res.writeHead(500); + res.end(JSON.stringify({ error: 'Failed to persist config to database', details: dbError.message })); + return; + } + } + + res.writeHead(200); + res.end(JSON.stringify({ success: true, config: configData, persisted: !!supabase })); + } catch (error) { + res.writeHead(500); + res.end(JSON.stringify({ error: error.message })); + } + }); + return; + } + + // GET /whitelist - Get whitelisted servers and users + if (req.url === "/whitelist" && req.method === "GET") { + const whitelistedServers = WHITELISTED_GUILDS.map(guildId => { + const guild = client.guilds.cache.get(guildId); + return { + id: guildId, + name: guild?.name || 'Not Connected', + memberCount: guild?.memberCount || 0, + connected: !!guild, + }; + }); + + res.writeHead(200); + res.end(JSON.stringify({ + servers: whitelistedServers, + users: whitelistedUsers.map(id => ({ id, note: 'Whitelisted User' })), + timestamp: new Date().toISOString(), + })); + return; + } + + // GET /roles - Get level roles and federation roles + if (req.url === "/roles" && req.method === "GET") { + (async () => { + const levelRoles = []; + const fedRoles = Array.from(federationMappings.entries()).map(([roleId, data]) => ({ + roleId, + name: data.name, + guildId: data.guildId, + guildName: data.guildName, + linkedAt: new Date(data.linkedAt).toISOString(), + })); + + // Try to get level roles from Supabase + if (supabase) { + try { + const { data } = await supabase.from('level_roles').select('*'); + if (data) { + for (const lr of data) { + const guild = client.guilds.cache.get(lr.guild_id); + const role = guild?.roles.cache.get(lr.role_id); + levelRoles.push({ + guildId: lr.guild_id, + guildName: guild?.name || 'Unknown', + roleId: lr.role_id, + roleName: role?.name || 'Unknown', + levelRequired: lr.level_required, + }); + } + } + } catch (e) { + console.warn('Could not fetch level roles:', e.message); + } + } + + res.writeHead(200); + res.end(JSON.stringify({ + levelRoles, + federationRoles: fedRoles, + timestamp: new Date().toISOString(), + })); + })(); + return; + } + + // POST /announce - Send announcement to servers + if (req.url === "/announce" && req.method === "POST") { + if (!checkAdminAuth(req)) { + res.writeHead(401); + res.end(JSON.stringify({ error: "Unauthorized" })); + return; + } + + let body = ""; + req.on("data", chunk => body += chunk); + req.on("end", async () => { + try { + const { title, message, targets, color } = JSON.parse(body); + + if (!title || !message) { + res.writeHead(400); + res.end(JSON.stringify({ error: "Title and message required" })); + return; + } + + const embed = new EmbedBuilder() + .setTitle(title) + .setDescription(message) + .setColor(color ? parseInt(color.replace('#', ''), 16) : 0x5865F2) + .setTimestamp() + .setFooter({ text: 'AeThex Network Announcement' }); + + const results = []; + const targetGuilds = targets && targets.length > 0 + ? targets + : Array.from(client.guilds.cache.keys()); + + for (const guildId of targetGuilds) { + const guild = client.guilds.cache.get(guildId); + if (!guild) { + results.push({ guildId, success: false, error: 'Guild not found' }); + continue; + } + + try { + const config = serverConfigs.get(guildId); + let channel = null; + + if (config?.welcome_channel) { + channel = guild.channels.cache.get(config.welcome_channel); + } + + if (!channel) { + channel = guild.systemChannel || + guild.channels.cache.find(c => c.type === 0 && c.permissionsFor(guild.members.me).has('SendMessages')); + } + + if (channel) { + await channel.send({ embeds: [embed] }); + results.push({ guildId, guildName: guild.name, success: true }); + } else { + results.push({ guildId, guildName: guild.name, success: false, error: 'No suitable channel' }); + } + } catch (error) { + results.push({ guildId, guildName: guild?.name, success: false, error: error.message }); + } + } + + addActivity('announcement', { title, targetCount: targetGuilds.length, successCount: results.filter(r => r.success).length }); + + res.writeHead(200); + res.end(JSON.stringify({ success: true, results })); + } catch (error) { + res.writeHead(500); + res.end(JSON.stringify({ error: error.message })); + } + }); + return; + } + + // GET /servers - List all connected servers (for dropdowns) + if (req.url === "/servers" && req.method === "GET") { + const servers = client.guilds.cache.map(g => ({ + id: g.id, + name: g.name, + icon: g.iconURL({ size: 64 }), + memberCount: g.memberCount, + })); + + res.writeHead(200); + res.end(JSON.stringify({ servers })); + return; + } + res.writeHead(404); res.end(JSON.stringify({ error: "Not found" })); }) diff --git a/aethex-bot/public/dashboard.html b/aethex-bot/public/dashboard.html index fe07c69..fa815e1 100644 --- a/aethex-bot/public/dashboard.html +++ b/aethex-bot/public/dashboard.html @@ -911,6 +911,196 @@ border-color: var(--accent); } + /* Config tabs */ + .config-tab { + display: none; + } + .config-tab.active { + display: block; + } + + /* Form elements */ + .form-group { + margin-bottom: 1.25rem; + } + .form-label { + display: block; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 0.5rem; + color: var(--text-primary); + } + .form-hint { + display: block; + font-size: 0.75rem; + color: var(--text-muted); + margin-top: 0.25rem; + } + .form-input, .form-select, .form-textarea { + width: 100%; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 8px; + padding: 0.625rem 0.875rem; + color: var(--text-primary); + font-size: 0.875rem; + transition: border-color 0.2s; + } + .form-input:focus, .form-select:focus, .form-textarea:focus { + outline: none; + border-color: var(--accent); + } + .form-input::placeholder, .form-textarea::placeholder { + color: var(--text-muted); + } + .form-textarea { + resize: vertical; + min-height: 100px; + } + .form-select { + cursor: pointer; + } + + /* Checkbox group */ + .checkbox-group { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + } + .checkbox-item { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--text-secondary); + cursor: pointer; + } + .checkbox-item input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--accent); + } + + /* Role item */ + .role-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem; + background: var(--bg-card); + border-radius: 8px; + margin-bottom: 0.5rem; + } + .role-item:last-child { + margin-bottom: 0; + } + .role-info { + display: flex; + align-items: center; + gap: 0.75rem; + } + .role-color { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent); + } + .role-name { + font-weight: 500; + } + .role-meta { + font-size: 0.8rem; + color: var(--text-muted); + } + + /* Whitelist item */ + .whitelist-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.625rem 0; + border-bottom: 1px solid var(--border); + } + .whitelist-item:last-child { + border-bottom: none; + } + .whitelist-info { + display: flex; + align-items: center; + gap: 0.75rem; + } + .whitelist-icon { + width: 32px; + height: 32px; + border-radius: 8px; + background: var(--bg-tertiary); + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 0.875rem; + } + .whitelist-name { + font-weight: 500; + } + .whitelist-id { + font-size: 0.75rem; + color: var(--text-muted); + font-family: monospace; + } + + /* Announcement item */ + .announce-item { + padding: 1rem; + background: var(--bg-card); + border-radius: 8px; + margin-bottom: 0.75rem; + } + .announce-item:last-child { + margin-bottom: 0; + } + .announce-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.5rem; + } + .announce-title { + font-weight: 600; + } + .announce-time { + font-size: 0.75rem; + color: var(--text-muted); + } + .announce-message { + font-size: 0.875rem; + color: var(--text-secondary); + } + + /* Toast notification */ + .toast { + position: fixed; + bottom: 1.5rem; + right: 1.5rem; + background: var(--bg-secondary); + border: 1px solid var(--border); + padding: 1rem 1.5rem; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + display: flex; + align-items: center; + gap: 0.75rem; + z-index: 1000; + animation: slideIn 0.3s ease; + } + .toast.success { border-left: 4px solid var(--success); } + .toast.error { border-left: 4px solid var(--danger); } + .toast.warning { border-left: 4px solid var(--warning); } + @keyframes slideIn { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } + } + /* Scrollbar */ ::-webkit-scrollbar { width: 6px; @@ -1445,14 +1635,200 @@
-
-
-

Server Configuration

+
+ + + + +
+ + +
+
+
+

+ + Select Server +

+
+
+ +
-
-
- -

Configuration options coming soon

+ + +
+ + +
+
+
+
+

+ + Level Roles +

+ +
+
+
+

No level roles configured

+
+
+
+ +
+
+

+ + Federation Roles +

+
+
+
+

No federation roles linked

+
+
+
+
+
+ + +
+
+
+
+

+ + Whitelisted Servers +

+
+
+
Loading...
+
+
+ +
+
+

+ + Whitelisted Users (Heat Skip) +

+
+
+
Loading...
+
+
+
+
+ + +
+
+
+

+ + Send Announcement +

+
+
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+ + +
+
+
+ +
+
+

Recent Announcements

+
+
+
+

No recent announcements

+
@@ -2086,6 +2462,334 @@ `; } + // ============================================ + // MANAGEMENT PANEL FUNCTIONS + // ============================================ + + let cachedServers = []; + let currentServerConfig = null; + + function switchConfigTab(tabName) { + document.querySelectorAll('.config-tab').forEach(tab => tab.classList.remove('active')); + document.querySelectorAll('#section-config .tab-btn').forEach(btn => btn.classList.remove('active')); + document.getElementById(`config-${tabName}`).classList.add('active'); + event.target.classList.add('active'); + + if (tabName === 'settings') { + populateServerSelect(); + } else if (tabName === 'whitelist') { + fetchWhitelist(); + } else if (tabName === 'roles') { + fetchRoles(); + } else if (tabName === 'announcements') { + populateAnnounceTargets(); + } + } + + async function populateServerSelect() { + try { + const response = await fetch('/stats'); + const data = await response.json(); + cachedServers = data.guilds || []; + + const select = document.getElementById('configServerSelect'); + select.innerHTML = '' + + cachedServers.map(g => ``).join(''); + } catch (e) { + console.error('Failed to fetch servers:', e); + } + } + + async function loadServerConfig() { + const guildId = document.getElementById('configServerSelect').value; + if (!guildId) { + document.getElementById('serverConfigPanel').style.display = 'none'; + return; + } + + try { + const response = await fetch(`/server-config/${guildId}`); + const data = await response.json(); + currentServerConfig = data.config || {}; + + document.getElementById('configWelcomeChannel').value = currentServerConfig.welcome_channel || ''; + document.getElementById('configGoodbyeChannel').value = currentServerConfig.goodbye_channel || ''; + document.getElementById('configModlogChannel').value = currentServerConfig.modlog_channel || ''; + document.getElementById('configLevelupChannel').value = currentServerConfig.level_up_channel || ''; + document.getElementById('configAutoRole').value = currentServerConfig.auto_role || ''; + document.getElementById('configVerifiedRole').value = currentServerConfig.verified_role || ''; + + document.getElementById('serverConfigPanel').style.display = 'block'; + } catch (e) { + console.error('Failed to load server config:', e); + showToast('Failed to load configuration', 'error'); + } + } + + async function saveServerConfig() { + const guildId = document.getElementById('configServerSelect').value; + if (!guildId) return; + + const config = { + guild_id: guildId, + welcome_channel: document.getElementById('configWelcomeChannel').value || null, + goodbye_channel: document.getElementById('configGoodbyeChannel').value || null, + modlog_channel: document.getElementById('configModlogChannel').value || null, + level_up_channel: document.getElementById('configLevelupChannel').value || null, + auto_role: document.getElementById('configAutoRole').value || null, + verified_role: document.getElementById('configVerifiedRole').value || null + }; + + try { + const token = localStorage.getItem('adminToken') || ''; + const response = await fetch('/server-config', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(config) + }); + + if (response.ok) { + showToast('Configuration saved successfully!', 'success'); + } else { + throw new Error('Failed to save'); + } + } catch (e) { + console.error('Failed to save config:', e); + showToast('Failed to save configuration', 'error'); + } + } + + async function fetchWhitelist() { + try { + const response = await fetch('/whitelist'); + const data = await response.json(); + + const serversList = document.getElementById('whitelistServersList'); + if (data.servers && data.servers.length > 0) { + serversList.innerHTML = data.servers.map(s => ` +
+
+
${(s.name || 'S').charAt(0).toUpperCase()}
+
+
${s.name || 'Unknown Server'}
+
${s.id}
+
+
+ Active +
+ `).join(''); + } else { + serversList.innerHTML = '

No whitelisted servers

'; + } + + const usersList = document.getElementById('whitelistUsersList'); + if (data.users && data.users.length > 0) { + usersList.innerHTML = data.users.map(u => ` +
+
+
${(u.username || 'U').charAt(0).toUpperCase()}
+
+
${u.username || 'Unknown User'}
+
${u.id}
+
+
+ Heat Skip +
+ `).join(''); + } else { + usersList.innerHTML = '

No whitelisted users

'; + } + } catch (e) { + console.error('Failed to fetch whitelist:', e); + document.getElementById('whitelistServersList').innerHTML = '

Failed to load

'; + document.getElementById('whitelistUsersList').innerHTML = '

Failed to load

'; + } + } + + async function fetchRoles() { + try { + const response = await fetch('/roles'); + const data = await response.json(); + + const levelRolesList = document.getElementById('levelRolesList'); + if (data.levelRoles && data.levelRoles.length > 0) { + levelRolesList.innerHTML = data.levelRoles.map(r => ` +
+
+
+
+
${r.name || 'Unknown Role'}
+
Level ${r.level} required
+
+
+ +
+ `).join(''); + } else { + levelRolesList.innerHTML = '

No level roles configured

'; + } + + const federationRolesList = document.getElementById('federationRolesList'); + if (data.federationRoles && data.federationRoles.length > 0) { + federationRolesList.innerHTML = data.federationRoles.map(r => ` +
+
+
+
+
${r.name || 'Unknown Role'}
+
${r.guildName || 'Unknown Server'}
+
+
+ Synced +
+ `).join(''); + } else { + federationRolesList.innerHTML = '

No federation roles linked

'; + } + } catch (e) { + console.error('Failed to fetch roles:', e); + } + } + + function showAddLevelRoleModal() { + showToast('Use /config levelrole command in Discord to add level roles', 'warning'); + } + + function removeLevelRole(roleId) { + showToast('Use /config remove-levelrole command in Discord to remove roles', 'warning'); + } + + async function populateAnnounceTargets() { + try { + const response = await fetch('/stats'); + const data = await response.json(); + const servers = data.guilds || []; + + const targetsDiv = document.getElementById('announceTargets'); + targetsDiv.innerHTML = ` + + ${servers.map(s => ` + + `).join('')} + `; + } catch (e) { + console.error('Failed to populate announce targets:', e); + } + } + + function toggleAllServers(checkbox) { + const serverCheckboxes = document.querySelectorAll('.server-checkbox'); + serverCheckboxes.forEach(cb => { + cb.checked = checkbox.checked; + cb.disabled = checkbox.checked; + }); + } + + function clearAnnouncement() { + document.getElementById('announceTitle').value = ''; + document.getElementById('announceMessage').value = ''; + } + + async function sendAnnouncement() { + const title = document.getElementById('announceTitle').value.trim(); + const message = document.getElementById('announceMessage').value.trim(); + + if (!title || !message) { + showToast('Please fill in both title and message', 'warning'); + return; + } + + const allChecked = document.querySelector('input[value="all"]').checked; + let targets = []; + if (!allChecked) { + document.querySelectorAll('.server-checkbox:checked').forEach(cb => { + targets.push(cb.value); + }); + if (targets.length === 0) { + showToast('Please select at least one server', 'warning'); + return; + } + } + + try { + const token = localStorage.getItem('adminToken') || ''; + const response = await fetch('/announce', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + title, + message, + targets: allChecked ? 'all' : targets + }) + }); + + if (response.ok) { + const result = await response.json(); + showToast(`Announcement sent to ${result.sentCount || 0} servers!`, 'success'); + clearAnnouncement(); + fetchRecentAnnouncements(); + } else { + throw new Error('Failed to send'); + } + } catch (e) { + console.error('Failed to send announcement:', e); + showToast('Failed to send announcement', 'error'); + } + } + + async function fetchRecentAnnouncements() { + try { + const response = await fetch('/announcements'); + const data = await response.json(); + + const container = document.getElementById('recentAnnouncements'); + if (data.announcements && data.announcements.length > 0) { + container.innerHTML = data.announcements.slice(0, 5).map(a => ` +
+
+ ${a.title} + ${formatTimeAgo(a.timestamp)} +
+
${a.message}
+
+ `).join(''); + } else { + container.innerHTML = '

No recent announcements

'; + } + } catch (e) { + console.error('Failed to fetch announcements:', e); + } + } + + function showToast(message, type = 'success') { + const toast = document.createElement('div'); + toast.className = `toast ${type}`; + toast.innerHTML = ` + + ${type === 'success' ? '' : + type === 'error' ? '' : + ''} + + ${message} + `; + document.body.appendChild(toast); + + setTimeout(() => { + toast.style.animation = 'slideIn 0.3s ease reverse'; + setTimeout(() => toast.remove(), 300); + }, 3000); + } + // Initialize document.addEventListener('DOMContentLoaded', () => { // Load theme