V1.4.3 - Minor Update + Bugfixes

All files have received a handful of debug messages, this is to aid in finding the source of some recent crashes and to help find future issues
config.example.ts, */index.html, README.md - version number bump
utils.ts - Console will only get spammed by DEBUG level messages if artificer is in DEBUG mode.  DEBUG statements are still logged to file
solver.ts - actually set the original index on the rolls to allow drop/keep to reset the order correctly, change return text to use localPrefix and remove code blocks
mod.ts - breaks args on \n now, -o arg now allows full words instead of just o or a, error checking on file size to make sure the results always send and details send when possible
api.ts - added error checking on file size to make sure the results always send and details send when possible
This commit is contained in:
Ean Milligan (Bastion) 2021-03-28 00:29:06 -04:00
parent d449d1d85d
commit 15864e7f6d
9 changed files with 124 additions and 90 deletions

View File

@ -1,5 +1,5 @@
# The Artificer - A Dice Rolling Discord Bot
Version 1.4.2 - 2021/02/14
Version 1.4.3 - 2021/03/21
The Artificer is a Discord bot that specializes in rolling dice. The bot utilizes the compact [Roll20 formatting](https://artificer.eanm.dev/roll20) for ease of use and will correctly perform any needed math on the roll (limited to basic algebra).

View File

@ -1,6 +1,6 @@
export const config = {
"name": "The Artificer", // Name of the bot
"version": "1.4.2", // Version of the bot
"version": "1.4.3", // Version of the bot
"token": "the_bot_token", // Discord API Token for this bot
"localtoken": "local_testing_token", // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token"
"prefix": "[[", // Prefix for all commands

40
mod.ts
View File

@ -52,6 +52,7 @@ startBot({
// Interval to rotate the status text every 30 seconds to show off more commands
setInterval(() => {
utils.log(LT.LOG, "Changing bot status");
try {
// Wrapped in try-catch due to hard crash possible
editBotsStatus(StatusTypes.Online, intervals.getRandomStatus(cache), ActivityType.Game);
@ -61,7 +62,10 @@ startBot({
}, 30000);
// Interval to update bot list stats every 24 hours
LOCALMODE ? utils.log(LT.INFO, "updateListStatistics not running") : setInterval(() => intervals.updateListStatistics(botID, cache.guilds.size), 86400000);
LOCALMODE ? utils.log(LT.INFO, "updateListStatistics not running") : setInterval(() => {
utils.log(LT.LOG, "Updating all bot lists statistics");
intervals.updateListStatistics(botID, cache.guilds.size);
}, 86400000);
// setTimeout added to make sure the startup message does not error out
setTimeout(() => {
@ -73,11 +77,13 @@ startBot({
}, 1000);
},
guildCreate: (guild: Guild) => {
utils.log(LT.LOG, `Handling joining guild ${JSON.stringify(guild)}`);
sendMessage(config.logChannel, `New guild joined: ${guild.name} (id: ${guild.id}). This guild has ${guild.memberCount} members!`).catch(e => {
utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(e)}`);
});
},
guildDelete: (guild: Guild) => {
utils.log(LT.LOG, `Handling leaving guild ${JSON.stringify(guild)}`);
sendMessage(config.logChannel, `I have been removed from: ${guild.name} (id: ${guild.id}).`).catch(e => {
utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(e)}`);
});
@ -90,8 +96,10 @@ startBot({
// Ignore all messages that are not commands
if (message.content.indexOf(config.prefix) !== 0) return;
utils.log(LT.LOG, `Handling message ${JSON.stringify(message)}`);
// Split into standard command + args format
const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
const args = message.content.slice(config.prefix.length).trim().split(/[ \n]+/g);
const command = args.shift()?.toLowerCase();
// All commands below here
@ -126,7 +134,7 @@ startBot({
});
}
// [[rollhelp or [[rh or [[hr
// [[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
@ -384,6 +392,7 @@ startBot({
// 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;
@ -414,6 +423,7 @@ startBot({
// -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);
@ -437,7 +447,7 @@ startBot({
case "-o":
args.splice(i, 1);
if (args[i].toLowerCase() !== "d" && args[i].toLowerCase() !== "a") {
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");
@ -450,7 +460,7 @@ startBot({
return;
}
modifiers.order = args[i].toLowerCase();
modifiers.order = args[i].toLowerCase()[0];
args.splice(i, 1);
i--;
@ -509,9 +519,19 @@ startBot({
// 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
returnText = "<@" + message.author.id + ">" + 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(e.substr(2, (e.length - 3)), returnText).catch(() => {
utils.sendIndirectMessage(message, "WARNING: " + e + " could not be messaged. If this issue persists, make sure direct messages are allowed from this server.", sendMessage, sendDirectMessage);
});
} else {
// Update return text
returnText = "<@" + message.author.id + ">" + returnmsg.line1 + "\n" + returnmsg.line2 + "\nFull details have been attached to this messaged as a `.txt` file for verification purposes.";
@ -519,6 +539,7 @@ startBot({
await sendDirectMessage(e.substr(2, (e.length - 3)), { "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }).catch(() => {
utils.sendIndirectMessage(message, "WARNING: " + e + " could not be messaged. If this issue persists, make sure direct messages are allowed from this server.", sendMessage, sendDirectMessage);
});
}
});
// Finally send the text
@ -536,6 +557,13 @@ startBot({
// 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.author.id + ">" + 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.author.id + ">" + 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.";
@ -543,6 +571,7 @@ startBot({
m.delete();
await utils.sendIndirectMessage(message, { "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }, sendMessage, sendDirectMessage);
}
} else {
// Finally send the text
m.edit(returnText);
@ -565,6 +594,7 @@ startBot({
else {
// Start looping thru the possible emojis
config.emojis.some((emoji: EmojiConf) => {
utils.log(LT.LOG, `Checking if command was emoji ${emoji}`);
// If a match gets found
if (emoji.aliases.indexOf(command || "") > -1) {
// Light telemetry to see how many times a command is being run

View File

@ -29,7 +29,7 @@ import config from "../config.ts";
// start initializes and runs the entire API for the bot
const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string, m: (string | MessageContent)) => Promise<Message>, sendDirectMessage: (c: string, m: (string | MessageContent)) => Promise<Message>): Promise<void> => {
const server = serve({ hostname: "0.0.0.0", port: config.api.port });
utils.log(LT.LOG, `HTTP api running at: http://localhost:${config.api.port}/`);
utils.log(LT.INFO, `HTTP api running at: http://localhost:${config.api.port}/`);
// rateLimitTime holds all users with the last time they started a rate limit timer
const rateLimitTime = new Map<string, number>();
@ -38,6 +38,7 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
// Catching every request made to the server
for await (const request of server) {
utils.log(LT.LOG, `Handling request: ${JSON.stringify(request)}`);
// Check if user is authenticated to be using this API
let authenticated = false;
let rateLimited = false;
@ -90,6 +91,7 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
const query = new Map<string, string>();
if (tempQ !== undefined) {
tempQ.split("&").forEach(e => {
utils.log(LT.LOG, `Breaking down request query: ${request} ${e}`);
const [option, params] = e.split("=");
query.set(option.toLowerCase(), params);
});
@ -279,6 +281,7 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
// Make a new return line to be sent to the roller
let normalText = apiPrefix + "<@" + query.get("user") + ">" + returnmsg.line1 + "\nResults have been messaged to the following GMs: ";
gms.forEach(e => {
utils.log(LT.LOG, `Appending GM ${e} to roll text`);
normalText += "<@" + e + "> ";
});
@ -295,16 +298,24 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
});
}
// And message the full details to each of the GMs, alerting roller of every GM that could not be messaged
gms.forEach(async e => {
const newMessage: MessageContent = {};
// 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 = apiPrefix + "<@" + query.get("user") + ">" + returnmsg.line1 + "\n" + returnmsg.line2 + "\nFull details have been attached to this messaged as a `.txt` file for verification purposes.";
newMessage.content = apiPrefix + "<@" + query.get("user") + ">" + 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.";
} else {
// Update return text
newMessage.content = apiPrefix + "<@" + query.get("user") + ">" + returnmsg.line1 + "\n" + returnmsg.line2 + "\nFull details have been attached to this messaged as a `.txt` file for verification purposes.";
newMessage.file = { "blob": b, "name": "rollDetails.txt" };
}
// And message the full details to each of the GMs, alerting roller of every GM that could not be messaged
gms.forEach(async e => {
utils.log(LT.LOG, `Messaging GM ${e} roll results`);
// Attempt to DM the GMs and send a warning if it could not DM a GM
await sendDirectMessage(e, { "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }).catch(async () => {
await sendDirectMessage(e, newMessage).catch(async () => {
const failedSend = "WARNING: <@" + e + "> could not be messaged. If this issue persists, make sure direct messages are allowed from this server."
// Send the return message as a DM or normal message depening on if the channel is set
if ((query.get("channel") || "").length > 0) {
@ -342,13 +353,15 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
// 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 = "<@" + query.get("user") + ">" + 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.";
// Set info into the newMessage
newMessage.content = returnText;
newMessage.content = apiPrefix + "<@" + query.get("user") + ">" + 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.";
} else {
// Update return text
newMessage.content = apiPrefix + "<@" + query.get("user") + ">" + 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.";
newMessage.file = { "blob": b, "name": "rollDetails.txt" };
}
}
// Send the return message as a DM or normal message depening on if the channel is set
if ((query.get("channel") || "").length > 0) {
@ -672,6 +685,7 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
const query = new Map<string, string>();
if (tempQ !== undefined) {
tempQ.split("&").forEach(e => {
utils.log(LT.LOG, `Parsing request query #2 ${request} ${e}`);
const [option, params] = e.split("=");
query.set(option.toLowerCase(), params);
});

View File

@ -39,6 +39,7 @@ const getRandomStatus = (cache: CacheData): string => {
// Sends the current server count to all bot list sites we are listed on
const updateListStatistics = (botID: string, serverCount: number): void => {
config.botLists.forEach(async e => {
utils.log(LT.LOG, `Updating statistics for ${JSON.stringify(e)}`)
if (e.enabled) {
const tempHeaders = new Headers();
tempHeaders.append(e.headers[0].header, e.headers[0].value);
@ -49,7 +50,7 @@ const updateListStatistics = (botID: string, serverCount: number): void => {
"headers": tempHeaders,
"body": JSON.stringify(e.body).replace('"?{server_count}"', serverCount.toString()) // ?{server_count} needs the "" removed from around it aswell to make sure its sent as a number
});
utils.log(LT.LOG, `${JSON.stringify(response)}`);
utils.log(LT.INFO, `Posted server count to ${e.name}. Results: ${JSON.stringify(response)}`);
}
});
};

View File

@ -61,6 +61,7 @@ const compareOrigidx = (a: RollSet, b: RollSet): number => {
const escapeCharacters = (str: string, esc: string): string => {
// Loop thru each esc char one at a time
for (let i = 0; i < esc.length; i++) {
utils.log(LT.LOG, `Escaping character ${esc[i]} | ${str}, ${esc}`);
// Create a new regex to look for that char that needs replaced and escape it
const temprgx = new RegExp(`[${esc[i]}]`, "g");
str = str.replace(temprgx, ("\\" + esc[i]));
@ -162,6 +163,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// Loop until all remaining args are parsed
while (remains.length > 0) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Parsing remains ${remains}`);
// Find the next number in the remains to be able to cut out the rule name
let afterSepIdx = remains.search(/\d/);
if (afterSepIdx < 0) {
@ -217,6 +219,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Parsing cs> ${i}`);
rollConf.critScore.range.push(i);
}
break;
@ -224,6 +227,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true;
for (let i = 0; i <= tNum; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Parsing cs< ${i}`);
rollConf.critScore.range.push(i);
}
break;
@ -237,6 +241,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Parsing cf> ${i}`);
rollConf.critFail.range.push(i);
}
break;
@ -244,6 +249,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true;
for (let i = 0; i <= tNum; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Parsing cf< ${i}`);
rollConf.critFail.range.push(i);
}
break;
@ -271,6 +277,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// Since only one drop or keep option can be active, count how many are active to throw the right error
let dkdkCnt = 0;
[rollConf.drop.on, rollConf.keep.on, rollConf.dropHigh.on, rollConf.keepLow.on].forEach(e => {
utils.log(LT.LOG, `Handling roll ${rollStr} | Checking if drop/keep is on ${e}`);
if (e) {
dkdkCnt++;
}
@ -335,6 +342,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// Initial rolling, not handling reroll or exploding here
for (let i = 0; i < rollConf.dieCount; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Initial rolling ${i} of ${JSON.stringify(rollConf)}`);
// If loopCount gets too high, stop trying to calculate infinity
if (loopCount > MAXLOOPS) {
throw new Error("MaxLoopsExceeded");
@ -344,6 +352,8 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
const rolling = JSON.parse(JSON.stringify(templateRoll));
// If maximiseRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll
rolling.roll = maximiseRoll ? rollConf.dieSize : (nominalRoll ? ((rollConf.dieSize / 2) + 0.5) : genRoll(rollConf.dieSize));
// Set origidx of roll
rolling.origidx = i;
// If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size
if (rollConf.critScore.on && rollConf.critScore.range.indexOf(rolling.roll) >= 0) {
@ -366,6 +376,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// If needed, handle rerolling and exploding dice now
if (rollConf.reroll.on || rollConf.exploding) {
for (let i = 0; i < rollSet.length; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Handling rerolling and exploding ${JSON.stringify(rollSet[i])}`);
// If loopCount gets too high, stop trying to calculate infinity
if (loopCount > MAXLOOPS) {
throw new Error("MaxLoopsExceeded");
@ -432,6 +443,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
let rerollCount = 0;
if (rollConf.reroll.on) {
for (let i = 0; i < rollSet.length; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[i])}`);
rollSet[i].origidx = i;
if (rollSet[i].rerolled) {
@ -480,6 +492,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// Now its time to drop all dice needed
let i = 0;
while (dropCount > 0 && i < rollSet.length) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`);
// Skip all rolls that were rerolled
if (!rollSet[i].rerolled) {
rollSet[i].dropped = true;
@ -508,6 +521,7 @@ const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll: boolea
// Loop thru all parts of the roll to document everything that was done to create the total roll
tempRollSet.forEach(e => {
utils.log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`);
let preFormat = "";
let postFormat = "";
@ -573,6 +587,7 @@ const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails: boolean
// Evaluate all parenthesis
while (conf.indexOf("(") > -1) {
utils.log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`);
// Get first open parenthesis
const openParen = conf.indexOf("(");
let closeParen = -1;
@ -580,6 +595,7 @@ const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails: boolean
// Using nextParen to count the opening/closing parens, find the matching paren to openParen above
for (let i = openParen; i < conf.length; i++) {
utils.log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for matching ) openIdx: ${openParen} checking: ${i}`);
// If we hit an open, add one (this includes the openParen we start with), if we hit a close, subtract one
if (conf[i] === "(") {
nextParen++;
@ -622,8 +638,10 @@ const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails: boolean
// Evaluate all EMMDAS by looping thru each teir of operators (exponential is the higehest teir, addition/subtraction the lowest)
const allCurOps = [["^"], ["*", "/", "%"], ["+", "-"]];
allCurOps.forEach(curOps => {
utils.log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`);
// Iterate thru all operators/operands in the conf
for (let i = 0; i < conf.length; i++) {
utils.log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)} | Checking ${JSON.stringify(conf[i])}`);
// Check if the current index is in the active teir of operators
if (curOps.indexOf(conf[i].toString()) > -1) {
// Grab the operands from before and after the operator
@ -754,6 +772,7 @@ const parseRoll = (fullCmd: string, localPrefix: string, localPostfix: string, m
// Loop thru all roll/math ops
for (let i = 0; i < sepRolls.length; i++) {
utils.log(LT.LOG, `Parsing roll ${fullCmd} | Working ${sepRolls[i]}`);
// Split the current iteration on the command postfix to separate the operation to be parsed and the text formatting after the opertaion
const [tempConf, tempFormat] = sepRolls[i].split(localPostfix);
@ -763,6 +782,7 @@ const parseRoll = (fullCmd: string, localPrefix: string, localPostfix: string, m
// Verify there are equal numbers of opening and closing parenthesis by adding 1 for opening parens and subtracting 1 for closing parens
let parenCnt = 0;
mathConf.forEach(e => {
utils.log(LT.LOG, `Parsing roll ${fullCmd} | Checking parenthesis balance ${e}`);
if (e === "(") {
parenCnt++;
} else if (e === ")") {
@ -777,6 +797,7 @@ const parseRoll = (fullCmd: string, localPrefix: string, localPostfix: string, m
// Evaluate all rolls into stepSolve format and all numbers into floats
for (let i = 0; i < mathConf.length; i++) {
utils.log(LT.LOG, `Parsing roll ${fullCmd} | Evaluating rolls into mathable items ${JSON.stringify(mathConf[i])}`);
if (mathConf[i].toString().length === 0) {
// If its an empty string, get it out of here
mathConf.splice(i, 1);
@ -841,8 +862,9 @@ const parseRoll = (fullCmd: string, localPrefix: string, localPostfix: string, m
fullCmd = fullCmd.substr(0, (fullCmd.length - 1));
}
// Escape any | chars in fullCmd to prevent spoilers from acting up
// Escape any | and ` chars in fullCmd to prevent spoilers and code blocks from acting up
fullCmd = escapeCharacters(fullCmd, "|");
fullCmd = fullCmd.replace(/`/g, "");
let line1 = "";
let line2 = "";
@ -850,27 +872,28 @@ const parseRoll = (fullCmd: string, localPrefix: string, localPostfix: string, m
// If maximiseRoll or nominalRoll are on, mark the output as such, else use default formatting
if (maximiseRoll) {
line1 = " requested the theoretical maximum of: `[[" + fullCmd + "`";
line1 = ` requested the theoretical maximum of: \`${localPrefix}${fullCmd}\``;
line2 = "Theoretical Maximum Results: ";
} else if (nominalRoll) {
line1 = " requested the theoretical nominal of: `[[" + fullCmd + "`";
line1 = ` requested the theoretical nominal of: \`${localPrefix}${fullCmd}\``;
line2 = "Theoretical Nominal Results: ";
} else if (order === "a") {
line1 = " requested the following rolls to be ordered from least to greatest: `[[" + fullCmd + "`";
line1 = ` requested the following rolls to be ordered from least to greatest: \`${localPrefix}${fullCmd}\``;
line2 = "Results: ";
tempReturnData.sort(compareTotalRolls);
} else if (order === "d") {
line1 = " requested the following rolls to be ordered from greatest to least: `[[" + fullCmd + "`";
line1 = ` requested the following rolls to be ordered from greatest to least: \`${localPrefix}${fullCmd}\``;
line2 = "Results: ";
tempReturnData.sort(compareTotalRolls);
tempReturnData.reverse();
} else {
line1 = " rolled: `[[" + fullCmd + "`";
line1 = ` rolled: \`${localPrefix}${fullCmd}\``;
line2 = "Results: ";
}
// Fill out all of the details and results now
tempReturnData.forEach(e => {
utils.log(LT.LOG, `Parsing roll ${fullCmd} | Making return text ${JSON.stringify(e)}`);
let preFormat = "";
let postFormat = "";

View File

@ -12,6 +12,7 @@ import {
nanoid
} from "../deps.ts";
import { DEBUG } from "../flags.ts";
import { LogTypes } from "./utils.enums.ts";
// Constant initialized at runtime for consistent file names
@ -19,36 +20,6 @@ let startDate: string;
let logFolder: string;
let initialized = false;
// split2k(longMessage) returns shortMessage[]
// split2k takes a long string in and cuts it into shorter strings to be sent in Discord
const split2k = (chunk: string): string[] => {
// Replace any malformed newline characters
chunk = chunk.replace(/\\n/g, "\n");
const bites = [];
// While there is more characters than allowed to be sent in discord
while (chunk.length > 2000) {
// Take 2001 chars to see if word magically ends on char 2000
let bite = chunk.substr(0, 2001);
const lastI = bite.lastIndexOf(" ");
if (lastI < 2000) {
// If there is a final word before the 2000 split point, split right after that word
bite = bite.substr(0, lastI);
} else {
// Else cut exactly 2000 characters
bite = bite.substr(0, 2000);
}
// Push and remove the bite taken out of the chunk
bites.push(bite);
chunk = chunk.slice(bite.length);
}
// Push leftovers into bites
bites.push(chunk);
return bites;
};
// ask(prompt) returns string
// ask prompts the user at command line for message
const ask = async (question: string, stdin = Deno.stdin, stdout = Deno.stdout): Promise<string> => {
@ -101,14 +72,10 @@ const cmdPrompt = async (logChannel: string, botName: string, sendMessage: (c: s
const channelID = args.shift() || "";
const message = args.join(" ");
// Utilize the split2k function to ensure a message over 2000 chars is not sent
const messages = split2k(message);
for (let i = 0; i < messages.length; i++) {
sendMessage(channelID, messages[i]).catch(reason => {
sendMessage(channelID, message).catch(reason => {
console.error(reason);
});
}
}
catch (e) {
console.error(e);
}
@ -119,14 +86,10 @@ const cmdPrompt = async (logChannel: string, botName: string, sendMessage: (c: s
else if (command === "ml") {
const message = args.join(" ");
// Utilize the split2k function to ensure a message over 2000 chars is not sent
const messages = split2k(message);
for (let i = 0; i < messages.length; i++) {
sendMessage(logChannel, messages[i]).catch(reason => {
sendMessage(logChannel, message).catch(reason => {
console.error(reason);
});
}
}
// help or h
// Shows a basic help menu
@ -183,10 +146,13 @@ const initLog = (name: string): void => {
// Handles sending messages to console.log and sending a copy of the log to a file for review on crashes
const log = async (level: LogTypes, message: string, error = new Error()): Promise<void> => {
const msgId = await nanoid(10);
const formattedMsg = `${new Date().toISOString()} | ${msgId} | ${level} | ${message}`;
const formattedMsg = `${new Date().toISOString()} | ${msgId} | ${level.padEnd(5)} | ${message}`;
const traceMsg = `${error.stack}`
// Default functionality of logging to console
if (level !== LogTypes.LOG || DEBUG) {
console[level](formattedMsg);
}
// Logging to files for permanent info
if (initialized) {
await Deno.writeTextFile(`./${logFolder}/${level}/${startDate}.log`, `${formattedMsg}\n`, {append: true});
@ -195,4 +161,4 @@ const log = async (level: LogTypes, message: string, error = new Error()): Promi
}
};
export default { split2k, cmdPrompt, sendIndirectMessage, initLog, log };
export default { cmdPrompt, sendIndirectMessage, initLog, log };

View File

@ -122,7 +122,7 @@
Built by <a href="https://github.com/Burn-E99/" target="_blank">Ean Milligan</a>
</div>
<div id="footer-right">
Version 1.4.2
Version 1.4.3
</div>
</div>
</div>

View File

@ -94,7 +94,7 @@
Built by <a href="https://github.com/Burn-E99/" target="_blank">Ean Milligan</a>
</div>
<div id="footer-right">
Version 1.4.2
Version 1.4.3
</div>
</div>
</div>