all repos — stealth-developers @ 6c06f4e3a6d9cddf81f94f2cd254d222fef3feab

feat: get linked roblox account; moved common logic to util module
vi v@vt3e.cat
Sun, 01 Mar 2026 01:33:23 +0000
commit

6c06f4e3a6d9cddf81f94f2cd254d222fef3feab

parent

6bb17a8faa466982ae7085638f89778869d56644

M src/config.tssrc/config.ts

@@ -7,6 +7,7 @@

const robloxSchema = z.object({ cookie: z.string(), apiKey: z.string(), + bloxlinkToken: z.string(), }); const discordSchema = z.object({
M src/interactions/commands/roblox/getUser.tssrc/interactions/commands/roblox/getUser.ts

@@ -5,10 +5,10 @@ type GuildMember,

SlashCommandBuilder, } from "discord.js"; -import { isApiErrorV2, roblox } from "@/roblox/client"; +import { roblox } from "@/roblox/client"; import type { RobloxUserId } from "@/roblox/types"; import { hasManagerPermissions } from "@/utils/permissions"; -import { constructUserContainer } from "@/utils/profile"; +import { processRobloxUserInteraction } from "@/utils/robloxUser"; const commandData = new SlashCommandBuilder() .setName("user")

@@ -49,79 +49,46 @@ );

await interaction.deferReply(); try { - if (subcommand === "discord") { - await interaction.editReply("Not implemented"); - return; - } - - const input = interaction.options.getString("input", true); - const isId = input.startsWith("id:"); let id: RobloxUserId; - if (isId) { - id = input.slice(3); - if (!id) { - await interaction.editReply("Invalid ID provided."); + if (subcommand === "discord") { + const target = interaction.options.getUser("user", true); + const [linkedRes, linkedErr] = await roblox.getLinkedAccount(target.id); + if (linkedErr) { + const message = linkedErr.error; + if (message === "User not found") { + await interaction.editReply( + "No linked Roblox account found for that user.", + ); + return; + } + await interaction.editReply( + `There was an error fetching the linked account: ${linkedErr.error}`, + ); return; } + id = linkedRes.robloxID; } else { - const [usersRes] = await roblox.getUsersByUsernames([input]); - if (!usersRes || !usersRes.data || usersRes.data.length === 0) { - await interaction.editReply("User not found"); - return; - } - id = usersRes.data[0].id; - } + const input = interaction.options.getString("input", true); + const isId = input.startsWith("id:"); - await interaction.editReply(`Fetching user with ID ${id}...`); - const [userRes, userErr] = await roblox.getUser(id); - if (userErr) { - if (isApiErrorV2(userErr)) { - await interaction.editReply( - `There was an error fetching the user: ${userErr.message}`, - ); + if (isId) { + id = input.slice(3); + if (!id) { + await interaction.editReply("Invalid ID provided."); + return; + } } else { - await interaction.editReply("There was an error fetching the user."); + const [usersRes] = await roblox.getUsersByUsernames([input]); + if (!usersRes || !usersRes.data || usersRes.data.length === 0) { + await interaction.editReply("User not found"); + return; + } + id = usersRes.data[0].id; } - return; } - const user = userRes; - await interaction.editReply( - `Found user with username ${user.name}, fetching thumbnail...`, - ); - const [thumbnailRes] = await roblox.getThumbnail(id, { shape: "SQUARE" }); - const thumbnailUri = thumbnailRes ? thumbnailRes.response.imageUri : null; - - await interaction.editReply(`Fetching bans for user with ID ${id}...`); - const [bansRes] = await roblox.getUserRestrictionLogs("21449357", id); - const bans = bansRes ?? null; - - const randomMessages = [ - "grok is this true", - "<:swagong:1176858666452922469>", - "awawawawawawawawawawwaawawawwa", - "<:wawa:1158423817698430977>", - "<:max:1476776753845370991>", - "<:snickerdoodle:1477173429575749716>", - ]; - const message = - randomMessages[Math.floor(Math.random() * randomMessages.length)]; - await interaction.editReply(message); - const { - mainContainer, - bans: { container: bansContainer }, - } = await constructUserContainer(user, { - bans, - thumbnailUri, - showPrivate: isModerator, - }); - - interaction.editReply(user.id); - await interaction.followUp({ - flags: ["IsComponentsV2"], - components: [mainContainer, bansContainer], - }); + await processRobloxUserInteraction(interaction, id, isModerator); } catch (err) { console.error(err); await interaction.editReply(
A src/interactions/menus/getUser.ts

@@ -0,0 +1,50 @@

+import { roblox } from "@/roblox/client"; +import type { RobloxUserId } from "@/roblox/types"; +import { hasManagerPermissions } from "@/utils/permissions"; +import { processRobloxUserInteraction } from "@/utils/robloxUser"; +import { + ApplicationCommandType, + type Client, + ContextMenuCommandBuilder, + type GuildMember, + type MessageContextMenuCommandInteraction, +} from "discord.js"; + +const commandData = new ContextMenuCommandBuilder() + .setName("Get Roblox Account") + .setType(ApplicationCommandType.User); + +async function execute( + _client: Client, + interaction: MessageContextMenuCommandInteraction, +) { + const isModerator = await hasManagerPermissions( + interaction.member as GuildMember, + ); + await interaction.deferReply(); + + const target = interaction.targetId; + const [linkedRes, linkedErr] = await roblox.getLinkedAccount(target); + + if (linkedErr) { + const message = linkedErr.error; + if (message === "User not found") { + await interaction.editReply( + "No linked Roblox account found for that user.", + ); + return; + } + await interaction.editReply( + `There was an error fetching the linked account: ${linkedErr.error}`, + ); + return; + } + + const id: RobloxUserId = linkedRes.robloxID; + await processRobloxUserInteraction(interaction, id, isModerator); +} + +export default { + data: commandData, + execute, +};
M src/roblox/client.tssrc/roblox/client.ts

@@ -2,6 +2,7 @@ import config from "@/config";

import type { ApiError, ApiErrorV2, + BloxlinkResult, ErrorCode, GatewayError, GenerateThumbnailOptions,

@@ -204,6 +205,23 @@ const path = `${_path}?${query.toString()}`;

const [data, err] = await this.request<GenerateThumbnailResponse>(path); if (err) return [null, err]; + + return [data, null]; + } + + async getLinkedAccount(discordId: string): Promise<BloxlinkResult> { + const response = await fetch( + `https://api.blox.link/v4/public/discord-to-roblox/${discordId}`, + { + headers: { + Authorization: config.roblox.bloxlinkToken, + }, + }, + ); + + const data = await response.json(); + + if (!response.ok) return [null, { error: data.error || "Unknown error" }]; return [data, null]; }
M src/roblox/types/users.tssrc/roblox/types/users.ts

@@ -64,3 +64,13 @@ shape?: "SHAPE_UNSPECIFIED" | "ROUND" | "SQUARE";

format?: "FORMAT_UNSPECIFIED" | "PNG" | "JPEG"; size?: 48 | 50 | 60 | 75 | 100 | 110 | 150 | 180 | 352 | 420 | 720; } + +// -- bloxlink ----------------------------------------------------------------- +export type BloxlinkError = { + error: string; +}; +export type BloxlinkSuccess = { + robloxID: string; + resolved: object; +}; +export type BloxlinkResult = [BloxlinkSuccess, null] | [null, BloxlinkError];
A src/utils/robloxUser.ts

@@ -0,0 +1,73 @@

+import { isApiErrorV2, roblox } from "@/roblox/client"; +import type { RobloxUserId } from "@/roblox/types"; +import { constructUserContainer } from "@/utils/profile"; +import type { + ChatInputCommandInteraction, + MessageContextMenuCommandInteraction, +} from "discord.js"; + +export async function processRobloxUserInteraction( + interaction: + | ChatInputCommandInteraction + | MessageContextMenuCommandInteraction, + id: RobloxUserId, + isModerator: boolean, +) { + try { + await interaction.editReply(`Fetching user with ID ${id}...`); + const [userRes, userErr] = await roblox.getUser(id); + if (userErr) { + if (isApiErrorV2(userErr)) { + await interaction.editReply( + `There was an error fetching the user: ${userErr.message}`, + ); + } else { + await interaction.editReply("There was an error fetching the user."); + } + return; + } + const user = userRes; + + await interaction.editReply( + `Found user with username ${user.name}, fetching thumbnail...`, + ); + const [thumbnailRes] = await roblox.getThumbnail(id, { shape: "SQUARE" }); + const thumbnailUri = thumbnailRes ? thumbnailRes.response.imageUri : null; + + await interaction.editReply(`Fetching bans for user with ID ${id}...`); + const [bansRes] = await roblox.getUserRestrictionLogs("21449357", id); + const bans = bansRes ?? null; + + const randomMessages = [ + "grok is this true", + "<:swagong:1176858666452922469>", + "awawawawawawawawawawwaawawawwa", + "<:wawa:1158423817698430977>", + "<:max:1476776753845370991>", + "<:snickerdoodle:1477173429575749716>", + ]; + const message = + randomMessages[Math.floor(Math.random() * randomMessages.length)]; + await interaction.editReply(message); + + const { + mainContainer, + bans: { container: bansContainer }, + } = await constructUserContainer(user, { + bans, + thumbnailUri, + showPrivate: isModerator, + }); + + await interaction.editReply(user.id); + await interaction.followUp({ + flags: ["IsComponentsV2"], + components: [mainContainer, bansContainer], + }); + } catch (err) { + console.error(err); + await interaction.editReply( + "An unexpected error occurred while fetching the user.", + ); + } +}