Skip to content

Commit 6b3221d

Browse files
Allow using 25 choices
1 parent d5ec352 commit 6b3221d

File tree

5 files changed

+56
-37
lines changed

5 files changed

+56
-37
lines changed

package-lock.json

Lines changed: 16 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bot.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Client, Collection, LimitedCollection } from "discord.js";
33
import { MyContext } from "./interfaces";
44
import { loadCommands, interactionCreateHandler } from "./handlers/InteractionCreateHandler";
55
import { messageHandler } from "./handlers/MessageHandler";
6+
import { deleteButtonHandler } from "./utils/CommandUtils";
67

78
(async function () {
89
const context: MyContext = {
@@ -15,7 +16,7 @@ import { messageHandler } from "./handlers/MessageHandler";
1516
// For DMs, a partial channel object is received, in order to receive dms, CHANNEL partials must be activated
1617
partials: ["CHANNEL"],
1718
makeCache: (manager) => {
18-
//! Disabling these caches will break djs funcitonality
19+
//! Disabling these caches will break djs functionality
1920
const unsupportedCaches = [
2021
"GuildManager",
2122
"ChannelManager",
@@ -38,6 +39,8 @@ import { messageHandler } from "./handlers/MessageHandler";
3839
};
3940
const docsBot = context.client;
4041
await loadCommands(context);
42+
// Add delete button handler
43+
context.commands.buttons.set("deletebtn", { custom_id: "deletebtn", run: deleteButtonHandler });
4144

4245
docsBot.on("error", console.error);
4346
docsBot.on("warn", console.warn);

src/commands/docs/djs.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Command } from "../../interfaces";
22
import { SlashCommandBuilder } from "@discordjs/builders";
33
import Doc, { sources } from "discord.js-docs";
44
import { checkEmbedLimits } from "../../utils/EmbedUtils";
5-
import { deleteButton, deleteButtonHandler } from "../../utils/CommandUtils";
5+
import { deleteButton } from "../../utils/CommandUtils";
66
import { MessageActionRow, MessageSelectMenu } from "discord.js";
77
import type { APIEmbed } from "discord-api-types";
88

@@ -18,7 +18,9 @@ const command: Command = {
1818
.addStringOption((option) =>
1919
option
2020
.setName("query")
21-
.setDescription("Enter the phrase you'd like to search for, e.g: client#isready")
21+
.setDescription(
22+
"Enter the phrase you'd like to search for, e.g: client#isready or builders:slashcommandbuilder",
23+
)
2224
.setRequired(true)
2325
.setAutocomplete(true),
2426
)
@@ -40,8 +42,9 @@ const command: Command = {
4042
const queryOption = interaction.options.getString("query");
4143
const sourceOption = interaction.options.getString("source") as keyof typeof sources;
4244

45+
// Support the source:Query format
4346
let { Source: source = "stable", Query: query } =
44-
queryOption.match(/(?:(?<Source>[^/]*)\/)?(?<Query>(?:.|\s)*)/i)?.groups ?? {};
47+
queryOption.match(/(?:(?<Source>[^:]*):)?(?<Query>(?:.|\s)*)/i)?.groups ?? {};
4548
// The Default source should be stable
4649
if (!sources[source]) source = "stable";
4750

@@ -101,7 +104,6 @@ const command: Command = {
101104
return;
102105
},
103106
},
104-
buttons: [{ custom_id: "deletebtn", run: deleteButtonHandler }],
105107
selectMenus: [
106108
{
107109
custom_id: "djsselect",
@@ -130,8 +132,9 @@ const command: Command = {
130132
focusedOption: "query",
131133
async run(interaction, focusedOption) {
132134
const focusedOptionValue = focusedOption.value as string;
135+
// Support source:query format
133136
let { Branch: branchOrProject = "stable", Query: query } =
134-
focusedOptionValue.match(/(?:(?<Branch>[^/]*)\/)?(?<Query>(?:.|\s)*)/i)?.groups ?? {};
137+
focusedOptionValue.match(/(?:(?<Branch>[^:]*):)?(?<Query>(?:.|\s)*)/i)?.groups ?? {};
135138
if (!sources[branchOrProject]) branchOrProject = "stable";
136139

137140
const doc = await Doc.fetch(branchOrProject, { force: false });
@@ -141,13 +144,13 @@ const command: Command = {
141144
.respond([
142145
{
143146
name: singleElement.formattedName,
144-
value: branchOrProject + "/" + singleElement.formattedName,
147+
value: branchOrProject + ":" + singleElement.formattedName,
145148
},
146149
])
147150
.catch(console.error);
148151
return;
149152
}
150-
const searchResults = doc.search(query, { excludePrivateElements: false });
153+
const searchResults = doc.search(query, { excludePrivateElements: false, maxResults: 25 });
151154
if (!searchResults) {
152155
await interaction.respond([]).catch(console.error);
153156
return;
@@ -156,7 +159,7 @@ const command: Command = {
156159
.respond(
157160
searchResults.map((elem) => ({
158161
name: elem.formattedName,
159-
value: branchOrProject + "/" + elem.formattedName,
162+
value: branchOrProject + ":" + elem.formattedName,
160163
})),
161164
)
162165
.catch(console.error);
@@ -174,14 +177,16 @@ function capitalize(str: string) {
174177

175178
// Export to reuse on the select menu handler
176179
export function searchDJSDoc(doc: Doc, query: string, searchPrivate?: boolean) {
177-
const options = { excludePrivateElements: !searchPrivate };
180+
// Get first 25 results
181+
const options = { excludePrivateElements: !searchPrivate, maxResults: 25 };
178182

179183
const singleElement = doc.get(...query.split(/\.|#/));
180184
// Return embed for the single element, the exact match
181185
if (singleElement) return singleElement.embed(options);
182186

183187
const searchResults = doc.search(query, options);
184188
if (!searchResults) return null;
189+
185190
return searchResults.map((res) => {
186191
const parsedDescription = res.description?.trim?.() ?? "No description provided";
187192
// Labels and values have a limit of 100 characters

src/commands/docs/mdn.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { SlashCommandBuilder } from "@discordjs/builders";
2-
import { deleteButton, deleteButtonHandler } from "../../utils/CommandUtils";
2+
import { deleteButton } from "../../utils/CommandUtils";
33
import { MessageActionRow, MessageEmbed, MessageSelectMenu } from "discord.js";
44
import { gunzipSync } from "zlib";
55
import { XMLParser } from "fast-xml-parser";
@@ -39,7 +39,8 @@ const command: Command = {
3939
const deleteButtonRow = new MessageActionRow().addComponents([deleteButton(interaction.user.id)]);
4040
const query = interaction.options.getString("query");
4141
const { index, sitemap } = await getSources();
42-
const search: string[] = index.search(query, { limit: 10 }).map((id) => sitemap[<number>id].loc);
42+
// Get the top 25 results
43+
const search: string[] = index.search(query, { limit: 25 }).map((id) => sitemap[<number>id].loc);
4344
const embed = new MessageEmbed()
4445
.setColor(MDN_BLUE_COLOR)
4546
.setAuthor({ name: "MDN Documentation", iconURL: MDN_ICON_URL })
@@ -49,8 +50,9 @@ const command: Command = {
4950
embed.setColor(0xff0000).setDescription("No results found...");
5051
await interaction.editReply({ embeds: [embed] }).catch(console.error);
5152
return;
52-
} else if (search.length === 1) {
53-
const resultEmbed = await getSingleMDNSearchResults(search[0]);
53+
} else if (search.length === 1 || search.includes(query)) {
54+
// If there's an exact match
55+
const resultEmbed = await getSingleMDNSearchResults(search.includes(query) ? query : search[0]);
5456
if (!resultEmbed) {
5557
await interaction.editReply({ content: "Couldn't find any results" }).catch(console.error);
5658
return;
@@ -109,15 +111,16 @@ const command: Command = {
109111
},
110112
},
111113
],
112-
buttons: [{ custom_id: "deletebtn", run: deleteButtonHandler }],
113114
autocomplete: [
114115
{
115116
focusedOption: "query",
116117
async run(interaction, focusedOption) {
117118
const query = focusedOption.value as string;
118119
const { index, sitemap } = await getSources();
119-
const search = index.search(query, { limit: 10 }).map((id) => {
120+
// The limit for autocomplete options is 25
121+
const search = index.search(query, { limit: 25 }).map((id) => {
120122
const val = sitemap[<number>id].loc;
123+
// Values and names have a limit of 100 characters
121124
const parsed = val.length >= 99 ? val.split("/").slice(-2).join("/") : val;
122125
return { name: parsed, value: parsed };
123126
});
@@ -131,9 +134,11 @@ const command: Command = {
131134
export async function getSingleMDNSearchResults(searchQuery: string) {
132135
// Search for the match once again
133136
const { index, sitemap } = await getSources();
134-
const secondSearch = index.search(searchQuery, { limit: 10 }).map((id) => sitemap[<number>id].loc)[0];
135-
136-
const res = await fetch(`${MDN_BASE_URL + secondSearch}/index.json`).catch(console.error);
137+
// Search one more time
138+
const secondSearch = index.search(searchQuery, { limit: 25 }).map((id) => sitemap[<number>id].loc);
139+
// Since it returns an array, the exact match might not be the first selection, if the exact match exists, fetch using that, if not get the first result
140+
const finalSelection = secondSearch.includes(searchQuery) ? searchQuery : secondSearch[0];
141+
const res = await fetch(`${MDN_BASE_URL + finalSelection}/index.json`).catch(console.error);
137142
if (!res || !res?.ok) return null;
138143
const resJSON = await res.json?.().catch(console.error);
139144
if (!res.json) return null;
@@ -154,8 +159,8 @@ export async function getSources(): Promise<typeof sources> {
154159

155160
const res = await fetch("https://developer.mozilla.org/sitemaps/en-us/sitemap.xml.gz");
156161
if (!res.ok) return sources; // Fallback to old sources if the new ones are not available for any reason
157-
const something = new XMLParser().parse(gunzipSync(await res.buffer()).toString());
158-
const sitemap: Sitemap<number> = something.urlset.url.map((entry: SitemapEntry<string>) => ({
162+
const parsedSitemap = new XMLParser().parse(gunzipSync(await res.buffer()).toString());
163+
const sitemap: Sitemap<number> = parsedSitemap.urlset.url.map((entry: SitemapEntry<string>) => ({
159164
loc: entry.loc.slice(MDN_BASE_URL.length),
160165
lastmod: new Date(entry.lastmod).valueOf(),
161166
}));

src/handlers/InteractionCreateHandler.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { commandCooldownCheck, commandPermissionCheck } from "../utils/CommandUtils";
2+
import glob from "glob";
13
import type { Command, MyContext } from "../interfaces";
24
import type {
35
AutocompleteInteraction,
@@ -7,10 +9,6 @@ import type {
79
SelectMenuInteraction,
810
} from "discord.js";
911

10-
import { commandCooldownCheck, commandPermissionCheck } from "../utils/CommandUtils";
11-
12-
import glob from "glob";
13-
1412
export async function interactionCreateHandler(context: MyContext, interaction: Interaction<"cached">) {
1513
try {
1614
if (interaction.isCommand()) {
@@ -80,7 +78,8 @@ async function commandInteractionHandler(context: MyContext, interaction: Comman
8078
}
8179
}
8280
async function buttonInteractionHandler(context: MyContext, interaction: ButtonInteraction<"cached">) {
83-
const button = context.commands.buttons.find((but) => interaction.customId.startsWith(but.custom_id));
81+
const buttonId = interaction.customId.split("/")[0];
82+
const button = context.commands.buttons.get(buttonId);
8483
if (button) {
8584
await button.run(interaction, context).catch(console.error);
8685
return;
@@ -93,9 +92,8 @@ async function buttonInteractionHandler(context: MyContext, interaction: ButtonI
9392
async function selectMenuInteractionHandler(context: MyContext, interaction: SelectMenuInteraction<"cached">) {
9493
await interaction.deferUpdate().catch(console.error);
9594

96-
const menu = context.commands.selectMenus.find((selectmenu) =>
97-
interaction.customId.startsWith(selectmenu.custom_id),
98-
);
95+
const menuId = interaction.customId.split("/")[0];
96+
const menu = context.commands.selectMenus.get(menuId);
9997
if (menu) {
10098
await menu.run(interaction, context).catch(console.error);
10199
return;

0 commit comments

Comments
 (0)