diff --git a/commands/about.js b/commands/about.js new file mode 100644 index 0000000..ccf1856 --- /dev/null +++ b/commands/about.js @@ -0,0 +1,76 @@ +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) { + 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.reply({ 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.reply({ 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.reply({ embeds: [aboutBotEmbed] }); + } + }, +}; diff --git a/commands/help.js b/commands/help.js new file mode 100644 index 0000000..154795e --- /dev/null +++ b/commands/help.js @@ -0,0 +1,14 @@ +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) { + 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.reply({ embeds: [helpEmbed] }); + }, +}; diff --git a/commands/rounds.js b/commands/rounds.js new file mode 100644 index 0000000..a9167c0 --- /dev/null +++ b/commands/rounds.js @@ -0,0 +1,110 @@ +const { SlashCommandBuilder } = require('@discordjs/builders'); +const { MessageEmbed } = require('discord.js'); + +const axios = require('axios'); + +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) { + 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) { + console.log(err); + return; + } + interaction.reply(`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.reply('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.reply({ + embeds: [roundsListEmbed], + ephemeral: true, + }); + } + else if (action === 'hit') { + const totalCount = await generatedRound.countDocuments({}); + const userCount = await generatedRound.countDocuments({ requestedBy: interaction.user.id }); + + interaction.reply(`Total Hits: ${totalCount}\nYour Hits: ${userCount}`); + } + }, +}; diff --git a/commands/top.js b/commands/top.js new file mode 100644 index 0000000..bb5abb9 --- /dev/null +++ b/commands/top.js @@ -0,0 +1,39 @@ +const { SlashCommandBuilder } = require('@discordjs/builders'); +const { MessageEmbed } = require('discord.js'); + +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) { + let messageContent = ''; + userScore + .find({}) + .sort({ score: -1 }) // Sort by descending order + .exec((err, obj) => { + if (err) { + console.log(err); + return interaction.reply( + 'Uh oh! :( There was an internal error. Please try again.', + ); + } + if (obj.length < 10) { + // Need at least 10 scores for top 10 + return interaction.reply( + `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.reply({ embeds: [leaderboardEmbed] }); + }); + }, +}; diff --git a/commands/train.js b/commands/train.js new file mode 100644 index 0000000..d050dbd --- /dev/null +++ b/commands/train.js @@ -0,0 +1,169 @@ +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 { 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) { + 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 { + console.log(err); + } + }); + + 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.reply( + 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; + const messageFilter = message => message.author.id === interaction.author.id; + interaction.reply({ content: decode(tossupQuestion) + `\n\n||Source: ${data.uri}||` }) + .then(() => { + interaction.channel.awaitMessages({ + messageFilter, + max: 1, + }) + .then(collected => { + console.log(collected.first()); + const answerMsg = collected.first(); + + 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() + ) { + 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:842778128966615060> to override your answer if you think you got it right.') + .setColor('#ffffff') + .setTimestamp(); + answerMsg.channel.send({ + embeds: [overrideEmbed], + }) + .then(overrideMsg => { + overrideMsg.react('<:override:842778128966615060>'); + const filter = (reaction, user) => { + return ( + ['override'].includes(reaction.emoji.name) && + user.id === answerMsg.author.id + ); + }; + overrideMsg + .awaitReactions({ + filter, + max: 1, + }) + .then(() => { + updateScore(true, score, authorId).then((msgToReply) => + answerMsg.reply(msgToReply), + ); + }).catch(console.error); + }).catch(console.error); + } + }).catch(console.error); + }).catch(console.error); + }).catch(console.error); + }, +}; diff --git a/deploy-commands.js b/deploy-commands.js new file mode 100755 index 0000000..0b7b816 --- /dev/null +++ b/deploy-commands.js @@ -0,0 +1,20 @@ +#!/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 commands = []; +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()); +} + +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); diff --git a/events/interactionCreate.js b/events/interactionCreate.js new file mode 100644 index 0000000..e364cf2 --- /dev/null +++ b/events/interactionCreate.js @@ -0,0 +1,20 @@ +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) { + console.error(error); + await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true }); + } + }, +}; diff --git a/events/ready.js b/events/ready.js new file mode 100644 index 0000000..e4a0aa7 --- /dev/null +++ b/events/ready.js @@ -0,0 +1,11 @@ +const db = require('../helpers/db'); +const { mongoUri } = require('../helpers/env'); + +module.exports = { + name: 'ready', + once: true, + async execute(client) { + await db.connect(mongoUri); + console.log(`Logged in at ${client.user.tag}!`); + }, +}; diff --git a/helpers/db.js b/helpers/db.js new file mode 100644 index 0000000..4754b42 --- /dev/null +++ b/helpers/db.js @@ -0,0 +1,41 @@ +const mongoose = require('mongoose'); + +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 { + 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, + }); + }, +}; diff --git a/helpers/env.js b/helpers/env.js new file mode 100644 index 0000000..69f0af6 --- /dev/null +++ b/helpers/env.js @@ -0,0 +1,8 @@ +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/index.js b/index.js index 6bd39b0..3104c0f 100755 --- a/index.js +++ b/index.js @@ -1,437 +1,31 @@ #!/usr/bin/env node -require('dotenv').config(); +const fs = require('node:fs'); +const { Client, Collection, Intents } = require('discord.js'); +const { token } = require('./helpers/env'); -const Discord = require('discord.js'); -const client = new Discord.Client({ - intents: ['GUILDS', 'GUILD_MESSAGES', 'GUILD_MESSAGE_REACTIONS', 'DIRECT_MESSAGES', 'DIRECT_MESSAGE_REACTIONS'], - partials: ['MESSAGE', 'CHANNEL', 'REACTION'], +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], }); -const axios = require('axios'); -const mongoose = require('mongoose'); -const gitlog = require('gitlog').default; -const decode = require('html-entities').decode; -const userScore = require('./models/userScore'); -const generatedRound = require('./models/generateRound'); +client.commands = new Collection(); -const helpMessage = 'AwesomeSciBo has migrated to using slash commands! You can take a look at the different commands by typing `/` and clicking on the AwesomeSciBo icon.'; -const slashCommands = require('./slashCommands.json'); -const config = { - topggauth: process.env['TOPGGAUTH'], -}; +const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js')); +const eventFiles = fs.readdirSync('./events').filter(file => file.endsWith('.js')); -client.once('ready', () => { - client.application.commands.set(slashCommands); - - // Connect to MongoDB using mongoose - if (!process.env.CI) { - mongoose - .connect(process.env.MONGO_URI, { - useUnifiedTopology: true, - useNewUrlParser: true, - }) - .then(() => { - // Log client tag and set status - console.log(`Logged in as: ${client.user.username}!`); - client.user.setActivity( - 'for /help', - { type: 'WATCHING' }, - ); - }) - .catch((err) => console.log(err)); - } -}); - -client.on('guildCreate', () => { - const topggAuthHeader = { - headers: { - 'Authorization': config.topggauth, - }, - }; - axios.post(`https://top.gg/api/bots/${client.user.id}/stats`, { server_count: client.guilds.cache.size }, topggAuthHeader).then(response => { console.log(response); }); -}); - -client.on('guildDelete', () => { - const topggAuthHeader = { - headers: { - 'Authorization': config.topggauth, - }, - }; - axios.post(`https://top.gg/api/bots/${client.user.id}/stats`, { server_count: client.guilds.cache.size }, topggAuthHeader); -}); - -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 { - const doc = await userScore.findOne({ - authorID: authorId, - }); - doc.score = doc.score + 4; - doc.save(); - } - - return `Great job! Your score is now ${score}.`; - } +for (const file of commandFiles) { + const command = require(`./commands/${file}`); + client.commands.set(command.data.name, command); } -async function training(subject, interaction) { - 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 { - console.log(err); - } - }); - - 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.reply( - new Discord.MessageEmbed() - .setDescription('<:red_x:816791117671825409> Not a valid subject!') - .setColor('#ffffff'), - ); - return; +for (const file of eventFiles) { + const event = require(`./events/${file}`); + if (event.once) { + client.once(event.name, (...args) => event.execute(...args)); } - - 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; - const messageFilter = message => message.author.id === interaction.author.id; - interaction.reply({ content: decode(tossupQuestion) + `\n\n||Source: ${data.uri}||` }) - .then(() => { - interaction.channel.awaitMessages({ - messageFilter, - max: 1, - }) - .then(collected => { - console.log(collected.first()); - const answerMsg = collected.first(); - - 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() - ) { - predicted = 'correct'; - } - else { - predicted = 'incorrect'; - } - - if (predicted === 'correct') { - updateScore(true, score, authorId).then((msgToReply) => - answerMsg.reply(msgToReply), - ); - } - else { - const overrideEmbed = new Discord.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:842778128966615060> to override your answer if you think you got it right.') - .setColor('#ffffff') - .setTimestamp(); - answerMsg.channel.send({ - embeds: [overrideEmbed], - }) - .then(overrideMsg => { - overrideMsg.react('<:override:842778128966615060>'); - const filter = (reaction, user) => { - return ( - ['override'].includes(reaction.emoji.name) && - user.id === answerMsg.author.id - ); - }; - overrideMsg - .awaitReactions({ - filter, - max: 1, - }) - .then(() => { - updateScore(true, score, authorId).then((msgToReply) => - answerMsg.reply(msgToReply), - ); - }).catch(console.error); - }).catch(console.error); - } - }).catch(console.error); - }).catch(console.error); - }).catch(console.error); -} - -function sendHelpMessage(interaction) { - const helpEmbed = new Discord.MessageEmbed().setDescription(helpMessage).setColor('ffffff'); - interaction.reply({ embeds: [helpEmbed] }); -} - -function showLeaderboard(interaction) { - let messageContent = ''; - userScore - .find({}) - .sort({ score: -1 }) // Sort by descending order - .exec((err, obj) => { - if (err) { - console.log(err); - return interaction.reply( - 'Uh oh! :( There was an internal error. Please try again.', - ); - } - if (obj.length < 10) { - // Need at least 10 scores for top 10 - return interaction.reply( - `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 Discord.MessageEmbed() - .setTitle('Top Ten!') - .setDescription(messageContent) - .setColor('#ffffff'); - - interaction.reply({ embeds: [leaderboardEmbed] }); - }); -} - -async function about(action, interaction) { - if (action === 'contributors') { - const contributorEmbed = new Discord.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.reply({ 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 Discord.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.reply({ embeds: [changelogEmbed] }); - } - else if (action === 'bot') { - await client.guilds.fetch(); - const trainingDocuments = await userScore.countDocuments({}); - const aboutBotEmbed = new Discord.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.reply({ embeds: [aboutBotEmbed] }); - } -} - -async function rounds(action, interaction) { - 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) { - console.log(err); - return; - } - interaction.reply(`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.reply('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 Discord.MessageEmbed() - .setAuthor({ name: interaction.user.tag, iconURL: interaction.user.displayAvatarURL() }) - .setTitle(`Last 5 roundsList requested by ${interaction.user.tag}`) - .setDescription(finalMessage) - .setTimestamp(); - - interaction.reply({ - embeds: [roundsListEmbed], - ephemeral: true, - }); - } - else if (action === 'hit') { - const totalCount = await generatedRound.countDocuments({}); - const userCount = await generatedRound.countDocuments({ requestedBy: interaction.user.id }); - - interaction.reply(`Total Hits: ${totalCount}\nYour Hits: ${userCount}`); - } -} - -async function result(interaction) { - if (interaction.channel.id !== '930275699644825600') { - return interaction.reply({ content: 'This command is unavailable outside of the designated channel.', ephemeral: true }); + else { + client.on(event.name, (...args) => event.execute(...args)); } - const resultEmbed = new Discord.MessageEmbed(); - resultEmbed.setTitle(`${interaction.options.get('location').value} Regionals`); - resultEmbed.setAuthor({ name: interaction.user.tag, iconURL: interaction.user.displayAvatarURL() }); - resultEmbed.addField(' Winner', `Congratulations to **${interaction.options.get('winner').value}**!`); - if (interaction.options.get('runner-up')) resultEmbed.addField('<:second:932138645601800252> Runners-Up', interaction.options.get('runner-up').value); - if (interaction.options.get('third-place')) resultEmbed.addField('<:third:932138645526315080> Third Place', interaction.options.get('third-place').value); - if (interaction.options.get('honorable-mentions')) resultEmbed.addField('🎖️ Honorable Mentions', interaction.options.get('honorable-mentions').value); - resultEmbed.setColor('#FFFFFF'); - resultEmbed.setTimestamp(); - - await interaction.reply({ embeds: [resultEmbed] }); } -client.on('interactionCreate', async interaction => { - // If the interaction isn't a slash command, return - if (!interaction.isCommand()) return; - - switch (interaction.commandName) { - case 'help': - sendHelpMessage(interaction); - break; - case 'train': - training(interaction.options.get('subject') ? interaction.options.get('subject').value : null, interaction); - break; - case 'roundsList': - rounds(interaction.options.getSubcommand(), interaction); - break; - case 'top': - showLeaderboard(interaction); - break; - case 'about': - about(interaction.options.getSubcommand(), interaction); - break; - case 'result': - result(interaction); - } -}); - -client - .login(process.env.TOKEN) - .then(() => console.log('Running!')) - .catch((error) => console.log(error)); +client.login(token); diff --git a/package.json b/package.json index 466668a..eb6edc3 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "@discordjs/rest": "^0.3.0", "axios": "^0.21.1", "discord.js": "^13.2.0", "dotenv": "^8.2.0", diff --git a/slashCommands.json b/slashCommands.json deleted file mode 100644 index 9e4a2d6..0000000 --- a/slashCommands.json +++ /dev/null @@ -1,137 +0,0 @@ -[ - { - "name": "train", - "description": "Sends a single training question to be answered", - "options": [ - { - "type": 3, - "name": "subject", - "description": "Optional subject to be used as a filter", - "default": false, - "required": false, - "choices": [ - { - "name": "astro", - "value": "astro" - }, - { - "name": "bio", - "value": "bio" - }, - { - "name": "ess", - "value": "ess" - }, - { - "name": "chem", - "value": "chem" - }, - { - "name": "phys", - "value": "phys" - }, - { - "name": "math", - "value": "math" - }, - { - "name": "energy", - "value": "energy" - } - ] - } - ] - }, - { - "name": "help", - "description": "Replies with a help message explaining what the bot can do" - }, - { - "name": "rounds", - "options": [ - { - "type": 1, - "name": "generate", - "description": "Generates a round with randomized questions from https://scibowldb.com/", - "options": [] - }, - { - "type": 1, - "name": "list", - "description": "Lists your 5 most recently generated rounds with links", - "options": [] - }, - { - "type": 1, - "name": "hit", - "description": "Shows the total number of rounds hit as well as the number for the specific user", - "options": [] - } - ], - "description": "Commands regarding rounds generated by AwesomeSciBo" - }, - { - "name": "top", - "description": "Lists top ten scores across servers (server specific leaderboard WIP)" - }, - { - "name": "about", - "options": [ - { - "type": 1, - "name": "contributors", - "description": "Lists contributors to the AwesomeSciBo bot", - "options": [] - }, - { - "type": 1, - "name": "changelog", - "description": "Lists the 5 most recent changes in a \"git log\" type format", - "options": [] - }, - { - "type": 1, - "name": "bot", - "description": "Lists information about AwesomeSciBo", - "options": [] - } - ], - "description": "Commands regarding the creation/development of the bot" - }, - { - "name": "result", - "description": "The result of a regional", - "options": [ - { - "type": "STRING", - "name": "location", - "description": "The location of the regional", - "required": true - }, - { - "type": "STRING", - "name": "winner", - "description": "The advancing team from the regional", - "required": true - }, - { - "type": "STRING", - "name": "runner-up", - "description": "The second-place team from the regional", - "required": false - }, - { - "type": "STRING", - "name": "third-place", - "description": "The third-place team from the regional", - "required": false - }, - { - "type": "STRING", - "name": "honorable-mentions", - "description": "Any extra teams to be mentioned or a space for technicalities to be explained", - "required": false - } - ] - } -] diff --git a/yarn.lock b/yarn.lock index a0fe562..43ed07a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,6 +18,19 @@ resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.4.0.tgz#b6488286a1cc7b41b644d7e6086f25a1c1e6f837" integrity sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw== +"@discordjs/rest@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-0.3.0.tgz#89e06a42b168c91598891d6bf860353e28fba5d2" + integrity sha512-F9aeP3odlAlllM1ciBZLdd+adiAyBj4VaZBejj4UMj4afE2wfCkNTGvYYiRxrXUE9fN7e/BuDP2ePl0tVA2m7Q== + dependencies: + "@discordjs/collection" "^0.4.0" + "@sapphire/async-queue" "^1.1.9" + "@sapphire/snowflake" "^3.0.1" + discord-api-types "^0.26.1" + form-data "^4.0.0" + node-fetch "^2.6.5" + tslib "^2.3.1" + "@eslint/eslintrc@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" @@ -52,6 +65,11 @@ resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.1.9.tgz#ce69611c8753c4affd905a7ef43061c7eb95c01b" integrity sha512-CbXaGwwlEMq+l1TRu01FJCvySJ1CEFKFclHT48nIfNeZXaAAmmwwy7scUKmYHPUa3GhoMp6Qr1B3eAJux6XgOQ== +"@sapphire/snowflake@^3.0.1": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.2.0.tgz#fa915798d0a0ce5fa93b16c793185ae5d0a3f117" + integrity sha512-tfHzY+6/5bbHdB+uNqsEQ5rhjaZAoFUrqP/l1S5jwxMdKeSCIiGkJjcE99/WGGdzyWGjTNgNVX/dt4Me/FdMlg== + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -428,7 +446,7 @@ denque@^1.4.1: resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== -discord-api-types@^0.26.0: +discord-api-types@^0.26.0, discord-api-types@^0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.26.1.tgz#726f766ddc37d60da95740991d22cb6ef2ed787b" integrity sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ== @@ -1108,7 +1126,7 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -node-fetch@^2.6.1: +node-fetch@^2.6.1, node-fetch@^2.6.5: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==