all repos — stealth-developers @ 9f330c936258e221eed58cf587262ebb4d796ba8

bot(roblox): user search command
vi did:web:vt3e.cat
Fri, 03 Jul 2026 19:19:06 +0100
commit

9f330c936258e221eed58cf587262ebb4d796ba8

parent

89e4c7ff2aa4e6d6e161f82ee270a6fb7662c00a

M apps/bot/src/feats/index.tsapps/bot/src/feats/index.ts

@@ -1,14 +1,14 @@

import { bugCommands, bugComponents } from "./bugs"; // import { syncCommand } from "./link"; import { miscCommands } from "./misc"; -import { robloxUserCommand } from "./roblox"; +import { robloxCommands } from "./roblox"; import { ticketCommands, ticketComponents } from "./tickets"; export const commands = [ ...ticketCommands, ...miscCommands, ...bugCommands, - robloxUserCommand, + ...robloxCommands, // syncCommand, ]; export const components = [...ticketComponents, ...bugComponents];
M apps/bot/src/feats/roblox/client.tsapps/bot/src/feats/roblox/client.ts

@@ -1,3 +1,5 @@

+import config from "@stealth-developers/config"; + export interface RobloxClientOptions { cookie: string; apiKey: string;

@@ -318,3 +320,8 @@ queryParams,

); } } + +export const client = new RobloxUserClient({ + cookie: config.roblox.cookie, + apiKey: config.roblox.apiKey, +});
M apps/bot/src/feats/roblox/index.tsapps/bot/src/feats/roblox/index.ts

@@ -1,1 +1,7 @@

+import { searchCommand } from "./search"; +import { robloxUserCommand } from "./user"; + export { robloxUserCommand } from "./user"; +export { searchCommand } from "./search"; + +export const robloxCommands = [robloxUserCommand, searchCommand];
A apps/bot/src/feats/roblox/search.ts

@@ -0,0 +1,76 @@

+import { option } from "@purrkit/router"; +import { Result } from "@sapphire/result"; +import { ContainerBuilder, TextDisplayBuilder } from "discord.js"; + +import { kitten } from "@/client"; +import { errorMessage, logger } from "@/lib"; + +import { client } from "./client"; + +export const searchCommand = kitten.command("search", { + description: "Search for users", + options: { + query: option.string("The user to search for", { required: true }), + }, + run: async (interaction, args) => { + const { query } = args; + const result = await Result.fromAsync(() => client.searchUsers(query)); + if (result.isErr()) { + logger.error(result.unwrapErr(), "error searching for users"); + return errorMessage(interaction, "There was an error fetching the search results"); + } + + const users = result.unwrap().data; + + if (!users || users.length === 0) { + const container = new ContainerBuilder().addTextDisplayComponents( + new TextDisplayBuilder().setContent(`No results found for **"${query}"**.`), + ); + return interaction.reply({ components: [container], flags: ["IsComponentsV2"] }); + } + + const normalizedQuery = query.toLowerCase(); + + const exactMatches = users.filter((u) => u.name.toLowerCase() === normalizedQuery); + const otherMatches = users.filter((u) => u.name.toLowerCase() !== normalizedQuery); + + const lines: string[] = []; + + if (exactMatches.length > 0) { + lines.push("### Exact Matches"); + for (const u of exactMatches) { + lines.push( + `* **${u.displayName}** \`@${u.name}\` — [View Profile](https://roblox.com/users/${u.id}/profile)`, + ); + } + if (otherMatches.length > 0) { + lines.push(""); + } + } + + if (otherMatches.length > 0) { + if (exactMatches.length > 0) { + lines.push("### Other Matches"); + } + const displayLimit = 10; + const displayOthers = otherMatches.slice(0, displayLimit); + + for (const u of displayOthers) { + lines.push( + `* ${u.displayName} \`@${u.name}\` — [View Profile](https://roblox.com/users/${u.id}/profile)`, + ); + } + + if (otherMatches.length > displayLimit) { + lines.push(`*...and ${otherMatches.length - displayLimit} more results.*`); + } + } + + const container = new ContainerBuilder().addTextDisplayComponents( + new TextDisplayBuilder().setContent(`**Search results for "${query}"**`), + new TextDisplayBuilder().setContent(lines.join("\n")), + ); + + return interaction.reply({ components: [container], flags: ["IsComponentsV2"] }); + }, +});
M apps/bot/src/feats/roblox/user.tsapps/bot/src/feats/roblox/user.ts

@@ -1,6 +1,5 @@

import { option } from "@purrkit/router"; import { Result } from "@sapphire/result"; -import config from "@stealth-developers/config"; import { ActionRowBuilder, ButtonBuilder,

@@ -18,7 +17,7 @@ import { useGetData } from "@/middleware";

import { Projects } from "../bugs/shared"; import { viewTicketMenu } from "../tickets/misc"; -import { RobloxUserClient, type UserRestrictionLog } from "./client"; +import { type UserRestrictionLog, client } from "./client"; type BannedBy = { displayName: string; username: string; id: string }; type Project = (typeof Projects)[keyof typeof Projects];

@@ -29,8 +28,6 @@ type EnrichedBans = Project & { bans: EnrichedUserRestrictionLog[] };

const DEFAULT_AVATAR_URL = "https://images-ext-1.discordapp.net/external/j7H9Ep50cCN5igmEKGwFnv7frj2590Xg2Z4hvHGFPPo/%3Fsize%3D4096/https/cdn.discordapp.com/icons/895998762630127616/36c6e14d0933eb338826599b632df818.png?format=webp&quality=lossless&width=852&height=852"; - -const client = new RobloxUserClient({ cookie: config.roblox.cookie, apiKey: config.roblox.apiKey }); function calculateBanDuration(from: Date, duration: string): Date { const match = duration.trim().match(/^(\d+)\s*([a-zA-Z]?)$/);