Skip to content

Commit 59f7f9f

Browse files
committed
fix discord char limit + abstracting out functions
1 parent 5f0f75b commit 59f7f9f

File tree

1 file changed

+133
-36
lines changed

1 file changed

+133
-36
lines changed

discord-bot/app.js

Lines changed: 133 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import {
1414
} from 'discord-interactions';
1515
import { 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
1823
const 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+
2884
async 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

Comments
 (0)