From 84768293c17880538ecd1c15215991524e875c63 Mon Sep 17 00:00:00 2001 From: "Ean Milligan (Bastion)" Date: Thu, 5 May 2022 00:57:34 -0400 Subject: [PATCH] Code cleanup Improved readability of command code by breaking each function into their own files, breaking down the api command, and starting to break down the roll command Dep versions also updated, dbClient implemented as its own import, and constantCmds/README updated to improve readability --- README.md | 2 +- deps.ts | 4 +- mod.ts | 510 ++--------------------------- src/api.ts | 6 +- src/commands/_index.ts | 27 ++ src/commands/apiCmd.ts | 55 ++++ src/commands/apiCmd/_apiIndex.ts | 11 + src/commands/apiCmd/allowBlock.ts | 39 +++ src/commands/apiCmd/apiHelp.ts | 10 + src/commands/apiCmd/deleteGuild.ts | 20 ++ src/commands/apiCmd/status.ts | 35 ++ src/commands/emoji.ts | 32 ++ src/commands/help.ts | 16 + src/commands/info.ts | 16 + src/commands/ping.ts | 20 ++ src/commands/privacy.ts | 16 + src/commands/report.ts | 20 ++ src/commands/rip.ts | 16 + src/commands/roll.ts | 146 +++++++++ src/commands/roll/_rollIndex.ts | 5 + src/commands/roll/getModifiers.ts | 116 +++++++ src/commands/rollHelp.ts | 16 + src/commands/stats.ts | 29 ++ src/commands/version.ts | 16 + src/constantCmds.ts | 24 +- src/db.ts | 11 + src/mod.d.ts | 11 + 27 files changed, 721 insertions(+), 508 deletions(-) create mode 100644 src/commands/_index.ts create mode 100644 src/commands/apiCmd.ts create mode 100644 src/commands/apiCmd/_apiIndex.ts create mode 100644 src/commands/apiCmd/allowBlock.ts create mode 100644 src/commands/apiCmd/apiHelp.ts create mode 100644 src/commands/apiCmd/deleteGuild.ts create mode 100644 src/commands/apiCmd/status.ts create mode 100644 src/commands/emoji.ts create mode 100644 src/commands/help.ts create mode 100644 src/commands/info.ts create mode 100644 src/commands/ping.ts create mode 100644 src/commands/privacy.ts create mode 100644 src/commands/report.ts create mode 100644 src/commands/rip.ts create mode 100644 src/commands/roll.ts create mode 100644 src/commands/roll/_rollIndex.ts create mode 100644 src/commands/roll/getModifiers.ts create mode 100644 src/commands/rollHelp.ts create mode 100644 src/commands/stats.ts create mode 100644 src/commands/version.ts create mode 100644 src/db.ts diff --git a/README.md b/README.md index 5bcc6b0..b1a6d3a 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Available Endpoints and Methods Required: * `424` - Failed dependancy - You will be emailed a delete code to rerun this endpoint with. * `200` - OK - Everything relating to your API key was successfully removed. -API Key management via a basic GUI is availble on [API Tools](https://artificer.eanm.dev/). +API Key management via a basic GUI is availble on the [API Tools](https://artificer.eanm.dev/) website. ## Problems? Feature requests? If you run into any errors or problems with the bot, or think you have a good idea to add to the bot, please submit a new GitHub issue detailing it. If you don't have a GitHub account, a report command (detailed above) is provided for use in Discord. diff --git a/deps.ts b/deps.ts index d20ace0..78b6f5d 100644 --- a/deps.ts +++ b/deps.ts @@ -11,8 +11,8 @@ export type { DiscordenoMessage, DiscordenoGuild, CreateMessage } from "https://deno.land/x/discordeno@12.0.1/mod.ts"; -export { Client } from "https://deno.land/x/mysql@v2.10.1/mod.ts"; +export { Client } from "https://deno.land/x/mysql@v2.10.2/mod.ts"; -export { Status, STATUS_TEXT } from "https://deno.land/std@0.115.1/http/http_status.ts"; +export { Status, STATUS_TEXT } from "https://deno.land/std@0.137.0/http/http_status.ts"; export { nanoid } from "https://deno.land/x/nanoid@v3.0.0/mod.ts"; diff --git a/mod.ts b/mod.ts index ae644c7..cb7c33b 100644 --- a/mod.ts +++ b/mod.ts @@ -4,43 +4,22 @@ * December 21, 2020 */ +import config from "./config.ts"; +import { DEBUG, LOCALMODE } from "./flags.ts"; import { // Discordeno deps startBot, editBotStatus, editBotNickname, Intents, - sendMessage, sendDirectMessage, - cache, cacheHandlers, botId, - hasGuildPermissions, + sendMessage, + cache, botId, DiscordActivityTypes, DiscordenoGuild, DiscordenoMessage, - - // MySQL Driver deps - Client } from "./deps.ts"; - import api from "./src/api.ts"; +import commands from "./src/commands/_index.ts"; import intervals from "./src/intervals.ts"; import utils from "./src/utils.ts"; -import solver from "./src/solver.ts"; -import { - constantCmds, - generatePing, generateReport, generateStats, generateApiFailed, generateApiStatus, generateApiSuccess, generateDMFailed -} from "./src/constantCmds.ts"; - -import { EmojiConf } from "./src/mod.d.ts"; import { LogTypes as LT } from "./src/utils.enums.ts"; -import { DEVMODE, DEBUG, LOCALMODE } from "./flags.ts"; -import config from "./config.ts"; - -// Initialize DB client -const dbClient = await new Client().connect({ - hostname: LOCALMODE ? config.db.localhost : config.db.host, - port: config.db.port, - db: config.db.name, - username: config.db.username, - password: config.db.password -}); - // Initialize logging client with folder to use for logs, needs --allow-write set on Deno startup utils.initLog("logs"); @@ -114,7 +93,7 @@ startBot({ }); }, debug: dmsg => utils.log(LT.LOG, `Debug Message | ${JSON.stringify(dmsg)}`), - messageCreate: async (message: DiscordenoMessage) => { + messageCreate: (message: DiscordenoMessage) => { // Ignore all other bots if (message.isBot) return; @@ -132,520 +111,73 @@ startBot({ // [[ping // Its a ping test, what else do you want. if (command === "ping") { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("ping");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - // Calculates ping between sending a message and editing it, giving a nice round-trip latency. - try { - const m = await message.send(generatePing(-1)); - m.edit(generatePing(m.timestamp - message.timestamp)); - } catch (e) { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - } + commands.ping(message); } // [[rip [[memory // Displays a short message I wanted to include else if (command === "rip" || command === "memory") { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("rip");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - message.send(constantCmds.rip).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); + commands.rip(message); } // [[rollhelp or [[rh or [[hr or [[?? // Help command specifically for the roll command else if (command === "rollhelp" || command === "rh" || command === "hr" || command === "??" || command?.startsWith("xdy")) { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("rollhelp");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - message.send(constantCmds.rollHelp).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); + commands.rollHelp(message); } // [[help or [[h or [[? // Help command, prints from help file else if (command === "help" || command === "h" || command === "?") { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("help");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - message.send(constantCmds.help).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); + commands.help(message); } // [[info or [[i // Info command, prints short desc on bot and some links else if (command === "info" || command === "i") { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("info");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - message.send(constantCmds.info).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); + commands.info(message); } // [[privacy // Privacy command, prints short desc on bot's privacy policy else if (command === "privacy") { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("privacy");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - message.send(constantCmds.privacy).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); + commands.privacy(message); } // [[version or [[v // Returns version of the bot else if (command === "version" || command === "v") { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("version");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - message.send(constantCmds.version).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); + commands.version(message); } // [[report or [[r (command that failed) // Manually report a failed roll else if (command === "report" || command === "r") { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("report");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - sendMessage(config.reportChannel, generateReport(args.join(" "))).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - message.send(constantCmds.report).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); + commands.report(message, args); } // [[stats or [[s // Displays stats on the bot else if (command === "stats" || command === "s") { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("stats");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - // Calculate how many times commands have been run - const rollQuery = await dbClient.query(`SELECT count FROM command_cnt WHERE command = "roll";`).catch(e => { - utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e)}`); - }); - const totalQuery = await dbClient.query(`SELECT SUM(count) as count FROM command_cnt;`).catch(e => { - utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e)}`); - }); - const rolls = BigInt(rollQuery[0].count); - const total = BigInt(totalQuery[0].count); - - const cachedGuilds = await cacheHandlers.size("guilds"); - const cachedChannels = await cacheHandlers.size("channels"); - const cachedMembers = await cacheHandlers.size("members"); - message.send(generateStats(cachedGuilds + cache.dispatchedGuildIds.size, cachedChannels + cache.dispatchedChannelIds.size, cachedMembers, rolls, total - rolls)).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); + commands.stats(message); } // [[api arg // API sub commands else if (command === "api") { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("api");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - // Local apiArg in lowercase - const apiArg = (args[0] || "help").toLowerCase(); - - // Alert users who DM the bot that this command is for guilds only - if (message.guildId === 0n) { - message.send(constantCmds.apiGuildOnly).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - return; - } - - // Makes sure the user is authenticated to run the API command - if (await hasGuildPermissions(message.authorId, message.guildId, ["ADMINISTRATOR"])) { - // [[api help - // Shows API help details - if (apiArg === "help") { - message.send(constantCmds.apiHelp).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - } - - // [[api allow/block - // Lets a guild admin allow or ban API rolls from happening in said guild - else if (apiArg === "allow" || apiArg === "block" || apiArg === "enable" || apiArg === "disable") { - const guildQuery = await dbClient.query(`SELECT guildid FROM allowed_guilds WHERE guildid = ?`, [message.guildId]).catch(e0 => { - utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e0)}`); - message.send(generateApiFailed(apiArg)).catch(e1 => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); - }); - return; - }); - - if (guildQuery.length === 0) { - // Since guild is not in our DB, add it in - await dbClient.execute(`INSERT INTO allowed_guilds(guildid,active) values(?,?)`, [BigInt(message.guildId), ((apiArg === "allow" || apiArg === "enable") ? 1 : 0)]).catch(e0 => { - utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e0)}`); - message.send(generateApiFailed(apiArg)).catch(e1 => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); - }); - return; - }); - } else { - // Since guild is in our DB, update it - await dbClient.execute(`UPDATE allowed_guilds SET active = ? WHERE guildid = ?`, [((apiArg === "allow" || apiArg === "enable") ? 1 : 0), BigInt(message.guildId)]).catch(e0 => { - utils.log(LT.ERROR, `Failed to update DB: ${JSON.stringify(e0)}`); - message.send(generateApiFailed(apiArg)).catch(e1 => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); - }); - return; - }); - } - // We won't get here if there's any errors, so we know it has bee successful, so report as such - message.send(generateApiSuccess(apiArg)).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - } - - // [[api delete - // Lets a guild admin delete their server from the database - else if (apiArg === "delete") { - await dbClient.execute(`DELETE FROM allowed_guilds WHERE guildid = ?`, [message.guildId]).catch(e0 => { - utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e0)}`); - message.send(constantCmds.apiDeleteFail).catch(e1 => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); - }); - return; - }); - - // We won't get here if there's any errors, so we know it has bee successful, so report as such - message.send(constantCmds.apiRemoveGuild).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - } - - // [[api status - // Lets a guild admin check the status of API rolling in said guild - else if (apiArg === "status") { - // Get status of guild from the db - const guildQuery = await dbClient.query(`SELECT active, banned FROM allowed_guilds WHERE guildid = ?`, [message.guildId]).catch(e0 => { - utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e0)}`); - message.send(constantCmds.apiStatusFail).catch(e1 => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); - }); - return; - }); - - // Check if we got an item back or not - if (guildQuery.length > 0) { - // Check if guild is banned from using API and return appropriate message - if (guildQuery[0].banned) { - message.send(generateApiStatus(true, false)).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - } else { - message.send(generateApiStatus(false, guildQuery[0].active)).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - } - } else { - // Guild is not in DB, therefore they are blocked - message.send(generateApiStatus(false, false)).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - } - } - } else { - message.send(constantCmds.apiPermError).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - } + commands.api(message, args); } // [[roll]] // Dice rolling commence! - else if ((`${command}${args.join("")}`).indexOf(config.postfix) > -1) { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("roll");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - // If DEVMODE is on, only allow this command to be used in the devServer - if (DEVMODE && message.guildId !== config.devServer) { - message.send(constantCmds.indev).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - return; - } - - // Rest of this command is in a try-catch to protect all sends/edits from erroring out - try { - const originalCommand = `${config.prefix}${command} ${args.join(" ")}`; - - const m = await message.send(constantCmds.rolling); - - const modifiers = { - noDetails: false, - spoiler: "", - maxRoll: false, - nominalRoll: false, - gmRoll: false, - gms: [], - order: "" - }; - - // Check if any of the args are command flags and pull those out into the modifiers object - for (let i = 0; i < args.length; i++) { - utils.log(LT.LOG, `Checking ${command}${args.join(" ")} for command modifiers ${i}`); - switch (args[i].toLowerCase()) { - case "-nd": - modifiers.noDetails = true; - - args.splice(i, 1); - i--; - break; - case "-s": - modifiers.spoiler = "||"; - - args.splice(i, 1); - i--; - break; - case "-m": - modifiers.maxRoll = true; - - args.splice(i, 1); - i--; - break; - case "-n": - modifiers.nominalRoll = true; - - args.splice(i, 1); - i--; - break; - case "-gm": - modifiers.gmRoll = true; - - // -gm is a little more complex, as we must get all of the GMs that need to be DMd - while (((i + 1) < args.length) && args[i + 1].startsWith("<@")) { - utils.log(LT.LOG, `Finding all GMs, checking args ${JSON.stringify(args)}`); - // Keep looping thru the rest of the args until one does not start with the discord mention code - modifiers.gms.push(args[i + 1].replace(/[!]/g, "")); - args.splice((i + 1), 1); - } - if (modifiers.gms.length < 1) { - // If -gm is on and none were found, throw an error - m.edit("Error: Must specifiy at least one GM by mentioning them"); - - if (DEVMODE && config.logRolls) { - // If enabled, log rolls so we can verify the bots math - dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,1)", [originalCommand, "NoGMsFound", m.id]).catch(e => { - utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); - }); - } - return; - } - - args.splice(i, 1); - i--; - break; - case "-o": - args.splice(i, 1); - - if (args[i].toLowerCase()[0] !== "d" && args[i].toLowerCase()[0] !== "a") { - // If -o is on and asc or desc was not specified, error out - m.edit("Error: Must specifiy a or d to order the rolls ascending or descending"); - - if (DEVMODE && config.logRolls) { - // If enabled, log rolls so we can verify the bots math - dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,1)", [originalCommand, "NoOrderFound", m.id]).catch(e => { - utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); - }); - } - return; - } - - modifiers.order = args[i].toLowerCase()[0]; - - args.splice(i, 1); - i--; - break; - default: - break; - } - } - - // maxRoll and nominalRoll cannot both be on, throw an error - if (modifiers.maxRoll && modifiers.nominalRoll) { - m.edit("Error: Cannot maximise and nominise the roll at the same time"); - - if (DEVMODE && config.logRolls) { - // If enabled, log rolls so we can verify the bots math - dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,1)", [originalCommand, "MaxAndNominal", m.id]).catch(e => { - utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); - }); - } - return; - } - - // Rejoin all of the args and send it into the solver, if solver returns a falsy item, an error object will be substituded in - const rollCmd = `${command} ${args.join(" ")}`; - const returnmsg = solver.parseRoll(rollCmd, config.prefix, config.postfix, modifiers.maxRoll, modifiers.nominalRoll, modifiers.order) || { error: true, errorCode: "EmptyMessage", errorMsg: "Error: Empty message", line1: "", line2: "", line3: "" }; - - let returnText = ""; - - // If there was an error, report it to the user in hopes that they can determine what they did wrong - if (returnmsg.error) { - returnText = returnmsg.errorMsg; - m.edit(returnText); - - if (DEVMODE && config.logRolls) { - // If enabled, log rolls so we can verify the bots math - dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,1)", [originalCommand, returnmsg.errorCode, m.id]).catch(e => { - utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); - }); - } - return; - } else { - // Else format the output using details from the solver - returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}`; - - if (modifiers.noDetails) { - returnText += "\nDetails suppressed by -nd flag."; - } else { - returnText += `\nDetails:\n${modifiers.spoiler}${returnmsg.line3}${modifiers.spoiler}`; - } - } - - // If the roll was a GM roll, send DMs to all the GMs - if (modifiers.gmRoll) { - // Make a new return line to be sent to the roller - const normalText = `<@${message.authorId}>${returnmsg.line1}\nResults have been messaged to the following GMs: ${modifiers.gms.join(" ")}`; - - // And message the full details to each of the GMs, alerting roller of every GM that could not be messaged - modifiers.gms.forEach(async e => { - utils.log(LT.LOG, `Messaging GM ${e}`); - // If its too big, collapse it into a .txt file and send that instead. - const b = await new Blob([returnText as BlobPart], { "type": "text" }); - - if (b.size > 8388290) { - // Update return text - // todo: embedify - returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}\nFull details could not be attached to this messaged as a \`.txt\` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages instead of bundled into one.`; - - // Attempt to DM the GMs and send a warning if it could not DM a GM - await sendDirectMessage(BigInt(e.substr(2, (e.length - 3))), returnText).catch(() => { - message.send(generateDMFailed(e)); - }); - } else { - // Update return - // todo: embedify - returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}\nFull details have been attached to this messaged as a \`.txt\` file for verification purposes.`; - - // Attempt to DM the GMs and send a warning if it could not DM a GM - await sendDirectMessage(BigInt(e.substr(2, (e.length - 3))), { "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }).catch(() => { - message.send(generateDMFailed(e)); - }); - } - }); - - // Finally send the text - m.edit(normalText); - - if (DEVMODE && config.logRolls) { - // If enabled, log rolls so we can verify the bots math - dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,0)", [originalCommand, returnText, m.id]).catch(e => { - utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); - }); - } - } else { - // When not a GM roll, make sure the message is not too big - if (returnText.length > 2000) { - // If its too big, collapse it into a .txt file and send that instead. - const b = await new Blob([returnText as BlobPart], { "type": "text" }); - - if (b.size > 8388290) { - // Update return text - returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}\nDetails have been ommitted from this message for being over 2000 characters. Full details could not be attached to this messaged as a \`.txt\` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages instead of bundled into one.`; - - // Send the results - m.edit(returnText); - } else { - // Update return text - returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}\nDetails have been ommitted from this message for being over 2000 characters. Full details have been attached to this messaged as a \`.txt\` file for verification purposes.`; - - // Remove the original message to send new one with attachment - m.delete(); - - // todo: embedify - await message.send({ "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }); - } - } else { - // Finally send the text - m.edit(returnText); - } - - if (DEVMODE && config.logRolls) { - // If enabled, log rolls so we can verify the bots math - dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,0)", [originalCommand, returnText, m.id]).catch(e => { - utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); - }); - } - } - } catch (e) { - utils.log(LT.ERROR, `Undandled Error: ${JSON.stringify(e)}`); - } + else if (command && (`${command}${args.join("")}`).indexOf(config.postfix) > -1) { + commands.roll(message, args, command); } // [[emoji or [[emojialias // Check if the unhandled command is an emoji request - else { - // Start looping thru the possible emojis - config.emojis.some((emoji: EmojiConf) => { - utils.log(LT.LOG, `Checking if command was emoji ${JSON.stringify(emoji)}`); - // If a match gets found - if (emoji.aliases.indexOf(command || "") > -1) { - // Light telemetry to see how many times a command is being run - dbClient.execute(`CALL INC_CNT("emojis");`).catch(e => { - utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); - }); - - // Send the needed emoji1 - message.send(`<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>`).catch(e => { - utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - // And attempt to delete if needed - if (emoji.deleteSender) { - message.delete().catch(e => { - utils.log(LT.WARN, `Failed to delete message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); - }); - } - return true; - } - }); + else if (command) { + commands.emoji(message, command); } } } @@ -658,5 +190,5 @@ if (DEBUG) { // Start up the API for rolling from third party apps (like excel macros) if (config.api.enable) { - api.start(dbClient); + api.start(); } diff --git a/src/api.ts b/src/api.ts index 7008a44..8aece4d 100644 --- a/src/api.ts +++ b/src/api.ts @@ -9,9 +9,6 @@ import { cache, CreateMessage, sendMessage, sendDirectMessage, - // MySQL Driver deps - Client, - // httpd deps Status, STATUS_TEXT, @@ -19,6 +16,7 @@ import { nanoid } from "../deps.ts"; +import { dbClient } from "./db.ts"; import solver from "./solver.ts"; import { LogTypes as LT } from "./utils.enums.ts"; import utils from "./utils.ts"; @@ -30,7 +28,7 @@ import config from "../config.ts"; // start(databaseClient) returns nothing // start initializes and runs the entire API for the bot -const start = async (dbClient: Client): Promise => { +const start = async (): Promise => { const server = Deno.listen({ port: config.api.port }); utils.log(LT.INFO, `HTTP api running at: http://localhost:${config.api.port}/`); diff --git a/src/commands/_index.ts b/src/commands/_index.ts new file mode 100644 index 0000000..72b1dcc --- /dev/null +++ b/src/commands/_index.ts @@ -0,0 +1,27 @@ +import { ping } from "./ping.ts"; +import { rip } from "./rip.ts"; +import { rollHelp } from "./rollHelp.ts"; +import { help } from "./help.ts"; +import { info } from "./info.ts"; +import { privacy } from "./privacy.ts"; +import { version } from "./version.ts"; +import { report } from "./report.ts"; +import { stats } from "./stats.ts"; +import { api } from "./apiCmd.ts"; +import { emoji } from "./emoji.ts"; +import { roll } from "./roll.ts"; + +export default { + ping, + rip, + rollHelp, + help, + info, + privacy, + version, + report, + stats, + api, + emoji, + roll +}; diff --git a/src/commands/apiCmd.ts b/src/commands/apiCmd.ts new file mode 100644 index 0000000..cfc9070 --- /dev/null +++ b/src/commands/apiCmd.ts @@ -0,0 +1,55 @@ +import { dbClient } from "../db.ts"; +import { DiscordenoMessage, hasGuildPermissions } from "../../deps.ts"; +import apiCommands from "./apiCmd/_apiIndex.ts"; +import utils from "../utils.ts"; +import { constantCmds } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const api = async (message: DiscordenoMessage, args: string[]) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("api");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + // Local apiArg in lowercase + const apiArg = (args[0] || "help").toLowerCase(); + + // Alert users who DM the bot that this command is for guilds only + if (message.guildId === 0n) { + message.send(constantCmds.apiGuildOnly).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); + return; + } + + // Makes sure the user is authenticated to run the API command + if (await hasGuildPermissions(message.authorId, message.guildId, ["ADMINISTRATOR"])) { + // [[api help + // Shows API help details + if (apiArg === "help") { + apiCommands.help(message); + } + + // [[api allow/block + // Lets a guild admin allow or ban API rolls from happening in said guild + else if (apiArg === "allow" || apiArg === "block" || apiArg === "enable" || apiArg === "disable") { + apiCommands.allowBlock(message, apiArg); + } + + // [[api delete + // Lets a guild admin delete their server from the database + else if (apiArg === "delete") { + apiCommands.deleteGuild(message); + } + + // [[api status + // Lets a guild admin check the status of API rolling in said guild + else if (apiArg === "status") { + apiCommands.status(message); + } + } else { + message.send(constantCmds.apiPermError).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); + } +}; diff --git a/src/commands/apiCmd/_apiIndex.ts b/src/commands/apiCmd/_apiIndex.ts new file mode 100644 index 0000000..263dff4 --- /dev/null +++ b/src/commands/apiCmd/_apiIndex.ts @@ -0,0 +1,11 @@ +import { help } from "./apiHelp.ts"; +import { allowBlock } from "./allowBlock.ts"; +import { deleteGuild } from "./deleteGuild.ts"; +import { status } from "./status.ts"; + +export default { + help, + allowBlock, + deleteGuild, + status +}; diff --git a/src/commands/apiCmd/allowBlock.ts b/src/commands/apiCmd/allowBlock.ts new file mode 100644 index 0000000..adf3c55 --- /dev/null +++ b/src/commands/apiCmd/allowBlock.ts @@ -0,0 +1,39 @@ +import { dbClient } from "../../db.ts"; +import { DiscordenoMessage } from "../../../deps.ts"; +import utils from "../../utils.ts"; +import { generateApiFailed, generateApiSuccess } from "../../constantCmds.ts"; +import { LogTypes as LT } from "../../utils.enums.ts"; + +export const allowBlock = async (message: DiscordenoMessage, apiArg: string) => { + const guildQuery = await dbClient.query(`SELECT guildid FROM allowed_guilds WHERE guildid = ?`, [message.guildId]).catch(e0 => { + utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e0)}`); + message.send(generateApiFailed(apiArg)).catch(e1 => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); + }); + return; + }); + + if (guildQuery.length === 0) { + // Since guild is not in our DB, add it in + await dbClient.execute(`INSERT INTO allowed_guilds(guildid,active) values(?,?)`, [BigInt(message.guildId), ((apiArg === "allow" || apiArg === "enable") ? 1 : 0)]).catch(e0 => { + utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e0)}`); + message.send(generateApiFailed(apiArg)).catch(e1 => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); + }); + return; + }); + } else { + // Since guild is in our DB, update it + await dbClient.execute(`UPDATE allowed_guilds SET active = ? WHERE guildid = ?`, [((apiArg === "allow" || apiArg === "enable") ? 1 : 0), BigInt(message.guildId)]).catch(e0 => { + utils.log(LT.ERROR, `Failed to update DB: ${JSON.stringify(e0)}`); + message.send(generateApiFailed(apiArg)).catch(e1 => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); + }); + return; + }); + } + // We won't get here if there's any errors, so we know it has bee successful, so report as such + message.send(generateApiSuccess(apiArg)).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/apiCmd/apiHelp.ts b/src/commands/apiCmd/apiHelp.ts new file mode 100644 index 0000000..bdf6f0b --- /dev/null +++ b/src/commands/apiCmd/apiHelp.ts @@ -0,0 +1,10 @@ +import { DiscordenoMessage } from "../../../deps.ts"; +import utils from "../../utils.ts"; +import { constantCmds } from "../../constantCmds.ts"; +import { LogTypes as LT } from "../../utils.enums.ts"; + +export const help = (message: DiscordenoMessage) => { + message.send(constantCmds.apiHelp).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/apiCmd/deleteGuild.ts b/src/commands/apiCmd/deleteGuild.ts new file mode 100644 index 0000000..3d54fc6 --- /dev/null +++ b/src/commands/apiCmd/deleteGuild.ts @@ -0,0 +1,20 @@ +import { dbClient } from "../../db.ts"; +import { DiscordenoMessage } from "../../../deps.ts"; +import utils from "../../utils.ts"; +import { constantCmds } from "../../constantCmds.ts"; +import { LogTypes as LT } from "../../utils.enums.ts"; + +export const deleteGuild = async (message: DiscordenoMessage) => { + await dbClient.execute(`DELETE FROM allowed_guilds WHERE guildid = ?`, [message.guildId]).catch(e0 => { + utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e0)}`); + message.send(constantCmds.apiDeleteFail).catch(e1 => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); + }); + return; + }); + + // We won't get here if there's any errors, so we know it has bee successful, so report as such + message.send(constantCmds.apiRemoveGuild).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/apiCmd/status.ts b/src/commands/apiCmd/status.ts new file mode 100644 index 0000000..553d536 --- /dev/null +++ b/src/commands/apiCmd/status.ts @@ -0,0 +1,35 @@ +import { dbClient } from "../../db.ts"; +import { DiscordenoMessage } from "../../../deps.ts"; +import utils from "../../utils.ts"; +import { constantCmds, generateApiStatus } from "../../constantCmds.ts"; +import { LogTypes as LT } from "../../utils.enums.ts"; + +export const status = async (message: DiscordenoMessage) => { + // Get status of guild from the db + const guildQuery = await dbClient.query(`SELECT active, banned FROM allowed_guilds WHERE guildid = ?`, [message.guildId]).catch(e0 => { + utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e0)}`); + message.send(constantCmds.apiStatusFail).catch(e1 => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e1)}`); + }); + return; + }); + + // Check if we got an item back or not + if (guildQuery.length > 0) { + // Check if guild is banned from using API and return appropriate message + if (guildQuery[0].banned) { + message.send(generateApiStatus(true, false)).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); + } else { + message.send(generateApiStatus(false, guildQuery[0].active)).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); + } + } else { + // Guild is not in DB, therefore they are blocked + message.send(generateApiStatus(false, false)).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); + } +}; diff --git a/src/commands/emoji.ts b/src/commands/emoji.ts new file mode 100644 index 0000000..07f3669 --- /dev/null +++ b/src/commands/emoji.ts @@ -0,0 +1,32 @@ +import config from "../../config.ts"; +import { dbClient } from "../db.ts"; +import { DiscordenoMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; +import { EmojiConf } from "../mod.d.ts"; + +export const emoji = (message: DiscordenoMessage, command: string) => { + // Start looping thru the possible emojis + config.emojis.some((emoji: EmojiConf) => { + utils.log(LT.LOG, `Checking if command was emoji ${JSON.stringify(emoji)}`); + // If a match gets found + if (emoji.aliases.indexOf(command || "") > -1) { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("emojis");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + // Send the needed emoji1 + message.send(`<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>`).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); + // And attempt to delete if needed + if (emoji.deleteSender) { + message.delete().catch(e => { + utils.log(LT.WARN, `Failed to delete message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); + } + return true; + } + }); +}; diff --git a/src/commands/help.ts b/src/commands/help.ts new file mode 100644 index 0000000..2229528 --- /dev/null +++ b/src/commands/help.ts @@ -0,0 +1,16 @@ +import { dbClient } from "../db.ts"; +import { DiscordenoMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { constantCmds } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const help = (message: DiscordenoMessage) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("help");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + message.send(constantCmds.help).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/info.ts b/src/commands/info.ts new file mode 100644 index 0000000..53615d3 --- /dev/null +++ b/src/commands/info.ts @@ -0,0 +1,16 @@ +import { dbClient } from "../db.ts"; +import { DiscordenoMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { constantCmds } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const info = (message: DiscordenoMessage) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("info");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + message.send(constantCmds.info).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/ping.ts b/src/commands/ping.ts new file mode 100644 index 0000000..bdb0bb5 --- /dev/null +++ b/src/commands/ping.ts @@ -0,0 +1,20 @@ +import { dbClient } from "../db.ts"; +import { DiscordenoMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { generatePing } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const ping = async (message: DiscordenoMessage) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("ping");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + // Calculates ping between sending a message and editing it, giving a nice round-trip latency. + try { + const m = await message.send(generatePing(-1)); + m.edit(generatePing(m.timestamp - message.timestamp)); + } catch (e) { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + } +}; diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts new file mode 100644 index 0000000..344a2a4 --- /dev/null +++ b/src/commands/privacy.ts @@ -0,0 +1,16 @@ +import { dbClient } from "../db.ts"; +import { DiscordenoMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { constantCmds } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const privacy = (message: DiscordenoMessage) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("privacy");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + message.send(constantCmds.privacy).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/report.ts b/src/commands/report.ts new file mode 100644 index 0000000..6bbf162 --- /dev/null +++ b/src/commands/report.ts @@ -0,0 +1,20 @@ +import config from "../../config.ts"; +import { dbClient } from "../db.ts"; +import { DiscordenoMessage, sendMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { constantCmds, generateReport } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const report = (message: DiscordenoMessage, args: string[]) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("report");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + sendMessage(config.reportChannel, generateReport(args.join(" "))).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); + message.send(constantCmds.report).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/rip.ts b/src/commands/rip.ts new file mode 100644 index 0000000..7b27f7d --- /dev/null +++ b/src/commands/rip.ts @@ -0,0 +1,16 @@ +import { dbClient } from "../db.ts"; +import { DiscordenoMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { constantCmds } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const rip = (message: DiscordenoMessage) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("rip");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + message.send(constantCmds.rip).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/roll.ts b/src/commands/roll.ts new file mode 100644 index 0000000..7ceb085 --- /dev/null +++ b/src/commands/roll.ts @@ -0,0 +1,146 @@ +import config from "../../config.ts"; +import { DEVMODE } from "../../flags.ts"; +import { dbClient } from "../db.ts"; +import { DiscordenoMessage, sendDirectMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import solver from "../solver.ts"; +import { constantCmds, generateDMFailed } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; +import rollFuncs from "./roll/_rollIndex.ts"; + +export const roll = async (message: DiscordenoMessage, args: string[], command: string) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("roll");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + // If DEVMODE is on, only allow this command to be used in the devServer + if (DEVMODE && message.guildId !== config.devServer) { + message.send(constantCmds.indev).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); + return; + } + + // Rest of this command is in a try-catch to protect all sends/edits from erroring out + try { + const originalCommand = `${config.prefix}${command} ${args.join(" ")}`; + + const m = await message.send(constantCmds.rolling); + + // Get modifiers from command + const modifiers = rollFuncs.getModifiers(m, args, command, originalCommand); + + // Return early if the modifiers were invalid + if (!modifiers.valid) { + return; + } + + // Rejoin all of the args and send it into the solver, if solver returns a falsy item, an error object will be substituded in + const rollCmd = `${command} ${args.join(" ")}`; + const returnmsg = solver.parseRoll(rollCmd, config.prefix, config.postfix, modifiers.maxRoll, modifiers.nominalRoll, modifiers.order) || { error: true, errorCode: "EmptyMessage", errorMsg: "Error: Empty message", line1: "", line2: "", line3: "" }; + + let returnText = ""; + + // If there was an error, report it to the user in hopes that they can determine what they did wrong + if (returnmsg.error) { + returnText = returnmsg.errorMsg; + m.edit(returnText); + + if (DEVMODE && config.logRolls) { + // If enabled, log rolls so we can verify the bots math + dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,1)", [originalCommand, returnmsg.errorCode, m.id]).catch(e => { + utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); + }); + } + return; + } else { + // Else format the output using details from the solver + returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}`; + + if (modifiers.noDetails) { + returnText += "\nDetails suppressed by -nd flag."; + } else { + returnText += `\nDetails:\n${modifiers.spoiler}${returnmsg.line3}${modifiers.spoiler}`; + } + } + + // If the roll was a GM roll, send DMs to all the GMs + if (modifiers.gmRoll) { + // Make a new return line to be sent to the roller + const normalText = `<@${message.authorId}>${returnmsg.line1}\nResults have been messaged to the following GMs: ${modifiers.gms.join(" ")}`; + + // And message the full details to each of the GMs, alerting roller of every GM that could not be messaged + modifiers.gms.forEach(async e => { + utils.log(LT.LOG, `Messaging GM ${e}`); + // If its too big, collapse it into a .txt file and send that instead. + const b = await new Blob([returnText as BlobPart], { "type": "text" }); + + if (b.size > 8388290) { + // Update return text + // todo: embedify + returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}\nFull details could not be attached to this messaged as a \`.txt\` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages instead of bundled into one.`; + + // Attempt to DM the GMs and send a warning if it could not DM a GM + await sendDirectMessage(BigInt(e.substr(2, (e.length - 3))), returnText).catch(() => { + message.send(generateDMFailed(e)); + }); + } else { + // Update return + // todo: embedify + returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}\nFull details have been attached to this messaged as a \`.txt\` file for verification purposes.`; + + // Attempt to DM the GMs and send a warning if it could not DM a GM + await sendDirectMessage(BigInt(e.substr(2, (e.length - 3))), { "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }).catch(() => { + message.send(generateDMFailed(e)); + }); + } + }); + + // Finally send the text + m.edit(normalText); + + if (DEVMODE && config.logRolls) { + // If enabled, log rolls so we can verify the bots math + dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,0)", [originalCommand, returnText, m.id]).catch(e => { + utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); + }); + } + } else { + // When not a GM roll, make sure the message is not too big + if (returnText.length > 2000) { + // If its too big, collapse it into a .txt file and send that instead. + const b = await new Blob([returnText as BlobPart], { "type": "text" }); + + if (b.size > 8388290) { + // Update return text + returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}\nDetails have been ommitted from this message for being over 2000 characters. Full details could not be attached to this messaged as a \`.txt\` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages instead of bundled into one.`; + + // Send the results + m.edit(returnText); + } else { + // Update return text + returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}\nDetails have been ommitted from this message for being over 2000 characters. Full details have been attached to this messaged as a \`.txt\` file for verification purposes.`; + + // Remove the original message to send new one with attachment + m.delete(); + + // todo: embedify + await message.send({ "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }); + } + } else { + // Finally send the text + m.edit(returnText); + } + + if (DEVMODE && config.logRolls) { + // If enabled, log rolls so we can verify the bots math + dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,0)", [originalCommand, returnText, m.id]).catch(e => { + utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); + }); + } + } + } catch (e) { + utils.log(LT.ERROR, `Undandled Error: ${JSON.stringify(e)}`); + } +}; diff --git a/src/commands/roll/_rollIndex.ts b/src/commands/roll/_rollIndex.ts new file mode 100644 index 0000000..efd6fdc --- /dev/null +++ b/src/commands/roll/_rollIndex.ts @@ -0,0 +1,5 @@ +import { getModifiers } from "./getModifiers.ts"; + +export default { + getModifiers +}; \ No newline at end of file diff --git a/src/commands/roll/getModifiers.ts b/src/commands/roll/getModifiers.ts new file mode 100644 index 0000000..16d1f2e --- /dev/null +++ b/src/commands/roll/getModifiers.ts @@ -0,0 +1,116 @@ +import config from "../../../config.ts"; +import { DEVMODE } from "../../../flags.ts"; +import { dbClient } from "../../db.ts"; +import { DiscordenoMessage } from "../../../deps.ts"; +import utils from "../../utils.ts"; +import { LogTypes as LT } from "../../utils.enums.ts"; +import { RollModifiers } from "../../mod.d.ts"; + +export const getModifiers = (m: DiscordenoMessage, args: string[], command: string, originalCommand: string): RollModifiers => { + const modifiers: RollModifiers = { + noDetails: false, + spoiler: "", + maxRoll: false, + nominalRoll: false, + gmRoll: false, + gms: [], + order: "", + valid: false + }; + + // Check if any of the args are command flags and pull those out into the modifiers object + for (let i = 0; i < args.length; i++) { + utils.log(LT.LOG, `Checking ${command}${args.join(" ")} for command modifiers ${i}`); + switch (args[i].toLowerCase()) { + case "-nd": + modifiers.noDetails = true; + + args.splice(i, 1); + i--; + break; + case "-s": + modifiers.spoiler = "||"; + + args.splice(i, 1); + i--; + break; + case "-m": + modifiers.maxRoll = true; + + args.splice(i, 1); + i--; + break; + case "-n": + modifiers.nominalRoll = true; + + args.splice(i, 1); + i--; + break; + case "-gm": + modifiers.gmRoll = true; + + // -gm is a little more complex, as we must get all of the GMs that need to be DMd + while (((i + 1) < args.length) && args[i + 1].startsWith("<@")) { + utils.log(LT.LOG, `Finding all GMs, checking args ${JSON.stringify(args)}`); + // Keep looping thru the rest of the args until one does not start with the discord mention code + modifiers.gms.push(args[i + 1].replace(/[!]/g, "")); + args.splice((i + 1), 1); + } + if (modifiers.gms.length < 1) { + // If -gm is on and none were found, throw an error + m.edit("Error: Must specifiy at least one GM by mentioning them"); + + if (DEVMODE && config.logRolls) { + // If enabled, log rolls so we can verify the bots math + dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,1)", [originalCommand, "NoGMsFound", m.id]).catch(e => { + utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); + }); + } + return modifiers; + } + + args.splice(i, 1); + i--; + break; + case "-o": + args.splice(i, 1); + + if (args[i].toLowerCase()[0] !== "d" && args[i].toLowerCase()[0] !== "a") { + // If -o is on and asc or desc was not specified, error out + m.edit("Error: Must specifiy a or d to order the rolls ascending or descending"); + + if (DEVMODE && config.logRolls) { + // If enabled, log rolls so we can verify the bots math + dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,1)", [originalCommand, "NoOrderFound", m.id]).catch(e => { + utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); + }); + } + return modifiers; + } + + modifiers.order = args[i].toLowerCase()[0]; + + args.splice(i, 1); + i--; + break; + default: + break; + } + } + + // maxRoll and nominalRoll cannot both be on, throw an error + if (modifiers.maxRoll && modifiers.nominalRoll) { + m.edit("Error: Cannot maximise and nominise the roll at the same time"); + + if (DEVMODE && config.logRolls) { + // If enabled, log rolls so we can verify the bots math + dbClient.execute("INSERT INTO roll_log(input,result,resultid,api,error) values(?,?,?,0,1)", [originalCommand, "MaxAndNominal", m.id]).catch(e => { + utils.log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); + }); + } + return modifiers; + } + + modifiers.valid = true; + return modifiers; +}; diff --git a/src/commands/rollHelp.ts b/src/commands/rollHelp.ts new file mode 100644 index 0000000..5406098 --- /dev/null +++ b/src/commands/rollHelp.ts @@ -0,0 +1,16 @@ +import { dbClient } from "../db.ts"; +import { DiscordenoMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { constantCmds } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const rollHelp = (message: DiscordenoMessage) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("rollhelp");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + message.send(constantCmds.rollHelp).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/stats.ts b/src/commands/stats.ts new file mode 100644 index 0000000..2917961 --- /dev/null +++ b/src/commands/stats.ts @@ -0,0 +1,29 @@ +import { dbClient } from "../db.ts"; +import { cache, cacheHandlers, DiscordenoMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { generateStats } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const stats = async (message: DiscordenoMessage) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("stats");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + // Calculate how many times commands have been run + const rollQuery = await dbClient.query(`SELECT count FROM command_cnt WHERE command = "roll";`).catch(e => { + utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e)}`); + }); + const totalQuery = await dbClient.query(`SELECT SUM(count) as count FROM command_cnt;`).catch(e => { + utils.log(LT.ERROR, `Failed to query DB: ${JSON.stringify(e)}`); + }); + const rolls = BigInt(rollQuery[0].count); + const total = BigInt(totalQuery[0].count); + + const cachedGuilds = await cacheHandlers.size("guilds"); + const cachedChannels = await cacheHandlers.size("channels"); + const cachedMembers = await cacheHandlers.size("members"); + message.send(generateStats(cachedGuilds + cache.dispatchedGuildIds.size, cachedChannels + cache.dispatchedChannelIds.size, cachedMembers, rolls, total - rolls)).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/commands/version.ts b/src/commands/version.ts new file mode 100644 index 0000000..0b98f59 --- /dev/null +++ b/src/commands/version.ts @@ -0,0 +1,16 @@ +import { dbClient } from "../db.ts"; +import { DiscordenoMessage } from "../../deps.ts"; +import utils from "../utils.ts"; +import { constantCmds } from "../constantCmds.ts"; +import { LogTypes as LT } from "../utils.enums.ts"; + +export const version = (message: DiscordenoMessage) => { + // Light telemetry to see how many times a command is being run + dbClient.execute(`CALL INC_CNT("version");`).catch(e => { + utils.log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`); + }); + + message.send(constantCmds.version).catch(e => { + utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`); + }); +}; diff --git a/src/constantCmds.ts b/src/constantCmds.ts index 97e37db..93de93e 100644 --- a/src/constantCmds.ts +++ b/src/constantCmds.ts @@ -201,47 +201,47 @@ export const constantCmds = { inline: true }, { name: "dz or dlz [Optional]", - value: "Drops the lowest z dice, cannot be used with kz", + value: "Drops the lowest `z` dice, cannot be used with kz", inline: true }, { name: "kz or khz [Optional]", - value: "Keeps the highest z dice, cannot be used with dz", + value: "Keeps the highest `z` dice, cannot be used with dz", inline: true }, { name: "dhz [Optional]", - value: "Drops the highest z dice, cannot be used with kz", + value: "Drops the highest `z` dice, cannot be used with kz", inline: true }, { name: "klz [Optional]", - value: "Keeps the lowest z dice, cannot be used with dz", + value: "Keeps the lowest `z` dice, cannot be used with dz", inline: true }, { name: "ra [Optional]", - value: "Rerolls any rolls that match a, r3 will reroll any dice that land on 3, throwing out old rolls", + value: "Rerolls any rolls that match `a`, r3 will reroll any dice that land on 3, throwing out old rolls", inline: true }, { name: "csq or cs=q [Optional]", - value: "Changes crit score to q", + value: "Changes crit score to `q`", inline: true }, { name: "csq [Optional]", - value: "Changes crit score to be greater than or equal to q", + value: "Changes crit score to be greater than or equal to `q`", inline: true }, { name: "cfq or cf=q [Optional]", - value: "Changes crit fail to q", + value: "Changes crit fail to `q`", inline: true }, { name: "cfq [Optional]", - value: "Changes crit fail to be greater than or equal to q", + value: "Changes crit fail to be greater than or equal to `q`", inline: true }, { name: "! [Optional]", @@ -288,7 +288,7 @@ export const constantCmds = { }, rolling: { embeds: [{ - title: "Rolling. . ." + title: "Rolling . . ." }] }, version: { diff --git a/src/db.ts b/src/db.ts new file mode 100644 index 0000000..cf7b7ce --- /dev/null +++ b/src/db.ts @@ -0,0 +1,11 @@ +import config from "../config.ts"; +import { Client } from "../deps.ts"; +import { LOCALMODE } from "../flags.ts"; + +export const dbClient = await new Client().connect({ + hostname: LOCALMODE ? config.db.localhost : config.db.host, + port: config.db.port, + db: config.db.name, + username: config.db.username, + password: config.db.password +}); diff --git a/src/mod.d.ts b/src/mod.d.ts index 9470dd6..5962a2a 100644 --- a/src/mod.d.ts +++ b/src/mod.d.ts @@ -8,3 +8,14 @@ export type EmojiConf = { animated: boolean, deleteSender: boolean }; + +export type RollModifiers = { + noDetails: boolean, + spoiler: string, + maxRoll: boolean, + nominalRoll: boolean, + gmRoll: boolean, + gms: string[], + order: string, + valid: boolean +}