diff --git a/.gitignore b/.gitignore index 189da84..1029b32 100755 --- a/.gitignore +++ b/.gitignore @@ -135,4 +135,4 @@ config.json.disabled .ssh/* !.ssh/.gitkeep testing/ -modPrompt.txt \ No newline at end of file +levels.db \ No newline at end of file diff --git a/commands.json b/commands.json new file mode 100644 index 0000000..1cdbe67 --- /dev/null +++ b/commands.json @@ -0,0 +1,15 @@ +[ + { + "name": "rank", + "description": "Get your rank", + "type": 1, + "options": [ + { + "name": "user", + "description": "The user you want to get the rank of", + "type": 6, + "required": false + } + ] + } +] \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..b0e8154 --- /dev/null +++ b/index.js @@ -0,0 +1,206 @@ +const config = require("./config.json"); +const Discord = require("discord.js"); +const rest = new Discord.REST({ + version: '10' +}).setToken(config.discord.token); +const fs = require("fs"); +const path = require("path"); +const colors = require("colors"); +const client = new Discord.Client({ + intents: [ + "GuildMessages", + "GuildMembers", + "Guilds" + ] +}); + +// Use sqlite3 for object storage, and create a database if it doesn't exist +const sqlite3 = require("sqlite3").verbose(); +const db = new sqlite3.Database("./levels.db"); + +// Create table if it doesn't exist +db.run("CREATE TABLE IF NOT EXISTS levels (id TEXT, xp INTEGER, lvl INTEGER, totalXp INTEGER, msgCount INTEGER)"); + +client.on("ready", async () => { + console.log(`${colors.cyan("[INFO]")} Logged in as ${colors.green(client.user.tag)}`) + // Load Commands + console.log(`${colors.cyan("[INFO]")} Loading Commands...`) + const commands = require('./commands.json'); + await (async () => { + try { + console.log(`${colors.cyan("[INFO]")} Registering Commands...`) + let start = Date.now() + // For every guild + for (const guild of client.guilds.cache.values()) { + let gStart = Date.now(); + console.log(`${colors.cyan("[INFO]")} Registering Commands for ${colors.green(guild.name)}...`); + // Register commands + await rest.put( + Discord.Routes.applicationGuildCommands(client.user.id, guild.id), { + body: commands + }, + ); + console.log(`${colors.cyan("[INFO]")} Successfully registered commands for ${colors.green(guild.name)}. Took ${colors.green((Date.now() - gStart) / 1000)} seconds.`); + }; + console.log(`${colors.cyan("[INFO]")} Successfully registered commands. Took ${colors.green((Date.now() - start) / 1000)} seconds.`); + } catch (error) { + console.error(error); + } + })(); + + // Log startup time in seconds + console.log(`${colors.cyan("[INFO]")} Startup took ${colors.green((Date.now() - initTime) / 1000)} seconds.`) +}); + + +client.on("messageCreate", async message => { + if (message.author.bot) return; + if (message.channel.type === "DM") return; + + // Calculate random xp + let xp = Math.floor(Math.random() * 10) + 15; + // If user is not in database, add them, {user: {xp = xp, lvl = 1, totalXp: xp, msgCount = 1}} + await db.get(`SELECT * FROM levels WHERE id = '${message.author.id}'`, async (err, row) => { + if (err) { + console.error(err); + } + if (!row) { + await db.run(`INSERT INTO levels (id, xp, lvl, totalXp, msgCount) VALUES ('${message.author.id}', ${xp}, 1, ${xp}, 1)`); // Add user to database + } + }); + + // Get user data + await db.get(`SELECT * FROM levels WHERE id = '${message.author.id}'`, async (err, row) => { + if (err) { + console.error(err); + } + if (row) { + var data = row; + let lvl = data.lvl; + data.msgCount++; + + // Cooldown + if (cooldowns[message.author.id] && new Date() - cooldowns[message.author.id] < config.discord.levels.cooldownMinutes * 60 * 1000) return await db.run(`UPDATE levels SET xp = ${data.xp}, lvl = ${data.lvl}, totalXp = ${data.totalXp}, msgCount = ${data.msgCount} WHERE id = '${message.author.id}'`); + cooldowns[message.author.id] = new Date(); + + data.xp += xp; + data.totalXp += xp; + + // If user is in database, and xp is greater than or equal to the calculated level up XP, add 1 to lvl and add the remainder to xp + let lvlUpXp = eval(config.discord.levels.lvlUpEquation); + if (data.xp >= lvlUpXp) { + data.lvl++; + data.xp -= lvlUpXp; + message.channel.send(`${message.author}, you have leveled up to level ${data.lvl}!`).then(msg => { + setTimeout(() => { + msg.delete(); + }, 10000); + }); + } + + // Update database + await db.run(`UPDATE levels SET xp = ${data.xp}, lvl = ${data.lvl}, totalXp = ${data.totalXp}, msgCount = ${data.msgCount} WHERE id = '${message.author.id}'`); + } + }); + +}); + +client.on("interactionCreate", async interaction => { + if (!interaction.isCommand()) return; + switch (interaction.commandName) { + case "rank": + var user; + if (interaction.options.getMember("user")) { + user = interaction.options.getMember("user"); + } else { + user = interaction.user; + } + // Get user data + await db.get(`SELECT * FROM levels WHERE id = '${user.id}'`, async (err, row) => { + if (err) { + console.error(err); + } + if (!row) return interaction.reply({ + content: "This user has not sent any messages yet.", + ephemeral: true + }); + if (row) { + var data = row; + let lvl = data.lvl; + interaction.reply({ + embeds: [{ + title: `${user.tag}'s Rank`, + fields: [{ + name: "Level", + value: data.lvl, + inline: true + }, + { + name: "XP", + value: `${data.xp}/${eval(config.discord.levels.lvlUpEquation)}`, + }, + { + name: "Total XP", + value: data.totalXp, + inline: true + }, + { + name: "Messages Sent", + value: data.msgCount, + inline: true + } + ], + color: 0x00ff00 + }] + }) + } + }); + break; + }; +}); + +// Handle SIGINT gracefully +process.on('SIGINT', async () => { + await console.log(`${colors.cyan("[INFO]")} Stop received, exiting...`); + await client.user.setPresence({ + status: "invisible", + activities: [] + }); + await client.destroy(); + await console.log(`${colors.cyan("[INFO]")} Goodbye!`); + process.exit(0); +}); + +// Global error handler +/* +process.on('uncaughtException', async (error) => { + await console.error(`${colors.red("[ERROR]")} Uncaught Exception: ${error}`); + if (client.user.tag) { + client.channels.fetch(config.discord.errorChannel).then(async channel => { + await channel.send({ + embeds: [{ + title: "Uncaught Exception", + description: `\`\`\`${error}\`\`\``, + color: 0xff0000 + }] + }); + }); + await client.user.setPresence({ + status: "invisible", + activities: [] + }); + await client.destroy(); + } + await process.exit(1); +}); + +*/ +// Global Variables +var cooldowns = {}; + + +console.log(`${colors.cyan("[INFO]")} Starting...`) +// Start timer to see how long startup takes +const initTime = Date.now() +// Login to Discord +client.login(config.discord.token); \ No newline at end of file