all repos — stealth-developers @ fa9b0a4f697756561c2736f8bce24a278c9aef53

apps/bot/src/feats/roblox/user.ts (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
import { option } from "@purrkit/router";
import {
	ActionRowBuilder,
	ButtonBuilder,
	ButtonStyle,
	ContainerBuilder,
	SectionBuilder,
	TextDisplayBuilder,
	ThumbnailBuilder,
} from "discord.js";

import { errorMessage, logger } from "@/lib";
import { useGetData } from "@/middleware";

import type { ApiErrorV2, ErrorCode } from "./types";

import { getThumbnail, getUser, getUsersByUsernames } from "./lib";

const ERROR_CODES: Set<string> = new Set<ErrorCode>([
	"INVALID_ARGUMENT",
	"PERMISSION_DENIED",
	"NOT_FOUND",
	"ABORTED",
	"RESOURCE_EXHAUSTED",
	"CANCELLED",
	"INTERNAL",
	"NOT_IMPLEMENTED",
	"UNAVAILABLE",
]);
function isErrorCode(value: string): value is ErrorCode {
	return ERROR_CODES.has(value);
}
export function isApiErrorV2(x: unknown): x is ApiErrorV2 {
	return (
		typeof x === "object" &&
		x !== null &&
		"code" in x &&
		"message" in x &&
		typeof (x as Record<string, unknown>).code === "string" &&
		typeof (x as Record<string, unknown>).message === "string" &&
		isErrorCode((x as Record<string, unknown>).code as string)
	);
}

export const robloxUserCommand = useGetData.command("user", {
	description: "Get a Roblox user's information",
	options: {
		username: option.string("The username of the user", { required: false }),
		id: option.string("The user ID of the user", { required: false }),
	},
	run: async (interaction, args, { actor: _actor }) => {
		const { username, id: userId } = args;
		if (!username && !userId)
			return errorMessage(interaction, "You must provide either a username or user ID.");

		let id = userId;

		if (username && !id) {
			const [usernameData, usernameError] = await getUsersByUsernames([username]);
			if (usernameError || (usernameData && !usernameData.data[0]))
				return errorMessage(interaction, "There was an error fetching the user's ID.");

			id = usernameData.data[0]!.id;
		}

		if (!id) return errorMessage(interaction, "You must provide a user ID.");

		const [user, error] = await getUser(id);
		if (error) {
			logger.error(error, `Failed to fetch user ${id}`);
			return errorMessage(interaction, "There was an error fetching the user.");
		}

		const [thumbnail] = await getThumbnail(id);
		let thumbnailUrl =
			"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";
		if (thumbnail) thumbnailUrl = thumbnail.response.imageUri ?? thumbnailUrl;

		const date = Math.round(new Date(user.createTime).getTime() / 1000);

		const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
			new ButtonBuilder()
				.setStyle(ButtonStyle.Link)
				.setLabel("Open Profile")
				.setURL(`https://roblox.com/users/${id}/profile`),
		);

		const userSection = new SectionBuilder()
			.addTextDisplayComponents(
				new TextDisplayBuilder().setContent(`# ${user.displayName || user.name}`),
				new TextDisplayBuilder().setContent(
					`@${user.name} - ${user.id} - created <t:${date}> (<t:${date}:R>)`,
				),
				new TextDisplayBuilder().setContent(
					user.about ? `>>> ${user.about}` : ">>> No bio provided.",
				),
			)
			.setThumbnailAccessory(new ThumbnailBuilder().setURL(thumbnailUrl));

		const container = new ContainerBuilder()
			.addSectionComponents(userSection)
			.addActionRowComponents(row);

		await interaction.reply(user.id);
		await interaction.followUp({
			components: [container],
			flags: ["IsComponentsV2"],
		});
	},
});