11import { SlashCommandBuilder } from "@discordjs/builders" ;
2- import { MessageEmbed } from "discord.js" ;
2+ import { deleteButton } from "../../utils/CommandUtils" ;
3+ import { MessageActionRow , MessageEmbed , MessageSelectMenu } from "discord.js" ;
34import { gunzipSync } from "zlib" ;
45import { XMLParser } from "fast-xml-parser" ;
56import { Command } from "../../interfaces" ;
@@ -20,7 +21,7 @@ let sources = {
2021
2122const MDN_BASE_URL = "https://developer.mozilla.org/en-US/docs/" as const ;
2223const MDN_ICON_URL = "https://i.imgur.com/1P4wotC.png" as const ;
23- const MDN_BLUE_COLOR = 0x83BFFF as const ;
24+ const MDN_BLUE_COLOR = 0x83bfff as const ;
2425
2526const command : Command = {
2627 data : new SlashCommandBuilder ( )
@@ -33,52 +34,81 @@ const command: Command = {
3334 . setRequired ( true ) ,
3435 ) ,
3536 async execute ( interaction ) {
37+ const deleteButtonRow = new MessageActionRow ( ) . addComponents ( [ deleteButton ( interaction . user . id ) ] ) ;
3638 const query = interaction . options . getString ( "query" ) ;
3739 const { index, sitemap } = await getSources ( ) ;
3840 const search : string [ ] = index . search ( query , { limit : 10 } ) . map ( ( id ) => sitemap [ < number > id ] . loc ) ;
3941 const embed = new MessageEmbed ( )
4042 . setColor ( MDN_BLUE_COLOR )
41- . setAuthor ( "MDN Documentation" , MDN_ICON_URL )
42- . setTitle ( `Search for: ${ query } ` ) ;
43+ . setAuthor ( { name : "MDN Documentation" , iconURL : MDN_ICON_URL } )
44+ . setTitle ( `Search for: ${ query . slice ( 0 , 243 ) } ` ) ;
4345
4446 if ( ! search . length ) {
45- embed . setColor ( 0xFF0000 ) . setDescription ( "No results found..." ) ;
46- interaction . editReply ( { embeds : [ embed ] } ) ;
47+ embed . setColor ( 0xff0000 ) . setDescription ( "No results found..." ) ;
48+ await interaction . reply ( { embeds : [ embed ] , ephemeral : true } ) ;
4749 return ;
48- }
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 ) ;
4958
50- if ( search . length === 1 ) {
51- const res = await fetch ( `${ MDN_BASE_URL + search [ 0 ] } /index.json` ) ;
52- const doc : MdnDoc = ( await res . json ( ) ) . doc ;
53- const docEmbed = embed
54- . setColor ( 0xFFFFFF )
55- . setTitle ( doc . pageTitle )
56- . setURL ( `https://developer.mozilla.org/${ doc . mdn_url } ` )
57- . setThumbnail ( this . MDN_ICON_URL )
58- . setDescription ( doc . summary ) ;
59- interaction . editReply ( { embeds : [ docEmbed ] } ) ;
6059 return ;
61- }
60+ } else {
61+ // If there are multiple results, send a select menu from which the user can choose which one to send
62+ const results = search . map ( ( path ) => `**• [${ path . replace ( / _ | - / g, " " ) } ](${ MDN_BASE_URL } ${ path } )**` ) ;
6263
63- const results = search . map ( ( path ) => `**• [${ path . replace ( / _ | - / g, " " ) } ](${ MDN_BASE_URL } ${ path } )**` ) ;
64- embed . setDescription ( results . join ( "\n" ) ) ;
65- interaction . editReply ( { embeds : [ embed ] } ) ;
66- return ;
64+ embed . setDescription ( results . join ( "\n" ) ) ;
65+ const selectMenuRow = new MessageActionRow ( ) . addComponents (
66+ new MessageSelectMenu ( )
67+ . setCustomId ( "mdnselect/" + interaction . user . id )
68+ . addOptions (
69+ search . map ( ( val ) => {
70+ const parsed = val . length >= 99 ? val . split ( "/" ) . at ( - 1 ) : val ;
71+ return { label : parsed , value : parsed } ;
72+ } ) ,
73+ )
74+ . setPlaceholder ( "Select documentation to send" ) ,
75+ ) ;
76+ await interaction
77+ . reply ( {
78+ content : "Didn't find an exact match, please select one from below" ,
79+ ephemeral : true ,
80+ components : [ selectMenuRow ] ,
81+ } )
82+ . catch ( console . error ) ;
83+ return ;
84+ }
6785 } ,
6886} ;
6987
88+ // Export to reuse on the select menu handler
89+ export async function getSingleMDNSearchResults ( searchQuery : string ) {
90+ const res = await fetch ( `${ MDN_BASE_URL + searchQuery } /index.json` ) ;
91+ const doc : MdnDoc = ( await res . json ( ) ) . doc ;
92+
93+ return new MessageEmbed ( )
94+ . setColor ( MDN_BLUE_COLOR )
95+ . setAuthor ( { name : "MDN Documentation" , iconURL : MDN_ICON_URL } )
96+ . setColor ( 0xffffff )
97+ . setTitle ( doc . pageTitle )
98+ . setURL ( `https://developer.mozilla.org/${ doc . mdn_url } ` )
99+ . setThumbnail ( MDN_ICON_URL )
100+ . setDescription ( doc . summary ) ;
101+ }
70102async function getSources ( ) : Promise < typeof sources > {
71103 if ( sources . lastUpdated && Date . now ( ) - sources . lastUpdated < 43200000 /* 12 hours */ ) return sources ;
72104
73105 const res = await fetch ( "https://developer.mozilla.org/sitemaps/en-us/sitemap.xml.gz" ) ;
74106 if ( ! res . ok ) return sources ; // Fallback to old sources if the new ones are not available for any reason
75-
76- const sitemap : Sitemap < number > = new XMLParser ( )
77- . parse ( gunzipSync ( await res . buffer ( ) ) . toString ( ) )
78- . urlset . url . map ( ( entry : SitemapEntry < string > ) => ( {
79- loc : entry . loc . slice ( MDN_BASE_URL . length ) ,
80- lastmod : new Date ( entry . lastmod ) . valueOf ( ) ,
81- } ) ) ;
107+ const something = new XMLParser ( ) . parse ( gunzipSync ( await res . buffer ( ) ) . toString ( ) ) ;
108+ const sitemap : Sitemap < number > = something . urlset . url . map ( ( entry : SitemapEntry < string > ) => ( {
109+ loc : entry . loc . slice ( MDN_BASE_URL . length ) ,
110+ lastmod : new Date ( entry . lastmod ) . valueOf ( ) ,
111+ } ) ) ;
82112
83113 const index = new flexsearch . Index ( ) ;
84114 sitemap . forEach ( ( entry , idx ) => index . add ( idx , entry . loc ) ) ;
0 commit comments