|
1 |
| -const { SlashCommandBuilder } = require("@discordjs/builders"); |
| 1 | +const { SlashCommandBuilder, SlashCommandSubcommandBuilder } = require("@discordjs/builders"); |
2 | 2 | const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js");
|
3 |
| -const fs = require("fs"); |
4 |
| -const { guide } = require("../config/travelguide.json"); |
| 3 | +const { DBTravelguide } = require("../lib/database/dbtravelguide"); |
| 4 | +const { v4: uuidv4 } = require("uuid"); |
| 5 | + |
| 6 | +// //////////////////////////////////////////// |
| 7 | +// //// GENERATE EMBEDS AND ACTION ROW //////// |
| 8 | +// //////////////////////////////////////////// |
5 | 9 |
|
6 |
| -// Creates general object and id constants for function use |
7 | 10 | const prevId = "travelguidePrevButtonId";
|
8 | 11 | const nextId = "travelguideNextButtonId";
|
9 | 12 |
|
|
19 | 22 | .setStyle(ButtonStyle.Secondary)
|
20 | 23 | .setEmoji("➡️");
|
21 | 24 |
|
| 25 | +/** |
| 26 | + * |
| 27 | + * @param {Number} currentIndex |
| 28 | + * @param {Number} numEntries |
| 29 | + * @returns like buttons for an embed |
| 30 | + */ |
22 | 31 | const generateLikeButtons = (currentIndex, numEntries) => {
|
23 | 32 | const buttons = [];
|
24 | 33 | const endIndex = Math.min(currentIndex + 3, numEntries);
|
|
37 | 46 | };
|
38 | 47 |
|
39 | 48 | /**
|
40 |
| - * Creates an actionRowBuilder with next and previous buttons |
| 49 | + * Creates an ActionRow with all buttons |
41 | 50 | * @param {number} currentIndex
|
42 | 51 | * @param {number} numEntries
|
43 |
| - * @returns |
| 52 | + * @returns {ActionRowBuilder} ActionRow containing all buttons for an embed |
44 | 53 | */
|
45 |
| -const getComponents = (currentIndex, numEntries) => { |
| 54 | +const generateActionRow = (currentIndex, numEntries) => { |
46 | 55 | const buttons = [
|
47 | 56 | ...(currentIndex > 0 ? [prevButton] : []),
|
48 | 57 | ...(numEntries - (currentIndex + 3) > 0 ? [nextButton] : []),
|
|
61 | 70 | * @param {number} start The index to start from.
|
62 | 71 | * @param {number} pages How many pages the embed has.
|
63 | 72 | * @param {Array<String>} recommendations An array of recommendations.
|
64 |
| - * @returns {EmbedBuilder} |
| 73 | + * @returns {EmbedBuilder} Embed containing 3 recommendations for the travelguide GET command |
65 | 74 | */
|
66 |
| - |
67 | 75 | const generateGetEmbed = (start, pages, recommendations) => {
|
68 | 76 | const current = recommendations.slice(start, start + 3);
|
69 | 77 | const pageNum = Math.floor(start / pages) + 1;
|
|
89 | 97 | };
|
90 | 98 |
|
91 | 99 | /**
|
92 |
| - * Creates an embed of the added recommendation |
93 |
| - * @param {object} recommendation The recommendation |
| 100 | + * |
| 101 | + * @param {String} location |
| 102 | + * @param {String} description |
| 103 | + * @param {String} season |
| 104 | + * @param {String} category |
| 105 | + * @returns an embed containing a summary of the newly added recommendation |
94 | 106 | */
|
95 |
| - |
96 |
| -const generateAddEmbed = (recommendation, category) => { |
| 107 | +const generateAddEmbed = (location, description, season, category) => { |
97 | 108 | return new EmbedBuilder()
|
98 | 109 | .setAuthor({
|
99 | 110 | name: "CSESoc Bot",
|
100 | 111 | iconURL: "https://i.imgur.com/EE3Q40V.png",
|
101 | 112 | })
|
102 |
| - .setTitle(`${recommendation.location} has been added!`) |
| 113 | + .setTitle(`${location} has been added!`) |
103 | 114 | .setDescription(
|
104 |
| - `**Description**: ${recommendation.description} |
105 |
| - **Season**: ${recommendation.season} |
| 115 | + `**Description**: ${description} |
| 116 | + **Season**: ${season} |
106 | 117 | **Category**: ${category}`,
|
107 | 118 | )
|
108 | 119 | .setColor(0x3a76f8)
|
|
112 | 123 | .setTimestamp();
|
113 | 124 | };
|
114 | 125 |
|
| 126 | +// //////////////////////////////////////////// |
| 127 | +// //////// SETTING UP THE COMMANDS /////////// |
| 128 | +// //////////////////////////////////////////// |
| 129 | + |
| 130 | +const commandTravelguideAdd = new SlashCommandSubcommandBuilder() |
| 131 | + .setName("add") |
| 132 | + .setDescription( |
| 133 | + "Add a recommendation to the travel guide. Then the recommendation will be considered for approval.", |
| 134 | + ) |
| 135 | + // [recommendation name] [category] [description] [season optional] [recommender?] |
| 136 | + .addStringOption((option) => |
| 137 | + option |
| 138 | + .setName("recommendation-location") |
| 139 | + .setDescription("Name of the recommended place.") |
| 140 | + .setRequired(true), |
| 141 | + ) |
| 142 | + .addStringOption((option) => |
| 143 | + option |
| 144 | + .setName("category") |
| 145 | + .setDescription( |
| 146 | + "The recommended category out of: entertainment, scenic views and restaurants.", |
| 147 | + ) |
| 148 | + .setRequired(true) |
| 149 | + .addChoices( |
| 150 | + { name: "entertainment", value: "entertainment" }, |
| 151 | + { name: "scenic views", value: "scenic views" }, |
| 152 | + { name: "restaurants", value: "restaurants" }, |
| 153 | + ), |
| 154 | + ) |
| 155 | + .addStringOption((option) => |
| 156 | + option |
| 157 | + .setName("description") |
| 158 | + .setDescription("Brief description of the recommended place in 1-2 sentences.") |
| 159 | + .setRequired(true), |
| 160 | + ) |
| 161 | + .addStringOption((option) => |
| 162 | + option |
| 163 | + .setName("season") |
| 164 | + .setDescription("The recommended season for the location.") |
| 165 | + .setRequired(true) |
| 166 | + .addChoices( |
| 167 | + { name: "summer", value: "summer" }, |
| 168 | + { name: "autumn", value: "autumn" }, |
| 169 | + { name: "winter", value: "winter" }, |
| 170 | + { name: "spring", value: "spring" }, |
| 171 | + { name: "all year round", value: "all year round" }, |
| 172 | + ), |
| 173 | + ); |
| 174 | + |
| 175 | +const commandTravelguideGet = new SlashCommandSubcommandBuilder() |
| 176 | + .setName("get") |
| 177 | + .setDescription("Get a recommendation from the travel guide") |
| 178 | + .addStringOption((option) => |
| 179 | + option |
| 180 | + .setName("category") |
| 181 | + .setDescription("Sort by the following category") |
| 182 | + .setRequired(false) |
| 183 | + .addChoices( |
| 184 | + { name: "entertainment", value: "entertainment" }, |
| 185 | + { name: "scenic views", value: "scenic views" }, |
| 186 | + { name: "restaurants", value: "restaurants" }, |
| 187 | + ), |
| 188 | + ) |
| 189 | + .addStringOption((option) => |
| 190 | + option |
| 191 | + .setName("season") |
| 192 | + .setDescription("Sort by the following season") |
| 193 | + .setRequired(false) |
| 194 | + .addChoices( |
| 195 | + { name: "summer", value: "summer" }, |
| 196 | + { name: "autumn", value: "autumn" }, |
| 197 | + { name: "winter", value: "winter" }, |
| 198 | + { name: "spring", value: "spring" }, |
| 199 | + { name: "all year round", value: "all year round" }, |
| 200 | + ), |
| 201 | + ); |
| 202 | + |
| 203 | +const commandTravelguideDelete = new SlashCommandSubcommandBuilder() |
| 204 | + .setName("delete") |
| 205 | + .setDescription("Delete your own recommendation from the travel guide."); |
| 206 | + |
| 207 | +const baseCommand = new SlashCommandBuilder() |
| 208 | + .setName("travelguide") |
| 209 | + .setDescription( |
| 210 | + "Add to and display a travel guide for the recommended restuarants, scenic views and more!", |
| 211 | + ) |
| 212 | + .addSubcommand(commandTravelguideAdd) |
| 213 | + .addSubcommand(commandTravelguideGet) |
| 214 | + .addSubcommand(commandTravelguideDelete); |
| 215 | + |
| 216 | +// //////////////////////////////////////////// |
| 217 | +// ///////// HANDLING THE COMMAND ///////////// |
| 218 | +// //////////////////////////////////////////// |
| 219 | + |
115 | 220 | /**
|
116 |
| - * Updates the travelguide.json database |
| 221 | + * |
| 222 | + * @param {CommandInteraction} interaction |
117 | 223 | */
|
118 |
| -const updateFile = () => { |
119 |
| - fs.writeFileSync("./config/travelguide.json", JSON.stringify({ guide }, null, 4)); |
120 |
| -}; |
| 224 | +async function handleInteraction(interaction) { |
| 225 | + /** @type {DBTravelguide} */ |
| 226 | + const travelguideStorage = global.travelguideStorage; |
| 227 | + const authorId = interaction.user.id; |
| 228 | + |
| 229 | + // figure out which command was called |
| 230 | + const subcommand = interaction.options.getSubcommand(false); |
| 231 | + switch (subcommand) { |
| 232 | + case "add": |
| 233 | + await handleTravelguideAdd(interaction, travelguideStorage, authorId); |
| 234 | + break; |
| 235 | + case "get": |
| 236 | + await handleTravelguideGet(interaction, travelguideStorage, authorId); |
| 237 | + break; |
| 238 | + case "delete": |
| 239 | + await handleTravelguideDelete(interaction, travelguideStorage, authorId); |
| 240 | + break; |
| 241 | + default: |
| 242 | + await interaction.reply("Internal Error AHHHHHHH! CONTACT ME PLEASE!"); |
| 243 | + } |
| 244 | +} |
121 | 245 |
|
122 |
| -module.exports = { |
123 |
| - data: new SlashCommandBuilder() |
124 |
| - .setName("travelguide") |
125 |
| - .setDescription( |
126 |
| - "Add to and display a travel guide for the recommended restuarants, scenic views and more!", |
127 |
| - ) |
128 |
| - .addSubcommand((subcommand) => |
129 |
| - subcommand |
130 |
| - .setName("add") |
131 |
| - .setDescription( |
132 |
| - "Add a recommendation to the travel guide. Then the recommendation will be considered for approval.", |
133 |
| - ) |
134 |
| - // [recommendation name] [category] [description] [season optional] [recommender?] |
135 |
| - .addStringOption((option) => |
136 |
| - option |
137 |
| - .setName("recommendation-location") |
138 |
| - .setDescription("Name of the recommended place.") |
139 |
| - .setRequired(true), |
140 |
| - ) |
141 |
| - .addStringOption((option) => |
142 |
| - option |
143 |
| - .setName("category") |
144 |
| - .setDescription( |
145 |
| - "The recommended category out of: entertainment, scenic views and restaurants.", |
146 |
| - ) |
147 |
| - .setRequired(true) |
148 |
| - .addChoices( |
149 |
| - { name: "entertainment", value: "entertainment" }, |
150 |
| - { name: "scenic views", value: "scenic views" }, |
151 |
| - { name: "restaurants", value: "restaurants" }, |
152 |
| - ), |
153 |
| - ) |
154 |
| - .addStringOption((option) => |
155 |
| - option |
156 |
| - .setName("description") |
157 |
| - .setDescription( |
158 |
| - "Brief description of the recommended place in 1-2 sentences.", |
159 |
| - ) |
160 |
| - .setRequired(true), |
161 |
| - ) |
162 |
| - .addStringOption((option) => |
163 |
| - option |
164 |
| - .setName("season") |
165 |
| - .setDescription("The recommended season for the location.") |
166 |
| - .setRequired(true) |
167 |
| - .addChoices( |
168 |
| - { name: "summer", value: "summer" }, |
169 |
| - { name: "autumn", value: "autumn" }, |
170 |
| - { name: "winter", value: "winter" }, |
171 |
| - { name: "spring", value: "spring" }, |
172 |
| - { name: "all year round", value: "all year round" }, |
173 |
| - ), |
174 |
| - ), |
175 |
| - ) |
176 |
| - .addSubcommand((subcommand) => |
177 |
| - subcommand |
178 |
| - .setName("get") |
179 |
| - .setDescription("Get a recommendation from the travel guide") |
180 |
| - .addStringOption((option) => |
181 |
| - option |
182 |
| - .setName("category") |
183 |
| - .setDescription("Sort by the following category") |
184 |
| - .setRequired(false) |
185 |
| - .addChoices( |
186 |
| - { name: "entertainment", value: "entertainment" }, |
187 |
| - { name: "scenic views", value: "scenic views" }, |
188 |
| - { name: "restaurants", value: "restaurants" }, |
189 |
| - ), |
190 |
| - ) |
191 |
| - .addStringOption((option) => |
192 |
| - option |
193 |
| - .setName("season") |
194 |
| - .setDescription("Sort by the following season") |
195 |
| - .setRequired(false) |
196 |
| - .addChoices( |
197 |
| - { name: "summer", value: "summer" }, |
198 |
| - { name: "autumn", value: "autumn" }, |
199 |
| - { name: "winter", value: "winter" }, |
200 |
| - { name: "spring", value: "spring" }, |
201 |
| - { name: "all year round", value: "all year round" }, |
202 |
| - ), |
203 |
| - ), |
204 |
| - ) |
205 |
| - .addSubcommand((subcommand) => |
206 |
| - subcommand |
207 |
| - .setName("delete") |
208 |
| - .setDescription("Delete your own recommendation from the travel guide."), |
209 |
| - ), |
210 |
| - |
211 |
| - async execute(interaction) { |
212 |
| - const authorId = interaction.user.id; |
213 |
| - |
214 |
| - if (interaction.options.getSubcommand() === "add") { |
215 |
| - const category = interaction.options.getString("category"); |
216 |
| - const season = interaction.options.getString("season"); |
217 |
| - const location = interaction.options.getString("recommendation-location"); |
218 |
| - const description = interaction.options.getString("description"); |
219 |
| - |
220 |
| - const recommendation = { |
221 |
| - location: location, |
222 |
| - description: description, |
223 |
| - season: season ? season : null, |
224 |
| - category: category, |
225 |
| - likes: [], |
226 |
| - authorId: authorId, |
227 |
| - dateAdded: Date.now(), |
228 |
| - }; |
229 |
| - const exists = guide.some( |
230 |
| - (item) => |
231 |
| - item.location === recommendation.location && |
232 |
| - item.description === recommendation.description && |
233 |
| - item.season === recommendation.season && |
234 |
| - item.category === recommendation.category, |
235 |
| - ); |
236 |
| - |
237 |
| - if (!exists) { |
238 |
| - guide.push(recommendation); |
| 246 | +// //////////////////////////////////////////// |
| 247 | +// //////// HANDLING THE SUBCOMMANDS ////////// |
| 248 | +// //////////////////////////////////////////// |
| 249 | + |
| 250 | +/** |
| 251 | + * Adds a new recommendation to the database and displays a summary of the recommendation |
| 252 | + * @param {CommandInteraction} interaction |
| 253 | + * @param {DBTravelguide} travelguideStorage |
| 254 | + * @param {Number} authorId |
| 255 | + */ |
| 256 | +async function handleTravelguideAdd(interaction, travelguideStorage, authorId) { |
| 257 | + const location = interaction.options.getString("recommendation-location"); |
| 258 | + const description = interaction.options.getString("description"); |
| 259 | + const season = interaction.options.getString("season"); |
| 260 | + const category = interaction.options.getString("category"); |
| 261 | + |
| 262 | + // check if entry exists in db |
| 263 | + const exists = await travelguideStorage.getRecommendation(location, description, category); |
| 264 | + if (exists.length === 0) { |
| 265 | + travelguideStorage.addRecommendation( |
| 266 | + uuidv4(), |
| 267 | + location, |
| 268 | + description, |
| 269 | + season, |
| 270 | + category, |
| 271 | + authorId, |
| 272 | + ); |
| 273 | + } else { |
| 274 | + return await interaction.reply({ |
| 275 | + content: "This entry has already been recommended before.", |
| 276 | + }); |
| 277 | + } |
| 278 | + |
| 279 | + return await interaction.reply({ |
| 280 | + embeds: [generateAddEmbed(location, description, season, category)], |
| 281 | + }); |
| 282 | +} |
| 283 | + |
| 284 | +/** |
| 285 | + * Gets 3 recommendations sorted by category/season/neither |
| 286 | + * @param {CommandInteraction} interaction |
| 287 | + * @param {DBTravelguide} travelguideStorage |
| 288 | + * @param {Number} authorId |
| 289 | + */ |
| 290 | +async function handleTravelguideGet(interaction, travelguideStorage, authorId) { |
| 291 | + const category = interaction.options.getString("category"); |
| 292 | + const season = interaction.options.getString("season"); |
| 293 | + let recommendations = await travelguideStorage.getRecommendations(category, season); |
| 294 | + if (recommendations.length === 0) { |
| 295 | + return await interaction.reply({ |
| 296 | + content: `There are currently no recommendations for your selection, add your own recommendation using the **/travelguide add command**`, |
| 297 | + }); |
| 298 | + } |
| 299 | + let currentIndex = 0; |
| 300 | + const pages = Math.ceil(recommendations.length / 3); |
| 301 | + |
| 302 | + await interaction.reply({ |
| 303 | + embeds: [generateGetEmbed(currentIndex, pages, recommendations)], |
| 304 | + components: generateActionRow(currentIndex, recommendations.length), |
| 305 | + }); |
| 306 | + |
| 307 | + // Creates a collector for button interaction events, setting a 120s maximum |
| 308 | + // timeout and a 30s inactivity timeout |
| 309 | + const filter = (resInteraction) => { |
| 310 | + return ( |
| 311 | + (resInteraction.customId === prevId || |
| 312 | + resInteraction.customId === nextId || |
| 313 | + resInteraction.customId.startsWith("like_")) && |
| 314 | + resInteraction.user.id === authorId && |
| 315 | + resInteraction.message.interaction.id === interaction.id |
| 316 | + ); |
| 317 | + }; |
| 318 | + const collector = interaction.channel.createMessageComponentCollector({ |
| 319 | + filter, |
| 320 | + time: 120000, |
| 321 | + idle: 30000, |
| 322 | + }); |
| 323 | + |
| 324 | + collector.on("collect", async (i) => { |
| 325 | + if (i.customId === prevId) { |
| 326 | + currentIndex -= 3; |
| 327 | + } else if (i.customId === nextId) { |
| 328 | + currentIndex += 3; |
| 329 | + } else if (i.customId.startsWith("like_")) { |
| 330 | + const index = parseInt(i.customId.split("_")[1], 10); |
| 331 | + const recId = recommendations[index].rec_id; |
| 332 | + |
| 333 | + await travelguideStorage.likeRecommendation(authorId, recId); |
| 334 | + recommendations = await travelguideStorage.getRecommendations(category, season); |
| 335 | + } |
| 336 | + |
| 337 | + await i.update({ |
| 338 | + embeds: [generateGetEmbed(currentIndex, pages, recommendations)], |
| 339 | + components: generateActionRow(currentIndex, recommendations.length), |
| 340 | + }); |
| 341 | + }); |
| 342 | + |
| 343 | + // Clears buttons from embed page after timeout on collector |
| 344 | + /*eslint-disable */ |
| 345 | + collector.on("end", (collection) => { |
| 346 | + interaction.editReply({ components: [] }); |
| 347 | + }); |
| 348 | +} |
| 349 | + |
| 350 | +/** |
| 351 | + * Deletes a recommendation that the user owns |
| 352 | + * @param {CommandInteraction} interaction |
| 353 | + * @param {DBTravelguide} travelguideStorage |
| 354 | + * @param {Number} authorId |
| 355 | + */ |
| 356 | +async function handleTravelguideDelete(interaction, travelguideStorage, authorId) { |
| 357 | + const authorEntries = await travelguideStorage.getAuthorRecommendations(authorId); |
| 358 | + if (authorEntries.length === 0) { |
| 359 | + return await interaction.reply({ |
| 360 | + content: `There are currently no recommendations for your deletion, add recommendations using the **/travelguide add command**`, |
| 361 | + }); |
| 362 | + } |
| 363 | + // Generate an embed listing the user's entries |
| 364 | + const authorEntriesEmbed = new EmbedBuilder({ |
| 365 | + title: `Your recommendations`, |
| 366 | + color: 0x3a76f8, |
| 367 | + author: { |
| 368 | + name: "CSESoc Bot", |
| 369 | + icon_url: "https://i.imgur.com/EE3Q40V.png", |
| 370 | + }, |
| 371 | + fields: authorEntries.map((recommendation, index) => ({ |
| 372 | + name: `${0 + index + 1}. ${recommendation.location}`, |
| 373 | + value: `**Description**: ${recommendation.description} |
| 374 | + **Season**: ${recommendation.season} |
| 375 | + **Likes**: ${recommendation.likes.length}`, |
| 376 | + })), |
| 377 | + footer: { |
| 378 | + text: "CSESoc Bot", |
| 379 | + }, |
| 380 | + timestamp: new Date(), |
| 381 | + }); |
| 382 | + |
| 383 | + // Send the embed |
| 384 | + await interaction.reply({ embeds: [authorEntriesEmbed] }); |
| 385 | + |
| 386 | + // Prompt for entry index |
| 387 | + await interaction.channel.send("Please provide the entry number to delete."); |
| 388 | + |
| 389 | + const collector = interaction.channel.createMessageCollector({ |
| 390 | + filter: (message) => message.author.id === authorId, |
| 391 | + max: 1, |
| 392 | + time: 10_000, |
| 393 | + }); |
| 394 | + |
| 395 | + collector.on("collect", async (message) => { |
| 396 | + const entryIndex = parseInt(message.content.trim()); |
| 397 | + if (isNaN(entryIndex) || entryIndex < 1 || entryIndex > authorEntries.length) { |
| 398 | + await interaction.followUp("Invalid entry number. No entry was deleted."); |
| 399 | + return; |
| 400 | + } |
| 401 | + // Confirm entry |
| 402 | + await interaction.channel.send( |
| 403 | + `Type 'Y' to confirm the deletion of index **${message.content}**`, |
| 404 | + ); |
| 405 | + |
| 406 | + const confirmCollector = interaction.channel.createMessageCollector({ |
| 407 | + filter: (message) => message.author.id === authorId, |
| 408 | + max: 1, |
| 409 | + time: 10_000, |
| 410 | + }); |
| 411 | + |
| 412 | + confirmCollector.on("collect", async (message) => { |
| 413 | + const confirmMessage = message.content.trim(); |
| 414 | + if (confirmMessage === "Y") { |
| 415 | + // get the recommendationId |
| 416 | + const recId = authorEntries[entryIndex - 1].rec_id; |
| 417 | + const deletedLocation = authorEntries[entryIndex - 1].location; |
| 418 | + // Delete the entry |
| 419 | + await travelguideStorage.deleteRecommendation(authorId, recId); |
| 420 | + // Notify the user about the deletion |
| 421 | + await interaction.followUp(`Entry "${deletedLocation}" has been deleted.`); |
239 | 422 | } else {
|
240 |
| - return await interaction.reply({ |
241 |
| - content: "This entry has already been recommended before.", |
242 |
| - }); |
| 423 | + await interaction.followUp(`No entry has been deleted.`); |
243 | 424 | }
|
| 425 | + return; |
| 426 | + }); |
| 427 | + }); |
244 | 428 |
|
245 |
| - updateFile(); |
246 |
| - |
247 |
| - return await interaction.reply({ |
248 |
| - embeds: [generateAddEmbed(recommendation, category)], |
249 |
| - }); |
250 |
| - } else if (interaction.options.getSubcommand() === "get") { |
251 |
| - const category = interaction.options.getString("category"); |
252 |
| - const season = interaction.options.getString("season"); |
253 |
| - let recommendations = guide; |
254 |
| - if (category) { |
255 |
| - recommendations = recommendations.filter((entry) => entry.category === category); |
256 |
| - } |
257 |
| - if (season) { |
258 |
| - recommendations = recommendations.filter((entry) => entry.season === season); |
259 |
| - } |
260 |
| - if (recommendations.length === 0) { |
261 |
| - return await interaction.reply({ |
262 |
| - content: `There are currently no recommendations for your selection, add your own recommendation using the **/travelguide add command**`, |
263 |
| - }); |
264 |
| - } |
265 |
| - let currentIndex = 0; |
266 |
| - const pages = Math.ceil(recommendations.length / 3); |
267 |
| - |
268 |
| - await interaction.reply({ |
269 |
| - embeds: [generateGetEmbed(currentIndex, pages, recommendations)], |
270 |
| - components: getComponents(currentIndex, recommendations.length), |
271 |
| - }); |
272 |
| - |
273 |
| - // Creates a collector for button interaction events, setting a 120s maximum |
274 |
| - // timeout and a 30s inactivity timeout |
275 |
| - const filter = (resInteraction) => { |
276 |
| - return ( |
277 |
| - (resInteraction.customId === prevId || |
278 |
| - resInteraction.customId === nextId || |
279 |
| - resInteraction.customId.startsWith("like_")) && |
280 |
| - resInteraction.user.id === authorId && |
281 |
| - resInteraction.message.interaction.id === interaction.id |
282 |
| - ); |
283 |
| - }; |
284 |
| - const collector = interaction.channel.createMessageComponentCollector({ |
285 |
| - filter, |
286 |
| - time: 120000, |
287 |
| - idle: 30000, |
288 |
| - }); |
289 |
| - |
290 |
| - collector.on("collect", async (i) => { |
291 |
| - if (i.customId === prevId) { |
292 |
| - currentIndex -= 3; |
293 |
| - } else if (i.customId === nextId) { |
294 |
| - currentIndex += 3; |
295 |
| - } else if (i.customId.startsWith("like_")) { |
296 |
| - const index = parseInt(i.customId.split("_")[1], 10); |
297 |
| - const userIndex = recommendations[index].likes.indexOf(authorId); |
298 |
| - if (userIndex === -1) { |
299 |
| - // If the user hasn't liked it, increment the likes and add their ID to the likes array |
300 |
| - recommendations[index].likes.push(authorId); |
301 |
| - } else { |
302 |
| - // If the user has already liked it, remove their ID from the likes array |
303 |
| - recommendations[index].likes.splice(userIndex, 1); |
304 |
| - } |
305 |
| - updateFile(); |
306 |
| - } |
307 |
| - await i.update({ |
308 |
| - embeds: [generateGetEmbed(currentIndex, pages, recommendations)], |
309 |
| - components: getComponents(currentIndex, recommendations.length), |
310 |
| - }); |
311 |
| - }); |
312 |
| - |
313 |
| - // Clears buttons from embed page after timeout on collector |
314 |
| - /*eslint-disable */ |
315 |
| - collector.on("end", (collection) => { |
316 |
| - interaction.editReply({ components: [] }); |
317 |
| - }); |
318 |
| - } else if (interaction.options.getSubcommand() === "delete") { |
319 |
| - const userEntries = []; |
320 |
| - userEntries.push(...guide.filter((entry) => entry.authorId === authorId)); |
321 |
| - if (userEntries.length === 0) { |
322 |
| - return await interaction.reply({ |
323 |
| - content: `There are currently no recommendations for your deletion, add recommendations using the **/travelguide add command**`, |
324 |
| - }); |
325 |
| - } |
326 |
| - // Generate an embed listing the user's entries |
327 |
| - const userEntriesEmbed = new EmbedBuilder({ |
328 |
| - title: `Your recommendations`, |
329 |
| - description: "Below are your recommendations.", |
330 |
| - color: 0x3a76f8, |
331 |
| - author: { |
332 |
| - name: "CSESoc Bot", |
333 |
| - icon_url: "https://i.imgur.com/EE3Q40V.png", |
334 |
| - }, |
335 |
| - fields: userEntries.map((recommendation, index) => ({ |
336 |
| - name: `${0 + index + 1}. ${recommendation.location}`, |
337 |
| - value: `**Description**: ${recommendation.description} |
338 |
| - **Season**: ${recommendation.season} |
339 |
| - **Likes**: ${recommendation.likes.length}`, |
340 |
| - })), |
341 |
| - footer: { |
342 |
| - text: "CSESoc Bot", |
343 |
| - }, |
344 |
| - timestamp: new Date(), |
345 |
| - }); |
346 |
| - |
347 |
| - // Send the embed |
348 |
| - await interaction.reply({ embeds: [userEntriesEmbed] }); |
349 |
| - |
350 |
| - // Prompt for entry index |
351 |
| - await interaction.channel.send("Please provide the entry number to delete."); |
352 |
| - |
353 |
| - const collector = interaction.channel.createMessageCollector({ |
354 |
| - filter: (message) => message.author.id === authorId, |
355 |
| - max: 1, |
356 |
| - time: 10_000, |
357 |
| - }); |
358 |
| - |
359 |
| - collector.on("collect", async (message) => { |
360 |
| - const entryIndex = parseInt(message.content.trim()); |
361 |
| - if (isNaN(entryIndex) || entryIndex < 1 || entryIndex > userEntries.length) { |
362 |
| - await interaction.followUp("Invalid entry number. No entry was deleted."); |
363 |
| - return; |
364 |
| - } |
365 |
| - // Confirm entry |
366 |
| - await interaction.channel.send( |
367 |
| - `Type 'Y' to confirm the deletion of index **${message.content}**`, |
368 |
| - ); |
369 |
| - |
370 |
| - const confirmCollector = interaction.channel.createMessageCollector({ |
371 |
| - filter: (message) => message.author.id === authorId, |
372 |
| - max: 1, |
373 |
| - time: 10_000, |
374 |
| - }); |
375 |
| - |
376 |
| - confirmCollector.on("collect", async (message) => { |
377 |
| - const confirmMessage = message.content.trim(); |
378 |
| - if (confirmMessage === "Y") { |
379 |
| - // Delete the entry |
380 |
| - const deletedEntry = userEntries[entryIndex - 1]; |
381 |
| - guide.splice(guide.indexOf(deletedEntry), 1); |
382 |
| - updateFile(); |
383 |
| - |
384 |
| - // Notify the user about the deletion |
385 |
| - await interaction.followUp( |
386 |
| - `Entry "${deletedEntry.location}" has been deleted.`, |
387 |
| - ); |
388 |
| - } else { |
389 |
| - await interaction.followUp(`No entry has been deleted.`); |
390 |
| - } |
391 |
| - return; |
392 |
| - }); |
393 |
| - }); |
394 |
| - |
395 |
| - collector.on("end", (collected) => {}); |
396 |
| - } else { |
397 |
| - return await interaction.reply({ |
398 |
| - content: "You do not have permission to execute this command.", |
399 |
| - }); |
400 |
| - } |
401 |
| - }, |
| 429 | + collector.on("end", (collected) => {}); |
| 430 | +} |
| 431 | + |
| 432 | +module.exports = { |
| 433 | + data: baseCommand, |
| 434 | + execute: handleInteraction, |
402 | 435 | };
|
0 commit comments