diff --git a/aethex-bot/bot.js b/aethex-bot/bot.js index 8c84dd4..a16a361 100644 --- a/aethex-bot/bot.js +++ b/aethex-bot/bot.js @@ -1565,6 +1565,179 @@ http return; } + // GET /user-lookup/:query - Search for a user by ID or username + if (req.url.startsWith("/user-lookup/") && req.method === "GET") { + const query = decodeURIComponent(req.url.split("/user-lookup/")[1].split("?")[0]); + + if (!query || query.length < 2) { + res.writeHead(400); + res.end(JSON.stringify({ error: "Query too short" })); + return; + } + + (async () => { + try { + const results = []; + const isNumericId = /^\d{17,19}$/.test(query); + + for (const guild of client.guilds.cache.values()) { + try { + if (isNumericId) { + const member = await guild.members.fetch(query).catch(() => null); + if (member) { + const existingIdx = results.findIndex(r => r.id === member.id); + if (existingIdx === -1) { + results.push({ + id: member.id, + tag: member.user.tag, + username: member.user.username, + displayName: member.displayName, + avatar: member.user.displayAvatarURL({ size: 128 }), + bot: member.user.bot, + createdAt: member.user.createdAt.toISOString(), + joinedAt: member.joinedAt?.toISOString(), + roles: member.roles.cache.filter(r => r.name !== '@everyone').map(r => ({ id: r.id, name: r.name, color: r.hexColor })).slice(0, 10), + servers: [{ id: guild.id, name: guild.name }], + heat: getHeat(member.id), + }); + } else { + results[existingIdx].servers.push({ id: guild.id, name: guild.name }); + } + } + } else { + const members = await guild.members.fetch({ query, limit: 10 }).catch(() => new Map()); + for (const member of members.values()) { + const existingIdx = results.findIndex(r => r.id === member.id); + if (existingIdx === -1) { + results.push({ + id: member.id, + tag: member.user.tag, + username: member.user.username, + displayName: member.displayName, + avatar: member.user.displayAvatarURL({ size: 128 }), + bot: member.user.bot, + createdAt: member.user.createdAt.toISOString(), + joinedAt: member.joinedAt?.toISOString(), + roles: member.roles.cache.filter(r => r.name !== '@everyone').map(r => ({ id: r.id, name: r.name, color: r.hexColor })).slice(0, 10), + servers: [{ id: guild.id, name: guild.name }], + heat: getHeat(member.id), + }); + } else { + results[existingIdx].servers.push({ id: guild.id, name: guild.name }); + } + } + } + } catch (e) {} + } + + // Fetch Supabase profile data if available + if (supabase && results.length > 0) { + for (const user of results) { + try { + const { data: link } = await supabase + .from('discord_links') + .select('user_id, primary_arm') + .eq('discord_id', user.id) + .single(); + + if (link) { + user.linked = true; + user.realm = link.primary_arm; + + const { data: profile } = await supabase + .from('user_profiles') + .select('username, xp, daily_streak, badges') + .eq('id', link.user_id) + .single(); + + if (profile) { + user.aethexUsername = profile.username; + user.xp = profile.xp || 0; + user.level = Math.floor(Math.sqrt((profile.xp || 0) / 100)); + user.dailyStreak = profile.daily_streak || 0; + user.badges = profile.badges || []; + } + } + + const { data: warnings } = await supabase + .from('warnings') + .select('id, reason, created_at') + .eq('user_id', user.id) + .order('created_at', { ascending: false }) + .limit(5); + + user.warnings = warnings || []; + + const { data: modActions } = await supabase + .from('mod_actions') + .select('action, reason, created_at') + .eq('user_id', user.id) + .order('created_at', { ascending: false }) + .limit(5); + + user.modHistory = modActions || []; + } catch (e) {} + } + } + + res.writeHead(200); + res.end(JSON.stringify({ + results: results.slice(0, 20), + count: results.length, + query, + timestamp: new Date().toISOString(), + })); + } catch (error) { + res.writeHead(500); + res.end(JSON.stringify({ error: error.message })); + } + })(); + return; + } + + // GET /mod-actions - Get recent moderation actions + if (req.url === "/mod-actions" && req.method === "GET") { + if (!supabase) { + res.writeHead(200); + res.end(JSON.stringify({ success: false, message: "Supabase not configured", actions: [] })); + return; + } + + (async () => { + try { + const { data: actions, error } = await supabase + .from('mod_actions') + .select('*') + .order('created_at', { ascending: false }) + .limit(50); + + if (error) throw error; + + const { count: warnCount } = await supabase.from('warnings').select('*', { count: 'exact', head: true }); + const { count: banCount } = await supabase.from('mod_actions').select('*', { count: 'exact', head: true }).eq('action', 'ban'); + const { count: kickCount } = await supabase.from('mod_actions').select('*', { count: 'exact', head: true }).eq('action', 'kick'); + const { count: timeoutCount } = await supabase.from('mod_actions').select('*', { count: 'exact', head: true }).eq('action', 'timeout'); + + res.writeHead(200); + res.end(JSON.stringify({ + success: true, + actions: actions || [], + counts: { + warnings: warnCount || 0, + bans: banCount || 0, + kicks: kickCount || 0, + timeouts: timeoutCount || 0, + }, + timestamp: new Date().toISOString(), + })); + } catch (error) { + res.writeHead(500); + res.end(JSON.stringify({ success: false, error: error.message })); + } + })(); + 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 fa815e1..1573a4e 100644 --- a/aethex-bot/public/dashboard.html +++ b/aethex-bot/public/dashboard.html @@ -1548,20 +1548,37 @@
-

User Lookup

+

+ + User Lookup +

-
- +
+ +
+
- -

Search for a user to view their profile

+ +

Search for a user by Discord ID or username

+

View profile, XP, warnings, and mod history

+ +
@@ -2771,6 +2788,257 @@ } } + // ============================================ + // USER LOOKUP FUNCTIONS + // ============================================ + + let currentUserResults = []; + + async function searchUser() { + const query = document.getElementById('userSearch').value.trim(); + if (!query || query.length < 2) { + showToast('Please enter at least 2 characters', 'warning'); + return; + } + + document.getElementById('userSearchLoading').style.display = 'flex'; + document.getElementById('userResult').style.display = 'none'; + document.getElementById('userProfileCard').style.display = 'none'; + + try { + const response = await fetch(`/user-lookup/${encodeURIComponent(query)}`); + const data = await response.json(); + currentUserResults = data.results || []; + + document.getElementById('userSearchLoading').style.display = 'none'; + document.getElementById('userResult').style.display = 'block'; + + if (currentUserResults.length === 0) { + document.getElementById('userResult').innerHTML = ` +
+ +

No users found for "${query}"

+

Try a Discord ID or different username

+
+ `; + return; + } + + if (currentUserResults.length === 1) { + showUserProfile(currentUserResults[0]); + } else { + document.getElementById('userResult').innerHTML = ` +

Found ${currentUserResults.length} users:

+ ${currentUserResults.map((user, idx) => ` +
+
+ ${user.avatar ? `` : user.username.charAt(0).toUpperCase()} +
+
+
${user.username} ${user.bot ? 'BOT' : ''}
+
${user.id}
+
+ ${user.linked ? 'Linked' : 'Not Linked'} +
+ `).join('')} + `; + } + } catch (error) { + console.error('User search failed:', error); + document.getElementById('userSearchLoading').style.display = 'none'; + document.getElementById('userResult').style.display = 'block'; + document.getElementById('userResult').innerHTML = ` +
+

Search failed. Please try again.

+
+ `; + } + } + + function showUserProfile(user) { + document.getElementById('userResult').innerHTML = ` +
+

Profile shown below

+
+ `; + document.getElementById('userProfileCard').style.display = 'block'; + + const heatClass = user.heat >= 3 ? 'danger' : user.heat >= 1 ? 'warning' : 'success'; + const rolesHtml = user.roles && user.roles.length > 0 + ? user.roles.map(r => `${r.name}`).join('') + : 'No roles'; + + const serversHtml = user.servers && user.servers.length > 0 + ? user.servers.map(s => `${s.name}`).join('') + : 'None'; + + const warningsHtml = user.warnings && user.warnings.length > 0 + ? user.warnings.map(w => ` +
+
+ +
+
+
${w.reason || 'No reason provided'}
+
${formatTimeAgo(w.created_at)}
+
+
+ `).join('') + : '

No warnings

'; + + const modHistoryHtml = user.modHistory && user.modHistory.length > 0 + ? user.modHistory.map(m => ` +
+
+ +
+
+
${m.action.toUpperCase()} - ${m.reason || 'No reason'}
+
${formatTimeAgo(m.created_at)}
+
+
+ `).join('') + : '

No mod actions

'; + + document.getElementById('userProfileContent').innerHTML = ` +
+
+
+
+ ${user.avatar ? `` : user.username.charAt(0).toUpperCase()} +
+
+

${user.displayName || user.username}

+

@${user.username} ${user.bot ? 'BOT' : ''}

+

${user.id}

+
+
+ +
+
+
${user.level || 0}
+
Level
+
+
+
${formatNumber(user.xp || 0)}
+
XP
+
+
+
${user.heat || 0}
+
Heat
+
+
+ +
+

Account Status

+
+ ${user.linked ? 'AeThex Linked' : 'Not Linked'} + ${user.realm ? `${user.realm}` : ''} + ${user.dailyStreak ? `${user.dailyStreak} day streak` : ''} +
+
+ +
+

Servers

+
${serversHtml}
+
+ +
+

Roles

+
${rolesHtml}
+
+ +
+

Dates

+

Created: ${new Date(user.createdAt).toLocaleDateString()}

+ ${user.joinedAt ? `

Joined: ${new Date(user.joinedAt).toLocaleDateString()}

` : ''} +
+
+ +
+
+
+

Warnings (${user.warnings?.length || 0})

+
+
+ ${warningsHtml} +
+
+ +
+
+

Mod History

+
+
+ ${modHistoryHtml} +
+
+ +
+

Quick Actions

+

Use Discord commands for moderation:

+
+
warn ${user.id}
+
kick ${user.id}
+
timeout ${user.id}
+
ban ${user.id}
+
+
+
+
+ `; + } + + function clearUserSearch() { + document.getElementById('userSearch').value = ''; + document.getElementById('userProfileCard').style.display = 'none'; + document.getElementById('userResult').innerHTML = ` +
+ +

Search for a user by Discord ID or username

+

View profile, XP, warnings, and mod history

+
+ `; + currentUserResults = []; + } + + // ============================================ + // MODERATION FETCH + // ============================================ + + async function fetchModActions() { + try { + const response = await fetch('/mod-actions'); + const data = await response.json(); + + if (data.counts) { + document.getElementById('warningCount').textContent = data.counts.warnings || 0; + document.getElementById('banCount').textContent = data.counts.bans || 0; + document.getElementById('timeoutCount').textContent = data.counts.timeouts || 0; + document.getElementById('kickCount').textContent = data.counts.kicks || 0; + } + + const modLogContent = document.getElementById('modLogContent'); + if (data.actions && data.actions.length > 0) { + modLogContent.innerHTML = data.actions.slice(0, 10).map(a => ` +
+
+ +
+
+
${a.action.toUpperCase()} ${a.user_tag || a.user_id} - ${a.reason || 'No reason'}
+
by ${a.moderator_tag || 'Unknown'} ${formatTimeAgo(a.created_at)}
+
+
+ `).join(''); + } else { + modLogContent.innerHTML = '

No recent moderation actions

'; + } + } catch (error) { + console.error('Failed to fetch mod actions:', error); + } + } + function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = `toast ${type}`;