diff --git a/.gitignore b/.gitignore
index 86a0176..d989dc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ config.json
.DS_Store
data/
*.patch
+built/
diff --git a/commands/about.js b/commands/about.js
deleted file mode 100644
index dfede13..0000000
--- a/commands/about.js
+++ /dev/null
@@ -1,78 +0,0 @@
-const { SlashCommandBuilder } = require('@discordjs/builders');
-const { MessageEmbed } = require('discord.js');
-
-const gitlog = require('gitlog').default;
-
-const userScore = require('../models/userScore');
-
-module.exports = {
- data: new SlashCommandBuilder()
- .setName('about')
- .setDescription('Commands regarding the creation/development of the bot')
- .addSubcommand(subcommand => {
- subcommand
- .setName('contributors')
- .setDescription('Lists contributors to the AwesomeSciBo bot');
- return subcommand;
- })
- .addSubcommand(subcommand => {
- subcommand
- .setName('changelog')
- .setDescription('Lists the 5 most recent changes in a "git log" type format');
- return subcommand;
- })
- .addSubcommand(subcommand => {
- subcommand
- .setName('bot')
- .setDescription('Lists information about AwesomeSciBo');
- return subcommand;
- }),
- async execute(interaction) {
- await interaction.deferReply();
-
- const client = interaction.client;
- const action = interaction.options.getSubcommand();
- if (action === 'contributors') {
- 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');
-
- interaction.followUp({ embeds: [contributorEmbed] });
- }
- else if (action === 'changelog') {
- 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`);
- });
-
- interaction.followUp({ embeds: [changelogEmbed] });
- }
- else if (action === 'bot') {
- 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();
-
- interaction.followUp({ embeds: [aboutBotEmbed] });
- }
- },
-};
diff --git a/commands/help.js b/commands/help.js
deleted file mode 100644
index 85c86ae..0000000
--- a/commands/help.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const { SlashCommandBuilder } = require('@discordjs/builders');
-const { MessageEmbed } = require('discord.js');
-
-module.exports = {
- data: new SlashCommandBuilder()
- .setName('help')
- .setDescription('Replies with a help message explaining what the bot can do'),
- async execute(interaction) {
- await interaction.deferReply();
-
- const helpEmbed = new MessageEmbed()
- .setDescription('AwesomeSciBo has migrated to using slash commands! You can take a look at the different commands by typing `/` and clicking on the AwesomeSciBo icon.')
- .setColor('#ffffff');
- interaction.followUp({ embeds: [helpEmbed] });
- },
-};
diff --git a/commands/rounds.js b/commands/rounds.js
deleted file mode 100644
index ba7e08b..0000000
--- a/commands/rounds.js
+++ /dev/null
@@ -1,113 +0,0 @@
-const { SlashCommandBuilder } = require('@discordjs/builders');
-const { MessageEmbed } = require('discord.js');
-
-const axios = require('axios');
-
-const { log } = require('../helpers/log');
-const generatedRound = require('../models/generateRound');
-
-module.exports = {
- 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;
- }),
- async execute(interaction) {
- await interaction.deferReply();
-
- const action = interaction.options.getSubcommand();
- if (action === 'generate') {
- let i;
- let finalizedHTML = '
ROUND GENERATED BY AWESOMESCIBO USING THE SCIBOWLDB API
';
- let tossup_question;
- let question_category;
- let tossup_format;
- let tossup_answer;
- let bonus_question;
- let bonus_format;
- let bonus_answer;
- let htmlContent = '';
- await axios.post('https://scibowldb.com/api/questions', { categories: ['BIOLOGY', 'PHYSICS', 'CHEMISTRY', 'EARTH AND SPACE', 'ASTRONOMY', 'MATH'] })
- .then((response) => {
- for (i = 1; i < 26; i++) {
- const data = response.data.questions[Math.floor(Math.random() * response.data.questions.length)];
- tossup_question = data.tossup_question;
- tossup_answer = data.tossup_answer;
- question_category = data.category;
- tossup_format = data.tossup_format;
- bonus_question = data.bonus_question;
- bonus_answer = data.bonus_answer;
- bonus_format = data.bonus_format;
- htmlContent = '
TOSS-UP
\n
' + `${i}) ${question_category}` + ' ' + `${tossup_format}` + ' ' + tossup_question + '
' + 'ANSWER: ' + tossup_answer + '
';
- htmlContent += '
BONUS
\n
' + `${i}) ${question_category}` + ' ' + `${bonus_format}` + ' ' + bonus_question + '
' + 'ANSWER: ' + bonus_answer + '
';
- htmlContent = htmlContent.replace(/\n/g, '
');
- finalizedHTML += htmlContent;
- }
-
- const newGeneratedRound = new generatedRound({
- htmlContent: finalizedHTML,
- requestedBy: interaction.user.id,
- authorTag: interaction.user.tag,
- timestamp: new Date().toISOString(),
- });
-
- newGeneratedRound.save((err, round) => {
- if (err) {
- log({ logger: 'rounds', content: `Saving round to DB failed: ${err}`, level: 'error' });
- return;
- }
- interaction.followUp(`Here's your round: https://api.adawesome.tech/round/${round._id.toString()}`, { ephemeral: true });
- });
- });
- }
- else if (action === 'list') {
- let roundsList = await generatedRound.find({ requestedBy: interaction.user.id }).sort({ timestamp: -1 });
- let finalMessage = '';
- if (!roundsList) {
- interaction.followUp('You haven\'t requested any roundsList!');
- 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 roundsList requested by ${interaction.user.tag}`)
- .setDescription(finalMessage)
- .setTimestamp();
-
- interaction.followUp({
- embeds: [roundsListEmbed],
- ephemeral: true,
- });
- }
- else if (action === 'hit') {
- const totalCount = await generatedRound.countDocuments({});
- const userCount = await generatedRound.countDocuments({ requestedBy: interaction.user.id });
-
- interaction.followUp(`Total Hits: ${totalCount}\nYour Hits: ${userCount}`);
- }
- },
-};
diff --git a/commands/top.js b/commands/top.js
deleted file mode 100644
index cd0ada7..0000000
--- a/commands/top.js
+++ /dev/null
@@ -1,40 +0,0 @@
-const { SlashCommandBuilder } = require('@discordjs/builders');
-const { MessageEmbed } = require('discord.js');
-
-const { log } = require('../helpers/log');
-const userScore = require('../models/userScore');
-
-module.exports = {
- data: new SlashCommandBuilder()
- .setName('top')
- .setDescription('Lists top ten scores across servers (server specific leaderboard WIP)'),
- async execute(interaction) {
- await interaction.deferReply();
-
- let messageContent = '';
- userScore
- .find({})
- .sort({ score: -1 }) // Sort by descending order
- .exec((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!`,
- );
- }
- for (let i = 0; i < 10; i++) {
- messageContent += `${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(messageContent)
- .setColor('#ffffff');
-
- interaction.followUp({ embeds: [leaderboardEmbed] });
- });
- },
-};
diff --git a/commands/train.js b/commands/train.js
deleted file mode 100644
index f93eae5..0000000
--- a/commands/train.js
+++ /dev/null
@@ -1,172 +0,0 @@
-const { SlashCommandBuilder } = require('@discordjs/builders');
-const { MessageEmbed } = require('discord.js');
-
-const decode = require('html-entities').decode;
-const axios = require('axios');
-
-const userScore = require('../models/userScore');
-
-const { log } = require('../helpers/log.js');
-const { updateScore } = require('../helpers/db.js');
-
-module.exports = {
- 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)
- .addChoice('astro', 'astro')
- .addChoice('bio', 'bio')
- .addChoice('ess', 'ess')
- .addChoice('chem', 'chem')
- .addChoice('phys', 'phys')
- .addChoice('math', 'math')
- .addChoice('energy', 'energy')
- .setRequired(false);
- return option;
- }),
- async execute(interaction) {
- await interaction.deferReply();
-
- const subject = interaction.options.get('subject') ? interaction.options.get('subject').value : null;
- const authorId = interaction.user.id;
- let score;
- userScore
- .findOne({ authorID: authorId })
- .lean()
- .then((obj, err) => {
- if (!obj) {
- score = 0;
- }
- else if (obj) {
- score = obj.score;
- }
- else {
- log({ logger: 'train', content: `Getting user score failed: ${err}`, level: 'error' });
- }
- });
-
- let categoryArray = [];
-
- switch (subject) {
- case null:
- categoryArray = ['BIOLOGY', 'PHYSICS', 'CHEMISTRY', 'EARTH AND SPACE', 'ASTRONOMY', 'MATH'];
- 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(
- 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 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
- }
- interaction.followUp({ content: decode(tossupQuestion) + `\n\n||Source: ${data.uri}||` })
- .then(() => {
- 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 = null;
- if (data.tossup_format === 'Multiple Choice') {
- if (answerMsg.content.charAt(0).toLowerCase() === tossupAnswer.charAt(0).toLowerCase()) {
- predicted = 'correct';
- }
- else {
- predicted = 'incorrect';
- }
- }
- else 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, 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();
- answerMsg.channel.send({
- embeds: [overrideEmbed],
- })
- .then(overrideMsg => {
- overrideMsg.react('<:override:955265585086857236>');
- const filter = (reaction, user) => {
- return (
- ['override'].includes(reaction.emoji.name) &&
- user.id === answerMsg.author.id
- );
- };
- overrideMsg
- .awaitReactions({
- filter: filter,
- max: 1,
- })
- .then(() => {
- updateScore(true, score, authorId).then((msgToReply) =>
- answerMsg.reply(msgToReply),
- );
- }).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' }));
- }
- }).catch(err => log({ logger: 'train', content: `${err}`, level: 'error' }));
- }).catch(err => log({ logger: 'train', content: `${err}`, level: 'error' }));
- }).catch(err => log({ logger: 'train', content: `${err}`, level: 'error' }));
- },
-};
diff --git a/events/interactionCreate.js b/events/interactionCreate.js
deleted file mode 100644
index 750fad0..0000000
--- a/events/interactionCreate.js
+++ /dev/null
@@ -1,21 +0,0 @@
-const { log } = require('../helpers/log');
-
-module.exports = {
- name: 'interactionCreate',
- once: false,
- async execute(interaction) {
- const client = interaction.client;
- if (!interaction.isCommand()) return;
-
- const command = client.commands.get(interaction.commandName);
- if (!command) return;
-
- try {
- await command.execute(interaction);
- }
- catch (error) {
- log({ logger: 'interaction', content: `Interaction ${interaction.name} failed!`, level: 'error' });
- await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
- }
- },
-};
diff --git a/events/messageCreate.js b/events/messageCreate.js
deleted file mode 100644
index fe0232d..0000000
--- a/events/messageCreate.js
+++ /dev/null
@@ -1,35 +0,0 @@
-const axios = require('axios');
-const { MessageEmbed } = require('discord.js');
-const decode = require('html-entities').decode;
-
-const { testingGuild } = require('../helpers/env');
-module.exports = {
- name: 'messageCreate',
- once: false,
- async execute(message) {
- 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],
- });
- });
- }
- },
-};
diff --git a/events/ready.js b/events/ready.js
deleted file mode 100644
index c9ecabe..0000000
--- a/events/ready.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const db = require('../helpers/db');
-const { mongoUri } = require('../helpers/env');
-const { log } = require('../helpers/log');
-
-module.exports = {
- name: 'ready',
- once: true,
- async 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' },
- );
- },
-};
diff --git a/helpers/db.js b/helpers/db.js
deleted file mode 100644
index e999dd4..0000000
--- a/helpers/db.js
+++ /dev/null
@@ -1,45 +0,0 @@
-const mongoose = require('mongoose');
-
-const { log } = require('../helpers/log.js');
-const userScore = require('../models/userScore');
-
-module.exports = {
- async updateScore(isCorrect, score, authorId) {
- if (!isCorrect) {
- return `Nice try! Your score is still ${score}.`;
- }
- else {
- score += 4;
- if (score == 4) {
- const newUserScore = new userScore({
- authorID: authorId,
- score: score,
- });
- newUserScore.save((err) =>
- err
- ? console.log('Error creating new user for scoring')
- : console.log('Sucessfully created user to score.'),
- );
- }
- else {
- // TODO: Error handling
- const doc = await userScore.findOne({
- authorID: authorId,
- });
- doc.score = doc.score + 4;
- doc.save();
- }
-
- return `Great job! Your score is now ${score}.`;
- }
- },
- async connect(mongoUri) {
- mongoose
- .connect(mongoUri, {
- useUnifiedTopology: true,
- useNewUrlParser: true,
- })
- .then(() => log({ logger: 'db', content: `Connected to the database at ${mongoUri}!`, level: 'info' }))
- .catch(err => log({ logger: 'db', content: `Failed to connect to the database at ${mongoUri}: ${err}`, level: 'fatal' }));
- },
-};
diff --git a/helpers/env.js b/helpers/env.js
deleted file mode 100644
index 69f0af6..0000000
--- a/helpers/env.js
+++ /dev/null
@@ -1,8 +0,0 @@
-require('dotenv').config();
-
-module.exports = {
- clientId: process.env.CLIENT_ID,
- testingGuild: process.env.TESTING_GUILD,
- token: process.env.TOKEN,
- mongoUri: process.env.MONGO_URI,
-};
diff --git a/helpers/log.js b/helpers/log.js
deleted file mode 100644
index eeac9f1..0000000
--- a/helpers/log.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const log4js = require('log4js');
-
-module.exports = {
- async log(config) {
- const logger = log4js.getLogger(config.logger);
- logger.level = 'debug';
- switch (config.level) {
- case 'trace':
- logger.trace(config.content);
- break;
- case 'debug':
- logger.debug(config.content);
- break;
- case 'info':
- logger.info(config.content);
- break;
- case 'warn':
- logger.warn(config.content);
- break;
- case 'error':
- logger.error(config.content);
- break;
- case 'fatal':
- logger.fatal(config.content);
- break;
- default:
- logger.debug(config.content);
- break;
- }
- },
-};
diff --git a/index.js b/index.js
deleted file mode 100755
index 74b6be1..0000000
--- a/index.js
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env node
-
-const fs = require('node:fs');
-const { Client, Collection, Intents } = require('discord.js');
-const { token } = require('./helpers/env');
-const { log } = require('./helpers/log');
-
-const client = new Client({
- intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MESSAGE_REACTIONS, Intents.FLAGS.DIRECT_MESSAGES, Intents.FLAGS.DIRECT_MESSAGE_REACTIONS],
-});
-
-client.commands = new Collection();
-
-const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
-const eventFiles = fs.readdirSync('./events').filter(file => file.endsWith('.js'));
-
-for (const file of commandFiles) {
- const command = require(`./commands/${file}`);
- client.commands.set(command.data.name, command);
- log({ logger: 'command', content: `Registered command ${file}!`, level: 'info' });
-}
-
-for (const file of eventFiles) {
- const event = require(`./events/${file}`);
- if (event.once) {
- client.once(event.name, (...args) => event.execute(...args));
- }
- else {
- client.on(event.name, (...args) => event.execute(...args));
- }
- log({ logger: 'event', content: `Registered event ${file}!`, level: 'info' });
-}
-
-client.login(token);
diff --git a/package.json b/package.json
index da30ddd..8bc8498 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,8 @@
},
"devDependencies": {
"eslint": "^8.7.0",
- "nodemon": "^2.0.15"
+ "nodemon": "^2.0.15",
+ "typescript": "^4.6.2"
},
"name": "awesomescibo",
"version": "3.2.1",
diff --git a/src/commands/about.ts b/src/commands/about.ts
new file mode 100644
index 0000000..c70a44b
--- /dev/null
+++ b/src/commands/about.ts
@@ -0,0 +1,77 @@
+import { SlashCommandBuilder } from '@discordjs/builders';
+import { MessageEmbed } from 'discord.js';
+
+const gitlog = require('gitlog').default;
+
+import userScore from '../models/userScore';
+
+export const data = new SlashCommandBuilder()
+ .setName('about')
+ .setDescription('Commands regarding the creation/development of the bot')
+ .addSubcommand(subcommand => {
+ subcommand
+ .setName('contributors')
+ .setDescription('Lists contributors to the AwesomeSciBo bot');
+ return subcommand;
+ })
+ .addSubcommand(subcommand => {
+ subcommand
+ .setName('changelog')
+ .setDescription('Lists the 5 most recent changes in a "git log" type format');
+ return subcommand;
+ })
+ .addSubcommand(subcommand => {
+ subcommand
+ .setName('bot')
+ .setDescription('Lists information about AwesomeSciBo');
+ return subcommand;
+ });
+
+export async function execute(interaction) {
+ await interaction.deferReply();
+
+ const client = interaction.client;
+ const action = interaction.options.getSubcommand();
+ if (action === 'contributors') {
+ 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');
+
+ interaction.followUp({ embeds: [contributorEmbed] });
+ }
+ else if (action === 'changelog') {
+ 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`);
+ });
+
+ interaction.followUp({ embeds: [changelogEmbed] });
+ }
+ else if (action === 'bot') {
+ 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();
+
+ interaction.followUp({ embeds: [aboutBotEmbed] });
+ }
+}
diff --git a/src/commands/help.ts b/src/commands/help.ts
new file mode 100644
index 0000000..38d5887
--- /dev/null
+++ b/src/commands/help.ts
@@ -0,0 +1,15 @@
+import { SlashCommandBuilder } from '@discordjs/builders';
+import { MessageEmbed } 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) {
+ await interaction.deferReply();
+
+ const helpEmbed = new MessageEmbed()
+ .setDescription('AwesomeSciBo has migrated to using slash commands! You can take a look at the different commands by typing `/` and clicking on the AwesomeSciBo icon.')
+ .setColor('#ffffff');
+ interaction.followUp({ embeds: [helpEmbed] });
+}
\ No newline at end of file
diff --git a/src/commands/rounds.ts b/src/commands/rounds.ts
new file mode 100644
index 0000000..ec46b7b
--- /dev/null
+++ b/src/commands/rounds.ts
@@ -0,0 +1,112 @@
+import { SlashCommandBuilder } from '@discordjs/builders';
+import { MessageEmbed } from 'discord.js';
+
+import axios from 'axios';
+
+import log from '../helpers/log';
+import generatedRound from '../models/generateRound';
+
+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) {
+ await interaction.deferReply();
+
+ const action = interaction.options.getSubcommand();
+ if (action === 'generate') {
+ let i;
+ let finalizedHTML = ' ROUND GENERATED BY AWESOMESCIBO USING THE SCIBOWLDB API
';
+ let tossup_question;
+ let question_category;
+ let tossup_format;
+ let tossup_answer;
+ let bonus_question;
+ let bonus_format;
+ let bonus_answer;
+ let htmlContent = '';
+ await axios.post('https://scibowldb.com/api/questions', { categories: ['BIOLOGY', 'PHYSICS', 'CHEMISTRY', 'EARTH AND SPACE', 'ASTRONOMY', 'MATH'] })
+ .then((response) => {
+ for (i = 1; i < 26; i++) {
+ const data = response.data.questions[Math.floor(Math.random() * response.data.questions.length)];
+ tossup_question = data.tossup_question;
+ tossup_answer = data.tossup_answer;
+ question_category = data.category;
+ tossup_format = data.tossup_format;
+ bonus_question = data.bonus_question;
+ bonus_answer = data.bonus_answer;
+ bonus_format = data.bonus_format;
+ htmlContent = '
TOSS-UP
\n
' + `${i}) ${question_category}` + ' ' + `${tossup_format}` + ' ' + tossup_question + '
' + 'ANSWER: ' + tossup_answer + '
';
+ htmlContent += '
BONUS
\n
' + `${i}) ${question_category}` + ' ' + `${bonus_format}` + ' ' + bonus_question + '
' + 'ANSWER: ' + bonus_answer + '
';
+ htmlContent = htmlContent.replace(/\n/g, '
');
+ finalizedHTML += htmlContent;
+ }
+
+ const newGeneratedRound = new generatedRound({
+ htmlContent: finalizedHTML,
+ requestedBy: interaction.user.id,
+ authorTag: interaction.user.tag,
+ timestamp: new Date().toISOString(),
+ });
+
+ newGeneratedRound.save((err, round) => {
+ if (err) {
+ log({ logger: 'rounds', content: `Saving round to DB failed: ${err}`, level: 'error' });
+ return;
+ }
+ interaction.followUp(`Here's your round: https://api.adawesome.tech/round/${round._id.toString()}`, { ephemeral: true });
+ });
+ });
+ }
+ else if (action === 'list') {
+ let roundsList = await generatedRound.find({ requestedBy: interaction.user.id }).sort({ timestamp: -1 });
+ let finalMessage = '';
+ if (!roundsList) {
+ interaction.followUp('You haven\'t requested any roundsList!');
+ 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 roundsList requested by ${interaction.user.tag}`)
+ .setDescription(finalMessage)
+ .setTimestamp();
+
+ interaction.followUp({
+ embeds: [roundsListEmbed],
+ ephemeral: true,
+ });
+ }
+ else if (action === 'hit') {
+ const totalCount = await generatedRound.countDocuments({});
+ const userCount = await generatedRound.countDocuments({ requestedBy: interaction.user.id });
+
+ interaction.followUp(`Total Hits: ${totalCount}\nYour Hits: ${userCount}`);
+ }
+}
\ No newline at end of file
diff --git a/src/commands/top.ts b/src/commands/top.ts
new file mode 100644
index 0000000..c1c12dc
--- /dev/null
+++ b/src/commands/top.ts
@@ -0,0 +1,39 @@
+import { SlashCommandBuilder } from '@discordjs/builders';
+import { 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 (server specific leaderboard WIP)');
+
+export async function execute(interaction) {
+ await interaction.deferReply();
+
+ let messageContent = '';
+ userScore
+ .find({})
+ .sort({ score: -1 }) // Sort by descending order
+ .exec((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!`,
+ );
+ }
+ for (let i = 0; i < 10; i++) {
+ messageContent += `${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(messageContent)
+ .setColor('#ffffff');
+
+ interaction.followUp({ embeds: [leaderboardEmbed] });
+ });
+}
\ No newline at end of file
diff --git a/src/commands/train.ts b/src/commands/train.ts
new file mode 100644
index 0000000..46b7ddd
--- /dev/null
+++ b/src/commands/train.ts
@@ -0,0 +1,171 @@
+import { SlashCommandBuilder } from '@discordjs/builders';
+import { MessageEmbed } from 'discord.js';
+
+import { decode } from 'html-entities';
+import axios from 'axios';
+
+import userScore from '../models/userScore';
+
+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)
+ .addChoice('astro', 'astro')
+ .addChoice('bio', 'bio')
+ .addChoice('ess', 'ess')
+ .addChoice('chem', 'chem')
+ .addChoice('phys', 'phys')
+ .addChoice('math', 'math')
+ .addChoice('energy', 'energy')
+ .setRequired(false);
+ return option;
+ });
+
+export async function execute(interaction) {
+ await interaction.deferReply();
+
+ const subject = interaction.options.get('subject') ? interaction.options.get('subject').value : null;
+ const authorId = interaction.user.id;
+ let score;
+ userScore
+ .findOne({ authorID: authorId })
+ .lean()
+ .then((obj, err) => {
+ if (!obj) {
+ score = 0;
+ }
+ else if (obj) {
+ score = obj.score;
+ }
+ else {
+ log({ logger: 'train', content: `Getting user score failed: ${err}`, level: 'error' });
+ }
+ });
+
+ let categoryArray : string[] = [];
+
+ switch (subject) {
+ case null:
+ categoryArray = ['BIOLOGY', 'PHYSICS', 'CHEMISTRY', 'EARTH AND SPACE', 'ASTRONOMY', 'MATH'];
+ 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(
+ 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 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
+ }
+ interaction.followUp({ content: decode(tossupQuestion) + `\n\n||Source: ${data.uri}||` })
+ .then(() => {
+ 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 (data.tossup_format === 'Multiple Choice') {
+ if (answerMsg.content.charAt(0).toLowerCase() === tossupAnswer.charAt(0).toLowerCase()) {
+ predicted = 'correct';
+ }
+ else {
+ predicted = 'incorrect';
+ }
+ }
+ else 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, 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();
+ answerMsg.channel.send({
+ embeds: [overrideEmbed],
+ })
+ .then(overrideMsg => {
+ overrideMsg.react('<:override:955265585086857236>');
+ const filter = (reaction, user) => {
+ return (
+ ['override'].includes(reaction.emoji.name) &&
+ user.id === answerMsg.author.id
+ );
+ };
+ overrideMsg
+ .awaitReactions({
+ filter: filter,
+ max: 1,
+ })
+ .then(() => {
+ updateScore(true, score, authorId).then((msgToReply) =>
+ answerMsg.reply(msgToReply),
+ );
+ }).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' }));
+ }
+ }).catch(err => log({ logger: 'train', content: `${err}`, level: 'error' }));
+ }).catch(err => log({ logger: 'train', content: `${err}`, level: 'error' }));
+ }).catch(err => log({ logger: 'train', content: `${err}`, level: 'error' }));
+}
\ No newline at end of file
diff --git a/deploy-commands.js b/src/deploy-commands.ts
similarity index 56%
rename from deploy-commands.js
rename to src/deploy-commands.ts
index 0b7b816..6e3cb3c 100755
--- a/deploy-commands.js
+++ b/src/deploy-commands.ts
@@ -1,16 +1,18 @@
#!/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');
+import fs from 'node:fs';
+import { REST } from '@discordjs/rest';
+import { Routes } from 'discord-api-types/v9';
+import { clientId, token } from './helpers/env';
-const commands = [];
+const commands : any[] = [];
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
- const command = require(`./commands/${file}`);
- commands.push(command.data.toJSON());
+ import(`./commands/${file}`)
+ .then(command => {
+ commands.push(command.data.toJSON());
+ })
}
const rest = new REST({ version: '9' }).setToken(token);
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
new file mode 100644
index 0000000..8cdd4b0
--- /dev/null
+++ b/src/events/interactionCreate.ts
@@ -0,0 +1,21 @@
+import log from '../helpers/log';
+
+export const name = 'interactionCreate';
+
+export const once = false;
+
+export async function execute(interaction) {
+ const client = interaction.client;
+ if (!interaction.isCommand()) return;
+
+ const command = client.commands.get(interaction.commandName);
+ if (!command) return;
+
+ try {
+ await command.execute(interaction);
+ }
+ catch (error) {
+ log({ logger: 'interaction', content: `Interaction ${interaction.name} failed!`, level: 'error' });
+ await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
+ }
+}
\ No newline at end of file
diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts
new file mode 100644
index 0000000..c4232b9
--- /dev/null
+++ b/src/events/messageCreate.ts
@@ -0,0 +1,36 @@
+import axios from 'axios';
+import { MessageEmbed } from 'discord.js';
+const decode = require('html-entities').decode;
+
+import { testingGuild } from '../helpers/env';
+
+export const name = 'messageCreate';
+
+export const once = false;
+
+export async function execute(message) {
+ if (message.author.bot || message.guild.id != testingGuild) return;
+
+ if (message.content.startsWith('!q')) {
+ const questionId = message.content.split(' ')[1];
+ axios
+ .get(`https://scibowldb.com/api/questions/${questionId}`)
+ .then((res) => {
+ const data = res.data.question;
+ const tossupQuestion = data.tossup_question;
+ const tossupAnswer = data.tossup_answer;
+ let answers = tossupAnswer.split(' (ACCEPT: ');
+ if (answers.length > 1) {
+ answers[1] = answers[1].slice(0, answers[1].length - 1); // If there are multiple elements, it means there was an 'accept' and therefore a trailing ')' which should be removed
+ answers = [answers[0], ...answers[1].split(new RegExp(' OR ', 'i'))]; // Use the first element plus the last element split by 'OR' case insensitive
+ }
+ const dataEmbed = new MessageEmbed()
+ .setTitle('Data')
+ .setDescription(`\`\`\`json\n${JSON.stringify(data, null, 2)}\`\`\``);
+ message.reply({
+ content: decode(tossupQuestion) + `\n\nAnswers: [${answers}]`,
+ embeds: [dataEmbed],
+ });
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/events/ready.ts b/src/events/ready.ts
new file mode 100644
index 0000000..f816b31
--- /dev/null
+++ b/src/events/ready.ts
@@ -0,0 +1,16 @@
+import * as db from '../helpers/db';
+import { mongoUri } from '../helpers/env';
+import log from '../helpers/log';
+
+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' },
+ );
+}
diff --git a/src/helpers/db.ts b/src/helpers/db.ts
new file mode 100644
index 0000000..afd7952
--- /dev/null
+++ b/src/helpers/db.ts
@@ -0,0 +1,44 @@
+import mongoose from 'mongoose';
+
+import log from '../helpers/log';
+import userScore from '../models/userScore';
+
+export async function updateScore(isCorrect, score, authorId) {
+ if (!isCorrect) {
+ return `Nice try! Your score is still ${score}.`;
+ }
+ else {
+ score += 4;
+ if (score == 4) {
+ const newUserScore = new userScore({
+ authorID: authorId,
+ score: score,
+ });
+ newUserScore.save((err) =>
+ err
+ ? console.log('Error creating new user for scoring')
+ : console.log('Sucessfully created user to score.'),
+ );
+ }
+ else {
+ // TODO: Error handling
+ const doc = await userScore.findOne({
+ authorID: authorId,
+ });
+ doc.score = doc.score + 4;
+ doc.save();
+ }
+
+ return `Great job! Your score is now ${score}.`;
+ }
+}
+
+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' }));
+}
\ No newline at end of file
diff --git a/src/helpers/env.ts b/src/helpers/env.ts
new file mode 100644
index 0000000..22b1b3d
--- /dev/null
+++ b/src/helpers/env.ts
@@ -0,0 +1,6 @@
+import 'dotenv/config';
+
+export const clientId : string = process.env.CLIENT_ID!;
+export const testingGuild : string = process.env.TESTING_GUILD!;
+export const token : string = process.env.TOKEN!;
+export const mongoUri : string = process.env.MONGO_URI!;
\ No newline at end of file
diff --git a/src/helpers/log.ts b/src/helpers/log.ts
new file mode 100644
index 0000000..7e092d1
--- /dev/null
+++ b/src/helpers/log.ts
@@ -0,0 +1,29 @@
+import log4js from 'log4js';
+
+export default function (config) {
+ const logger = log4js.getLogger(config.logger);
+ logger.level = 'debug';
+ switch (config.level) {
+ case 'trace':
+ logger.trace(config.content);
+ break;
+ case 'debug':
+ logger.debug(config.content);
+ break;
+ case 'info':
+ logger.info(config.content);
+ break;
+ case 'warn':
+ logger.warn(config.content);
+ break;
+ case 'error':
+ logger.error(config.content);
+ break;
+ case 'fatal':
+ logger.fatal(config.content);
+ break;
+ default:
+ logger.debug(config.content);
+ break;
+ }
+}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
new file mode 100755
index 0000000..1249a6e
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,38 @@
+#!/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';
+
+const client = new Client({
+ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MESSAGE_REACTIONS, Intents.FLAGS.DIRECT_MESSAGES, Intents.FLAGS.DIRECT_MESSAGE_REACTIONS],
+});
+
+client['commands'] = new Collection();
+
+const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
+const eventFiles = fs.readdirSync('./events').filter(file => file.endsWith('.js'));
+
+for (const file of commandFiles) {
+ import(`./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(`./events/${file}`)
+ .then(event => {
+ if (event.once) {
+ client.once(event.name, (...args) => event.execute(...args));
+ }
+ else {
+ client.on(event.name, (...args) => event.execute(...args));
+ }
+ log({ logger: 'event', content: `Registered event ${file}!`, level: 'info' });
+ })
+}
+
+client.login(token);
diff --git a/models/generateRound.js b/src/models/generateRound.ts
similarity index 70%
rename from models/generateRound.js
rename to src/models/generateRound.ts
index 48eb765..1cc3611 100644
--- a/models/generateRound.js
+++ b/src/models/generateRound.ts
@@ -1,4 +1,4 @@
-const mongoose = require('mongoose');
+import mongoose from 'mongoose';
const generatedRoundSchema = new mongoose.Schema({
htmlContent: {
@@ -19,4 +19,4 @@ const generatedRoundSchema = new mongoose.Schema({
},
});
-module.exports = mongoose.model('GeneratedRounds', generatedRoundSchema);
+export default mongoose.model('GeneratedRounds', generatedRoundSchema);
diff --git a/models/userScore.js b/src/models/userScore.ts
similarity index 59%
rename from models/userScore.js
rename to src/models/userScore.ts
index 5c069ff..3ef9a9a 100644
--- a/models/userScore.js
+++ b/src/models/userScore.ts
@@ -1,4 +1,4 @@
-const mongoose = require('mongoose');
+import mongoose from 'mongoose';
const userScoreSchema = new mongoose.Schema({
authorID: {
@@ -11,4 +11,4 @@ const userScoreSchema = new mongoose.Schema({
},
});
-module.exports = mongoose.model('UserScore', userScoreSchema);
+export default mongoose.model('UserScore', userScoreSchema);
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..fb4c0b6
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,101 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig.json to read more about this file */
+
+ /* Projects */
+ // "incremental": true, /* Enable incremental compilation */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
+ // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+
+ /* Modules */
+ "module": "commonjs", /* Specify what module code is generated. */
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "resolveJsonModule": true, /* Enable importing .json files */
+ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
+
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
+ "outDir": "./built", /* Specify an output folder for all emitted files. */
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+
+ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */
+ "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
+ // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
+ // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 643b629..dbb899a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1580,6 +1580,11 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
+typescript@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
+ integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
+
undefsafe@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"