Roll command now utilizes embeds, count decorator (-c) complete, added [[api h shorthand

This commit is contained in:
Ean Milligan (Bastion) 2022-05-27 20:59:09 -04:00
parent bbba797dc3
commit b887b93bb2
7 changed files with 254 additions and 155 deletions

View File

@ -31,7 +31,7 @@ export const api = async (message: DiscordenoMessage, args: string[]) => {
if (await hasGuildPermissions(message.authorId, message.guildId, ['ADMINISTRATOR'])) { if (await hasGuildPermissions(message.authorId, message.guildId, ['ADMINISTRATOR'])) {
// [[api help // [[api help
// Shows API help details // Shows API help details
if (apiArg === 'help') { if (apiArg === 'help' || apiArg === 'h') {
apiCommands.help(message); apiCommands.help(message);
} // [[api allow/block } // [[api allow/block
// Lets a guild admin allow or ban API rolls from happening in said guild // Lets a guild admin allow or ban API rolls from happening in said guild

View File

@ -10,7 +10,8 @@ import {
sendDirectMessage, sendDirectMessage,
} from '../../deps.ts'; } from '../../deps.ts';
import solver from '../solver/_index.ts'; import solver from '../solver/_index.ts';
import { constantCmds, generateDMFailed } from '../constantCmds.ts'; import { SolvedRoll } from '../solver/solver.d.ts';
import { constantCmds, generateCountDetailsEmbed, generateDMFailed, generateRollEmbed } from '../constantCmds.ts';
import rollFuncs from './roll/_index.ts'; import rollFuncs from './roll/_index.ts';
export const roll = async (message: DiscordenoMessage, args: string[], command: string) => { export const roll = async (message: DiscordenoMessage, args: string[], command: string) => {
@ -31,11 +32,15 @@ export const roll = async (message: DiscordenoMessage, args: string[], command:
try { try {
const originalCommand = `${config.prefix}${command} ${args.join(' ')}`; const originalCommand = `${config.prefix}${command} ${args.join(' ')}`;
const m = await message.send(constantCmds.rolling); const m = await message.reply(constantCmds.rolling);
// Get modifiers from command // Get modifiers from command
const modifiers = rollFuncs.getModifiers(m, args, command, originalCommand); const modifiers = rollFuncs.getModifiers(m, args, command, originalCommand);
// gmModifiers used to create gmEmbed (basically just turn off the gmRoll)
const gmModifiers = JSON.parse(JSON.stringify(modifiers));
gmModifiers.gmRoll = false;
// Return early if the modifiers were invalid // Return early if the modifiers were invalid
if (!modifiers.valid) { if (!modifiers.valid) {
return; return;
@ -43,111 +48,58 @@ export const roll = async (message: DiscordenoMessage, args: string[], command:
// Rejoin all of the args and send it into the solver, if solver returns a falsy item, an error object will be substituded in // 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 rollCmd = `${command} ${args.join(' ')}`;
const returnmsg = solver.parseRoll(rollCmd, modifiers) || { error: true, errorCode: 'EmptyMessage', errorMsg: 'Error: Empty message', line1: '', line2: '', line3: '' }; const returnmsg = solver.parseRoll(rollCmd, modifiers) || <SolvedRoll> { error: true, errorCode: 'EmptyMessage', errorMsg: 'Error: Empty message' };
let returnText = ''; const pubEmbedDetails = await generateRollEmbed(message.authorId, returnmsg, modifiers);
const gmEmbedDetails = await generateRollEmbed(message.authorId, returnmsg, gmModifiers);
const countEmbed = generateCountDetailsEmbed(returnmsg.counts);
// If there was an error, report it to the user in hopes that they can determine what they did wrong // If there was an error, report it to the user in hopes that they can determine what they did wrong
if (returnmsg.error) { if (returnmsg.error) {
returnText = returnmsg.errorMsg; m.edit({embeds: [pubEmbedDetails.embed]});
m.edit(returnText);
if (DEVMODE && config.logRolls) { if (DEVMODE && config.logRolls) {
// If enabled, log rolls so we can verify the bots math // If enabled, log rolls so we can see what went wrong
dbClient.execute(queries.insertRollLogCmd(0, 1), [originalCommand, returnmsg.errorCode, m.id]).catch((e) => { dbClient.execute(queries.insertRollLogCmd(0, 1), [originalCommand, returnmsg.errorCode, m.id]).catch((e) => {
log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`); log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`);
}); });
} }
return;
} else { } else {
// Else format the output using details from the solver // Determine if we are to send a GM roll or a normal roll
returnText = `<@${message.authorId}>${returnmsg.line1}\n${returnmsg.line2}`; if (modifiers.gmRoll) {
// Send the public embed to correct channel
m.edit({embeds: [pubEmbedDetails.embed]});
if (!modifiers.superNoDetails) { // And message the full details to each of the GMs, alerting roller of every GM that could not be messaged
if (modifiers.noDetails) { modifiers.gms.forEach(async (gm) => {
returnText += '\nDetails suppressed by -nd flag.'; log(LT.LOG, `Messaging GM ${gm}`);
} else { // Attempt to DM the GM and send a warning if it could not DM a GM
returnText += `\nDetails:\n${modifiers.spoiler}${returnmsg.line3}${modifiers.spoiler}`; await sendDirectMessage(BigInt(gm.substring(2, gm.length - 1)), {
} embeds: modifiers.count ? [gmEmbedDetails.embed, countEmbed] : [gmEmbedDetails.embed],
} }).then(async () => {
} // Check if we need to attach a file and send it after the initial details sent
if (gmEmbedDetails.hasAttachment) {
// If the roll was a GM roll, send DMs to all the GMs await sendDirectMessage(BigInt(gm.substring(2, gm.length - 1)), {
if (modifiers.gmRoll) { file: gmEmbedDetails.attachment,
// Make a new return line to be sent to the roller }).catch(() => {
const normalText = `<@${message.authorId}>${returnmsg.line1}\nResults have been messaged to the following GMs: ${modifiers.gms.join(' ')}`; message.reply(generateDMFailed(gm));
});
// 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) => { }).catch(() => {
log(LT.LOG, `Messaging GM ${e}`); message.reply(generateDMFailed(gm));
// 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.substring(2, e.length - 1)), 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.substring(2, e.length - 1)), { '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(queries.insertRollLogCmd(0, 0), [originalCommand, returnText, m.id]).catch((e) => {
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 { } else {
// Finally send the text // Not a gm roll, so just send normal embed to correct channel
m.edit(returnText); await m.edit({
} embeds: modifiers.count ? [pubEmbedDetails.embed, countEmbed] : [pubEmbedDetails.embed],
if (DEVMODE && config.logRolls) {
// If enabled, log rolls so we can verify the bots math
dbClient.execute(queries.insertRollLogCmd(0, 0), [originalCommand, returnText, m.id]).catch((e) => {
log(LT.ERROR, `Failed to insert into DB: ${JSON.stringify(e)}`);
}); });
if (pubEmbedDetails.hasAttachment) {
// Attachment requires you to send a new message
message.send({
file: pubEmbedDetails.attachment,
});
}
} }
} }
} catch (e) { } catch (e) {

View File

@ -1,5 +1,7 @@
import config from '../config.ts'; import config from '../config.ts';
import { CountDetails } from './solver/solver.d.ts'; import { DiscordenoMessage } from '../deps.ts';
import { CountDetails, SolvedRoll } from './solver/solver.d.ts';
import { RollModifiers } from './mod.d.ts';
const failColor = 0xe71212; const failColor = 0xe71212;
const warnColor = 0xe38f28; const warnColor = 0xe38f28;
@ -625,41 +627,144 @@ export const generateRollError = (errorType: string, errorMsg: string) => ({
}], }],
}); });
export const generateCountDetails = (counts: CountDetails) => ({ export const generateCountDetailsEmbed = (counts: CountDetails) => ({
embeds: [{ color: infoColor1,
color: infoColor1, title: 'Roll Count Details:',
title: 'Roll Count Details:', fields: [
fields: [ {
{ name: 'Total Rolls:',
name: 'Total Rolls:', value: `${counts.total}`,
details: `${counts.total}`, inline: true,
inline: true, },
}, {
{ name: 'Successful Rolls:',
name: 'Successful Rolls:', value: `${counts.successful}`,
details: `${counts.successful}`, inline: true,
inline: true, },
}, {
{ name: 'Failed Rolls:',
name: 'Failed Rolls:', value: `${counts.failed}`,
details: `${counts.failed}`, inline: true,
inline: true, },
}, {
{ name: 'Rerolled Dice:',
name: 'Rerolled Dice:', value: `${counts.rerolled}`,
details: `${counts.rerolled}`, inline: true,
inline: true, },
}, {
{ name: 'Dropped Dice:',
name: 'Dropped Dice:', value: `${counts.dropped}`,
details: `${counts.dropped}`, inline: true,
inline: true, },
}, {
{ name: 'Exploded Dice:',
name: 'Exploded Dice:', value: `${counts.exploded}`,
details: `${counts.exploded}`, inline: true,
inline: true, },
}, ],
],
}],
}); });
export const generateRollEmbed = async (authorId: bigint, returnDetails: SolvedRoll, modifiers: RollModifiers) => {
if (returnDetails.error) {
// Roll had an error, send error embed
return {
embed: {
color: failColor,
title: 'Roll failed:',
description: `${returnDetails.errorMsg}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text'}),
'name': 'rollDetails.txt',
},
};
} else {
if (modifiers.gmRoll) {
// Roll is a GM Roll, send this in the pub channel (this funciton will be ran again to get details for the GMs)
return {
embed: {
color: infoColor2,
description: `<@${authorId}>${returnDetails.line1}
Results have been messaged to the following GMs: ${modifiers.gms.join(' ')}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text'}),
'name': 'rollDetails.txt',
},
};
} else {
// Roll is normal, make normal embed
const line2Details = returnDetails.line2.split(': ');
let details = '';
if (!modifiers.superNoDetails) {
if (modifiers.noDetails) {
details = `**Details:**
Suppressed by -nd flag`;
} else {
details = `**Details:**
${modifiers.spoiler}${returnDetails.line3}${modifiers.spoiler}`;
}
}
const baseDesc = `<@${authorId}>${returnDetails.line1}
**${line2Details.shift()}:**
${line2Details.join(': ')}`;
if (baseDesc.length + details.length < 4090) {
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text'}),
'name': 'rollDetails.txt',
},
};
} else {
// If its too big, collapse it into a .txt file and send that instead.
const b = await new Blob([`${baseDesc}\n\n${details}` as BlobPart], { 'type': 'text' });
details = 'Details have been ommitted from this message for being over 2000 characters.';
if (b.size > 8388290) {
details +=
'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.';
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text'}),
'name': 'rollDetails.txt',
},
};
} else {
details += 'Full details have been attached to this messaged as a \`.txt\` file for verification purposes.';
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
},
hasAttachment: true,
attachment: {
'blob': b,
'name': 'rollDetails.txt',
},
};
}
}
}
}
};

23
src/solver/counter.ts Normal file
View File

@ -0,0 +1,23 @@
import { CountDetails, RollSet } from './solver.d.ts';
export const rollCounter = (rollSet: RollSet[]): CountDetails => {
const countDetails = {
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
};
rollSet.forEach((roll) => {
countDetails.total++;
if (roll.critHit) countDetails.successful++;
if (roll.critFail) countDetails.failed++;
if (roll.rerolled) countDetails.rerolled++;
if (roll.dropped) countDetails.dropped++;
if (roll.exploding) countDetails.exploded++;
});
return countDetails;
};

View File

@ -7,7 +7,7 @@ import {
import config from '../../config.ts'; import config from '../../config.ts';
import { RollModifiers } from '../mod.d.ts'; import { RollModifiers } from '../mod.d.ts';
import { ReturnData, SolvedRoll, SolvedStep } from './solver.d.ts'; import { CountDetails, ReturnData, SolvedRoll, SolvedStep } from './solver.d.ts';
import { compareTotalRolls, escapeCharacters } from './rollUtils.ts'; import { compareTotalRolls, escapeCharacters } from './rollUtils.ts';
import { formatRoll } from './rollFormatter.ts'; import { formatRoll } from './rollFormatter.ts';
import { fullSolver } from './solver.ts'; import { fullSolver } from './solver.ts';
@ -15,13 +15,8 @@ import { fullSolver } from './solver.ts';
// parseRoll(fullCmd, modifiers) // parseRoll(fullCmd, modifiers)
// parseRoll handles converting fullCmd into a computer readable format for processing, and finally executes the solving // parseRoll handles converting fullCmd into a computer readable format for processing, and finally executes the solving
export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll => { export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll => {
const returnmsg = { const returnmsg = <SolvedRoll> {
error: false, error: false,
errorMsg: '',
errorCode: '',
line1: '',
line2: '',
line3: '',
}; };
// Whole function lives in a try-catch to allow safe throwing of errors on purpose // Whole function lives in a try-catch to allow safe throwing of errors on purpose
@ -30,6 +25,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
const sepRolls = fullCmd.split(config.prefix); const sepRolls = fullCmd.split(config.prefix);
const tempReturnData: ReturnData[] = []; const tempReturnData: ReturnData[] = [];
const tempCountDetails: CountDetails[] = [];
// Loop thru all roll/math ops // Loop thru all roll/math ops
for (const sepRoll of sepRolls) { for (const sepRoll of sepRolls) {
@ -68,7 +64,9 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
mathConf[i] = parseFloat(mathConf[i].toString()); mathConf[i] = parseFloat(mathConf[i].toString());
} else if (/([0123456789])/g.test(mathConf[i].toString())) { } else if (/([0123456789])/g.test(mathConf[i].toString())) {
// If there is a number somewhere in mathconf[i] but there are also other characters preventing it from parsing correctly as a number, it should be a dice roll, parse it as such (if it for some reason is not a dice roll, formatRoll/roll will handle it) // If there is a number somewhere in mathconf[i] but there are also other characters preventing it from parsing correctly as a number, it should be a dice roll, parse it as such (if it for some reason is not a dice roll, formatRoll/roll will handle it)
mathConf[i] = formatRoll(mathConf[i].toString(), modifiers.maxRoll, modifiers.nominalRoll); const formattedRoll = formatRoll(mathConf[i].toString(), modifiers.maxRoll, modifiers.nominalRoll);
mathConf[i] = formattedRoll.solvedStep;
tempCountDetails.push(formattedRoll.countDetails);
} else if (mathConf[i].toString().toLowerCase() === 'e') { } else if (mathConf[i].toString().toLowerCase() === 'e') {
// If the operand is the constant e, create a SolvedStep for it // If the operand is the constant e, create a SolvedStep for it
mathConf[i] = { mathConf[i] = {
@ -77,7 +75,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
containsCrit: false, containsCrit: false,
containsFail: false, containsFail: false,
}; };
} else if (mathConf[i].toString().toLowerCase() === 'pi' || mathConf[i].toString().toLowerCase() == '𝜋') { } else if (mathConf[i].toString().toLowerCase() === 'pi' || mathConf[i].toString().toLowerCase() === '𝜋') {
// If the operand is the constant pi, create a SolvedStep for it // If the operand is the constant pi, create a SolvedStep for it
mathConf[i] = { mathConf[i] = {
total: Math.PI, total: Math.PI,
@ -190,6 +188,16 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
returnmsg.line1 = line1; returnmsg.line1 = line1;
returnmsg.line2 = line2; returnmsg.line2 = line2;
returnmsg.line3 = line3; returnmsg.line3 = line3;
// Reduce counts to a single object
returnmsg.counts = tempCountDetails.reduce((acc, cnt) => ({
total: acc.total + cnt.total,
successful: acc.successful + cnt.successful,
failed: acc.failed + cnt.failed,
rerolled: acc.rerolled + cnt.rerolled,
dropped: acc.dropped + cnt.dropped,
exploded: acc.exploded + cnt.exploded,
}));
} catch (solverError) { } catch (solverError) {
// Welp, the unthinkable happened, we hit an error // Welp, the unthinkable happened, we hit an error

View File

@ -5,11 +5,12 @@ import {
} from '../../deps.ts'; } from '../../deps.ts';
import { roll } from './roller.ts'; import { roll } from './roller.ts';
import { SolvedStep } from './solver.d.ts'; import { rollCounter } from './counter.ts';
import { RollFormat } from './solver.d.ts';
// formatRoll(rollConf, maximiseRoll, nominalRoll) returns one SolvedStep // formatRoll(rollConf, maximiseRoll, nominalRoll) returns one SolvedStep
// formatRoll handles creating and formatting the completed rolls into the SolvedStep format // formatRoll handles creating and formatting the completed rolls into the SolvedStep format
export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll: boolean): SolvedStep => { export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll: boolean): RollFormat => {
let tempTotal = 0; let tempTotal = 0;
let tempDetails = '['; let tempDetails = '[';
let tempCrit = false; let tempCrit = false;
@ -59,9 +60,12 @@ export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll:
tempDetails += ']'; tempDetails += ']';
return { return {
total: tempTotal, solvedStep: {
details: tempDetails, total: tempTotal,
containsCrit: tempCrit, details: tempDetails,
containsFail: tempFail, containsCrit: tempCrit,
containsFail: tempFail,
},
countDetails: rollCounter(tempRollSet),
}; };
}; };

View File

@ -29,16 +29,6 @@ export type ReturnData = {
initConfig: string; initConfig: string;
}; };
// SolvedRoll is the complete solved and formatted roll, or the error said roll created
export type SolvedRoll = {
error: boolean;
errorMsg: string;
errorCode: string;
line1: string;
line2: string;
line3: string;
};
// CountDetails is the object holding the count data for creating the Count Embed // CountDetails is the object holding the count data for creating the Count Embed
export type CountDetails = { export type CountDetails = {
total: number; total: number;
@ -48,3 +38,20 @@ export type CountDetails = {
dropped: number; dropped: number;
exploded: number; exploded: number;
}; };
// RollFormat is the return structure for the rollFormatter
export type RollFormat = {
solvedStep: SolvedStep;
countDetails: CountDetails;
};
// SolvedRoll is the complete solved and formatted roll, or the error said roll created
export type SolvedRoll = {
error: boolean;
errorMsg: string;
errorCode: string;
line1: string;
line2: string;
line3: string;
counts: CountDetails;
};