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 # 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). 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 = { export const config = {
"name": "The Artificer", // Name of the bot "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 "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" "localtoken": "local_testing_token", // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token"
"prefix": "[[", // Prefix for all commands "prefix": "[[", // Prefix for all commands

66
mod.ts
View File

@ -52,6 +52,7 @@ startBot({
// Interval to rotate the status text every 30 seconds to show off more commands // Interval to rotate the status text every 30 seconds to show off more commands
setInterval(() => { setInterval(() => {
utils.log(LT.LOG, "Changing bot status");
try { try {
// Wrapped in try-catch due to hard crash possible // Wrapped in try-catch due to hard crash possible
editBotsStatus(StatusTypes.Online, intervals.getRandomStatus(cache), ActivityType.Game); editBotsStatus(StatusTypes.Online, intervals.getRandomStatus(cache), ActivityType.Game);
@ -61,7 +62,10 @@ startBot({
}, 30000); }, 30000);
// Interval to update bot list stats every 24 hours // 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 added to make sure the startup message does not error out
setTimeout(() => { setTimeout(() => {
@ -73,11 +77,13 @@ startBot({
}, 1000); }, 1000);
}, },
guildCreate: (guild: Guild) => { 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 => { 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)}`); utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(e)}`);
}); });
}, },
guildDelete: (guild: Guild) => { 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 => { 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)}`); utils.log(LT.ERROR, `Failed to send message: ${JSON.stringify(e)}`);
}); });
@ -86,12 +92,14 @@ startBot({
messageCreate: async (message: Message) => { messageCreate: async (message: Message) => {
// Ignore all other bots // Ignore all other bots
if (message.author.bot) return; if (message.author.bot) return;
// Ignore all messages that are not commands // Ignore all messages that are not commands
if (message.content.indexOf(config.prefix) !== 0) return; if (message.content.indexOf(config.prefix) !== 0) return;
utils.log(LT.LOG, `Handling message ${JSON.stringify(message)}`);
// Split into standard command + args format // 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(); const command = args.shift()?.toLowerCase();
// All commands below here // 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 // Help command specifically for the roll command
else if (command === "rollhelp" || command === "rh" || command === "hr" || command === "??" || command?.startsWith("xdy")) { else if (command === "rollhelp" || command === "rh" || command === "hr" || command === "??" || command?.startsWith("xdy")) {
// Light telemetry to see how many times a command is being run // 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 // 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++) { for (let i = 0; i < args.length; i++) {
utils.log(LT.LOG, `Checking ${command + args.join(" ")} for command modifiers ${i}`);
switch (args[i].toLowerCase()) { switch (args[i].toLowerCase()) {
case "-nd": case "-nd":
modifiers.noDetails = true; 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 // -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("<@")) { 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 // 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, "")); modifiers.gms.push(args[i + 1].replace(/[!]/g, ""));
args.splice((i + 1), 1); args.splice((i + 1), 1);
@ -437,7 +447,7 @@ startBot({
case "-o": case "-o":
args.splice(i, 1); 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 // 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"); m.edit("Error: Must specifiy a or d to order the rolls ascending or descending");
@ -450,7 +460,7 @@ startBot({
return; return;
} }
modifiers.order = args[i].toLowerCase(); modifiers.order = args[i].toLowerCase()[0];
args.splice(i, 1); args.splice(i, 1);
i--; i--;
@ -509,16 +519,27 @@ startBot({
// And message the full details to each of the GMs, alerting roller of every GM that could not be messaged // 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 => { 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. // If its too big, collapse it into a .txt file and send that instead.
const b = await new Blob([returnText as BlobPart], { "type": "text" }); const b = await new Blob([returnText as BlobPart], { "type": "text" });
// Update return text if (b.size > 8388290) {
returnText = "<@" + message.author.id + ">" + returnmsg.line1 + "\n" + returnmsg.line2 + "\nFull details have been attached to this messaged as a `.txt` file for verification purposes."; // 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 // Attempt to DM the GMs and send a warning if it could not DM a GM
await sendDirectMessage(e.substr(2, (e.length - 3)), { "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }).catch(() => { 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); 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.";
// Attempt to DM the GMs and send a warning if it could not DM a GM
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 // Finally send the text
@ -536,13 +557,21 @@ startBot({
// If its too big, collapse it into a .txt file and send that instead. // If its too big, collapse it into a .txt file and send that instead.
const b = await new Blob([returnText as BlobPart], { "type": "text" }); const b = await new Blob([returnText as BlobPart], { "type": "text" });
// Update return text if (b.size > 8388290) {
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."; // 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.";
// Remove the original message to send new one with attachment // Send the results
m.delete(); m.edit(returnText);
} else {
await utils.sendIndirectMessage(message, { "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }, sendMessage, sendDirectMessage); // 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.";
// Remove the original message to send new one with attachment
m.delete();
await utils.sendIndirectMessage(message, { "content": returnText, "file": { "blob": b, "name": "rollDetails.txt" } }, sendMessage, sendDirectMessage);
}
} else { } else {
// Finally send the text // Finally send the text
m.edit(returnText); m.edit(returnText);
@ -565,6 +594,7 @@ startBot({
else { else {
// Start looping thru the possible emojis // Start looping thru the possible emojis
config.emojis.some((emoji: EmojiConf) => { config.emojis.some((emoji: EmojiConf) => {
utils.log(LT.LOG, `Checking if command was emoji ${emoji}`);
// If a match gets found // If a match gets found
if (emoji.aliases.indexOf(command || "") > -1) { if (emoji.aliases.indexOf(command || "") > -1) {
// Light telemetry to see how many times a command is being run // 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 // 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 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 }); 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 // rateLimitTime holds all users with the last time they started a rate limit timer
const rateLimitTime = new Map<string, number>(); 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 // Catching every request made to the server
for await (const request of 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 // Check if user is authenticated to be using this API
let authenticated = false; let authenticated = false;
let rateLimited = false; let rateLimited = false;
@ -90,6 +91,7 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
const query = new Map<string, string>(); const query = new Map<string, string>();
if (tempQ !== undefined) { if (tempQ !== undefined) {
tempQ.split("&").forEach(e => { tempQ.split("&").forEach(e => {
utils.log(LT.LOG, `Breaking down request query: ${request} ${e}`);
const [option, params] = e.split("="); const [option, params] = e.split("=");
query.set(option.toLowerCase(), params); 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 // 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: "; let normalText = apiPrefix + "<@" + query.get("user") + ">" + returnmsg.line1 + "\nResults have been messaged to the following GMs: ";
gms.forEach(e => { gms.forEach(e => {
utils.log(LT.LOG, `Appending GM ${e} to roll text`);
normalText += "<@" + e + "> "; normalText += "<@" + e + "> ";
}); });
@ -295,16 +298,24 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
}); });
} }
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
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 // And message the full details to each of the GMs, alerting roller of every GM that could not be messaged
gms.forEach(async e => { gms.forEach(async e => {
// If its too big, collapse it into a .txt file and send that instead. utils.log(LT.LOG, `Messaging GM ${e} roll results`);
const b = await new Blob([returnText as BlobPart], { "type": "text" });
// 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.";
// Attempt to DM the GMs and send a warning if it could not DM a GM // 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." 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 // Send the return message as a DM or normal message depening on if the channel is set
if ((query.get("channel") || "").length > 0) { if ((query.get("channel") || "").length > 0) {
@ -342,12 +353,14 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
// If its too big, collapse it into a .txt file and send that instead. // If its too big, collapse it into a .txt file and send that instead.
const b = await new Blob([returnText as BlobPart], { "type": "text" }); const b = await new Blob([returnText as BlobPart], { "type": "text" });
// Update return text if (b.size > 8388290) {
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."; // 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 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.";
// Set info into the newMessage } else {
newMessage.content = returnText; // Update return text
newMessage.file = { "blob": b, "name": "rollDetails.txt" }; 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 // Send the return message as a DM or normal message depening on if the channel is set
@ -672,6 +685,7 @@ const start = async (dbClient: Client, cache: CacheData, sendMessage: (c: string
const query = new Map<string, string>(); const query = new Map<string, string>();
if (tempQ !== undefined) { if (tempQ !== undefined) {
tempQ.split("&").forEach(e => { tempQ.split("&").forEach(e => {
utils.log(LT.LOG, `Parsing request query #2 ${request} ${e}`);
const [option, params] = e.split("="); const [option, params] = e.split("=");
query.set(option.toLowerCase(), params); 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 // Sends the current server count to all bot list sites we are listed on
const updateListStatistics = (botID: string, serverCount: number): void => { const updateListStatistics = (botID: string, serverCount: number): void => {
config.botLists.forEach(async e => { config.botLists.forEach(async e => {
utils.log(LT.LOG, `Updating statistics for ${JSON.stringify(e)}`)
if (e.enabled) { if (e.enabled) {
const tempHeaders = new Headers(); const tempHeaders = new Headers();
tempHeaders.append(e.headers[0].header, e.headers[0].value); tempHeaders.append(e.headers[0].header, e.headers[0].value);
@ -49,7 +50,7 @@ const updateListStatistics = (botID: string, serverCount: number): void => {
"headers": tempHeaders, "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 "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 => { const escapeCharacters = (str: string, esc: string): string => {
// Loop thru each esc char one at a time // Loop thru each esc char one at a time
for (let i = 0; i < esc.length; i++) { 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 // Create a new regex to look for that char that needs replaced and escape it
const temprgx = new RegExp(`[${esc[i]}]`, "g"); const temprgx = new RegExp(`[${esc[i]}]`, "g");
str = str.replace(temprgx, ("\\" + esc[i])); 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 // Loop until all remaining args are parsed
while (remains.length > 0) { 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 // Find the next number in the remains to be able to cut out the rule name
let afterSepIdx = remains.search(/\d/); let afterSepIdx = remains.search(/\d/);
if (afterSepIdx < 0) { 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) // Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true; rollConf.critScore.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { for (let i = tNum; i <= rollConf.dieSize; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Parsing cs> ${i}`);
rollConf.critScore.range.push(i); rollConf.critScore.range.push(i);
} }
break; 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) // Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true; rollConf.critScore.on = true;
for (let i = 0; i <= tNum; i++) { for (let i = 0; i <= tNum; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Parsing cs< ${i}`);
rollConf.critScore.range.push(i); rollConf.critScore.range.push(i);
} }
break; 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) // Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true; rollConf.critFail.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { for (let i = tNum; i <= rollConf.dieSize; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Parsing cf> ${i}`);
rollConf.critFail.range.push(i); rollConf.critFail.range.push(i);
} }
break; 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) // Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true; rollConf.critFail.on = true;
for (let i = 0; i <= tNum; i++) { for (let i = 0; i <= tNum; i++) {
utils.log(LT.LOG, `Handling roll ${rollStr} | Parsing cf< ${i}`);
rollConf.critFail.range.push(i); rollConf.critFail.range.push(i);
} }
break; 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 // Since only one drop or keep option can be active, count how many are active to throw the right error
let dkdkCnt = 0; let dkdkCnt = 0;
[rollConf.drop.on, rollConf.keep.on, rollConf.dropHigh.on, rollConf.keepLow.on].forEach(e => { [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) { if (e) {
dkdkCnt++; dkdkCnt++;
} }
@ -335,6 +342,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
// Initial rolling, not handling reroll or exploding here // Initial rolling, not handling reroll or exploding here
for (let i = 0; i < rollConf.dieCount; i++) { 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 gets too high, stop trying to calculate infinity
if (loopCount > MAXLOOPS) { if (loopCount > MAXLOOPS) {
throw new Error("MaxLoopsExceeded"); throw new Error("MaxLoopsExceeded");
@ -344,6 +352,8 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
const rolling = JSON.parse(JSON.stringify(templateRoll)); 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 // 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)); 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 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) { 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 needed, handle rerolling and exploding dice now
if (rollConf.reroll.on || rollConf.exploding) { if (rollConf.reroll.on || rollConf.exploding) {
for (let i = 0; i < rollSet.length; i++) { 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 gets too high, stop trying to calculate infinity
if (loopCount > MAXLOOPS) { if (loopCount > MAXLOOPS) {
throw new Error("MaxLoopsExceeded"); throw new Error("MaxLoopsExceeded");
@ -432,6 +443,7 @@ const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): Rol
let rerollCount = 0; let rerollCount = 0;
if (rollConf.reroll.on) { if (rollConf.reroll.on) {
for (let i = 0; i < rollSet.length; i++) { 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; rollSet[i].origidx = i;
if (rollSet[i].rerolled) { 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 // Now its time to drop all dice needed
let i = 0; let i = 0;
while (dropCount > 0 && i < rollSet.length) { 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 // Skip all rolls that were rerolled
if (!rollSet[i].rerolled) { if (!rollSet[i].rerolled) {
rollSet[i].dropped = true; 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 // Loop thru all parts of the roll to document everything that was done to create the total roll
tempRollSet.forEach(e => { tempRollSet.forEach(e => {
utils.log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`);
let preFormat = ""; let preFormat = "";
let postFormat = ""; let postFormat = "";
@ -573,6 +587,7 @@ const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails: boolean
// Evaluate all parenthesis // Evaluate all parenthesis
while (conf.indexOf("(") > -1) { while (conf.indexOf("(") > -1) {
utils.log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`);
// Get first open parenthesis // Get first open parenthesis
const openParen = conf.indexOf("("); const openParen = conf.indexOf("(");
let closeParen = -1; 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 // Using nextParen to count the opening/closing parens, find the matching paren to openParen above
for (let i = openParen; i < conf.length; i++) { 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 we hit an open, add one (this includes the openParen we start with), if we hit a close, subtract one
if (conf[i] === "(") { if (conf[i] === "(") {
nextParen++; 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) // Evaluate all EMMDAS by looping thru each teir of operators (exponential is the higehest teir, addition/subtraction the lowest)
const allCurOps = [["^"], ["*", "/", "%"], ["+", "-"]]; const allCurOps = [["^"], ["*", "/", "%"], ["+", "-"]];
allCurOps.forEach(curOps => { allCurOps.forEach(curOps => {
utils.log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`);
// Iterate thru all operators/operands in the conf // Iterate thru all operators/operands in the conf
for (let i = 0; i < conf.length; i++) { 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 // Check if the current index is in the active teir of operators
if (curOps.indexOf(conf[i].toString()) > -1) { if (curOps.indexOf(conf[i].toString()) > -1) {
// Grab the operands from before and after the operator // 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 // Loop thru all roll/math ops
for (let i = 0; i < sepRolls.length; i++) { 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 // 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); 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 // 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; let parenCnt = 0;
mathConf.forEach(e => { mathConf.forEach(e => {
utils.log(LT.LOG, `Parsing roll ${fullCmd} | Checking parenthesis balance ${e}`);
if (e === "(") { if (e === "(") {
parenCnt++; parenCnt++;
} else if (e === ")") { } 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 // Evaluate all rolls into stepSolve format and all numbers into floats
for (let i = 0; i < mathConf.length; i++) { 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 (mathConf[i].toString().length === 0) {
// If its an empty string, get it out of here // If its an empty string, get it out of here
mathConf.splice(i, 1); mathConf.splice(i, 1);
@ -841,8 +862,9 @@ const parseRoll = (fullCmd: string, localPrefix: string, localPostfix: string, m
fullCmd = fullCmd.substr(0, (fullCmd.length - 1)); 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 = escapeCharacters(fullCmd, "|");
fullCmd = fullCmd.replace(/`/g, "");
let line1 = ""; let line1 = "";
let line2 = ""; 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 or nominalRoll are on, mark the output as such, else use default formatting
if (maximiseRoll) { if (maximiseRoll) {
line1 = " requested the theoretical maximum of: `[[" + fullCmd + "`"; line1 = ` requested the theoretical maximum of: \`${localPrefix}${fullCmd}\``;
line2 = "Theoretical Maximum Results: "; line2 = "Theoretical Maximum Results: ";
} else if (nominalRoll) { } else if (nominalRoll) {
line1 = " requested the theoretical nominal of: `[[" + fullCmd + "`"; line1 = ` requested the theoretical nominal of: \`${localPrefix}${fullCmd}\``;
line2 = "Theoretical Nominal Results: "; line2 = "Theoretical Nominal Results: ";
} else if (order === "a") { } 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: "; line2 = "Results: ";
tempReturnData.sort(compareTotalRolls); tempReturnData.sort(compareTotalRolls);
} else if (order === "d") { } 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: "; line2 = "Results: ";
tempReturnData.sort(compareTotalRolls); tempReturnData.sort(compareTotalRolls);
tempReturnData.reverse(); tempReturnData.reverse();
} else { } else {
line1 = " rolled: `[[" + fullCmd + "`"; line1 = ` rolled: \`${localPrefix}${fullCmd}\``;
line2 = "Results: "; line2 = "Results: ";
} }
// Fill out all of the details and results now // Fill out all of the details and results now
tempReturnData.forEach(e => { tempReturnData.forEach(e => {
utils.log(LT.LOG, `Parsing roll ${fullCmd} | Making return text ${JSON.stringify(e)}`);
let preFormat = ""; let preFormat = "";
let postFormat = ""; let postFormat = "";

View File

@ -12,6 +12,7 @@ import {
nanoid nanoid
} from "../deps.ts"; } from "../deps.ts";
import { DEBUG } from "../flags.ts";
import { LogTypes } from "./utils.enums.ts"; import { LogTypes } from "./utils.enums.ts";
// Constant initialized at runtime for consistent file names // Constant initialized at runtime for consistent file names
@ -19,36 +20,6 @@ let startDate: string;
let logFolder: string; let logFolder: string;
let initialized = false; 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(prompt) returns string
// ask prompts the user at command line for message // ask prompts the user at command line for message
const ask = async (question: string, stdin = Deno.stdin, stdout = Deno.stdout): Promise<string> => { const ask = async (question: string, stdin = Deno.stdin, stdout = Deno.stdout): Promise<string> => {
@ -101,13 +72,9 @@ const cmdPrompt = async (logChannel: string, botName: string, sendMessage: (c: s
const channelID = args.shift() || ""; const channelID = args.shift() || "";
const message = args.join(" "); const message = args.join(" ");
// Utilize the split2k function to ensure a message over 2000 chars is not sent sendMessage(channelID, message).catch(reason => {
const messages = split2k(message); console.error(reason);
for (let i = 0; i < messages.length; i++) { });
sendMessage(channelID, messages[i]).catch(reason => {
console.error(reason);
});
}
} }
catch (e) { catch (e) {
console.error(e); console.error(e);
@ -119,13 +86,9 @@ const cmdPrompt = async (logChannel: string, botName: string, sendMessage: (c: s
else if (command === "ml") { else if (command === "ml") {
const message = args.join(" "); const message = args.join(" ");
// Utilize the split2k function to ensure a message over 2000 chars is not sent sendMessage(logChannel, message).catch(reason => {
const messages = split2k(message); console.error(reason);
for (let i = 0; i < messages.length; i++) { });
sendMessage(logChannel, messages[i]).catch(reason => {
console.error(reason);
});
}
} }
// help or h // help or h
@ -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 // 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 log = async (level: LogTypes, message: string, error = new Error()): Promise<void> => {
const msgId = await nanoid(10); 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}` const traceMsg = `${error.stack}`
// Default functionality of logging to console // Default functionality of logging to console
console[level](formattedMsg); if (level !== LogTypes.LOG || DEBUG) {
console[level](formattedMsg);
}
// Logging to files for permanent info // Logging to files for permanent info
if (initialized) { if (initialized) {
await Deno.writeTextFile(`./${logFolder}/${level}/${startDate}.log`, `${formattedMsg}\n`, {append: true}); 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> Built by <a href="https://github.com/Burn-E99/" target="_blank">Ean Milligan</a>
</div> </div>
<div id="footer-right"> <div id="footer-right">
Version 1.4.2 Version 1.4.3
</div> </div>
</div> </div>
</div> </div>

View File

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