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,
};
|