Abheek Dhawan
3 years ago
31 changed files with 726 additions and 621 deletions
@ -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] }); |
|||
} |
|||
}, |
|||
}; |
@ -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] }); |
|||
}, |
|||
}; |
@ -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 = '<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; |
|||
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 = '<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(`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}`); |
|||
} |
|||
}, |
|||
}; |
@ -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] }); |
|||
}); |
|||
}, |
|||
}; |
@ -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' })); |
|||
}, |
|||
}; |
@ -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 }); |
|||
} |
|||
}, |
|||
}; |
@ -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], |
|||
}); |
|||
}); |
|||
} |
|||
}, |
|||
}; |
@ -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' }, |
|||
); |
|||
}, |
|||
}; |
@ -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' })); |
|||
}, |
|||
}; |
@ -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, |
|||
}; |
@ -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; |
|||
} |
|||
}, |
|||
}; |
@ -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); |
@ -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] }); |
|||
} |
|||
} |
@ -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] }); |
|||
} |
@ -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 = '<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; |
|||
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 = '<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(`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}`); |
|||
} |
|||
} |
@ -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] }); |
|||
}); |
|||
} |
@ -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' })); |
|||
} |
@ -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); |
@ -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 }); |
|||
} |
|||
} |
@ -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], |
|||
}); |
|||
}); |
|||
} |
|||
} |
@ -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' }, |
|||
); |
|||
} |
@ -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' })); |
|||
} |
@ -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!; |
@ -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; |
|||
} |
|||
} |
@ -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); |
@ -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 `<reference>`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. */ |
|||
} |
|||
} |
Loading…
Reference in new issue