modified: aethex-bot/server/webServer.js
This commit is contained in:
parent
3a76186fc6
commit
1d69c3b9dc
4 changed files with 292 additions and 46 deletions
|
|
@ -442,11 +442,19 @@ CREATE TABLE IF NOT EXISTS command_logs (
|
|||
CREATE TABLE IF NOT EXISTS server_backups (
|
||||
id SERIAL PRIMARY KEY,
|
||||
guild_id VARCHAR(32) NOT NULL,
|
||||
name VARCHAR(100),
|
||||
description TEXT,
|
||||
backup_type VARCHAR(20) DEFAULT 'manual', -- manual, auto
|
||||
backup_data JSONB NOT NULL,
|
||||
roles_count INTEGER DEFAULT 0,
|
||||
channels_count INTEGER DEFAULT 0,
|
||||
size_bytes BIGINT DEFAULT 0,
|
||||
created_by VARCHAR(32),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_server_backups_guild ON server_backups(guild_id);
|
||||
|
||||
-- Backup Settings - Auto-backup config
|
||||
CREATE TABLE IF NOT EXISTS backup_settings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
|
|
|
|||
11
aethex-bot/migrations/fix_server_backups.sql
Normal file
11
aethex-bot/migrations/fix_server_backups.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
-- Fix server_backups table - add missing columns
|
||||
-- Run this if you already created the table with the old schema
|
||||
|
||||
ALTER TABLE server_backups ADD COLUMN IF NOT EXISTS name VARCHAR(100);
|
||||
ALTER TABLE server_backups ADD COLUMN IF NOT EXISTS description TEXT;
|
||||
ALTER TABLE server_backups ADD COLUMN IF NOT EXISTS backup_type VARCHAR(20) DEFAULT 'manual';
|
||||
ALTER TABLE server_backups ADD COLUMN IF NOT EXISTS roles_count INTEGER DEFAULT 0;
|
||||
ALTER TABLE server_backups ADD COLUMN IF NOT EXISTS channels_count INTEGER DEFAULT 0;
|
||||
ALTER TABLE server_backups ADD COLUMN IF NOT EXISTS size_bytes BIGINT DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_server_backups_guild ON server_backups(guild_id);
|
||||
|
|
@ -8,6 +8,9 @@
|
|||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="theme-color" content="#6366f1">
|
||||
<link rel="icon" href="/logo.png" type="image/png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--background: #030712;
|
||||
|
|
@ -32,7 +35,7 @@
|
|||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
min-height: 100vh;
|
||||
|
|
@ -183,15 +186,37 @@
|
|||
border: 1px solid var(--card-border);
|
||||
border-top: none;
|
||||
border-radius: 0 0 12px 12px;
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.guild-dropdown.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.guild-list {
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.guild-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.guild-list::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.guild-list::-webkit-scrollbar-thumb {
|
||||
background: rgba(99, 102, 241, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.guild-list::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(99, 102, 241, 0.5);
|
||||
}
|
||||
|
||||
.guild-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -288,6 +313,91 @@
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Server Filter Tabs */
|
||||
.guild-filter-tabs {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem;
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.guild-filter-tab {
|
||||
flex: 1;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.guild-filter-tab:hover {
|
||||
color: var(--foreground);
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
|
||||
.guild-filter-tab.active {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.guild-filter-tab .count {
|
||||
font-size: 0.65rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Bot Status Badge */
|
||||
.guild-bot-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.guild-bot-badge.has-bot {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.guild-bot-badge.no-bot {
|
||||
background: rgba(100, 116, 139, 0.15);
|
||||
border: 1px solid rgba(100, 116, 139, 0.3);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.guild-invite-btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.guild-invite-btn:hover {
|
||||
background: var(--primary-light);
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
|
@ -328,7 +438,22 @@
|
|||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.nav-icon { width: 20px; text-align: center; font-size: 1rem; }
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
margin-top: auto;
|
||||
|
|
@ -804,6 +929,16 @@
|
|||
font-size: 3.5rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-state-icon svg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
stroke: var(--muted);
|
||||
stroke-width: 1.5;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.login-prompt {
|
||||
|
|
@ -1202,76 +1337,83 @@
|
|||
<path d="M6 9l6 6 6-6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="guild-dropdown" id="guildDropdown"></div>
|
||||
<div class="guild-dropdown" id="guildDropdown">
|
||||
<div class="guild-filter-tabs" id="guildFilterTabs">
|
||||
<button class="guild-filter-tab active" data-filter="all" onclick="filterGuilds('all')">All <span class="count" id="countAll">0</span></button>
|
||||
<button class="guild-filter-tab" data-filter="has-bot" onclick="filterGuilds('has-bot')">Active <span class="count" id="countActive">0</span></button>
|
||||
<button class="guild-filter-tab" data-filter="no-bot" onclick="filterGuilds('no-bot')">Invite <span class="count" id="countInvite">0</span></button>
|
||||
</div>
|
||||
<div class="guild-list" id="guildList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Overview</div>
|
||||
<div class="nav-item active" data-page="profile">
|
||||
<span class="nav-icon">👤</span> Profile
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></span> Profile
|
||||
</div>
|
||||
<div class="nav-item" data-page="leaderboard">
|
||||
<span class="nav-icon">🏆</span> Leaderboard
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M8 21v-8M12 21V9M16 21v-5"/><path d="M3 21h18"/></svg></span> Leaderboard
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Rewards</div>
|
||||
<div class="nav-item" data-page="achievements">
|
||||
<span class="nav-icon">🎖️</span> Achievements
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="6"/><path d="M15.477 12.89 17 22l-5-3-5 3 1.523-9.11"/></svg></span> Achievements
|
||||
</div>
|
||||
<div class="nav-item" data-page="quests">
|
||||
<span class="nav-icon">🎯</span> Quests
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg></span> Quests
|
||||
</div>
|
||||
<div class="nav-item" data-page="shop">
|
||||
<span class="nav-icon">🛒</span> Shop
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/></svg></span> Shop
|
||||
</div>
|
||||
<div class="nav-item" data-page="inventory">
|
||||
<span class="nav-icon">🎒</span> Inventory
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg></span> Inventory
|
||||
</div>
|
||||
<div class="nav-item" data-page="titles">
|
||||
<span class="nav-icon">🏷️</span> Titles
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M12 2 2 7l10 5 10-5-10-5Z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg></span> Titles
|
||||
</div>
|
||||
<div class="nav-item" data-page="coins">
|
||||
<span class="nav-icon">🪙</span> Coins
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><circle cx="8" cy="8" r="6"/><path d="M18.09 10.37A6 6 0 1 1 10.34 18"/><path d="M7 6h1v4"/><path d="m16.71 13.88.7.71-2.82 2.82"/></svg></span> Coins
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav-section" id="adminSection" style="display:none">
|
||||
<div class="nav-section-title">Admin</div>
|
||||
<div class="nav-item" data-page="admin-xp">
|
||||
<span class="nav-icon">⚙️</span> XP Settings
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg></span> XP Settings
|
||||
</div>
|
||||
<div class="nav-item" data-page="admin-quests">
|
||||
<span class="nav-icon">📝</span> Manage Quests
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg></span> Manage Quests
|
||||
</div>
|
||||
<div class="nav-item" data-page="admin-achievements">
|
||||
<span class="nav-icon">🏅</span> Manage Achievements
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg></span> Manage Achievements
|
||||
</div>
|
||||
<div class="nav-item" data-page="admin-shop">
|
||||
<span class="nav-icon">🏪</span> Manage Shop
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"/><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"/><path d="M2 7h20"/><path d="M22 7v3a2 2 0 0 1-2 2v0a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12v0a2 2 0 0 1-2-2V7"/></svg></span> Manage Shop
|
||||
</div>
|
||||
<div class="nav-item" data-page="moderation">
|
||||
<span class="nav-icon">🛡</span> Moderation
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span> Moderation
|
||||
</div>
|
||||
<div class="nav-item" data-page="analytics">
|
||||
<span class="nav-icon">📊</span> Analytics
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/></svg></span> Analytics
|
||||
</div>
|
||||
<div class="nav-item" data-page="activity-roles">
|
||||
<span class="nav-icon">🎯</span> Activity Roles
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg></span> Activity Roles
|
||||
</div>
|
||||
<div class="nav-item" data-page="cooldowns">
|
||||
<span class="nav-icon">⏱️</span> Cooldowns
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></span> Cooldowns
|
||||
</div>
|
||||
<div class="nav-item" data-page="federation">
|
||||
<span class="nav-icon">🌐</span> Federation
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></span> Federation
|
||||
</div>
|
||||
<div class="nav-item" data-page="backups">
|
||||
<span class="nav-icon">💾</span> Backups
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg></span> Backups
|
||||
</div>
|
||||
<div class="nav-item" data-page="branding">
|
||||
<span class="nav-icon">🎨</span> Branding
|
||||
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="m9.06 11.9 8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08"/><path d="M7.07 14.94c-1.66 0-3 1.35-3 3.02 0 1.33-2.5 1.52-2 2.02 1.08 1.1 2.49 2.02 4 2.02 2.2 0 4-1.8 4-4.04a3.01 3.01 0 0 0-3-3.02z"/></svg></span> Branding
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
@ -2488,7 +2630,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div style="margin-top:1rem;display:flex;gap:1rem;flex-wrap:wrap">
|
||||
<a href="/pricing.html#branding" target="_blank" class="btn btn-primary">View Plans</a>
|
||||
<a href="/pricing#branding" target="_blank" class="btn btn-primary">View Plans</a>
|
||||
<button class="btn btn-secondary" onclick="refreshBranding()">Refresh Status</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2720,6 +2862,8 @@
|
|||
let currentUser = null;
|
||||
let currentGuild = null;
|
||||
let currentPage = 'profile';
|
||||
let allGuilds = [];
|
||||
let currentFilter = 'all';
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
|
|
@ -2736,10 +2880,19 @@
|
|||
document.getElementById('userAvatar').src = currentUser.avatarUrl;
|
||||
document.getElementById('userName').textContent = currentUser.globalName || currentUser.username;
|
||||
|
||||
populateGuildCards(currentUser.guilds);
|
||||
allGuilds = currentUser.guilds;
|
||||
updateFilterCounts();
|
||||
populateGuildCards(allGuilds);
|
||||
|
||||
if (currentUser.guilds.length > 0) {
|
||||
selectGuild(currentUser.guilds[0]);
|
||||
// Auto-select first server with bot, or first admin server
|
||||
const botServer = allGuilds.find(g => g.hasBot && g.isAdmin);
|
||||
const firstAdmin = allGuilds.find(g => g.isAdmin);
|
||||
if (botServer) {
|
||||
selectGuild(botServer);
|
||||
} else if (firstAdmin) {
|
||||
selectGuild(firstAdmin);
|
||||
} else if (allGuilds.length > 0) {
|
||||
selectGuild(allGuilds[0]);
|
||||
}
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
|
|
@ -2787,11 +2940,11 @@
|
|||
}
|
||||
|
||||
function populateGuildCards(guilds) {
|
||||
const dropdown = document.getElementById('guildDropdown');
|
||||
dropdown.innerHTML = '';
|
||||
const listEl = document.getElementById('guildList');
|
||||
listEl.innerHTML = '';
|
||||
|
||||
if (guilds.length === 0) {
|
||||
dropdown.innerHTML = '<div style="padding: 1rem; text-align: center; color: var(--muted);">No servers found</div>';
|
||||
listEl.innerHTML = '<div style="padding: 1.5rem; text-align: center; color: var(--muted);">No servers match this filter</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2800,35 +2953,84 @@
|
|||
card.className = 'guild-card';
|
||||
card.dataset.guildId = g.id;
|
||||
card.dataset.isAdmin = g.isAdmin;
|
||||
card.onclick = () => selectGuild(g);
|
||||
card.dataset.hasBot = g.hasBot;
|
||||
|
||||
const iconContent = g.icon
|
||||
? `<img src="${g.icon}" alt="${g.name}">`
|
||||
: g.name.charAt(0).toUpperCase();
|
||||
|
||||
let statusHtml = '';
|
||||
if (g.hasBot) {
|
||||
statusHtml = '<span class="guild-bot-badge has-bot">Active</span>';
|
||||
} else {
|
||||
statusHtml = '<span class="guild-bot-badge no-bot">Not Active</span>';
|
||||
}
|
||||
if (g.isAdmin) {
|
||||
statusHtml = '<span class="guild-admin-badge">Admin</span>';
|
||||
statusHtml += '<span class="guild-admin-badge">Admin</span>';
|
||||
}
|
||||
if (g.memberCount) {
|
||||
statusHtml += `<span class="guild-member-count">${g.memberCount.toLocaleString()} members</span>`;
|
||||
statusHtml += `<span class="guild-member-count">${g.memberCount.toLocaleString()}</span>`;
|
||||
}
|
||||
|
||||
// If bot not in server, show invite button
|
||||
const actionHtml = g.hasBot
|
||||
? `<svg class="guild-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M20 6L9 17l-5-5"/></svg>`
|
||||
: `<button class="guild-invite-btn" onclick="event.stopPropagation(); inviteBot('${g.id}')">+ Add Bot</button>`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="guild-icon">${iconContent}</div>
|
||||
<div class="guild-info">
|
||||
<div class="guild-name">${escapeHtml(g.name)}</div>
|
||||
<div class="guild-status">${statusHtml}</div>
|
||||
</div>
|
||||
<svg class="guild-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
|
||||
<path d="M20 6L9 17l-5-5"/>
|
||||
</svg>
|
||||
${actionHtml}
|
||||
`;
|
||||
|
||||
dropdown.appendChild(card);
|
||||
if (g.hasBot) {
|
||||
card.onclick = () => selectGuild(g);
|
||||
} else {
|
||||
card.style.opacity = '0.7';
|
||||
card.style.cursor = 'default';
|
||||
}
|
||||
|
||||
listEl.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function updateFilterCounts() {
|
||||
const withBot = allGuilds.filter(g => g.hasBot).length;
|
||||
const withoutBot = allGuilds.filter(g => !g.hasBot).length;
|
||||
document.getElementById('countAll').textContent = allGuilds.length;
|
||||
document.getElementById('countActive').textContent = withBot;
|
||||
document.getElementById('countInvite').textContent = withoutBot;
|
||||
}
|
||||
|
||||
function filterGuilds(filter) {
|
||||
currentFilter = filter;
|
||||
|
||||
// Update tab states
|
||||
document.querySelectorAll('.guild-filter-tab').forEach(tab => {
|
||||
tab.classList.toggle('active', tab.dataset.filter === filter);
|
||||
});
|
||||
|
||||
// Filter and render
|
||||
let filtered = allGuilds;
|
||||
if (filter === 'has-bot') {
|
||||
filtered = allGuilds.filter(g => g.hasBot);
|
||||
} else if (filter === 'no-bot') {
|
||||
filtered = allGuilds.filter(g => !g.hasBot);
|
||||
}
|
||||
|
||||
populateGuildCards(filtered);
|
||||
}
|
||||
|
||||
function inviteBot(guildId) {
|
||||
const clientId = '578971258073522187'; // Your bot's client ID
|
||||
const permissions = '8'; // Admin permissions
|
||||
const url = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&permissions=${permissions}&scope=bot%20applications.commands&guild_id=${guildId}&disable_guild_select=true`;
|
||||
window.open(url, '_blank', 'width=500,height=800');
|
||||
}
|
||||
|
||||
function selectGuild(guild) {
|
||||
currentGuild = guild.id;
|
||||
|
||||
|
|
@ -5013,7 +5215,8 @@
|
|||
try {
|
||||
const res = await fetch('/api/guild/' + currentGuild + '/branding');
|
||||
if (res.ok) {
|
||||
currentBranding = await res.json();
|
||||
const response = await res.json();
|
||||
currentBranding = response.branding || {};
|
||||
populateBrandingForm(currentBranding);
|
||||
} else {
|
||||
currentBranding = null;
|
||||
|
|
|
|||
|
|
@ -297,10 +297,15 @@ function createWebServer(discordClient, supabase, options = {}) {
|
|||
avatarUrl: user.avatar
|
||||
? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`
|
||||
: `https://cdn.discordapp.com/embed/avatars/${parseInt(user.discriminator || '0') % 5}.png`,
|
||||
guilds: user.guilds.map(g => ({
|
||||
guilds: user.guilds.map(g => {
|
||||
const botGuild = discordClient?.guilds?.cache?.get(g.id);
|
||||
return {
|
||||
...g,
|
||||
icon: g.icon ? `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.png` : null
|
||||
}))
|
||||
icon: g.icon ? `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.png` : null,
|
||||
hasBot: !!botGuild,
|
||||
memberCount: botGuild?.memberCount || null
|
||||
};
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -3191,7 +3196,7 @@ function createWebServer(discordClient, supabase, options = {}) {
|
|||
});
|
||||
|
||||
// Update branding config
|
||||
app.post('/api/guild/:guildId/branding', async (req, res) => {
|
||||
app.patch('/api/guild/:guildId/branding', async (req, res) => {
|
||||
if (!supabase) {
|
||||
return res.status(503).json({ error: 'Database not available' });
|
||||
}
|
||||
|
|
@ -3208,9 +3213,28 @@ function createWebServer(discordClient, supabase, options = {}) {
|
|||
}
|
||||
|
||||
try {
|
||||
const result = await brandingManager.updateBranding(supabase, guildId, req.body, userId);
|
||||
res.json(result);
|
||||
const { custom_handle, ...otherUpdates } = req.body;
|
||||
|
||||
// If trying to set a custom handle, use claimHandle for validation
|
||||
if (custom_handle !== undefined) {
|
||||
const branding = await brandingManager.getBranding(supabase, guildId);
|
||||
const handleResult = await brandingManager.claimHandle(supabase, guildId, custom_handle, branding.tier);
|
||||
if (!handleResult.success) {
|
||||
return res.status(400).json({ error: handleResult.error });
|
||||
}
|
||||
}
|
||||
|
||||
// Update other fields if any
|
||||
if (Object.keys(otherUpdates).length > 0) {
|
||||
const result = await brandingManager.updateBranding(supabase, guildId, otherUpdates, userId);
|
||||
if (!result.success) {
|
||||
return res.status(400).json({ error: result.error });
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('[Branding] Update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update branding' });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue