diff --git a/.gitignore b/.gitignore index 1029b32..66bf350 100755 --- a/.gitignore +++ b/.gitignore @@ -135,4 +135,5 @@ config.json.disabled .ssh/* !.ssh/.gitkeep testing/ -levels.db \ No newline at end of file +levels.db +database.db \ No newline at end of file diff --git a/commands.json b/commands.json index 4b89a25..1fba2da 100644 --- a/commands.json +++ b/commands.json @@ -1,59 +1,70 @@ [ { - "name": "rank", - "description": "Get your rank", + "name": "modify", + "description": "Modify a users points", "type": 1, "options": [ { "name": "user", - "description": "The user you want to get the rank of", + "description": "The user to modify", "type": 6, - "required": false + "required": true + }, + { + "name": "amount", + "description": "The amount to modify (can be negative)", + "type": 10, + "required": true + } + ] + }, + { + "name": "give-all", + "description": "Give all users (who already have points) points", + "type": 1, + "options": [ + { + "name": "amount", + "description": "The amount to give (or take if negative)", + "type": 10, + "required": true } ] }, { "name": "leaderboard", - "description": "Get the top 10 users", + "description": "Cactus points leaderboard", "type": 1 }, { - "name": "givexp", - "description": "Give XP to a user", + "name": "transfer", + "description": "Transfer points to another user (2:1 ratio)", "type": 1, - "default_member_permissions": 0, "options": [ { "name": "user", - "description": "The user you want to give XP to", + "description": "The user to transfer to", "type": 6, "required": true }, { "name": "amount", - "description": "The amount of XP you want to give", - "type": 4, + "description": "The amount to transfer", + "type": 10, "required": true } ] }, { - "name": "removexp", - "description": "Remove XP from a user", + "name": "points", + "description": "Check the points of yourself or another user", "type": 1, - "default_member_permissions": 0, "options": [ { "name": "user", - "description": "The user you want to remove XP from", + "description": "The user to check", "type": 6, - "required": true - }, - { - "name": "amount", - "description": "The amount of XP you want to remove", - "type": 4, - "required": true + "required": false } ] } diff --git a/config.json.default b/config.json.default index bd3747f..cd03dad 100644 --- a/config.json.default +++ b/config.json.default @@ -2,15 +2,10 @@ "discord": { "token": "", "errorChannel": "", - "levels": { - "lvlUpEquation": "5 * (lvl ^ 2) + (50 * lvl) + 100", - "lvlUpMessage": "{user}, you have leveled up to level {lvl}!", - "cooldownMinutes": 1, - "blacklist": [] - } - }, - "api": { - "port": 8080, - "enabled": false + "coin": "<:cactus:1135411352987504660>", + "givers": [ + "348601979473362955", + "289884287765839882" + ] } } diff --git a/index.js b/index.js index 90d3368..ed7728d 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,5 @@ const config = require("./config.json"); const Discord = require("discord.js"); -const express = require("express"); const rest = new Discord.REST({ version: '10' }).setToken(config.discord.token); @@ -9,29 +8,19 @@ const path = require("path"); const colors = require("colors"); const client = new Discord.Client({ intents: [ - "GuildMessages", "GuildMembers", "Guilds" ] }); -const app = express(); // 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"); +const db = new sqlite3.Database("./database.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, tag TEXT)"); +db.run("CREATE TABLE IF NOT EXISTS points (id TEXT, points INTEGER)"); // update table if it does exist -// Check if tag column exists in the levels table -db.all("PRAGMA table_info(levels)", async (err, rows) => { - // Check if tag column exists - if (rows.filter(row => row.name === "tag").length === 0) { - // Add tag column - await db.run("ALTER TABLE levels ADD COLUMN tag TEXT"); - } -}); client.on("ready", async () => { console.log(`${colors.cyan("[INFO]")} Logged in as ${colors.green(client.user.tag)}`) @@ -42,18 +31,11 @@ client.on("ready", 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.`); - }; + // Global commands + await rest.put(Discord.Routes.applicationCommands(client.user.id), { + body: commands + }); + console.log(`${colors.cyan("[INFO]")} Successfully registered commands. Took ${colors.green((Date.now() - start) / 1000)} seconds.`); } catch (error) { console.error(error); @@ -64,69 +46,32 @@ client.on("ready", async () => { console.log(`${colors.cyan("[INFO]")} Startup took ${colors.green((Date.now() - initTime) / 1000)} seconds.`) }); +// Functions -client.on("messageCreate", async message => { - if (message.author.bot) return; - if (message.channel.type === "DM") return; - if (config.discord.levels.blacklist.includes(message.channel.id)) return; - if (config.discord.levels.blacklist.includes(message.channel.parentId)) return; +checkAndModifyPoints = async (user, amount) => { + // Check if the user exists, if not, add them to the database + await db.get(`SELECT * FROM points WHERE id = '${user.id}'`, async (err, row) => { - // 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); + console.error(`Smthn went wrong: ${err}`); + return false; } if (!row) { - await db.run(`INSERT INTO levels (id, xp, lvl, totalXp, msgCount, tag) VALUES ('${message.author.id}', ${xp}, 1, ${xp}, 1, '${message.author.tag}')`); // 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); + await db.run(`INSERT INTO points (id, points) VALUES ('${user.id}', ${amount})`); + return amount; } 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); - - // Keep running level up equation until xp is less than the calculated level up xp - while (data.xp >= lvlUpXp) { - data.lvl++; - data.xp -= lvlUpXp; - lvlUpXp = eval(config.discord.levels.lvlUpEquation); - // use config.discord.levels.lvlUpMessage to send a message when the user levels up - message.channel.send(config.discord.levels.lvlUpMessage.replace("{user}", `<@${message.author.id}>`).replace("{lvl}", 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}, tag = '${message.author.tag}' WHERE id = '${message.author.id}'`); + await db.run(`UPDATE points SET points = ${row.points + amount} WHERE id = '${user.id}'`); + return row.points + amount; } + return false; }); - -}); +} client.on("interactionCreate", async interaction => { if (!interaction.isCommand()) return; switch (interaction.commandName) { - case "rank": + case "points": var user; if (interaction.options.getMember("user")) { user = interaction.options.getMember("user").user; @@ -134,84 +79,42 @@ client.on("interactionCreate", async interaction => { user = interaction.user; } // Get user data - await db.get(`SELECT * FROM levels WHERE id = '${user.id}'`, async (err, row) => { + await db.get(`SELECT * FROM points 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.", + content: "This user does not have any coins.", ephemeral: true }); if (row) { var data = row; - let lvl = data.lvl; - let rank; - // Calculate rank - await db.all(`SELECT * FROM levels ORDER BY totalXp DESC`, async (err, rows) => { - if (err) { - console.error(err); - } - if (rows) { - let rank = 0; - for (let i = 0; i < rows.length; i++) { - if (rows[i].id === user.id) { - rank = i + 1; - break; - } - } - interaction.reply({ - embeds: [{ - title: `${user.tag}'s Rank`, - fields: [{ - name: "Rank", - value: `#${rank}`, - inline: true - }, - { - 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 - }] - }) - } + interaction.reply({ + embeds: [{ + title: `${user.username}'s Coins`, + description: `${config.discord.coin} ${data.points}`, + }] }); } }); break; case "leaderboard": - await db.all(`SELECT * FROM levels ORDER BY totalXp DESC`, async (err, rows) => { + await db.all(`SELECT * FROM points ORDER BY points DESC`, async (err, rows) => { if (err) { console.error(err); } if (!rows) return interaction.reply({ - content: "No one has sent any messages yet.", + content: "It's quiet here...", ephemeral: true }); if (rows) { let leaderboard = []; // Top 10 - for (let i = 0; i < 10; i++) { + for (let i = 0; i < 5; i++) { if (rows[i]) { let user = await client.users.fetch(rows[i].id); let lvl = rows[i].lvl; - leaderboard.push(`${i + 1}. <@${user.id}> - ${rows[i].xp}/${eval(config.discord.levels.lvlUpEquation)} L${rows[i].lvl} - ${rows[i].totalXp} XP - ${rows[i].msgCount} Messages`); + leaderboard.push(`${i + 1}. <@${user.id}> - ${rows[i].points}`); } } interaction.reply({ @@ -225,40 +128,89 @@ client.on("interactionCreate", async interaction => { }); break; - case "givexp": - // Dont gotta check perms, done on discord - // Dont gotta check arguments, done on discord - - // Get user data - await db.get(`SELECT * FROM levels WHERE id = '${interaction.options.getUser("user").id}'`, async (err, row) => { + case "modify": + // check if the user is in the config.discord.givers array + if (!config.discord.givers.includes(interaction.user.id)) return interaction.reply({ + content: "You do not have permission to use this command.", + ephemeral: true + }); + // check if the arguments are there + if (!interaction.options.getMember("user")) return interaction.reply({ + content: "You must specify a user.", + ephemeral: true + }); + if (!interaction.options.getNumber("amount")) return interaction.reply({ + content: "You must specify an amount.", + ephemeral: true + }); + let outputStatus = await checkAndModifyPoints(interaction.options.getMember("user").user, interaction.options.getNumber("amount")); + if (outputStatus !== false) { + interaction.reply({ + content: `Gave ${interaction.options.getMember("user").user.username} ${interaction.options.getNumber("amount")} coins.`, + ephemeral: true + }); + } else { + interaction.reply({ + content: `An error occurred.\n`, + ephemeral: true + }); + } + break; + case "transfer": // Allows a user to transfer a positive amount of coins to another user at a 50% tax, rounded down, if the user sends 2 coins, the other user will receive 1, the other gets sent to the abyss. + // check if the arguments are there + if (!interaction.options.getMember("user")) return interaction.reply({ + content: "You must specify a user.", + ephemeral: true + }); + if (!interaction.options.getNumber("amount")) return interaction.reply({ + content: "You must specify an amount.", + ephemeral: true + }); + // Sanity check to make sure they arent trying to send negative coins and break the economy + if (interaction.options.getNumber("amount") < 0) return interaction.reply({ + content: "You cannot send negative coins you lil goober.", + ephemeral: true + }); + // check if the user has enough coins + await db.get(`SELECT * FROM points WHERE id = '${interaction.user.id}'`, async (err, row) => { if (err) { console.error(err); + return interaction.reply({ + content: "An error occurred.", + ephemeral: true + }); } if (!row) return interaction.reply({ - content: "This user has not sent any messages yet.", + content: "You do not have any coins.", ephemeral: true }); if (row) { - var data = row; - let lvl = data.lvl; - data.xp += interaction.options.getInteger("amount"); - data.totalXp += interaction.options.getInteger("amount"); - // 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); - - // Keep running level up equation until xp is less than the calculated level up xp - while (data.xp >= lvlUpXp) { - data.lvl++; - data.xp -= lvlUpXp; - lvlUpXp = eval(config.discord.levels.lvlUpEquation); - } - - // Update database - await db.run(`UPDATE levels SET xp = ${data.xp}, lvl = ${data.lvl}, totalXp = ${data.totalXp}, msgCount = ${data.msgCount} WHERE id = '${interaction.options.getUser("user").id}'`); - interaction.reply({ - content: `Gave ${interaction.options.getInteger("amount")} XP to ${interaction.options.getUser("user").tag}!`, + if (row.points < interaction.options.getNumber("amount")) return interaction.reply({ + content: "You do not have enough coins.", ephemeral: true }); + // If the user doesnt have any coins tell them. + if (row.points == 0) return interaction.reply({ + content: "You do not have any coins.", + ephemeral: true + }); + // Now check if they have enough for the tax + if (row.points < Math.floor(interaction.options.getNumber("amount") * 2)) return interaction.reply({ + content: "You do not have enough coins to pay the tax.", + ephemeral: true + }); + // At this point we know they have enough coins, so we can take them away, make sure to take the tax away too + checkAndModifyPoints(interaction.user, -Math.floor(interaction.options.getNumber("amount") * 2)); + // Now we can give the other user the coins + checkAndModifyPoints(interaction.options.getMember("user").user, Math.floor(interaction.options.getNumber("amount"))); + // Now we can tell the user that it worked + interaction.reply({ + embeds: [{ + title: "Transfer Successful", + color: 0x00ff00, + description: `You sent ${interaction.options.getNumber("amount")} coins to ${interaction.options.getMember("user").user.username}.` + }] + }); } }); break; @@ -266,42 +218,6 @@ client.on("interactionCreate", async interaction => { }); -app.get("/api/levels", async (req, res) => { - // Pretty much send the entire database - await db.all(`SELECT * FROM levels ORDER BY totalXp DESC`, async (err, rows) => { - if (err) { - console.error(err); - return res.sendStatus(500); // Internal server error - } - if (!rows) return res.sendStatus(204) // No content - if (rows) { - let output = rows; - return res.json(output); - } - }); -}); - -app.get("/api/levels/:id", async (req, res) => { - // Get user data - await db.get(`SELECT * FROM levels WHERE id = '${req.params.id}'`, async (err, row) => { - if (err) { - console.error(err); - return res.sendStatus(500); // Internal server error - } - if (!row) return res.sendStatus(404) // Not found - if (row) { - let output = row; - // Get user info {avatar, tag, etc} - let user = await client.users.fetch(req.params.id); - output.tag = user.tag; - output.avatar = user.displayAvatarURL({extension: "png", size: 1024}); - output.banner = user.bannerURL({extension: "png"}); - if (!output.tag) output.tag = "Unknown#0000"; - return res.json(output); - } - }); -}); - // Handle SIGINT gracefully process.on('SIGINT', async () => { @@ -331,15 +247,6 @@ process.on('SIGINT', async () => { } });*/ -if (config.api.enabled) { - // Start API - app.listen(config.api.port, () => { - console.log(`${colors.cyan("[INFO]")} API listening on port ${config.api.port}`); - }); -} - -// Global Variables -var cooldowns = {}; console.log(`${colors.cyan("[INFO]")} Starting...`)