all repos — stealth-developers @ 51ec2cd70fd887488faeda7d74266e5e27a57a87

feat: summarise ban status in user embed
vi v@vt3e.cat
Wed, 04 Mar 2026 21:10:13 +0000
commit

51ec2cd70fd887488faeda7d74266e5e27a57a87

parent

e74848aa6e450a179d4e77cf2781a79f59248d77

3 files changed, 81 insertions(+), 18 deletions(-)

jump to
M src/interactions/commands/roblox/getBanHistory.tssrc/interactions/commands/roblox/getBanHistory.ts

@@ -1,4 +1,5 @@

import { + type ButtonInteraction, type ChatInputCommandInteraction, type Client, type GuildMember,

@@ -11,9 +12,9 @@ import type { RobloxUserId } from "@/roblox/types";

import { hasManagerPermissions } from "@/utils/permissions"; import { constructBansContainer, + constructBansSummaryContainer, constructUserContainer, } from "@/utils/profile"; -import { processRobloxUserInteraction } from "@/utils/robloxUser"; const commandData = new SlashCommandBuilder() .setName("ban-history")

@@ -185,7 +186,24 @@ );

} } +async function buttonExecute(_client: Client, interaction: ButtonInteraction) { + const [, action, userId] = interaction.customId.split(":"); + + const isModerator = await hasManagerPermissions( + interaction.member as GuildMember, + ); + const { container } = await constructBansSummaryContainer(userId, { + showPrivate: isModerator, + }); + + await interaction.reply({ + components: [container], + flags: ["IsComponentsV2"], + }); +} + export default { data: commandData, + buttonExecute, execute, };
M src/utils/profile.tssrc/utils/profile.ts

@@ -1,4 +1,4 @@

-import config from "@/config"; +import config, { type Project } from "@/config"; import { roblox } from "@/roblox/client"; import type { GetUserResponse,

@@ -135,6 +135,7 @@ startMs: number;

endMs: number; nowActive: boolean; universeId?: string; + project?: Project; }; type UserRestrictionLogWithUid = UserRestrictionLog & {

@@ -154,6 +155,9 @@ startMs,

endMs, nowActive, ...(log.universeId ? { universeId: log.universeId } : {}), + project: Object.values(config.projects).find( + (p) => p.universe === log.universeId, + ), }; }); }

@@ -166,11 +170,7 @@ includeProjectLabel?: string | null;

includeStatus?: boolean; } = {}, ) { - const { - showPrivate = false, - includeProjectLabel = null, - includeStatus = false, - } = opts; + const { showPrivate = false, includeProjectLabel = null } = opts; const reason = log.displayReason || "no public reason"; const moderator = await moderatorMention(log.moderator);

@@ -194,9 +194,6 @@

const lines = [ includeProjectLabel ? `**${includeProjectLabel}**` : undefined, `> Banned **${bannedFor}**, ${startRel}, by **${moderator}**`, - includeStatus - ? `> **Status:** ${log.active ? "Active" : "Expired"}` - : undefined, `> **Reason:** ${reason}`, showPrivate ? `> **Private Reason:** ${log.privateReason || "no private reason"}`

@@ -257,6 +254,31 @@ );

return { container, total: bans.length }; } +export async function getBans(userId: RobloxUserId) { + const universeIds = Object.values(config.projects) + .map((p) => p.universe) + .filter((id): id is string => Boolean(id)); + + const perUniverseLogs = await Promise.all( + universeIds.map(async (universeId) => { + const [logs] = await roblox.getUserRestrictionLogs(universeId, userId); + const arr = logs ?? []; + return arr.map( + (log) => + ({ + ...log, + universeId, + project: Object.values(config.projects).find( + (p) => p.universe === universeId, + ), + }) as EnrichedLog, + ); + }), + ); + + return perUniverseLogs; +} + export async function constructBansSummaryContainer( userId: string, opts: { showPrivate?: boolean } = {},

@@ -318,11 +340,24 @@ // ---- user -------------------------------------------------------------------

export async function constructUserContainer( profile: GetUserResponse, extras?: { - bans?: UserRestrictionLog[] | null; + bans?: EnrichedLog[] | null; thumbnailUri?: string | null; showPrivate?: boolean; + originCommand?: string; }, ) { + const lastBans = extras?.bans + ?.sort( + (a, b) => + new Date(b.createTime).getTime() - new Date(a.createTime).getTime(), + ) + .slice(0, 3); + + const banSummary = + lastBans && lastBans.length > 0 + ? `Currently banned in ${[...new Set(lastBans.map((b) => b.project?.displayName))].join(", ")}` + : "No recent bans"; + const createdAt = new Date(profile.createTime); const createdAtMs = Math.round(createdAt.getTime() / 1000); const createdAtString = `<t:${createdAtMs}> (<t:${createdAtMs}:R>)`;

@@ -340,6 +375,7 @@ ["### About", profile.about ? profile.about : "-# No bio provided"].join(

"\n", ), ); + const banText = text([`-# ${banSummary}`].join("\n")); const section = new SectionBuilder().addTextDisplayComponents(bodyText); const icon = thumbnail(extras?.thumbnailUri ?? undefined);

@@ -348,15 +384,23 @@

const mainContainer = new ContainerBuilder() .addSectionComponents(section) .addSeparatorComponents(new SeparatorBuilder()) - .addTextDisplayComponents(aboutText); + .addTextDisplayComponents(aboutText) + .addTextDisplayComponents(banText); const profileButton = new ButtonBuilder() .setLabel("View Profile") .setStyle(ButtonStyle.Link) .setURL(profileUrl); + const bansButton = new ButtonBuilder() + .setLabel("View Bans") + .setStyle(ButtonStyle.Primary) + .setCustomId(`ban-history:summary:${profile.id}`); mainContainer.addActionRowComponents( - new ActionRowBuilder<ButtonBuilder>().addComponents(profileButton), + new ActionRowBuilder<ButtonBuilder>().addComponents( + profileButton, + bansButton, + ), ); return { mainContainer, buttons: [profileButton] };
M src/utils/robloxUser.tssrc/utils/robloxUser.ts

@@ -3,6 +3,7 @@ import type { RobloxUserId } from "@/roblox/types";

import { constructBansSummaryContainer, constructUserContainer, + getBans, } from "@/utils/profile"; import type { ChatInputCommandInteraction,

@@ -49,19 +50,19 @@ const message =

randomMessages[Math.floor(Math.random() * randomMessages.length)]; await interaction.editReply(message); + const bans = await getBans(id); + const { mainContainer } = await constructUserContainer(user, { thumbnailUri, - showPrivate: isModerator, - }); - - const banSummary = await constructBansSummaryContainer(id, { + bans: bans.flat(), showPrivate: isModerator, + originCommand: interaction.commandName, }); await interaction.editReply(user.id); await interaction.followUp({ flags: ["IsComponentsV2"], - components: [mainContainer, banSummary.container], + components: [mainContainer], }); } catch (err) { console.error(err);