Compare commits

...

17 Commits

Author SHA1 Message Date
Ean Milligan 83595482b7 V4.1.2 - increasing maxLoops to 2M to hopefully limit maxLoopsError in simnom 2025-08-06 15:18:51 -04:00
Ean Milligan cdc710385f add missing file 2025-08-06 15:12:31 -04:00
Ean Milligan 59d4435c32 improve logging 2025-08-06 15:04:30 -04:00
Ean Milligan 2cfa923093 -6 loops overall by only incrementing loop count when regex/str.replace is being called 2025-08-06 14:50:20 -04:00
Ean Milligan c58ebcc9f9 -1 loop per iteration by only asserting paren () balance when () exist in the mathConf 2025-08-06 14:47:32 -04:00
Ean Milligan e01097b1e3 improve loop log message 2025-08-06 14:45:07 -04:00
Ean Milligan 4efbed7424 deno fmt + fix spacing in log message 2025-08-06 14:43:26 -04:00
Ean Milligan 4a5e33c9a0 -3 loops per iteration by only incrementing loop count when math is actually being handled 2025-08-06 14:40:46 -04:00
Ean Milligan 6bf671f82d -1 loop per iteration by only counting when necessary 2025-08-06 14:35:33 -04:00
Ean Milligan 587d5aa19d -1 loop per iteration by only doing rollDist when needed 2025-08-06 14:30:41 -04:00
Ean Milligan 1bb8c1a308 fix loopCount debug to actually log 2025-08-05 17:59:03 -04:00
Ean Milligan e06abac9cf Add debug logging to loopCountCheck 2025-08-05 17:48:24 -04:00
Ean Milligan c80b7c2235 V4.1.1 ver bump 2025-08-05 16:53:02 -04:00
Ean Milligan 959fd6e120 fix simnom being able to be negative 2025-08-05 16:52:12 -04:00
Ean Milligan de02ebcc09 fix getModifiers errors being ignored 2025-08-05 16:51:59 -04:00
Ean Milligan f989be56db Make simnom iterations performed per roll more readable 2025-08-05 16:46:30 -04:00
Ean Milligan 255955d854 V4.1.0 - Add unrestricted repeat roll system. 2025-08-05 15:46:11 -04:00
41 changed files with 448 additions and 93 deletions

View File

@ -29,6 +29,11 @@ Like all Discord bots, _The Bot_ reads every message that it is allowed to, mean
* The Inline Roll System only stores the Discord Guild ID upon usage. Discord Guild IDs are internal IDs generated and provided by Discord. * The Inline Roll System only stores the Discord Guild ID upon usage. Discord Guild IDs are internal IDs generated and provided by Discord.
* _The Bot_ only uses the stored Discord Guild IDs to determine which Guilds it should do a preliminary scan for Inline Rolls for all messages sent. * _The Bot_ only uses the stored Discord Guild IDs to determine which Guilds it should do a preliminary scan for Inline Rolls for all messages sent.
* The Discord Guild IDs are only visible to _The Developer_ thru direct database administration. This direct database administration is only used when there are issues with _The Bot_'s database. * The Discord Guild IDs are only visible to _The Developer_ thru direct database administration. This direct database administration is only used when there are issues with _The Bot_'s database.
* The Unrestricted Repeat Roll System (in Discord, these commands are known as `/toggle-unrestricted-repeat enable`, `[[repeat enable`, `[[repeat allow`, `/toggle-unrestricted-repeat disable`, `[[repeat block`, `[[repeat disable`, and `[[repeat delete`):
* This system is entirely optional, meaning users never need to run these commands under normal usage of _The Bot_. This system is only intended to be used when a user wants to utilize Unrestricted Repeat Rolls in their Guild.
* The Unrestricted Repeat Roll System only stores the Discord Guild ID upon usage. Discord Guild IDs are internal IDs generated and provided by Discord.
* _The Bot_ only uses the stored Discord Guild IDs to determine which Guilds it should allow Unrestricted Repeat Rolls in.
* The Discord Guild IDs are only visible to _The Developer_ thru direct database administration. This direct database administration is only used when there are issues with _The Bot_'s database.
* The Roll Alias System (in Discord, this system contains all commands starting with `/alias`, `[[rollalias`, `[[ralias`, `[[alias`, `[[rolla`, and `[[ra`, and the subcommands that _The Bot_ will store data from are `add`, `create`, `set`, `update`, `replace`, `copy`, `clone`, and `rename`): * The Roll Alias System (in Discord, this system contains all commands starting with `/alias`, `[[rollalias`, `[[ralias`, `[[alias`, `[[rolla`, and `[[ra`, and the subcommands that _The Bot_ will store data from are `add`, `create`, `set`, `update`, `replace`, `copy`, `clone`, and `rename`):
* This system is entirely optional, meaning users never need to run these commands under normal usage of _The Bot_. This system is only intended to be used when a user wants to save roll commands for later reuse. * This system is entirely optional, meaning users never need to run these commands under normal usage of _The Bot_. This system is only intended to be used when a user wants to save roll commands for later reuse.
* The Roll Alias System stores the user provided Alias Name, the user provided Roll String, and in Guild mode, the Discord Guild ID of the Guild the command was run in; and in personal mode, the Discord User ID of the user who ran the command. The Alias Name is string of up to 200 characters that the user provided to save the Roll String under. The Roll String is a string of up to 4,000 characters containing roll commands and formatting text the user provided. Discord Guild IDs and Discord User IDs are internal IDs generated and provided by Discord. * The Roll Alias System stores the user provided Alias Name, the user provided Roll String, and in Guild mode, the Discord Guild ID of the Guild the command was run in; and in personal mode, the Discord User ID of the user who ran the command. The Alias Name is string of up to 200 characters that the user provided to save the Roll String under. The Roll String is a string of up to 4,000 characters containing roll commands and formatting text the user provided. Discord Guild IDs and Discord User IDs are internal IDs generated and provided by Discord.
@ -83,6 +88,11 @@ If you would like to remove your Discord Guild ID from _The Bot_'s database, sim
Additionally, _The Bot_ will automatically delete the Discord Guild ID from _The Bot_'s database when _The Bot_ is removed from your guild. Additionally, _The Bot_ will automatically delete the Discord Guild ID from _The Bot_'s database when _The Bot_ is removed from your guild.
## Unrestricted Repeat Roll System Data Deletion
If you would like to remove your Discord Guild ID from _The Bot_'s database, simply send one of the following commands in the Guild: `/toggle-unrestricted-repeat disable`, `[[repeat disable`, `[[repeat block`, or `[[repeat delete`. All variants of this delete the Discord Guild ID from _The Bot_'s database.
Additionally, _The Bot_ will automatically delete the Discord Guild ID from _The Bot_'s database when _The Bot_ is removed from your guild.
## Alias System Data Deletion ## Alias System Data Deletion
### Personal Mode ### Personal Mode
If you would like to remove all your Personal aliases from _The Bot_'s database, simply send one of the following commands in any channel you share with _The Bot_: `/alias personal delete-all`, `[[alias delete-all`, or `[[alias remove-all`. As deletion is irreversible, _The Bot_ requires a verification code to execute the deletion. _The Bot_ will create a verification code for you when you first send the command. If you would like to remove all your Personal aliases from _The Bot_'s database, simply send one of the following commands in any channel you share with _The Bot_: `/alias personal delete-all`, `[[alias delete-all`, or `[[alias remove-all`. As deletion is irreversible, _The Bot_ requires a verification code to execute the deletion. _The Bot_ will create a verification code for you when you first send the command.

View File

@ -1,4 +1,4 @@
# The Artificer - A Dice Rolling Discord Bot | V4.0.2 - 2025/08/04 # The Artificer - A Dice Rolling Discord Bot | V4.1.2 - 2025/08/06
[![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-orange.svg)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-orange.svg)](https://sonarcloud.io/summary/new_code?id=TheArtificer)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=bugs)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=bugs)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=TheArtificer) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=TheArtificer&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=TheArtificer)

View File

@ -1,7 +1,7 @@
export const config = { export const config = {
name: 'The Artificer', // Name of the bot name: 'The Artificer', // Name of the bot
maxFileSize: 8_388_290, // Max file size bot can send maxFileSize: 8_388_290, // Max file size bot can send
version: '4.0.2', // Version of the bot version: '4.1.2', // 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
@ -16,7 +16,7 @@ export const config = {
guild: 1_000, // Allows guilds to have 1000 aliased rolls for free guild: 1_000, // Allows guilds to have 1000 aliased rolls for free
}, },
}, },
maxLoops: 1_000_000, // Determines how long the bot will attempt a roll, number of loops before it kills a roll. Increase this at your own risk. maxLoops: 2_000_000, // Determines how long the bot will attempt a roll, number of loops before it kills a roll. Increase this at your own risk.
maxWorkers: 16, // Maximum number of worker threads to spawn at once (Set this to less than the number of threads your CPU has, Artificer will eat it all if too many rolls happen at once) maxWorkers: 16, // Maximum number of worker threads to spawn at once (Set this to less than the number of threads your CPU has, Artificer will eat it all if too many rolls happen at once)
workerTimeout: 300_000, // Maximum time before the bot kills a worker thread in ms workerTimeout: 300_000, // Maximum time before the bot kills a worker thread in ms
defaultSimulatedNominal: 10_000, // Default number of loops to run for simulating a nominal defaultSimulatedNominal: 10_000, // Default number of loops to run for simulating a nominal

View File

@ -22,8 +22,20 @@ await dbClient.execute(`DROP TABLE IF EXISTS command_cnt;`);
await dbClient.execute(`DROP TABLE IF EXISTS ignore_list;`); await dbClient.execute(`DROP TABLE IF EXISTS ignore_list;`);
await dbClient.execute(`DROP TABLE IF EXISTS allow_inline;`); await dbClient.execute(`DROP TABLE IF EXISTS allow_inline;`);
await dbClient.execute(`DROP TABLE IF EXISTS aliases;`); await dbClient.execute(`DROP TABLE IF EXISTS aliases;`);
await dbClient.execute(`DROP TABLE IF EXISTS allow_unrestricted_repeat;`);
console.log('Tables dropped'); console.log('Tables dropped');
// Holds guilds that have explicitly allowed anyone to repeat anyone's rolls
console.log('Attempting to create table allow_unrestricted_repeat');
await dbClient.execute(`
CREATE TABLE allow_unrestricted_repeat (
guildid bigint unsigned NOT NULL,
PRIMARY KEY (guildid),
UNIQUE KEY allow_unrestricted_repeat_guildid_UNIQUE (guildid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
console.log('Table created');
// Holds all aliases that have been created // Holds all aliases that have been created
console.log('Attempting to create table aliases'); console.log('Attempting to create table aliases');
await dbClient.execute(` await dbClient.execute(`

View File

@ -25,6 +25,7 @@ const commands = [
'ping', 'ping',
'privacy', 'privacy',
'rip', 'rip',
'repeat',
'report', 'report',
'roll', 'roll',
'rolldecorators', 'rolldecorators',

View File

@ -60,6 +60,22 @@ This document uses the default prefix (`[[`) on all commands listed. If a comma
* `/toggle-inline-rolls disable` * `/toggle-inline-rolls disable`
* `[[inline block` or `[[inline disable` or `[[inline delete` * `[[inline block` or `[[inline disable` or `[[inline delete`
* Blocks inline rolls in the guild. * Blocks inline rolls in the guild.
* `/toggle-unrestricted-repeat [subcommand]` or `[[repeat [subcommand]`
* Controls whether or not unrestricted repeat rolls can be done in a guild, defaults off. Unrestricted Repeat Rolls are whether or not anyone in a guild can use the `Repeat Roll` button on anyone's roll or only the original roller can use them. These commands may only be used by the Owner or Admins of your guild.
* An inline roll is a roll that does not immediately start with `[[`, such as `test [[d20]]`.
* Available subcommands:
* `/toggle-unrestricted-repeat help`
* `[[repeat help`
* Provides a message similar to this subcommand description.
* `/toggle-unrestricted-repeat status`
* `[[repeat status`
* Shows the current status of unrestricted repeat rolls for this guild.
* `/toggle-unrestricted-repeat enable`
* `[[repeat allow` or `[[repeat enable`
* Allows unrestricted repeat rolls in the guild.
* `/toggle-unrestricted-repeat disable`
* `[[repeat block` or `[[repeat disable` or `[[repeat delete`
* Blocks unrestricted repeat rolls in the guild.
* `/alias [subcommand]` or `[[rollalias [subcommand]` or `[[ralias [subcommand]` or `[[alias [subcommand]` or `[[rolla [subcommand]` or `[[ra [subcommand]` * `/alias [subcommand]` or `[[rollalias [subcommand]` or `[[ralias [subcommand]` or `[[alias [subcommand]` or `[[rolla [subcommand]` or `[[ra [subcommand]`
* Custom Roll Alias System * Custom Roll Alias System
* Allows anyone to store a roll string as a shortcut/alias for later use/reuse. * Allows anyone to store a roll string as a shortcut/alias for later use/reuse.

View File

@ -23,6 +23,7 @@ export interface SolvedRoll {
line1: string; line1: string;
line2: string; line2: string;
line3: string; line3: string;
footer: string;
counts: CountDetails; counts: CountDetails;
rollDistributions: RollDistributionMap; rollDistributions: RollDistributionMap;
} }

View File

@ -5,12 +5,12 @@ import { tokenizeCmd } from 'artigen/cmdTokenizer.ts';
import { Modifiers } from 'artigen/dice/getModifiers.ts'; import { Modifiers } from 'artigen/dice/getModifiers.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts'; import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts'; import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { reduceCountDetails } from 'artigen/utils/counter.ts'; import { reduceCountDetails } from 'artigen/utils/counter.ts';
import { cmdSplitRegex, escapeCharacters, withYVarsDash } from 'artigen/utils/escape.ts'; import { cmdSplitRegex, escapeCharacters, withYVarsDash } from 'artigen/utils/escape.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { loggingEnabled, showLoopCountDebug } from 'artigen/utils/logFlag.ts';
import { assertPrePostBalance } from 'artigen/utils/parenBalance.ts'; import { assertPrePostBalance } from 'artigen/utils/parenBalance.ts';
import { reduceRollDistMaps } from 'artigen/utils/rollDist.ts'; import { reduceRollDistMaps } from 'artigen/utils/rollDist.ts';
import { compareTotalRolls, compareTotalRollsReverse, sortYVars } from 'artigen/utils/sortFuncs.ts'; import { compareTotalRolls, compareTotalRollsReverse, sortYVars } from 'artigen/utils/sortFuncs.ts';
@ -26,6 +26,7 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
line1: '', line1: '',
line2: '', line2: '',
line3: '', line3: '',
footer: '',
counts: { counts: {
total: 0, total: 0,
successful: 0, successful: 0,
@ -106,10 +107,10 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
} }
// List number of iterations on simulated nominals // List number of iterations on simulated nominals
if (rollRequest.modifiers.simulatedNominal) line2 += `Iterations performed per roll: \`${rollRequest.modifiers.simulatedNominal}\`\n`; if (rollRequest.modifiers.simulatedNominal) line2 += `Iterations performed per roll: \`${rollRequest.modifiers.simulatedNominal.toLocaleString()}\`\n`;
// Reduce counts to a single object // Reduce counts to a single object
returnMsg.counts = reduceCountDetails(tempCountDetails); if (rollRequest.modifiers.count) returnMsg.counts = reduceCountDetails(tempCountDetails);
// If a regular nominal and roll looks somewhat complex, alert user simulatedNominal exists // If a regular nominal and roll looks somewhat complex, alert user simulatedNominal exists
if (rollRequest.modifiers.nominalRoll && tempReturnData.filter((data) => data.isComplex).length) { if (rollRequest.modifiers.nominalRoll && tempReturnData.filter((data) => data.isComplex).length) {
@ -120,7 +121,7 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
const line2Space = rollRequest.modifiers.noSpaces ? '' : ' '; const line2Space = rollRequest.modifiers.noSpaces ? '' : ' ';
// Fill out all of the details and results now // Fill out all of the details and results now
tempReturnData.forEach((e, i) => { tempReturnData.forEach((e, i) => {
loopCountCheck(); loopCountCheck('artigen.ts - tempReturnData');
loggingEnabled && log(LT.LOG, `Parsing roll ${rollRequest.rollCmd} | Making return text ${JSON.stringify(e)}`); loggingEnabled && log(LT.LOG, `Parsing roll ${rollRequest.rollCmd} | Making return text ${JSON.stringify(e)}`);
let preFormat = ''; let preFormat = '';
@ -169,7 +170,7 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
returnMsg.line3 = line3; returnMsg.line3 = line3;
// Reduce rollDist maps into a single map // Reduce rollDist maps into a single map
returnMsg.rollDistributions = reduceRollDistMaps(tempRollDists); if (rollRequest.modifiers.rollDist) returnMsg.rollDistributions = reduceRollDistMaps(tempRollDists);
} catch (e) { } catch (e) {
// Fill in the return block // Fill in the return block
const solverError = e as Error; const solverError = e as Error;
@ -178,5 +179,7 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
[returnMsg.errorCode, returnMsg.errorMsg] = translateError(solverError); [returnMsg.errorCode, returnMsg.errorMsg] = translateError(solverError);
} }
if (showLoopCountDebug) returnMsg.footer = `Loop Count: ${getLoopCount()}`;
return returnMsg; return returnMsg;
}; };

View File

@ -32,7 +32,7 @@ export const tokenizeCmd = (
// Wrapped commands still exist, unwrap them // Wrapped commands still exist, unwrap them
while (cmd.includes(config.prefix)) { while (cmd.includes(config.prefix)) {
loopCountCheck(); loopCountCheck('cmdTokenizer.ts - while cmd includes prefix');
const openIdx = cmd.indexOf(config.prefix); const openIdx = cmd.indexOf(config.prefix);
const closeIdx = getMatchingPostfixIdx(cmd, openIdx); const closeIdx = getMatchingPostfixIdx(cmd, openIdx);
@ -49,7 +49,7 @@ export const tokenizeCmd = (
const simulatedData: ReturnData[] = []; const simulatedData: ReturnData[] = [];
for (let i = 0; i < simulatedLoopCount; i++) { for (let i = 0; i < simulatedLoopCount; i++) {
loopCountCheck(); loopCountCheck(`cmdTokenizer.ts - simulate nominal loop #${i}`);
loggingEnabled && log(LT.LOG, `In simLoop:${i} "${currentCmd}" of ${JSON.stringify(cmd)}`); loggingEnabled && log(LT.LOG, `In simLoop:${i} "${currentCmd}" of ${JSON.stringify(cmd)}`);
@ -78,7 +78,7 @@ export const tokenizeCmd = (
loggingEnabled && log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)}`); loggingEnabled && log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)}`);
let done = false; let done = false;
while (!done) { while (!done) {
loopCountCheck(); loopCountCheck('cmdTokenizer.ts - confirming crit');
// Keep running the same roll again until its not successful // Keep running the same roll again until its not successful
const [ccTempData, ccTempCounts, ccTempDists] = tokenizeCmd( const [ccTempData, ccTempCounts, ccTempDists] = tokenizeCmd(
@ -194,7 +194,7 @@ export const tokenizeCmd = (
const tempInitConf = data.initConfig.split(internalGrpWrapRegex).filter((x) => x); const tempInitConf = data.initConfig.split(internalGrpWrapRegex).filter((x) => x);
loggingEnabled && log(LT.LOG, `Split solved math into tempInitConf ${JSON.stringify(tempInitConf)}`); loggingEnabled && log(LT.LOG, `Split solved math into tempInitConf ${JSON.stringify(tempInitConf)}`);
while (tempInitConf.includes(openInternalGrp)) { while (tempInitConf.includes(openInternalGrp)) {
loopCountCheck(); loopCountCheck('cmdTokenizer.ts - handling internal group result merging');
const openIdx = tempInitConf.indexOf(openInternalGrp); const openIdx = tempInitConf.indexOf(openInternalGrp);
const closeIdx = getMatchingInternalGrpIdx(tempInitConf, openIdx); const closeIdx = getMatchingInternalGrpIdx(tempInitConf, openIdx);
@ -214,7 +214,7 @@ export const tokenizeCmd = (
.filter((x) => x); .filter((x) => x);
loggingEnabled && log(LT.LOG, `Split tempInitConfig into initConf ${JSON.stringify(initConf)}`); loggingEnabled && log(LT.LOG, `Split tempInitConfig into initConf ${JSON.stringify(initConf)}`);
while (initConf.includes(openInternal)) { while (initConf.includes(openInternal)) {
loopCountCheck(); loopCountCheck('cmdTokenizer.ts - handling internal nested roll result merging');
const openIdx = initConf.indexOf(openInternal); const openIdx = initConf.indexOf(openInternal);
const closeIdx = getMatchingInternalIdx(initConf, openIdx); const closeIdx = getMatchingInternalIdx(initConf, openIdx);

View File

@ -80,7 +80,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
for (let i = 0; i < rollConf.dieCount; i++) { for (let i = 0; i < rollConf.dieCount; i++) {
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Initial rolling ${i} of ${JSON.stringify(rollConf)}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${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
loopCountCheck(); loopCountCheck('executeRoll.ts - handling initial rolling');
// Copy the template to fill out for this iteration // Copy the template to fill out for this iteration
const rolling = getTemplateRoll(); const rolling = getTemplateRoll();
@ -104,7 +104,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
for (let i = 0; i < rollSet.length; i++) { for (let i = 0; i < rollSet.length; i++) {
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Handling rerolling and exploding ${JSON.stringify(rollSet[i])}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${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
loopCountCheck(); loopCountCheck('executeRoll.ts - handling rerolling and exploding');
// This big boolean statement first checks if reroll is on, if the roll is within the reroll range, and finally if ro is ON, make sure we haven't already rerolled the roll // This big boolean statement first checks if reroll is on, if the roll is within the reroll range, and finally if ro is ON, make sure we haven't already rerolled the roll
if (rollConf.reroll.on && rollConf.reroll.nums.includes(rollSet[i].roll) && (!rollConf.reroll.once || !rollSet[i ? i - 1 : i].rerolled)) { if (rollConf.reroll.on && rollConf.reroll.nums.includes(rollSet[i].roll) && (!rollConf.reroll.once || !rollSet[i ? i - 1 : i].rerolled)) {
@ -117,7 +117,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
if (modifiers.maxRoll && !minMaxOverride) { if (modifiers.maxRoll && !minMaxOverride) {
// If maximizeRoll is on and we've entered the reroll code, dieSize is not allowed, determine the next best option and always return that // If maximizeRoll is on and we've entered the reroll code, dieSize is not allowed, determine the next best option and always return that
mmMaxLoop: for (let m = rollConf.dieSize - 1; m > 0; m--) { mmMaxLoop: for (let m = rollConf.dieSize - 1; m > 0; m--) {
loopCountCheck(); loopCountCheck('executeRoll.ts - maximizeRoll');
if (!rollConf.reroll.nums.includes(m)) { if (!rollConf.reroll.nums.includes(m)) {
minMaxOverride = m; minMaxOverride = m;
@ -127,7 +127,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
} else if (modifiers.minRoll && !minMaxOverride) { } else if (modifiers.minRoll && !minMaxOverride) {
// If minimizeRoll is on and we've entered the reroll code, 1 is not allowed, determine the next best option and always return that // If minimizeRoll is on and we've entered the reroll code, 1 is not allowed, determine the next best option and always return that
mmMinLoop: for (let m = rollConf.dPercent.on ? 1 : 2; m <= rollConf.dieSize; m++) { mmMinLoop: for (let m = rollConf.dPercent.on ? 1 : 2; m <= rollConf.dieSize; m++) {
loopCountCheck(); loopCountCheck('executeRoll.ts - minimizeRoll');
if (!rollConf.reroll.nums.includes(m)) { if (!rollConf.reroll.nums.includes(m)) {
minMaxOverride = m; minMaxOverride = m;
@ -181,7 +181,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
for (const penRoll of rollSet) { for (const penRoll of rollSet) {
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Handling penetrating explosions ${JSON.stringify(penRoll)}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Handling penetrating explosions ${JSON.stringify(penRoll)}`);
// If loopCount gets too high, stop trying to calculate infinity // If loopCount gets too high, stop trying to calculate infinity
loopCountCheck(); loopCountCheck('executeRoll.ts - penetrating explosion');
// If the die was from an explosion, decrement it by one // If the die was from an explosion, decrement it by one
if (penRoll.exploding) { if (penRoll.exploding) {
@ -195,7 +195,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
for (let i = 0; i < rollSet.length; i++) { for (let i = 0; i < rollSet.length; i++) {
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Handling compounding explosions ${JSON.stringify(rollSet[i])}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Handling compounding explosions ${JSON.stringify(rollSet[i])}`);
// If loopCount gets too high, stop trying to calculate infinity // If loopCount gets too high, stop trying to calculate infinity
loopCountCheck(); loopCountCheck('executeRoll.ts - compounding explosion');
// Compound the exploding rolls, including the exploding flag and // Compound the exploding rolls, including the exploding flag and
if (rollSet[i].exploding) { if (rollSet[i].exploding) {
@ -215,8 +215,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
let rerollCount = 0; let rerollCount = 0;
if (rollConf.reroll.on) { if (rollConf.reroll.on) {
for (let j = 0; j < rollSet.length; j++) { for (let j = 0; j < rollSet.length; j++) {
// If loopCount gets too high, stop trying to calculate infinity loopCountCheck('executeRoll.ts - count rerolls');
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`);
rollSet[j].origIdx = j; rollSet[j].origIdx = j;
@ -265,8 +264,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
// 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) {
// If loopCount gets too high, stop trying to calculate infinity loopCountCheck('executeRoll.ts - dropping/keeping');
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`);
// Skip all rolls that were rerolled // Skip all rolls that were rerolled
@ -290,7 +288,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
// Drop all dice that are not a part of the max // Drop all dice that are not a part of the max
for (const ovaRoll of rollSet) { for (const ovaRoll of rollSet) {
loopCountCheck(); loopCountCheck('executeRoll.ts - OVA');
loggingEnabled && loggingEnabled &&
log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | checking if this roll should be dropped ${ovaRoll.roll} | to keep: ${maxRoll}`); log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | checking if this roll should be dropped ${ovaRoll.roll} | to keep: ${maxRoll}`);
@ -311,7 +309,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
const labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let labelIdx = 0; let labelIdx = 0;
const rollLabels: Array<string> = rollVals.map((count) => { const rollLabels: Array<string> = rollVals.map((count) => {
loopCountCheck(); loopCountCheck('executeRoll.ts - matching');
if (labelIdx >= labels.length) { if (labelIdx >= labels.length) {
throw new Error(`TooManyLabels_${labels.length}`); throw new Error(`TooManyLabels_${labels.length}`);
@ -327,7 +325,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): Executed
// Apply labels // Apply labels
for (const roll of rollSet) { for (const roll of rollSet) {
loopCountCheck(); loopCountCheck('executeRoll.ts - labeling matches');
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | trying to add a label to ${JSON.stringify(roll)}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | trying to add a label to ${JSON.stringify(roll)}`);
if (rollLabels[roll.roll - 1]) { if (rollLabels[roll.roll - 1]) {

View File

@ -20,7 +20,7 @@ export const formatRoll = (executedRoll: ExecutedRoll, modifiers: RollModifiers)
// 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
loggingEnabled && log(LT.LOG, `Formatting roll ${JSON.stringify(executedRoll)}`); loggingEnabled && log(LT.LOG, `Formatting roll ${JSON.stringify(executedRoll)}`);
executedRoll.rollSet.forEach((e) => { executedRoll.rollSet.forEach((e) => {
loopCountCheck(); loopCountCheck('generateFormattedRoll.ts - formatting executed roll');
loggingEnabled && log(LT.LOG, `At ${JSON.stringify(e)}`); loggingEnabled && log(LT.LOG, `At ${JSON.stringify(e)}`);
let preFormat = ''; let preFormat = '';

View File

@ -14,7 +14,7 @@ export const getGroupConf = (groupStr: string, rawStr: string): GroupConf => {
let biggest = parseInt(numberMatches.length ? numberMatches[0] : '1'); let biggest = parseInt(numberMatches.length ? numberMatches[0] : '1');
for (const num of numberMatches) { for (const num of numberMatches) {
loopCountCheck(); loopCountCheck('getGroupConf.ts - finding biggest number for die size');
const curNum = parseInt(num); const curNum = parseInt(num);
loggingEnabled && log(LT.LOG, `Finding biggest number to use as die size, ${curNum} ${biggest}`); loggingEnabled && log(LT.LOG, `Finding biggest number to use as die size, ${curNum} ${biggest}`);
@ -37,7 +37,7 @@ export const getGroupConf = (groupStr: string, rawStr: string): GroupConf => {
let maxFail: number | null = null; let maxFail: number | null = null;
while (groupSplit.length) { while (groupSplit.length) {
loopCountCheck(); loopCountCheck('getGroupConf.ts - parsing groupConf');
const option = groupSplit.shift() ?? ''; const option = groupSplit.shift() ?? '';
const value = parseInt(groupSplit.shift() ?? ''); const value = parseInt(groupSplit.shift() ?? '');

View File

@ -247,5 +247,11 @@ export const getModifiers = (args: string[]): [RollModifiers, string[]] => {
modifiers.valid = false; modifiers.valid = false;
} }
if (modifiers.simulatedNominal < 0) {
modifiers.error.name = 'NegativeSimNominal';
modifiers.error.message = 'Number of iterations for `simulatedNominal` must be at least 1';
modifiers.valid = false;
}
return [modifiers, args]; return [modifiers, args];
}; };

View File

@ -126,7 +126,7 @@ export const getRollConf = (rollStr: string, customTypes: CustomDiceShapes = new
const difficulty = parseInt(tempDifficulty.slice(0, afterDifficultyIdx) || '10'); const difficulty = parseInt(tempDifficulty.slice(0, afterDifficultyIdx) || '10');
for (let i = difficulty; i <= rollConf.dieSize; i++) { for (let i = difficulty; i <= rollConf.dieSize; i++) {
loopCountCheck(); loopCountCheck('getRollConf.ts - setting cwod difficulty');
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling cwod ${rollStr} | Parsing difficulty ${i}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling cwod ${rollStr} | Parsing difficulty ${i}`);
rollConf.success.range.push(i); rollConf.success.range.push(i);
@ -216,7 +216,7 @@ export const getRollConf = (rollStr: string, customTypes: CustomDiceShapes = new
// Loop until all remaining args are parsed // Loop until all remaining args are parsed
while (remains.length > 0) { while (remains.length > 0) {
loopCountCheck(); loopCountCheck('getRollConf.ts - parsing rollConf');
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing remains ${remains}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${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
@ -232,7 +232,7 @@ export const getRollConf = (rollStr: string, customTypes: CustomDiceShapes = new
let noNumberAfter = false; let noNumberAfter = false;
if (!(Object.values(DiceOptions) as string[]).includes(tempSep)) { if (!(Object.values(DiceOptions) as string[]).includes(tempSep)) {
NumberlessDiceOptions.some((opt) => { NumberlessDiceOptions.some((opt) => {
loopCountCheck(); loopCountCheck('getRollConf.ts - parsing numberlessDiceOptions');
loggingEnabled && log(LT.LOG, `In NumberlessDiceOptions ${opt} ${tempSep.startsWith(opt) && tempSep !== opt}`); loggingEnabled && log(LT.LOG, `In NumberlessDiceOptions ${opt} ${tempSep.startsWith(opt) && tempSep !== opt}`);
if (tempSep.startsWith(opt) && tempSep !== opt) { if (tempSep.startsWith(opt) && tempSep !== opt) {
afterSepIdx = opt.length; afterSepIdx = opt.length;

View File

@ -30,7 +30,7 @@ export const handleGroup = (
// Nested groups still exist, unwrap them // Nested groups still exist, unwrap them
while (groupParts.includes('{')) { while (groupParts.includes('{')) {
loopCountCheck(); loopCountCheck('groupHandler.ts - handling nested groups');
loggingEnabled && log(LT.LOG, `Handling Nested Groups | Current cmd: ${JSON.stringify(groupParts)}`); loggingEnabled && log(LT.LOG, `Handling Nested Groups | Current cmd: ${JSON.stringify(groupParts)}`);
@ -72,7 +72,7 @@ export const handleGroup = (
const groupResults: ReturnData[] = []; const groupResults: ReturnData[] = [];
for (const part of commaParts) { for (const part of commaParts) {
loopCountCheck(); loopCountCheck('groupHandler.ts - solving commaParts');
loggingEnabled && log(LT.LOG, `Solving commaPart: ${part}`); loggingEnabled && log(LT.LOG, `Solving commaPart: ${part}`);
const [tempData, tempCounts, tempDists] = tokenizeMath(part, modifiers, previousResults, prevGrpReturnData); const [tempData, tempCounts, tempDists] = tokenizeMath(part, modifiers, previousResults, prevGrpReturnData);
@ -131,7 +131,7 @@ export const handleGroup = (
let i = 0; let i = 0;
while (dropCount > 0 && i < groupResults.length) { while (dropCount > 0 && i < groupResults.length) {
loopCountCheck(); loopCountCheck('groupHandler.ts - handling group drop/keep');
loggingEnabled && log(LT.LOG, `Handling group dropping | Dropping ${dropCount}`); loggingEnabled && log(LT.LOG, `Handling group dropping | Dropping ${dropCount}`);
@ -148,7 +148,7 @@ export const handleGroup = (
let failCnt = 0; let failCnt = 0;
if (groupConf.success.on || groupConf.fail.on) { if (groupConf.success.on || groupConf.fail.on) {
groupResults.forEach((rd, idx) => { groupResults.forEach((rd, idx) => {
loopCountCheck(); loopCountCheck('groupHandler.ts - handling group success/fail');
if (!resultFlags[idx].dropped) { if (!resultFlags[idx].dropped) {
if ( if (
@ -265,7 +265,7 @@ export const handleGroup = (
const initConf = retData.initConfig.split(internalGrpWrapRegex).filter((x) => x); const initConf = retData.initConfig.split(internalGrpWrapRegex).filter((x) => x);
loggingEnabled && log(LT.LOG, `Split retData into initConf ${JSON.stringify(initConf)}`); loggingEnabled && log(LT.LOG, `Split retData into initConf ${JSON.stringify(initConf)}`);
while (initConf.includes(openInternalGrp)) { while (initConf.includes(openInternalGrp)) {
loopCountCheck(); loopCountCheck('groupHandler.ts - handling merging nested groups up');
const openIdx = initConf.indexOf(openInternalGrp); const openIdx = initConf.indexOf(openInternalGrp);
const closeIdx = getMatchingInternalGrpIdx(initConf, openIdx); const closeIdx = getMatchingInternalGrpIdx(initConf, openIdx);

View File

@ -1,12 +1,13 @@
import { closeLog, initLog } from '@Log4Deno'; import { closeLog, initLog } from '@Log4Deno';
import { runCmd } from 'artigen/artigen.ts'; import { runCmd } from 'artigen/artigen.ts';
import { SolvedRoll } from 'artigen/artigen.d.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts'; import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { loggingEnabled, loopLoggingEnabled } from 'artigen/utils/logFlag.ts';
loggingEnabled && initLog('logs/worker', loggingEnabled); if (loggingEnabled || loopLoggingEnabled) initLog('logs/worker', loggingEnabled || loopLoggingEnabled);
// Extend the BigInt prototype to support JSON.stringify // Extend the BigInt prototype to support JSON.stringify
interface BigIntX extends BigInt { interface BigIntX extends BigInt {
@ -23,13 +24,14 @@ self.postMessage('ready');
// Handle the roll // Handle the roll
self.onmessage = async (e: MessageEvent<QueuedRoll>) => { self.onmessage = async (e: MessageEvent<QueuedRoll>) => {
const payload = e.data; const payload = e.data;
const returnMsg = runCmd(payload) || { const returnMsg: SolvedRoll = runCmd(payload) || {
error: true, error: true,
errorCode: 'EmptyMessage', errorCode: 'EmptyMessage',
errorMsg: 'Error: Empty message', errorMsg: 'Error: Empty message',
line1: '', line1: '',
line2: '', line2: '',
line3: '', line3: '',
footer: '',
counts: { counts: {
total: 0, total: 0,
successful: 0, successful: 0,
@ -37,9 +39,12 @@ self.onmessage = async (e: MessageEvent<QueuedRoll>) => {
rerolled: 0, rerolled: 0,
dropped: 0, dropped: 0,
exploded: 0, exploded: 0,
success: 0,
fail: 0,
matches: new Map<string, number>(),
}, },
}; };
self.postMessage(returnMsg); self.postMessage(returnMsg);
loggingEnabled && (await closeLog()); if (loggingEnabled || loopLoggingEnabled) await closeLog();
self.close(); self.close();
}; };

View File

@ -28,6 +28,15 @@ import utils from 'utils/utils.ts';
import { STATUS_CODE, STATUS_TEXT } from '@std/http/status'; import { STATUS_CODE, STATUS_TEXT } from '@std/http/status';
const getUserIdForEmbed = (rollRequest: QueuedRoll): bigint => { const getUserIdForEmbed = (rollRequest: QueuedRoll): bigint => {
if (rollRequest.apiRoll) return rollRequest.api.userId;
if (rollRequest.ddRoll) {
if (rollRequest.dd.overrideAuthorId === 0n) return rollRequest.dd.authorId;
return rollRequest.dd.overrideAuthorId;
}
return 0n;
};
const getAuthorIdForButton = (rollRequest: QueuedRoll): bigint => {
if (rollRequest.apiRoll) return rollRequest.api.userId; if (rollRequest.apiRoll) return rollRequest.api.userId;
if (rollRequest.ddRoll) return rollRequest.dd.authorId; if (rollRequest.ddRoll) return rollRequest.dd.authorId;
return 0n; return 0n;
@ -189,6 +198,7 @@ export const onWorkerComplete = async (workerMessage: MessageEvent<SolvedRoll>,
}); });
} else { } else {
newMsg = await rollRequest.dd.myResponse.edit({ newMsg = await rollRequest.dd.myResponse.edit({
content: rollRequest.dd.overrideAuthorId === 0n ? '' : `<@${rollRequest.dd.overrideAuthorId}> used the \`Repeat Roll\` button for the referenced message:`,
embeds: pubEmbeds, embeds: pubEmbeds,
components: [ components: [
{ {
@ -197,7 +207,7 @@ export const onWorkerComplete = async (workerMessage: MessageEvent<SolvedRoll>,
{ {
type: MessageComponentTypes.Button, type: MessageComponentTypes.Button,
label: 'Repeat Roll', label: 'Repeat Roll',
customId: `${repeatRollCustomId}${InteractionValueSeparator}${getUserIdForEmbed(rollRequest).toString()}`, customId: `${repeatRollCustomId}${InteractionValueSeparator}${getAuthorIdForButton(rollRequest).toString()}`,
style: ButtonStyles.Secondary, style: ButtonStyles.Secondary,
emoji: '🎲', emoji: '🎲',
}, },
@ -268,7 +278,7 @@ Please click on "<@${botId}> *Click to see attachment*" above this message to se
details: returnMsg.line3, details: returnMsg.line3,
}, },
counts: rollRequest.modifiers.count ? returnMsg.counts : null, counts: rollRequest.modifiers.count ? returnMsg.counts : null,
rollDistributions: returnMsg.rollDistributions.entries().toArray(), rollDistributions: rollRequest.modifiers.rollDist ? returnMsg.rollDistributions.entries().toArray() : null,
}, },
}), }),
{ {
@ -286,7 +296,7 @@ Please click on "<@${botId}> *Click to see attachment*" above this message to se
embeds: [ embeds: [
( (
await generateRollEmbed( await generateRollEmbed(
rollRequest.dd.authorId, 0n,
<SolvedRoll> { <SolvedRoll> {
error: true, error: true,
errorMsg: errorMsg:

View File

@ -28,7 +28,7 @@ export const terminateWorker = async (rollWorker: Worker, rollRequest: QueuedRol
embeds: [ embeds: [
( (
await generateRollEmbed( await generateRollEmbed(
rollRequest.dd.authorId, 0n,
<SolvedRoll> { <SolvedRoll> {
error: true, error: true,
errorCode: 'TooComplex', errorCode: 'TooComplex',

View File

@ -1,10 +1,15 @@
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config'; import config from '~config';
import { loopLoggingEnabled } from 'artigen/utils/logFlag.ts';
let loopCount = 0; let loopCount = 0;
// Will ensure if maxLoops is 10, 10 loops will be allowed, 11 will not. // Will ensure if maxLoops is 10, 10 loops will be allowed, 11 will not.
export const loopCountCheck = (): void => { export const loopCountCheck = (location = 'unset'): void => {
loopCount++; loopCount++;
loopLoggingEnabled && log(LT.LOG, `Loop #${loopCount} at "${location}"`);
if (loopCount > config.limits.maxLoops) { if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded'); throw new Error('MaxLoopsExceeded');
} }

View File

@ -26,6 +26,7 @@ interface DDQueuedRoll extends BaseQueuedRoll {
dd: { dd: {
myResponse: DiscordenoMessage; myResponse: DiscordenoMessage;
originalMessage: DiscordenoMessage; originalMessage: DiscordenoMessage;
overrideAuthorId: bigint;
authorId: bigint; authorId: bigint;
}; };
} }

View File

@ -34,7 +34,7 @@ export const mathSolver = (conf: MathConf[], wrapDetails = false): SolvedStep =>
// Evaluate all parenthesis // Evaluate all parenthesis
while (conf.includes('(')) { while (conf.includes('(')) {
loopCountCheck(); loopCountCheck('mathSolver.ts - evaluating parens');
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`); loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`);
// Get first open parenthesis // Get first open parenthesis
@ -75,7 +75,7 @@ export const mathSolver = (conf: MathConf[], wrapDetails = false): SolvedStep =>
// Start at index 1 as there will never be implicit multiplication before the first element // Start at index 1 as there will never be implicit multiplication before the first element
loggingEnabled && log(LT.LOG, `Checking for missing implicit multiplication ${JSON.stringify(conf)}`); loggingEnabled && log(LT.LOG, `Checking for missing implicit multiplication ${JSON.stringify(conf)}`);
for (let i = 1; i < conf.length; i++) { for (let i = 1; i < conf.length; i++) {
loopCountCheck(); loopCountCheck('mathSolver.ts - checking for implicit multiplication');
const prevConfAsStr = <string> conf[i - 1]; const prevConfAsStr = <string> conf[i - 1];
const curConfAsStr = <string> conf[i]; const curConfAsStr = <string> conf[i];
@ -94,14 +94,15 @@ export const mathSolver = (conf: MathConf[], wrapDetails = false): SolvedStep =>
['+', '-'], ['+', '-'],
]; ];
allCurOps.forEach((curOps) => { allCurOps.forEach((curOps) => {
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`); // No loopCountCheck here since its finite/will always be 3 loops
loggingEnabled && log(LT.LOG, `Cur Ops ${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++) {
loopCountCheck(); loggingEnabled && log(LT.LOG, `Checking ${JSON.stringify(conf[i])}`);
loggingEnabled && 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 tier of operators // Check if the current index is in the active tier of operators
if (curOps.includes(conf[i].toString())) { if (curOps.includes(conf[i].toString())) {
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} as ${JSON.stringify(conf[i])} is in curOps`);
loopCountCheck('mathSolver.ts - evaluating roll');
// Grab the operands from before and after the operator // Grab the operands from before and after the operator
const operand1 = conf[i - 1]; const operand1 = conf[i - 1];
const operand2 = conf[i + 1]; const operand2 = conf[i + 1];

View File

@ -45,11 +45,11 @@ export const tokenizeMath = (
loggingEnabled && log(LT.LOG, `Split roll into mathConf ${JSON.stringify(mathConf)}`); loggingEnabled && log(LT.LOG, `Split roll into mathConf ${JSON.stringify(mathConf)}`);
// Verify balanced parens before doing anything // Verify balanced parens before doing anything
assertParenBalance(mathConf); if (mathConf.includes('(') || mathConf.includes(')')) assertParenBalance(mathConf);
// 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++) {
loopCountCheck(); loopCountCheck('mathTokenizer.ts - parsing all tokens into MathConf');
loggingEnabled && log(LT.LOG, `Parsing roll ${JSON.stringify(cmd)} | Evaluating rolls into math-able items ${JSON.stringify(mathConf[i])}`); loggingEnabled && log(LT.LOG, `Parsing roll ${JSON.stringify(cmd)} | Evaluating rolls into math-able items ${JSON.stringify(mathConf[i])}`);
@ -201,7 +201,7 @@ export const tokenizeMath = (
const formattedRoll = formatRoll(executedRoll, modifiers); const formattedRoll = formatRoll(executedRoll, modifiers);
mathConf[i] = formattedRoll.solvedStep; mathConf[i] = formattedRoll.solvedStep;
countDetails.push(formattedRoll.countDetails); countDetails.push(formattedRoll.countDetails);
rollDists.push(formattedRoll.rollDistributions); if (modifiers.rollDist) rollDists.push(formattedRoll.rollDistributions);
} }
} }
@ -272,7 +272,7 @@ export const tokenizeMath = (
let i = 0; let i = 0;
while (dropCount > 0 && i < allRollSets.length) { while (dropCount > 0 && i < allRollSets.length) {
loopCountCheck(); loopCountCheck('mathTokenizer.ts - handling group dropping');
loggingEnabled && log(LT.LOG, `Handling group dropping | Dropping ${dropCount}, looking at ${JSON.stringify(allRollSets[i])}`); loggingEnabled && log(LT.LOG, `Handling group dropping | Dropping ${dropCount}, looking at ${JSON.stringify(allRollSets[i])}`);
@ -293,7 +293,7 @@ export const tokenizeMath = (
// Handle marking new successes/fails // Handle marking new successes/fails
if (groupConf.success.on || groupConf.fail.on) { if (groupConf.success.on || groupConf.fail.on) {
allRollSets.forEach((rs) => { allRollSets.forEach((rs) => {
loopCountCheck(); loopCountCheck('mathTokenizer.ts - handling group success/fails');
if (!rs.dropped && !rs.rerolled) { if (!rs.dropped && !rs.rerolled) {
if (groupConf.success.on && groupConf.success.range.includes(rs.roll)) { if (groupConf.success.on && groupConf.success.range.includes(rs.roll)) {
@ -318,7 +318,7 @@ export const tokenizeMath = (
const formattedRoll = formatRoll(executedRoll, modifiers); const formattedRoll = formatRoll(executedRoll, modifiers);
mathConf[rollGroupIdx] = formattedRoll.solvedStep; mathConf[rollGroupIdx] = formattedRoll.solvedStep;
countDetails.push(formattedRoll.countDetails); countDetails.push(formattedRoll.countDetails);
rollDists.push(formattedRoll.rollDistributions); if (modifiers.rollDist) rollDists.push(formattedRoll.rollDistributions);
}); });
} }

View File

@ -16,7 +16,7 @@ export const rollCounter = (rollSet: RollSet[]): CountDetails => {
}; };
rollSet.forEach((roll) => { rollSet.forEach((roll) => {
loopCountCheck(); loopCountCheck('counter.ts - summing RollSet into CountDetails');
countDetails.total++; countDetails.total++;
if (roll.critHit) countDetails.successful++; if (roll.critHit) countDetails.successful++;
if (roll.critFail) countDetails.failed++; if (roll.critFail) countDetails.failed++;
@ -34,9 +34,9 @@ export const rollCounter = (rollSet: RollSet[]): CountDetails => {
export const reduceCountDetails = (counts: CountDetails[]): CountDetails => export const reduceCountDetails = (counts: CountDetails[]): CountDetails =>
counts.reduce( counts.reduce(
(acc, cur) => { (acc, cur) => {
loopCountCheck(); loopCountCheck('counter.ts - merging array of CountDetails down to single CountDetail');
cur.matches.forEach((cnt, label) => { cur.matches.forEach((cnt, label) => {
loopCountCheck(); loopCountCheck('counter.ts - merging matches');
acc.matches.set(label, (acc.matches.get(label) ?? 0) + cnt); acc.matches.set(label, (acc.matches.get(label) ?? 0) + cnt);
}); });
return { return {

View File

@ -259,13 +259,17 @@ export const generateRollEmbed = (
// Embed desc limit is 4096 // Embed desc limit is 4096
// Discord only formats 200 items per message // Discord only formats 200 items per message
if (fullDesc.length < 4_000 && formattingCount <= 200) { const fullSize = fullDesc.length + returnDetails.footer.length;
if (fullSize < 4_000 && formattingCount <= 200) {
// Response is valid size // Response is valid size
return { return {
charCount: fullDesc.length, charCount: fullSize,
embed: { embed: {
color: infoColor2, color: infoColor2,
description: fullDesc, description: fullDesc,
footer: {
text: returnDetails.footer,
},
}, },
hasAttachment: false, hasAttachment: false,
}; };

View File

@ -11,13 +11,15 @@ import { loggingEnabled } from 'artigen/utils/logFlag.ts';
export const escapeCharacters = (str: string, esc: string): string => { export 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 (const e of esc) { for (const e of esc) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`); loggingEnabled && log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`);
if (str.includes(e)) {
loopCountCheck(`escape.ts - escaping character ${e}`);
// 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(`[${e}]`, 'g'); const tempRgx = new RegExp(`[${e}]`, 'g');
str = str.replace(tempRgx, `\\${e}`); str = str.replace(tempRgx, `\\${e}`);
} }
}
return str; return str;
}; };

View File

@ -1 +1,3 @@
export const loggingEnabled = false; export const loggingEnabled = false;
export const loopLoggingEnabled = false;
export const showLoopCountDebug = false;

View File

@ -22,7 +22,8 @@ const checkBalance = (
// 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
for (let i = openIdx; i < conf.length; i++) { for (let i = openIdx; i < conf.length; i++) {
countLoops && loopCountCheck(); countLoops &&
loopCountCheck(`parenBalance.ts - ${getMatching ? 'Looking for matching' : 'Checking'} ${openStr}/${closeStr}${getMatching ? '' : ' balance'}`);
loggingEnabled && loggingEnabled &&
log( log(
LT.LOG, LT.LOG,

View File

@ -14,7 +14,7 @@ export const addToRange = (tSep: string, range: Array<number>, tNum: number) =>
const internalAddMultipleToRange = (tSep: string, range: Array<number>, start: number, end: number) => { const internalAddMultipleToRange = (tSep: string, range: Array<number>, start: number, end: number) => {
for (let i = start; i <= end; i++) { for (let i = start; i <= end; i++) {
loopCountCheck(); loopCountCheck(`rangeAdder.ts - ${tSep} range adder`);
addToRange(tSep, range, i); addToRange(tSep, range, i);
} }
}; };

View File

@ -10,7 +10,7 @@ export const createRollDistMap = (rollSet: RollSet[]): RollDistributionMap => {
const rollDistMap = new Map<string, number[]>(); const rollDistMap = new Map<string, number[]>();
rollSet.forEach((roll) => { rollSet.forEach((roll) => {
loopCountCheck(); loopCountCheck('rollDist.ts - convert RollSet into RollDist');
const tempArr: number[] = rollDistMap.get(rollDistKey(roll.type, roll.size)) ?? new Array<number>(roll.type === 'fate' ? roll.size + 2 : roll.size).fill(0); const tempArr: number[] = rollDistMap.get(rollDistKey(roll.type, roll.size)) ?? new Array<number>(roll.type === 'fate' ? roll.size + 2 : roll.size).fill(0);
tempArr[roll.type === 'fate' ? roll.roll + 1 : roll.roll - 1]++; tempArr[roll.type === 'fate' ? roll.roll + 1 : roll.roll - 1]++;
rollDistMap.set(rollDistKey(roll.type, roll.size), tempArr); rollDistMap.set(rollDistKey(roll.type, roll.size), tempArr);
@ -22,17 +22,17 @@ export const createRollDistMap = (rollSet: RollSet[]): RollDistributionMap => {
// Collapses an array of RollDistMaps into a single RollDistMap // Collapses an array of RollDistMaps into a single RollDistMap
export const reduceRollDistMaps = (rollDistArr: RollDistributionMap[]): RollDistributionMap => export const reduceRollDistMaps = (rollDistArr: RollDistributionMap[]): RollDistributionMap =>
rollDistArr.reduce((acc, cur) => { rollDistArr.reduce((acc, cur) => {
loopCountCheck(); loopCountCheck('rollDist.ts - merge array of RollDists into single RollDist');
cur cur
.entries() .entries()
.toArray() .toArray()
.forEach(([key, value]) => { .forEach(([key, value]) => {
loopCountCheck(); loopCountCheck('rollDist.ts - doing the merge on each item of current');
const tempArr = acc.get(key) ?? new Array<number>(value.length).fill(0); const tempArr = acc.get(key) ?? new Array<number>(value.length).fill(0);
for (let i = 0; i < tempArr.length; i++) { for (let i = 0; i < tempArr.length; i++) {
loopCountCheck(); loopCountCheck('rollDist.ts - doing the merge');
tempArr[i] += value[i]; tempArr[i] += value[i];
} }

View File

@ -12,7 +12,7 @@ export const generateRollVals = (rollConf: RollConf, rollSet: RollSet[], rollStr
// Count up all rolls // Count up all rolls
for (const ovaRoll of rollSet) { for (const ovaRoll of rollSet) {
loopCountCheck(); loopCountCheck('rollValCounter.ts - counting roll vals');
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | incrementing rollVals for ${JSON.stringify(ovaRoll)}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | incrementing rollVals for ${JSON.stringify(ovaRoll)}`);
if (!ovaRoll.dropped && !ovaRoll.rerolled) { if (!ovaRoll.dropped && !ovaRoll.rerolled) {

View File

@ -18,10 +18,11 @@ import { roll, rollSC } from 'commands/roll.ts';
import { rollHelp } from 'commands/rollHelp.ts'; import { rollHelp } from 'commands/rollHelp.ts';
import { stats, statsSC } from 'commands/stats.ts'; import { stats, statsSC } from 'commands/stats.ts';
import { toggleInline, toggleInlineSC } from 'commands/toggleInline.ts'; import { toggleInline, toggleInlineSC } from 'commands/toggleInline.ts';
import { toggleRepeat, toggleRepeatSC } from 'commands/toggleUnrestrictedRepeat.ts';
import { version, versionSC } from 'commands/version.ts'; import { version, versionSC } from 'commands/version.ts';
export const announceSlashCommands = () => { export const announceSlashCommands = () => {
upsertSlashCommands([aliasSC, heatmapSC, helpSC, infoSC, privacySC, reportSC, ripSC, rollSC, statsSC, toggleInlineSC, versionSC]); upsertSlashCommands([aliasSC, heatmapSC, helpSC, infoSC, privacySC, reportSC, ripSC, rollSC, statsSC, toggleInlineSC, toggleRepeatSC, versionSC]);
}; };
export const commands = { export const commands = {
@ -43,6 +44,7 @@ export const commands = {
rollHelp, rollHelp,
stats, stats,
toggleInline, toggleInline,
toggleRepeat,
version, version,
}; };
@ -57,5 +59,6 @@ export const slashCommandDetails = {
rollSC, rollSC,
statsSC, statsSC,
toggleInlineSC, toggleInlineSC,
toggleRepeatSC,
versionSC, versionSC,
}; };

View File

@ -4,6 +4,7 @@ import { RollAliasHelpPages } from 'commands/helpLibrary/aliasHelp.ts';
import { ApiHelpPages } from 'commands/helpLibrary/apiHelp.ts'; import { ApiHelpPages } from 'commands/helpLibrary/apiHelp.ts';
import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts'; import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
import { InlineHelpPages } from 'commands/helpLibrary/inlineHelp.ts'; import { InlineHelpPages } from 'commands/helpLibrary/inlineHelp.ts';
import { RepeatHelpPages } from 'commands/helpLibrary/repeatHelp.ts';
import { RootRollHelpPages } from 'commands/helpLibrary/rollHelp/_rootRollHelp.ts'; import { RootRollHelpPages } from 'commands/helpLibrary/rollHelp/_rootRollHelp.ts';
@ -13,6 +14,7 @@ const dict = new Map<string, HelpPage | HelpContents>([
['roll-help', RootRollHelpPages], ['roll-help', RootRollHelpPages],
['alias', RollAliasHelpPages], ['alias', RollAliasHelpPages],
['inline', InlineHelpPages], ['inline', InlineHelpPages],
['repeat', RepeatHelpPages],
[ [
'opt-out', 'opt-out',
{ {

View File

@ -0,0 +1,53 @@
import config from '~config';
import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
const name = 'Unrestricted Repeat';
const description = `${config.name} has an option to allow anyone to use the \`Repeat Roll\` button.
By default, Unrestricted Repeat Rolls are disabled in your guild, meaning only the original roller can use the \`Repeat Roll\` button. These commands may only be used by the Owner or Admins of your guild.`;
const dict = new Map<string, HelpContents>([
[
'status',
{
name: 'Status',
description: `**Usage:** \`${config.prefix}repeat status\`
Shows the current status of Repeat Rolling for this guild.`,
},
],
[
'enable',
{
name: 'Allow Unrestricted Repeat',
description: `**Usage:** \`${config.prefix}repeat allow\` or \`${config.prefix}repeat enable\`
Allows Unrestricted Repeat Rolls for this guild. This allows anyone in the guild to use the \`Repeat Roll\` button on any roll from anyone.`,
},
],
[
'disable',
{
name: 'Block Unrestricted Repeat',
description: `**Usage:** \`${config.prefix}repeat block\` or \`${config.prefix}repeat disable\` or \`${config.prefix}repeat delete\`
Blocks Unrestricted Repeat rolls for this guild. This only allows the original roller to use the \`Repeat Roll\` button.`,
},
],
[
'help',
{
name: 'Help',
description: `**Usage:** \`${config.prefix}repeat help\` or \`${config.prefix}repeat h\`
Opens the help library to the Unrestricted Repeat Help section.`,
},
],
]);
export const RepeatHelpPages: HelpPage = {
name,
description,
isPage: true,
dict,
};

View File

@ -27,7 +27,13 @@ export const rollSC: CreateGlobalApplicationCommand = {
], ],
}; };
export const roll = async (msgOrInt: DiscordenoMessage | Interaction, args: string[], command: string) => { export const roll = async (
msgOrInt: DiscordenoMessage | Interaction,
args: string[],
command: string,
overrideAuthor?: bigint,
forceOriginalAuthor?: bigint,
) => {
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
const currDateTime = new Date(); const currDateTime = new Date();
dbClient.execute(queries.callIncCnt('roll')).catch((e) => utils.commonLoggers.dbError('roll.ts:20', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('roll')).catch((e) => utils.commonLoggers.dbError('roll.ts:20', 'call sproc INC_CNT on', e));
@ -44,7 +50,7 @@ export const roll = async (msgOrInt: DiscordenoMessage | Interaction, args: stri
originalCommand = `${config.prefix}${originalCommand.trim()}`; originalCommand = `${config.prefix}${originalCommand.trim()}`;
} }
const m = await utils.sendOrInteract(msgOrInt, 'roll.ts:47', rollingEmbed, true); const m = await utils.sendOrInteract(msgOrInt, 'roll.ts:47', rollingEmbed, true, !overrideAuthor);
if (!m) { if (!m) {
throw new Error("My message didn't send!"); throw new Error("My message didn't send!");
} }
@ -55,6 +61,7 @@ export const roll = async (msgOrInt: DiscordenoMessage | Interaction, args: stri
// Return early if the modifiers were invalid // Return early if the modifiers were invalid
if (!modifiers.valid) { if (!modifiers.valid) {
m.edit(generateRollError('Modifiers invalid:', modifiers.error.name, modifiers.error.message)).catch((e) => utils.commonLoggers.messageEditError('roll.ts:50', m, e)); m.edit(generateRollError('Modifiers invalid:', modifiers.error.name, modifiers.error.message)).catch((e) => utils.commonLoggers.messageEditError('roll.ts:50', m, e));
return;
} }
let rollCmd = (hasOwnProperty(msgOrInt, 'token') ? args.join('') : msgOrInt.content).startsWith(`${config.prefix}r`) ? remainingArgs.join('') : `${command}${remainingArgs.join('')}`; let rollCmd = (hasOwnProperty(msgOrInt, 'token') ? args.join('') : msgOrInt.content).startsWith(`${config.prefix}r`) ? remainingArgs.join('') : `${command}${remainingArgs.join('')}`;
@ -72,7 +79,8 @@ export const roll = async (msgOrInt: DiscordenoMessage | Interaction, args: stri
ddRoll: true, ddRoll: true,
testRoll: false, testRoll: false,
dd: { dd: {
authorId: utils.getAuthorIdFromMessageOrInteraction(msgOrInt), overrideAuthorId: overrideAuthor ?? 0n,
authorId: forceOriginalAuthor ? forceOriginalAuthor : utils.getAuthorIdFromMessageOrInteraction(msgOrInt),
myResponse: m, myResponse: m,
originalMessage: hasOwnProperty(msgOrInt, 'token') ? m : msgOrInt, originalMessage: hasOwnProperty(msgOrInt, 'token') ? m : msgOrInt,
}, },

View File

@ -42,8 +42,8 @@ export const toggleInline = async (msgOrInt: DiscordenoMessage | Interaction, ar
// Light telemetry to see how many times a command is being run // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('inline')).catch((e) => utils.commonLoggers.dbError('toggleInline.ts:16', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('inline')).catch((e) => utils.commonLoggers.dbError('toggleInline.ts:16', 'call sproc INC_CNT on', e));
// Local apiArg in lowercase // Local inlineArg in lowercase
const apiArg = (args[0] || '').toLowerCase(); const inlineArg = (args[0] || '').toLowerCase();
const guildId = BigInt(msgOrInt.guildId ?? '0'); const guildId = BigInt(msgOrInt.guildId ?? '0');
@ -78,7 +78,7 @@ export const toggleInline = async (msgOrInt: DiscordenoMessage | Interaction, ar
if (await hasGuildPermissions(guildId, utils.getAuthorIdFromMessageOrInteraction(msgOrInt), ['ADMINISTRATOR'])) { if (await hasGuildPermissions(guildId, utils.getAuthorIdFromMessageOrInteraction(msgOrInt), ['ADMINISTRATOR'])) {
let enable = false; let enable = false;
switch (apiArg) { switch (inlineArg) {
case 'allow': case 'allow':
case 'enable': case 'enable':
enable = true; enable = true;

View File

@ -0,0 +1,156 @@
import { CreateGlobalApplicationCommand, DiscordApplicationCommandOptionTypes, DiscordenoMessage, hasGuildPermissions, Interaction } from '@discordeno';
import config from '~config';
import { generateHelpMessage } from 'commands/helpLibrary/generateHelpMessage.ts';
import dbClient from 'db/client.ts';
import { queries, repeatList } from 'db/common.ts';
import { failColor, infoColor1, successColor } from 'embeds/colors.ts';
import utils from 'utils/utils.ts';
export const toggleRepeatSC: CreateGlobalApplicationCommand = {
name: 'toggle-unrestricted-repeat',
description: 'Enable or disable unrestricted repeat rolling for this guild.',
options: [
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'enable',
description: 'Enables/Allows unrestricted repeat rolling in this guild.',
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'disable',
description: 'Disables/Blocks unrestricted repeat rolling in this guild.',
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'status',
description: 'Gets the current status of repeat rolling for this guild.',
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'help',
description: 'Opens the help library to the Toggle Repeat help page.',
},
],
};
export const toggleRepeat = async (msgOrInt: DiscordenoMessage | Interaction, args: string[]) => {
// Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('repeat')).catch((e) => utils.commonLoggers.dbError('toggleRepeat.ts:16', 'call sproc INC_CNT on', e));
// Local repeatArg in lowercase
const repeatArg = (args[0] || '').toLowerCase();
const guildId = BigInt(msgOrInt.guildId ?? '0');
// Alert users who DM the bot that this command is for guilds only
if (guildId === 0n) {
utils.sendOrInteract(msgOrInt, 'toggleRepeat.ts:45', {
embeds: [
{
color: failColor,
title: 'Toggle Repeat commands are only available in guilds.',
},
],
});
return;
}
let errorOut = false;
const guildQuery = await dbClient.query(`SELECT guildid FROM allow_unrestricted_repeat WHERE guildid = ?`, [guildId]).catch((e0) => {
utils.commonLoggers.dbError('toggleRepeat.ts:36', 'query', e0);
utils.sendOrInteract(msgOrInt, 'toggleRepeat.ts:59', {
embeds: [
{
color: failColor,
title: 'Failed to check Unrestricted Repeat status for this guild.',
description: 'If this issue persists, please report this to the developers.',
},
],
});
errorOut = true;
});
if (errorOut) return;
if (await hasGuildPermissions(guildId, utils.getAuthorIdFromMessageOrInteraction(msgOrInt), ['ADMINISTRATOR'])) {
let enable = false;
switch (repeatArg) {
case 'allow':
case 'enable':
enable = true;
if (!repeatList.includes(guildId)) {
await dbClient.execute('INSERT INTO allow_unrestricted_repeat(guildid) values(?)', [guildId]).catch((e) => {
utils.commonLoggers.dbError('toggleRepeat.ts:58', 'insert into allow_unrestricted_repeat', e);
errorOut = true;
});
if (!errorOut) {
repeatList.push(guildId);
}
}
break;
case 'block':
case 'disable':
case 'delete':
await dbClient.execute('DELETE FROM allow_unrestricted_repeat WHERE guildid = ?', [guildId]).catch((e) => {
utils.commonLoggers.dbError('toggleRepeat.ts:65', 'delete from allow_unrestricted_repeat', e);
errorOut = true;
});
if (!errorOut && repeatList.includes(guildId)) {
repeatList.splice(repeatList.indexOf(guildId), 1);
}
break;
case 'status':
utils.sendOrInteract(msgOrInt, 'toggleRepeat.ts:98', {
embeds: [
{
color: infoColor1,
title: `Unrestricted Repeat is ${guildQuery.length ? 'Enabled' : 'Disabled'} for this guild`,
description: `This means ${guildQuery.length ? 'anyone' : 'only the original roller'} can use the \`Repeat Roll\` button.
To ${guildQuery.length ? 'disable' : 'enable'} it, run the following command:\n\`${config.prefix}repeat ${guildQuery.length ? 'disable' : 'enable'}\``,
},
],
});
return;
case 'h':
case 'help':
default:
utils.sendOrInteract(msgOrInt, 'toggleRepeat.ts:113', generateHelpMessage('repeat'));
return;
}
if (errorOut) {
utils.sendOrInteract(msgOrInt, 'toggleRepeat.ts:117', {
embeds: [
{
color: failColor,
title: `Failed to ${enable ? 'Enable' : 'Disable'} Unrestricted Repeat for this guild`,
description: 'Please try the command again. If this issue persists, please report this to the developers.',
},
],
});
return;
}
utils.sendOrInteract(msgOrInt, 'toggleRepeat.ts:128', {
embeds: [
{
color: successColor,
title: `Successfully ${enable ? 'Enabled' : 'Disabled'} Unrestricted Repeat for this guild`,
description: `${enable ? 'Anyone' : 'Only the original roller'} may now use the \`Repeat Roll\` button.`,
},
],
});
} else {
utils.sendOrInteract(msgOrInt, 'toggleRepeat.ts:137', {
embeds: [
{
color: failColor,
title: 'Toggle Unrestricted Repeat commands are powerful and can only be used by guild Owners and Admins.',
},
],
});
}
};

View File

@ -21,6 +21,13 @@ dbInlineList.forEach((guildIdObj: GuildIdObj) => {
inlineList.push(guildIdObj.guildid); inlineList.push(guildIdObj.guildid);
}); });
// List of guilds who have allowed unrestricted repeat rolls
export const repeatList: Array<bigint> = [];
const dbRepeatList = await dbClient.query('SELECT * FROM allow_unrestricted_repeat');
dbRepeatList.forEach((guildIdObj: GuildIdObj) => {
repeatList.push(guildIdObj.guildid);
});
export const weekDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; export const weekDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
export const queries = { export const queries = {

View File

@ -42,7 +42,10 @@ export const guildDeleteHandler = (guild: DiscordenoGuild) => {
.execute('DELETE FROM allowed_guilds WHERE guildid = ? AND banned = 0', [guild.id]) .execute('DELETE FROM allowed_guilds WHERE guildid = ? AND banned = 0', [guild.id])
.catch((e) => utils.commonLoggers.dbError('guildDelete.ts:41', 'delete from', e)); .catch((e) => utils.commonLoggers.dbError('guildDelete.ts:41', 'delete from', e));
dbClient.execute('DELETE FROM allow_inline WHERE guildid = ?', [guild.id]).catch((e) => utils.commonLoggers.dbError('guildDelete.ts:42', 'delete from', e)); dbClient.execute('DELETE FROM allow_inline WHERE guildid = ?', [guild.id]).catch((e) => utils.commonLoggers.dbError('guildDelete.ts:42', 'delete from', e));
dbClient
.execute('DELETE FROM allow_unrestricted_repeat WHERE guildid = ?', [guild.id])
.catch((e) => utils.commonLoggers.dbError('guildDelete.ts:47', 'delete from', e));
dbClient dbClient
.execute('DELETE FROM aliases WHERE guildid = ? AND userid = ?', [guild.id, 0n]) .execute('DELETE FROM aliases WHERE guildid = ? AND userid = ?', [guild.id, 0n])
.catch((e) => utils.commonLoggers.dbError('guildDelete.ts:45', 'delete from', e)); .catch((e) => utils.commonLoggers.dbError('guildDelete.ts:50', 'delete from', e));
}; };

View File

@ -15,6 +15,8 @@ import {
} from '@discordeno'; } from '@discordeno';
import { log, LogTypes as LT } from '@Log4Deno'; import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import { Modifiers } from 'artigen/dice/getModifiers.ts'; import { Modifiers } from 'artigen/dice/getModifiers.ts';
import { repeatRollCustomId } from 'artigen/managers/handler/workerComplete.ts'; import { repeatRollCustomId } from 'artigen/managers/handler/workerComplete.ts';
@ -26,6 +28,8 @@ import { commands, slashCommandDetails } from 'commands/_index.ts';
import { generateHelpMessage, helpCustomId } from 'commands/helpLibrary/generateHelpMessage.ts'; import { generateHelpMessage, helpCustomId } from 'commands/helpLibrary/generateHelpMessage.ts';
import { repeatList } from 'db/common.ts';
import { failColor } from 'embeds/colors.ts'; import { failColor } from 'embeds/colors.ts';
import { messageCreateHandler } from 'events/messageCreate.ts'; import { messageCreateHandler } from 'events/messageCreate.ts';
@ -83,9 +87,10 @@ export const interactionCreateHandler = async (interaction: Interaction) => {
} }
if (interaction.data.customId.startsWith(repeatRollCustomId) && interaction.message) { if (interaction.data.customId.startsWith(repeatRollCustomId) && interaction.message) {
const missingUserId = 'missingUserId';
const ownerId = interaction.data.customId.split(InteractionValueSeparator)[1] ?? 'missingOwnerId'; const ownerId = interaction.data.customId.split(InteractionValueSeparator)[1] ?? 'missingOwnerId';
const userInteractingId = interaction.member?.user.id ?? interaction.user?.id ?? 'missingUserId'; const userInteractingId = interaction.member?.user.id ?? interaction.user?.id ?? missingUserId;
if (ownerId === userInteractingId) { if (ownerId === userInteractingId || (userInteractingId !== missingUserId && repeatList.includes(BigInt(interaction.guildId ?? '0')))) {
const botMsg: DiscordenoMessage = await structures.createDiscordenoMessage(interaction.message); const botMsg: DiscordenoMessage = await structures.createDiscordenoMessage(interaction.message);
if (botMsg && botMsg.messageReference) { if (botMsg && botMsg.messageReference) {
const rollMsg = await getMessage(BigInt(botMsg.messageReference.channelId ?? '0'), BigInt(botMsg.messageReference.messageId ?? '0')).catch((e) => const rollMsg = await getMessage(BigInt(botMsg.messageReference.channelId ?? '0'), BigInt(botMsg.messageReference.messageId ?? '0')).catch((e) =>
@ -98,7 +103,11 @@ export const interactionCreateHandler = async (interaction: Interaction) => {
); );
if (rollMsg && !rollMsg.isBot) { if (rollMsg && !rollMsg.isBot) {
ackInteraction(interaction); ackInteraction(interaction);
if (ownerId === userInteractingId) {
messageCreateHandler(rollMsg); messageCreateHandler(rollMsg);
} else {
messageCreateHandler(rollMsg, BigInt(userInteractingId));
}
return; return;
} }
} }
@ -120,7 +129,28 @@ export const interactionCreateHandler = async (interaction: Interaction) => {
} }
rollStr += ` ${Modifiers.YVars} ${yVarVals.join(',')}`; rollStr += ` ${Modifiers.YVars} ${yVarVals.join(',')}`;
} }
commands.roll(interaction, rollStr.split(argSpacesSplitRegex), '');
let responseInteraction: Interaction | DiscordenoMessage = interaction;
if (botMsg && botMsg.messageReference) {
const rollMsg = await getMessage(BigInt(botMsg.messageReference.channelId ?? '0'), BigInt(botMsg.messageReference.messageId ?? '0')).catch((e) =>
utils.commonLoggers.messageGetError(
'interactionCreate.ts:92',
botMsg.messageReference?.channelId ?? '0',
botMsg.messageReference?.messageId ?? '0',
e,
)
);
if (rollMsg) {
ackInteraction(interaction);
responseInteraction = rollMsg;
}
}
if (ownerId === userInteractingId) {
commands.roll(responseInteraction, rollStr.split(argSpacesSplitRegex), '');
} else {
commands.roll(responseInteraction, rollStr.split(argSpacesSplitRegex), '', BigInt(userInteractingId), BigInt(ownerId));
}
return; return;
} }
} }
@ -133,7 +163,9 @@ export const interactionCreateHandler = async (interaction: Interaction) => {
{ {
color: failColor, color: failColor,
title: 'Not Allowed!', title: 'Not Allowed!',
description: 'Only the original user that requested this roll can repeat it.', description: `Only the original user that requested this roll can repeat it.
Do you want to be able to use the \`Repeat Roll\` button on any rolls from anyone? Ask your Guild Owner or Admins to run \`${config.prefix}repeat enable\`. More information can be found in the help library under \`Unrestricted Repeat\`.`,
}, },
], ],
}, },
@ -220,6 +252,12 @@ export const interactionCreateHandler = async (interaction: Interaction) => {
commands.toggleInline(interaction, [subCommand]); commands.toggleInline(interaction, [subCommand]);
return; return;
} }
case slashCommandDetails.toggleRepeatSC.name: {
const option = (interaction.data.options as ApplicationCommandInteractionDataOptionSubCommand[])?.shift();
const subCommand = option ? option.name : '';
commands.toggleRepeat(interaction, [subCommand]);
return;
}
case slashCommandDetails.versionSC.name: case slashCommandDetails.versionSC.name:
commands.version(interaction); commands.version(interaction);
return; return;

View File

@ -8,7 +8,7 @@ import { commands } from 'commands/_index.ts';
import { ignoreList, inlineList } from 'db/common.ts'; import { ignoreList, inlineList } from 'db/common.ts';
import { argSpacesSplitRegex } from 'artigen/utils/escape.ts'; import { argSpacesSplitRegex } from 'artigen/utils/escape.ts';
export const messageCreateHandler = (message: DiscordenoMessage) => { export const messageCreateHandler = (message: DiscordenoMessage, overrideAuthor?: bigint) => {
// Ignore all other bots // Ignore all other bots
if (message.isBot) return; if (message.isBot) return;
@ -31,6 +31,7 @@ export const messageCreateHandler = (message: DiscordenoMessage) => {
.split(argSpacesSplitRegex) .split(argSpacesSplitRegex)
.filter((x) => x), .filter((x) => x),
'', '',
overrideAuthor,
); );
} }
// return as we are done handling this message // return as we are done handling this message
@ -160,11 +161,16 @@ export const messageCreateHandler = (message: DiscordenoMessage) => {
// Manage and roll using aliases // Manage and roll using aliases
commands.alias(message, argSpaces); commands.alias(message, argSpaces);
break; break;
case 'repeat':
// [[repeat arg
// Enable or Disable unrestricted repeat rolling
commands.toggleRepeat(message, args);
break;
case 'roll': case 'roll':
case 'r': case 'r':
// [[roll or [[r // [[roll or [[r
// Dice rolling commence! // Dice rolling commence!
commands.roll(message, argSpaces, ''); commands.roll(message, argSpaces, '', overrideAuthor);
break; break;
default: default:
// Non-standard commands // Non-standard commands
@ -175,7 +181,7 @@ export const messageCreateHandler = (message: DiscordenoMessage) => {
} else if (command && `${command}${args.join('')}`.includes(config.postfix)) { } else if (command && `${command}${args.join('')}`.includes(config.postfix)) {
// [[roll]] // [[roll]]
// Dice rolling commence! // Dice rolling commence!
commands.roll(message, argSpaces, `${config.prefix}${command}`); commands.roll(message, argSpaces, `${config.prefix}${command}`, overrideAuthor);
} else if (command) { } else if (command) {
// [[emoji or [[emoji-alias // [[emoji or [[emoji-alias
// Check if the unhandled command is an emoji request // Check if the unhandled command is an emoji request

View File

@ -22,6 +22,7 @@ const sendOrInteract = async (
callLocation: string, callLocation: string,
payload: CreateMessage, payload: CreateMessage,
tryGetOriginal = false, tryGetOriginal = false,
mentionUser = true,
): Promise<void | DiscordenoMessage> => { ): Promise<void | DiscordenoMessage> => {
let newMsg; let newMsg;
if (hasOwnProperty(msgOrInt, 'token')) { if (hasOwnProperty(msgOrInt, 'token')) {
@ -32,7 +33,7 @@ const sendOrInteract = async (
}).catch((e: Error) => messageSendError(callLocation, interaction, e)); }).catch((e: Error) => messageSendError(callLocation, interaction, e));
if (tryGetOriginal) newMsg = await getOriginalInteractionResponse(interaction.token); if (tryGetOriginal) newMsg = await getOriginalInteractionResponse(interaction.token);
} else { } else {
newMsg = await msgOrInt.reply(payload).catch((e: Error) => messageSendError(callLocation, msgOrInt, e)); newMsg = await msgOrInt.reply(payload, mentionUser).catch((e: Error) => messageSendError(callLocation, msgOrInt, e));
} }
return newMsg; return newMsg;
}; };