diff --git a/.replit b/.replit index a9c7711..e23466d 100644 --- a/.replit +++ b/.replit @@ -23,7 +23,7 @@ localPort = 8080 externalPort = 8080 [[ports]] -localPort = 44107 +localPort = 35789 externalPort = 3000 [workflows] diff --git a/aethex-bot/bot.js b/aethex-bot/bot.js index b18933f..14d0658 100644 --- a/aethex-bot/bot.js +++ b/aethex-bot/bot.js @@ -290,6 +290,59 @@ const MAX_ACTIVITY_EVENTS = 100; const threatAlerts = []; const MAX_THREAT_ALERTS = 50; +// Analytics tracking +const analyticsData = { + commandUsage: {}, + xpDistributed: 0, + newMembers: 0, + modActions: { warnings: 0, kicks: 0, bans: 0, timeouts: 0 }, + hourlyActivity: Array(24).fill(0), + dailyActivity: Array(7).fill(0), + lastReset: Date.now(), +}; + +function trackCommand(commandName) { + analyticsData.commandUsage[commandName] = (analyticsData.commandUsage[commandName] || 0) + 1; + const hour = new Date().getHours(); + const day = new Date().getDay(); + analyticsData.hourlyActivity[hour]++; + analyticsData.dailyActivity[day]++; +} + +function trackXP(amount) { + analyticsData.xpDistributed += amount; +} + +function trackNewMember() { + analyticsData.newMembers++; +} + +function trackModAction(type) { + if (analyticsData.modActions[type] !== undefined) { + analyticsData.modActions[type]++; + } +} + +function resetDailyAnalytics() { + const now = Date.now(); + const lastReset = analyticsData.lastReset; + const dayMs = 24 * 60 * 60 * 1000; + + if (now - lastReset > dayMs) { + analyticsData.commandUsage = {}; + analyticsData.xpDistributed = 0; + analyticsData.newMembers = 0; + analyticsData.modActions = { warnings: 0, kicks: 0, bans: 0, timeouts: 0 }; + analyticsData.lastReset = now; + } +} + +client.trackCommand = trackCommand; +client.trackXP = trackXP; +client.trackNewMember = trackNewMember; +client.trackModAction = trackModAction; +client.analyticsData = analyticsData; + function addActivity(type, data) { const event = { id: Date.now() + Math.random().toString(36).substr(2, 9), @@ -456,6 +509,9 @@ client.on("interactionCreate", async (interaction) => { await command.execute(interaction, supabase, client); console.log(`[Command] Completed: ${interaction.commandName}`); + trackCommand(interaction.commandName); + resetDailyAnalytics(); + addActivity('command', { command: interaction.commandName, user: interaction.user.tag, @@ -767,6 +823,89 @@ http return; } + if (req.url === "/analytics") { + resetDailyAnalytics(); + + const commandsArray = Object.entries(analyticsData.commandUsage) + .map(([name, count]) => ({ name, count })) + .sort((a, b) => b.count - a.count); + + const totalCommands = commandsArray.reduce((sum, c) => sum + c.count, 0); + const totalModActions = Object.values(analyticsData.modActions).reduce((sum, c) => sum + c, 0); + + res.writeHead(200); + res.end(JSON.stringify({ + commandsToday: totalCommands, + xpDistributed: analyticsData.xpDistributed, + newMembers: analyticsData.newMembers, + modActionsTotal: totalModActions, + modActions: analyticsData.modActions, + commandUsage: commandsArray.slice(0, 15), + hourlyActivity: analyticsData.hourlyActivity, + dailyActivity: analyticsData.dailyActivity, + lastReset: new Date(analyticsData.lastReset).toISOString(), + timestamp: new Date().toISOString(), + })); + return; + } + + if (req.url === "/leaderboard") { + if (!supabase) { + res.writeHead(200); + res.end(JSON.stringify({ + success: false, + message: "Supabase not configured", + xpLeaders: [], + topChatters: [], + topMods: [], + })); + return; + } + + (async () => { + try { + const { data: xpLeaders } = await supabase + .from('user_profiles') + .select('id, username, avatar_url, xp') + .order('xp', { ascending: false }) + .limit(10); + + const { data: modActors } = await supabase + .from('mod_actions') + .select('moderator_id, moderator_tag') + .limit(100); + + const modCounts = {}; + (modActors || []).forEach(m => { + modCounts[m.moderator_id] = modCounts[m.moderator_id] || { count: 0, tag: m.moderator_tag }; + modCounts[m.moderator_id].count++; + }); + + const topMods = Object.entries(modCounts) + .map(([id, data]) => ({ id, tag: data.tag, count: data.count })) + .sort((a, b) => b.count - a.count) + .slice(0, 10); + + res.writeHead(200); + res.end(JSON.stringify({ + success: true, + xpLeaders: (xpLeaders || []).map((u, i) => ({ + rank: i + 1, + username: u.username || 'Unknown', + avatarUrl: u.avatar_url, + xp: u.xp || 0, + level: Math.floor(Math.sqrt((u.xp || 0) / 100)), + })), + topMods, + })); + } catch (error) { + res.writeHead(500); + res.end(JSON.stringify({ success: false, error: error.message })); + } + })(); + return; + } + if (req.url === "/bot-status") { if (!checkAdminAuth(req)) { res.writeHead(401); diff --git a/aethex-bot/public/dashboard.html b/aethex-bot/public/dashboard.html index a9dbe59..fe07c69 100644 --- a/aethex-bot/public/dashboard.html +++ b/aethex-bot/public/dashboard.html @@ -608,6 +608,238 @@ .badge-warning { background: rgba(245, 158, 11, 0.2); color: var(--warning); } .badge-danger { background: rgba(239, 68, 68, 0.2); color: var(--danger); } .badge-info { background: rgba(59, 130, 246, 0.2); color: var(--info); } + + /* Analytics Charts */ + .chart-bars { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + .chart-bar-row { + display: flex; + align-items: center; + gap: 0.75rem; + } + + .chart-bar-label { + width: 80px; + font-size: 0.8rem; + color: var(--text-secondary); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .chart-bar-track { + flex: 1; + height: 24px; + background: var(--bg-card); + border-radius: 4px; + overflow: hidden; + } + + .chart-bar-fill { + height: 100%; + background: linear-gradient(90deg, var(--accent), #a855f7); + border-radius: 4px; + transition: width 0.5s ease; + min-width: 2px; + } + + .chart-bar-value { + width: 40px; + text-align: right; + font-size: 0.8rem; + font-weight: 600; + color: var(--text-primary); + } + + /* Mod Breakdown */ + .mod-stat-row { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.5rem 0; + } + + .mod-label { + width: 80px; + font-size: 0.875rem; + color: var(--text-secondary); + } + + .mod-bar-container { + flex: 1; + height: 12px; + background: var(--bg-card); + border-radius: 6px; + overflow: hidden; + } + + .mod-bar { + height: 100%; + border-radius: 6px; + transition: width 0.5s ease; + } + + .mod-bar.warning { background: var(--warning); } + .mod-bar.danger { background: var(--danger); } + .mod-bar.purple { background: var(--accent); } + .mod-bar.info { background: var(--info); } + + .mod-count { + width: 40px; + text-align: right; + font-size: 0.875rem; + font-weight: 600; + } + + /* Heatmap */ + .heatmap-container { + display: flex; + flex-direction: column; + gap: 1rem; + } + + .heatmap-row { + display: flex; + gap: 0.5rem; + } + + .heatmap-labels { + display: flex; + flex-direction: column; + gap: 2px; + font-size: 0.7rem; + color: var(--text-muted); + min-width: 30px; + } + + .heatmap-grid { + display: flex; + gap: 2px; + flex: 1; + } + + .heatmap-cell { + flex: 1; + aspect-ratio: 1; + min-width: 20px; + max-width: 40px; + background: var(--accent); + border-radius: 3px; + opacity: 0.1; + transition: opacity 0.3s; + } + + .heatmap-legend { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 0.5rem; + font-size: 0.75rem; + color: var(--text-muted); + } + + .legend-boxes { + display: flex; + gap: 2px; + } + + .legend-box { + width: 14px; + height: 14px; + background: var(--accent); + border-radius: 2px; + } + + /* Weekly Chart */ + .weekly-chart { + display: flex; + justify-content: space-around; + align-items: flex-end; + height: 150px; + padding: 1rem 0; + } + + .week-bar-wrapper { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + flex: 1; + } + + .week-bar { + width: 40px; + max-width: 60px; + background: linear-gradient(180deg, var(--accent), #a855f7); + border-radius: 4px 4px 0 0; + transition: height 0.5s ease; + min-height: 4px; + } + + .week-label { + font-size: 0.75rem; + color: var(--text-muted); + } + + /* Leaderboard */ + .leaderboard-item { + display: flex; + align-items: center; + gap: 1rem; + padding: 0.75rem 0; + border-bottom: 1px solid var(--border); + } + + .leaderboard-item:last-child { border-bottom: none; } + + .leaderboard-rank { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + font-weight: 700; + font-size: 0.875rem; + } + + .leaderboard-rank.gold { background: rgba(255, 193, 7, 0.2); color: #ffc107; } + .leaderboard-rank.silver { background: rgba(192, 192, 192, 0.2); color: #c0c0c0; } + .leaderboard-rank.bronze { background: rgba(205, 127, 50, 0.2); color: #cd7f32; } + .leaderboard-rank.default { background: var(--bg-card); color: var(--text-muted); } + + .leaderboard-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--bg-tertiary); + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + overflow: hidden; + } + + .leaderboard-avatar img { width: 100%; height: 100%; object-fit: cover; } + + .leaderboard-info { flex: 1; } + + .leaderboard-name { font-weight: 500; } + + .leaderboard-level { + font-size: 0.8rem; + color: var(--text-muted); + } + + .leaderboard-xp { + text-align: right; + font-weight: 600; + color: var(--accent); + } /* Tooltip */ [data-tooltip] { @@ -950,13 +1182,129 @@ +
Analytics data coming soon
+No command usage yet
No leaderboard data available
+