@@ -14,6 +14,11 @@ import {
1414} from 'discord-interactions' ;
1515import { getRandomEmoji } from './utils.js' ;
1616
17+ // Global Variables:
18+ let currentIndex = 0 ; // Current page index of the bot's response
19+ let chunks ; // Message chunks (used when the response exceeds the character limit)
20+ let dotInterval = null ;
21+
1722// Create an express app
1823const app = express ( ) ;
1924// Get port, or default to 3000
@@ -25,6 +30,57 @@ app.get('/', (req, res) => {
2530} ) ;
2631
2732// Helper functions below
33+ function startLoadingDots ( endpoint , initialMessage ) {
34+ let dotCount = 0 ;
35+ let maxDots = 4
36+
37+ dotInterval = setInterval ( ( ) => {
38+ dotCount = ( dotCount % maxDots ) + 1 ;
39+ const loadingMessage = `${ initialMessage } ${ '.' . repeat ( dotCount ) } ` ;
40+ const options = {
41+ content : loadingMessage ,
42+ flags : InteractionResponseFlags . EPHEMERAL ,
43+ components : [ ] ,
44+ } ;
45+
46+ sendResponse ( endpoint , options ) ;
47+ } , 500 ) ; // Interval delay
48+ }
49+
50+ function stopLoadingDots ( ) {
51+ if ( dotInterval ) {
52+ clearInterval ( dotInterval ) ;
53+ }
54+ }
55+
56+ function createMessageWithButtons ( index , chunks ) {
57+ currentIndex = index ; // Set the global currentIndex to the current index
58+ return {
59+ content : chunks [ index ] ,
60+ components : [
61+ {
62+ type : 1 ,
63+ components : [
64+ {
65+ type : 2 ,
66+ label : 'Previous' ,
67+ style : 1 ,
68+ custom_id : `prev_${ index } ` ,
69+ disabled : index === 0 , // Disable if on the first chunk
70+ } ,
71+ {
72+ type : 2 ,
73+ label : 'Next' ,
74+ style : 1 ,
75+ custom_id : `next_${ index } ` ,
76+ disabled : index === chunks . length - 1 , // Disable if on the last chunk
77+ } ,
78+ ] ,
79+ } ,
80+ ] ,
81+ } ;
82+ }
83+
2884async function sendPlaceholderResponse ( res , placeholderResponse ) {
2985 await res . send ( {
3086 type : InteractionResponseType . CHANNEL_MESSAGE_WITH_SOURCE ,
@@ -56,19 +112,53 @@ async function fetchAnswer(question) {
56112 return rawResponse || 'No answer provided.' ;
57113}
58114
59- async function sendFollowUpResponse ( endpoint , content ) {
60- await fetch ( `https://discord.com/api/v10/${ endpoint } ` , {
61- method : 'PATCH' ,
62- headers : {
63- 'Authorization' : `Bot ${ process . env . DISCORD_TOKEN } ` ,
64- 'Content-Type' : 'application/json' ,
65- } ,
66- body : JSON . stringify ( {
67- content,
115+ async function sendResponse ( endpoint , options ) {
116+ try {
117+ const response = await fetch ( `https://discord.com/api/v10/${ endpoint } ` , {
118+ method : 'PATCH' ,
119+ headers : {
120+ 'Authorization' : `Bot ${ process . env . DISCORD_TOKEN } ` ,
121+ 'Content-Type' : 'application/json' ,
122+ } ,
123+ body : JSON . stringify ( {
124+ ...options
125+ } ) ,
126+ } ) ;
127+
128+ if ( ! response . ok ) {
129+ console . error ( `Failed to send follow-up response. Status: ${ response . status } , StatusText: ${ response . statusText } ` ) ;
130+ }
131+ } catch ( error ) {
132+ console . error ( 'Error sending follow-up response:' , error ) ;
133+ }
134+ }
135+
136+ async function sendFollowUpResponse ( endpoint , followUpMessage ) {
137+ // Check if the follow-up message exceeds Discord's character limit (2000 characters)
138+ if ( followUpMessage . length > 2000 ) {
139+ // Split response into chunks of 2000 characters
140+ chunks = followUpMessage . match ( / ( .| [ \r \n ] ) { 1 , 1990 } (? = \s | $ ) / g) || [ ] ;
141+ // Send the first chunk with prev/next buttons
142+ await sendResponse ( endpoint , createMessageWithButtons ( 0 , chunks ) ) ;
143+ } else {
144+ let options = {
145+ content : followUpMessage ,
68146 flags : InteractionResponseFlags . EPHEMERAL ,
69147 components : [ ] ,
70- } ) ,
71- } ) ;
148+ } ;
149+ await sendResponse ( endpoint , options ) ;
150+ }
151+ }
152+
153+ async function fetchFollowUpMessage ( question , userId , endpoint ) {
154+ try {
155+ // Call an external API to fetch the answer
156+ const answer = await fetchAnswer ( question ) ;
157+ return `\n> ${ question } \n\nHere's what I found, <@${ userId } >:\n\n${ answer } ` ;
158+ } catch ( error ) {
159+ console . error ( 'Error fetching answer:' , error ) ;
160+ return `\n> ${ question } \n\nSorry <@${ userId } >, I couldn't fetch an answer to your question. Please try again later.` ;
161+ }
72162}
73163
74164/**
@@ -97,42 +187,27 @@ app.post('/interactions', verifyKeyMiddleware(process.env.DISCORD_PUBLIC_KEY), a
97187 if ( name === 'ask' ) {
98188 const context = req . body . context ;
99189 const userId = context === 0 ? req . body . member . user . id : req . body . user . id
100-
190+
101191 const question = data . options [ 0 ] ?. value || 'No question provided' ;
102192 const endpoint = `webhooks/${ process . env . DISCORD_APP_ID } /${ req . body . token } /messages/@original` ;
103193 const initialMessage = `\n> ${ question } \n\nLet me find the answer for you. This might take a moment`
194+ let followUpMessage = "Something went wrong! Please try again later." ;
104195
105196 // Send a placeholder response
106197 await sendPlaceholderResponse ( res , initialMessage ) ;
107198
108- // Show animated dots in the message while waiting
109- let dotCount = 0 ;
110- const maxDots = 4 ;
111- let isFetching = true ;
112-
113- const interval = setInterval ( ( ) => {
114- if ( isFetching ) {
115- dotCount = ( dotCount % maxDots ) + 1 ;
116- sendFollowUpResponse ( endpoint , `${ initialMessage } ${ '.' . repeat ( dotCount ) } ` ) ;
117- }
118- } , 500 ) ;
119-
120- // Create the follow-up response
121- let followUpMessage ;
199+ // Begin loading dots while fetching follow-up message
122200 try {
123- // Call an external API to fetch the answer
124- const answer = await fetchAnswer ( question ) ;
125- followUpMessage = `\n> ${ question } \n\nHere's what I found, <@${ userId } >:\n\n${ answer } ` ;
126- } catch ( error ) {
127- console . error ( 'Error fetching answer:' , error ) ;
128- followUpMessage = `\n> ${ question } \n\nSorry <@${ userId } >, I couldn't fetch an answer to your question. Please try again later.` ;
201+ startLoadingDots ( endpoint , initialMessage )
202+ followUpMessage = await fetchFollowUpMessage ( question , userId , endpoint ) ;
129203 } finally {
130- // Ensure cleanup and state updates
131- isFetching = false ; // Mark fetching as complete
132- clearInterval ( interval ) ; // Stop the dot interval
204+ stopLoadingDots ( )
133205 }
134206
135- return sendFollowUpResponse ( endpoint , followUpMessage ) ;
207+ // Send the follow-up response
208+ sendFollowUpResponse ( endpoint , followUpMessage ) ;
209+
210+ return ;
136211 }
137212
138213 // "test" command
@@ -151,6 +226,28 @@ app.post('/interactions', verifyKeyMiddleware(process.env.DISCORD_PUBLIC_KEY), a
151226 return res . status ( 400 ) . json ( { error : 'unknown command' } ) ;
152227 }
153228
229+ // Handle button interactions
230+ if ( type === InteractionType . MESSAGE_COMPONENT ) {
231+ const customId = data . custom_id ;
232+
233+ if ( customId . startsWith ( 'prev_' ) || customId . startsWith ( 'next_' ) ) {
234+ const [ action , index ] = customId . split ( '_' ) ;
235+ currentIndex = parseInt ( index , 10 ) ;
236+
237+ if ( action === 'prev' && currentIndex > 0 ) {
238+ currentIndex -= 1 ;
239+ } else if ( action === 'next' && currentIndex < chunks . length - 1 ) {
240+ currentIndex += 1 ;
241+ }
242+
243+ // Respond with the updated message chunk
244+ return res . send ( {
245+ type : InteractionResponseType . UPDATE_MESSAGE ,
246+ data : createMessageWithButtons ( currentIndex , chunks ) ,
247+ } ) ;
248+ }
249+ }
250+
154251 console . error ( 'unknown interaction type' , type ) ;
155252 return res . status ( 400 ) . json ( { error : 'unknown interaction type' } ) ;
156253} ) ;
0 commit comments