From 5929dbbbfd1b68869b3ac38afab7f59b39c70905 Mon Sep 17 00:00:00 2001 From: Declan Chidlow Date: Wed, 22 Jul 2026 03:53:52 +0000 Subject: [PATCH] Fix infraction display performance and bugs. Fix some emoji display --- packages/bot/src/bot/util.ts | 2 +- packages/web/static/js/server.js | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- packages/api/src/routes/dash/server-infractions.ts | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------ packages/bot/src/bot/modules/event_handler.ts | 2 +- packages/web/static/styles/pages/server.css | 7 +++++++ packages/bot/src/bot/commands/moderation/infractions.ts | 2 +- packages/bot/src/bot/commands/owner/override.ts | 8 ++++---- 7 file(s) changed, 137 insertion(s)(+), 53 deletion(s)(-) diff --git a/packages/bot/src/bot/util.ts b/packages/bot/src/bot/util.ts --- a/packages/bot/src/bot/util.ts +++ b/packages/bot/src/bot/util.ts @@ -74,7 +74,7 @@ if (!hasPerm) return false; if (announce) { - await message.reply("# :unlock: Bypassed permission check\n" + `Sudo mode is enabled for @${message.author!.username}.\n`); + await message.reply(`# 🔓 Bypassed permission check\n Sudo mode is enabled for @${message.author!.username}.\n`); } return true; } diff --git a/packages/web/static/js/server.js b/packages/web/static/js/server.js --- a/packages/web/static/js/server.js +++ b/packages/web/static/js/server.js @@ -18,6 +18,8 @@ let antispamLoading = false; let infractionsLoaded = false; let infractionsLoading = false; +let infractionsResolving = false; +let resolvedUserIds = new Set(); let rules = []; let perms = 0; @@ -721,11 +723,9 @@ - ${items.length ? renderInfractionTable(items, hasMore) : `

No infractions found.

`}`; + ${renderInfractionTable(items)}`; } else { const table = container.querySelector("table tbody"); - const loadMore = container.querySelector("#inf-load-more"); - if (loadMore) loadMore.remove(); if (table) { table.insertAdjacentHTML("beforeend", buildInfractionRows(items)); @@ -743,22 +743,24 @@ } bindInfractionEvents(); + + if (!append) resolvedUserIds = new Set(); + resolveInfractionUsers(); } -function renderInfractionTable(items, hasMore) { +function renderInfractionTable(items) { return ` ${perms >= 2 ? `` : ""} ${perms >= 2 ? `` : ""} - ${buildInfractionRows(items)} + ${items.length ? buildInfractionRows(items) : ``}
DateUserTypeModeratorReason
No infractions found.
- ${hasMore ? `
` : ""}`; + `; } function buildInfractionRows(items) { @@ -767,13 +769,13 @@ (i) => ` ${perms >= 2 ? `` : ""} ${new Date(i.date).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" })} - ${i.userName ? `${escHtml(i.userName)}` : `${escHtml(i.user)}`} + ${i.userName ? `${escHtml(i.userName)}` : `${escHtml(i.user)}`} ${i.actionType} - ${i.createdByName ? `${escHtml(i.createdByName)}` : i.createdBy ? `${escHtml(i.createdBy)}` : "AutoMod"} + ${i.createdByName ? `${escHtml(i.createdByName)}` : i.createdBy ? `${escHtml(i.createdBy)}` : "AutoMod"} ${escHtml(i.reason || "No reason provided")} ${perms >= 2 ? `` : ""} - ${perms >= 2 && i.actionType === "ban" && i.isBanned ? `` : ""} + ${perms >= 2 && i.actionType === "ban" ? `` : ""} `, ) @@ -781,6 +783,58 @@ } let infObs = null; + +async function resolveInfractionUsers() { + if (infractionsResolving) return; + const container = document.getElementById("tab-infractions"); + const userCells = container.querySelectorAll("[data-resolve-user]"); + const banButtons = container.querySelectorAll("[data-resolve-ban]"); + + const idsToResolve = new Set(); + for (const cell of userCells) { + const id = (cell as HTMLElement).dataset.resolveUser; + if (id && !resolvedUserIds.has(id)) idsToResolve.add(id); + } + for (const btn of banButtons) { + const id = (btn as HTMLElement).dataset.resolveBan; + if (id && !resolvedUserIds.has(id)) idsToResolve.add(id); + } + + if (idsToResolve.size === 0) return; + + infractionsResolving = true; + try { + const data = await request("POST", `/dash/server/${serverId}/infractions/resolve`, { + userIds: [...idsToResolve], + }); + + const users = data.users || {}; + const bannedIds = new Set(data.bannedIds || []); + + for (const cell of userCells) { + const id = (cell as HTMLElement).dataset.resolveUser; + if (!id || !users[id] || !users[id].username) continue; + cell.innerHTML = `${escHtml(users[id].username)}`; + cell.removeAttribute("data-resolve-user"); + resolvedUserIds.add(id); + } + + for (const btn of banButtons) { + const id = (btn as HTMLElement).dataset.resolveBan; + if (!id) continue; + if (bannedIds.has(id)) { + (btn as HTMLElement).hidden = false; + } else { + btn.remove(); + } + resolvedUserIds.add(id); + } + } catch { + /* names will remain as IDs */ + } finally { + infractionsResolving = false; + } +} async function handleInfractionAction(e) { const delBtn = e.target.closest(".inf-delete"); diff --git a/packages/api/src/routes/dash/server-infractions.ts b/packages/api/src/routes/dash/server-infractions.ts --- a/packages/api/src/routes/dash/server-infractions.ts +++ b/packages/api/src/routes/dash/server-infractions.ts @@ -4,6 +4,32 @@ import { botReq } from "../internal/ws"; import { ObjectId } from "mongodb"; +async function resolveUserInfo(userIds: string[]): Promise> { + const map: Record = {}; + if (userIds.length === 0) return map; + try { + const userRes = await botReq("getUsers", { users: [...new Set(userIds)] }); + if (userRes.success && userRes.users) { + Object.assign(map, userRes.users); + } + } catch { + /* fallback: show IDs only */ + } + return map; +} + +async function resolveBannedUsers(server: string): Promise> { + try { + const banRes = await botReq("getBannedUserIds", { server }); + if (banRes.success && banRes.bannedIds) { + return new Set(banRes.bannedIds); + } + } catch { + /* fallback: no banned users shown */ + } + return new Set(); +} + app.get("/dash/server/:server/infractions", requireAuth({ permission: 1 }), async (req: Request, res: Response) => { const user = await isAuthenticated(req, res, true); if (!user) return; @@ -96,47 +122,44 @@ }; }); - const userIds = [...new Set(items.flatMap((i) => [i.user, i.createdBy].filter(Boolean)))]; - const userMapPromise: Promise> = (async () => { - const map: Record = {}; - if (userIds.length > 0) { - try { - const userRes = await botReq("getUsers", { users: userIds }); - if (userRes.success && userRes.users) { - Object.assign(map, userRes.users); - } - } catch { - /* fallback: show IDs only */ - } - } - return map; - })(); - - const bannedSetPromise: Promise> = (async () => { - const banIds = items.filter((i) => i.actionType === "ban").map((i) => i.user); - if (banIds.length > 0) { - try { - const banRes = await botReq("getBannedUserIds", { server }); - if (banRes.success && banRes.bannedIds) { - return new Set(banRes.bannedIds); - } - } catch { - /* fallback: no banned users shown */ - } - } - return new Set(); - })(); - - const [userMap, bannedSet] = await Promise.all([userMapPromise, bannedSetPromise]); - const enriched = items.map((i) => ({ ...i, - userName: userMap[i.user]?.username || null, - createdByName: i.createdBy ? userMap[i.createdBy]?.username || null : null, - isBanned: i.actionType === "ban" && bannedSet.has(i.user), + userName: null, + createdByName: null, + isBanned: false, })); res.send({ infractions: enriched, total, hasMore, stats: { total, warns, kicks, bans, timeouts } }); + } catch (e: any) { + console.error(e); + res.status(500).send({ error: e.message || "Internal server error" }); + } +}); + +app.post("/dash/server/:server/infractions/resolve", requireAuth({ permission: 1 }), async (req: Request, res: Response) => { + const user = await isAuthenticated(req, res, true); + if (!user) return; + + const { server } = req.params; + if (!server || typeof server !== "string") return badRequest(res); + + const { userIds } = req.body; + if (!Array.isArray(userIds) || !userIds.every((id: any) => typeof id === "string")) { + return badRequest(res, "userIds must be an array of strings"); + } + + try { + const [userMap, bannedSet] = await Promise.all([ + resolveUserInfo(userIds), + resolveBannedUsers(server), + ]); + + const users: Record = {}; + for (const id of userIds) { + users[id] = userMap[id] || null; + } + + res.send({ users, bannedIds: [...bannedSet] }); } catch (e: any) { console.error(e); res.status(500).send({ error: e.message || "Internal server error" }); diff --git a/packages/bot/src/bot/modules/event_handler.ts b/packages/bot/src/bot/modules/event_handler.ts --- a/packages/bot/src/bot/modules/event_handler.ts +++ b/packages/bot/src/bot/modules/event_handler.ts @@ -109,7 +109,7 @@ if (!channel) return console.debug("Cannot send hello message: No suitable channel found"); channel .sendMessage({ - content: `:wave: "Hi there!")`, + content: `👋 "Hi there!")`, embeds: [embed], }) .catch((e) => console.debug("Cannot send hello message: " + e)); diff --git a/packages/web/static/styles/pages/server.css b/packages/web/static/styles/pages/server.css --- a/packages/web/static/styles/pages/server.css +++ b/packages/web/static/styles/pages/server.css @@ -168,6 +168,13 @@ display: none; } +td.empty { + text-align: center; + padding: 2rem 1rem; + color: var(--grey); + font-style: italic; +} + .inf-filter-checks { display: flex; gap: 0.75rem; diff --git a/packages/bot/src/bot/commands/moderation/infractions.ts b/packages/bot/src/bot/commands/moderation/infractions.ts --- a/packages/bot/src/bot/commands/moderation/infractions.ts +++ b/packages/bot/src/bot/commands/moderation/infractions.ts @@ -182,7 +182,7 @@ function getInfEmoji(inf: Infraction) { switch (inf.actionType) { case "kick": - return "🥾 "; + return "👢 "; case "ban": return "🔨 "; case "timeout": diff --git a/packages/bot/src/bot/commands/owner/override.ts b/packages/bot/src/bot/commands/owner/override.ts --- a/packages/bot/src/bot/commands/owner/override.ts +++ b/packages/bot/src/bot/commands/owner/override.ts @@ -37,8 +37,8 @@ `This will automatically disable **5 minutes** after your last bot interaction. ` + `To disable now, run \`${message.prefix}override disable\`.`; - const sentMsg = await message.reply(msg.replace("%emoji%", ":lock:"), false); - setTimeout(() => sentMsg?.edit({ content: msg.replace("%emoji%", ":unlock:") }).catch(() => {}), 200); + const sentMsg = await message.reply(msg.replace("%emoji%", "🔒"), false); + setTimeout(() => sentMsg?.edit({ content: msg.replace("%emoji%", "🔓") }).catch(() => {}), 200); break; } @@ -50,8 +50,8 @@ sudoOverrides[message.authorId!] = null; let msg = `## %emoji% Override disabled`; - const sentMsg = await message.reply(msg.replace("%emoji%", ":unlock:"), false); - setTimeout(() => sentMsg?.edit({ content: msg.replace("%emoji%", ":lock:") }).catch(() => {}), 200); + const sentMsg = await message.reply(msg.replace("%emoji%", "🔓"), false); + setTimeout(() => sentMsg?.edit({ content: msg.replace("%emoji%", "🔒") }).catch(() => {}), 200); break; } -- tangled.sh