77 ActionRowBuilder,
88 ButtonBuilder,
99 ButtonStyle,
10+ StringSelectMenuBuilder,
1011} = require ( "discord.js" ) ;
1112const stringSimilarity = require ( "string-similarity" ) ;
1213const fs = require ( "fs" ) ;
@@ -68,6 +69,26 @@ function getFaqPageContent(page, faqs) {
6869 return content ;
6970}
7071
72+ // Helper to create a select menu for a page of FAQs
73+ function getFaqSelectMenu ( page , faqs ) {
74+ const start = page * FAQ_PAGE_SIZE ;
75+ const end = start + FAQ_PAGE_SIZE ;
76+ const slice = faqs . slice ( start , end ) ;
77+
78+ const options = slice . map ( ( f , i ) => ( {
79+ label : f . question . length > 100 ? f . question . slice ( 0 , 97 ) + "..." : f . question ,
80+ description : `Question #${ start + i + 1 } ` ,
81+ value : `${ start + i + 1 } ` , // question number as string (1-based)
82+ } ) ) ;
83+
84+ return new ActionRowBuilder ( ) . addComponents (
85+ new StringSelectMenuBuilder ( )
86+ . setCustomId ( "faq_select_question" )
87+ . setPlaceholder ( "Select a question to get its answer" )
88+ . addOptions ( options )
89+ ) ;
90+ }
91+
7192const serverId = "1378813132788727970" ;
7293const TARGET_GUILD_ID = "1378813132788727970" ;
7394
@@ -116,21 +137,55 @@ client.once("ready", async () => {
116137
117138client . on ( Events . InteractionCreate , async ( interaction ) => {
118139 try {
140+ if ( interaction . isStringSelectMenu ( ) ) {
141+ if ( interaction . customId === "faq_select_question" ) {
142+ const selectedValue = interaction . values [ 0 ] ;
143+ const qNum = parseInt ( selectedValue , 10 ) ;
144+ if ( isNaN ( qNum ) || qNum < 1 || qNum > faqs . length ) {
145+ await interaction . reply ( { content : "❌ Invalid question selection." , ephemeral : true } ) ;
146+ return ;
147+ }
148+ const faq = faqs [ qNum - 1 ] ;
149+ await interaction . reply ( {
150+ content : `**Q${ qNum } . ${ faq . question } **\n\n${ faq . answer } ` ,
151+ ephemeral : true ,
152+ } ) ;
153+ return ;
154+ }
155+ }
156+
119157 if ( interaction . isChatInputCommand ( ) ) {
120158 const userQuestion = interaction . options . getString ( "question" ) ;
121- console . log ( "userque: " , userQuestion ) ;
122159
123160 if ( interaction . commandName === "faq" ) {
124161 if ( ! userQuestion ) {
125162 throw new Error ( "No question provided" ) ;
126163 }
127164
128- // Handle paginated all commands
165+ // Check if input is a number representing FAQ index
166+ const trimmed = userQuestion . trim ( ) ;
167+ const numberMatch = trimmed . match ( / ^ ( \d { 1 , 2 } ) $ / ) ;
168+
169+ if ( numberMatch ) {
170+ const qNum = parseInt ( numberMatch [ 1 ] , 10 ) ;
171+ if ( qNum >= 1 && qNum <= faqs . length ) {
172+ const match = faqs [ qNum - 1 ] ;
173+ await interaction . reply ( `**Q${ qNum } . ${ match . question } **\n\n${ match . answer } ` ) ;
174+ return ;
175+ } else {
176+ await interaction . reply (
177+ `❌ Invalid question number. Please enter a number between 1 and ${ faqs . length } .`
178+ ) ;
179+ return ;
180+ }
181+ }
182+
183+ // --- "all commands" => paginated list with select menu
129184 if ( userQuestion . toLowerCase ( ) . includes ( "all commands" ) ) {
130185 const totalPages = Math . ceil ( faqs . length / FAQ_PAGE_SIZE ) ;
131186 const page = 0 ;
132187
133- const row = new ActionRowBuilder ( ) . addComponents (
188+ const rowPagination = new ActionRowBuilder ( ) . addComponents (
134189 new ButtonBuilder ( )
135190 . setCustomId ( `faq_prev_${ page } ` )
136191 . setLabel ( "Previous" )
@@ -143,19 +198,18 @@ client.on(Events.InteractionCreate, async (interaction) => {
143198 . setStyle ( ButtonStyle . Primary )
144199 . setDisabled ( totalPages <= 1 )
145200 ) ;
201+ const rowSelectMenu = getFaqSelectMenu ( page , faqs ) ;
146202
147203 await interaction . reply ( {
148204 content : `**📋 FAQ List (Page ${
149205 page + 1
150- } /${ totalPages } ):**\n\n${ getFaqPageContent (
151- page ,
152- faqs
153- ) } \n\n*Use \`/faq\` and start typing your question to get an instant answer!*`,
154- components : [ row ] ,
206+ } /${ totalPages } ):**\n\n${ getFaqPageContent ( page , faqs ) } \n\n*Select a question below, or type \`/faq question:<number>\` to get an answer!*`,
207+ components : [ rowPagination , rowSelectMenu ] ,
155208 } ) ;
156209 return ;
157210 }
158211
212+ // --- Fuzzy match/autocomplete fallback ---
159213 const questions = faqs . map ( ( faq ) => faq . question ) ;
160214
161215 const { bestMatch, bestMatchIndex } = stringSimilarity . findBestMatch (
@@ -399,7 +453,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
399453 if ( page < 0 ) page = 0 ;
400454 if ( page >= totalPages ) page = totalPages - 1 ;
401455
402- const row = new ActionRowBuilder ( ) . addComponents (
456+ const rowPagination = new ActionRowBuilder ( ) . addComponents (
403457 new ButtonBuilder ( )
404458 . setCustomId ( `faq_prev_${ page } ` )
405459 . setLabel ( "Previous" )
@@ -413,14 +467,16 @@ client.on(Events.InteractionCreate, async (interaction) => {
413467 . setDisabled ( page === totalPages - 1 )
414468 ) ;
415469
470+ const rowSelectMenu = getFaqSelectMenu ( page , faqs ) ;
471+
416472 await interaction . update ( {
417473 content : `**📋 FAQ List (Page ${
418474 page + 1
419475 } /${ totalPages } ):**\n\n${ getFaqPageContent (
420476 page ,
421477 faqs
422- ) } \n\n*Use \`/faq\` and start typing your question to get an instant answer!*`,
423- components : [ row ] ,
478+ ) } \n\n*Select a question below, or type \`/faq question:<number> \` to get an answer!*`,
479+ components : [ rowPagination , rowSelectMenu ] ,
424480 } ) ;
425481} ) ;
426482
@@ -432,6 +488,8 @@ client.login(process.env.BOT_TOKEN).catch((error) => {
432488app . use ( "/docs" , express . static ( path . join ( __dirname , "views" ) ) ) ;
433489app . use ( "/docs" , documentationRoute ) ;
434490
491+
435492app . listen ( 3000 , ( ) => {
436493 console . log ( `🚀 Running at http://localhost:3000/docs` ) ;
437494} ) ;
495+
0 commit comments