Skip to content

Commit 7af4cd5

Browse files
Add djs select menu
1 parent 47f6d30 commit 7af4cd5

File tree

5 files changed

+281
-145
lines changed

5 files changed

+281
-145
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"rules": {
1919
"indent": [
2020
"error",
21-
4
21+
4, { "SwitchCase": 1 }
2222
],
2323
"linebreak-style": [
2424
"error",

src/commands/docs/djs.ts

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { SlashCommandBuilder } from "@discordjs/builders";
33
import Doc, { sources } from "discord.js-docs";
44
import { checkEmbedLimits } from "../../utils/EmbedUtils";
55
import { deleteButton } from "../../utils/CommandUtils";
6-
import { MessageActionRow } from "discord.js";
6+
import { MessageActionRow, MessageSelectMenu } from "discord.js";
7+
import { APIEmbed } from "discord-api-types";
78

89
const supportedBranches = Object.keys(sources).map((branch) => [capitalize(branch), branch] as [string, string]);
910

@@ -31,7 +32,7 @@ const command: Command = {
3132
.setRequired(false),
3233
),
3334
async execute(interaction) {
34-
const deleteButtonRow = new MessageActionRow().addComponents([deleteButton]);
35+
const deleteButtonRow = new MessageActionRow().addComponents([deleteButton(interaction.user.id)]);
3536
const query = interaction.options.getString("query");
3637
// The Default source should be stable
3738
const source: keyof typeof sources =
@@ -40,20 +41,36 @@ const command: Command = {
4041
const searchPrivate = interaction.options.getBoolean("private") || false;
4142
const doc = await Doc.fetch(source, { force: true });
4243

43-
const resultEmbed = doc.resolveEmbed(query, { excludePrivateElements: !searchPrivate });
44-
45-
const notFoundEmbed = doc.baseEmbed();
46-
notFoundEmbed.description = "Didn't find any results for that query";
44+
// const resultEmbed = doc.resolveEmbed(query, { excludePrivateElements: !searchPrivate });
45+
const result = searchDJSDoc(doc, query, searchPrivate);
4746
// If a result wasn't found
48-
if (!resultEmbed || resultEmbed.description === "") {
47+
if (!result || (result as APIEmbed).description === "") {
48+
const notFoundEmbed = doc.baseEmbed();
49+
notFoundEmbed.description = "Didn't find any results for that query";
50+
4951
const timeStampDate = new Date(notFoundEmbed.timestamp);
5052
// Satisfies the method's MessageEmbedOption type
5153
const embedObj = { ...notFoundEmbed, timestamp: timeStampDate };
5254

53-
interaction.editReply({ embeds: [embedObj], components: [deleteButtonRow] }).catch(console.error);
55+
await interaction.reply({ embeds: [embedObj], ephemeral: true }).catch(console.error);
56+
return;
57+
} else if (Array.isArray(result)) {
58+
const selectMenuRow = new MessageActionRow().addComponents(
59+
new MessageSelectMenu()
60+
.setCustomId(`djsselect/${source}/${searchPrivate}/${interaction.user.id}`)
61+
.addOptions(result)
62+
.setPlaceholder("Select documentation to send"),
63+
);
64+
await interaction
65+
.reply({
66+
content: "Didn't find an exact match, please select one from below",
67+
ephemeral: true,
68+
components: [selectMenuRow],
69+
})
70+
.catch(console.error);
5471
return;
5572
}
56-
73+
const resultEmbed = result;
5774
const timeStampDate = new Date(resultEmbed.timestamp);
5875
const embedObj = { ...resultEmbed, timestamp: timeStampDate };
5976

@@ -63,7 +80,7 @@ const command: Command = {
6380
// The final field should be the View Source button
6481
embedObj.fields = [embedObj.fields?.at(-1)];
6582
}
66-
interaction.editReply({ embeds: [embedObj], components: [deleteButtonRow] }).catch(console.error);
83+
await interaction.reply({ embeds: [embedObj], components: [deleteButtonRow] }).catch(console.error);
6784
return;
6885
},
6986
};
@@ -75,4 +92,43 @@ function capitalize(str: string) {
7592
.join("-");
7693
}
7794

95+
export function searchDJSDoc(doc: Doc, query: string, searchPrivate?: boolean) {
96+
const options = { excludePrivateElements: !searchPrivate };
97+
98+
const singleElement = doc.get(...query.split(/\.|#/));
99+
if (singleElement) return singleElement.embed(options);
100+
101+
const searchResults = doc.search(query, options);
102+
if (!searchResults) return null;
103+
return searchResults.map((res) => {
104+
const description = res.description.length >= 99 ? res.description.slice(0, 96) + "..." : res.description;
105+
return {
106+
label: res.formattedName,
107+
description,
108+
emoji: resolveRegionalEmoji(res.embedPrefix),
109+
value: res.formattedName,
110+
};
111+
});
112+
}
113+
function resolveRegionalEmoji(regionalEmoji: string) {
114+
const character = regionalEmoji.match(/:regional_indicator_(.):/)?.[1];
115+
if (!character) return null;
116+
switch (character) {
117+
case "c":
118+
return "🇨";
119+
case "e":
120+
return "🇪";
121+
case "i":
122+
return "🇮";
123+
case "m":
124+
return "🇲";
125+
case "t":
126+
return "🇹";
127+
case "p":
128+
return "🇵";
129+
default:
130+
return null;
131+
}
132+
}
133+
78134
export default command;

src/commands/docs/mdn.ts

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { SlashCommandBuilder } from "@discordjs/builders";
22
import { deleteButton } from "../../utils/CommandUtils";
3-
import { MessageActionRow, MessageEmbed } from "discord.js";
3+
import { MessageActionRow, MessageEmbed, MessageSelectMenu } from "discord.js";
44
import { gunzipSync } from "zlib";
55
import { XMLParser } from "fast-xml-parser";
66
import { Command } from "../../interfaces";
@@ -34,53 +34,79 @@ const command: Command = {
3434
.setRequired(true),
3535
),
3636
async execute(interaction) {
37-
const deleteButtonRow = new MessageActionRow().addComponents([deleteButton]);
37+
const deleteButtonRow = new MessageActionRow().addComponents([deleteButton(interaction.user.id)]);
3838
const query = interaction.options.getString("query");
3939
const { index, sitemap } = await getSources();
4040
const search: string[] = index.search(query, { limit: 10 }).map((id) => sitemap[<number>id].loc);
4141
const embed = new MessageEmbed()
4242
.setColor(MDN_BLUE_COLOR)
4343
.setAuthor({ name: "MDN Documentation", iconURL: MDN_ICON_URL })
44-
.setTitle(`Search for: ${query}`);
44+
.setTitle(`Search for: ${query.slice(0, 243)}`);
4545

4646
if (!search.length) {
4747
embed.setColor(0xff0000).setDescription("No results found...");
48-
interaction.editReply({ embeds: [embed], components: [deleteButtonRow] });
48+
await interaction.reply({ embeds: [embed], ephemeral: true });
4949
return;
50-
}
50+
} else if (search.length === 1) {
51+
const resultEmbed = await getSingleMDNSearchResults(search[0]);
52+
await interaction
53+
.reply({
54+
embeds: [resultEmbed],
55+
components: [deleteButtonRow],
56+
})
57+
.catch(console.error);
5158

52-
if (search.length === 1) {
53-
const res = await fetch(`${MDN_BASE_URL + search[0]}/index.json`);
54-
const doc: MdnDoc = (await res.json()).doc;
55-
const docEmbed = embed
56-
.setColor(0xffffff)
57-
.setTitle(doc.pageTitle)
58-
.setURL(`https://developer.mozilla.org/${doc.mdn_url}`)
59-
.setThumbnail(this.MDN_ICON_URL)
60-
.setDescription(doc.summary);
61-
interaction.editReply({ embeds: [docEmbed], components: [deleteButtonRow] });
6259
return;
63-
}
60+
} else {
61+
const results = search.map((path) => `**• [${path.replace(/_|-/g, " ")}](${MDN_BASE_URL}${path})**`);
6462

65-
const results = search.map((path) => `**• [${path.replace(/_|-/g, " ")}](${MDN_BASE_URL}${path})**`);
66-
embed.setDescription(results.join("\n"));
67-
interaction.editReply({ embeds: [embed], components: [deleteButtonRow] });
68-
return;
63+
embed.setDescription(results.join("\n"));
64+
const selectMenuRow = new MessageActionRow().addComponents(
65+
new MessageSelectMenu()
66+
.setCustomId("mdnselect/" + interaction.user.id)
67+
.addOptions(
68+
search.map((val) => {
69+
const parsed = val.length >= 99 ? val.split("/").at(-1) : val;
70+
return { label: parsed, value: parsed };
71+
}),
72+
)
73+
.setPlaceholder("Select documentation to send"),
74+
);
75+
await interaction
76+
.reply({
77+
content: "Didn't find an exact match, please select one from below",
78+
ephemeral: true,
79+
components: [selectMenuRow],
80+
})
81+
.catch(console.error);
82+
return;
83+
}
6984
},
7085
};
7186

87+
export async function getSingleMDNSearchResults(searchQuery: string) {
88+
const res = await fetch(`${MDN_BASE_URL + searchQuery}/index.json`);
89+
const doc: MdnDoc = (await res.json()).doc;
90+
91+
return new MessageEmbed()
92+
.setColor(MDN_BLUE_COLOR)
93+
.setAuthor({ name: "MDN Documentation", iconURL: MDN_ICON_URL })
94+
.setColor(0xffffff)
95+
.setTitle(doc.pageTitle)
96+
.setURL(`https://developer.mozilla.org/${doc.mdn_url}`)
97+
.setThumbnail(MDN_ICON_URL)
98+
.setDescription(doc.summary);
99+
}
72100
async function getSources(): Promise<typeof sources> {
73101
if (sources.lastUpdated && Date.now() - sources.lastUpdated < 43200000 /* 12 hours */) return sources;
74102

75103
const res = await fetch("https://developer.mozilla.org/sitemaps/en-us/sitemap.xml.gz");
76104
if (!res.ok) return sources; // Fallback to old sources if the new ones are not available for any reason
77-
78-
const sitemap: Sitemap<number> = new XMLParser()
79-
.parse(gunzipSync(await res.buffer()).toString())
80-
.urlset.url.map((entry: SitemapEntry<string>) => ({
81-
loc: entry.loc.slice(MDN_BASE_URL.length),
82-
lastmod: new Date(entry.lastmod).valueOf(),
83-
}));
105+
const something = new XMLParser().parse(gunzipSync(await res.buffer()).toString());
106+
const sitemap: Sitemap<number> = something.urlset.url.map((entry: SitemapEntry<string>) => ({
107+
loc: entry.loc.slice(MDN_BASE_URL.length),
108+
lastmod: new Date(entry.lastmod).valueOf(),
109+
}));
84110

85111
const index = new flexsearch.Index();
86112
sitemap.forEach((entry, idx) => index.add(idx, entry.loc));

0 commit comments

Comments
 (0)