Browse Source

style: format with prettier

dependabot/npm_and_yarn/semver-5.7.2
Abheek Dhawan 2 years ago
parent
commit
bd7ffe8e9d
Signed by: abheekd GPG Key ID: 7BE81B8C14475B67
  1. 130
      src/commands/about.ts
  2. 34
      src/commands/help.ts
  3. 303
      src/commands/rounds.ts
  4. 90
      src/commands/score.ts
  5. 508
      src/commands/settings.ts
  6. 138
      src/commands/top.ts
  7. 581
      src/commands/train.ts
  8. 23
      src/deploy-commands.js
  9. 32
      src/events/interactionCreate.ts
  10. 58
      src/events/messageCreate.ts
  11. 21
      src/events/ready.ts
  12. 118
      src/helpers/db.ts
  13. 11
      src/helpers/env.ts
  14. 56
      src/helpers/log.ts
  15. 178
      src/helpers/util/pagination.ts
  16. 62
      src/index.ts
  17. 36
      src/models/generatedRound.ts
  18. 26
      src/models/userConfig.ts
  19. 20
      src/models/userScore.ts

130
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);
}

34
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] });
}
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] });
}

303
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 = '<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 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 = '<br><br><h3 style=\'text-align: center;\'><strong>TOSS-UP</strong></h3>\n<br>' + `${i}) <strong>${question_category}</strong>` + ' ' + `<em>${tossup_format}</em>` + ' ' + tossup_question + '<br><br>' + '<strong>ANSWER:</strong> ' + tossup_answer + '<br>';
htmlContent += '<br><br><h3 style=\'text-align: center;\'><strong>BONUS</strong></h3>\n<br>' + `${i}) <strong>${question_category}</strong>` + ' ' + `<em>${bonus_format}</em>` + ' ' + bonus_question + '<br><br>' + '<strong>ANSWER:</strong> ' + bonus_answer + '<br><br><hr><br>';
htmlContent = htmlContent.replace(/\n/g, '<br>');
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;
}
}
}
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 =
"<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 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 =
"<br><br><h3 style='text-align: center;'><strong>TOSS-UP</strong></h3>\n<br>" +
`${i}) <strong>${question_category}</strong>` +
" " +
`<em>${tossup_format}</em>` +
" " +
tossup_question +
"<br><br>" +
"<strong>ANSWER:</strong> " +
tossup_answer +
"<br>";
htmlContent +=
"<br><br><h3 style='text-align: center;'><strong>BONUS</strong></h3>\n<br>" +
`${i}) <strong>${question_category}</strong>` +
" " +
`<em>${bonus_format}</em>` +
" " +
bonus_question +
"<br><br>" +
"<strong>ANSWER:</strong> " +
bonus_answer +
"<br><br><hr><br>";
htmlContent = htmlContent.replace(/\n/g, "<br>");
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;
}
}
}

90
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] });
});
}

508
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<string>();
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<string>();
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<string>();
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<string>();
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;
}
}
}

138
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 });
});
}

581
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" })
);
}

23
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);

32
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,
});
}
}

58
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],
});
});
}
}
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],
});
});
}
}

21
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" });
}

118
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",
})
);
}

11
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");

56
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;
}
}
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;
}
}

178
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]] });
});
});
}
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]],
});
});
});
}

62
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);

36
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);

26
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);

20
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);

Loading…
Cancel
Save