all repos — stealth-developers @ 94d6817ede30336125cbd29cdb3234467fe65b54

src/interactions/commands/search.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
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
import {
	ActionRowBuilder,
	ButtonBuilder,
	type ButtonInteraction,
	ButtonStyle,
	type ChatInputCommandInteraction,
	type Client,
	ContainerBuilder,
	type GuildMember,
	MediaGalleryBuilder,
	MediaGalleryItemBuilder,
	SlashCommandBuilder,
	TextDisplayBuilder,
	escapeMarkdown,
} from "discord.js";

import { getRobloxUser, searchRobloxUsers } from "@/utils/roblox";
import { formatUserInfo } from "@/utils/userInfo";

const commandData = new SlashCommandBuilder()
	.setName("search")
	.setDescription("search for roblox users")
	.addStringOption((option) =>
		option
			.setName("query")
			.setDescription("the username to search for")
			.setRequired(true),
	)
	.addIntegerOption((option) =>
		option
			.setName("limit")
			.setDescription("number of results to show (1-25)")
			.setMinValue(1)
			.setMaxValue(25)
			.setRequired(false),
	);

async function execute(
	_client: Client,
	interaction: ChatInputCommandInteraction,
) {
	const query = interaction.options.getString("query", true);
	const limit = interaction.options.getInteger("limit") || 10;

	await interaction.deferReply();

	const {
		users: usersResult,
		error,
		code,
	} = await searchRobloxUsers(query, limit);

	if (error) {
		if (code === 429)
			return interaction.editReply({
				content: "❌ ran into a rate limit, wait a minute or so and retry",
			});

		return interaction.editReply({
			content: `❌ error while searching: ${error}`,
		});
	}

	if (usersResult.length === 0) {
		await interaction.editReply({
			content: `❌ no users found for "${query}"`,
		});
		return;
	}

	const userContainers = Promise.all(
		usersResult.map(async (user) => {
			const userId = user.id;
			const userResult = await getRobloxUser(String(userId), 75);
			if (!userResult || "code" in userResult.user) return null;

			const container = new ContainerBuilder().setAccentColor([203, 166, 247]);
			const avatarParent = new MediaGalleryBuilder();
			const avatar = userResult.thumbnail.done
				? new MediaGalleryItemBuilder().setURL(
						userResult.thumbnail.response.imageUri,
					)
				: null;

			const title = new TextDisplayBuilder().setContent(
				`## ${userResult.user.name} (${userResult.user.id})`,
			);

			const description = new TextDisplayBuilder().setContent(
				escapeMarkdown(userResult.user.about || "no description provided"),
			);

			const profileLink = new ButtonBuilder()
				.setLabel("view profile")
				.setStyle(ButtonStyle.Link)
				.setURL(`https://www.roblox.com/users/${userResult.user.id}/profile`);
			const showMore = new ButtonBuilder()
				.setLabel("show more")
				.setStyle(ButtonStyle.Primary)
				.setCustomId(`search:${userResult.user.id}`);

			const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
				profileLink,
				showMore,
			);

			if (avatar)
				container.addMediaGalleryComponents(avatarParent.addItems(avatar));
			container.addTextDisplayComponents(title, description);
			container.addActionRowComponents(actionRow);

			return container;
		}),
	);

	const usersData = await userContainers;
	const nonNull = usersData.filter((userData) => userData !== null);

	const MAX_COMPONENTS = 40;
	const MAX_USERS = MAX_COMPONENTS / 8;
	if (nonNull.length > MAX_USERS) nonNull.length = Math.floor(MAX_USERS);

	await interaction.followUp({
		components: nonNull,
		flags: ["IsComponentsV2"],
	});
}

async function buttonExecute(_client: Client, interaction: ButtonInteraction) {
	if (!interaction.isButton() || !interaction.member) return;

	const [action, userId] = interaction.customId.split(":");
	if (action !== "search") return;

	const { user, thumbnail } = await getRobloxUser(userId, 420);
	if (!user || "code" in user)
		return interaction.reply({
			content: `❌ failed to fetch user with id ${userId}: ${user?.message || "unknown error"}`,
			flags: ["Ephemeral"],
		});

	const { embed } = await formatUserInfo(
		user,
		thumbnail.done ? thumbnail.response.imageUri : null,
		interaction.member as GuildMember,
	);

	await interaction.reply({ embeds: [embed], flags: ["Ephemeral"] });
}

export default {
	enabled: true,
	data: commandData,
	execute,
	buttonExecute,
};