diff --git a/src/commands/about.ts b/src/commands/about.ts index cd7c6e6..9b4f277 100644 --- a/src/commands/about.ts +++ b/src/commands/about.ts @@ -1,58 +1,72 @@ -import { SlashCommandBuilder } from '@discordjs/builders'; -import { MessageEmbed, CommandInteraction } from 'discord.js'; - -import gitlog from 'gitlog'; - -import userScore from '../models/userScore'; - -import { paginateInteraction } from '../helpers/util/pagination'; - -export const data = new SlashCommandBuilder() - .setName('about') - .setDescription('Commands regarding the creation/development of the bot'); - -export async function execute(interaction: CommandInteraction) { - await interaction.deferReply(); - - const client = interaction.client; - const embeds: MessageEmbed[] = []; - - const contributorEmbed = new MessageEmbed().setTitle('Contributors') - .addField('Creator', '<@745063586422063214> [ADawesomeguy#3602]', true) - .addField('Contributors', '<@650525101048987649> [tEjAs#8127]\n<@426864344463048705> [tetrident#9396]', true) // Add more contributors here, first one is Abheek, second one is Tejas - .setTimestamp() - .setColor('#ffffff'); - embeds.push(contributorEmbed); - - const gitRepoLocation = __dirname; - - const commits = gitlog({ - repo: gitRepoLocation, - number: 5, - fields: ['hash', 'abbrevHash', 'subject', 'authorName', 'authorDateRel'], - }); - - const changelogEmbed = new MessageEmbed() - .setAuthor({ name: interaction.user.tag, iconURL: interaction.user.displayAvatarURL() }) - .setTitle('Changelog') - .setColor('#ffffff') - .setTimestamp(); - - commits.forEach(commit => { - changelogEmbed.addField(commit.abbrevHash, `> \`Hash:\`${commit.hash}\n> \`Subject:\`${commit.subject}\n> \`Author:\`${commit.authorName}\n> \`Date:\`${commit.authorDateRel}\n> \`Link\`: [GitHub](https://github.com/ADawesomeguy/AwesomeSciBo/commit/${commit.hash})\n`); - }); - embeds.push(changelogEmbed); - - await client.guilds.fetch(); - const trainingDocuments = await userScore.countDocuments({}); - const aboutBotEmbed = new MessageEmbed() - .setAuthor({ name: interaction.user.tag, iconURL: interaction.user.displayAvatarURL() }) - .setTitle('About AwesomeSciBo') - .addField('Servers', `${client.guilds.cache.size}`, true) - .addField('Training Users', `${trainingDocuments}`, true) - .setTimestamp(); - - embeds.push(aboutBotEmbed); - - paginateInteraction(interaction, embeds); -} +import { SlashCommandBuilder } from "@discordjs/builders"; +import { MessageEmbed, CommandInteraction } from "discord.js"; + +import gitlog from "gitlog"; + +import userScore from "../models/userScore"; + +import { paginateInteraction } from "../helpers/util/pagination"; + +export const data = new SlashCommandBuilder() + .setName("about") + .setDescription("Commands regarding the creation/development of the bot"); + +export async function execute(interaction: CommandInteraction) { + await interaction.deferReply(); + + const client = interaction.client; + const embeds: MessageEmbed[] = []; + + const contributorEmbed = new MessageEmbed() + .setTitle("Contributors") + .addField("Creator", "<@745063586422063214> [ADawesomeguy#3602]", true) + .addField( + "Contributors", + "<@650525101048987649> [tEjAs#8127]\n<@426864344463048705> [tetrident#9396]", + true + ) // Add more contributors here, first one is Abheek, second one is Tejas + .setTimestamp() + .setColor("#ffffff"); + embeds.push(contributorEmbed); + + const gitRepoLocation = __dirname; + + const commits = gitlog({ + repo: gitRepoLocation, + number: 5, + fields: ["hash", "abbrevHash", "subject", "authorName", "authorDateRel"], + }); + + const changelogEmbed = new MessageEmbed() + .setAuthor({ + name: interaction.user.tag, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTitle("Changelog") + .setColor("#ffffff") + .setTimestamp(); + + commits.forEach((commit) => { + changelogEmbed.addField( + commit.abbrevHash, + `> \`Hash:\`${commit.hash}\n> \`Subject:\`${commit.subject}\n> \`Author:\`${commit.authorName}\n> \`Date:\`${commit.authorDateRel}\n> \`Link\`: [GitHub](https://github.com/ADawesomeguy/AwesomeSciBo/commit/${commit.hash})\n` + ); + }); + embeds.push(changelogEmbed); + + await client.guilds.fetch(); + const trainingDocuments = await userScore.countDocuments({}); + const aboutBotEmbed = new MessageEmbed() + .setAuthor({ + name: interaction.user.tag, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTitle("About AwesomeSciBo") + .addField("Servers", `${client.guilds.cache.size}`, true) + .addField("Training Users", `${trainingDocuments}`, true) + .setTimestamp(); + + embeds.push(aboutBotEmbed); + + paginateInteraction(interaction, embeds); +} diff --git a/src/commands/help.ts b/src/commands/help.ts index ae7bb2a..20b2d1d 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,16 +1,18 @@ -import { SlashCommandBuilder } from '@discordjs/builders'; -import { MessageEmbed, CommandInteraction } from 'discord.js'; - -export const data = new SlashCommandBuilder() - .setName('help') - .setDescription('Replies with a help message explaining what the bot can do'); - -export async function execute(interaction: CommandInteraction) { - await interaction.deferReply(); - await interaction.deferReply(); - - const helpEmbed = new MessageEmbed() - .setDescription('AwesomeSciBo has migrated to using slash commands! You can take a look at the different commands by typing `/` and clicking on the AwesomeSciBo icon.') - .setColor('#ffffff'); - interaction.followUp({ embeds: [helpEmbed] }); -} \ No newline at end of file +import { SlashCommandBuilder } from "@discordjs/builders"; +import { MessageEmbed, CommandInteraction } from "discord.js"; + +export const data = new SlashCommandBuilder() + .setName("help") + .setDescription("Replies with a help message explaining what the bot can do"); + +export async function execute(interaction: CommandInteraction) { + await interaction.deferReply(); + await interaction.deferReply(); + + const helpEmbed = new MessageEmbed() + .setDescription( + "AwesomeSciBo has migrated to using slash commands! You can take a look at the different commands by typing `/` and clicking on the AwesomeSciBo icon." + ) + .setColor("#ffffff"); + interaction.followUp({ embeds: [helpEmbed] }); +} diff --git a/src/commands/rounds.ts b/src/commands/rounds.ts index 0d90b14..004357b 100644 --- a/src/commands/rounds.ts +++ b/src/commands/rounds.ts @@ -1,125 +1,178 @@ -import { SlashCommandBuilder } from '@discordjs/builders'; -import { MessageEmbed, CommandInteraction } from 'discord.js'; - -import axios from 'axios'; - -import log from '../helpers/log'; -import generatedRound from '../models/generatedRound'; - -export const data = new SlashCommandBuilder() - .setName('rounds') - .setDescription('Commands regarding the generation of rounds') - .addSubcommand(subcommand => { - subcommand - .setName('generate') - .setDescription('Generates a round with randomized questions from https://scibowldb.com/'); - return subcommand; - }) - .addSubcommand(subcommand => { - subcommand - .setName('list') - .setDescription('Lists your 5 most recently generated rounds with links'); - return subcommand; - }) - .addSubcommand(subcommand => { - subcommand - .setName('hit') - .setDescription('Shows the total number of rounds hit as well as the number for the specific user'); - return subcommand; - }); - -export async function execute(interaction: CommandInteraction) { - const action = interaction.options.getSubcommand(); - switch (action) { - case 'generate': { - interaction.deferReply({ ephemeral: true }); - - let finalizedHTML = '

ROUND GENERATED BY AWESOMESCIBO USING THE SCIBOWLDB API

'; - let tossup_question: string; - let question_category: string; - let tossup_format: string; - let tossup_answer: string; - let bonus_question: string; - let bonus_format: string; - let bonus_answer: string; - let htmlContent = ''; - await axios.post('https://scibowldb.com/api/questions', { categories: ['BIOLOGY', 'PHYSICS', 'CHEMISTRY', 'EARTH AND SPACE', 'ASTRONOMY', 'MATH'] }) - .then((response) => { - for (let i = 1; i < 26; i++) { - const questionData = response.data.questions[Math.floor(Math.random() * response.data.questions.length)]; - tossup_question = questionData.tossup_question; - tossup_answer = questionData.tossup_answer; - question_category = questionData.category; - tossup_format = questionData.tossup_format; - bonus_question = questionData.bonus_question; - bonus_answer = questionData.bonus_answer; - bonus_format = questionData.bonus_format; - htmlContent = '

TOSS-UP

\n
' + `${i}) ${question_category}` + ' ' + `${tossup_format}` + ' ' + tossup_question + '

' + 'ANSWER: ' + tossup_answer + '
'; - htmlContent += '

BONUS

\n
' + `${i}) ${question_category}` + ' ' + `${bonus_format}` + ' ' + bonus_question + '

' + 'ANSWER: ' + bonus_answer + '



'; - htmlContent = htmlContent.replace(/\n/g, '
'); - finalizedHTML += htmlContent; - } - - const newGeneratedRound = new generatedRound({ - htmlContent: finalizedHTML, - requestedBy: interaction.user.id, - authorTag: interaction.user.tag, - timestamp: new Date().toISOString(), - }); - - newGeneratedRound.save((err, round) => { - if (err) { - log({ logger: 'rounds', content: `Saving round to DB failed: ${err}`, level: 'error' }); - return; - } - interaction.followUp({ - content: `Here's your round: https://api.adawesome.tech/round/${round._id.toString()}`, - ephemeral: true, - }); - }); - }); - break; - } - - case 'list': { - interaction.deferReply({ ephemeral: true }); - - let roundsList = await generatedRound.find({ requestedBy: interaction.user.id }).sort({ timestamp: -1 }); - let finalMessage = ''; - if (!roundsList) { - interaction.followUp('You haven\'t requested any rounds!'); - return; - } - - if (roundsList.length > 5) { - roundsList = roundsList.slice(0, 5); - } - - roundsList.forEach(async (item, index) => { - finalMessage += `${index + 1}. [${item.timestamp.split('T')[0]}](https://api.adawesome.tech/round/${item._id.toString()})\n`; - }); - - const roundsListEmbed = new MessageEmbed() - .setAuthor({ name: interaction.user.tag, iconURL: interaction.user.displayAvatarURL() }) - .setTitle('Last 5 rounds requested') - .setDescription(finalMessage) - .setTimestamp(); - - interaction.followUp({ - embeds: [roundsListEmbed], - ephemeral: true, - }); - break; - } - - case 'hit': { - await interaction.deferReply(); - - const totalCount = await generatedRound.countDocuments({}); - const userCount = await generatedRound.countDocuments({ requestedBy: interaction.user.id }); - - interaction.followUp(`Total Hits: ${totalCount}\nYour Hits: ${userCount}`); - break; - } - } -} \ No newline at end of file +import { SlashCommandBuilder } from "@discordjs/builders"; +import { MessageEmbed, CommandInteraction } from "discord.js"; + +import axios from "axios"; + +import log from "../helpers/log"; +import generatedRound from "../models/generatedRound"; + +export const data = new SlashCommandBuilder() + .setName("rounds") + .setDescription("Commands regarding the generation of rounds") + .addSubcommand((subcommand) => { + subcommand + .setName("generate") + .setDescription( + "Generates a round with randomized questions from https://scibowldb.com/" + ); + return subcommand; + }) + .addSubcommand((subcommand) => { + subcommand + .setName("list") + .setDescription("Lists your 5 most recently generated rounds with links"); + return subcommand; + }) + .addSubcommand((subcommand) => { + subcommand + .setName("hit") + .setDescription( + "Shows the total number of rounds hit as well as the number for the specific user" + ); + return subcommand; + }); + +export async function execute(interaction: CommandInteraction) { + const action = interaction.options.getSubcommand(); + switch (action) { + case "generate": { + interaction.deferReply({ ephemeral: true }); + + let finalizedHTML = + "

ROUND GENERATED BY AWESOMESCIBO USING THE SCIBOWLDB API

"; + let tossup_question: string; + let question_category: string; + let tossup_format: string; + let tossup_answer: string; + let bonus_question: string; + let bonus_format: string; + let bonus_answer: string; + let htmlContent = ""; + await axios + .post("https://scibowldb.com/api/questions", { + categories: [ + "BIOLOGY", + "PHYSICS", + "CHEMISTRY", + "EARTH AND SPACE", + "ASTRONOMY", + "MATH", + ], + }) + .then((response) => { + for (let i = 1; i < 26; i++) { + const questionData = + response.data.questions[ + Math.floor(Math.random() * response.data.questions.length) + ]; + tossup_question = questionData.tossup_question; + tossup_answer = questionData.tossup_answer; + question_category = questionData.category; + tossup_format = questionData.tossup_format; + bonus_question = questionData.bonus_question; + bonus_answer = questionData.bonus_answer; + bonus_format = questionData.bonus_format; + htmlContent = + "

TOSS-UP

\n
" + + `${i}) ${question_category}` + + " " + + `${tossup_format}` + + " " + + tossup_question + + "

" + + "ANSWER: " + + tossup_answer + + "
"; + htmlContent += + "

BONUS

\n
" + + `${i}) ${question_category}` + + " " + + `${bonus_format}` + + " " + + bonus_question + + "

" + + "ANSWER: " + + bonus_answer + + "



"; + htmlContent = htmlContent.replace(/\n/g, "
"); + finalizedHTML += htmlContent; + } + + const newGeneratedRound = new generatedRound({ + htmlContent: finalizedHTML, + requestedBy: interaction.user.id, + authorTag: interaction.user.tag, + timestamp: new Date().toISOString(), + }); + + newGeneratedRound.save((err, round) => { + if (err) { + log({ + logger: "rounds", + content: `Saving round to DB failed: ${err}`, + level: "error", + }); + return; + } + interaction.followUp({ + content: `Here's your round: https://api.adawesome.tech/round/${round._id.toString()}`, + ephemeral: true, + }); + }); + }); + break; + } + + case "list": { + interaction.deferReply({ ephemeral: true }); + + let roundsList = await generatedRound + .find({ requestedBy: interaction.user.id }) + .sort({ timestamp: -1 }); + let finalMessage = ""; + if (!roundsList) { + interaction.followUp("You haven't requested any rounds!"); + return; + } + + if (roundsList.length > 5) { + roundsList = roundsList.slice(0, 5); + } + + roundsList.forEach(async (item, index) => { + finalMessage += `${index + 1}. [${ + item.timestamp.split("T")[0] + }](https://api.adawesome.tech/round/${item._id.toString()})\n`; + }); + + const roundsListEmbed = new MessageEmbed() + .setAuthor({ + name: interaction.user.tag, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTitle("Last 5 rounds requested") + .setDescription(finalMessage) + .setTimestamp(); + + interaction.followUp({ + embeds: [roundsListEmbed], + ephemeral: true, + }); + break; + } + + case "hit": { + await interaction.deferReply(); + + const totalCount = await generatedRound.countDocuments({}); + const userCount = await generatedRound.countDocuments({ + requestedBy: interaction.user.id, + }); + + interaction.followUp( + `Total Hits: ${totalCount}\nYour Hits: ${userCount}` + ); + break; + } + } +} diff --git a/src/commands/score.ts b/src/commands/score.ts index 2408e4b..3c4d973 100644 --- a/src/commands/score.ts +++ b/src/commands/score.ts @@ -1,43 +1,47 @@ -import { SlashCommandBuilder } from '@discordjs/builders'; -import { CommandInteraction, MessageEmbed } from 'discord.js'; - -import log from '../helpers/log'; -import userScore from '../models/userScore'; - -export const data = new SlashCommandBuilder() - .setName('score') - .setDescription('Returns the score of the current user or another') - .addUserOption(option => { - option - .setName('user') - .setDescription('The user to find the score for') - .setRequired(false); - - return option; - }); - -export async function execute(interaction: CommandInteraction) { - const scoreEmbed = new MessageEmbed() - .setColor('#ffffff'); - - const user = interaction.options.getUser('user') || interaction.user; - userScore.findOne({ authorID: user.id }, async (err, score) => { - if (err) { - log({ logger: 'db', content: `Unable to obtain user: ${err}`, level: 'info' }); - } - - if (!score) { - await interaction.reply({ - content: 'Unfortunately, that user does not seem to have used AwesomeSciBo yet.', - ephemeral: true, - }); - return; - } - - scoreEmbed - .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) - .setDescription(`Score: \`${score.score}\``); - - await interaction.reply({ embeds: [scoreEmbed] }); - }); -} +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction, MessageEmbed } from "discord.js"; + +import log from "../helpers/log"; +import userScore from "../models/userScore"; + +export const data = new SlashCommandBuilder() + .setName("score") + .setDescription("Returns the score of the current user or another") + .addUserOption((option) => { + option + .setName("user") + .setDescription("The user to find the score for") + .setRequired(false); + + return option; + }); + +export async function execute(interaction: CommandInteraction) { + const scoreEmbed = new MessageEmbed().setColor("#ffffff"); + + const user = interaction.options.getUser("user") || interaction.user; + userScore.findOne({ authorID: user.id }, async (err, score) => { + if (err) { + log({ + logger: "db", + content: `Unable to obtain user: ${err}`, + level: "info", + }); + } + + if (!score) { + await interaction.reply({ + content: + "Unfortunately, that user does not seem to have used AwesomeSciBo yet.", + ephemeral: true, + }); + return; + } + + scoreEmbed + .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) + .setDescription(`Score: \`${score.score}\``); + + await interaction.reply({ embeds: [scoreEmbed] }); + }); +} diff --git a/src/commands/settings.ts b/src/commands/settings.ts index a0bc130..97c290d 100644 --- a/src/commands/settings.ts +++ b/src/commands/settings.ts @@ -1,251 +1,289 @@ -import { SlashCommandBuilder } from '@discordjs/builders'; -import { Message, MessageActionRow, MessageSelectMenu } from 'discord.js'; -import { CommandInteraction, MessageEmbed } from 'discord.js'; -import log from '../helpers/log'; -import userConfig from '../models/userConfig'; +import { SlashCommandBuilder } from "@discordjs/builders"; +import { Message, MessageActionRow, MessageSelectMenu } from "discord.js"; +import { CommandInteraction, MessageEmbed } from "discord.js"; +import log from "../helpers/log"; +import userConfig from "../models/userConfig"; export const data = new SlashCommandBuilder() - .setName('settings') - .setDescription('BETA - settings configuration') - .addSubcommand(subcommand => { - subcommand - .setName('subject') - .setDescription('Changes subject of problems'); - return subcommand; - }) - .addSubcommand(subcommand => { - subcommand - .setName('display') - .setDescription('Displays current settings'); - return subcommand; - }) - .addSubcommand(subcommand => { - subcommand - .setName('gradelevels') - .setDescription('Changes grade level of problems'); - return subcommand; - }); + .setName("settings") + .setDescription("BETA - settings configuration") + .addSubcommand((subcommand) => { + subcommand.setName("subject").setDescription("Changes subject of problems"); + return subcommand; + }) + .addSubcommand((subcommand) => { + subcommand.setName("display").setDescription("Displays current settings"); + return subcommand; + }) + .addSubcommand((subcommand) => { + subcommand + .setName("gradelevels") + .setDescription("Changes grade level of problems"); + return subcommand; + }); export async function execute(interaction: CommandInteraction) { - const action = interaction.options.getSubcommand(); - switch (action) { - case 'display': { - await interaction.deferReply(); - const settingsEmbed = new MessageEmbed() - .setColor('#ffffff'); + const action = interaction.options.getSubcommand(); + switch (action) { + case "display": { + await interaction.deferReply(); + const settingsEmbed = new MessageEmbed().setColor("#ffffff"); - const user = interaction.options.getUser('user') || interaction.user; + const user = interaction.options.getUser("user") || interaction.user; - settingsEmbed - .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) - .setDescription('Current selections: '); - const menu = new MessageActionRow() - .addComponents( - new MessageSelectMenu() - .setCustomId('selectdisp') - .setPlaceholder('Nothing selected') - .addOptions([ - { - label: 'subjects', - description: 'subjects', - value: 'subjects', - }, - { - label: 'gradelevels', - description: 'grade levels', - value: 'gradelevels', - }, - ]), - ); + settingsEmbed + .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) + .setDescription("Current selections: "); + const menu = new MessageActionRow().addComponents( + new MessageSelectMenu() + .setCustomId("selectdisp") + .setPlaceholder("Nothing selected") + .addOptions([ + { + label: "subjects", + description: "subjects", + value: "subjects", + }, + { + label: "gradelevels", + description: "grade levels", + value: "gradelevels", + }, + ]) + ); - interaction.followUp({ - embeds: [/* settingsEmbed*/], - components: [menu], - }) - .then((dispMsg => { - const w = dispMsg as Message; - const dispFilter = i => ['selectdisp'].includes(i.customId) && i.user.id == interaction.user.id; // <== ATTENTION! First argument... - w.awaitMessageComponent({ filter: dispFilter, componentType: 'SELECT_MENU' }) - .then(async dispChoice => { - const vals = dispChoice.values; - const config = await userConfig.findById(interaction.user.id); - if (!config) { - await interaction.editReply({ - content: 'You don\'t have a configuration!', - embeds: [], - components: [], - }); - } - else if (vals.length === 1 && vals.at(0) === 'subjects') { - await interaction.editReply({ - content: `Current subjects setting: ${config.subjects.toString().split(',').join(', ')}`, - components: [], - }); - } - else if (vals.length === 1 && vals.at(0) === 'gradelevels') { - await interaction.editReply({ - content: `Current grade level setting: ${config.gradeLevels.toString().split(',').join(', ')}`, - components: [], - }); - } - else { - err => log({ - logger: '\'Error occurred: /settings:display did not equal subjects or gradelevels.\'', - content: `${err}`, - level: 'error', - }); - } - }); - })); - break; - } - case 'gradelevels': { - await interaction.deferReply(); + interaction + .followUp({ + embeds: [ + /* settingsEmbed*/ + ], + components: [menu], + }) + .then((dispMsg) => { + const w = dispMsg as Message; + const dispFilter = (i) => + ["selectdisp"].includes(i.customId) && + i.user.id == interaction.user.id; // <== ATTENTION! First argument... + w.awaitMessageComponent({ + filter: dispFilter, + componentType: "SELECT_MENU", + }).then(async (dispChoice) => { + const vals = dispChoice.values; + const config = await userConfig.findById(interaction.user.id); + if (!config) { + await interaction.editReply({ + content: "You don't have a configuration!", + embeds: [], + components: [], + }); + } else if (vals.length === 1 && vals.at(0) === "subjects") { + await interaction.editReply({ + content: `Current subjects setting: ${config.subjects + .toString() + .split(",") + .join(", ")}`, + components: [], + }); + } else if (vals.length === 1 && vals.at(0) === "gradelevels") { + await interaction.editReply({ + content: `Current grade level setting: ${config.gradeLevels + .toString() + .split(",") + .join(", ")}`, + components: [], + }); + } else { + (err) => + log({ + logger: + "'Error occurred: /settings:display did not equal subjects or gradelevels.'", + content: `${err}`, + level: "error", + }); + } + }); + }); + break; + } + case "gradelevels": { + await interaction.deferReply(); - const settingsEmbed = new MessageEmbed() - .setColor('#ffffff'); + const settingsEmbed = new MessageEmbed().setColor("#ffffff"); - const user = interaction.options.getUser('user') || interaction.user; + const user = interaction.options.getUser("user") || interaction.user; - settingsEmbed - .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) - .setDescription('Current level settings: '); - const menu = new MessageActionRow() - .addComponents( - new MessageSelectMenu() - .setCustomId('selectlvl') - .setPlaceholder('Nothing selected') - .setMinValues(1) - .setMaxValues(2) - .addOptions([ - { - label: 'Middle School', - description: 'Middle school level problems', - value: 'MS', - }, - { - label: 'High School', - description: 'High school level problems', - value: 'HS', - }, - ]), - ); + settingsEmbed + .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) + .setDescription("Current level settings: "); + const menu = new MessageActionRow().addComponents( + new MessageSelectMenu() + .setCustomId("selectlvl") + .setPlaceholder("Nothing selected") + .setMinValues(1) + .setMaxValues(2) + .addOptions([ + { + label: "Middle School", + description: "Middle school level problems", + value: "MS", + }, + { + label: "High School", + description: "High school level problems", + value: "HS", + }, + ]) + ); - interaction.followUp({ - embeds: [/* settingsEmbed*/], - components: [menu], - }) - .then((lvlMsg => { - const w = lvlMsg as Message; - const lvlFilter = i => ['selectlvl'].includes(i.customId) && i.user.id == interaction.user.id; // <== ATTENTION! First argument... - w.awaitMessageComponent({ filter: lvlFilter, componentType: 'SELECT_MENU' }) - .then(async lvlChoice => { - const vals = lvlChoice.values; - const levels = new Array(); - await userConfig.findOneAndUpdate({ _id: interaction.user.id }, { gradeLevels: vals }, { - upsert: true, - new: true, - }); - await vals.forEach(v => { - switch (v) { - case 'MS': - levels.push('Middle School'); - break; - case 'HS': - levels.push('High School'); - } - }); - await interaction.editReply({ - content: `Level set to: ${levels.toString().split(',').join(', ')}`, - embeds: [], - components: [], - }); - }); - })); - break; - } - case 'subject': { - await interaction.deferReply(); + interaction + .followUp({ + embeds: [ + /* settingsEmbed*/ + ], + components: [menu], + }) + .then((lvlMsg) => { + const w = lvlMsg as Message; + const lvlFilter = (i) => + ["selectlvl"].includes(i.customId) && + i.user.id == interaction.user.id; // <== ATTENTION! First argument... + w.awaitMessageComponent({ + filter: lvlFilter, + componentType: "SELECT_MENU", + }).then(async (lvlChoice) => { + const vals = lvlChoice.values; + const levels = new Array(); + await userConfig.findOneAndUpdate( + { _id: interaction.user.id }, + { gradeLevels: vals }, + { + upsert: true, + new: true, + } + ); + await vals.forEach((v) => { + switch (v) { + case "MS": + levels.push("Middle School"); + break; + case "HS": + levels.push("High School"); + } + }); + await interaction.editReply({ + content: `Level set to: ${levels + .toString() + .split(",") + .join(", ")}`, + embeds: [], + components: [], + }); + }); + }); + break; + } + case "subject": { + await interaction.deferReply(); - const settingsEmbed = new MessageEmbed() - .setColor('#ffffff'); + const settingsEmbed = new MessageEmbed().setColor("#ffffff"); - const user = interaction.options.getUser('user') || interaction.user; + const user = interaction.options.getUser("user") || interaction.user; - settingsEmbed - .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) - .setDescription('Current subject settings: '); - const menu = new MessageActionRow() - .addComponents( - new MessageSelectMenu() - .setCustomId('selectsubject') - .setPlaceholder('Nothing selected') - .setMinValues(1) - .setMaxValues(7) - .addOptions([ - { - label: 'Astronomy', - description: 'Astronomy', - value: 'ASTRONOMY', - }, - { - label: 'Biology', - description: 'Biology', - value: 'BIOLOGY', - }, - { - label: 'Earth Science', - description: 'Earth Science', - value: 'EARTH SCIENCE', - }, - { - label: 'Chemistry', - description: 'Chemistry', - value: 'CHEMISTRY', - }, - { - label: 'Physics', - description: 'Physics', - value: 'PHYSICS', - }, - { - label: 'Mathematics', - description: 'Mathematics', - value: 'MATH', - }, - { - label: 'Energy', - description: 'Energy', - value: 'ENERGY', - }, - ]), - ); + settingsEmbed + .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) + .setDescription("Current subject settings: "); + const menu = new MessageActionRow().addComponents( + new MessageSelectMenu() + .setCustomId("selectsubject") + .setPlaceholder("Nothing selected") + .setMinValues(1) + .setMaxValues(7) + .addOptions([ + { + label: "Astronomy", + description: "Astronomy", + value: "ASTRONOMY", + }, + { + label: "Biology", + description: "Biology", + value: "BIOLOGY", + }, + { + label: "Earth Science", + description: "Earth Science", + value: "EARTH SCIENCE", + }, + { + label: "Chemistry", + description: "Chemistry", + value: "CHEMISTRY", + }, + { + label: "Physics", + description: "Physics", + value: "PHYSICS", + }, + { + label: "Mathematics", + description: "Mathematics", + value: "MATH", + }, + { + label: "Energy", + description: "Energy", + value: "ENERGY", + }, + ]) + ); - interaction.followUp({ - embeds: [/* settingsEmbed*/], - components: [menu], - }) - .then((subjectMsg => { - const subjectFilter = i => ['selectsubject'].includes(i.customId) && i.user.id == interaction.user.id; // <== ATTENTION! First argument... - (subjectMsg as Message).awaitMessageComponent({ filter: subjectFilter, componentType: 'SELECT_MENU' }) - .then(async subjectChoice => { - const vals = subjectChoice.values; - await userConfig.findOneAndUpdate({ _id: interaction.user.id }, { subjects: vals }, { - upsert: true, - new: true, - }); - const subjects = new Array(); - await vals.forEach(v => { - subjects.push(v.toLowerCase().split(' ').map(w => w[0].toUpperCase() + w.substring(1)).join(' ')); - }); - await interaction.editReply({ - content: `Subjects set to: ${subjects.toString().split(',').join(', ')}`, - components: [], - embeds: [], - }); - }); - })); - break; - } - } + interaction + .followUp({ + embeds: [ + /* settingsEmbed*/ + ], + components: [menu], + }) + .then((subjectMsg) => { + const subjectFilter = (i) => + ["selectsubject"].includes(i.customId) && + i.user.id == interaction.user.id; // <== ATTENTION! First argument... + (subjectMsg as Message) + .awaitMessageComponent({ + filter: subjectFilter, + componentType: "SELECT_MENU", + }) + .then(async (subjectChoice) => { + const vals = subjectChoice.values; + await userConfig.findOneAndUpdate( + { _id: interaction.user.id }, + { subjects: vals }, + { + upsert: true, + new: true, + } + ); + const subjects = new Array(); + await vals.forEach((v) => { + subjects.push( + v + .toLowerCase() + .split(" ") + .map((w) => w[0].toUpperCase() + w.substring(1)) + .join(" ") + ); + }); + await interaction.editReply({ + content: `Subjects set to: ${subjects + .toString() + .split(",") + .join(", ")}`, + components: [], + embeds: [], + }); + }); + }); + break; + } + } } diff --git a/src/commands/top.ts b/src/commands/top.ts index 1ddd36a..3e08a4c 100644 --- a/src/commands/top.ts +++ b/src/commands/top.ts @@ -1,63 +1,75 @@ -import { SlashCommandBuilder } from '@discordjs/builders'; -import { CommandInteraction, MessageEmbed } from 'discord.js'; - -import log from '../helpers/log'; -import userScore from '../models/userScore'; - -export const data = new SlashCommandBuilder() - .setName('top') - .setDescription('Lists top ten scores across servers and in the current server'); - -export async function execute(interaction: CommandInteraction) { - await interaction.deferReply(); - - userScore - .find({}) - .sort({ score: -1 }) // Sort by descending order - .exec(async (err, obj) => { - if (err) { - log({ logger: 'top', content: `Getting top players failed: ${err}`, level: 'error' }); - console.log(err); - } - - if (obj.length < 10) { - // Need at least 10 scores for top 10 - return interaction.followUp( - `There are only ${obj.length} users, we need at least 10!`, - ); - } - - const embeds: MessageEmbed[] = []; - let lbMessageContent = ''; - - for (let i = 0; i < 10; i++) { - lbMessageContent += `${i + 1}: <@${obj[i].authorID}>: ${obj[i].score}\n`; // Loop through each user and add their name and score to leaderboard content - } - - const leaderboardEmbed = new MessageEmbed() - .setTitle('Top Ten!') - .setDescription(lbMessageContent) - .setColor('#ffffff'); - - embeds.push(leaderboardEmbed); - - let sMessageContent = ''; - const members = await interaction.guild?.members.fetch(); - - const serverLeaderBoardArray = await obj.filter(o => members?.some(m => m.user.id === o.authorID)); - if (serverLeaderBoardArray.length > 10) { - for (let i = 0; i < 10; i++) { - sMessageContent += `${i + 1}: <@${serverLeaderBoardArray[i].authorID}>: ${serverLeaderBoardArray[i].score}\n`; - } - - const sLeaderboardEmbed = new MessageEmbed() - .setTitle(`Top Ten in ${interaction.guild?.name}!`) - .setDescription(sMessageContent) - .setColor('#ffffff'); - - embeds.push(sLeaderboardEmbed); - } - - interaction.followUp({ embeds: embeds }); - }); -} +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction, MessageEmbed } from "discord.js"; + +import log from "../helpers/log"; +import userScore from "../models/userScore"; + +export const data = new SlashCommandBuilder() + .setName("top") + .setDescription( + "Lists top ten scores across servers and in the current server" + ); + +export async function execute(interaction: CommandInteraction) { + await interaction.deferReply(); + + userScore + .find({}) + .sort({ score: -1 }) // Sort by descending order + .exec(async (err, obj) => { + if (err) { + log({ + logger: "top", + content: `Getting top players failed: ${err}`, + level: "error", + }); + console.log(err); + } + + if (obj.length < 10) { + // Need at least 10 scores for top 10 + return interaction.followUp( + `There are only ${obj.length} users, we need at least 10!` + ); + } + + const embeds: MessageEmbed[] = []; + let lbMessageContent = ""; + + for (let i = 0; i < 10; i++) { + lbMessageContent += `${i + 1}: <@${obj[i].authorID}>: ${ + obj[i].score + }\n`; // Loop through each user and add their name and score to leaderboard content + } + + const leaderboardEmbed = new MessageEmbed() + .setTitle("Top Ten!") + .setDescription(lbMessageContent) + .setColor("#ffffff"); + + embeds.push(leaderboardEmbed); + + let sMessageContent = ""; + const members = await interaction.guild?.members.fetch(); + + const serverLeaderBoardArray = await obj.filter((o) => + members?.some((m) => m.user.id === o.authorID) + ); + if (serverLeaderBoardArray.length > 10) { + for (let i = 0; i < 10; i++) { + sMessageContent += `${i + 1}: <@${ + serverLeaderBoardArray[i].authorID + }>: ${serverLeaderBoardArray[i].score}\n`; + } + + const sLeaderboardEmbed = new MessageEmbed() + .setTitle(`Top Ten in ${interaction.guild?.name}!`) + .setDescription(sMessageContent) + .setColor("#ffffff"); + + embeds.push(sLeaderboardEmbed); + } + + interaction.followUp({ embeds: embeds }); + }); +} diff --git a/src/commands/train.ts b/src/commands/train.ts index f398d6d..7f57201 100644 --- a/src/commands/train.ts +++ b/src/commands/train.ts @@ -1,259 +1,322 @@ -import { SlashCommandBuilder } from '@discordjs/builders'; -import { MessageEmbed, MessageActionRow, MessageButton, CommandInteraction, Message } from 'discord.js'; - -import { decode } from 'html-entities'; -import axios from 'axios'; - -import userScore from '../models/userScore'; -import userConfig from '../models/userConfig'; - -import log from '../helpers/log.js'; -import { updateScore } from '../helpers/db.js'; - -export const data = new SlashCommandBuilder() - .setName('train') - .setDescription('Sends a training question to be answered') - .addStringOption(option => { - option - .setName('subject') - .setDescription('Optional subject to be used as a filter') - .setRequired(false) - .addChoices( - { name: 'astro', value: 'astro' }, - { name: 'bio', value: 'bio' }, - { name: 'chem', value: 'chem' }, - { name: 'ess', value: 'ess' }, - { name: 'phys', value: 'phys' }, - { name: 'math', value: 'math' }, - { name: 'energy', value: 'energy' }, - ) - .setRequired(false); - return option; - }); - -export async function execute(interaction: CommandInteraction) { - await interaction.deferReply(); - - const subject = interaction.options.get('subject') ? interaction.options.get('subject')?.value : null; - const authorId = interaction.user.id; - let score: number; - userScore - .findOne({ authorID: authorId }) - .lean() - .then((obj: { score: number; }, err: unknown) => { - if (!obj) { - score = 0; - const firstTimeEmbed = new MessageEmbed() - .setAuthor({ - name: interaction.client.user?.tag ? interaction.client.user?.tag : '', - iconURL: interaction.client.user?.displayAvatarURL(), - }) - .setDescription('Hey! It seems like it\'s your first time using AwesomeSciBo. Here\'s some information regarding the bot if you need it (for issues, contributions, etc.):') - .addField('Creator', '<@745063586422063214> [@abheekd#3602]') - .addField('GitHub', '[Link](https://github.com/ADawesomeguy/AwesomeSciBo) (a star couldn\'t hurt...)') - .setColor('#ffffff') - .setTimestamp(); - interaction.user.send({ embeds: [firstTimeEmbed] }) - .catch(err => log({ logger: 'train', content: `${err}`, level: 'error' })); - } - else if (obj) { - score = obj.score; - } - else { - log({ logger: 'train', content: `Getting user score failed: ${err}`, level: 'error' }); - } - }); - - let categoryArray: string[] = []; - const allCategories = ['BIOLOGY', 'PHYSICS', 'CHEMISTRY', 'EARTH AND SPACE', 'ASTRONOMY', 'MATH']; - const configCategories = await userConfig.findById(interaction.user.id); - - switch (subject) { - case null: - categoryArray = (configCategories ? (configCategories.subjects || allCategories) : allCategories); - break; - case 'astro': - case 'astronomy': - categoryArray = ['ASTRONOMY']; - break; - case 'bio': - case 'biology': - categoryArray = ['BIOLOGY']; - break; - case 'ess': - case 'earth science': - case 'es': - categoryArray = ['EARTH SCIENCE']; - break; - case 'chem': - case 'chemistry': - categoryArray = ['CHEMISTRY']; - break; - case 'phys': - case 'physics': - categoryArray = ['PHYSICS']; - break; - case 'math': - categoryArray = ['MATH']; - break; - case 'energy': - categoryArray = ['ENERGY']; - break; - default: - interaction.followUp({ - embeds: [new MessageEmbed() - .setDescription('<:red_x:816791117671825409> Not a valid subject!') - .setColor('#ffffff')], - }); - return; - } - - axios - .post('https://scibowldb.com/api/questions/random', { categories: categoryArray }) - .then((res) => { - const questionData = res.data.question; - const tossupQuestion = questionData.tossup_question; - const tossupAnswer = questionData.tossup_answer; - const tossupFormat = questionData.tossup_format; - let answers = tossupAnswer.split(' (ACCEPT: '); - if (answers.length > 1) { - answers[1] = answers[1].slice(0, answers[1].length - 1); // If there are multiple elements, it means there was an 'accept' and therefore a trailing ')' which should be removed - answers = [answers[0], ...answers[1].split(new RegExp(' OR ', 'i'))]; // Use the first element plus the last element split by 'OR' case insensitive - } - interaction.followUp({ content: decode(tossupQuestion), fetchReply: true }) - .then(q => { - const questionMessage = q as Message; - const sourceButton = new MessageActionRow() - .addComponents( - new MessageButton() - .setURL(questionData.uri) - .setLabel('Source') - .setStyle('LINK'), - ); - switch (tossupFormat) { - case 'Short Answer': { - // eslint-disable-next-line no-case-declarations - const messageFilter = m => m.author.id === interaction.user.id || m.author.id === interaction.client.user?.id; - interaction.channel?.awaitMessages({ - filter: messageFilter, - max: 1, - }) - .then(collected => { - const answerMsg = collected.first(); - - if (answerMsg?.author.id === interaction.client.user?.id) return; - - let predicted = ''; - if (answerMsg?.content.toLowerCase() === tossupAnswer.toLowerCase() || answers.includes(answerMsg?.content.toUpperCase())) { - predicted = 'correct'; - } - else { - predicted = 'incorrect'; - } - - if (predicted === 'correct') { - updateScore(true, score, authorId).then((msgToReply) => - answerMsg?.reply(msgToReply), - ); - } - else { - const overrideEmbed = new MessageEmbed() - .setAuthor({ - name: answerMsg?.author.tag ? answerMsg.author.tag : '', - iconURL: answerMsg?.author.displayAvatarURL(), - }) - .addField('Correct answer', `\`${tossupAnswer}\``) - .setDescription('It seems your answer was incorrect. Please react with <:override:955265585086857236> to override your answer if you think you got it right.') - .setColor('#ffffff') - .setTimestamp(); - const overrideButton = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('override') - .setEmoji('<:override:955265585086857236>') - .setStyle('SECONDARY'), - ); - answerMsg?.channel.send({ - embeds: [overrideEmbed], - components: [overrideButton], - }) - .then(overrideMsg => { - const overrideFilter = i => { - return ( - ['override'].includes(i.customId) && - i.user.id === answerMsg.author.id - ); - }; - overrideMsg - .awaitMessageComponent({ - filter: overrideFilter, - }) - .then(i => { - updateScore(true, score, authorId).then(async msgToReply => { - await i.reply(msgToReply); - overrideMsg.edit({ components: [] }); - }); - }).catch(err => log({ - logger: 'train', - content: `Failed to override score: ${err}`, - level: 'error', - })); - }).catch(err => log({ - logger: 'train', - content: `Failed to send override message: ${err}`, - level: 'error', - })); - } - interaction.editReply({ components: [sourceButton] }); - }).catch(err => log({ logger: 'train', content: `${err}`, level: 'error' })); - break; - } - case 'Multiple Choice': { - const choices = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('w') - .setLabel('W') - .setStyle('SECONDARY'), - new MessageButton() - .setCustomId('x') - .setLabel('X') - .setStyle('SECONDARY'), - new MessageButton() - .setCustomId('y') - .setLabel('Y') - .setStyle('SECONDARY'), - new MessageButton() - .setCustomId('z') - .setLabel('Z') - .setStyle('SECONDARY'), - ); - interaction.editReply({ components: [choices] }); - const mcFilter = i => ['w', 'x', 'y', 'z'].includes(i.customId) && i.user.id === interaction.user.id; - questionMessage.awaitMessageComponent({ filter: mcFilter }) - .then(mcChoice => { - if (tossupAnswer.charAt(0).toLowerCase() === mcChoice.customId) { - updateScore(true, score, authorId).then((msgToReply) => - mcChoice.reply(msgToReply), - ); - } - else { - const incorrectEmbed = new MessageEmbed() - .setAuthor({ - name: interaction.user.tag, - iconURL: interaction.user.displayAvatarURL(), - }) - .addField('Correct answer', `\`${tossupAnswer}\``) - .setDescription(`It seems your answer ${mcChoice.customId.toUpperCase()} was incorrect.`) - .setColor('#ffffff') - .setTimestamp(); - mcChoice.reply({ embeds: [incorrectEmbed] }); - } - interaction.editReply({ components: [sourceButton] }); - }); - break; - } - } - }).catch(err => log({ logger: 'train', content: `${err}`, level: 'error' })); - }).catch(err => log({ logger: 'train', content: `${err}`, level: 'error' })); -} +import { SlashCommandBuilder } from "@discordjs/builders"; +import { + MessageEmbed, + MessageActionRow, + MessageButton, + CommandInteraction, + Message, +} from "discord.js"; + +import { decode } from "html-entities"; +import axios from "axios"; + +import userScore from "../models/userScore"; +import userConfig from "../models/userConfig"; + +import log from "../helpers/log.js"; +import { updateScore } from "../helpers/db.js"; + +export const data = new SlashCommandBuilder() + .setName("train") + .setDescription("Sends a training question to be answered") + .addStringOption((option) => { + option + .setName("subject") + .setDescription("Optional subject to be used as a filter") + .setRequired(false) + .addChoices( + { name: "astro", value: "astro" }, + { name: "bio", value: "bio" }, + { name: "chem", value: "chem" }, + { name: "ess", value: "ess" }, + { name: "phys", value: "phys" }, + { name: "math", value: "math" }, + { name: "energy", value: "energy" } + ) + .setRequired(false); + return option; + }); + +export async function execute(interaction: CommandInteraction) { + await interaction.deferReply(); + + const subject = interaction.options.get("subject") + ? interaction.options.get("subject")?.value + : null; + const authorId = interaction.user.id; + let score: number; + userScore + .findOne({ authorID: authorId }) + .lean() + .then((obj: { score: number }, err: unknown) => { + if (!obj) { + score = 0; + const firstTimeEmbed = new MessageEmbed() + .setAuthor({ + name: interaction.client.user?.tag + ? interaction.client.user?.tag + : "", + iconURL: interaction.client.user?.displayAvatarURL(), + }) + .setDescription( + "Hey! It seems like it's your first time using AwesomeSciBo. Here's some information regarding the bot if you need it (for issues, contributions, etc.):" + ) + .addField("Creator", "<@745063586422063214> [@abheekd#3602]") + .addField( + "GitHub", + "[Link](https://github.com/ADawesomeguy/AwesomeSciBo) (a star couldn't hurt...)" + ) + .setColor("#ffffff") + .setTimestamp(); + interaction.user + .send({ embeds: [firstTimeEmbed] }) + .catch((err) => + log({ logger: "train", content: `${err}`, level: "error" }) + ); + } else if (obj) { + score = obj.score; + } else { + log({ + logger: "train", + content: `Getting user score failed: ${err}`, + level: "error", + }); + } + }); + + let categoryArray: string[] = []; + const allCategories = [ + "BIOLOGY", + "PHYSICS", + "CHEMISTRY", + "EARTH AND SPACE", + "ASTRONOMY", + "MATH", + ]; + const configCategories = await userConfig.findById(interaction.user.id); + + switch (subject) { + case null: + categoryArray = configCategories + ? configCategories.subjects || allCategories + : allCategories; + break; + case "astro": + case "astronomy": + categoryArray = ["ASTRONOMY"]; + break; + case "bio": + case "biology": + categoryArray = ["BIOLOGY"]; + break; + case "ess": + case "earth science": + case "es": + categoryArray = ["EARTH SCIENCE"]; + break; + case "chem": + case "chemistry": + categoryArray = ["CHEMISTRY"]; + break; + case "phys": + case "physics": + categoryArray = ["PHYSICS"]; + break; + case "math": + categoryArray = ["MATH"]; + break; + case "energy": + categoryArray = ["ENERGY"]; + break; + default: + interaction.followUp({ + embeds: [ + new MessageEmbed() + .setDescription("<:red_x:816791117671825409> Not a valid subject!") + .setColor("#ffffff"), + ], + }); + return; + } + + axios + .post("https://scibowldb.com/api/questions/random", { + categories: categoryArray, + }) + .then((res) => { + const questionData = res.data.question; + const tossupQuestion = questionData.tossup_question; + const tossupAnswer = questionData.tossup_answer; + const tossupFormat = questionData.tossup_format; + let answers = tossupAnswer.split(" (ACCEPT: "); + if (answers.length > 1) { + answers[1] = answers[1].slice(0, answers[1].length - 1); // If there are multiple elements, it means there was an 'accept' and therefore a trailing ')' which should be removed + answers = [answers[0], ...answers[1].split(new RegExp(" OR ", "i"))]; // Use the first element plus the last element split by 'OR' case insensitive + } + interaction + .followUp({ content: decode(tossupQuestion), fetchReply: true }) + .then((q) => { + const questionMessage = q as Message; + const sourceButton = new MessageActionRow().addComponents( + new MessageButton() + .setURL(questionData.uri) + .setLabel("Source") + .setStyle("LINK") + ); + switch (tossupFormat) { + case "Short Answer": { + // eslint-disable-next-line no-case-declarations + const messageFilter = (m) => + m.author.id === interaction.user.id || + m.author.id === interaction.client.user?.id; + interaction.channel + ?.awaitMessages({ + filter: messageFilter, + max: 1, + }) + .then((collected) => { + const answerMsg = collected.first(); + + if (answerMsg?.author.id === interaction.client.user?.id) + return; + + let predicted = ""; + if ( + answerMsg?.content.toLowerCase() === + tossupAnswer.toLowerCase() || + answers.includes(answerMsg?.content.toUpperCase()) + ) { + predicted = "correct"; + } else { + predicted = "incorrect"; + } + + if (predicted === "correct") { + updateScore(true, score, authorId).then((msgToReply) => + answerMsg?.reply(msgToReply) + ); + } else { + const overrideEmbed = new MessageEmbed() + .setAuthor({ + name: answerMsg?.author.tag ? answerMsg.author.tag : "", + iconURL: answerMsg?.author.displayAvatarURL(), + }) + .addField("Correct answer", `\`${tossupAnswer}\``) + .setDescription( + "It seems your answer was incorrect. Please react with <:override:955265585086857236> to override your answer if you think you got it right." + ) + .setColor("#ffffff") + .setTimestamp(); + const overrideButton = new MessageActionRow().addComponents( + new MessageButton() + .setCustomId("override") + .setEmoji("<:override:955265585086857236>") + .setStyle("SECONDARY") + ); + answerMsg?.channel + .send({ + embeds: [overrideEmbed], + components: [overrideButton], + }) + .then((overrideMsg) => { + const overrideFilter = (i) => { + return ( + ["override"].includes(i.customId) && + i.user.id === answerMsg.author.id + ); + }; + overrideMsg + .awaitMessageComponent({ + filter: overrideFilter, + }) + .then((i) => { + updateScore(true, score, authorId).then( + async (msgToReply) => { + await i.reply(msgToReply); + overrideMsg.edit({ components: [] }); + } + ); + }) + .catch((err) => + log({ + logger: "train", + content: `Failed to override score: ${err}`, + level: "error", + }) + ); + }) + .catch((err) => + log({ + logger: "train", + content: `Failed to send override message: ${err}`, + level: "error", + }) + ); + } + interaction.editReply({ components: [sourceButton] }); + }) + .catch((err) => + log({ logger: "train", content: `${err}`, level: "error" }) + ); + break; + } + case "Multiple Choice": { + const choices = new MessageActionRow().addComponents( + new MessageButton() + .setCustomId("w") + .setLabel("W") + .setStyle("SECONDARY"), + new MessageButton() + .setCustomId("x") + .setLabel("X") + .setStyle("SECONDARY"), + new MessageButton() + .setCustomId("y") + .setLabel("Y") + .setStyle("SECONDARY"), + new MessageButton() + .setCustomId("z") + .setLabel("Z") + .setStyle("SECONDARY") + ); + interaction.editReply({ components: [choices] }); + const mcFilter = (i) => + ["w", "x", "y", "z"].includes(i.customId) && + i.user.id === interaction.user.id; + questionMessage + .awaitMessageComponent({ filter: mcFilter }) + .then((mcChoice) => { + if ( + tossupAnswer.charAt(0).toLowerCase() === mcChoice.customId + ) { + updateScore(true, score, authorId).then((msgToReply) => + mcChoice.reply(msgToReply) + ); + } else { + const incorrectEmbed = new MessageEmbed() + .setAuthor({ + name: interaction.user.tag, + iconURL: interaction.user.displayAvatarURL(), + }) + .addField("Correct answer", `\`${tossupAnswer}\``) + .setDescription( + `It seems your answer ${mcChoice.customId.toUpperCase()} was incorrect.` + ) + .setColor("#ffffff") + .setTimestamp(); + mcChoice.reply({ embeds: [incorrectEmbed] }); + } + interaction.editReply({ components: [sourceButton] }); + }); + break; + } + } + }) + .catch((err) => + log({ logger: "train", content: `${err}`, level: "error" }) + ); + }) + .catch((err) => + log({ logger: "train", content: `${err}`, level: "error" }) + ); +} diff --git a/src/deploy-commands.js b/src/deploy-commands.js index a56e0b3..f531fa8 100644 --- a/src/deploy-commands.js +++ b/src/deploy-commands.js @@ -1,20 +1,23 @@ #!/usr/bin/env node const fs = require('node:fs'); -const {REST} = require('@discordjs/rest'); -const {Routes} = require('discord-api-types/v9'); -const {clientId, token} = require('./helpers/env'); +const { REST } = require('@discordjs/rest'); +const { Routes } = require('discord-api-types/v9'); +const { clientId, token } = require('./helpers/env'); const commands = []; -const commandFiles = fs.readdirSync(__dirname + '/commands').filter(file => file.endsWith('.js')); +const commandFiles = fs + .readdirSync(__dirname + '/commands') + .filter((file) => file.endsWith('.js')); for (const file of commandFiles) { - const command = require(`${__dirname}/commands/${file}`); - commands.push(command.data.toJSON()); + const command = require(`${__dirname}/commands/${file}`); + commands.push(command.data.toJSON()); } -const rest = new REST({version: '9'}).setToken(token); +const rest = new REST({ version: '9' }).setToken(token); -rest.put(Routes.applicationCommands(clientId), {body: commands}) - .then(() => console.log('Successfully registered application commands.')) - .catch(console.error); +rest + .put(Routes.applicationCommands(clientId), { body: commands }) + .then(() => console.log('Successfully registered application commands.')) + .catch(console.error); diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 6692a94..0c4c61a 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -1,23 +1,29 @@ -import log from '../helpers/log'; +import log from "../helpers/log"; -export const name = 'interactionCreate'; +export const name = "interactionCreate"; export const once = false; export async function execute(interaction) { - const client = interaction.client; + const client = interaction.client; - if (!interaction.isCommand()) return; + if (!interaction.isCommand()) return; - const command = client.commands.get(interaction.commandName); + const command = client.commands.get(interaction.commandName); - if (!command) return; + if (!command) return; - try { - await command.execute(interaction); - } - catch (error) { - log({ logger: 'interaction', content: `Interaction ${interaction.commandName} failed!`, level: 'error' }); - await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true }); - } + try { + await command.execute(interaction); + } catch (error) { + log({ + logger: "interaction", + content: `Interaction ${interaction.commandName} failed!`, + level: "error", + }); + await interaction.followUp({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } } diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 3898e04..fa21848 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -1,36 +1,36 @@ -import axios from 'axios'; -import { MessageEmbed } from 'discord.js'; -import { decode } from 'html-entities'; +import axios from "axios"; +import { MessageEmbed } from "discord.js"; +import { decode } from "html-entities"; -import { testingGuild } from '../helpers/env'; +import { testingGuild } from "../helpers/env"; -export const name = 'messageCreate'; +export const name = "messageCreate"; export const once = false; export async function execute(message) { - if (message.author.bot || message.guild.id != testingGuild) return; + if (message.author.bot || message.guild.id != testingGuild) return; - if (message.content.startsWith('!q')) { - const questionId = message.content.split(' ')[1]; - axios - .get(`https://scibowldb.com/api/questions/${questionId}`) - .then((res) => { - const data = res.data.question; - const tossupQuestion = data.tossup_question; - const tossupAnswer = data.tossup_answer; - let answers = tossupAnswer.split(' (ACCEPT: '); - if (answers.length > 1) { - answers[1] = answers[1].slice(0, answers[1].length - 1); // If there are multiple elements, it means there was an 'accept' and therefore a trailing ')' which should be removed - answers = [answers[0], ...answers[1].split(new RegExp(' OR ', 'i'))]; // Use the first element plus the last element split by 'OR' case insensitive - } - const dataEmbed = new MessageEmbed() - .setTitle('Data') - .setDescription(`\`\`\`json\n${JSON.stringify(data, null, 2)}\`\`\``); - message.reply({ - content: decode(tossupQuestion) + `\n\nAnswers: [${answers}]`, - embeds: [dataEmbed], - }); - }); - } -} \ No newline at end of file + if (message.content.startsWith("!q")) { + const questionId = message.content.split(" ")[1]; + axios + .get(`https://scibowldb.com/api/questions/${questionId}`) + .then((res) => { + const data = res.data.question; + const tossupQuestion = data.tossup_question; + const tossupAnswer = data.tossup_answer; + let answers = tossupAnswer.split(" (ACCEPT: "); + if (answers.length > 1) { + answers[1] = answers[1].slice(0, answers[1].length - 1); // If there are multiple elements, it means there was an 'accept' and therefore a trailing ')' which should be removed + answers = [answers[0], ...answers[1].split(new RegExp(" OR ", "i"))]; // Use the first element plus the last element split by 'OR' case insensitive + } + const dataEmbed = new MessageEmbed() + .setTitle("Data") + .setDescription(`\`\`\`json\n${JSON.stringify(data, null, 2)}\`\`\``); + message.reply({ + content: decode(tossupQuestion) + `\n\nAnswers: [${answers}]`, + embeds: [dataEmbed], + }); + }); + } +} diff --git a/src/events/ready.ts b/src/events/ready.ts index f816b31..9ff98b1 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,16 +1,17 @@ -import * as db from '../helpers/db'; -import { mongoUri } from '../helpers/env'; -import log from '../helpers/log'; +import * as db from "../helpers/db"; +import { mongoUri } from "../helpers/env"; +import log from "../helpers/log"; -export const name = 'ready'; +export const name = "ready"; export const once = true; export async function execute(client) { - await db.connect(mongoUri); - log({ logger: 'status', content: `Logged in as ${client.user.tag}!`, level: 'info' }); - client.user.setActivity( - 'for /help', - { type: 'WATCHING' }, - ); + await db.connect(mongoUri); + log({ + logger: "status", + content: `Logged in as ${client.user.tag}!`, + level: "info", + }); + client.user.setActivity("for /help", { type: "WATCHING" }); } diff --git a/src/helpers/db.ts b/src/helpers/db.ts index 26c2a15..7cde3d4 100644 --- a/src/helpers/db.ts +++ b/src/helpers/db.ts @@ -1,51 +1,67 @@ -import mongoose from 'mongoose'; - -import log from '../helpers/log'; -import userScore from '../models/userScore'; - -export async function updateScore(isCorrect: boolean, score: number, authorId: string) { - if (!isCorrect) { - return `Nice try! Your score is still ${score}.`; - } - else { - // TODO: Error handling - const doc = await userScore.findOne({ - authorID: authorId, - }); - if (!doc) { - const newUserScore = new userScore({ - authorID: authorId, - score: score + 4, - }); - newUserScore.save(err => { - if (err) { - log({ logger: 'db', content: `Error creating new user ${authorId} for scoring`, level: 'error' }); - } - else { - log({ logger: 'db', content: `Successfully created user ${authorId} for scoring`, level: 'debug' }); - } - }); - } - else { - doc.score = doc.score + 4; - doc.save(); - } - - return `Great job! Your score is now ${score + 4}.`; - } -} - -export async function connect(mongoUri) { - mongoose - .connect(mongoUri, { - useUnifiedTopology: true, - useNewUrlParser: true, - }) - .then(() => log({ logger: 'db', content: `Connected to the database at ${mongoUri}!`, level: 'info' })) - .catch(err => log({ - logger: 'db', - content: `Failed to connect to the database at ${mongoUri}: ${err}`, - level: 'fatal', - })); -} - +import mongoose from "mongoose"; + +import log from "../helpers/log"; +import userScore from "../models/userScore"; + +export async function updateScore( + isCorrect: boolean, + score: number, + authorId: string +) { + if (!isCorrect) { + return `Nice try! Your score is still ${score}.`; + } else { + // TODO: Error handling + const doc = await userScore.findOne({ + authorID: authorId, + }); + if (!doc) { + const newUserScore = new userScore({ + authorID: authorId, + score: score + 4, + }); + newUserScore.save((err) => { + if (err) { + log({ + logger: "db", + content: `Error creating new user ${authorId} for scoring`, + level: "error", + }); + } else { + log({ + logger: "db", + content: `Successfully created user ${authorId} for scoring`, + level: "debug", + }); + } + }); + } else { + doc.score = doc.score + 4; + doc.save(); + } + + return `Great job! Your score is now ${score + 4}.`; + } +} + +export async function connect(mongoUri) { + mongoose + .connect(mongoUri, { + useUnifiedTopology: true, + useNewUrlParser: true, + }) + .then(() => + log({ + logger: "db", + content: `Connected to the database at ${mongoUri}!`, + level: "info", + }) + ) + .catch((err) => + log({ + logger: "db", + content: `Failed to connect to the database at ${mongoUri}: ${err}`, + level: "fatal", + }) + ); +} diff --git a/src/helpers/env.ts b/src/helpers/env.ts index 79f63b4..8f79d7a 100644 --- a/src/helpers/env.ts +++ b/src/helpers/env.ts @@ -1,6 +1,7 @@ -import 'dotenv/config'; +import "dotenv/config"; -export const clientId = process.env.CLIENT_ID || ''; -export const testingGuild = process.env.TESTING_GUILD || ''; -export const token = process.env.TOKEN || ''; -export const mongoUri = process.env.MONGO_URI = 'mongodb://mongo:27017/AWESOME'; +export const clientId = process.env.CLIENT_ID || ""; +export const testingGuild = process.env.TESTING_GUILD || ""; +export const token = process.env.TOKEN || ""; +export const mongoUri = (process.env.MONGO_URI = + "mongodb://mongo:27017/AWESOME"); diff --git a/src/helpers/log.ts b/src/helpers/log.ts index 07995e4..bc9a156 100644 --- a/src/helpers/log.ts +++ b/src/helpers/log.ts @@ -1,29 +1,29 @@ -import log4js from 'log4js'; +import log4js from "log4js"; -export default function(config) { - const logger = log4js.getLogger(config.logger); - logger.level = 'debug'; - switch (config.level) { - case 'trace': - logger.trace(config.content); - break; - case 'debug': - logger.debug(config.content); - break; - case 'info': - logger.info(config.content); - break; - case 'warn': - logger.warn(config.content); - break; - case 'error': - logger.error(config.content); - break; - case 'fatal': - logger.fatal(config.content); - break; - default: - logger.debug(config.content); - break; - } -} \ No newline at end of file +export default function (config) { + const logger = log4js.getLogger(config.logger); + logger.level = "debug"; + switch (config.level) { + case "trace": + logger.trace(config.content); + break; + case "debug": + logger.debug(config.content); + break; + case "info": + logger.info(config.content); + break; + case "warn": + logger.warn(config.content); + break; + case "error": + logger.error(config.content); + break; + case "fatal": + logger.fatal(config.content); + break; + default: + logger.debug(config.content); + break; + } +} diff --git a/src/helpers/util/pagination.ts b/src/helpers/util/pagination.ts index b296fb5..36f5342 100644 --- a/src/helpers/util/pagination.ts +++ b/src/helpers/util/pagination.ts @@ -1,87 +1,113 @@ -import { CommandInteraction, Message, MessageActionRow, MessageButton, MessageEmbed } from 'discord.js'; +import { + CommandInteraction, + Message, + MessageActionRow, + MessageButton, + MessageEmbed, +} from "discord.js"; -export async function paginateMessage(message: Message, embeds: MessageEmbed[]) { - let index = 0; +export async function paginateMessage( + message: Message, + embeds: MessageEmbed[] +) { + let index = 0; - const row = new MessageActionRow; - row.addComponents( - new MessageButton() - .setCustomId('paginator-left') - .setEmoji('868552005977788466') - .setStyle('SECONDARY'), - new MessageButton() - .setCustomId('paginator-right') - .setEmoji('868551772887711754') - .setStyle('SECONDARY') - ); + const row = new MessageActionRow(); + row.addComponents( + new MessageButton() + .setCustomId("paginator-left") + .setEmoji("868552005977788466") + .setStyle("SECONDARY"), + new MessageButton() + .setCustomId("paginator-right") + .setEmoji("868551772887711754") + .setStyle("SECONDARY") + ); - await message.reply({ content: `Page 1 of ${embeds.length}:`, embeds: [embeds[index]], components: [row] }) - .then(async paginatorMessage => { - const filter = m => m.author.id === message.author.id; + await message + .reply({ + content: `Page 1 of ${embeds.length}:`, + embeds: [embeds[index]], + components: [row], + }) + .then(async (paginatorMessage) => { + const filter = (m) => m.author.id === message.author.id; - const paginatorCollector = paginatorMessage.createMessageComponentCollector({ - componentType: 'BUTTON', - filter: filter, - }); + const paginatorCollector = + paginatorMessage.createMessageComponentCollector({ + componentType: "BUTTON", + filter: filter, + }); - paginatorCollector.on('collect', async i => { - switch (i.customId) { - case 'paginator-left': - index--; - if (index < 0) index = embeds.length - 1; - break; - case 'paginator-right': - index++; - if (index > embeds.length - 1) index = 0; - break; - } - paginatorMessage.edit({ content: `Page ${index + 1} of ${embeds.length}:`, embeds: [embeds[index]] }); - }); - }); + paginatorCollector.on("collect", async (i) => { + switch (i.customId) { + case "paginator-left": + index--; + if (index < 0) index = embeds.length - 1; + break; + case "paginator-right": + index++; + if (index > embeds.length - 1) index = 0; + break; + } + paginatorMessage.edit({ + content: `Page ${index + 1} of ${embeds.length}:`, + embeds: [embeds[index]], + }); + }); + }); } -export async function paginateInteraction(interaction: CommandInteraction, embeds: MessageEmbed[]) { - let index = 0; +export async function paginateInteraction( + interaction: CommandInteraction, + embeds: MessageEmbed[] +) { + let index = 0; - const row = new MessageActionRow; - row.addComponents( - new MessageButton() - .setCustomId('paginator-left') - .setEmoji('868552005977788466') - .setStyle('SECONDARY'), - new MessageButton() - .setCustomId('paginator-right') - .setEmoji('868551772887711754') - .setStyle('SECONDARY') - ); + const row = new MessageActionRow(); + row.addComponents( + new MessageButton() + .setCustomId("paginator-left") + .setEmoji("868552005977788466") + .setStyle("SECONDARY"), + new MessageButton() + .setCustomId("paginator-right") + .setEmoji("868551772887711754") + .setStyle("SECONDARY") + ); - await interaction.followUp({ - content: `Page 1 of ${embeds.length}:`, - embeds: [embeds[index]], - components: [row], - fetchReply: true, - }) - .then(async p => { - const paginatorMessage = p as Message; - const filter = i => i.user.id === interaction.user.id; + await interaction + .followUp({ + content: `Page 1 of ${embeds.length}:`, + embeds: [embeds[index]], + components: [row], + fetchReply: true, + }) + .then(async (p) => { + const paginatorMessage = p as Message; + const filter = (i) => i.user.id === interaction.user.id; - const paginatorCollector = paginatorMessage.createMessageComponentCollector({ - componentType: 'BUTTON', - filter: filter, - }); + const paginatorCollector = + paginatorMessage.createMessageComponentCollector({ + componentType: "BUTTON", + filter: filter, + }); - paginatorCollector.on('collect', async i => { - switch (i.customId) { - case 'paginator-left': - index--; - if (index < 0) index = embeds.length - 1; - break; - case 'paginator-right': - index++; - if (index > embeds.length - 1) index = 0; - break; - } - await i.update({ content: `Page ${index + 1} of ${embeds.length}:`, embeds: [embeds[index]] }); - }); - }); -} \ No newline at end of file + paginatorCollector.on("collect", async (i) => { + switch (i.customId) { + case "paginator-left": + index--; + if (index < 0) index = embeds.length - 1; + break; + case "paginator-right": + index++; + if (index > embeds.length - 1) index = 0; + break; + } + await i.update({ + content: `Page ${index + 1} of ${embeds.length}:`, + embeds: [embeds[index]], + }); + }); + }); +} diff --git a/src/index.ts b/src/index.ts index 1567f02..831e906 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,38 +1,54 @@ #!/usr/bin/env node -import fs from 'node:fs'; -import { Client, Collection, Intents } from 'discord.js'; -import { token } from './helpers/env'; -import log from './helpers/log'; +import fs from "node:fs"; +import { Client, Collection, Intents } from "discord.js"; +import { token } from "./helpers/env"; +import log from "./helpers/log"; const client = new Client({ - intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MESSAGE_REACTIONS, Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.DIRECT_MESSAGES, Intents.FLAGS.DIRECT_MESSAGE_REACTIONS], + intents: [ + Intents.FLAGS.GUILDS, + Intents.FLAGS.GUILD_MESSAGES, + Intents.FLAGS.GUILD_MESSAGE_REACTIONS, + Intents.FLAGS.GUILD_MEMBERS, + Intents.FLAGS.DIRECT_MESSAGES, + Intents.FLAGS.DIRECT_MESSAGE_REACTIONS, + ], }); -client['commands'] = new Collection(); +client["commands"] = new Collection(); -const commandFiles = fs.readdirSync(`${__dirname}/commands`).filter(file => file.endsWith('.js')); -const eventFiles = fs.readdirSync(`${__dirname}/events`).filter(file => file.endsWith('.js')); +const commandFiles = fs + .readdirSync(`${__dirname}/commands`) + .filter((file) => file.endsWith(".js")); +const eventFiles = fs + .readdirSync(`${__dirname}/events`) + .filter((file) => file.endsWith(".js")); for (const file of commandFiles) { - import(`${__dirname}/commands/${file}`) - .then(command => { - client['commands'].set(command.data.name, command); - log({ logger: 'command', content: `Registered command ${file}!`, level: 'info' }); - }); + import(`${__dirname}/commands/${file}`).then((command) => { + client["commands"].set(command.data.name, command); + log({ + logger: "command", + content: `Registered command ${file}!`, + level: "info", + }); + }); } for (const file of eventFiles) { - import(`${__dirname}/events/${file}`) - .then(event => { - if (event.once) { - client.once(event.name, (...args) => event.execute(...args)); - } - else { - client.on(event.name, (...args) => event.execute(...args)); - } - log({ logger: 'event', content: `Registered event ${file}!`, level: 'info' }); - }); + import(`${__dirname}/events/${file}`).then((event) => { + if (event.once) { + client.once(event.name, (...args) => event.execute(...args)); + } else { + client.on(event.name, (...args) => event.execute(...args)); + } + log({ + logger: "event", + content: `Registered event ${file}!`, + level: "info", + }); + }); } client.login(token); diff --git a/src/models/generatedRound.ts b/src/models/generatedRound.ts index 0fbd5f2..ad895b7 100644 --- a/src/models/generatedRound.ts +++ b/src/models/generatedRound.ts @@ -1,22 +1,22 @@ -import mongoose from 'mongoose'; +import mongoose from "mongoose"; const generatedRoundSchema = new mongoose.Schema({ - htmlContent: { - type: String, - required: true, - }, - requestedBy: { - type: String, - required: true, - }, - authorTag: { - type: String, - required: true, - }, - timestamp: { - type: String, - required: true, - }, + htmlContent: { + type: String, + required: true, + }, + requestedBy: { + type: String, + required: true, + }, + authorTag: { + type: String, + required: true, + }, + timestamp: { + type: String, + required: true, + }, }); -export default mongoose.model('GeneratedRound', generatedRoundSchema); +export default mongoose.model("GeneratedRound", generatedRoundSchema); diff --git a/src/models/userConfig.ts b/src/models/userConfig.ts index 5beff3f..9443e02 100644 --- a/src/models/userConfig.ts +++ b/src/models/userConfig.ts @@ -1,17 +1,17 @@ -import mongoose from 'mongoose'; +import mongoose from "mongoose"; const userConfigSchema = new mongoose.Schema({ - _id: String, - subjects: { - type: [String], - required: true, - default: null, - }, - gradeLevels: { - type: [String], - required: true, - default: null, - }, + _id: String, + subjects: { + type: [String], + required: true, + default: null, + }, + gradeLevels: { + type: [String], + required: true, + default: null, + }, }); -export default mongoose.model('UserConfig', userConfigSchema); +export default mongoose.model("UserConfig", userConfigSchema); diff --git a/src/models/userScore.ts b/src/models/userScore.ts index 3ef9a9a..37f8c1a 100644 --- a/src/models/userScore.ts +++ b/src/models/userScore.ts @@ -1,14 +1,14 @@ -import mongoose from 'mongoose'; +import mongoose from "mongoose"; const userScoreSchema = new mongoose.Schema({ - authorID: { - type: String, - required: true, - }, - score: { - type: Number, - required: true, - }, + authorID: { + type: String, + required: true, + }, + score: { + type: Number, + required: true, + }, }); -export default mongoose.model('UserScore', userScoreSchema); +export default mongoose.model("UserScore", userScoreSchema);