Browse Source

style: prevent ESLint from clashing with Prettier

dependabot/npm_and_yarn/semver-5.7.2
Abheek Dhawan 2 years ago
parent
commit
196f0dc6f1
Signed by: abheekd GPG Key ID: 7BE81B8C14475B67
  1. 208
      .eslintrc.json
  2. 3
      .prettierrc
  3. 110
      src/commands/about.ts
  4. 24
      src/commands/help.ts
  5. 342
      src/commands/rounds.ts
  6. 80
      src/commands/score.ts
  7. 546
      src/commands/settings.ts
  8. 116
      src/commands/top.ts
  9. 612
      src/commands/train.ts
  10. 38
      src/events/interactionCreate.ts
  11. 56
      src/events/messageCreate.ts
  12. 22
      src/events/ready.ts
  13. 116
      src/helpers/db.ts
  14. 10
      src/helpers/env.ts
  15. 52
      src/helpers/log.ts
  16. 192
      src/helpers/util/pagination.ts
  17. 74
      src/index.ts
  18. 36
      src/models/generatedRound.ts
  19. 26
      src/models/userConfig.ts
  20. 20
      src/models/userScore.ts

208
.eslintrc.json

@ -1,119 +1,91 @@
{ {
"extends": [ "extends": [
"plugin:@typescript-eslint/recommended" "plugin:@typescript-eslint/recommended"
], ],
"plugins": [ "plugins": [
"@typescript-eslint" "@typescript-eslint"
], ],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"env": { "env": {
"node": true, "node": true,
"es6": true "es6": true
}, },
"ignorePatterns": [ "ignorePatterns": [
"src/deploy-commands.js" "src/deploy-commands.js"
], ],
"rules": { "rules": {
"arrow-spacing": [ "arrow-spacing": [
"error", "error",
{ {
"before": true, "before": true,
"after": true "after": true
} }
], ],
"brace-style": [ "comma-dangle": [
"error", "error",
"stroustrup", "always-multiline"
{ ],
"allowSingleLine": true "comma-spacing": "error",
} "comma-style": "error",
], "dot-location": [
"comma-dangle": [ "error",
"error", "property"
"always-multiline" ],
], "handle-callback-err": "off",
"comma-spacing": "error", "keyword-spacing": "error",
"comma-style": "error", "max-nested-callbacks": [
"curly": [ "error",
"error", {
"multi-line", "max": 4
"consistent" }
], ],
"dot-location": [ "max-statements-per-line": [
"error", "error",
"property" {
], "max": 2
"handle-callback-err": "off", }
"indent": [ ],
"error", "no-console": "off",
"tab" "no-empty-function": "error",
], "no-floating-decimal": "error",
"keyword-spacing": "error", "no-lonely-if": "error",
"max-nested-callbacks": [ "no-multi-spaces": "error",
"error", "no-multiple-empty-lines": [
{ "error",
"max": 4 {
} "max": 2,
], "maxEOF": 1,
"max-statements-per-line": [ "maxBOF": 0
"error", }
{ ],
"max": 2 "no-shadow": [
} "error",
], {
"no-console": "off", "allow": [
"no-empty-function": "error", "err",
"no-floating-decimal": "error", "resolve",
"no-lonely-if": "error", "reject"
"no-multi-spaces": "error", ]
"no-multiple-empty-lines": [ }
"error", ],
{ "no-trailing-spaces": [
"max": 2, "error"
"maxEOF": 1, ],
"maxBOF": 0 "no-var": "error",
} "object-curly-spacing": [
], "error",
"no-shadow": [ "always"
"error", ],
{ "prefer-const": "error",
"allow": [ "semi": [
"err", "error",
"resolve", "always"
"reject" ],
] "space-before-blocks": "error",
} "space-in-parens": "error",
], "space-infix-ops": "error",
"no-trailing-spaces": [ "space-unary-ops": "error",
"error" "spaced-comment": "error",
], "yoda": "error"
"no-var": "error", }
"object-curly-spacing": [ }
"error",
"always"
],
"prefer-const": "error",
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"space-before-blocks": "error",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}
],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "error",
"yoda": "error"
}
}

3
.prettierrc

@ -1,6 +1,7 @@
{ {
"singleQuote": false, "singleQuote": true,
"semi": true, "semi": true,
"useTabs": true,
"endOfLine": "lf", "endOfLine": "lf",
"trailingComma": "es5" "trailingComma": "es5"
} }

110
src/commands/about.ts

@ -1,72 +1,72 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from '@discordjs/builders';
import { MessageEmbed, CommandInteraction } from "discord.js"; import { MessageEmbed, CommandInteraction } from 'discord.js';
import gitlog from "gitlog"; import gitlog from 'gitlog';
import userScore from "../models/userScore"; import userScore from '../models/userScore';
import { paginateInteraction } from "../helpers/util/pagination"; import { paginateInteraction } from '../helpers/util/pagination';
export const data = new SlashCommandBuilder() export const data = new SlashCommandBuilder()
.setName("about") .setName('about')
.setDescription("Commands regarding the creation/development of the bot"); .setDescription('Commands regarding the creation/development of the bot');
export async function execute(interaction: CommandInteraction) { export async function execute(interaction: CommandInteraction) {
await interaction.deferReply(); await interaction.deferReply();
const client = interaction.client; const client = interaction.client;
const embeds: MessageEmbed[] = []; const embeds: MessageEmbed[] = [];
const contributorEmbed = new MessageEmbed() const contributorEmbed = new MessageEmbed()
.setTitle("Contributors") .setTitle('Contributors')
.addField("Creator", "<@745063586422063214> [ADawesomeguy#3602]", true) .addField('Creator', '<@745063586422063214> [ADawesomeguy#3602]', true)
.addField( .addField(
"Contributors", 'Contributors',
"<@650525101048987649> [tEjAs#8127]\n<@426864344463048705> [tetrident#9396]", '<@650525101048987649> [tEjAs#8127]\n<@426864344463048705> [tetrident#9396]',
true true
) // Add more contributors here, first one is Abheek, second one is Tejas ) // Add more contributors here, first one is Abheek, second one is Tejas
.setTimestamp() .setTimestamp()
.setColor("#ffffff"); .setColor('#ffffff');
embeds.push(contributorEmbed); embeds.push(contributorEmbed);
const gitRepoLocation = __dirname; const gitRepoLocation = __dirname;
const commits = gitlog({ const commits = gitlog({
repo: gitRepoLocation, repo: gitRepoLocation,
number: 5, number: 5,
fields: ["hash", "abbrevHash", "subject", "authorName", "authorDateRel"], fields: ['hash', 'abbrevHash', 'subject', 'authorName', 'authorDateRel'],
}); });
const changelogEmbed = new MessageEmbed() const changelogEmbed = new MessageEmbed()
.setAuthor({ .setAuthor({
name: interaction.user.tag, name: interaction.user.tag,
iconURL: interaction.user.displayAvatarURL(), iconURL: interaction.user.displayAvatarURL(),
}) })
.setTitle("Changelog") .setTitle('Changelog')
.setColor("#ffffff") .setColor('#ffffff')
.setTimestamp(); .setTimestamp();
commits.forEach((commit) => { commits.forEach((commit) => {
changelogEmbed.addField( changelogEmbed.addField(
commit.abbrevHash, 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` `> \`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); embeds.push(changelogEmbed);
await client.guilds.fetch(); await client.guilds.fetch();
const trainingDocuments = await userScore.countDocuments({}); const trainingDocuments = await userScore.countDocuments({});
const aboutBotEmbed = new MessageEmbed() const aboutBotEmbed = new MessageEmbed()
.setAuthor({ .setAuthor({
name: interaction.user.tag, name: interaction.user.tag,
iconURL: interaction.user.displayAvatarURL(), iconURL: interaction.user.displayAvatarURL(),
}) })
.setTitle("About AwesomeSciBo") .setTitle('About AwesomeSciBo')
.addField("Servers", `${client.guilds.cache.size}`, true) .addField('Servers', `${client.guilds.cache.size}`, true)
.addField("Training Users", `${trainingDocuments}`, true) .addField('Training Users', `${trainingDocuments}`, true)
.setTimestamp(); .setTimestamp();
embeds.push(aboutBotEmbed); embeds.push(aboutBotEmbed);
paginateInteraction(interaction, embeds); paginateInteraction(interaction, embeds);
} }

24
src/commands/help.ts

@ -1,18 +1,18 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from '@discordjs/builders';
import { MessageEmbed, CommandInteraction } from "discord.js"; import { MessageEmbed, CommandInteraction } from 'discord.js';
export const data = new SlashCommandBuilder() export const data = new SlashCommandBuilder()
.setName("help") .setName('help')
.setDescription("Replies with a help message explaining what the bot can do"); .setDescription('Replies with a help message explaining what the bot can do');
export async function execute(interaction: CommandInteraction) { export async function execute(interaction: CommandInteraction) {
await interaction.deferReply(); await interaction.deferReply();
await interaction.deferReply(); await interaction.deferReply();
const helpEmbed = new MessageEmbed() const helpEmbed = new MessageEmbed()
.setDescription( .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." '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"); .setColor('#ffffff');
interaction.followUp({ embeds: [helpEmbed] }); interaction.followUp({ embeds: [helpEmbed] });
} }

342
src/commands/rounds.ts

@ -1,178 +1,178 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from '@discordjs/builders';
import { MessageEmbed, CommandInteraction } from "discord.js"; import { MessageEmbed, CommandInteraction } from 'discord.js';
import axios from "axios"; import axios from 'axios';
import log from "../helpers/log"; import log from '../helpers/log';
import generatedRound from "../models/generatedRound"; import generatedRound from '../models/generatedRound';
export const data = new SlashCommandBuilder() export const data = new SlashCommandBuilder()
.setName("rounds") .setName('rounds')
.setDescription("Commands regarding the generation of rounds") .setDescription('Commands regarding the generation of rounds')
.addSubcommand((subcommand) => { .addSubcommand((subcommand) => {
subcommand subcommand
.setName("generate") .setName('generate')
.setDescription( .setDescription(
"Generates a round with randomized questions from https://scibowldb.com/" 'Generates a round with randomized questions from https://scibowldb.com/'
); );
return subcommand; return subcommand;
}) })
.addSubcommand((subcommand) => { .addSubcommand((subcommand) => {
subcommand subcommand
.setName("list") .setName('list')
.setDescription("Lists your 5 most recently generated rounds with links"); .setDescription('Lists your 5 most recently generated rounds with links');
return subcommand; return subcommand;
}) })
.addSubcommand((subcommand) => { .addSubcommand((subcommand) => {
subcommand subcommand
.setName("hit") .setName('hit')
.setDescription( .setDescription(
"Shows the total number of rounds hit as well as the number for the specific user" 'Shows the total number of rounds hit as well as the number for the specific user'
); );
return subcommand; return subcommand;
}); });
export async function execute(interaction: CommandInteraction) { export async function execute(interaction: CommandInteraction) {
const action = interaction.options.getSubcommand(); const action = interaction.options.getSubcommand();
switch (action) { switch (action) {
case "generate": { case 'generate': {
interaction.deferReply({ ephemeral: true }); interaction.deferReply({ ephemeral: true });
let finalizedHTML = let finalizedHTML =
"<html><head><link rel='preconnect' href='https://fonts.gstatic.com'><link href='https://fonts.googleapis.com/css2?family=Ubuntu&display=swap' rel='stylesheet'> </head><body style='width: 70%; margin-left: auto; margin-right: auto;'><h2 style='text-align: center; text-decoration: underline overline; padding: 7px;'>ROUND GENERATED BY AWESOMESCIBO USING THE SCIBOWLDB API</h2>"; "<html><head><link rel='preconnect' href='https://fonts.gstatic.com'><link href='https://fonts.googleapis.com/css2?family=Ubuntu&display=swap' rel='stylesheet'> </head><body style='width: 70%; margin-left: auto; margin-right: auto;'><h2 style='text-align: center; text-decoration: underline overline; padding: 7px;'>ROUND GENERATED BY AWESOMESCIBO USING THE SCIBOWLDB API</h2>";
let tossup_question: string; let tossup_question: string;
let question_category: string; let question_category: string;
let tossup_format: string; let tossup_format: string;
let tossup_answer: string; let tossup_answer: string;
let bonus_question: string; let bonus_question: string;
let bonus_format: string; let bonus_format: string;
let bonus_answer: string; let bonus_answer: string;
let htmlContent = ""; let htmlContent = '';
await axios await axios
.post("https://scibowldb.com/api/questions", { .post('https://scibowldb.com/api/questions', {
categories: [ categories: [
"BIOLOGY", 'BIOLOGY',
"PHYSICS", 'PHYSICS',
"CHEMISTRY", 'CHEMISTRY',
"EARTH AND SPACE", 'EARTH AND SPACE',
"ASTRONOMY", 'ASTRONOMY',
"MATH", 'MATH',
], ],
}) })
.then((response) => { .then((response) => {
for (let i = 1; i < 26; i++) { for (let i = 1; i < 26; i++) {
const questionData = const questionData =
response.data.questions[ response.data.questions[
Math.floor(Math.random() * response.data.questions.length) Math.floor(Math.random() * response.data.questions.length)
]; ];
tossup_question = questionData.tossup_question; tossup_question = questionData.tossup_question;
tossup_answer = questionData.tossup_answer; tossup_answer = questionData.tossup_answer;
question_category = questionData.category; question_category = questionData.category;
tossup_format = questionData.tossup_format; tossup_format = questionData.tossup_format;
bonus_question = questionData.bonus_question; bonus_question = questionData.bonus_question;
bonus_answer = questionData.bonus_answer; bonus_answer = questionData.bonus_answer;
bonus_format = questionData.bonus_format; bonus_format = questionData.bonus_format;
htmlContent = htmlContent =
"<br><br><h3 style='text-align: center;'><strong>TOSS-UP</strong></h3>\n<br>" + "<br><br><h3 style='text-align: center;'><strong>TOSS-UP</strong></h3>\n<br>" +
`${i}) <strong>${question_category}</strong>` + `${i}) <strong>${question_category}</strong>` +
" " + ' ' +
`<em>${tossup_format}</em>` + `<em>${tossup_format}</em>` +
" " + ' ' +
tossup_question + tossup_question +
"<br><br>" + '<br><br>' +
"<strong>ANSWER:</strong> " + '<strong>ANSWER:</strong> ' +
tossup_answer + tossup_answer +
"<br>"; '<br>';
htmlContent += htmlContent +=
"<br><br><h3 style='text-align: center;'><strong>BONUS</strong></h3>\n<br>" + "<br><br><h3 style='text-align: center;'><strong>BONUS</strong></h3>\n<br>" +
`${i}) <strong>${question_category}</strong>` + `${i}) <strong>${question_category}</strong>` +
" " + ' ' +
`<em>${bonus_format}</em>` + `<em>${bonus_format}</em>` +
" " + ' ' +
bonus_question + bonus_question +
"<br><br>" + '<br><br>' +
"<strong>ANSWER:</strong> " + '<strong>ANSWER:</strong> ' +
bonus_answer + bonus_answer +
"<br><br><hr><br>"; '<br><br><hr><br>';
htmlContent = htmlContent.replace(/\n/g, "<br>"); htmlContent = htmlContent.replace(/\n/g, '<br>');
finalizedHTML += htmlContent; finalizedHTML += htmlContent;
} }
const newGeneratedRound = new generatedRound({ const newGeneratedRound = new generatedRound({
htmlContent: finalizedHTML, htmlContent: finalizedHTML,
requestedBy: interaction.user.id, requestedBy: interaction.user.id,
authorTag: interaction.user.tag, authorTag: interaction.user.tag,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
newGeneratedRound.save((err, round) => { newGeneratedRound.save((err, round) => {
if (err) { if (err) {
log({ log({
logger: "rounds", logger: 'rounds',
content: `Saving round to DB failed: ${err}`, content: `Saving round to DB failed: ${err}`,
level: "error", level: 'error',
}); });
return; return;
} }
interaction.followUp({ interaction.followUp({
content: `Here's your round: https://api.adawesome.tech/round/${round._id.toString()}`, content: `Here's your round: https://api.adawesome.tech/round/${round._id.toString()}`,
ephemeral: true, ephemeral: true,
}); });
}); });
}); });
break; break;
} }
case "list": { case 'list': {
interaction.deferReply({ ephemeral: true }); interaction.deferReply({ ephemeral: true });
let roundsList = await generatedRound let roundsList = await generatedRound
.find({ requestedBy: interaction.user.id }) .find({ requestedBy: interaction.user.id })
.sort({ timestamp: -1 }); .sort({ timestamp: -1 });
let finalMessage = ""; let finalMessage = '';
if (!roundsList) { if (!roundsList) {
interaction.followUp("You haven't requested any rounds!"); interaction.followUp("You haven't requested any rounds!");
return; return;
} }
if (roundsList.length > 5) { if (roundsList.length > 5) {
roundsList = roundsList.slice(0, 5); roundsList = roundsList.slice(0, 5);
} }
roundsList.forEach(async (item, index) => { roundsList.forEach(async (item, index) => {
finalMessage += `${index + 1}. [${ finalMessage += `${index + 1}. [${
item.timestamp.split("T")[0] item.timestamp.split('T')[0]
}](https://api.adawesome.tech/round/${item._id.toString()})\n`; }](https://api.adawesome.tech/round/${item._id.toString()})\n`;
}); });
const roundsListEmbed = new MessageEmbed() const roundsListEmbed = new MessageEmbed()
.setAuthor({ .setAuthor({
name: interaction.user.tag, name: interaction.user.tag,
iconURL: interaction.user.displayAvatarURL(), iconURL: interaction.user.displayAvatarURL(),
}) })
.setTitle("Last 5 rounds requested") .setTitle('Last 5 rounds requested')
.setDescription(finalMessage) .setDescription(finalMessage)
.setTimestamp(); .setTimestamp();
interaction.followUp({ interaction.followUp({
embeds: [roundsListEmbed], embeds: [roundsListEmbed],
ephemeral: true, ephemeral: true,
}); });
break; break;
} }
case "hit": { case 'hit': {
await interaction.deferReply(); await interaction.deferReply();
const totalCount = await generatedRound.countDocuments({}); const totalCount = await generatedRound.countDocuments({});
const userCount = await generatedRound.countDocuments({ const userCount = await generatedRound.countDocuments({
requestedBy: interaction.user.id, requestedBy: interaction.user.id,
}); });
interaction.followUp( interaction.followUp(
`Total Hits: ${totalCount}\nYour Hits: ${userCount}` `Total Hits: ${totalCount}\nYour Hits: ${userCount}`
); );
break; break;
} }
} }
} }

80
src/commands/score.ts

@ -1,47 +1,47 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from '@discordjs/builders';
import { CommandInteraction, MessageEmbed } from "discord.js"; import { CommandInteraction, MessageEmbed } from 'discord.js';
import log from "../helpers/log"; import log from '../helpers/log';
import userScore from "../models/userScore"; import userScore from '../models/userScore';
export const data = new SlashCommandBuilder() export const data = new SlashCommandBuilder()
.setName("score") .setName('score')
.setDescription("Returns the score of the current user or another") .setDescription('Returns the score of the current user or another')
.addUserOption((option) => { .addUserOption((option) => {
option option
.setName("user") .setName('user')
.setDescription("The user to find the score for") .setDescription('The user to find the score for')
.setRequired(false); .setRequired(false);
return option; return option;
}); });
export async function execute(interaction: CommandInteraction) { export async function execute(interaction: CommandInteraction) {
const scoreEmbed = new MessageEmbed().setColor("#ffffff"); const scoreEmbed = new MessageEmbed().setColor('#ffffff');
const user = interaction.options.getUser("user") || interaction.user; const user = interaction.options.getUser('user') || interaction.user;
userScore.findOne({ authorID: user.id }, async (err, score) => { userScore.findOne({ authorID: user.id }, async (err, score) => {
if (err) { if (err) {
log({ log({
logger: "db", logger: 'db',
content: `Unable to obtain user: ${err}`, content: `Unable to obtain user: ${err}`,
level: "info", level: 'info',
}); });
} }
if (!score) { if (!score) {
await interaction.reply({ await interaction.reply({
content: content:
"Unfortunately, that user does not seem to have used AwesomeSciBo yet.", 'Unfortunately, that user does not seem to have used AwesomeSciBo yet.',
ephemeral: true, ephemeral: true,
}); });
return; return;
} }
scoreEmbed scoreEmbed
.setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() })
.setDescription(`Score: \`${score.score}\``); .setDescription(`Score: \`${score.score}\``);
await interaction.reply({ embeds: [scoreEmbed] }); await interaction.reply({ embeds: [scoreEmbed] });
}); });
} }

546
src/commands/settings.ts

@ -1,289 +1,289 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from '@discordjs/builders';
import { Message, MessageActionRow, MessageSelectMenu } from "discord.js"; import { Message, MessageActionRow, MessageSelectMenu } from 'discord.js';
import { CommandInteraction, MessageEmbed } from "discord.js"; import { CommandInteraction, MessageEmbed } from 'discord.js';
import log from "../helpers/log"; import log from '../helpers/log';
import userConfig from "../models/userConfig"; import userConfig from '../models/userConfig';
export const data = new SlashCommandBuilder() export const data = new SlashCommandBuilder()
.setName("settings") .setName('settings')
.setDescription("BETA - settings configuration") .setDescription('BETA - settings configuration')
.addSubcommand((subcommand) => { .addSubcommand((subcommand) => {
subcommand.setName("subject").setDescription("Changes subject of problems"); subcommand.setName('subject').setDescription('Changes subject of problems');
return subcommand; return subcommand;
}) })
.addSubcommand((subcommand) => { .addSubcommand((subcommand) => {
subcommand.setName("display").setDescription("Displays current settings"); subcommand.setName('display').setDescription('Displays current settings');
return subcommand; return subcommand;
}) })
.addSubcommand((subcommand) => { .addSubcommand((subcommand) => {
subcommand subcommand
.setName("gradelevels") .setName('gradelevels')
.setDescription("Changes grade level of problems"); .setDescription('Changes grade level of problems');
return subcommand; return subcommand;
}); });
export async function execute(interaction: CommandInteraction) { export async function execute(interaction: CommandInteraction) {
const action = interaction.options.getSubcommand(); const action = interaction.options.getSubcommand();
switch (action) { switch (action) {
case "display": { case 'display': {
await interaction.deferReply(); 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 settingsEmbed
.setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() })
.setDescription("Current selections: "); .setDescription('Current selections: ');
const menu = new MessageActionRow().addComponents( const menu = new MessageActionRow().addComponents(
new MessageSelectMenu() new MessageSelectMenu()
.setCustomId("selectdisp") .setCustomId('selectdisp')
.setPlaceholder("Nothing selected") .setPlaceholder('Nothing selected')
.addOptions([ .addOptions([
{ {
label: "subjects", label: 'subjects',
description: "subjects", description: 'subjects',
value: "subjects", value: 'subjects',
}, },
{ {
label: "gradelevels", label: 'gradelevels',
description: "grade levels", description: 'grade levels',
value: "gradelevels", value: 'gradelevels',
}, },
]) ])
); );
interaction interaction
.followUp({ .followUp({
embeds: [ embeds: [
/* settingsEmbed*/ /* settingsEmbed*/
], ],
components: [menu], components: [menu],
}) })
.then((dispMsg) => { .then((dispMsg) => {
const w = dispMsg as Message; const w = dispMsg as Message;
const dispFilter = (i) => const dispFilter = (i) =>
["selectdisp"].includes(i.customId) && ['selectdisp'].includes(i.customId) &&
i.user.id == interaction.user.id; // <== ATTENTION! First argument... i.user.id == interaction.user.id; // <== ATTENTION! First argument...
w.awaitMessageComponent({ w.awaitMessageComponent({
filter: dispFilter, filter: dispFilter,
componentType: "SELECT_MENU", componentType: 'SELECT_MENU',
}).then(async (dispChoice) => { }).then(async (dispChoice) => {
const vals = dispChoice.values; const vals = dispChoice.values;
const config = await userConfig.findById(interaction.user.id); const config = await userConfig.findById(interaction.user.id);
if (!config) { if (!config) {
await interaction.editReply({ await interaction.editReply({
content: "You don't have a configuration!", content: "You don't have a configuration!",
embeds: [], embeds: [],
components: [], components: [],
}); });
} else if (vals.length === 1 && vals.at(0) === "subjects") { } else if (vals.length === 1 && vals.at(0) === 'subjects') {
await interaction.editReply({ await interaction.editReply({
content: `Current subjects setting: ${config.subjects content: `Current subjects setting: ${config.subjects
.toString() .toString()
.split(",") .split(',')
.join(", ")}`, .join(', ')}`,
components: [], components: [],
}); });
} else if (vals.length === 1 && vals.at(0) === "gradelevels") { } else if (vals.length === 1 && vals.at(0) === 'gradelevels') {
await interaction.editReply({ await interaction.editReply({
content: `Current grade level setting: ${config.gradeLevels content: `Current grade level setting: ${config.gradeLevels
.toString() .toString()
.split(",") .split(',')
.join(", ")}`, .join(', ')}`,
components: [], components: [],
}); });
} else { } else {
(err) => (err) =>
log({ log({
logger: logger:
"'Error occurred: /settings:display did not equal subjects or gradelevels.'", "'Error occurred: /settings:display did not equal subjects or gradelevels.'",
content: `${err}`, content: `${err}`,
level: "error", level: 'error',
}); });
} }
}); });
}); });
break; break;
} }
case "gradelevels": { case 'gradelevels': {
await interaction.deferReply(); 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 settingsEmbed
.setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() })
.setDescription("Current level settings: "); .setDescription('Current level settings: ');
const menu = new MessageActionRow().addComponents( const menu = new MessageActionRow().addComponents(
new MessageSelectMenu() new MessageSelectMenu()
.setCustomId("selectlvl") .setCustomId('selectlvl')
.setPlaceholder("Nothing selected") .setPlaceholder('Nothing selected')
.setMinValues(1) .setMinValues(1)
.setMaxValues(2) .setMaxValues(2)
.addOptions([ .addOptions([
{ {
label: "Middle School", label: 'Middle School',
description: "Middle school level problems", description: 'Middle school level problems',
value: "MS", value: 'MS',
}, },
{ {
label: "High School", label: 'High School',
description: "High school level problems", description: 'High school level problems',
value: "HS", value: 'HS',
}, },
]) ])
); );
interaction interaction
.followUp({ .followUp({
embeds: [ embeds: [
/* settingsEmbed*/ /* settingsEmbed*/
], ],
components: [menu], components: [menu],
}) })
.then((lvlMsg) => { .then((lvlMsg) => {
const w = lvlMsg as Message; const w = lvlMsg as Message;
const lvlFilter = (i) => const lvlFilter = (i) =>
["selectlvl"].includes(i.customId) && ['selectlvl'].includes(i.customId) &&
i.user.id == interaction.user.id; // <== ATTENTION! First argument... i.user.id == interaction.user.id; // <== ATTENTION! First argument...
w.awaitMessageComponent({ w.awaitMessageComponent({
filter: lvlFilter, filter: lvlFilter,
componentType: "SELECT_MENU", componentType: 'SELECT_MENU',
}).then(async (lvlChoice) => { }).then(async (lvlChoice) => {
const vals = lvlChoice.values; const vals = lvlChoice.values;
const levels = new Array<string>(); const levels = new Array<string>();
await userConfig.findOneAndUpdate( await userConfig.findOneAndUpdate(
{ _id: interaction.user.id }, { _id: interaction.user.id },
{ gradeLevels: vals }, { gradeLevels: vals },
{ {
upsert: true, upsert: true,
new: true, new: true,
} }
); );
await vals.forEach((v) => { await vals.forEach((v) => {
switch (v) { switch (v) {
case "MS": case 'MS':
levels.push("Middle School"); levels.push('Middle School');
break; break;
case "HS": case 'HS':
levels.push("High School"); levels.push('High School');
} }
}); });
await interaction.editReply({ await interaction.editReply({
content: `Level set to: ${levels content: `Level set to: ${levels
.toString() .toString()
.split(",") .split(',')
.join(", ")}`, .join(', ')}`,
embeds: [], embeds: [],
components: [], components: [],
}); });
}); });
}); });
break; break;
} }
case "subject": { case 'subject': {
await interaction.deferReply(); 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 settingsEmbed
.setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() }) .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() })
.setDescription("Current subject settings: "); .setDescription('Current subject settings: ');
const menu = new MessageActionRow().addComponents( const menu = new MessageActionRow().addComponents(
new MessageSelectMenu() new MessageSelectMenu()
.setCustomId("selectsubject") .setCustomId('selectsubject')
.setPlaceholder("Nothing selected") .setPlaceholder('Nothing selected')
.setMinValues(1) .setMinValues(1)
.setMaxValues(7) .setMaxValues(7)
.addOptions([ .addOptions([
{ {
label: "Astronomy", label: 'Astronomy',
description: "Astronomy", description: 'Astronomy',
value: "ASTRONOMY", value: 'ASTRONOMY',
}, },
{ {
label: "Biology", label: 'Biology',
description: "Biology", description: 'Biology',
value: "BIOLOGY", value: 'BIOLOGY',
}, },
{ {
label: "Earth Science", label: 'Earth Science',
description: "Earth Science", description: 'Earth Science',
value: "EARTH SCIENCE", value: 'EARTH SCIENCE',
}, },
{ {
label: "Chemistry", label: 'Chemistry',
description: "Chemistry", description: 'Chemistry',
value: "CHEMISTRY", value: 'CHEMISTRY',
}, },
{ {
label: "Physics", label: 'Physics',
description: "Physics", description: 'Physics',
value: "PHYSICS", value: 'PHYSICS',
}, },
{ {
label: "Mathematics", label: 'Mathematics',
description: "Mathematics", description: 'Mathematics',
value: "MATH", value: 'MATH',
}, },
{ {
label: "Energy", label: 'Energy',
description: "Energy", description: 'Energy',
value: "ENERGY", value: 'ENERGY',
}, },
]) ])
); );
interaction interaction
.followUp({ .followUp({
embeds: [ embeds: [
/* settingsEmbed*/ /* settingsEmbed*/
], ],
components: [menu], components: [menu],
}) })
.then((subjectMsg) => { .then((subjectMsg) => {
const subjectFilter = (i) => const subjectFilter = (i) =>
["selectsubject"].includes(i.customId) && ['selectsubject'].includes(i.customId) &&
i.user.id == interaction.user.id; // <== ATTENTION! First argument... i.user.id == interaction.user.id; // <== ATTENTION! First argument...
(subjectMsg as Message) (subjectMsg as Message)
.awaitMessageComponent({ .awaitMessageComponent({
filter: subjectFilter, filter: subjectFilter,
componentType: "SELECT_MENU", componentType: 'SELECT_MENU',
}) })
.then(async (subjectChoice) => { .then(async (subjectChoice) => {
const vals = subjectChoice.values; const vals = subjectChoice.values;
await userConfig.findOneAndUpdate( await userConfig.findOneAndUpdate(
{ _id: interaction.user.id }, { _id: interaction.user.id },
{ subjects: vals }, { subjects: vals },
{ {
upsert: true, upsert: true,
new: true, new: true,
} }
); );
const subjects = new Array<string>(); const subjects = new Array<string>();
await vals.forEach((v) => { await vals.forEach((v) => {
subjects.push( subjects.push(
v v
.toLowerCase() .toLowerCase()
.split(" ") .split(' ')
.map((w) => w[0].toUpperCase() + w.substring(1)) .map((w) => w[0].toUpperCase() + w.substring(1))
.join(" ") .join(' ')
); );
}); });
await interaction.editReply({ await interaction.editReply({
content: `Subjects set to: ${subjects content: `Subjects set to: ${subjects
.toString() .toString()
.split(",") .split(',')
.join(", ")}`, .join(', ')}`,
components: [], components: [],
embeds: [], embeds: [],
}); });
}); });
}); });
break; break;
} }
} }
} }

116
src/commands/top.ts

@ -1,75 +1,75 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from '@discordjs/builders';
import { CommandInteraction, MessageEmbed } from "discord.js"; import { CommandInteraction, MessageEmbed } from 'discord.js';
import log from "../helpers/log"; import log from '../helpers/log';
import userScore from "../models/userScore"; import userScore from '../models/userScore';
export const data = new SlashCommandBuilder() export const data = new SlashCommandBuilder()
.setName("top") .setName('top')
.setDescription( .setDescription(
"Lists top ten scores across servers and in the current server" 'Lists top ten scores across servers and in the current server'
); );
export async function execute(interaction: CommandInteraction) { export async function execute(interaction: CommandInteraction) {
await interaction.deferReply(); await interaction.deferReply();
userScore userScore
.find({}) .find({})
.sort({ score: -1 }) // Sort by descending order .sort({ score: -1 }) // Sort by descending order
.exec(async (err, obj) => { .exec(async (err, obj) => {
if (err) { if (err) {
log({ log({
logger: "top", logger: 'top',
content: `Getting top players failed: ${err}`, content: `Getting top players failed: ${err}`,
level: "error", level: 'error',
}); });
console.log(err); console.log(err);
} }
if (obj.length < 10) { if (obj.length < 10) {
// Need at least 10 scores for top 10 // Need at least 10 scores for top 10
return interaction.followUp( return interaction.followUp(
`There are only ${obj.length} users, we need at least 10!` `There are only ${obj.length} users, we need at least 10!`
); );
} }
const embeds: MessageEmbed[] = []; const embeds: MessageEmbed[] = [];
let lbMessageContent = ""; let lbMessageContent = '';
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
lbMessageContent += `${i + 1}: <@${obj[i].authorID}>: ${ lbMessageContent += `${i + 1}: <@${obj[i].authorID}>: ${
obj[i].score obj[i].score
}\n`; // Loop through each user and add their name and score to leaderboard content }\n`; // Loop through each user and add their name and score to leaderboard content
} }
const leaderboardEmbed = new MessageEmbed() const leaderboardEmbed = new MessageEmbed()
.setTitle("Top Ten!") .setTitle('Top Ten!')
.setDescription(lbMessageContent) .setDescription(lbMessageContent)
.setColor("#ffffff"); .setColor('#ffffff');
embeds.push(leaderboardEmbed); embeds.push(leaderboardEmbed);
let sMessageContent = ""; let sMessageContent = '';
const members = await interaction.guild?.members.fetch(); const members = await interaction.guild?.members.fetch();
const serverLeaderBoardArray = await obj.filter((o) => const serverLeaderBoardArray = await obj.filter((o) =>
members?.some((m) => m.user.id === o.authorID) members?.some((m) => m.user.id === o.authorID)
); );
if (serverLeaderBoardArray.length > 10) { if (serverLeaderBoardArray.length > 10) {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
sMessageContent += `${i + 1}: <@${ sMessageContent += `${i + 1}: <@${
serverLeaderBoardArray[i].authorID serverLeaderBoardArray[i].authorID
}>: ${serverLeaderBoardArray[i].score}\n`; }>: ${serverLeaderBoardArray[i].score}\n`;
} }
const sLeaderboardEmbed = new MessageEmbed() const sLeaderboardEmbed = new MessageEmbed()
.setTitle(`Top Ten in ${interaction.guild?.name}!`) .setTitle(`Top Ten in ${interaction.guild?.name}!`)
.setDescription(sMessageContent) .setDescription(sMessageContent)
.setColor("#ffffff"); .setColor('#ffffff');
embeds.push(sLeaderboardEmbed); embeds.push(sLeaderboardEmbed);
} }
interaction.followUp({ embeds: embeds }); interaction.followUp({ embeds: embeds });
}); });
} }

612
src/commands/train.ts

@ -1,322 +1,322 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from '@discordjs/builders';
import { import {
MessageEmbed, MessageEmbed,
MessageActionRow, MessageActionRow,
MessageButton, MessageButton,
CommandInteraction, CommandInteraction,
Message, Message,
} from "discord.js"; } from 'discord.js';
import { decode } from "html-entities"; import { decode } from 'html-entities';
import axios from "axios"; import axios from 'axios';
import userScore from "../models/userScore"; import userScore from '../models/userScore';
import userConfig from "../models/userConfig"; import userConfig from '../models/userConfig';
import log from "../helpers/log.js"; import log from '../helpers/log.js';
import { updateScore } from "../helpers/db.js"; import { updateScore } from '../helpers/db.js';
export const data = new SlashCommandBuilder() export const data = new SlashCommandBuilder()
.setName("train") .setName('train')
.setDescription("Sends a training question to be answered") .setDescription('Sends a training question to be answered')
.addStringOption((option) => { .addStringOption((option) => {
option option
.setName("subject") .setName('subject')
.setDescription("Optional subject to be used as a filter") .setDescription('Optional subject to be used as a filter')
.setRequired(false) .setRequired(false)
.addChoices( .addChoices(
{ name: "astro", value: "astro" }, { name: 'astro', value: 'astro' },
{ name: "bio", value: "bio" }, { name: 'bio', value: 'bio' },
{ name: "chem", value: "chem" }, { name: 'chem', value: 'chem' },
{ name: "ess", value: "ess" }, { name: 'ess', value: 'ess' },
{ name: "phys", value: "phys" }, { name: 'phys', value: 'phys' },
{ name: "math", value: "math" }, { name: 'math', value: 'math' },
{ name: "energy", value: "energy" } { name: 'energy', value: 'energy' }
) )
.setRequired(false); .setRequired(false);
return option; return option;
}); });
export async function execute(interaction: CommandInteraction) { export async function execute(interaction: CommandInteraction) {
await interaction.deferReply(); await interaction.deferReply();
const subject = interaction.options.get("subject") const subject = interaction.options.get('subject')
? interaction.options.get("subject")?.value ? interaction.options.get('subject')?.value
: null; : null;
const authorId = interaction.user.id; const authorId = interaction.user.id;
let score: number; let score: number;
userScore userScore
.findOne({ authorID: authorId }) .findOne({ authorID: authorId })
.lean() .lean()
.then((obj: { score: number }, err: unknown) => { .then((obj: { score: number }, err: unknown) => {
if (!obj) { if (!obj) {
score = 0; score = 0;
const firstTimeEmbed = new MessageEmbed() const firstTimeEmbed = new MessageEmbed()
.setAuthor({ .setAuthor({
name: interaction.client.user?.tag name: interaction.client.user?.tag
? interaction.client.user?.tag ? interaction.client.user?.tag
: "", : '',
iconURL: interaction.client.user?.displayAvatarURL(), iconURL: interaction.client.user?.displayAvatarURL(),
}) })
.setDescription( .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.):" "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('Creator', '<@745063586422063214> [@abheekd#3602]')
.addField( .addField(
"GitHub", 'GitHub',
"[Link](https://github.com/ADawesomeguy/AwesomeSciBo) (a star couldn't hurt...)" "[Link](https://github.com/ADawesomeguy/AwesomeSciBo) (a star couldn't hurt...)"
) )
.setColor("#ffffff") .setColor('#ffffff')
.setTimestamp(); .setTimestamp();
interaction.user interaction.user
.send({ embeds: [firstTimeEmbed] }) .send({ embeds: [firstTimeEmbed] })
.catch((err) => .catch((err) =>
log({ logger: "train", content: `${err}`, level: "error" }) log({ logger: 'train', content: `${err}`, level: 'error' })
); );
} else if (obj) { } else if (obj) {
score = obj.score; score = obj.score;
} else { } else {
log({ log({
logger: "train", logger: 'train',
content: `Getting user score failed: ${err}`, content: `Getting user score failed: ${err}`,
level: "error", level: 'error',
}); });
} }
}); });
let categoryArray: string[] = []; let categoryArray: string[] = [];
const allCategories = [ const allCategories = [
"BIOLOGY", 'BIOLOGY',
"PHYSICS", 'PHYSICS',
"CHEMISTRY", 'CHEMISTRY',
"EARTH AND SPACE", 'EARTH AND SPACE',
"ASTRONOMY", 'ASTRONOMY',
"MATH", 'MATH',
]; ];
const configCategories = await userConfig.findById(interaction.user.id); const configCategories = await userConfig.findById(interaction.user.id);
switch (subject) { switch (subject) {
case null: case null:
categoryArray = configCategories categoryArray = configCategories
? configCategories.subjects || allCategories ? configCategories.subjects || allCategories
: allCategories; : allCategories;
break; break;
case "astro": case 'astro':
case "astronomy": case 'astronomy':
categoryArray = ["ASTRONOMY"]; categoryArray = ['ASTRONOMY'];
break; break;
case "bio": case 'bio':
case "biology": case 'biology':
categoryArray = ["BIOLOGY"]; categoryArray = ['BIOLOGY'];
break; break;
case "ess": case 'ess':
case "earth science": case 'earth science':
case "es": case 'es':
categoryArray = ["EARTH SCIENCE"]; categoryArray = ['EARTH SCIENCE'];
break; break;
case "chem": case 'chem':
case "chemistry": case 'chemistry':
categoryArray = ["CHEMISTRY"]; categoryArray = ['CHEMISTRY'];
break; break;
case "phys": case 'phys':
case "physics": case 'physics':
categoryArray = ["PHYSICS"]; categoryArray = ['PHYSICS'];
break; break;
case "math": case 'math':
categoryArray = ["MATH"]; categoryArray = ['MATH'];
break; break;
case "energy": case 'energy':
categoryArray = ["ENERGY"]; categoryArray = ['ENERGY'];
break; break;
default: default:
interaction.followUp({ interaction.followUp({
embeds: [ embeds: [
new MessageEmbed() new MessageEmbed()
.setDescription("<:red_x:816791117671825409> Not a valid subject!") .setDescription('<:red_x:816791117671825409> Not a valid subject!')
.setColor("#ffffff"), .setColor('#ffffff'),
], ],
}); });
return; return;
} }
axios axios
.post("https://scibowldb.com/api/questions/random", { .post('https://scibowldb.com/api/questions/random', {
categories: categoryArray, categories: categoryArray,
}) })
.then((res) => { .then((res) => {
const questionData = res.data.question; const questionData = res.data.question;
const tossupQuestion = questionData.tossup_question; const tossupQuestion = questionData.tossup_question;
const tossupAnswer = questionData.tossup_answer; const tossupAnswer = questionData.tossup_answer;
const tossupFormat = questionData.tossup_format; const tossupFormat = questionData.tossup_format;
let answers = tossupAnswer.split(" (ACCEPT: "); let answers = tossupAnswer.split(' (ACCEPT: ');
if (answers.length > 1) { 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[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 answers = [answers[0], ...answers[1].split(new RegExp(' OR ', 'i'))]; // Use the first element plus the last element split by 'OR' case insensitive
} }
interaction interaction
.followUp({ content: decode(tossupQuestion), fetchReply: true }) .followUp({ content: decode(tossupQuestion), fetchReply: true })
.then((q) => { .then((q) => {
const questionMessage = q as Message; const questionMessage = q as Message;
const sourceButton = new MessageActionRow().addComponents( const sourceButton = new MessageActionRow().addComponents(
new MessageButton() new MessageButton()
.setURL(questionData.uri) .setURL(questionData.uri)
.setLabel("Source") .setLabel('Source')
.setStyle("LINK") .setStyle('LINK')
); );
switch (tossupFormat) { switch (tossupFormat) {
case "Short Answer": { case 'Short Answer': {
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const messageFilter = (m) => const messageFilter = (m) =>
m.author.id === interaction.user.id || m.author.id === interaction.user.id ||
m.author.id === interaction.client.user?.id; m.author.id === interaction.client.user?.id;
interaction.channel interaction.channel
?.awaitMessages({ ?.awaitMessages({
filter: messageFilter, filter: messageFilter,
max: 1, max: 1,
}) })
.then((collected) => { .then((collected) => {
const answerMsg = collected.first(); const answerMsg = collected.first();
if (answerMsg?.author.id === interaction.client.user?.id) if (answerMsg?.author.id === interaction.client.user?.id)
return; return;
let predicted = ""; let predicted = '';
if ( if (
answerMsg?.content.toLowerCase() === answerMsg?.content.toLowerCase() ===
tossupAnswer.toLowerCase() || tossupAnswer.toLowerCase() ||
answers.includes(answerMsg?.content.toUpperCase()) answers.includes(answerMsg?.content.toUpperCase())
) { ) {
predicted = "correct"; predicted = 'correct';
} else { } else {
predicted = "incorrect"; predicted = 'incorrect';
} }
if (predicted === "correct") { if (predicted === 'correct') {
updateScore(true, score, authorId).then((msgToReply) => updateScore(true, score, authorId).then((msgToReply) =>
answerMsg?.reply(msgToReply) answerMsg?.reply(msgToReply)
); );
} else { } else {
const overrideEmbed = new MessageEmbed() const overrideEmbed = new MessageEmbed()
.setAuthor({ .setAuthor({
name: answerMsg?.author.tag ? answerMsg.author.tag : "", name: answerMsg?.author.tag ? answerMsg.author.tag : '',
iconURL: answerMsg?.author.displayAvatarURL(), iconURL: answerMsg?.author.displayAvatarURL(),
}) })
.addField("Correct answer", `\`${tossupAnswer}\``) .addField('Correct answer', `\`${tossupAnswer}\``)
.setDescription( .setDescription(
"It seems your answer was incorrect. Please react with <:override:955265585086857236> to override your answer if you think you got it right." 'It seems your answer was incorrect. Please react with <:override:955265585086857236> to override your answer if you think you got it right.'
) )
.setColor("#ffffff") .setColor('#ffffff')
.setTimestamp(); .setTimestamp();
const overrideButton = new MessageActionRow().addComponents( const overrideButton = new MessageActionRow().addComponents(
new MessageButton() new MessageButton()
.setCustomId("override") .setCustomId('override')
.setEmoji("<:override:955265585086857236>") .setEmoji('<:override:955265585086857236>')
.setStyle("SECONDARY") .setStyle('SECONDARY')
); );
answerMsg?.channel answerMsg?.channel
.send({ .send({
embeds: [overrideEmbed], embeds: [overrideEmbed],
components: [overrideButton], components: [overrideButton],
}) })
.then((overrideMsg) => { .then((overrideMsg) => {
const overrideFilter = (i) => { const overrideFilter = (i) => {
return ( return (
["override"].includes(i.customId) && ['override'].includes(i.customId) &&
i.user.id === answerMsg.author.id i.user.id === answerMsg.author.id
); );
}; };
overrideMsg overrideMsg
.awaitMessageComponent({ .awaitMessageComponent({
filter: overrideFilter, filter: overrideFilter,
}) })
.then((i) => { .then((i) => {
updateScore(true, score, authorId).then( updateScore(true, score, authorId).then(
async (msgToReply) => { async (msgToReply) => {
await i.reply(msgToReply); await i.reply(msgToReply);
overrideMsg.edit({ components: [] }); overrideMsg.edit({ components: [] });
} }
); );
}) })
.catch((err) => .catch((err) =>
log({ log({
logger: "train", logger: 'train',
content: `Failed to override score: ${err}`, content: `Failed to override score: ${err}`,
level: "error", level: 'error',
}) })
); );
}) })
.catch((err) => .catch((err) =>
log({ log({
logger: "train", logger: 'train',
content: `Failed to send override message: ${err}`, content: `Failed to send override message: ${err}`,
level: "error", level: 'error',
}) })
); );
} }
interaction.editReply({ components: [sourceButton] }); interaction.editReply({ components: [sourceButton] });
}) })
.catch((err) => .catch((err) =>
log({ logger: "train", content: `${err}`, level: "error" }) log({ logger: 'train', content: `${err}`, level: 'error' })
); );
break; break;
} }
case "Multiple Choice": { case 'Multiple Choice': {
const choices = new MessageActionRow().addComponents( const choices = new MessageActionRow().addComponents(
new MessageButton() new MessageButton()
.setCustomId("w") .setCustomId('w')
.setLabel("W") .setLabel('W')
.setStyle("SECONDARY"), .setStyle('SECONDARY'),
new MessageButton() new MessageButton()
.setCustomId("x") .setCustomId('x')
.setLabel("X") .setLabel('X')
.setStyle("SECONDARY"), .setStyle('SECONDARY'),
new MessageButton() new MessageButton()
.setCustomId("y") .setCustomId('y')
.setLabel("Y") .setLabel('Y')
.setStyle("SECONDARY"), .setStyle('SECONDARY'),
new MessageButton() new MessageButton()
.setCustomId("z") .setCustomId('z')
.setLabel("Z") .setLabel('Z')
.setStyle("SECONDARY") .setStyle('SECONDARY')
); );
interaction.editReply({ components: [choices] }); interaction.editReply({ components: [choices] });
const mcFilter = (i) => const mcFilter = (i) =>
["w", "x", "y", "z"].includes(i.customId) && ['w', 'x', 'y', 'z'].includes(i.customId) &&
i.user.id === interaction.user.id; i.user.id === interaction.user.id;
questionMessage questionMessage
.awaitMessageComponent({ filter: mcFilter }) .awaitMessageComponent({ filter: mcFilter })
.then((mcChoice) => { .then((mcChoice) => {
if ( if (
tossupAnswer.charAt(0).toLowerCase() === mcChoice.customId tossupAnswer.charAt(0).toLowerCase() === mcChoice.customId
) { ) {
updateScore(true, score, authorId).then((msgToReply) => updateScore(true, score, authorId).then((msgToReply) =>
mcChoice.reply(msgToReply) mcChoice.reply(msgToReply)
); );
} else { } else {
const incorrectEmbed = new MessageEmbed() const incorrectEmbed = new MessageEmbed()
.setAuthor({ .setAuthor({
name: interaction.user.tag, name: interaction.user.tag,
iconURL: interaction.user.displayAvatarURL(), iconURL: interaction.user.displayAvatarURL(),
}) })
.addField("Correct answer", `\`${tossupAnswer}\``) .addField('Correct answer', `\`${tossupAnswer}\``)
.setDescription( .setDescription(
`It seems your answer ${mcChoice.customId.toUpperCase()} was incorrect.` `It seems your answer ${mcChoice.customId.toUpperCase()} was incorrect.`
) )
.setColor("#ffffff") .setColor('#ffffff')
.setTimestamp(); .setTimestamp();
mcChoice.reply({ embeds: [incorrectEmbed] }); mcChoice.reply({ embeds: [incorrectEmbed] });
} }
interaction.editReply({ components: [sourceButton] }); interaction.editReply({ components: [sourceButton] });
}); });
break; break;
} }
} }
}) })
.catch((err) => .catch((err) =>
log({ logger: "train", content: `${err}`, level: "error" }) log({ logger: 'train', content: `${err}`, level: 'error' })
); );
}) })
.catch((err) => .catch((err) =>
log({ logger: "train", content: `${err}`, level: "error" }) log({ logger: 'train', content: `${err}`, level: 'error' })
); );
} }

38
src/events/interactionCreate.ts

@ -1,29 +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 const once = false;
export async function execute(interaction) { 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 { try {
await command.execute(interaction); await command.execute(interaction);
} catch (error) { } catch (error) {
log({ log({
logger: "interaction", logger: 'interaction',
content: `Interaction ${interaction.commandName} failed!`, content: `Interaction ${interaction.commandName} failed!`,
level: "error", level: 'error',
}); });
await interaction.followUp({ await interaction.followUp({
content: "There was an error while executing this command!", content: 'There was an error while executing this command!',
ephemeral: true, ephemeral: true,
}); });
} }
} }

56
src/events/messageCreate.ts

@ -1,36 +1,36 @@
import axios from "axios"; import axios from 'axios';
import { MessageEmbed } from "discord.js"; import { MessageEmbed } from 'discord.js';
import { decode } from "html-entities"; 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 const once = false;
export async function execute(message) { 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")) { if (message.content.startsWith('!q')) {
const questionId = message.content.split(" ")[1]; const questionId = message.content.split(' ')[1];
axios axios
.get(`https://scibowldb.com/api/questions/${questionId}`) .get(`https://scibowldb.com/api/questions/${questionId}`)
.then((res) => { .then((res) => {
const data = res.data.question; const data = res.data.question;
const tossupQuestion = data.tossup_question; const tossupQuestion = data.tossup_question;
const tossupAnswer = data.tossup_answer; const tossupAnswer = data.tossup_answer;
let answers = tossupAnswer.split(" (ACCEPT: "); let answers = tossupAnswer.split(' (ACCEPT: ');
if (answers.length > 1) { 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[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 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() const dataEmbed = new MessageEmbed()
.setTitle("Data") .setTitle('Data')
.setDescription(`\`\`\`json\n${JSON.stringify(data, null, 2)}\`\`\``); .setDescription(`\`\`\`json\n${JSON.stringify(data, null, 2)}\`\`\``);
message.reply({ message.reply({
content: decode(tossupQuestion) + `\n\nAnswers: [${answers}]`, content: decode(tossupQuestion) + `\n\nAnswers: [${answers}]`,
embeds: [dataEmbed], embeds: [dataEmbed],
}); });
}); });
} }
} }

22
src/events/ready.ts

@ -1,17 +1,17 @@
import * as db from "../helpers/db"; import * as db from '../helpers/db';
import { mongoUri } from "../helpers/env"; import { mongoUri } from '../helpers/env';
import log from "../helpers/log"; import log from '../helpers/log';
export const name = "ready"; export const name = 'ready';
export const once = true; export const once = true;
export async function execute(client) { export async function execute(client) {
await db.connect(mongoUri); await db.connect(mongoUri);
log({ log({
logger: "status", logger: 'status',
content: `Logged in as ${client.user.tag}!`, content: `Logged in as ${client.user.tag}!`,
level: "info", level: 'info',
}); });
client.user.setActivity("for /help", { type: "WATCHING" }); client.user.setActivity('for /help', { type: 'WATCHING' });
} }

116
src/helpers/db.ts

@ -1,67 +1,67 @@
import mongoose from "mongoose"; import mongoose from 'mongoose';
import log from "../helpers/log"; import log from '../helpers/log';
import userScore from "../models/userScore"; import userScore from '../models/userScore';
export async function updateScore( export async function updateScore(
isCorrect: boolean, isCorrect: boolean,
score: number, score: number,
authorId: string authorId: string
) { ) {
if (!isCorrect) { if (!isCorrect) {
return `Nice try! Your score is still ${score}.`; return `Nice try! Your score is still ${score}.`;
} else { } else {
// TODO: Error handling // TODO: Error handling
const doc = await userScore.findOne({ const doc = await userScore.findOne({
authorID: authorId, authorID: authorId,
}); });
if (!doc) { if (!doc) {
const newUserScore = new userScore({ const newUserScore = new userScore({
authorID: authorId, authorID: authorId,
score: score + 4, score: score + 4,
}); });
newUserScore.save((err) => { newUserScore.save((err) => {
if (err) { if (err) {
log({ log({
logger: "db", logger: 'db',
content: `Error creating new user ${authorId} for scoring`, content: `Error creating new user ${authorId} for scoring`,
level: "error", level: 'error',
}); });
} else { } else {
log({ log({
logger: "db", logger: 'db',
content: `Successfully created user ${authorId} for scoring`, content: `Successfully created user ${authorId} for scoring`,
level: "debug", level: 'debug',
}); });
} }
}); });
} else { } else {
doc.score = doc.score + 4; doc.score = doc.score + 4;
doc.save(); doc.save();
} }
return `Great job! Your score is now ${score + 4}.`; return `Great job! Your score is now ${score + 4}.`;
} }
} }
export async function connect(mongoUri) { export async function connect(mongoUri) {
mongoose mongoose
.connect(mongoUri, { .connect(mongoUri, {
useUnifiedTopology: true, useUnifiedTopology: true,
useNewUrlParser: true, useNewUrlParser: true,
}) })
.then(() => .then(() =>
log({ log({
logger: "db", logger: 'db',
content: `Connected to the database at ${mongoUri}!`, content: `Connected to the database at ${mongoUri}!`,
level: "info", level: 'info',
}) })
) )
.catch((err) => .catch((err) =>
log({ log({
logger: "db", logger: 'db',
content: `Failed to connect to the database at ${mongoUri}: ${err}`, content: `Failed to connect to the database at ${mongoUri}: ${err}`,
level: "fatal", level: 'fatal',
}) })
); );
} }

10
src/helpers/env.ts

@ -1,7 +1,7 @@
import "dotenv/config"; import 'dotenv/config';
export const clientId = process.env.CLIENT_ID || ""; export const clientId = process.env.CLIENT_ID || '';
export const testingGuild = process.env.TESTING_GUILD || ""; export const testingGuild = process.env.TESTING_GUILD || '';
export const token = process.env.TOKEN || ""; export const token = process.env.TOKEN || '';
export const mongoUri = (process.env.MONGO_URI = export const mongoUri = (process.env.MONGO_URI =
"mongodb://mongo:27017/AWESOME"); 'mongodb://mongo:27017/AWESOME');

52
src/helpers/log.ts

@ -1,29 +1,29 @@
import log4js from "log4js"; import log4js from 'log4js';
export default function (config) { export default function (config) {
const logger = log4js.getLogger(config.logger); const logger = log4js.getLogger(config.logger);
logger.level = "debug"; logger.level = 'debug';
switch (config.level) { switch (config.level) {
case "trace": case 'trace':
logger.trace(config.content); logger.trace(config.content);
break; break;
case "debug": case 'debug':
logger.debug(config.content); logger.debug(config.content);
break; break;
case "info": case 'info':
logger.info(config.content); logger.info(config.content);
break; break;
case "warn": case 'warn':
logger.warn(config.content); logger.warn(config.content);
break; break;
case "error": case 'error':
logger.error(config.content); logger.error(config.content);
break; break;
case "fatal": case 'fatal':
logger.fatal(config.content); logger.fatal(config.content);
break; break;
default: default:
logger.debug(config.content); logger.debug(config.content);
break; break;
} }
} }

192
src/helpers/util/pagination.ts

@ -1,113 +1,113 @@
import { import {
CommandInteraction, CommandInteraction,
Message, Message,
MessageActionRow, MessageActionRow,
MessageButton, MessageButton,
MessageEmbed, MessageEmbed,
} from "discord.js"; } from 'discord.js';
export async function paginateMessage( export async function paginateMessage(
message: Message, message: Message,
embeds: MessageEmbed[] embeds: MessageEmbed[]
) { ) {
let index = 0; let index = 0;
const row = new MessageActionRow(); const row = new MessageActionRow();
row.addComponents( row.addComponents(
new MessageButton() new MessageButton()
.setCustomId("paginator-left") .setCustomId('paginator-left')
.setEmoji("868552005977788466") .setEmoji('868552005977788466')
.setStyle("SECONDARY"), .setStyle('SECONDARY'),
new MessageButton() new MessageButton()
.setCustomId("paginator-right") .setCustomId('paginator-right')
.setEmoji("868551772887711754") .setEmoji('868551772887711754')
.setStyle("SECONDARY") .setStyle('SECONDARY')
); );
await message await message
.reply({ .reply({
content: `Page 1 of ${embeds.length}:`, content: `Page 1 of ${embeds.length}:`,
embeds: [embeds[index]], embeds: [embeds[index]],
components: [row], components: [row],
}) })
.then(async (paginatorMessage) => { .then(async (paginatorMessage) => {
const filter = (m) => m.author.id === message.author.id; const filter = (m) => m.author.id === message.author.id;
const paginatorCollector = const paginatorCollector =
paginatorMessage.createMessageComponentCollector({ paginatorMessage.createMessageComponentCollector({
componentType: "BUTTON", componentType: 'BUTTON',
filter: filter, filter: filter,
}); });
paginatorCollector.on("collect", async (i) => { paginatorCollector.on('collect', async (i) => {
switch (i.customId) { switch (i.customId) {
case "paginator-left": case 'paginator-left':
index--; index--;
if (index < 0) index = embeds.length - 1; if (index < 0) index = embeds.length - 1;
break; break;
case "paginator-right": case 'paginator-right':
index++; index++;
if (index > embeds.length - 1) index = 0; if (index > embeds.length - 1) index = 0;
break; break;
} }
paginatorMessage.edit({ paginatorMessage.edit({
content: `Page ${index + 1} of ${embeds.length}:`, content: `Page ${index + 1} of ${embeds.length}:`,
embeds: [embeds[index]], embeds: [embeds[index]],
}); });
}); });
}); });
} }
export async function paginateInteraction( export async function paginateInteraction(
interaction: CommandInteraction, interaction: CommandInteraction,
embeds: MessageEmbed[] embeds: MessageEmbed[]
) { ) {
let index = 0; let index = 0;
const row = new MessageActionRow(); const row = new MessageActionRow();
row.addComponents( row.addComponents(
new MessageButton() new MessageButton()
.setCustomId("paginator-left") .setCustomId('paginator-left')
.setEmoji("868552005977788466") .setEmoji('868552005977788466')
.setStyle("SECONDARY"), .setStyle('SECONDARY'),
new MessageButton() new MessageButton()
.setCustomId("paginator-right") .setCustomId('paginator-right')
.setEmoji("868551772887711754") .setEmoji('868551772887711754')
.setStyle("SECONDARY") .setStyle('SECONDARY')
); );
await interaction await interaction
.followUp({ .followUp({
content: `Page 1 of ${embeds.length}:`, content: `Page 1 of ${embeds.length}:`,
embeds: [embeds[index]], embeds: [embeds[index]],
components: [row], components: [row],
fetchReply: true, fetchReply: true,
}) })
.then(async (p) => { .then(async (p) => {
const paginatorMessage = p as Message; const paginatorMessage = p as Message;
const filter = (i) => i.user.id === interaction.user.id; const filter = (i) => i.user.id === interaction.user.id;
const paginatorCollector = const paginatorCollector =
paginatorMessage.createMessageComponentCollector({ paginatorMessage.createMessageComponentCollector({
componentType: "BUTTON", componentType: 'BUTTON',
filter: filter, filter: filter,
}); });
paginatorCollector.on("collect", async (i) => { paginatorCollector.on('collect', async (i) => {
switch (i.customId) { switch (i.customId) {
case "paginator-left": case 'paginator-left':
index--; index--;
if (index < 0) index = embeds.length - 1; if (index < 0) index = embeds.length - 1;
break; break;
case "paginator-right": case 'paginator-right':
index++; index++;
if (index > embeds.length - 1) index = 0; if (index > embeds.length - 1) index = 0;
break; break;
} }
await i.update({ await i.update({
content: `Page ${index + 1} of ${embeds.length}:`, content: `Page ${index + 1} of ${embeds.length}:`,
embeds: [embeds[index]], embeds: [embeds[index]],
}); });
}); });
}); });
} }

74
src/index.ts

@ -1,54 +1,54 @@
#!/usr/bin/env node #!/usr/bin/env node
import fs from "node:fs"; import fs from 'node:fs';
import { Client, Collection, Intents } from "discord.js"; import { Client, Collection, Intents } from 'discord.js';
import { token } from "./helpers/env"; import { token } from './helpers/env';
import log from "./helpers/log"; import log from './helpers/log';
const client = new Client({ const client = new Client({
intents: [ intents: [
Intents.FLAGS.GUILDS, Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MESSAGE_REACTIONS, Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_MEMBERS,
Intents.FLAGS.DIRECT_MESSAGES, Intents.FLAGS.DIRECT_MESSAGES,
Intents.FLAGS.DIRECT_MESSAGE_REACTIONS, Intents.FLAGS.DIRECT_MESSAGE_REACTIONS,
], ],
}); });
client["commands"] = new Collection(); client['commands'] = new Collection();
const commandFiles = fs const commandFiles = fs
.readdirSync(`${__dirname}/commands`) .readdirSync(`${__dirname}/commands`)
.filter((file) => file.endsWith(".js")); .filter((file) => file.endsWith('.js'));
const eventFiles = fs const eventFiles = fs
.readdirSync(`${__dirname}/events`) .readdirSync(`${__dirname}/events`)
.filter((file) => file.endsWith(".js")); .filter((file) => file.endsWith('.js'));
for (const file of commandFiles) { for (const file of commandFiles) {
import(`${__dirname}/commands/${file}`).then((command) => { import(`${__dirname}/commands/${file}`).then((command) => {
client["commands"].set(command.data.name, command); client['commands'].set(command.data.name, command);
log({ log({
logger: "command", logger: 'command',
content: `Registered command ${file}!`, content: `Registered command ${file}!`,
level: "info", level: 'info',
}); });
}); });
} }
for (const file of eventFiles) { for (const file of eventFiles) {
import(`${__dirname}/events/${file}`).then((event) => { import(`${__dirname}/events/${file}`).then((event) => {
if (event.once) { if (event.once) {
client.once(event.name, (...args) => event.execute(...args)); client.once(event.name, (...args) => event.execute(...args));
} else { } else {
client.on(event.name, (...args) => event.execute(...args)); client.on(event.name, (...args) => event.execute(...args));
} }
log({ log({
logger: "event", logger: 'event',
content: `Registered event ${file}!`, content: `Registered event ${file}!`,
level: "info", level: 'info',
}); });
}); });
} }
client.login(token); client.login(token);

36
src/models/generatedRound.ts

@ -1,22 +1,22 @@
import mongoose from "mongoose"; import mongoose from 'mongoose';
const generatedRoundSchema = new mongoose.Schema({ const generatedRoundSchema = new mongoose.Schema({
htmlContent: { htmlContent: {
type: String, type: String,
required: true, required: true,
}, },
requestedBy: { requestedBy: {
type: String, type: String,
required: true, required: true,
}, },
authorTag: { authorTag: {
type: String, type: String,
required: true, required: true,
}, },
timestamp: { timestamp: {
type: String, type: String,
required: true, required: true,
}, },
}); });
export default mongoose.model("GeneratedRound", generatedRoundSchema); export default mongoose.model('GeneratedRound', generatedRoundSchema);

26
src/models/userConfig.ts

@ -1,17 +1,17 @@
import mongoose from "mongoose"; import mongoose from 'mongoose';
const userConfigSchema = new mongoose.Schema({ const userConfigSchema = new mongoose.Schema({
_id: String, _id: String,
subjects: { subjects: {
type: [String], type: [String],
required: true, required: true,
default: null, default: null,
}, },
gradeLevels: { gradeLevels: {
type: [String], type: [String],
required: true, required: true,
default: null, default: null,
}, },
}); });
export default mongoose.model("UserConfig", userConfigSchema); export default mongoose.model('UserConfig', userConfigSchema);

20
src/models/userScore.ts

@ -1,14 +1,14 @@
import mongoose from "mongoose"; import mongoose from 'mongoose';
const userScoreSchema = new mongoose.Schema({ const userScoreSchema = new mongoose.Schema({
authorID: { authorID: {
type: String, type: String,
required: true, required: true,
}, },
score: { score: {
type: Number, type: Number,
required: true, required: true,
}, },
}); });
export default mongoose.model("UserScore", userScoreSchema); export default mongoose.model('UserScore', userScoreSchema);

Loading…
Cancel
Save